深层副本和浅层副本有什么区别?


Answers:


759

浅拷贝应尽可能少地重复。集合的浅表副本是集合结构的副本,而不是元素。使用浅表副本,现在两个集合共享各个元素。

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


在常规意义上,.NET MemberwiseClone()实现可能比浅表复制做的更多
Lu55 2012年

5
请记住,还有混合的副本(不仅是诸如lazy copy),它仅复制其中的一部分(这里是一个实例)!;)
cregox 2015年

因此,浅表X可以更改X中的元素,但是深表不能吗?
punstress

1
什么是集合结构?
亲爱的

1
@Honey Collections可以是存储多个数据项的各种数据结构。在python中,我们有元组,列表,字典等
Murphy

850

广度与深度;从引用树的角度出发,以对象为根节点。

浅:

复制前 浅复制 浅做

变量A和B指代不同的内存区域,当B分配给A时,两个变量指代相同的内存区域。由于它们共享内容,因此对其中一个的内容的后续修改会立即反映在另一个的内容中。

深:

复制前 深度复制 做得好

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


32
下面是维基百科的文章,这说明来源于情况下,它没有任何意义断章取义你en.wikipedia.org/wiki/Object_copy#Shallow_copy
科尔宾

4
在浅拷贝的情况下,如果我们对数组B进行任何更改,由于A和B都指向相同的内存位置,这是否会反映在数组A中?
tek3

3
在单行中,按引用复制与按值复制。不知道答案是否正确!
Mannu

2
未经引文直接从维基百科直接获取的图像
jasonleonhard

9
@jasonleonhard所以9年前我只是在图像上添加了url,因为不支持嵌入图像。因此,URL引用了其来源。后来,该社区将URL放入嵌入的图像中,而没有对其进行任何形式的引用。4岁的热门评论还指出了您所指出的内容。看看:stackoverflow.com/posts/184780/revisions为什么不自己自己编辑引文?下次有人对我10岁的写作风格有所抱怨时,我可能无法上班。
dlamblin

156

简而言之,它取决于什么指向什么。在浅表副本中,对象B指向对象A在内存中的位置。在深层复制中,对象A的存储位置中的所有内容都将复制到对象B的存储位置。

这篇Wiki文章有一个很棒的图表。

http://en.wikipedia.org/wiki/Object_copy



69

特别是对于iOS开发人员:

如果B浅拷贝A,那么它就像原始数据B = [A assign];,并为它的状物体B = [A retain];

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

如果B深拷贝A,那么它像B = [A copy];

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

B内存地址与A相同

B与A的内容相同


8
“ B内存地址与A的地址相同”-怎么来的?

2
在Deep Copy中,“ B内存地址与A的内存地址不同”
ismail baig

60

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

深度复制:将成员值从一个对象复制到另一个对象。
                     任何指针对象均已复制并进行深度复制。

例:

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.)

47

我在这里没有看到简短易懂的答案-因此,我将尝试一下。

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

使用深层副本时,将复制源指向的任何对象,而目标指向的副本(因此,每个引用的对象将有2个)。这递归到对象树。



36

{想象两个对象:_t(相对于C ++)类型相同的A和B,您正在考虑将A复制到B的浅层/深层}

浅表复制: 将对A的引用简单地复制到B中。将其视为A的地址的副本。因此,A和B的地址将相同,即它们将指向相同的存储位置,即数据内容。

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

你可以选择做一个浅复制ONLY_IF你了解所涉及的利害关系。当你有一个指针的数量巨大,处理在C ++或C,做一个对象的浅拷贝是真的是一个坏主意。

Example_OF_DEEP COPY_例如,当您尝试进行图像处理和对象识别时,您需要在处理区域之外遮盖“不重复运动”。如果使用图像指针,则可能具有保存这些蒙版图像的规范。现在...如果您进行图像的浅表复制,则当指针引用从堆栈中被杀死时,您将丢失该引用及其副本,即在某些时候会出现访问冲突的运行时错误。在这种情况下,您需要克隆其图像的深层副本。这样,您便可以在将来需要时取回这些口罩。

