浅拷贝(shallow copy)与深拷贝(deep copy)对比

一、碎碎念

C语言和C++中的浅拷贝(shallow copy)与深拷贝(deep copy)

概念考古

深浅拷贝,或许最初没有这两个概念,只是因为编码出现了点问题,大家总结抽象出来的两个概念
后来,随着使用这个的人数的增加,所以总算有书上记录这些说法了。

吐槽

  • 见怪不怪了,概念各种各样,无论是由于英语翻译的问题,导致的”谐音“中文。(虽然很想吐槽,有的翻译真的反过来告诉我英语,我似乎也不会读成那样。)
  • 然后就是,个人打比方,或者迁移其他学科的概念,导致,很多名词,又称又称...
  • 吐槽归吐槽,解决方案:
  • 1)专业术语,最好讲解的时候改为英语来读,比如哈希改为hash
  • 2)遇到不熟悉,或者英语原版没有的单词,Google或百度相关概念,理解这些野生的概念,毕竟,可能用的人多了,就真的成为专业概念了。

二、浅拷贝:

浅拷贝(又称值拷贝

  • Q:为什么叫做值拷贝?
  • A:因为,浅拷贝是将源变量内存中存的每个字节都原封不动的拷贝到目标变量中,也就是将源变量中的“值”拷贝到目标变量中。
  • Q:产生的现象是什么?
  • 比如,数组char aa[10]="111";然后,char *p=aa;这样指针变量p将数组指针变量aa中的字节全拷贝过来,然后就导致了指针p和aa指向同片内存。当那篇内存发生变化,两者都会变化。这是浅拷贝,修改p的同时也会修改aa,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同。
  • Q:那么深拷贝是什么?
  • A:先预告一下,深拷贝,比上面的操作还多了一步。浅拷贝只是简单的改变指向去那,指针p和aa是共用空间。
    但是,深拷贝,是p我他要和aa一样,不仅指向一个内存,而且那部分要和我是一伙的,那么问题来了,aa指向的只有一份,如何变成一模一样呢?那就是开辟空间,复制那个aa指向的东西到开辟的空间,然后我p指向开辟的空间。
  • Q:有点蒙圈,打个比方吧
  • A:小兔是一个橡皮泥艺术家,保留橡皮泥,但是小兔捏橡皮泥很懒,能少捏一块是一块。很久之前,小兔一块橡皮泥都没捏,一天小A向小兔申请,我要一个孙悟空的橡皮泥模型,小兔捏好了。这时候,小B看到小A的孙悟空橡皮泥,也想要一个,所以向小兔也申请。小兔可以有两种方式处理:
  • 浅拷贝:小兔告诉小B说,我懒得捏。你们两共用这一个吧,现在我宣布着这块橡皮泥你们两共用,过了一天,小B觉得孙悟空不好看,就从小兔那里申请,把孙悟空改为猪八戒,小兔通过了。这时候,小A得到消息,他的橡皮泥模型被人改了,小A和小B争吵了起来,小兔知道是他的错。不应该让小A和小B共用那个的。
  • 深拷贝:小兔告诉小B说,好的,我另外哪块橡皮泥给你捏一个,这样小A和小B都有了孙悟空模型,一天,小A申请把橡皮泥改为喜羊羊,小B申请改为灰太狼,他们两的愿望都能实现,并且,无人擅自修改,小A和小B高兴极了。

1)C语言中浅拷贝

#include<stdio.h>
#include<stdlib.h>
using namespace std;

int main()
{

    char aa[10]="111111";

    //one,two都是浅拷贝,指向"1111"对应的区域 
    char *one=aa;
    char *two=aa;

    printf("one__%s\n",one);
    printf("two__%s\n",two);

    *(one+3)='9';
    printf("one__%s\n",one);
    printf("two__%s\n",two);

    //由于是浅拷贝,所以,我用free释放one指针指向的内存
    //会导致程序打印two指向的内存,出错 ,因为指向的位置被释放了 
    //所以本程序会崩溃
    free(one);
    printf("two__%s\n",two);
    return 0;    
}

