在Part 1但中,我们简单介绍了堆栈的功能以及值类型、引用类型在堆栈中的存储位置的问题,也简单介绍了指针是虾米。让我们沿着革命的步伐继续前进!
我们的代码执行的时候,底层到底有哪些内幕交易在发生呢?当我们调用一个方法时:
代码又来了:
public int AddFive(int pValue)
{
int result;
result = pValue + 5;
return result;
}
这时我们的栈看起来这酱紫滴:
就像前面说过的那样,参数如果是值类型,内容会被完整的复制过来。如果是引用类型,被复制的则只是指向堆里头实例的一个指针。
首先,当我们传递一个值类型的时候,栈顶分配相应大小的空间,并且把值完整复制过去。例如:
class Class1
{
public void Go()
{
int x = 5;
AddFive(x);
Console.WriteLine(x.ToString());
}
public int AddFive(int pValue)
{
pValue += 5;
return pValue;
}
}
当方法被执行时,会给x分配空间,并且值5被赋值到分配好的空间里。
接下来,AddFive被压栈,同时其参数也会压栈(分配空间),然后参数的值会从x,一个字节一个字节的复制过来。
当AddFive结束执行,线程转回到Go方法中。因为AddFive已经没有利用价值了,因此它的参数pValue也跟着没用了,“删之”。
所以我们代码的输出应该是5,对不?重点是任何值类型用这种方式传递进方法都是传递的其一个复制本,同时我们希望原先的变量里的值保持不变。
但是要记住的是,如果我们的值类型是个相当大(占用空间)的东东,例如一个很大的struct,那么对其进行复制操作将是非常耗费时间的。同时栈空间并不是无限大的,就像瓶子从水龙头接水,总是会溢出的哦!struct经常可能会变得很大,因此要小心使用。多大才算大?这就是个大大大的struct咯(有那么大嘛……*(*&@#¥%):
public struct MyStruct
{
long a, b, c, d, e, f, g, h, i, j, k, l, m;
}
看看使用这个struct时发生了虾米:
public void Go()
{
MyStruct x = new MyStruct();
DoSomething(x);
}
public void DoSomething(MyStruct pValue)
{
// DO SOMETHING HERE....
}
这可真是效率低下啊。想像一下,如果有数千个对这个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 MyStruct pValue)
{
// DO SOMETHING HERE....
}
这样我们就能避免无效率的分配内存了。
现在我们要小心的问题变成了,如果使用引用传递参数,那么我们操作的就是原来值类型中的值了。也就是说,如果我们改变pValue的值,那么x也跟着变了。看看以下的代码,我们得到的结果将是12345,因为pValue实际上指向的就是x的内容。
public void Go()
{
MyStruct x = new MyStruct();
x.a = 5;
DoSomething(ref x);
Console.WriteLine(x.a.ToString());
}
public void DoSomething(ref MyStruct pValue)
{
pValue.a = 12345;
}
传递引用类型跟使用引用传递值类型其实基本上差球不多。
如果我们在其中使用值类型:
public class MyInt
{
public int MyValue;
}
并且调用Go方法,MyInt存在于堆上因为其是引用类型:
public void Go()
{
MyInt x = new MyInt();
}
如果我们把Go方法改成:
public void Go()
{
MyInt x = new MyInt();
x.MyValue = 2;
DoSomething(x);
Console.WriteLine(x.MyValue.ToString());
}
public void DoSomething(MyInt pValue)
{
pValue.MyValue = 12345;
}
这时情形如下图:
这样就能解释为什么我们改变作为引用类型属性的值类型MyValue时,结果得到的是改变以后的12345了。
有意思的是,当我们使用引用传递一个引用类型的时候,会发生什么呢?
试试吧。假设有下列引用类型:
public class Thing
{
}
public class Animal:Thing
{
public int Weight;
}
public class Vegetable:Thing
{
public int Length;
}
定义Go方法如下:
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变成了Vegetable。
x is Animal : False
x is Vegetable : True
看看都怎么回事:
如果我们不通过引用传递,那么我们将得到相反的结果,x仍然是Animal。(为什么?可以自个儿去画个图哈)