函数(方法)传值,从本质上来说其实是只有一种机制——将实参在栈中生成一个备份,称作"形参"。然后在栈中展开函数,对形参进行操作。函数中所有对形参的更改都不会保留到实参中。比如下面这个常见的例子:
void main()
{
int a=3;
int b=5;
swap(a, b);
cout << "a=" << a << "; b=" << b <<endl;
}
void swap(int var1, int var2)
{
int temp = var1;
var1 = var2;
var2 = temp;
}
在上面的例子中,本意是想通过调用swap函数来更改a,b的值,结果因为函数中只对形参进行操作,当函数调用结束,栈被清空,存储在内存数据段的a,b值没有受到任何影响。
这时,如果对上 面的swap函数进行如下调整:
{
int temp = (*p1);
(*p1) = (*p2);
(*p2) = temp;
}
对应的函数调用也应该改为 swap(&a, &b);再次输出结果就会看出变量a,b的值的确被对调过了。
BUT WHY?
原因很简单,关键就在于更改后的函数签名中的参数已经不再是单纯的int型,而是int型指针——说白了,就是两个内存地址。于是,当swap函数被调用时。变量a和b的地址会各被复制一份用作函数的形参。而在上例的函数体中,进行交换的则是两个地址中所存储的数值。而这一点是相当重要的,因为保存有两个地址的形参仍然会在函数调用结束后从栈中清空,但由于两个地址是数据段的地址,而在形参被清空之前,两个数据段地址实际保存的值已经被交换了(就是变量a,b的地址所保存的值被交换),所以在函数调用结束后可以看出a和b的值被交换过了
由于很多资料对引用传递的机制 表述很含糊,就会读者造成这么一种错觉——如果传给参数的是指针,那么在函数体内操纵的就是实际的指针。因此,下面一种错误就出来了。
{
int *temp = p1;
p1 = p2;
p2 = temp;
}
调用函数的方法依旧为swap(&a, &b);作者本以为函数体内操纵的就是a,b的实际地址,所以预料输出结果应该是正确的。但实际上,它在swap函数内操作的仍旧为a, b地址在栈中的备份。
现在来看看小wing在去年八月所犯下的一个错误:我想要写一个函数来生成一个单链表的头节点,所以写了如下方法(之前已经定义了节点 Node——是一个结构体):
{
if (pHead)
{
return 0;
}
else
{
pHead=(Node*)malloc(sizeof (Node));
// Assign the value to the members of Node
... ...
return 1;
}
}
当我调用该函数时,我首先让头节点指针pHead为NULL,之后通过int flag = GetFirstNode(pHead)的方式来调用函数。满以为调用结束后,pHead会指向新生成的节点,结果一跟踪却 发现pHead仍旧为 NULL。
当然,现在来看这段代码很容易就能发现问题的所在(这是一段比较危险的代码):由于pHead一开始为空,调用函数时,pHead的形参自然也是为空,之后的代码中却改变了 pHead的值(是个地址)。但是调用结束后,形参被销毁,pHead没有变化。事实上,这段代码中,pHead 没有正确指向预期的地址倒是小时,关键问题是在函数体中调用了malloc在堆上申请了空间后却没有任何指针可以控制它,更不用说去释放这段内存空间。这便造成了烦人的内存泄漏。
实际上,这段代 码可以进行如下修正就可以解决这个问题:
{
if ((*pHead))
{
return 0;
}
else
{
(*pHead)=(Node*)malloc(sizeof (Node));
// Assign the value to the members of Node
... ...
return 1;
}
}
实际调用时,假设一开始声明仍旧是Node * pHead = NULL;那么就可以利用 GetFirstNode(&pHead)来让pHead指向新生成的节点。因为指向pHead的指针(即双重指针)在函数体中没有发生变化。
SO...
到现在,我们就可以得出这样一个结论——值传递 和引用传递在本质上没有区别,都是利用函数的形参。关键在于,如果在函数体中想要改变一个外部变量(可能是个基本数据类型变量,也有可能是一个指针变量)的值,并且希望这种改变能够在函数调用完成之后仍旧被保持。那么,就请将该外部变量的地址作为函数的参数传进去。
另外,由于C#的托管代码中没有指针和取址符&amp;所以C#提供了两个特有的关键字ref和out,前者更像C/C++中的取址符 &,而后者则更能应付在调用函数前没有经过初始化的变量。而C#中需要记住的另一个原则就是,任何class类型的变量其本质都是一个指针——即变量所保存的值其实是一个指向该class实例的地址。