上面讲解了C语言中浅拷贝的缺点,但是C语言默认的就是浅拷贝,如何解释这种设计初衷。
其实,浅拷贝有优点

#include<stdio.h>
#include<stdlib.h>
using namespace std;

//函数的数组传递,其实也是浅拷贝 
//相当于char *c=aa;
void test(char *c[])
{    
    printf("%s",c);
}

int main()
{

    char aa[10]="111111";
    test(aa);

    return 0;    
}

优点:
对于C语言这种很注重效率的语言,这样的设计。能够减少在新的函数中,再开辟一个数组那么大的空间承载。
转而直接用一个指针指向那里,就可以实现

  • 1)节省内存
  • 2)提高传递参数的效率

2)C++中浅拷贝

#include<bits/stdc++.h>
using namespace std;

int main()
{
    string str1="afeqfew";
    string str2="fdag";

    //浅拷贝 
    str2=str1;

    return 0;    
}
#include<bits/stdc++.h>
using namespace std;

int main()
{
    string str1="afeqfew";

    //浅拷贝 
    string str2(str1);

    return 0;    
}

三、深拷贝

深拷贝:拷贝源变量指向的位置的全部东西,这样拷贝之后,我修改我的副本,源变量修改你的正版,互不影响。

1)C语言中深拷贝的常用方式

申请空间,拷贝到申请的空间中

#include<stdio.h>
#include<stdlib.h>
#include<string.h> 
using namespace std;

int main()
{

    char aa[10]="111111";

    //one申请一个指向的空间,再用strcpy拷贝过来
    //实现深拷贝 
    char *one=(char *)malloc(10*sizeof(char));
    strcpy(one,aa);

    //two还是浅拷贝,只是简单的指向 
    char *two=aa;

    printf("one__%s\n",one);
    printf("two__%s\n",two);

    *(one+3)='9';
    printf("one__%s\n",one);
    printf("two__%s\n",two);

    //由于是深拷贝,所以,我用free释放one指针指向的内存
    //只是释放我one指针指向的在堆空间申请的内存,
    //不会影响原先aa和two指向的空间,所以,程序不会崩溃 
    free(one);
    printf("two__%s\n",two);
    return 0;    
}

2)C++中深拷贝的常用方式

除了,C语言中常用的实现深拷贝的方式。C++中类的深拷贝,一般有一下两种实现。

重载拷贝构造函数

以下代码是参考的

比如重载string类的拷贝构造函数

string::string (string & s)
{
    if(s.str)
    {
        str=new char[strlen(s.str)+1];
        strcpy(str,s.str);
    } 
    else
    {
        str=NULL;
    }
}

重载运算符“=”号

以下代码是参考的

string & string::operator = (const string & s)
{
    if(str==s.str)
    {
        return *this;
    }

    if(str)
    {
        delete []str;
    }

    if(s.str)//s.str不为NULL才执行复制操作 
    {
        str=new char[strlen(s.str)+1];
        strcpy(str,s.str); 
    }
    else
    {
        str=NULL;
    }

    return *this;
}

四、深浅拷贝优缺点

项目 浅拷贝 深拷贝
内存开销 低,因为只是直接改变指针指向 高,申请了额外的内存空间
效率 高,只需要改变指向 低,需要进行申请内存操作,速度慢一些
释放内存时状况 由于两个指针同时指向一块内存,要是释放着两个指针,会导致重复释放内存错误 不会出现重复释放内存错误
值的修改是否独立 共用内存,谁修改了内存,另一方都会知道,是共用内存 深拷贝,自己管自家的内存,互相独立

总的来说,由于效率等因素,C语言和C++中默认为浅拷贝。并且,你可以通过申请内存,实现深拷贝。此外,深浅拷贝没有谁更好一说,看你自己的应用场景。