关于语言不可知论:深拷贝和浅拷贝有什么区别?

What is the difference between a deep copy and a shallow copy?

深拷贝和浅拷贝有什么区别?


宽度与深度;以对象作为根节点的引用树的形式考虑。

Shallow:

Before CopyShallow CopyingShallow Done

变量a和b指的是不同的内存区域,当b被分配给a时,这两个变量指的是相同的内存区域。稍后对其中一个内容的修改会立即反映在另一个内容中,因为它们共享内容。

迪普:

Before CopyDeep CopyingDeep Done

变量A和B指的是内存的不同区域,当B被分配给A时,A指向的内存区域中的值被复制到B指向的内存区域中。稍后对内容的修改对A或B保持唯一性;内容不共享。


浅薄的副本尽可能少地复制。集合的浅副本是集合结构的副本,而不是元素的副本。使用一个简单的副本,两个集合现在共享单个元素。

深度复制复制复制所有内容。集合的深度副本是两个集合,原始集合中的所有元素都是重复的。


简而言之,这取决于指向什么。在一个浅拷贝中,对象B指向对象A在内存中的位置。在深度复制中,对象A的内存位置中的所有内容都被复制到对象B的内存位置。

这篇wiki文章有一个很好的图表。

http://en.wikipedia.org/wiki/objectu副本


特别是对于iOS开发者:

如果BA的浅拷贝,那么对于原始数据,它就像B = [A assign];一样,对于对象,它就像B = [A retain]一样;

B和A指向相同的内存位置

如果BA的深拷贝,那么它就像B = [A copy];一样。

B和A指向不同的存储位置

B内存地址与A相同

B和A的内容相同


试着考虑以下图片

enter image description here

例如,object.memberWiseClone创建一个浅复制链接

使用icloneable接口,您可以得到这里描述的深度复制。


浅复制:将成员值从一个对象复制到另一个对象。

深度复制:将成员值从一个对象复制到另一个对象。&任何指针对象都会被复制和深度复制。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class String
{
     int   size;
     char* data;
};

String  s1("Ace");   // s1.size = 3 s1.data=0x0000F000

String  s2 = shallowCopy(s1);
 // s2.size =3 s2.data = 0X0000F000
String  s3 = deepCopy(s1);
 // s3.size =3 s3.data = 0x0000F00F
 //                      (With Ace copied to this location.)

我在这里没有看到一个简短、易懂的答案——所以我会试试。

对于浅复制,源指向的任何对象也会被目标指向(这样就不会复制引用的对象)。

对于深度复制,复制源指向的任何对象,并由目标指向该副本(因此现在每个引用的对象将有2个)。这会在对象树中循环出现。


为了便于理解,您可以阅读本文:https://www.cs.utexas.edu/~scottm/cs307/讲义/deepcopying.htm

浅拷贝:

Shallow Copy

深拷贝:

Deep Copy


{想象两个对象:A和B类型相同的T(关于C++),并且你正在考虑浅/深复制A到B}。

浅拷贝:只需将引用a的副本复制到b中。将其视为a地址的副本。因此,A和B的地址是相同的,即它们将指向相同的内存位置,即数据内容。

深拷贝:只需复制A的所有成员,在不同的位置为B分配内存,然后将复制的成员分配给B以实现深度复制。这样,如果a变为不存在b在内存中仍然有效。要使用的正确术语是克隆,您知道它们完全相同,但又不同(即存储在内存空间中的两个不同实体)。您还可以提供克隆包装器,您可以在其中通过包含/排除列表决定在深度复制期间选择哪些属性。在创建API时,这是非常常见的做法。

如果你了解其中的利害关系,你可以选择做一个浅显的复制。当你有大量的指针在C++或C中处理时,做一个对象的浅拷贝真的是一个坏主意。

"深度复制"的例子就是,当你试图进行图像处理和对象识别时,你需要把"不相关和重复的运动"从你的处理区域屏蔽掉。如果您使用的是图像指针,那么您可能有保存这些遮罩图像的规范。现在。。。如果对图像进行浅复制,则当从堆栈中删除指针引用时,会丢失引用及其副本,即在某一点上会出现访问冲突的运行时错误。在这种情况下,您需要的是通过克隆图像来对其进行深度复制。这样,您就可以在将来需要时检索这些遮罩。

