c++类——复制构造函数(含深浅拷贝)

本文最后更新于:1 年前

(一)引言:

对于普通类型,复制是极其简单的:

1
2
3
4
int a=1;
int b=a;//复制
int *p1=&a;
int *p2=p1;//复制

但对于内部含有多个数据成员的类对象,其结构复杂,复制也相对复杂。

这时就需要调用复制构造函数来完成拷贝过程。

(二)概念:

复制构造函数又名拷贝构造函数,是一种特殊的构造函数,它通过编译器调用完成基于同一类的其他对象的构造及初始化。

其形参是唯一的,且只能是本类的引用,一般情况下会在前面加const引用来保证被复制的类对象在复制过程中不被修改。

  • 如果形参是值传递,将会发生无穷递归,即被复制的类对象需要先复制出一个临时对象传入复制构造函数,然后临时对象又需要调用复制构造函数产生新的临时对象......

  • 如果形参是指针传递,能够实现要求,但是会导致歧义。

在类中如果没有显式给出复制构造函数时,则编译器自动给出一个默认复制构造函数。如果有自己定义的构造函数(包括复制构造函数),则按函数重载的规律,调用合适的构造函数。

(三)调用时机:

  1. 对象以值传递的方式传入函数参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <iostream>
    using namespace std;
    class Time
    {
    private:
    int hour;
    int minute;
    int sec;
    public:
    Time() { hour = minute = sec = 0; }
    Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
    Time(const Time& t)
    {
    hour = t.hour;
    minute = t.minute;
    sec = t.sec;
    cout << "复制构造函数" << endl;
    }//复制构造函数
    void set_time()
    {
    cin >> hour >> minute >> sec;
    return;
    }
    void show_time()
    {
    cout << hour << ":" << minute << ":" << sec << endl;
    }
    };
    void test(Time t)
    {
    return;
    }
    int main()
    {
    Time t1(4, 5, 6);
    test(t1);//发生复制
    t1.show_time();
    return 0;
    }

    调用test()时,会进行以下步骤:

    • Time对象t1传入test形参时,产生临时对象temp。

    • 调用复制构造函数将t1的值赋值给temp。即有Time temp(t1)发生。

    • 在test函数本身执行完毕后,将temp析构掉

    • temp本质上是test的局部变量,生命周期和test相同。

  2. 对象以值传递的方式从函数返回。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #include <iostream>
    using namespace std;
    class Time
    {
    private:
    int hour;
    int minute;
    int sec;
    public:
    Time() { hour = minute = sec = 0; }
    Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
    Time(const Time& t)
    {
    hour = t.hour;
    minute = t.minute;
    sec = t.sec;
    cout << "复制构造函数" << endl;
    }//复制构造函数
    void set_time()
    {
    cin >> hour >> minute >> sec;
    return;
    }
    void show_time()
    {
    cout << hour << ":" << minute << ":" << sec << endl;
    }
    };
    Time test(int i1,int i2,int i3)
    {
    Time t(i1, i2, i3);
    return t;//发生复制
    }
    int main()
    {
    Time t1(4, 5, 6);
    t1 = test(10, 9, 8);
    t1.show_time();
    return 0;
    }

    调用test至return时,会进行以下步骤:

    • 在return处产生临时对象temp。

    • 调用复制构造函数将test内创建的局部变量t的值赋值给temp。即有Time temp(t)发生。

    • 在test函数本身执行到最后,将t析构掉

    • 在test()执行完毕后(此时赋给t1已经结束了),析构temp(因其为临时对象)。

  3. 对象需要通过另外一个对象进行初始化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #include <iostream>
    using namespace std;
    class Time
    {
    private:
    int hour;
    int minute;
    int sec;
    public:
    Time() { hour = minute = sec = 0; }
    Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
    Time(const Time& t)
    {
    hour = t.hour;
    minute = t.minute;
    sec = t.sec;
    cout << "复制构造函数" << endl;
    }//复制构造函数
    void set_time()
    {
    cin >> hour >> minute >> sec;
    return;
    }
    void show_time()
    {
    cout << hour << ":" << minute << ":" << sec << endl;
    }
    };
    int main()
    {
    Time t1(4, 5, 6);
    Time t2 = t1;//发生复制
    t1.show_time();
    return 0;
    }
  4. 当成员变量为类类型时。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    #include <iostream>
    using namespace std;
    class year
    {
    private:
    int years;
    public:
    year() { years = 0; }
    year(const year& y) { cout << "year复制构造函数" << endl; }//year的复制构造函数
    };
    class Time
    {
    private:
    int hour;
    int minute;
    int sec;
    year y;
    public:
    Time() { hour = minute = sec = 0; }
    Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
    void set_time()
    {
    cin >> hour >> minute >> sec;
    return;
    }
    void show_time()
    {
    cout << hour << ":" << minute << ":" << sec << endl;
    }
    };
    int main()
    {
    Time t1(4, 5, 6);
    Time t2 = t1;//发生复制,且调用的是成员类的复制构造函数。
    t1.show_time();
    return 0;
    }

    因为Time的复制构造函数不存在,且其成员类year中存在复制构造函数,所以,此时编译器合成一个Time类的复制构造函数,但是这个合成的复制构造函数的目的是向其中插入能够去调用类year的复制构造函数的代码。

    如需要两个被同时调用,需要自己构造Time的复制构造函数,并且对成员类进行显式赋值。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    #include <iostream>
    using namespace std;
    class year
    {
    private:
    int years;
    public:
    year() { years = 0; }
    year(const year& y) {cout << "year复制构造函数" << endl;}//year复制构造函数
    };
    class Time
    {
    private:
    int hour;
    int minute;
    int sec;
    year y;
    public:
    Time() { hour = minute = sec = 0; }
    Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
    Time(const Time& t):y(t.y)
    {
    hour = t.hour;
    minute = t.minute;
    sec = t.sec;
    cout << "复制构造函数" << endl;
    }//复制构造函数
    void set_time()
    {
    cin >> hour >> minute >> sec;
    return;
    }
    void show_time()
    {
    cout << hour << ":" << minute << ":" << sec << endl;
    }
    };
    int main()
    {
    Time t1(4, 5, 6);
    Time t2 = t1;//发生复制,且调用了各自的复制构造函数
    t1.show_time();
    return 0;
    }

