指针参数传递和.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




