一道题引发的对引用类型的思考
参考资料:
http://www.cnblogs.com/qguohog/archive/2009/12/26/1632967.html
http://www.cppblog.com/luyulaile/archive/2011/04/08/143703.aspx
http://www.cnblogs.com/guowenhui/archive/2011/10/24/2222927.html
http://www.csharpwin.com/csharpspace/6876r6119.shtml
从网上看了一道题:
class Class1 {
private string str = "Class1.str";
private int i = 0;
static void StringConvert(string str) {
str = "string being converted.";
}
static void StringConvert(Class1 c) {
c.str = "string being converted.";
}
static void Add(int i) {
i++;
}
static void AddWithRef(ref int i) {
i++;
}
static void Main() {
int i1 = 10;
int i2 = 20;
string str = "str";
Class1 c = new Class1();
Add(i1);
AddWithRef(ref i2);
Add(c.i);
StringConvert(str);
StringConvert(c);
Console.WriteLine(i1);
Console.WriteLine(i2);
Console.WriteLine(c.i);
Console.WriteLine(str);
Console.WriteLine(c.str);
}
}
出觉得不明白的地方是:
StringConvert(str);
StringConvert(c);
Console.WriteLine(str);
Console.WriteLine(c.str);
这块应该输出什么?
static void StringConvert(string str) 、static void StringConvert(Class1 c) 这两个函数的参数,都是默认按值传递的。
在我理解感觉跟赋值有点类似:
string str=str; /* string 是引用类型,继承了 ICloneable接口。实现了深拷贝,所以字符串赋值的时候会重新new一个字符串出来赋给方法参数,
这个相当于产生一个新的对象,函数体中的操作和Main里穿过来的str参数没有关联,不会改变str的值。*/
Class1 c = c; /* class是引用类型,引用类型的引用地址放在了栈上,实际的实例对象放在了堆上,赋值的时候是在栈里面复制了Main里面new的c的引用地址,
它们同时指向了一个堆的地址。所以函数体里对str的操作反映到Main里面new的c.str。 */
另外:
string跟普通的引用类型不太一样的地方:
例如:
- string str1="abc";
- string str2="abc";
C# String对象是存放在堆上,而不是堆栈上的。
字符串驻留的技术:CLR初始化时,会创建一个内部的散列表,其中的键为字符串,值为指向托管堆中字符串的引用。刚开始,散列表为空,JIT编译器编译方法时,会在散列表中查找每一个文本常量字符串,首先会查找"abc"字符串,并且因为没有找到,编译器会在托管堆中构造一个新的指向"abc"的C# String对象引用,然后将"abc"字符串和指向该对象的引用添加到散列表中。
接着,在散列表中查找第二个"abc",这一次由于找到了该字符串,所以编译器不会执行任何操作,代码中再没有其它的文本常量字符串,编译器的任务完成,代码开始执行。执行时,CLR发现第一个语句需要一个"abc"字符串引用,于是,CLR会在内部的散列表中查找"abc",并且会找到,这样指向先前创建的C# String对象的引用就被保存在变量s1中,执行第二条语句时,CLR会再一次在散列表中查找"abc",并且仍然会找到,指向同一个C# String对象的引用会被保存在变量s2中,到此s1和s2指向了同一个引用。
str1 = str2; // 字符串继承了ICloneable接口,深拷贝,会创建一个新对象。