对于string类的困惑

我们都知道,string类是一个reference type。看下面这段代码:
string s1 = "hello";
string s2 = s1;
Console.WriteLine(s1);
Console.WriteLine(s2);
s1 
= "world";
Console.WriteLine(s1);
Console.WriteLine(s2);
按理来说,s1被赋予world之后,那么s2应该也是指向这段内存空间,输出world,但实际上不是这样。
代码输出为:
hello
hello
world
hello
C#高级编程中是这么解释的,s1被修改后,会重新分配一块内存,来指向新的s1。这是解释了为什么会有这个结果。但是,它没有解释,CLR为什么要这么特殊处理string类。难道,因为string类用的特别多,要符合大家的使用习惯?

看下面这段代码,class很明显也是一个reference,但是结果却和上面不同。
// 类的定义
public class Test{
    
private int number;

    
public Test(int number){
        
this.number = number;
    }


    
public int Number{
        
get{return number;}}

        
set{number = value;}
    }


    
public override string ToString(){
        
return Number.ToString();
    }

}

// 测试代码
Test t1 = new Test(10);
Test t2 
= t1;
Console.WriteLine(t1.ToString());
Console.WriteLine(t2.ToString());
t1.Number 
= 20;
Console.WriteLine(t1.ToString());
Console.WriteLine(t2.ToString());

上述代码输出的,是我们期望的值:
10
10
20
20

posted on 2004-08-30 09:04 鞠强 阅读(3040) 评论(29)  编辑 收藏 所属分类: .NET CLR研究

评论

#1楼  2004-08-30 09:15 宽带鱼      

string s1 = "hello";
string s2 = s1;
假设此时s2指向的地址为A1,因为“s1被修改后,会重新分配一块内存”,假设新分配的内存地址为A2。注意此时s2指向的仍然是A1,当然输出时s2的值还是“hello”了。   回复  引用  查看    

#2楼  2004-08-30 09:19 juqiang      

to 宽带鱼,我困惑的是,“s1被修改后,会重新分配一块内存”。CLR为什么要这么处理?这个和reference相违背吧?   回复  引用  查看    

#3楼  2004-08-30 09:29 juqiang      

看汇编代码,对于s2 = s1,是这样的:

对于s1 = "hello",是这样的:
string s2 = s1;
00000017  mov         edi,esi 

s1 = "你好";
0000006d  mov         eax,dword ptr ds:[01DC0078h] 
00000073  mov         esi,eax 

注意上面的dword ptr,很明显,CLR把ds:[01DC0078h]的偏移作为了esi的新位置。但是,为什么对s1改变值的时候,CLR要这么做?要分配一块新的内存?
  回复  引用  查看    

#4楼  2004-08-30 09:33 Felix Wang      

In .Net as well as Java, we cannot modify a string value. Everytime we attempt a string value, actuall another string is created. In other words, when we assign s1="world". The original string "hello" has not been touched. Instead, a new string "world" is created and s1 now points to the new string.

Keep in mind, string is immutable.   回复  引用  查看    

#5楼  2004-08-30 09:34 肖波 [未注册用户]

string是简单数据类型的类,与普通的类的处理方式上不太一样   回复  引用    

#6楼  2004-08-30 09:39 FantasySoft      

s1="hello"这样的语句,它的操作是在堆中创建了一个String类型的对象,然后呢,将这个对象的地址赋给s1。接着,如果使用了s2=s1这样的语句,等于是将"hello"对象的地址也赋给了s2。而到了最后,s1="world"的语句实质上并不会去改变"hello"这个对象,它改变的是s1指向的地址,而不是它原来指向对象的内容。   回复  引用  查看    

#7楼  2004-08-30 09:44 FantasySoft      

您下面给出的那个例子跟String的例子并没有可比性,因为下面的例子并没有出现改变引用本身的语句,因为s1="world"不等同于t1.Number=20,而是等同与t1=new Test();t1.Number=20。   回复  引用  查看    

#8楼  2004-08-30 09:45 juqiang      

to fantasysoft,我明白了!最后的s1 = "world",相当于是s1 = new string("world");已经是一个新的实例了,呵呵。谢谢!!!   回复  引用  查看    

#9楼  2004-08-30 09:51 sumtec      

楼主看看这几个:

http://blog.joycode.com/qqchen/archive/2004/08/21/31216.aspx
http://blog.joycode.com/qqchen/archive/2004/08/21/31217.aspx
http://blog.joycode.com/qqchen/archive/2004/08/23/31344.aspx   回复  引用  查看    

#10楼  2004-08-30 09:55 myrat      

谢谢楼上几位的解释:)   回复  引用  查看    

#11楼  2004-08-30 10:05 juqiang      

多谢sumtec的指点!   回复  引用  查看    

#12楼  2004-08-30 10:42 juqiang      

sumtec贴的qqchen写的这篇,以及jgtm(mvp)的回复,我认为基本上把string问题解释清楚了。
代码我也运行了,大家如有兴趣,自己run一下看看!而且,我自己在其中作了一点改动,增加了一个:string s = string.Intern("Hello");其实看这句就知道了。

static unsafe void ModifyConst() 
{
string str = "Hello";
fixed(char* pstr = str) 
{
pstr[0] = 'X';
}
}

static void Test2() 
{
ModifyConst();
StringBuilder sb = new StringBuilder("Hel");
sb.Append("lo");
string str = sb.ToString();
Console.WriteLine(str);
string s = string.Intern("Hello");

switch(str) 
{
case "Xello":
Console.WriteLine("string is Xello"); break;
case "Hello":
Console.WriteLine("string is Hello"); break;
default:
Console.WriteLine("Not Found"); break;
}
Console.WriteLine("Hello");
Console.ReadLine();
}

