posts - 156, comments - 484, trackbacks - 5, articles - 25
   :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

关于String为值类型还是引用类型的讨论一直没有平息,最近一直在研究性能方面的问题,今天再次将此问题进行一次明确。希望能给大家带来点帮助。 如果有错误请指出。

来看下面例子:

            //值类型
int a = 1;
int b = a;
a = 2;
Console.WriteLine("a is {0},b is {1}", a, b);

//字符串
string str1 = "ab";
string str2 = str1;
str1 = "abc";
Console.WriteLine("str1 is {0},str2 is {1}", str1, str2);
Console.Read();

 

根据上面的例子:你觉得输出结果应该是什么?

 

输出结果:

            //结果:
//a is 2,b is 1
//str1 is abc,str2 is ab

str2依然是ab,并没有随str1的改变而改变。

如果string是引用类型,按理Str1和Str指针都指向同一内存地址,如果Str的内容发生改变,Str1应该也会相应变化。

此例子,看着string更像是值类型。 

但是MSDN却说String是引用类型,

引用类型包括: 
String

所有数组,即使其元素是值类型

类类型,如 Form

委托

可参考:http://msdn.microsoft.com/zh-cn/library/t63sy5hs(VS.80).aspx

查看具体引用是否相同

如果Net能够查看内存地址就容易了,但不允许,只能通过间接方法来实现,看下面:

        static void TestRefAddress()
{
String str1 = "abc";
String str2 = "abc";
int a = 1;
int b = 1;
StringBuilder strb1 = new StringBuilder("abc");
StringBuilder strb2 = new StringBuilder("abc");
Console.WriteLine("Reference equal for string: " + Object.ReferenceEquals(str1, str2)); //结果true
Console.WriteLine("Reference equal for int: " + Object.ReferenceEquals(a, b)); //结果false
Console.WriteLine("Reference equal for StringBuilder: " + Object.ReferenceEquals(strb1, strb2)); //结果false
Console.WriteLine("Value equal for string: " + str1.Equals(str2)); //结果true,类似于值类型
Console.Read();
}

结果为何出现如此情况,分析如下:

    Console.WriteLine("Reference equal for string: " + Object.ReferenceEquals(str1, str2)); //结果true,不同对象,但引用地址相同
Console.WriteLine("Reference equal for int: " + Object.ReferenceEquals(a, b)); //结果false,值类型装箱操作造成
Console.WriteLine("Reference equal for StringBuilder: " + Object.ReferenceEquals(strb1, strb2)); //结果false,不同对象,引用地址不同
Console.WriteLine("Value equal for string: " + str1.Equals(str2)); //结果true,类似于值类型

由第一条结果,可以判定不同的String的,相同的值,其引用地址相同,再由第四条结果,str1.Equals(str2),两者结合,可得出结论,两个String,如果赋值为同一个值,在内存中只有一个字符串存在,两个引用的地址相同。由此引出String的不变性。

String的不变性

string最为显著的一个特点就是它具有恒定不变性:我们一旦创建了一个string,在managed heap 上为他分配了一块连续的内存空间,我们将不能以任何方式对这个string进行修改使之变长、变短、改变格式。所有对这个string进行各项操作(比如调用ToUpper获得大写格式的string)而返回的string,实际上另一个重新创建的string,其本身并不会产生任何变化。
string   对象称为不可变的(只读),因为一旦创建了该对象,就不能修改该对象的值。有的时候看来似乎修改了,实际是string经过了特殊处理,每次改变值时都会建立一个新的string对象,变量会指向这个新的对象,而原来的还是指向原来的对象,所以不会改变。这也是string效率低下的原因。

String的不变,并非说string不能改变,而是其值不能改变。

在例子中str1="ab",这时在内存中就将“ab”存下来,如果再创建字符串对象,其值也等于“ab”,str2="ab",则并非再重新分配内存空间,而是将之前保存的“ab”的地址赋给str2的引用,这就能印证例子2中的结果。而当str1="abc"其值发生改变时,这时检查内存,发现不存在此字符串,则重新分配内存空间,存储“abc”,并将其地址赋给str1,而str2依然指向“ab”的地址。可以印证例子1中的结果。

 

结论:

String是引用类型,只是编译器对其做了特殊处理。

 

关于讨论:http://social.msdn.microsoft.com/Forums/zh-CN/visualcshartzhchs/thread/ce580186-86d9-45f7-b5ff-20302caf1324

 

成长,我们一起见证!

Feedback

#1楼  回复 引用 查看   

2011-10-24 14:18 by Treenew Lyn      
String 的声明:
public sealed class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string>
string 是不可变的引用的类型,CLR在运行时进行了优化,相同的string实际上在内存中只存在一份

#3楼  回复 引用 查看   

2011-10-24 14:51 by 深蓝医生      
听说String的Replace效率很高,是不是没有重新生成新的字符串对象?

