C#的引用类型与数组探究

定义

引用类型1

C# 中有两种类型:引用类型和值类型。 引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。 对于引用类型,两种变量可引用同一对象;因此,对一个变量执行的操作会影响另一个变量所引用的对象。 对于值类型,每个变量都具有其自己的数据副本,对一个变量执行的操作不会影响另一个变量。

数组2

数组是一种数据结构,其中包含许多通过计算索引访问的变量。 数组中的变量(亦称为数组的元素)均为同一种类型,我们将这种类型称为数组的元素类型。
数组类型是引用类型,声明数组变量只是为引用数组实例预留空间。 实际的数组实例是在运行时使用 new 运算符动态创建而成。 new 运算指定了新数组实例的长度,然后在此实例的生存期内固定使用这个长度。 数组元素的索引介于 0 到 Length - 1 之间。 new 运算符自动将数组元素初始化为其默认值(例如,所有数值类型的默认值为 0,所有引用类型的默认值为 null)。

探索

引用类型

引用C++的指针思想,C#的所有引用类型声明后,只是一个像指针一样的东西,指向堆的一片内存;而C#的回收机制会在没有指针(引用类型)指向某块内存时,将那块内存释放
可以用代码测试:

public class Test		//要用引用类型的class而不是值类型的struct
{
	public int a = 0;
	public Test()
	{
		System.Console.WriteLine("new");
	}
	public static void Main()
	{
		//第一部分
		Test a = new Test(){ a = 1 };
		Test b = a;		//不用new,直接指向a的内存
		Test c = new Test(){ a = 1 };		//不同内存但是数值一样
		if(b == a)
			System.Console.WriteLine("b == a");
		if(c == a)
			System.Console.WriteLine("c == a");
		//第二部分
		b.a = 2;
		System.Console.WriteLine(a.a);
	}
}

结果是:

new
new
b == a
2

显然,b并没有实例化,只是浅拷贝了a
b和a是同一个对象
改变b的值,a也跟着改变(因为他们是同一个对象)

数组

这里主要指普通的数组,而不是List泛型
同样,数组也可以用指针理解,但用盘子理解也许更形象:
数组
蓝色是大盘子,装着小盘子,也就是数组,类型是Type[]
红色是小盘子,就是普通的引用类型,类型是Type
黑色是对象,但有可能有红盘子没有对象,也可能多个红盘子托着同一个对象
实例化的时候要循序渐进:先实例化数组,再实例化每个元素
下面是程序验证(为了清楚拆分了定义和实例化,但这不是好风格):

public class Test		//要用引用类型的class而不是值类型的struct
{
	public int a = 0;
	public Test()
	{
		System.Console.WriteLine("new");
	}
	public static void Main()
	{
		//第一部分
		Test[] a;		//定义一个数组,此时直接 a[0] = new Test() 会报错null
		a = new Test[3];		//实例化数组,此时直接 a[0].a = 1 会报错null
		a[0] = new Test();		//实例化元素
		a[1] = new Test();
		a[0].a = 1;
		a[1].a = 2;
		a[2] = new Test();		//可以需要用的时候再实例化,节省空间
		a[2].a = 3;
		//第二部分
		Test[] b = null;
		b[0] = a[0];		//报错,b[0]是null
		Test[] c = new Test[3];
	}
}

结果是:

new
new
new

如果加断点可以发现,new Test[3]时候控制台没有出现new,而new Test()时才出现,说明了数组和元素的实例化是两个不同的步骤

补充

...
var a = new Test[3] { new Test() { a = 1 }, new Test() { a = 1 }, new Test() { a = 1 } };
//最省行数的初始化方法,但建议有条件可以把{ a = 1 }写在构造函数参数列表里
var b = a;
...

说明除了int、char还有enum、struct等,一切类型皆是引用

public class Test		//要用引用类型的class而不是值类型的struct
{
	public int a = 0;
}
public class MainClass
{
	public readonly int a = 0;
	public readonly Test b = new Test();
 	public static void Main()
	{
		a = 1;		//错误,无法对只读类型赋值
		b = new Test();		//错误,无法对只读类型赋值
		b.a = 3;		//正确
	}
}

这里用指针理解就非常轻松了,值类型就是不能改变值的指针,引用类型可以改变
但是引入了 readonly 后,便不能改变这个变量的值,对于值类型来说这个数字就不能改变了,对于引用类型来说他的对象就不能改变了,但是对象里的值还可以改变在这里插入图片描述
如果想让Test a的成员值不能修改,只需要在Test中成员变量a前加一个readonly

随笔

如果把C++(左)和C#(右)部分关键字做一个对比,大概有下面几个类似的:

  • auto ≈ var
  • const ≈ readonly
  • constexpr ≈ const
  • &(参数列表中) ≈ ref
  • …(参数列表中) ≈ params

其实还是有一些微妙的区别,例如readonly不能修饰局部变量,而const可以……但这些瑕疵在编程时编译器都会提醒你,所以没有必要深究


  1. 引用类型(C# 参考) 出自 Microsoft Docs ↩︎

  2. 数组 出自 Microsoft Docs ↩︎

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值