C#学习入门(七)——值类型和引用类型的使用

本文详细解析.NET中值类型和引用类型的内存布局,涵盖栈内存和堆内存的区别,以及方法参数的不同种类(值参数、引用参数、输出参数、可变参数和可选参数)。还介绍了字符串池机制、常量与枚举结构,以及装箱与拆箱的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.值类型和引用类型

1.1堆内存和栈内存

 在.NET中,程序在存储空间中都是以栈(Stack)和堆(Heap)两种机制来管理的。

1.1.1 栈空间

 栈是一种先进后出,后进先出的数据结构。而用于存储、管理栈的空间称为栈空间。
在这里插入图片描述

1.1.2 堆空间

 相对于栈来说,堆的存储没有什么限制,堆的空间是取决于虚拟内存,也就是说虚拟内存中的任何空间都是堆管理的部分。
在这里插入图片描述

1.2值类型和引用类型的定义

1.2.1 值类型

值类型的变量直接包含其数据,分配在线程栈上。
值类型

1.2.2 引用类型

引用类型的数据在线程栈上存储其对象的引用,也就是内存地址;而对象的实例则分配到托管堆中。
在这里插入图片描述
栈的内存分配是自动释放(调用子程序时进栈,子程序返回时出栈),而堆在.NET是过垃圾回收来释放。

1.3.NET的通用类型系统

 C#的基本数据类型都以平台无关的方式来定义,也就是.NET的语言都是通用的。C#的预定义类型并没有内置与C#语言中,而是内置于.NET Framework中。
 CTS(通用类型系统)定义了在中间语言(IL)中使用的预定义数据类型,所有面向.NET的语言最终都会翻译 为IL。

在这里插入图片描述

1.4.NET数据类型

在这里插入图片描述

2.方法的参数类型

2.1 值参数

static void Main(string[] args)
{
    int x = 3, y = 4;
    Console.WriteLine("x = {0}, y = {1}",x, y);
    Console.WriteLine("交换后:");
    Change(x, y);
    Console.WriteLine("x = {0}, y = {1}", x, y);
}

