不可变的字符串误解或文档中的错误?

时间:2008-10-09 15:07:48

标签: c# string

我刚刚在MS Visual Studio文档中看到了这一点,粗体部分对我没有意义。这是错的还是我不理解它?如果你运行它,b似乎保持“你好”(正如我所料)而不是“h”。

字符串不可变 - 创建对象后无法更改字符串对象的内容,尽管语法使其看起来好像可以执行此操作。例如,当您编写此代码时,编译器实际上会创建一个新的字符串对象来保存新的字符序列,而变量b继续保持“h”

string b =“h”;

b + =“ello”;

13 个答案:

答案 0 :(得分:7)

您只需一步即可完成添加和分配。字符串是不可变的,但也是引用类型。

string b = "h";
b = b + "ello";

我们可以像这样看待伪记忆:

string b = "h";         // b    := 0x00001000 ["h"]
string tmp1 = "ello";   // tmp1 := 0x00002000 ["ello"]
string tmp2 = b + tmp1; // tmp2 := 0x00003000 ["hello"]
string b = tmp2;        // b    := 0x00003000 ["hello"]

我不完全确定你在哪里获得该文本,因为当我阅读string类的文档时,我发现(不是我认为“h”实际上是垃圾收集):

  

字符串是不可变的 - 创建对象后无法更改字符串对象的内容,尽管语法使其看起来好像可以执行此操作。例如,当您编写此代码时,编译器实际上会创建一个新的字符串对象来保存新的字符序列,并将该新对象分配给b。然后字符串“h”有资格进行垃圾收集。

@Jon Skeet提出由于字符串实习,“h”永远不会被垃圾收集,我同意他,但更多的是C#Standard同意他,否则§2.4.4.5字符串文字中的以下内容不能是真的:

  

每个字符串文字不一定会产生新的字符串实例。当根据字符串相等运算符(第7.9.7节)等效的两个或多个字符串文字出现在同一程序中时,这些字符串文字引用相同的字符串实例。

答案 1 :(得分:5)

人们似乎并没有理解这个问题。没有人认为字符串对象不是不可变的。争论的焦点是他粗犷的:

  

并且变量b继续保持   “H”

我同意OP同意这部分文档在两个方面是不正确的:

(1)从显而易见的直观意义上说,如果你在他的两个样本行之后打印(b)(或者使用这种语言的正确陈述),你将得到“你好”的结果。
(2)严格意义上,变量b不保持“h”,“hello”或任何字符串值。它包含对字符串对象的引用。

变量b的内容确实因赋值而改变 - 它从一个点变为字符串对象“h”变为一个指向字符串对象“hello”的指针。

当他们说“持有”他们真正的意思是“指向”。并且他们错了,在任务之后b不再指向“h”。

我认为他们真正想要的例子是:

string a = "h";
string b = a;
b += "ello";

关键是,我相信,仍然会指向“h”;即,对b的赋值不会修改它指向的对象,它会创建一个新对象并将b更改为指向它。

(我实际上并没有写C#,但这是我的理解。)

答案 2 :(得分:4)

是的,文档错了。 (许多字符串方法的文档也暗示了可变性。它们写得很差。)

哎呀,即使使用“编译器”创建新的字符串对象也是关闭的。基本上它正在做:

string b = "h";
b = string.Concat(b, "ello");

此时编译器的工作已经完成 - 它是创建新字符串对象的框架。

答案 3 :(得分:4)

文档错了。 变量b现在保持“hello”。字符串是不可变的,但可以重新分配变量。

答案 4 :(得分:4)

这里的误解是关于参考类型
字符串是引用类型,而不是值类型。这意味着,您的变量 b不是字符串类型的对象,它是对内存中字符串类型的对象的引用。
文档说的是,记忆中的对象是不可改变的 仍然,您对对象的引用可以更改以指向内存中的其他(不可变)对象。
对于你来说,它可能看起来像对象的内容已经改变了,但是在内存中它没有改变,这就是 immutable thingy的全部内容。

字符串本身 不可变。您的示例更改的不是内存中的字符串类,而是您的变量指向的引用。

请参阅此略微修改的代码:

string b = "h";
string m1 = b;
b += "ello";
// now b == "hello", m1 == "h"

最后b将指向“hello”,而m1将指向“h”。对你而言,似乎“h”已经改为“你好”,但事实并非如此。 b + =“ello”创建了一个包含“hello”的新字符串类,并将其分配给b,而旧b仍然存在于Memory中并且仍然包含“b”。

如果string不是不可变的,那么m1也会包含“hello”,而不仅仅是“b”,因为b和m1都指向相同的引用。

答案 5 :(得分:2)

字符串不能更改,但字符串变量可以分配不同的值。你正在做的更接近:

string b = "h";
string temp = b + "ello";
b = temp;

要显示字符串的实际不可变性,这将失败:

   string b="hello";
   if(b[0] == 'h')  // we can read via indexer
      b[0] = 'H';   // but this will fail.

答案 6 :(得分:1)

现在有三个字符串。一个是原始的“h”,一个是“ello”,第三个是“你好”。你的b变量指向“hello”字符串。其他两个字符串没有对它们的引用,可以被垃圾收集器抛弃。

答案 7 :(得分:0)

string b =“h”; b + =“ello”;

b只是对堆中对象的引用。 实际上,在“+ =”操作之后,b不再引用原始的“h”。现在,它引用一个新的字符串对象“hello”,它是“h”和“ello”的串联。 GC将收集“h”字符串。

答案 8 :(得分:0)

发生的事情是你正在制作一个新的变量,它包含'hello',然后改变b来引用它,'old'b的内存仍然包含'h',但是不再需要这样的垃圾了收藏家会清理它。这就是为什么在迭代和粘贴字符串时使用字符串生成器非常好的原因 - 有关详细信息,请参阅this

答案 9 :(得分:0)

我不知道C#做了什么,但我确实用Java读过这个,基于Java的实现更像是这样:

string b =“h”;

b =(new StringBuilder(b))。Append(“ello”)。ToString();

关键是字符串不存在“+”或“Append”,因为字符串是不可变的。

答案 10 :(得分:0)

试试这个:

string b = "h";
string c = b + "ello";    // b still == "h", c = "hello"
string d = string.concat(b, "ello"); // d == hello, b still "h"

为什么b仍然是“h”?因为“b”不是对象,所以它是一个对象引用。您无法对b引用的对象进行任何更改。如果字符串在哪里可变,那么使用:

string b = "ello";
string f = b.Insert("h",0);

将b修改为“hello”(因为h插入了位置0)但是因为它是不可变的b仍然是“ello”。

如果您将对其他对象的引用更改为其他内容。

b = "ello";
b = "Some other string";
// b not references "Some other string" , but the object "ello" remains unchanged.

我希望它有所帮助(并且有效:S)

答案 11 :(得分:0)

简单地说,字符串不能就地修改(如果字符串是一个字符数组)

答案 12 :(得分:0)

将所有类型存储位置视为保存“对象ID”可能是最清楚的。假设,最初,编译器已将ID #123分配给字符串“h”,并已将ID #547分配给字符串“ello”。然后在语句b = "h";之后,变量b将保留ID #123。语句b += "ello";将使编译器将ID #123ID #547传递给+运算符以获取字符串,然后将其传递给String.Concat方法。该方法将依次要求系统创建类型为ID #915的新对象(例如System.String),保留五个字符"hello",并将该对象返回给调用者。然后,编译器将ID #915存储到b