为啥 String 是 Value 类型,尽管它是类而不是结构?
Posted
技术标签:
【中文标题】为啥 String 是 Value 类型,尽管它是类而不是结构?【英文标题】:Why String is Value type although it is a class not a struct?为什么 String 是 Value 类型,尽管它是类而不是结构? 【发布时间】:2011-11-09 03:32:09 【问题描述】:举个例子:
string me = "Ibraheem";
string copy = me;
me = "Empty";
Console.WriteLine(me);
Console.WriteLine(copy);
输出是:
Empty
Ibraheem
由于它是类类型(即不是结构),String copy
也应该包含Empty
,因为 C# 中的 = operator
分配对象的引用而不是对象本身(如在 C++ 中)??
【问题讨论】:
=
不会更改对象,而是将引用更改为指向不同的对象。您的代码有两个引用:me
和 copy
,由于您只是将不同的对象分配给 me
,copy
仍然指向旧对象。
【参考方案1】:
它不是一个值类型。当您使用字符串文字时,它实际上是在编译时存储的引用。因此,当您分配一个字符串时,您基本上是在更改指针,就像在 C++ 中一样。
【讨论】:
那么为什么要复制任何其他类? @Mr.DDD 不是。所有类都以这种方式运行。当您重新分配一个引用时,只有这个引用发生变化,没有其他变化。 @Adam:(顺便说一句,好名字)。不变性、实习等都与 OP 提出的问题无关;它是一个字符串的事实是一个红鲱鱼。 OP 只是对分配的作用感到困惑,而不是对属于string
的任何特定行为感到困惑。【参考方案2】:
System.String
不是值类型。它展示了一些类似于值类型的行为,但您遇到的行为不是其中之一。考虑以下代码。
class Foo
public string SomeProperty get; private set;
public Foo(string bar) SomeProperty = bar
Foo someOtherFoo = new Foo("B");
Foo foo = someOtherFoo;
someOtherFoo = new Foo("C");
如果您检查了foo.SomeProperty
的输出,您认为它与someOtherFoo.SomeProperty
相同吗?如果是这样,你对语言的理解有缺陷。
在您的示例中,您为字符串分配了一个值。而已。它与值类型、引用类型、类或结构无关。这是一个简单的赋值,无论您是在谈论字符串、长整型还是 Foos 都是如此。您的变量暂时包含相同的值(对字符串“Ibraheem”的引用),但随后您重新分配了其中一个。这些变量并非一直都存在千丝万缕的联系,它们只是暂时具有一些共同点。
【讨论】:
啊,但是你的例子内部使用了string
s,所以当然和OP的代码一样。 ;-)
如果他被这个例子弄糊涂了,我们就完蛋了。
@Mr.DDD,分配与new
无关。这只是关于x = y
。 y
可能是新的,也可能是已经存在的东西。不管它是什么,它都分配给x
。在您的 sn-p 中,me
的值是对"Ibraheem"
的引用。您将该值(这是一个引用)复制到copy
。但是随后您通过赋值运算符使用对另一个字符串的引用来覆盖 me
的值。 copy
仍然保留原始引用,因为您没有采取任何措施导致 copy
发生更改。
@Mr. DDD:C# 中的变量是为存储值而预留的一段内存。 值类型(结构和原始类型)直接存储在变量中,而引用类型存储在其他地方,对该位置的 reference 是变量中的值。当您将 值 分配给变量(无论该值是引用、原始类型还是结构)时,它不会影响任何其他内存位置。在您的示例中,copy
和 me
是两个完全不同的变量(因此是内存位置),它们在某些时候碰巧具有相同的 值。
@Mr.DDD 如果缺少new
关键字让您感到困惑,请考虑将"Empty"
视为new string("Empty")
的快捷方式(从概念上讲,new string("Empty")
不会实际编译,因为那不是 string
) 的构造函数之一【参考方案3】:
string copy = me;
表示copy
引用将指向me
所指向的相同内存位置。
稍后me
可以指向其他内存位置,但不会影响copy
。
【讨论】:
【参考方案4】:您的示例显示的是字符串是引用类型的经典行为。
【讨论】:
明确地说,他的例子展示了所有赋值的经典行为,不管引用类型与否。【参考方案5】:字符串的行为与任何其他类相同。考虑:
class Test
public int SomeValue get; set;
public Test(int someValue) this.SomeValue = someValue;
Test x = new Test(42);
Test y = x;
x = new Test(23);
Console.WriteLine(x.SomeValue + " " + y.SomeValue);
输出:
23 42
– 与您的 string
示例中的行为完全相同。
【讨论】:
【参考方案6】:虽然其他答案确切地说明了您的答案的解决方案是什么,但为了更好地了解您为什么要阅读堆和堆栈内存分配以及垃圾收集器何时从内存中删除数据.
这是一个很好的页面,描述了堆栈和堆内存以及垃圾收集器。在文章的底部有解释其他部分的链接: http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx?ArticleID=9adb0e3c-b3f6-40b5-98b5-413b6d348b91
希望这能让您更好地理解原因
【讨论】:
我不同意;堆与堆栈分配的语义(以及当然垃圾收集!)是对他的问题的辅助,只会进一步混淆显然刚刚开始使用该语言的人。 @Adam 我也不同意 :) 我有大量的 C# 项目并用 C# 编写了数千行代码,因为到目前为止我已经编写了 3 年 C#! @Stuart 您的网站非常好。如果您在已有选择的答案时没有回答,我会选择它作为最佳答案。请不要回答有最佳答案的问题。 @Mr.DDD:如果我的评论被冒犯了,我深表歉意;这只是基于问题主题的假设。但是,用公认的答案来回答问题是完全可以接受的。如果您认为这个更合适,您可以取消选择当前答案并选择这个。 @ِِAdam 谢谢。我知道我可以取消选择,但我讨厌 (Ctrl + Z) 撤消【参考方案7】:如果您也使用值类型,您的代码也会这样做。考虑使用整数:
int me = 1;
int copy = me;
me = 2;
Console.WriteLine(me);
Console.WriteLine(copy);
这将打印出以下内容:
2
1
【讨论】:
【参考方案8】:虽然已接受的答案解决了这个问题(与其他一些人一样),但我想给出一个专门针对您实际上在问什么的答案,即关于变量赋值的语义。
C# 中的变量只是一块内存,用于保存单个值。需要注意的是,没有“值变量”和“引用变量”之类的东西,因为变量只保存值。
“值”和“引用”的区别在于类型。值类型 (VT) 意味着整条数据都存储在变量中。
如果我有一个名为abc
的整数变量保存值100
,那么这意味着我的应用程序中有一个四字节的内存块,其中存储了文字值100
。这是因为int
是值类型,因此所有数据都存储在变量中。
另一方面,如果我有一个名为foo
的string
变量保存值"Adam"
,则涉及两个实际内存位置。第一个是存储实际字符"Adam"
的内存,以及关于我的字符串的其他信息(它的长度等)。然后将对该位置的 reference 存储在我的变量中。引用与 C/C++ 中的指针非常相似;虽然它们不一样,但类比足以说明这一点。
所以,总而言之,引用类型的值是对内存中另一个位置的引用,其中值类型的值是数据本身 .
当你给一个变量赋值时,你改变的只是那个变量的值。如果我有这个:
string str1 = "foo";
string str2 = str1;
然后我有两个保存相同值的字符串变量(在这种情况下,它们每个都保存对同一字符串的引用,"foo"
。)如果然后这样做:
str1 = "bar";
然后我将str1
的值更改为对字符串"bar"
的引用。这根本不会改变 str2
,因为它的值仍然是对字符串 "foo"
的引用。
【讨论】:
+1 我更喜欢这个作为答案,因为它实际上为像我这样的 c# 初学者消除了概念上的困惑(2 年 c#) 很好的解释。 这与字符串的不变性有关吗?因为在任何其他类的情况下,必须创建一个指定的Copy
(克隆)方法来完全解除副本与原件的关联。以上是关于为啥 String 是 Value 类型,尽管它是类而不是结构?的主要内容,如果未能解决你的问题,请参考以下文章
为啥数字字符的 Integer.valueOf 不返回其值?
为啥用c_str()转换路径类型后opendir()打不开路径?
为啥 MySQL 和 JPA 会发生“java.sql.SQLException: Incorrect string value...”