public static void Change(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

 比如一个简单的交换函数,值类型的参数,传递进去后,实际上值是不会改变的。

在这里插入图片描述
 因为传递进值进去后,系统会定义一个新的函数变量,将传入的值赋值给新的变量,然后交换的其实是新的变量的值,而新的变量在函数结束后就消失了。在C中则需要传递指针变量,传递地址则修改其值;而在C中,可以使用引用参数,虽然实际上也是地址。

2.2 引用参数

static void Main(string[] args)
{
    int x = 3, y = 4;
    Console.WriteLine("x = {0}, y = {1}",x, y);
    Console.WriteLine("交换后:");
    Change(ref x, ref y);
    Console.WriteLine("x = {0}, y = {1}", x, y);
}

public static void Change(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

在这里插入图片描述
 引用参数以ref修饰符声明,传递的形参实际上是指针,所以再方法中的操作实际上是对传入的变量进行操作,从而修改其值。

2.3 输出参数

在C#中可以以out修饰符,输出多个参数。

static void Main(string[] args)
{
    int a = 3, b = 4, max, min;
    Max(a, b, out max, out min);
    Console.WriteLine("max={0},min={1}",max, min);
}

public static void Max(int a, int b, out int max, out int min)
{
    if (a > b)
    {
        max = a;
        min = b;
    }
    else
    {
        max = b;
        min = a;
    }
}

 比如上面的Max最值函数,通过对比后输出max和min,通过out输出参数可以轻松得将数据传递出去。

2.4 数目可变参数

static void Main(string[] args)
{
    Data(1, 2, 4);
}

public static void Data(params object[] num)
{
    for (int i = 0; i < num.Length; i++)
    {
        Console.WriteLine(num[i]);
    }
}

 可以使用params修饰符来声明输入的形参为数目可变参数,也就是数组参数。用来处理不同或者相同的数据,接收输入参数不确定,会变化的情况。

2.5 可选参数(默认值)

static void Main(string[] args)
{
    Fun(20);
}

public static void Fun(int a, int b = 10)
{
    Console.WriteLine("a = {0}",a);
    Console.WriteLine("b = {0}",b);
}

 可以在形参的位置加上默认值,从而调用时如果没有参数,默认以形参的默认值为形参的值。当然,需要满足从右到左的原则,也就是没有默认值的左边不能有默认值,因为参数传递是从左到右传递的。

2.6 命名参数

static void Main(string[] args)
{
    Fun(b:30);
}

public static void Fun(int a = 10, int b = 20)
{
    Console.WriteLine("a = {0}",a);
    Console.WriteLine("b = {0}",b);
}

 利用命名参数,能够为特定形参指定实参,方法的调用者将不再需要记住或查找形参在所调用方法的形参列表中的顺序,可以按形参名称指定每个实参的形参。调用方法时,在实参列表中,写上形参名,冒号后写上实参值。

3.相等判断

值类型比较:值类型各自包含的值是否相等。
引用类型比较:两者是否指向同一对象,即判断两者引用的地址是否相同,如果两者指向不同对象,此时判断这两个对象的值是否相同

3.1 静态ReferenceEquals()方法

static void Main(string[] args)
{
    string str1 = "name";
    string str2 = "name";
    Console.WriteLine(ReferenceEquals(str1, str2));
}

3.2 ==运算符

string str1 = "name";
string str2 = "name";
Console.WriteLine(str1 == str2);

对于string,已经重载了==运算符,此时比较的是两个字符串的字面量是否相等,而不是比较两个字符串的地址是否相等。

3.3 Equals()方法

string str1 = "name";
string str2 = "name";
Console.WriteLine(str1.Equals(str2));	//实例Equals()方法
Console.WriteLine(Equals(str1, str2));	//静态Equals()方法

PS:如果是两个自定义的类的对象,如果地址不同,那么判断返回的结果是false.对于string,已经重载了==运算符,此时比较的是两个字符串的字面量是否相等,而不是比较两个字符串的地址是否相等。

4.字符串池机制

 字符串池技术:编译器将源代码一个多次出现的字符串合并为一个实例方式,可以极大减少生成文件的大小。

在这里插入图片描述
 程序中对同一个字符串多次修改(如改成’Hello’),要产生临时大量的临时字符串对象,极大程度降低系统的性能。使用.NET的字符串池机制可以解决问题。
 公共语言运行时(Common Language Runtime,CLR)启动时,会在内部创建一个容器,容器的键是字符串内容,而值是字符串在堆上的的引用。当一个新的字符串对象需要分配时,CLR首先检测容器内是否已包含了该字符串的对象, 如果已经包含,则直接返回已经存在字符串对象的引用,如果不存在,则新分配一个字符串对象,同时把其添加到容器中。但是,当程序使用new关键字显式创建一个字符串对象时,该机制将不起作用。
在这里插入图片描述

5.常量、枚举和结构

5.1 常量

(1)直接常量
直接常量是指把数据值直接写在程序代码中。例如,18、True、‘A’、“ABC”、null等,表达式y=3*x+2中的3和2。在编译时,源代码中的常量直接替换成中间语言(IL)代码。
(2)命名常量
使用命名常量可以为特殊值提供有意义的名称,而不是数字或文本。在C#中定义命名常量的方式有两种,一种叫做编译时常量(Compile-time constant),另一种叫做运行时常量(Runtime constant)。命名常量可以被当作常规的变量,只是它们的值在定义后不能被修改。

5.1.1 编译时常量

const 数据类型 常量名 = 表达式;

 表达式必须是一个可以隐式转换为目标类型的常量表达式。常数表达式是在编译时可被完全计算的表达式。因此,对于引用类型的常数,可能的值只能是string和null引用。

仅C#内置类型可以声明为常量(不包括object)
在这里插入图片描述

5.1.2 运行时常量

使用readonly修饰符创建在运行时一次性(例如在构造函数中)初始化的类、结构或数组,此后不能更改。

//readonly 数据类型 常量名 = 值;

class Age //定义类
{
    readonly int  Year;//定义常量
    Age(int year) //构造函数
    { 
        //由于构造函数在创建对象时只运行一次,所以可以一次性赋值
        Year = year;//给常量赋初值
    }
    void ChangeYear()
    {
        //Year = 1993; // 如果取消注释,则编译错误
    }
}

5.2 枚举

枚举类型是一种用户定义的数据类型 ,是一组命名的整型常量。

访问修饰符 enum 枚举名 : 整型基础类型
{
    成员1, 成员2,}

如果成员没有赋值,则默认从0开始,并依次类推+1.

enum Day { Sat=6 , Sun, Mon =1, Tue, Wed, Thu, Fri };
//成员的值分别为:6、7、1、2、3、4、5

5.3 结构

访问修饰符 struct 结构名
{
    结构成员1;
       ...
    结构成员n;
}

6.装箱和拆箱

6.1 装箱

装箱是指把一个值类型的数据隐式地转换为一个Object类型的数据。把一个值类型装箱就是创建一个Object类型的实例,并把该值类型的值复制给这个Object实例。
在这里插入图片描述

6.2 拆箱

拆箱是把一个引用类型的数据显式地转换成一个值类型数据。
在这里插入图片描述
程序中尽量减少不必要的装箱和拆箱,否则降低程序性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暗夜无风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值