string是引用类型,但是为什么它在更新赋值时作为值类型工作

时间:2016-03-29 06:05:28

标签: c# string

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

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

调试结果为abc:123

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

4 个答案:

答案 0 :(得分:6)

这是对使用参考文献的常见误解。

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

    string s1 = "abc"; 

s1等于" abc"存储,让我们说0x0000AAAA

    string s2 = s1; 

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

    s2 = "123"; 

字符串是不可变的,这意味着你不能修改字符串,无论何时你分配一个新值或修改,你在内存中的其他地方创建一个新的字符串,而前一个字符串准备好GC,如果需要的话(不是我们的情况)其实)。此时,s1仍然具有值0x0000AAAA,而s2具有新的0X0000BBBB。

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

由于两个点的内容不同,因此会打印出不同的结果。

它只是一个引用类型,因为变量中包含的值不是按原样使用,而是用于将指针发送到存储实际对象的内存中的地址位置。

除非你使用out / ref(& in 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

编辑:OP需要对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获取GameObejct作为参数,编译器将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的地址。因此obj可能包含0x0000AAAA,但它自己的地址是0x0000AABB,那么objParam的值现在是0x0000AABB,更改objParam意味着更改存储在0x0000AABB的值。

out和ref的工作原理相同,只有那个需要在方法中分配一个值,而ref可以离开方法而不影响给定的参数。

答案 1 :(得分:1)

字符串是不可变的。 分配string s1 = "abc"时,您将由字符串文字abc创建的新对象引用分配给s1的引用。
因此,当您指定s2 = "123";时,s2将引用由字符串文字"123"创建的新对象。
希望这会有所帮助!!

答案 2 :(得分:1)

通过将字符串文字分配给String变量,实际上是实例化一个新的字符串对象并分配给现有的字符串对象。即,

如果我们声明并初始化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

答案 3 :(得分: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参数(需要明确完成)。