EXAMPLE_OF_SHALLOW_COPY与StackOverflow中的用户相比,我的知识面不是很广,因此可以删除此部分并举一个很好的例子。但是我真的认为,如果您知道程序将无限期运行,即使用函数调用在堆栈上进行连续的“ push-pop”操作,则执行浅表复制不是一个好主意。如果您要向业余或新手演示某些内容(例如C / C ++教程内容),那么可能就可以了。但是,如果您正在运行监视和检测系统或Sonar跟踪系统之类的应用程序,则不应随意复制对象,因为它迟早会杀死程序。


32
char * Source = "Hello, world.";

char * ShallowCopy = Source;    

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

“ ShallowCopy”指向内存中与“ Source”相同的位置。“ DeepCopy”指向内存中的其他位置,但内容相同。


22

什么是浅拷贝?

浅拷贝是对象的按位拷贝。创建一个新对象,该对象具有原始对象中值的精确副本。如果对象的任何字段是对其他对象的引用,则仅复制参考地址,即仅复制内存地址。浅拷贝

在此图中,MainObject1具有field1类型为int和ContainObject1类型为的字段ContainObject。当您执行的浅表副本时MainObject1MainObject2会创建,field2其中包含的复制值field1并仍指向ContainObject1自身。请注意,由于field1是基本类型,因此将其值复制到,field2但由于ContainedObject1是对象,因此MainObject2仍指向ContainObject1。因此,对ContainObject1in 所做的任何更改MainObject1都将反映在中MainObject2

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

什么是深拷贝?

深层副本将复制所有字段,并复制这些字段所指向的动态分配的内存。当将对象及其引用的对象一起复制时,将发生深层复制。 深拷贝

在此图中,MainObject1具有field1类型为int和ContainObject1类型为的字段ContainObject。对进行深层复制时MainObject1MainObject2会创建,field2其中包含的复制值field1ContainObject2的复制值ContainObject1。注意所做的任何更改ContainObject1MainObject1不会反映MainObject2

好文章


尽管此示例所指的不是您的错,但是field3当该位置能够尝试理解与该问题一样深的内容时,该示例中的#3在哪里发生ContainObject2
Robb_2015 2015年

16

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

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

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


12

“ ShallowCopy”指向内存中与“ Source”相同的位置。“ DeepCopy”指向内存中的其他位置,但内容相同。


这有点误导。浅表副本和深表副本都会将对象复制到内存中的新位置,深表副本还将复制子对象,而浅表副本只会使新对象引用旧的子对象。不参考原始对象就很难阅读。
Bill K

10

浅克隆:
定义:“对象的浅表副本将复制'主'对象,但不复制内部对象。” 当自定义对象(例如Employee)仅具有基本的String类型变量时,可以使用“浅层克隆”。

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

您返回super.clone();覆盖的clone()方法,您的工作结束了。

深度克隆
定义:“与浅表副本不同,深表副本是对象的完全独立副本。”
表示当Employee对象包含另一个自定义对象时:

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

然后,您必须编写代码以在重写的clone()方法中克隆“ Address”对象。否则,当您更改克隆的Employee对象中的Address值时,Address对象将不会克隆,并且会导致错误,这也反映了原始对象。


8
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

那不是一个好例子。浅表副本通常用于对象的快速副本,而无需复制数据,但是一旦对象需要修改共享数据,便会获取其深层副本。您的示例可能会使初学者感到困惑。
CMircea 2010年

这仅适用于使用指针表示字符串的语言。DHA试图说明的一点是,浅拷贝仅将指针复制到相同(单一)原始内容,而深拷贝也将克隆指针的引用内容。两种方法都复制表面内容。如果该语言将字符串存储为表面文字内容(例如,在WAV标头中),则此示例将不起作用。请注意,对于大多数并非深奥的现实问题,这可能过于挑剔。
DragonLord

8

深拷贝

深拷贝将复制所有字段,并复制这些字段所指向的动态分配的内存。当将对象及其引用的对象一起复制时,将发生深层复制。

浅拷贝

