zoukankan      html  css  js  c++  java
  • C++:构造函数3——浅拷贝和深拷贝

    一、默认拷贝构造函数

    拷贝构造函数是一种特殊的构造函数(详情见:http://www.cnblogs.com/duwenxing/p/7429777.html),如果用户在定义类时没有显式地编写拷贝构造函数,那么C++编译器会在类中生成一个默认的拷贝构造函数。默认拷贝函数完成对象之间的位拷贝(即浅拷贝),换句话说就是用“旧对象”的成员对象对“新对象”的成员对象进行对应的一一赋值,如下:

    1 Student:Student(const Student &stu){
    2     Name=stu.Name;
    3     Age=stu.Age;
    4     Gender=stu.Gender;
    5 }

    在编写自定义的类时,如果对象之间的拷贝仅仅要求完成对象之间的位拷贝,就像上例所示,那么编译器提供的默认拷贝构造函数就可以满足需求了,此时编程者可以不必再显式地在类中定义拷贝构造函数,以提高编程效率。

    特别注意:如何禁止默认拷贝构造函数的调用

    假设我们在定义类时不希望该类的默认拷贝构造函数被调用,即禁止用该类的一个对象通过简单的“值传递”方式去拷贝该类的另一个对象,那么我们可以在类中声明一个私有的拷贝构造函数(注意是声明,我们并不需要定义该函数)。由于该拷贝构造函数是私有的,则当用户试图调用默认拷贝构造函数时,系统会报出一个编译错误。

     1 #include<iostream>
     2 #include<string>
     3 using namespace std;
     4 class Student{
     5 public:
     6     Student()=default;
     7     Student(string name){ //带参数的构造函数 
     8         Name=new string;//利用new为指针Name在内存中动态地分配空间
     9         *Name=name; 
    10     }
    11     ~Student(){ //析构函数 
    12         if(Name!=NULL){
    13             delete Name; //释放动态分配的空间
    14             Name=NULL; 
    15         }
    16     }
    17 private:
    18     string *Name;
    19     Student(const Student& stu);//将拷贝构造函数声明为私有 
    20 }; 
    21 
    22 int main(){
    23     Student stu1("Tomwenxing");
    24     Student stu2=stu1; //错误! 
    25     return 0;
    26 } 

    二、浅拷贝

    浅拷贝,又称位拷贝,换句话说就是在进行对象拷贝时,只是用“旧对象”的成员对象对“新对象”的成员对象进行对应的一一赋值(就像默认拷贝构造函数做的那样)。如下图所示:

    在大多数情况下,浅拷贝可以很好的完成工作。但如果类中含有动态成员或指针成员变量时,浅拷贝就会出现问题。例如:

     1 #include<iostream>
     2 #include<string>
     3 using namespace std;
     4 class Student{
     5 public:
     6     Student()=default;
     7     Student(string name){ //带参数的构造函数 
     8         Name=new string;//利用new为指针Name在内存中动态地分配空间
     9         *Name=name; 
    10     }
    11     Student(const Student &stu){ //浅拷贝 
    12         Name=stu.Name;
    13     }
    14     ~Student(){ //析构函数 
    15         if(Name!=NULL){
    16             delete Name; //释放动态分配的空间
    17             Name=NULL; 
    18         }
    19     }
    20 private:
    21     string *Name;
    22 }; 
    23 
    24 int main(){
    25     Student stu1("Tomwenxing");
    26     Student stu2=stu1;
    27     return 0;
    28 } 

    上面的例子在运行时会报出运行错误,我们在这里简单分析一下为什么:

    当我们定义了对象stu1之后,系统中的内存情况大致如下:

    此时对象stu1中的成员指针变量Name指向内存在堆为其分配的空间。当我们调用拷贝构造函数并利用对象stu1来创建对象stu2时,由于执行的是浅拷贝,只是将对象成员变量之间的值进行简单的拷贝赋值,此时对象stu1会将自己的成员变量指针Name的值拷贝赋值给对象stu2的成员指针变量Name,也就是说指针stu1.Name和指针stu2.Name此时指向的是堆中的同一个空间。如下图所示:

    当程序执行完毕需要将对象stu1和对象stu2进行销毁时,两个对象的析构函数将会对同一个内存空间释放两次,从而导致运行错误。为了解决这种问题,在拷贝对象时就不能是简单的浅拷贝,而应该是深拷贝。

    三、深拷贝

    以上面的例子为例,深拷贝时不是简单的将对象stu1的指针变量Name赋值给对象stu2的指针变量Name,而是在堆中为指针stu2.Name分配内存空间,并将指针stu1.Name所指空间中存储的内容拷贝给指针stu2.Name所指的内存空间。如下:

     1 #include<iostream>
     2 #include<string>
     3 using namespace std;
     4 class Student{
     5 public:
     6     Student()=default;
     7     Student(string name){ //带参数的构造函数 
     8         Name=new string;//利用new为指针Name在内存中动态地分配空间
     9         *Name=name; 
    10     }
    11     Student(const Student &stu){ //深拷贝
    12         Name=new string;
    13         *Name=*(stu.Name);
    14     }
    15     ~Student(){ //析构函数 
    16         if(Name!=NULL){
    17             delete Name; //释放动态分配的空间
    18             Name=NULL; 
    19         }
    20     }
    21 private:
    22     string *Name;
    23 }; 
    24 
    25 int main(){
    26     Student stu1("Tomwenxing");
    27     Student stu2=stu1;
    28     return 0;
    29 } 

    上面的程序可以正确运行,在这里我们简单分析一下:

    当我们定义了对象stu1之后,系统中的内存情况大致如下:

     

    当对象stu2创建时,由于执行的是深拷贝,故会在堆中为对象stu2的指针Name重新分配空间,并将stu1.Name所指空间中存储的内容拷贝给stu2.Name所指的内存空间,此时系统中的内存情况大致如下:

    此时指针stu1.Name和指针stu2.Name各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

    四、总结

    假设一个类中拥有资源(堆或其他系统资源),当该类的一个对象对另一个对象进行拷贝时,系统对该类的资源进行了重新分配,那么这个过程就是深拷贝;反之若没有发生资源的重新分配而是简单的“值传递”,那么该过程就是浅拷贝。

  • 相关阅读:
    Educational Codeforces Round 80 (Rated for Div. 2)
    2020 CCPC Wannafly Winter Camp
    Codeforces Round #613 (Div. 2)
    Codeforces Round #612 (Div. 2)
    Hello 2020
    Good Bye 2019
    Codeforces Round #590 (Div. 3)
    依赖注入
    Spring 拦截器
    rsync服务端一键安装rsync脚本(非源码)
  • 原文地址:https://www.cnblogs.com/duwenxing/p/7451163.html
Copyright © 2011-2022 走看看