与StackOverflow中的用户相比,我不是非常了解"肤浅"副本,因此请随意删除此部分,并在可以澄清的情况下给出一个很好的示例。但我真的认为,如果您知道您的程序将无限长时间运行,即在堆栈上连续执行带有函数调用的"push pop"操作,那么执行浅拷贝并不是一个好主意。如果你向业余或新手(例如C/C++教程)演示一些东西,那就没问题了。但是如果你在运行一个应用程序,比如监视和探测系统,或者声纳跟踪系统,你不应该一直在肤浅地复制你周围的物体,因为它迟早会杀死你的程序。


1
2
3
4
5
6
char * Source ="Hello, world.";

char * ShallowCopy = Source;    

char * DeepCopy = new char(strlen(Source)+1);
strcpy(DeepCopy,Source);

"shallowcopy"指向内存中与"source"相同的位置。"deepcopy"指向内存中的不同位置,但内容相同。


什么是肤浅的复制?

浅拷贝是对象的一个稍微明智的拷贝。将创建一个新对象,该对象具有原始对象中值的精确副本。如果对象的任何字段是对其他对象的引用,则只复制引用地址,即只复制内存地址。Shallow Copy

在这个图中,MainObject1有int型的field1字段和ContainObject型的ContainObject1字段。当您对MainObject1进行浅复制时,MainObject2是使用field2创建的,其中包含field1的复制值,但仍指向ContainObject1本身。注意,由于field1是原始类型,其值被复制到field2,但由于ContainedObject1是对象,MainObject2仍然指向ContainObject1。因此,对MainObject1ContainObject1所做的任何更改都将反映在MainObject2中。

如果这是浅拷贝,让我们看看深拷贝是什么?

什么是深度复制?

深度复制复制复制所有字段,并复制字段指向的动态分配内存。当对象与其引用的对象一起复制时,将发生深度复制。Deep Copy

在该图中,主对象1具有类型为int的field1字段和类型为ContainObjectContainObject1字段。当您对MainObject1进行深度复制时,MainObject2是用field2创建的,其中包含field1ContainObject2的复制值,其中包含ContainObject1的复制值。注:在MainObject1中对ContainObject1所做的任何更改将不会反映在MainObject2中。

好文章


在面向对象编程中,类型包括成员字段的集合。这些字段可以通过值或引用(即指向值的指针)来存储。

在浅复制中,将创建类型的新实例,并将值复制到新实例中。引用指针也像值一样被复制。因此,引用指向原始对象。对通过引用存储的成员所做的任何更改都会同时出现在原始和副本中,因为没有对引用的对象进行复制。

在深度复制中,按值存储的字段会像以前一样被复制,但指向按引用存储的对象的指针不会被复制。相反,将对引用的对象进行深度复制,并存储指向新对象的指针。对这些引用对象所做的任何更改都不会影响对象的其他副本。


"shallowcopy"指向内存中与"source"相同的位置。deepcopy指向内存中的不同位置,但内容相同。


1
2
3
4
5
6
7
var source = { firstName="Jane", lastname="Jones" };
var shallow = ShallowCopyOf(source);
var deep = DeepCopyOf(source);
source.lastName ="Smith";
WriteLine(source.lastName); // prints Smith
WriteLine(shallow.lastName); // prints Smith
WriteLine(deep.lastName); // prints Jones


我想举个例子,而不是正式的定义。

1
2
3
4
5
var originalObject = {
    a : 1,
    b : 2,
    c : 3,
};

此代码显示了一个浅显的副本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var copyObject1 = originalObject;

console.log(copyObject1.a);         // it will print 1
console.log(originalObject.a);       // it will also print 1
copyObject1.a = 4;
console.log(copyObject1.a);           //now it will print 4
console.log(originalObject.a);       // now it will also print 4

