C# 值类型和引用类型
概念上的直观区别:
- 值类型:值类型直接存储其值
- 引用类型:引用类型存储对值的引用
存储位置的区别: - 值类型:堆栈
- 引用类型:托管堆
注意区分某个类型是值类型还是引用类型,因为这种存储位置的不同会有不同的影响。下面看两个例子:
i=20;
j=i;
int是值类型,这表示上面的语句会在内存的两个地方存储值20。
第二个例子,这段代码假定已经定义了一个类Vector,Vector是一个引用类型,它有一个int类型的成员变量Value:
Vector x,y;
x = new Vector();
x.Value = 30;
y = x;
Conso.WriteLine(y.Value);
y.Value=50;
Conso.WriteLine(x.Value);
要理解的重要一点是在执行这段代码后,只有一个Vector对象。x和 y都指向包含该对象的内存位置。因为x和 y是引用类型的变量,声明这两个变量只保留了一个引用,而不会实例化给定类型的对象。两种情况下都不会真正创建对象。要创建对象,就必须使用new关键字,如上所示。因为x和y引用同一个对象,所以对x的修改会影响y,反之亦然。因此上面的代码会显示30和50。
如果变量是一个引用,就可以把其值设置为null,表示它不引用任何对象:
y = null;
如果将引用设置为null,显然就不可能对它调用任何非静态的成员函数或字段,这么做会在运行期间抛出一个异常。
有点特别的 string 类型
string类型是C#预定义的引用类型。但是 , string与引用类型在常见的操作上有一些区别。例如,字符串是不可改变的。修改其中一个字符串 , 就会创建一个全新的string对象,而另一个字符串不发生任何变化。考虑下面的代码
namespace C1
{
class Program
{
public static int Main()
{
string s1 = "a string";
string s2 = s1;
Console.WriteLine("s1:" + s1);
Console.WriteLine("s2:" + s2);
s1 = "another string";
Console.WriteLine("s1 after:" + s1);
Console.WriteLine("s2 after:" + s2);
Console.ReadLine();
return 0;
}
}
}
其输出结果为:
s1:a string
s2:a string
s1 after:another string
s2 after:a string
改变s1的值对s2没有影响,这与我们期待的引用类型正好相反。当用值"a string"初始化s1时 , 就在堆上分配了一个新的string对象。在初始化s2时,引用也指向这个对象,所以s2的值也是"a string"。 但是当改变s1的值时,并不会替换原来的值,堆上会为新值分配一个新对象。s2变量仍指向原来的对象,所以它的值没有改变。这实际上是运算符重载的结果。
ref关键字
namespace C1
{
class Program
{
static void SomeFunction(int[] inst, int i)
{
inst[0] = 100;
i = 100;
}
static void RefFunction(ref int i)
{
i = 80;
}
public static int Main()
{
int i = 0;
int[] inst={0, 3, 4, 6, 2, 7, 8};
///显示初始值
Console.WriteLine("i= " + i); //输出值为0
Console.WriteLine("inst[0]= " + inst[0]); //输出值为0
///调用SomeFunction()方法
SomeFunction(inst,i);
Console.WriteLine("i after= " + i); //输出值为0
Console.WriteLine("inst[0]= " + inst[0]); //输出值为100
/*在变量通过引用传递给方法时,被调用的方法得到的就是这个变量,所以
*在方法内部对变量进行的任何改变在方法退出后仍旧有效。而如果变量
*通过值传送给方法,被调用的方法得到的是变量的一个相同副本,也就是
*说,在方法退出后,对变量进行的修改会丢失。对于复杂的数据类型,按引
*用传递的效率更高,因为在按值传递时,必须复制大量的数据 */
//调用RefFunction()方法
RefFunction(ref i);
Console.WriteLine("i after= " + i); //输出值为80
Console.ReadLine();
return 0;
/*ref关键字使值参数通过引用传送给方法*/
}
}
}
注:学习内容来源于清华大学出版社的《C#高级编程》(第七版)