浅拷贝是对象的按位拷贝。创建一个新对象,该对象具有原始对象中值的精确副本。如果对象的任何字段是对其他对象的引用,则仅复制参考地址,即仅复制内存地址。


令人遗憾的是,该链接不再有效-它现在指向2019年2月以来有关Web设计的一篇文章(除非作者是千篇一律的?)。
PhilPhil

7

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

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

克隆总是做浅拷贝。

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();
    }
}

主要类别如下-

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

} 

以上的输出将是-

假真真

错误错误错误

对原始对象所做的任何更改都将反映在浅对象而不是深对象中。

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

输出 -ViSuaLBaSiC C


7

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

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

这段代码显示了一个浅表副本

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

此代码显示了深层副本

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 1 4 4 4 4 4 4
Suresh Prajapati,

在深层复制中,执行copyObject.a = 8,然后检查。希望您能得到正确的答案。
Vivek Mehta

5
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);
}

5

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

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

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


5

想象有两个数组,分别称为arr1和arr2。

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

5

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

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

让我们举个例子。

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

上面的代码显示FALSE。

让我们看看。

原始复合对象x=[1,[2]](之所以称为复合对象,是因为它在对象内部具有对象(盗用))

在此处输入图片说明

如您在图像中看到的,列表中有一个列表。

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

在此处输入图片说明

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

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

在此处输入图片说明

最后,代码Falsey和z不是同一对象。

HTH。


2

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

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


2

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

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

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

说明:

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

如果对象具有任何指针,则需要执行深层复制。使用对象的深层副本,可以在免费存储中为该对象分配内存,并复制指向的元素。深拷贝用于从函数返回的对象。


2

要为其他答案添加更多内容,

  • 对象的浅表复制对基于值类型的属性执行按值复制,对基于引用类型的属性执行按引用复制。
  • 对象的深层复制对基于值类型的属性执行按值复制,对于深度(引用类型)的基于引用类型的属性执行按值复制

2

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

这是解释深层和浅层副本的程序。

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 + "]";
    }
}

1

复制Arary:

Array是一个类,这意味着它是引用类型,因此array1 = array2导致两个引用同一数组的变量。

但是看这个例子:

  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#/ VB中,浅复制值类型数组不会复制值。这两个数组引用相同的对象。将按钮添加到窗体并添加此代码,以查看:private void button1_Click(object sender, EventArgs e) { int[] arr1 = new int[]{1,2,3,4,5}; int[] arr2 = new int[]{6,7,8,9,0}; MessageBox.Show(arr1[2] + " " + arr2[2]); arr2 = arr1; MessageBox.Show(arr1[2] + " " + arr2[2]); arr1[2] = 12; MessageBox.Show(arr1[2] + " " + arr2[2]); }
DeanOC

没错,我在阵列上使用克隆将答案更精确。完全正确的说法是“浅复制值类型的数组不会复制值”,但是在数组上使用克隆确实可以。我试图解释一下,尝试一下。谢谢
lukaszk 2012年

1

我从以下几行中了解了。

浅拷贝将对象值类型(int,float,bool)字段复制到目标对象中,并将对象的引用类型(字符串,类等)复制为目标对象中的引用。在该目标中,引用类型将指向源对象的存储位置。

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


0

除了上述所有定义外,该类的副本构造函数(或重载赋值运算符)中包含一个且也是最常用的深层副本。

浅复制->是不提供复制构造函数的时间。在这里,仅复制对象,而不复制所有类的成员。

深层复制->是您决定在类中实现复制构造函数或重载分配并允许复制该类的所有成员时。

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
}

0

复制构造函数用于使用先前创建的相同类的对象来初始化新对象。默认情况下,编译器编写了一个浅表副本。当不涉及动态内存分配时,浅表复制就可以正常工作,因为当涉及动态内存分配时,两个对象都将指向堆中的同一内存位置。因此,为了消除此问题,我们编写了深拷贝,因此两个对象都有自己的属性副本在记忆中。为了阅读完整示例和解释的详细信息,您可以参阅文章C ++构造函数

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.