(四)浅拷贝和深拷贝:

  1. 默认拷贝构造函数

    当我们自己未定义任何复制构造函数时,编译器会为我们自动生成一个默认拷贝构造函数,以保证需要复制对象时不会产生问题。

    但是此函数是使用被复制对象的值对复制对象的数据成员进行赋值,不会复制静态数据成员(实际上不会进行任何操作),需要手动添加。

  2. 浅拷贝

    浅拷贝是指对象复制只对对象中的数据成员进行简单赋值(默认构造函数即为浅拷贝)。

    浅拷贝的对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 。如果其中一个对象改变了这个地址,就会影响到另一个对象。

    浅拷贝中,一旦存在动态成员,将会产生大问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    #include <iostream>
    using namespace std;
    class Time
    {
    private:
    int hour;
    int minute;
    int sec;
    int* p;
    public:
    Time() { hour = minute = sec = 0; p = new int(10); }
    Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) { p = new int(5); }
    ~Time()
    {
    delete p;
    }
    void set_time()
    {
    cin >> hour >> minute >> sec;
    return;
    }
    void show_time()
    {
    cout << hour << ":" << minute << ":" << sec << endl;
    }
    };
    int main()
    {
    Time t1(4, 5, 6);
    Time t2(t1);
    t2.show_time();
    return 0;
    }

    运行此代码将会发生p指针指向的动态数组发生了多次释放。

    分析此过程:

    因为浅拷贝是将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。所以,两个不同对象的动态数组指针p地址值其实是相同的,它们指向的是同一个空间

    在销毁对象时,两个对象的析构函数对同一空间进行了两次释放,进而产生了指针悬挂错误。

    我们所需要的是两个动态指针p指向两个不同空间,但是两个空间具有相同的值,而不是两个动态指针p指向同一空间。

    此时需要进行深拷贝。

  3. 深拷贝

    深拷贝是指将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

    深拷贝创建一个新的对象和数组,重新动态分配空间,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,拷贝的是“值”而不是“引用”。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #include <iostream>
    using namespace std;
    class Time
    {
    private:
    int hour;
    int minute;
    int sec;
    int* p;
    public:
    Time() { hour = minute = sec = 0; p = new int(10); }
    Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) { p = new int(10); }
    Time(const Time& t)
    {
    hour = t.hour;
    minute = t.minute;
    sec = t.sec;
    p=new int(*t.p);
    cout << "复制构造函数" << endl;
    }//复制构造函数
    ~Time()
    {
    if (p != NULL) {
    cout << "析构"<<&p << endl;
    delete p;
    p = NULL;
    }
    }
    void set_time()
    {
    cin >> hour >> minute >> sec;
    return;
    }
    void show_time()
    {
    cout << hour << ":" << minute << ":" << sec << endl;
    }
    };
    int main()
    {
    Time t1(4, 5, 6);
    Time t2(t1);//发生复制
    t2.show_time();
    return 0;
    }

    可以看到两次析构函数析构前的动态指针p指向地址不同,说明两个动态指针p各自指向不同的内存空间,且指向的内容相同,此时即有深拷贝。

  4. 防止浅拷贝发生

    声明一个私有复制构造函数,可以不定义,此时如果按值传递或或函数返回该类对象,不会发生浅拷贝错误,可以避免问题。


c++类——复制构造函数(含深浅拷贝)
https://github.com/xiaohei07/xiaohei07.github.io/2023/03/17/c++类——复制构造函数(含深浅拷贝)/
作者
07xiaohei
发布于
2023年3月17日
许可协议