8

为什么字符串是引用类型,即使它通常是原始数据类型,例如 int、float 或 double。

4

3 回答 3

19

除了Dan发布的原因之外:

根据定义,值类型是那些将值存储在自身中的类型,而不是在其他地方引用值。这就是为什么值类型被称为“值类型”而引用类型被称为“引用类型”的原因。所以你的问题实际上是“为什么一个字符串引用它的内容而不是简单地包含它的内容?”

这是因为值类型具有很好的属性,即给定值类型的每个实例在内存中的大小相同。

所以呢?为什么这是一个不错的属性?好吧,假设字符串是可以是任意大小的值类型,并考虑以下内容:

string[] mystrings = new string[3];

这个由三个字符串组成的数组的初始内容是什么?值类型没有“null”,因此唯一明智的做法是创建一个包含三个空字符串的数组。那将如何在内存中布局?想一想。你会怎么做?

现在假设你说

string[] mystrings = new string[3];
mystrings[1] = "hello";

现在我们在数组中有“”、“hello”和“”。“你好”在内存中的哪个位置? 无论如何,为 mystrings[1] 分配的插槽有多大?数组及其元素的内存必须放在某个地方

这使 CLR 有以下选择:

  • 每次更改其中一个元素时调整数组的大小,复制整个内容,其大小可能为兆字节
  • 禁止创建大小未知的值类型数组
  • 不允许创建未知大小的值类型

CLR 团队选择了后者。将字符串转换为引用类型意味着您可以有效地创建它们的数组。

于 2010-09-07T15:33:26.463 回答
10

哎呀,这个答案被接受了,然后我改变了它。我可能应该在底部包含原始答案,因为这是 OP 所接受的。

新答案

更新:事情是这样的。string 绝对需要表现得像一个引用类型。到目前为止,所有答案都提到了其原因:string类型没有固定大小,将字符串的全部内容从一种方法复制到另一种方法是没有意义的,string[]否则数组将不得不调整主题的大小——仅举几个。

但是您仍然可以将 a定义 stringstruct内部指向一个char[]数组甚至是一个char*指针,并int为其长度定义一个,使其不可变,瞧!,您将拥有一个行为类似于引用类型但在技术上是值类型的类型。

老实说,这似乎很愚蠢。正如 Eric Lippert 在对其他答案的一些评论中指出的那样,定义这样的值类型与定义引用类型基本相同。在几乎所有意义上,它都与以相同方式定义的引用类型没有区别。

所以回答“为什么是string引用类型?”这个问题的答案。基本上是:“让它成为一个值类型只是愚蠢的。” 但如果这是唯一的原因,那么合乎逻辑的结论是,string实际上可以将其定义为struct如上所述,并且没有特别好的论据反对该选择。

但是,有一些理由比纯粹的智力要好string。这是我能想到的一对:classstruct

为了防止拳击

如果string是一个值类型,那么每次你将它传递给一些期望它的方法时,object它都必须被装箱,这将创建一个新的object,这会膨胀堆并导致毫无意义的 GC 压力。由于字符串基本上无处不在,让它们一直导致拳击将是一个大问题。

用于直观的相等比较

是的,无论它是引用类型还是值类型,string都可以覆盖。Equals如果它是一个值类型,那么ReferenceEquals("a", "a")将返回false这是因为两个参数都会被装箱,而装箱的参数永远不会有相等的引用(据我所知)。

因此,即使您确实可以定义一个值类型,通过让它包含一个引用类型字段来像引用类型一样工作,但它仍然不会完全相同。因此,我认为这是引用类型的更完整原因string:您可以将其设为值类型,但这只会给它带来不必要的弱点。


原始答案

它是一个引用类型,因为只有对它的引用才会被传递。

如果它是一个值类型,那么每次你将一个字符串从一个方法传递到另一个方法时,整个字符串都会被复制*。

由于它是引用类型,而不是像“Hello world!”这样的字符串值 被传递——“世界你好!” 顺便说一句,它是 12 个字符,这意味着它需要(至少)24 个字节的存储空间——只传递对这些字符串的引用。传递引用比传递字符串中的每个字符要便宜得多。

此外,它确实不是普通的原始数据类型。谁告诉你的?

*实际上,这并不完全正确。如果字符串内部包含一个char[]数组,那么只要数组类型是引用类型,则字符串的内容实际上不会按值传递——只有对数组的引用。不过,我仍然认为这基本上是正确的答案。

于 2010-09-07T05:24:43.423 回答
1

String 是引用类型,而不是值类型。在很多情况下,你知道字符串的长度和字符串的内容,在这种情况下,很容易为字符串分配内存。但考虑这样的事情。

string s = Console.ReadLine();

编译时不可能知道“s”的分配细节吗?用户输入值,所有输入的字符串/行都存储在 s. 因此,字符串存储在堆上,以便重新分配内存以适应字符串 s 的内容。并且对该字符串的引用存储在堆栈中。

要了解更多信息,请阅读:petzold 的 .net zero

阅读:通过 C# 从 CLR 收集垃圾,了解堆栈的分配详细信息。

编辑: Console.WriteLine(); 到 Console.ReadLine();

于 2010-09-07T06:02:56.983 回答