浅谈值类型和引用类型在堆和栈中的存储二
前一篇我们浅谈了“堆”和“栈”,这篇文章我们主要谈一下值类型和引用类型在作为参数传递时候,有什么不同。
主要分为两种情况:
1.传递值类型(Passing Value Types)
2.传递引用类型(Passing Reference Types)
首先我们来看一下第一种情况,传递值类型(Passing Value Types):
public void Go() { var x = 5; AddFive(x); Console.WriteLine(x.ToString()); } public int AddFive(int pValue) { pValue += 5; return pValue; }
上一篇文章我们已经讨论过关于值类型存储的问题,所以这里不再过多叙述。
我们都知道,值类型使用的就是数据本身,所以5会被拷贝到pValue。所以最终x的值还是不会改变。
需要谨记的是,如果我们将一个非常大的值类型变量(如一个很大的struct)复制到“栈“上,这对内存空间和处理器处理周期的消耗是很大的,因为”栈“空间并非无限的。
如何解决这种效率低的问题呢?先别慌,让我们来看一个例子。
public void Go() { MyStruct x = new MyStruct(); DoSomething(ref x); } public struct MyStruct { long a, b, c, d, e, f, g, h, i, j, k, l, m; } public void DoSomething(ref pValue) { // DO SOMETHING HERE.... }
看到没有,DoSomething()方法有一个ref关键字。这就引入了我们在传递值类型的另外一种情况,值类型按照引用传递。
看到没有,这个时候的pValue就是一个指向x的一个地址指针。
需要小心的是,当我们用引用的方法传递一个值参数时,我们访问的是源参数本身。任何对pValue的改变都是对x的改变。下面这段代码,会使x.a改变为12345。
public void Go() { MyStruct x = new MyStruct(); x.a = 5; DoSomething(ref x); Console.WriteLine(x.a.ToString()); } public voidDoSomething(ref MyStructpValue) { pValue.a = 12345; }
接下来我们来看看另外一种情况,传递引用类型(Passing Reference Types):
public class MyInt { public int MyValue; } public void Go() { var x = new MyInt(); x.MyValue = 2; DoSomething(x); Console.WriteLine(x.MyValue.ToString()); } public void DoSomething(MyInt pValue) { pValue.MyValue = 12345; }
最终会输出12345,发现结果已经被改变。将x的值(位于“堆“上的 MyInt 对象的地址)拷贝至pValue,使x和pValue同时指向MyInt对象的地址。
既然值类型可以使用ref关键字,那么引用类型在使用ref关键字的情况下,又是怎么一回事呢?好的,我们同样来看一个例子:
public class Thing { } public class Animal:Thing { public int Weight; } public class Vegetable:Thing { public int Length; } public void Go() { Thing x = new Animal(); Switcharoo(ref x); Console.WriteLine("x is Animal:"+ (x is Animal).ToString()); Console.WriteLine("x is Vegetable:" + (x is Vegetable).ToString()); } public void Switcharoo(ref Thing pValue) { pValue = new Vegetable(); }
最后程序执行的结果是:
x is Animal:False
x is Vegetable:True
pValue这次是指向x的,然后通过改变x的地址,使得pValue也发生改变。
好了,暂时就介绍到这里,如果有什么疑问,或者有误的地方,欢迎大家指点和交流。