在探讨本文的主题之前,先来介绍下C#中的值类型和引用类型 众所周知C#中有值类型和引用类型,值类型有基础数据类型(诸如int,double,bool等)、结构体、枚举,引用类型有接口、类、委托。 值类型全部在操作系统的栈空间中申请,而引用类型则在操作系统的堆空间中建立对象,然后在栈空间中申请一个指针指向这个对象的地址。 因此C#的引用类型其实就如同C++的指针类型。
下面我再来看看函数传参的问题。 早在C时代就有函数参数传值和传地址的概念,请记住在C#中函数参数默认都是传值。
所以不管是值类型还是引用类型在作为参数传进函数时,其实都是传的值,只不过引用类型传的是对象在堆中的的地址罢了。 而且从上面的定义可以看出C#中引用类型的变量用C++来说就相当于是该引用类型的指针,比如有类(引用类型)RefClass: RefClass rc就相当于是C++上的RefClass *rc 在C#中使用rc.IntValue++;时,相当于C++的rc->IntValue++;
因为引用类型在函数传参时是传地址的,所以我脑袋里就形成了一种惯性思维,认为只要传进函数的是引用类型,那么在函数中做的任何更改都会反映到实参上。但是我发现并不完全是这样,下面给出个例子(注释内容为对应等效的C++代码): using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace RefWarn { class RefClass { public int IntValue { get; set; } } class Program { static void AddValue(RefClass prc)//RefClass *prc,prc和传进来的rc指向同一个RefClass对象的地址 { prc.IntValue++; //prc->IntValue++; } static void AddValue(ref RefClass prc)//RefClass **prc,prc指向传进来的rc的地址 { prc.IntValue++;//(*prc)->IntValue++; } static void ChangeRef(RefClass prc)//RefClass *prc,prc和传进来的rc指向同一个RefClass对象的地址 { prc = new RefClass() { IntValue = 1000 }; //prc=new RefClass() { IntValue = 1000 };请注意new关键字在C++中创建的是指向对象的指针不是对象 } static void ChangeRef(ref RefClass prc)//RefClass **prc,prc指向传进来的rc的地址 { prc = new RefClass() { IntValue = 1000 }; //*prc=new RefClass() { IntValue = 1000 };请注意new关键字在C++中创建的是指向对象的指针不是对象 } static void Main(string[] args) { RefClass rc = new RefClass() { IntValue = 1 };//RefClass *rc=new RefClass() { IntValue = 1 };请注意new关键字在C++中创建的是指向对象的指针不是对象 AddValue(rc);//rc,传递指向RefClass对象的指针 Console.WriteLine("调用AddValue(rc)后IntValue为:" + rc.IntValue); rc.IntValue = 1; AddValue(ref rc);//&rc,传递指向RefClass对象指针的指针 Console.WriteLine("调用AddValue(ref rc)后IntValue为:" + rc.IntValue); rc.IntValue = 1; ChangeRef(rc);//rc,传递指向RefClass对象的指针 Console.WriteLine("调用ChangeRef(rc)后IntValue为:" + rc.IntValue); rc.IntValue = 1; ChangeRef(ref rc);//&rc,传递指向RefClass对象指针的指针 Console.WriteLine("调用ChangeRef(ref rc)后IntValue为:" + rc.IntValue); } } } 你会发现在Main函数中调用ChangeRef(rc)后,rc并没有发生改变,其属性IntValue的值还是1。 这是为什么?我们先来看看static void ChangeRef(RefClass prc)函数的结构,看看里面都做了什么 static void ChangeRef(RefClass prc)//RefClass *prc,prc和传进来的rc指向同一个RefClass对象的地址 可以看到函数里就是对RefClass 类型的形参引用变量prc重新赋了值。但是最后我们看到这个赋值并没有反应到实参引用变量rc上。原因其实很简单就像本文开始所说的一样,由于实参变量pc和形参变量rpc都是引用类型的变量,那么它们实际上是在操作系统栈空间上的两个指针,只不过指向的是操作系统堆空间上的同一个RefClass 对象。在函数ChangeRef中对引用变量rpc重新赋值,相当于是将栈中的rpc指针重新指向了堆中的另一个RefClass 对象。形参变量rpc指向的地址改变后,并不会对实参变量pc的指向发生改变,所以pc还是指向函数ChangeRef(RefClass prc)调用前的那个RefClass 对象。
但是也许你又会问为什么AddValue(rc)执行后,函数对rc做了更改呢?我们来看看AddValue(RefClass prc)函数 static void AddValue(RefClass prc)//RefClass *prc,prc和传进来的rc指向同一个RefClass对象的地址 请注意函数AddValue并不是更改了实参引用变量rc,它更改的是rc指向的RefClass 对象的属性,是因为实参变量pc和形参变量rpc都指向同一个RefClass 对象的原理,所以在AddValue里面rpc更改了它所指向RefClass 对象的属性,也就等于更改了pc指向RefClass 对象的属性。所以才在执行AddValue(rc)后给人一种好像rpc和pc是同一个变量,更改了rpc就等于更改了pc的错觉。但是请记住这是绝对错误的,rpc和pc是两个完全不同的引用变量,只不过指向的是内存中的同一个RefClass 对象。
最后我们来探讨下有没有办法使函数在传递引用类型的参数时,让形参完全等于实参呢?能否做到不管对形参是重新赋值还是做更改,都反映到实参上? 答案是肯定就是使用ref关键字 这个关键字用在值类型上的时候,就相当于C++的指针类型,比如: ref int param 就相当于C++的 int *param 且该指针指向的就是其对应的实参变量 所以在C#中使用声明为ref的int形参变量param.ToString()时候,相当于C++上使用int指针*param.ToString() 所以在使用声明为ref的int形参param时,就相当于是C++上的*param,其操作的就是param指向的那个int变量,即实参。
而当这个关键字用在引用类型前面的时候,就相当于是指向引用类型变量的地址,而前面说过C#引用类型的变量就相当于是C++的指针,那么指向引用类型变量的地址也相当于就是指向指针的指针。 因为前面说了RefClass rc相当于C++的RefClass *rc 那么ref RefClass rc相当于C++的RefClass **rc 在C#中使用声明为ref的RefClass变量rc.ToString()时,当于C++上上使用RefClass指针的指针*rc->ToString() 所以在使用声明为ref的RefClass类型形参rc时,就相当于是C++上的*rc(注意*rc还是指针,因为rc是指向指针的指针),其操作的是形参rc指向的那个RefClass类型的引用变量(即rc指向的是实参变量的地址,而不实参变量指向堆空间中对象的地址),即实参。
而实参前面的ref相当于是C++的&符号即取该变量的地址。
所以在函数形参前加上ref那么形参变量指向的就是实参变量的地址,只不过如果实参类型是值类型,那么形参变量指向的就是该实参变量在操作系统栈中的地址。如果实参是引用类型,那么形参变量指向的也是实参变量在操作系统栈中的地址,只不过该实参变量又指向对象在操作系统堆中的地址。所以无论是引用类型还是值类型,只要在其作为形参时在前面加上ref,那么形参变量都是指向实参变量的指针,则操作形参变量就等于是在操作实参变量。
最后一定要清楚在引用类型做函数形参时,加上ref和不加ref的不同。 还是拿RefClass rc来举例:
|
|
来自: herowuking > 《C#》