字符串是引用类型,但为什么在赋值更新时它却像值类型一样工作?

7

这是一个简单的代码片段,但让我有些困惑:

string s1 = "abc";
string s2 = s1;
s2 = "123";
Debug.Log(s1 + ":" + s2);

调试结果为abc:123

那么为什么s1没有更新为123,因为s1和s2包含相同的引用,如果一个变量更新它,那么第二个变量将自动更新。


字符串是不可变的。 - Amit Kumar Ghosh
@un-lucky,问题的标题相同,但问题内容不同。我在之前检查过了。 - Muhammad Faizan Khan
字符串是不可变的还是可变的(或者它们是引用类型还是值类型)对于这个问题并不重要。这个问题是关于变量本身,而不是它们的内容。 - Jcl
4个回答

11

这是对引用使用的一个常见误解。

s1是一个引用类型,但它的内容是一个值。你可以认为所有变量都是值类型,但编译器处理它们的方式因值或引用类型而异。

    string s1 = "abc"; 

s1等于存储“abc”的地址,假设为0x0000AAAA。

    string s2 = s1; 

s2指向与s1相同的地址,因此其值与s1相同。两者的值都为0x000AAAA。

    s2 = "123"; 

字符串是不可变的,这意味着您无法修改字符串,每当您分配新值或进行修改时,就会在内存中的其他位置创建一个新字符串,而先前的字符串则会准备好进行垃圾回收(如果需要,但这在我们的情况下并非如此)。此时,s1仍具有值0x0000AAAA,而s2具有新值0X0000BBBB。

    Debug.Log(s1 + ":" + s2);

由于两个点引用不同的内容,因此它们打印不同的结果。

这只是一个引用类型,因为变量中包含的值并不意味着要直接使用,而是意味着将指针发送到存储实际对象的内存地址位置。

除非使用 out/ref (C++ 中的 &),否则暗示要使用变量的值(地址),最有可能作为参数。

请注意,这种行为对于任何对象都是相同的,而不仅仅是字符串。

Dog dogA = new Dog();
Dog dogB = dogA;
dogA.name = "Wolfie"; // Affect both since we are dereferencing
dogA = new Dog(); // dogA is new object, dogB is still Wolfie

编辑:原帖要求对ref/out进行解释。

当你想要改变一个对象时,你应该考虑以下几点:

void ChangeObject(GameObject objParam)
{
   objParam = new GameObject("Eve");
}

void Start(){
    GameObject obj = new GameObject("Adam");
    ChangeObject(obj);
    Debug.Log(obj.name); // Adam...hold on should be Eve (??!!)
}

ChangeObject以GameObject为参数,编译器将obj中包含的值(00000AAAA)复制到objParam中,并对其进行副本制作,两者现在具有相同的值。

在方法内,objParam被赋予一个新值,不再与方法外部的obj相关联。 objParam是该方法的局部变量,在完成后被删除(游戏对象仍然在场景中,但引用已丢失)。

如果您希望在方法内更改obj:

void ChangeObject(ref GameObject objParam)
{
   objParam = new GameObject("Eve");
}

void Start(){
    GameObject obj = new GameObject("Adam");
    ChangeObject(ref obj);
    Debug.Log(obj.name); // Yeah it is Eve !!!
}

这一次,传递的不是obj的值而是它的地址。因此,obj可能包含0x0000AAAA但其自身地址为0x0000AABB,则objParam的值现在为0x0000AABB并且更改objParam意味着更改存储在0x0000AABB处的值。

out和ref工作方式相同,只是out要求在方法内分配一个值,而ref可以在不影响给定参数的情况下离开该方法。


你能否更明确地定义它?除非你使用out/ref(在C++中是&),否则变量的值被隐含地使用(地址),很可能作为参数。 - Muhammad Faizan Khan

1

字符串是不可变的。当你赋值string s1 = "abc"时,你将由字符串字面量abc创建的新对象引用分配给s1的引用。
因此,当你赋值s2 = "123";时,s2将引用由字符串字面量"123"创建的新对象。
希望这可以帮助你!


1
通过将字符串文字分配给字符串变量,您实际上是实例化了一个新的字符串对象并将其分配给现有的对象。即,
如果我们像下面这样声明和初始化s1;
string s1 = "abc";

实际上正在发生的是我们正在创建一个新的字符串对象abc并将其赋值给s1,这类似于:[假设Foo是一个类];
Foo fooObj= new Foo();
fooObj= new Foo(); // this will be a new instant 

还有一点需要澄清; new Foo();会在内部创建一个对象并分配给fooObj,我们无法更改new Foo();的值,因为它是在内部创建的,但我们可以处理fooObj


很好的回答,但fafse已经说了几乎相同的话。谢谢。 - Muhammad Faizan Khan

0

因为变量不是对象。变量是指向对象的引用

当你执行s2 = "123"时,你重新分配了变量s2所指向的引用(而不是它所指向的对象),将其指向一个不同的对象(一个类型为string且值为123的对象)

是的,string是不可变的(正如其他人所指出的),但实际上这并不是这个问题的罪魁祸首。这种情况发生在可变和不可变类型以及值和引用类型之间。关键区别是你正在修改变量指向的引用(内存地址),而不是改变该内存地址的内容。

对于值类型:

int i1 = 0;
int i2 = i1;
i2 = 2;
Debug.Log(i1.ToString() + ":" + i2.ToString());

会记录日志:0:2

同时使用引用类型:

public class MyClass {
   private string _cont;
   public MyClass(string cont) { _cont = cont; }
   public override string ToString() { return _cont; }
}

MyClass c1 = new MyClass("abc");
MyClass c2 = c1;
c2 = new MyClass("123");
Debug.Log(c1.ToString() + ":" + c2.ToString());

还会记录 abc:123

C# 中改变变量指向的内存地址实际内容只有两种方法:一种是使用 unsafe 指针(并且它们有自己的语法),另一种是将该变量作为 refout 参数传递给方法(必须明确执行)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接