#4楼  回复 引用 查看   

2011-10-24 14:55 by 发奋图强II      
穿着值类型马甲的引用类型

#5楼[楼主]  回复 引用 查看   

2011-10-24 15:29 by 停留的风      
@深蓝医生
如果调用Replace方法,同样生成新的串
通常的方法:
str = str.Replace('a','b'); 创建了新的串,然后重新指向这个串
如果只执行str.Replace('a','b');str的值是不会发生变化的。

#6楼  回复 引用 查看   

2011-10-24 15:42 by 陈梓瀚(vczh)      
“更像值类型”这个说法不对啊。这就跟下面的C代码一样:

char* s1="abc";
char* s2=s1;
s2="def";

你不能因此说char就是什么类型,实际上只能说明char*是值类型。因此string的引用是值类型,而string却不是。这就跟上面char*和char的关系一样,只不过C#把“引用”在语法上不使用字符来表示而已。就跟Delphi的class一样,你定义了一个TX = class;,然后声明TX变量——他是一个指针,因为Delphi的class只给你用堆来创建。

#7楼  回复 引用 查看   

2011-10-24 17:11 by 冠吸柏汁霆疯      
string str1="abc"; //是引用类型

string str2=new string("abc"); //是值类型

这不关编译器的事情,是内存中的堆栈分配问题!

#8楼  回复 引用 查看   

2011-10-24 17:20 by liusuifeng      
@冠吸柏汁霆疯
引用冠吸柏汁霆疯:
string str1="abc"; //是值类型
string str2=new string("abc"); //是引用类型
这不关编译器的事情,是内存中的堆栈分配问题!


string str1="abc"; 也是引用类型。。。

#9楼  回复 引用 查看   

2011-10-24 17:42 by 冠吸柏汁霆疯      
@liusuifeng
String str1 = "abc";
String str2 = "abc";
Console.WriteLine("Reference equal for string: " + Object.ReferenceEquals(str1, str2)); //结果true,不同对象,但引用地址相同

string是一种特殊的引用类型,上面的输出结果为什么是true,而不是false是因为:1、当代码运行到第二句时,内存会先到堆去搜索是否存在abc的字符串,2、如果没有,就会创建一个新的内存空间,存储abc;3、如果有,则存储abc字符串的引用,这样就节省了资源不被浪费;如果abc不是直接赋值给变量,而是new stirng("abc")的话,那么内存就不会去搜索,直接执行第2个步骤,没有第3个步骤。

#10楼  回复 引用 查看   

2011-10-24 18:16 by Ivony...      
月经文,,,,,

#11楼  回复 引用 查看   

2011-10-24 18:31 by blackcat      
看来是不知道CopyOnWrite的。建议从设计的角度研究这种问题。

#12楼  回复 引用 查看   

2011-10-24 18:51 by 乱舞春秋      
SOS调试可以看到内存,建议你试试

#13楼  回复 引用 查看   

2011-10-24 19:29 by 马甲门      
C# 的 string 相当于 C++ 里面包装好的 auto_ptr<std:string>,你要硬是认为他是值类型也可以,因为 C# 的 string 已经合体了

#14楼  回复 引用 查看   

2011-10-24 20:41 by JasenKin      
string的性能从本质上是无法提高的,因为C#的驻留机制中保存的字符串内存无法被GC回收。string只有当进程关闭的时候才进行资源释放。当然,驻留机制不适合(变量+文本的格式)(内部实现可能为享元模式)。

不要企图在string方面来提高性能。

#15楼  回复 引用 查看   

2011-10-24 22:23 by 钧梓昊逑      
引用冠吸柏汁霆疯:
string str1="abc"; //是值类型
string str2=new string("abc"); //是引用类型
这不关编译器的事情,是内存中的堆栈分配问题!

脑残!!!!!!

#16楼  回复 引用 查看   

2011-10-24 22:51 by _冻结_      
引用冠吸柏汁霆疯:
string str1="abc"; //是值类型
string str2=new string("abc"); //是引用类型
这不关编译器的事情,是内存中的堆栈分配问题!


第一次听说引用类型和值类型还是动态分配的,

#17楼  回复 引用 查看   

2011-10-25 06:46 by 乱舞春秋      
12楼说的好!这才是本质原因

#18楼  回复 引用 查看   

2011-10-25 08:49 by 鹤冲天      
引用blackcat:看来是不知道CopyOnWrite的。建议从设计的角度研究这种问题。

学习了

#19楼[楼主]  回复 引用 查看   

2011-10-26 09:15 by 停留的风      
@blackcat
引用blackcat:看来是不知道CopyOnWrite的。建议从设计的角度研究这种问题。

感谢提醒。

#20楼  回复 引用 查看   

2011-11-20 14:59 by 永远的阿哲      
在.net里,CopyOnWrite表现为字符串驻留机制吧