C++拷贝构造函数:深拷贝和浅拷贝

1 拷贝构造函数

它是一种特殊的构造函数,由编译器调用来完成一些基于同一类的其他对象的构件及初始化。

1.1 拷贝函数的调用场景:

1、值传递传递函数体

2、值传递从函数体返回(返回匿名对象)

3、用一个对象初始化另一个对象

1.2 举例说明

1、值传递传递函数体

#include <iostream>
using namespace std;
public:
    int a;
    Test(int x){
        a=x;
    }
    Test( Test &test){
        cout<<"copy"<<endl;
        a=test.a;
    }
};
void fun1(Test test){     //值传递传入函数体
    cout<<"run fun1"<<endl;
}

2、值传递从函数体返回(返回匿名对象)

#include <iostream>
using namespace std;
class Test{
public:
    int a;
    Test(int x){
        a=x;
    }
    Test( Test &test){
        cout<<"copy"<<endl;
        a=test.a;
    }
};
Test fun2(){
    Test t(2);
    cout<<"run fun2"<<endl;
    return t;   //返回匿名对象、复杂类型(值传递从函数体返回)
}
int main() {
    Test t3 = fun2();
    cout<<"fun2"<<endl;
    return 0;
}

3、用一个对象初始化另一个对象

#include <iostream>
using namespace std;
class Test{
public:
    int a;
    Test(int x){
        a=x;
    }
    Test( Test &test){
        cout<<"copy"<<endl;
        a=test.a;
    }
};
int main() {
    Test t1(1);
    Test t2=t1;  //用一个对象初始化另外一个对象
    return 0;
}

2 编译器与默认的拷贝构造函数

(1)如果用户没有自定义拷贝构造函数,并且代码中用到了拷贝构造函数,那么编译器会自动生成默认的拷贝构造函数

(2)但如果用户定义了拷贝构造函数,编译器就不会再生成拷贝构造函数了。

(3)如果用户之定义了一个构造函数,但不是拷贝构造函数,且代码中用到了拷贝构造函数,纳闷编译器还是会生成默认的拷贝构造函数。如果没有使用,编译器就不生成了默认构造函数了。

2 深拷贝和浅拷贝

浅拷贝:只是对指针的靠背,拷贝后两个指针指向同一个内存空间

深拷贝:不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针。

重点:系统默认生成的拷贝构造函数是浅拷贝

2.1  浅拷贝存在的问题

例如程序:

#include <iostream>
#include "string.h"
using namespace std;
class Test{
public:
    char *buf;
    Test(void){
        buf=NULL;
    }
    Test(const char* str){
        buf=new char[strlen(str)+1];
        strcpy(buf,str);
    }
    ~Test(){
        if(buf!=NULL){
            delete buf;
            buf=NULL;
        }
    }
};
int main() {
    Test t1("hello");
    Test t2=t1;
    cout<<"(t1.buf==t2.buf)?"<<(t1.buf==t2.buf?"Yes":"No")<<endl;
    return 0;
}

Test类的buf是一个字符指针,在带参数的构造函数中为它分配了一块堆内存来存放字符串,然后在析构函数中又将堆内存释放。在main函数中 Test t2=t1;会调用默认的拷贝构造函数。因此输出结果如下,导致程序崩溃。

输出结构可以看出,默认的拷贝构造函数只是简单地把两个对象的指针做赋值运算,它们指向的仍然是同一个地址,当产生两次析构的时候,程序会释放同一块堆内存,然后崩溃。

总结浅拷贝会出现的问题:

(1)浅拷贝只是拷贝了指针,使两个对象的指针指向同一地址,在对象结束的时候,会造成同一块内存资源析构两次,造成程序崩溃

(2)指针指向同一块内存,任何一方有改动都会影响另一方

(3)在释放内存时,后者的内存先释放会导致前者内存也释放,导致空间不能再被利用。

2.1 浅拷贝问题的解决办法

可以在类中添加一个自定义的拷贝构造函数解决两次析构的问题。

如:

    Test(Test &test){
        buf=new char[strlen(test.buf)+1];
        strcpy(buf,test.buf);
    }

总代码:

#include <iostream>
#include "string.h"
using namespace std;
class Test{
public:
    char *buf;
    Test(void){
        buf=NULL;
    }
    Test(const char* str){
        buf=new char[strlen(str)+1];
        strcpy(buf,str);
    }
    Test(Test &test){
        buf=new char[strlen(test.buf)+1];
        strcpy(buf,test.buf);
    }
    ~Test(){
        if(buf!=NULL){
            delete buf;
            buf=NULL;
        }
    }
};
int main() {
    Test t1("hello");
    Test t2=t1;
    cout<<"(t1.buf==t2.buf)?"<<(t1.buf==t2.buf?"Yes":"No")<<endl;
    return 0;
}

由于buf又分配了一块堆内存来保存字符串,t1和t2的buf指向了不同的堆内存,析构时就不会发生程序崩溃了,也就是深拷贝。

输出结果:

 3 拷贝构造函数与赋值函数

区别:

1、拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区

     赋值函数是对于一个已经被初始化的对象来进行operator=操作。

例如:

Class A;
A a;
A b=a; //调用拷贝构造函数
A b(a); //调用拷贝构造函数
Class A;
A a;
A b;
b=a; //调用赋值函数

2、当数据成员包含指针对象的时候,普遍有两种处理需求:第一种是拷贝指针对象;另一种是引用指针对象。

     拷贝构造函数在大多数情况下是拷贝;赋值函数则是引用对象。

3、实现方式不同。

     拷贝构造函数首先是一个构造函数,它调用的时候是通过参数传进来的那个对象来初始化产生一个对象。

     赋值函数则是把一个对象赋值给一个原有的对象。

     因此,如果原来的对象中有内存分配,要先把内存释放掉,而且还需要检查一下两个对象是不是同一个对象,如果是,就无需任何操作。

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值