C++中的深拷贝和浅拷贝(详解)

2020-07-13

拷贝构造函数是一种特殊的构造函数,在创建对象时,它是使用同一类中之前创建过的对象来初始化新创建的对象。如果没有自定义拷贝构造函数,系统会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数对于基本类型的成员变量,按字节复制,对于类类型成员变量,调用其相应类型的拷贝构造函数。

我们在编写程序的过程中,如果不主动编写拷贝构造函数和赋值函数,编译器将会调用默认的函数,如果类中含有指针变量,那么如果使用的默认的函数就会有错误,下面首先我们先进行简单的介绍,之后再用具体的例子来加以说明。

1.拷贝构造函数和赋值函数

拷贝构造函数,顾名思义,它是一个构造函数,所以它是在对象创建的时候被主动调用的函数,可以将
另外一个对象的变量拷贝给当前对象。赋值函数,是在对象已经存在的情况下才会进行调用。
#include <iostream>
#include <set>
using namespace std;

class Test {
    
    

public:
    Test() {
    
     // 默认构造函数
        cout << "Test()" << endl;
    };
    Test(int v) :value(v) {
    
     // 带参数的构造函数
        cout << "Test(int v)" << endl;
    }
    Test(const Test& obj){
    
     //拷贝构造函数
        cout << "Test(const Test& obj)" << endl;
    }
    Test& operator=(const Test& obj)
    {
    
    
        cout << "Test& operator=(const Test& obj)" << endl;
        return *this;
    }
    ~Test() {
    
     // 析构函数
    }

private:
    int value;
};

int main() {
    
    
    Test a(1);
    Test c = a;
    Test d;
    d = a;
    getchar();
    return 0;
}

在这里插入图片描述
(1)拷贝构造函数和赋值函数

Test c = a;
d = a;

这里第一个"=“时对象c还没有存在,所以这里调用的是拷贝构造函数,第二个”="时对象d已经存在了,所以调用的是赋值函数

(2)拷贝构造函数
拷贝构造函数是其它构造函数的重载函数,它的参数是const对象的引用,const比较容易理解,我们将一个对象拷贝给另外一个对象,那该对象的值我们是不希望被改变了的,另外一个原因是,添加 const 限制后,就可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型,如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了;那么这里我们为什么选择传递对象的引用作为函数的参数呢?一个原因是引用传参的时候,形参是实参的一个别名,也就是说它们俩其实是相同的,但是如果我们不传递引用的话,在进入函数的时候,会另外分配一块存储空间给形参使用,形参将会初始化实参的值,在函数调用结束的时候,这块存储空间将会被释放掉,如果说对象比较大的话,在这个初始化的过程中将会比较的耗时;还有一个十分重要的原因是我们在调用拷贝构造函数的时候,如果传递的是对象的话,由于要将实参的值赋给形参,将会调用拷贝构造函数进行赋值,那么就会形成一个死循环,如果您将上述拷贝构造函数中的"&"删除掉,程序将会报错。

(3)赋值函数
赋值函数用到的是运算符的重载,它的返回值是对象的引用,参数是const对象的引用。对于返回值而言,我们希望保留运算符原有的特性,考虑到a=b=c,这个赋值语句的顺序应该是a=(b=c),所以赋值函数重载函数的返回值应该是类的对象,返回对象的引用是因为如果返回对象的话,将会把原先对象的值赋值给临时的对象,这样还会调用一次拷贝构造函数。赋值函数的参数是对象的引用,一个原因是节省时间,另外一个原因是使函数可以传递const的对象。

#include <iostream>
using namespace std;

class Test {
    
    
public:
    Test() {
    
     // 默认构造函数
        cout << "Test()" << endl;
    };
    Test(int v) :value(v) {
    
     // 带参数的构造函数
        cout << "Test(int v)" << endl;
    }
    Test(const Test& obj){
    
     //拷贝构造函数
        cout << "Test(const Test& obj)" << endl;
    }
    Test operator=(const Test& obj)
    {
    
    
        cout << "Test& operator=(const Test& obj)" << endl;
        return *this;
    }

    ~Test() {
    
     // 析构函数
    }

private:
    int value;
};

int main() {
    
    
    Test a(1);
    Test c = a;
    Test d;
    d = c = a;
    getchar();
    return 0;
}

在这里插入图片描述
由于赋值函数的返回值是对象,这里在函数返回时就调用了拷贝构造函数。

2.何时调用拷贝构造函数

(1)定义一个对象时,以本类另一个对象作为初始值,发生拷贝构造。
(2)如果函数的形参是类的对象,调用函数时,将使用实参初始化形参,发生拷贝构造。
(3)如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化
一个临时的无名对象,传递给主调函数,发生拷贝构造。

3.浅拷贝和深拷贝

浅拷贝:如果用默认的拷贝构造函数(赋值函数)去赋值有指针类型的成员变量的对象,将会使两个对象
的指针地址也是一样的,也就是说这两个对象的指针成员变量指向的是相同的地址。
深拷贝:每个对象拥有自己的资源,此时需要显示提供拷贝构造函数和赋值函数。

在这里插入图片描述
如果调用了默认的拷贝构造函数(赋值函数),在拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标,这将会使同一指针指向相同的区域,同时也会导致同一块资源被释放多次,从而造成错误。
下面的例子是显示定义的赋值函数。

#include <iostream>
#include <string.h>
using namespace std;

class Test {
    
    
public:
    Test():ptr(new char[1]) {
    
     // 带参数的构造函数
        cout << "Test():ptr(new char[1])" << endl;
    }
    Test operator=(const Test& obj)
    {
    
    
        if (this == &obj) {
    
     // s=s
            return *this;
        }
        delete[] ptr;
        ptr = new char[strlen(obj.ptr) + 1];
        strcpy(ptr, obj.ptr);
        cout << "Test& operator=(const Test& obj)" << endl;
        return *this;
    }
    Test& operator=(const char *s)
    {
    
    
        delete[] ptr;
        ptr = new char[strlen(s) + 1];
        strcpy(ptr, s);
        cout << "Test& operator=(const Test& obj)" << endl;
        return *this;
    }
    ~Test() {
    
     // 析构函数
        delete[] ptr;
    }
private:
    char* ptr;
};

int main() {
    
    
    Test a;
    a= "test";
    Test b;
    b= a;
    Test d;
    getchar();
    return 0;
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_34600424/article/details/107318430
今日推荐