指针参数传递和.Net中String类型的表现
本来不想对这两个老掉牙的问题进行讨论,但联系起来看确实有用。先看代码:
class Program { static void Foo( string s ) { s = "变了"; } static void Main( string[] args ) { string s = "原始String"; Foo( s ); Console.WriteLine( s ); Console.ReadKey(); } }
没错,这就是最经典的“作为引用类型的String类传参时候表现值类型特点”的例子。上述程序意料之中输出为“原始String”,但其实这并不是因为String自身特殊到所谓“引用类型表现值类型特点”,为什么这么说?看下面这个例子:
class Person { public String Name; } class Program { static void Foo( Person p ) { p = new Person { Name = "李四" }; } static void Main( string[] args ) { Person p = new Person { Name = "张三" }; Foo( p ); Console.WriteLine( p.Name ); Console.ReadKey(); } }
这次就清楚多了,基本都能看出输出的是“张三”,关键在于自定义class用了new这个关键字、而String类的等号便是最大的迷惑点。所以说String类在当作参数传递的时候和其他引用类型并无二致。
作为本地参数的s和p同原来的变量是完全不同的引用存在,只不过引用到的是同一块内存。使用new或者重载的=进行新内存分配后,原来的变量指向并没有发生变化。
好,那么就引出标题里面的第一个问题了“指针参数传递”,在C/C++中很容易见到这样的错误:
void Foo( int* a, int* b ) { int* tmp = a; a = b; b = tmp; } int main(int argc, char *argv[]) { int a = 0; int b = 5; Foo( &a, &b ); cout<<a<<","<<b<<endl; }
对于形参a和b而言,它们只是在栈上对于原本变量的一份拷贝,指向同一块内存,改变它的指向是没有意义的。
a(变量) --| |---> 0 a(参数) --|
如果没有在这上面碰过钉子的请受我一拜。如果希望改变原有指针的内容,需要直接改变被指向区域的值或者传递二级指针:
void Foo( int* a, int* b ) { int tmp = *a; *a = *b; *b = tmp; } void FooAlloc( int** p, int size ) { *p = malloc( sizeof( int ) * size ); }
可以说这个问题和开始提到的String的特殊表现殊途同归:
- 归根结底,不管是指针还是引用,当作参数进行传递的时候仍然是值。
- 指针本身的操作和指针指向内容的操作是不同的,星星太多就容易乱。同理的还有const int* i和int* const i。
还是那句话,String并不特殊,它在参数传递上表现得和其他引用类型没什么不同,但不同点在于String是一个immutable类型,所有对其进行的修改,例如SubString、连接(+)、赋值(=)等等,都会当作创建新拷贝的操作对待,这也就是导致String表现出类似于值类型特点的原因。
参考资料
- 你必须知道的.NET 第十二回:参数之惑—传递的艺术(下), Anytao