浅拷贝 - 引用类型的异常特性

3
我无法理解下面给出的两组代码片段的输出。我真的不太理解浅拷贝的概念。它该如何解释呢?
类:
 public class Person : ICloneable
 {
    public string Name;        
    public int[] arr; 
    public object Clone()
    {
        return this.MemberwiseClone();
    }
 }

代码片段 1:
 static void Main(string[] args)
    {
        Person p1 = new Person();
        p1.Name = "Name1";
        p1.arr = new int[5] {1,2,3,4,5 };
        Person p2 = (Person)p1.Clone();

        p1.Name = "Name2";
        p1.arr[0] = 11;

        Console.WriteLine(p2.Name);
        Console.WriteLine(p2.arr[0].ToString());
        Console.Read();

    }

输出: Name1 11
疑问:字符串不是引用类型吗?那么为什么在代码段1中p2.Name被打印为“Name1”?
代码段2:
static void Main(string[] args)
    {
        Person p1 = new Person();
        p1.Name = "Name1";
        p1.arr = new int[5] { 1, 2, 3, 4, 5 };
        Person p2 = (Person)p1.Clone();

        p1.Name = "Name2";
        p1.arr = new int[5] { 11, 12, 13, 14, 15 };

        Console.WriteLine(p2.Name);
        Console.WriteLine(p2.arr[0].ToString());
        Console.Read();

    }

输出: 姓名1 1

完全离题,可能不合适,但我感到强烈的需要在问题中添加黑山标签... :) - Mihai Limbășan
4个回答

4

你示例中的 int[] 数组是一个引用类型。这意味着,p1.arrp2.arr 都指向内存中同一个数组。

如果你改变 p1.arr 的第一个索引的值,那么 p2.arr 的第一个索引的值也会被改变。因此,代码片段 1 的行为就出现了。

第二个代码片段的区别在于,你改变了 p1 的数组的引用。现在,p1.arr 是指向一个新对象的引用。而 p2.arr 仍然指向“原始”的数组。因此,打印 p2.arr[0] 将输出 1。

编辑:

为了消除一些疑虑,或许更清晰的方法是记住以下内容:

p1.Name = "Name2";

实际上是:

p1.Name = new String("Name2");

这与您的 int[] 数组完全相同。您并没有改变 p1.Name 的值,而是创建了一个新的字符串对象,并将 p1.Name 的引用更改为此新的字符串对象。p2.Name 仍然持有其自己的引用,指向原始的字符串对象,即 'Name1'。通过更改 p1.Name 的引用, 引用并不会改变。


2

来自http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx

MemberwiseClone方法通过创建一个新对象,并将当前对象的非静态字段复制到新对象中,从而创建一个浅表副本。如果字段是值类型,则执行位逐位复制。如果字段是引用类型,则复制引用但不复制所引用的对象;因此,原始对象和其克隆引用同一对象。


2
为了解决您对原问题的疑惑,需要说明字符串确实是引用类型。需要记住的是,您对数组所做的操作与您对字符串所做的操作并不相同。
p1.Name = "Name2"; // 新的字符串 - 相当于 p1.Name = new string("Name2")
p1.arr[0] = 11; // 更新数组元素
对于数组,您正在更改所引用的内存中的数据。对于字符串,您正在创建一个新的字符串(在新的内存位置),并使p1.Name这个引用指向那个新分配的内存。p2.Name(另一个引用)仍然指向存储字符“Name1”的原始内存位置。
顺便说一下,由于字符串的不可变性,没有办法通过更改p1.Name来显示p2.Name中的更改。任何尝试更改字符串的操作,例如string.replace,都会在内存中创建一个新的字符串。
希望这可以帮助您。

嗨,我认为你应该在解释中加入以下这行代码: p1.arr[0] = 11; 否则可能会产生误导。 - blitzkriegz
1
我的意思是你的代码片段行p1.arr = new int[5] { 11, 12, 13, 14, 15 }; //更新后的数组与你的解释不符。 - blitzkriegz
谢谢Mahatma,你当然是正确的。我把数组更新片段从OP的帖子的错误部分复制过来了。 - TygerKrash

0
请查看内联注释:
static void Main(string[] args)
{
    Person p1 = new Person();
    p1.Name = "Name1";
    p1.arr = new int[5] {1,2,3,4,5 };
    Person p2 = (Person)p1.Clone();

    p1.Name = "Name2"; //Now p1.Name points to a new memory location
    //But p2.Name is still pointing to the location p1.Name had
    // originally pointed to.

    p1.arr[0] = 11; //here p1.arr and p2.arr are pointing to the same place
    //So since you are changing the value of one location it gets 
    //reflected in both

    Console.WriteLine(p2.Name); //Prints Name1
    Console.WriteLine(p2.arr[0].ToString()); //Prints 11
    Console.Read();

}

在第二个代码片段中,当你说
p1.arr = new int[5] { 11, 12, 13, 14, 15 };

p1.arr 被指向一个全新的位置。(就像当你设置 p1.Name = "Name2" 时发生的情况一样)所以它并没有反映在 p2.arr 上,p2.arr 仍然指向 p1.arr 先前所指向的地方(即数组 {1,2,3,4,5})。


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