练习 - 发现引用类型
- 8 分钟
引用类型包括数组、类和字符串。 关于应用程序执行时存储值的方式,引用类型与值类型的处理方式不同。
在此练习中了解引用类型与值类型的不同之处,以及如何使用 new 运算符将变量与计算机内存中的值相关联。
引用类型与值类型有何不同
值类型变量将其值直接存储在名为堆栈的存储区域中。 堆栈是为 CPU 上当前运行的代码分配的内存(也称为堆栈帧或激活帧)。 堆栈帧执行完毕后,堆栈中的值将被删除。
引用类型变量将其值存储在名为堆的单独内存区域中。 堆是一个内存区域,由操作系统上运行的多个应用程序同时共享。 .NET 运行时与操作系统进行通信以确定可用的内存地址,并请求可存储该值的地址。 .NET 运行时会存储值,然后将内存地址返回给变量。 当代码使用变量时,.NET 运行时会无缝查找变量中存储的地址,并检索其中存储的值。
接下来你将编写一些代码来更清楚地说明这些概念。
定义引用类型变量
删除或使用行注释运算符
//注释掉前面步骤中的所有代码。在 Visual Studio Code 编辑器中更新代码,如下所示:
int[] data;前面的代码定义可存储
int数组类型值的变量。此时,
data仅仅是一个变量,可以存储引用,或者说堆中某个值的内存地址。 由于它不指向内存地址,因此称为空引用。使用
int关键字创建new数组的实例使用以下代码在 Visual Studio Code 编辑器中更新代码,以创建和分配
int数组的新实例:int[] data; data = new int[3];new关键字指示 .NET 运行时创建int数组的实例,然后与操作系统协调以将大小为三个 int 值的数组存储在内存中。 .NET 运行时进行编译并返回新的int阵列的内存地址。 最后,内存地址存储在变量数据中。int阵列的元素默认为值0,因此它是int的默认值。修改代码示例,以一行代码执行两项操作
上一步中的两行代码通常会缩短为一行代码,以同时声明变量并创建
int数组的新实例。 请将步骤 3 中的代码修改为以下代码。int[] data = new int[3];虽然没有输出可供查看,但好的是此练习更清楚地说明了 C# 语法与使用引用类型的过程步骤之间的关系。
C# 字符串数据类型有何不同?
string 数据类型也是引用类型。 你可能想知道在声明字符串时不使用 new 运算符的原因。 这仅仅是因为 C# 设计者想要提供一种便利。 由于 string 数据类型的使用频率很高,因此可以使用以下格式:
string shortenedString = "Hello World!";
Console.WriteLine(shortenedString);
但在后台,系统会创建 System.String 的新实例,并将其初始化为“Hello World!”。
使用值和引用类型的实际问题
- 值类型(整数):在此示例中,
val_A和val_B是整数值类型。
int val_A = 2;
int val_B = val_A;
val_B = 5;
Console.WriteLine("--Value Types--");
Console.WriteLine($"val_A: {val_A}");
Console.WriteLine($"val_B: {val_B}");
应会看到以下输出:
--Value Types--
val_A: 2
val_B: 5
执行 val_B = val_A 时,将复制 val_A 的值并将其存储在 val_B 中。 因此,val_B 更改后,val_A 将不受影响。
- 引用类型(数组):在此示例中,
ref_A和ref_B是数组引用类型。
int[] ref_A= new int[1];
ref_A[0] = 2;
int[] ref_B = ref_A;
ref_B[0] = 5;
Console.WriteLine("--Reference Types--");
Console.WriteLine($"ref_A[0]: {ref_A[0]}");
Console.WriteLine($"ref_B[0]: {ref_B[0]}");
应会看到以下输出:
--Reference Types--
ref_A[0]: 5
ref_B[0]: 5
执行 ref_B = ref_A 时,ref_B 将指向与 ref_A 相同的内存位置。 因此,ref_B[0] 更改后,ref_A[0] 也会更改,因为它们都指向相同的内存位置。 这是值类型和引用类型之间的主要区别。
概括
- 值类型可以存储较小的值,并存储在堆栈中。 引用类型可以存储大额的值,引用类型的新实例使用
new操作符创建。 引用类型变量存储堆中存储的实际值的引用(内存地址)。 - 引用类型包括数组、字符串和类。