var copyObject2 = Object.assign({}, originalObject);

console.log(copyObject2.a);        // it will print 1
console.log(originalObject.a);      // it will also print 1
copyObject2.a = 4;
console.log(copyObject2.a);        // now it will print 4
console.log(originalObject.a);      // now it will print 1

此代码显示一个深度副本:

1
2
3
4
5
6
7
var copyObject2 = Object.assign({}, originalObject);

console.log(copyObject2.a);        // it will print 1
console.log(originalObject.a);      // it will also print 1
copyObject2.a = 4;
console.log(copyObject2.a);        // now it will print 4
console.log(originalObject.a);      // !! now it will print 1 !!

浅克隆:定义:"对象的浅副本复制"主"对象,但不复制内部对象。"当自定义对象(例如Employee)只有基元、字符串类型变量时,则使用浅克隆。

1
2
Employee e = new Employee(2,"john cena");
Employee e2=e.clone();

您在overrided clone()方法中返回super.clone();,您的工作就结束了。

深克隆:定义:"与浅拷贝不同,深拷贝是对象的完全独立拷贝。"表示当一个雇员对象持有另一个自定义对象时:

1
Employee e = new Employee(2,"john cena", new Address(12,"West Newbury","Massachusetts");

然后,您必须编写代码来克隆"address"对象以及重写的clone()方法中的对象。否则,地址对象将不会进行克隆,当您更改克隆员工对象中的地址值时,它会导致错误,这也反映了原始的地址对象。


深拷贝

深度复制复制复制所有字段,并复制字段指向的动态分配内存。当对象与其引用的对象一起复制时,将发生深度复制。

浅拷贝

浅拷贝是对象的一个稍微明智的拷贝。将创建一个新对象,该对象具有原始对象中值的精确副本。如果对象的任何字段是对其他对象的引用,则只复制引用地址,即只复制内存地址。

深拷贝和浅拷贝示例


浅复制-原始和浅复制对象内的引用变量引用公共对象。

深度复制-原始对象和深度复制对象内的引用变量引用不同的对象。

clone always does shallow copy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Language implements Cloneable{

    String name;
    public Language(String name){
        this.name=name;
    }

    public String getName() {
        return name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

主要课程如下-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String args[]) throws ClassNotFoundException, CloneNotSupportedException{

      ArrayList<Language> list=new ArrayList<Language>();
      list.add(new Language("C"));
      list.add(new Language("JAVA"));

      ArrayList<Language> shallow=(ArrayList<Language>) list.clone();
      //We used here clone since this always shallow copied.

      System.out.println(list==shallow);

      for(int i=0;i<list.size();i++)
      System.out.println(list.get(i)==shallow.get(i));//true

      ArrayList<Language> deep=new ArrayList<Language>();
      for(Language language:list){
          deep.add((Language) language.clone());
      }
      System.out.println(list==deep);
      for(int i=0;i<list.size();i++)
          System.out.println(list.get(i)==deep.get(i));//false

}

以上输出为-

false true true

false false false

原始物体的任何变化都会反映在浅物体上,而不是深物体上。

1
2
  list.get(0).name="ViSuaLBaSiC";
  System.out.println(shallow.get(0).getName()+" "+deep.get(0).getName());

输出-VisualBasic C


假设有两个数组称为arr1和arr2。

1
2
arr1 = arr2;   //shallow copy
arr1 = arr2.clone(); //deep copy


简单来说,浅拷贝类似于引用调用,深拷贝类似于值调用。

在引用调用中,函数的形式参数和实际参数都引用相同的内存位置和值。

在按值调用中,函数的形式参数和实际参数都引用不同的内存位置,但具有相同的值。


1
2
3
4
5
6
7
8
9
10
11
12
13
struct sample
{
    char * ptr;
}
void shallowcpy(sample & dest, sample & src)
{
    dest.ptr=src.ptr;
}
void deepcpy(sample & dest, sample & src)
{
    dest.ptr=malloc(strlen(src.ptr)+1);
    memcpy(dest.ptr,src.ptr);
}

为了增加其他答案,

  • 对象的浅复制对值类型执行按值复制基于属性,并按引用复制基于引用类型的属性。
  • 对象的深度复制根据值类型执行值复制属性,以及基于引用类型的按值复制层次结构深处的属性(引用类型)

摘自[博客]:http://sickprogrammersare.blogspot.in/2014/03/technical-interview-questions-on-c_6.html

深度复制涉及到使用一个对象的内容来创建同一类的另一个实例。在深度复制中,两个对象可能包含相同的信息,但目标对象将拥有自己的缓冲区和资源。销毁任一对象都不会影响其余对象。重载的赋值运算符将创建对象的深度副本。

浅复制涉及将一个对象的内容复制到同一类的另一个实例中,从而创建镜像。由于引用和指针的直接复制,两个对象将共享另一个对象的外部包含的内容,这是不可预测的。

说明:

使用复制构造函数,我们只需逐个成员复制数据值。这种复制方法称为浅复制。如果对象是一个简单的类,由内置类型和没有指针组成,这是可以接受的。此函数将使用值和对象,并且它的行为不会随一个浅副本而改变,只复制成员指针的地址,而不复制地址指向的值。然后,该函数会无意中更改对象的数据值。当函数超出范围时,对象及其所有数据的副本将从堆栈中弹出。

如果对象有任何指针,则需要执行深度复制。通过对象的深度复制,可以为自由存储中的对象分配内存,并复制指向的元素。深度复制用于从函数返回的对象。


浅复制是创建一个新对象,然后将当前对象的非静态字段复制到新对象。如果字段是值类型--->则执行字段的逐位复制;对于引用类型--->则复制引用,但不复制引用的对象;因此原始对象及其克隆引用同一对象。

深度复制正在创建一个新对象,然后将当前对象的非静态字段复制到新对象。如果字段是值类型--->将执行字段的逐位复制。如果字段是引用类型--->将执行被引用对象的新副本。要克隆的类必须标记为[可序列化]。


浅副本构造新的复合对象,并将其引用插入到原始对象中。

与浅复制不同,deepcopy构造新的复合对象,并插入原始复合对象的原始对象的副本。

让我们举个例子。

1
2
3
4
5
import copy
x =[1,[2]]
y=copy.copy(x)
z= copy.deepcopy(x)
print(y is z)

上面的代码打印错误。

让我们看看如何。

原始复合对象x=[1,[2]](由于对象内有对象,所以称为复合对象(inception))。

enter image description here

如图所示,列表中有一个列表。

然后我们使用y = copy.copy(x)创建一个简单的副本。python在这里所做的是,它将创建一个新的复合对象,但其中的对象指向原始对象。

enter image description here

在图像中,它为外部列表创建了一个新副本。但内部列表与原始列表相同。

现在我们使用z = copy.deepcopy(x)创建它的deepcopy。python在这里所做的是,它将为外部列表和内部列表创建新的对象。如下图所示(红色突出显示)。

enter image description here

最后代码打印False,因为y和z不是相同的对象。

Hth.


浅拷贝不会创建新引用,但深拷贝会创建新引用。

这是解释深度和浅拷贝的程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class DeepAndShollowCopy {
    int id;
    String name;
    List<String> testlist = new ArrayList<>();

    /*
    // To performing Shallow Copy
    // Note: Here we are not creating any references.
      public DeepAndShollowCopy(int id, String name, List<String>testlist)
       {

       System.out.println("Shallow Copy for Object initialization");
       this.id = id;
       this.name = name;
       this.testlist = testlist;

       }
    */  

    // To performing Deep Copy
    // Note: Here we are creating one references( Al arraylist object ).
    public DeepAndShollowCopy(int id, String name, List<String> testlist) {
        System.out.println("Deep Copy for Object initialization");
        this.id = id;
        this.name = name;
        String item;
        List<String> Al = new ArrayList<>();
        Iterator<String> itr = testlist.iterator();
        while (itr.hasNext()) {
            item = itr.next();
            Al.add(item);
        }
        this.testlist = Al;
    }


    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Oracle");
        list.add("C++");
        DeepAndShollowCopy copy=new DeepAndShollowCopy(10,"Testing", list);
        System.out.println(copy.toString());
    }
    @Override
    public String toString() {
        return"DeepAndShollowCopy [id=" + id +", name=" + name +", testlist=" + testlist +"]";
    }
}

复制Ararys:

数组是一个类,这意味着它是引用类型,所以array1=array2结果在引用同一数组的两个变量中。

但是看看这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
  static void Main()
    {
        int[] arr1 = new int[] { 1, 2, 3, 4, 5 };
        int[] arr2 = new int[] { 6, 7, 8, 9, 0 };

        Console.WriteLine(arr1[2] +"" + arr2[2]);
        arr2 = arr1;
        Console.WriteLine(arr1[2] +"" + arr2[2]);
        arr2 = (int[])arr1.Clone();
        arr1[2] = 12;
        Console.WriteLine(arr1[2] +"" + arr2[2]);
    }

浅克隆意味着只复制由克隆数组表示的内存。

如果数组包含值类型对象,则复制值;

如果数组包含引用类型,则只复制引用-因此,有两个数组的成员引用相同的对象。

若要在引用类型重复的位置创建深度复制,必须遍历数组并手动克隆每个元素。


复制构造函数用于用以前创建的同一类的对象初始化新对象。默认情况下,编译器编写了一个浅副本。当不涉及动态内存分配时,浅拷贝工作得很好,因为当涉及动态内存分配时,两个对象将指向堆中的相同内存位置,因此为了消除此问题,我们编写了深拷贝,以便两个对象在内存中都有自己的属性副本。为了阅读完整的例子和解释的细节,你可以看到文章C++构造函数。


我从以下几行开始理解。

浅复制将对象值类型(int、float、bool)字段复制到目标对象中,对象的引用类型(string、class等)复制为目标对象中的引用。在此目标中,引用类型将指向源对象的内存位置。

深度复制将对象的值和引用类型复制到目标对象的完整新副本中。这意味着值类型和引用类型都将被分配一个新的内存位置。


为了在浅拷贝之间增加一点混乱,只需为列表分配一个新的变量名。

"说我们有:

1
2
3
4
x = [
    [1,2,3],
    [4,5,6],
    ]

此语句创建3个列表:2个内部列表和一个外部列表。然后以x的名义提供对外部列表的引用。

1
y = x

未复制任何数据。我们在某个地方的内存中仍然有相同的3个列表。所有这些都使外部列表在名称Y下可用,除了它以前的名称X之外。

1
y = list(x)

1
y = x[:]

这将创建一个与X内容相同的新列表。列表X包含对两个内部列表的引用,因此新列表也将包含对这两个内部列表的引用。只有一个列表被复制到外部列表。现在内存中有4个列表,两个内部列表、外部列表和外部列表的副本。原始外部列表在名称x下可用,新外部列表在名称y下可用。

内部列表尚未复制!此时可以从X或Y访问和编辑内部列表!

如果您有一个二维(或更高)列表,或任何类型的嵌套数据结构,并且您想要对所有内容进行完整复制,那么您需要在复制模块中使用deepcopy()函数。您的解决方案也适用于二维列表,因为它迭代外部列表中的项目并复制每个项目,然后为所有内部副本构建一个新的外部列表。"

资料来源:https://www.reddit.com/r/learnpython/comments/1afldr/why_is_copying_a_list_so_damn_hardness_in_python/


在所有上述定义中,还有一个最常用的深度复制位于类的复制构造函数(或重载赋值运算符)中。

shallow copy->是指不提供复制构造函数的情况。在这里,只复制对象,但不复制类的所有成员。

deep copy->是指您决定在类中实现复制构造函数或重载分配,并允许复制类的所有成员。

1
2
3
4
5
6
7
8
MyClass& MyClass(const MyClass& obj) // copy constructor for MyClass
{
          // write your code, to copy all the members and return the new object
}
MyClass& operator=(const MyClass& obj) // overloading assignment operator,
{
          // write your code, to copy all the members and return the new object
}