结果是:
Hello
Xello
Not Found
Xello   回复  引用  查看    

#13楼  2004-08-30 10:44 juqiang      

对了,主要是这个连接:
http://blog.joycode.com/qqchen/archive/2004/08/23/31344.aspx
对于string为什么要“特殊”处理,qqchen是这么解释的:
http://blog.joycode.com/qqchen/archive/2004/08/21/31216.aspx

感谢各位牛人!!!   回复  引用  查看    

#14楼  2004-08-30 10:50 beloved [未注册用户]

在java里也有同样的情况,所以在java里要对字符串进行大量操作的话一般都用StringBuffer来操作这样可以避免多次分配内存占用系统资源.不知道C#里应该怎么做呢?   回复  引用    

#15楼  2004-08-30 10:52 juqiang      

C#里面是StringBuilder。   回复  引用  查看    

#16楼  2004-08-30 11:00 juqiang      

漏了一句,
Console.WriteLine(str); 
string s = string.Intern("Hello"); 
加上这句:
Console.WriteLine(s);   回复  引用  查看    

#17楼  2004-08-30 16:26 飞刀 [未注册用户]

其实楼主从内存、算法上考虑这个问题,实际上是考虑偏了。

如果你学过设计模式,就知道实际上String类的设计是一个标准、经典的不变模式,Java、C#都是这样设计的,为什么要这样设计,楼主去google上以"不变模式“或"Immtable"搜搜就明白了。   回复  引用    

#18楼  2004-08-30 19:23 beloved [未注册用户]

按照楼上的知道找到了一篇文章。
http://www.blogbus.com/blogbus/blog/diary.php?diaryid=114805
写的挺明白了,所以转过来。   回复  引用    

#19楼  2004-08-30 20:40 吕震宇      

我想,看看Delphi是怎么实现String的对大家有好处。Delphi中的字符串有很多中,从中我们也可以看出字符串的发展历史以及演化进程。

实际上,操作系统内部使用的是copy-on-write技术。推荐一个站点,看看Delphi中的AnsiString以及WideString的实现技术会有帮助!

《A Brief History of Strings》

http://www.codexterity.com/delphistrings.htm   回复  引用  查看    

#20楼  2004-08-30 21:57 鞠强      

to beloved,我感觉最不明白的是,string为什么要这么处理,而他处理的机制我已经比较清楚了。
这些文章,我感觉,qqchen在sumtec老大上面的第一个连接中,已经讲得很清楚了。   回复  引用  查看    

#21楼  2004-08-30 21:57 鞠强      

谢谢:吕震宇。

我去这个连接学习学习。如有心得,继续和大家分享。   回复  引用  查看    

#22楼  2004-08-31 00:46 JGTM'2004 [MVP] [未注册用户]

我的观点呢:string从使用的角度应该是value type的语义(就像Integer、DateTime一样——immutable!),而从存储与执行的效率来考虑更适合实现为reference type——因此这个差异就自然的应该由CLR来透明化了——而实现这个的技术就是copy-on-write。   回复  引用    

#23楼  2004-08-31 01:03 JGTM'2004 [MVP] [未注册用户]

http://blog.joycode.com/qqchen/archive/2004/08/23/31344.aspx#32030

其实我也只是从现象分析出来的,等我找找更精确的证据去……  :)   回复  引用    

#24楼  2004-09-03 12:40 moshangchen [未注册用户]

这个通常叫Copy on write 技术,就是如果一个对象的值不改变,那么对象赋值的时候,就是新声明的对象指向其欲赋值对象的指针,如果数值改变了,就新分配一个内存空间,释放掉旧的空间,这就避免了,不管对象的值是否改变都要进行值复制的开销,这个对值传递的一种策略,有时也采用引用传递就不用考虑这个了,两个对象指向同一内存空间,改变一个变量,另一个也改变。   回复  引用    

#25楼  2004-10-11 10:04 圈圈      

编译器会把代码里每出现一个常量的string时"....."放在元数据中,运行时,每出现一个string都会去看string表中是否已存在这个string,如果已有,则把string变量指向它,如果没有,则再new一个。所以s1,s2刚开始指向同一个地址,后来指向不同的地方。而类不一样,t2=t1,指t2和t1是指向同一个地址。string不是简单的值类型,但是也不是引用类型。对stirng的处理是特殊的。   回复  引用  查看    

#26楼  2006-07-26 10:07 看看 [未注册用户]

我编写了一个程序,需要长时间不间断运行,程序里面使用了大量的string+string的语句,发现系统占用的内存一致在增长,从开始的50MB最好到200MB,然后就死了,不知道,.net的内存回收是怎么搞的   回复  引用    

#27楼  2008-01-06 23:46 flyingchen      

delphi中string似乎都是直接嵌入汇编的吧。谁安装了可以贴出来看看   回复  引用  查看    

#28楼 [楼主] 2008-01-10 22:09 鞠强      

delphi的string,有一个E文的文章仔细分析过,不过忘记了。

晕,3年前的帖子,居然也被翻出来了。   回复  引用  查看    

#29楼  2008-05-15 15:15 zx [未注册用户]

string是值类型
Test是类 是引用类型   回复  引用    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
博客园首页

新闻频道

社区

小组

博问

网摘

闪存

  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
成果网帮您增加网站收入


相关链接:
 





<2004年8月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
2930311234

导航

统计

公告

web counter
访问量是此计数器+213636(粗略值) 大家不要给我私人留言了,经常忘记看。有事情往这里发邮件吧:juqiang@live.com,多谢!!!

与我联系

搜索

 

常用链接

留言簿(97)

我参加的小组

我参与的团队

我的标签

随笔分类

随笔档案

相册

积分与排名

最新评论

阅读排行榜

评论排行榜