zoukankan      html  css  js  c++  java
  • 类(二)——拷贝控制(浅拷贝,深拷贝,浅赋值,深赋值)

    一、拷贝构造函数

    浅拷贝:

    1、如果类未定义自己的拷贝构造函数,编译器会为它合成一个默认拷贝构造函数(默认合成的是public的)。

    拷贝构造函数从来不显式调用,而是由编译器隐式地调用。在以下三种情况:

    (1)定义对象
    Object a;
    Object b(a); // 或写成 Object b = a;

    (2)动态创建对象
    Object a;
    Object* p = new Object(a);

    (3)函数的传值调用
    void Test(Object obj);

    2、区分构造与赋值:
    构造:
    Object a;
    Object b = a; // 或写作 Object b(a);
    // 此为“构造”,在创建对象的时候给初值,拷贝构造函数被调用

    赋值:
    Object a(1, 2);
    Object b;
    b = a; // 此为“赋值”,不会调用拷贝构造函数,调用的是拷贝赋值运算符

    3、注意:除非深拷贝(即类中含有指针成员时),否则不要人为定义拷贝构造函数,使用编译器合成的默认拷贝构造函数即可。

    一旦你决定了要添加拷贝构造函数,请仔细检查:

    (1)所有的成员变量,要依次拷贝,所有成员变量,不能遗漏

    (2)记得调用父类的拷贝构造函数

    深拷贝(类中有指针成员):

    当类有指针成员的时候,需要人为定义拷贝构造函数,而不能继续使用编译器默认合成的。

    因为默认的拷贝构造函数只会单纯的复制指针,而不会把指针指向的对象的值保存到一块新开辟的内存中。

     看如下例子:

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 class Text
     6 {
     7 public:
     8     Text(string s)
     9     {
    10         size = s.length();
    11         p = new string(s);
    12     }
    13     ~Text()
    14     {
    15         delete p;   //默认析构函数只会销毁成员指针,不会delete成员指针指向的对象,所以此处要人为定义一个析构函数。
    16     }
    17 private:
    18     int size;
    19     string* p;
    20 };
    21 
    22 int main()
    23 {
    24     Text t1("hello");
    25     Text t2(t1);    //调用拷贝构造函数
    26 }

    此程序运行时崩溃,主要原因是:

    // 对象创建
    t1 的指针对象p,指向一块内存
    t2 拷贝了 t1, 此时 t2.p 和 t1.p 指向了同一块内存

    // 对象析构
    对象t1析构, t1.p 被delete,释放其指向对象的内存;
    对象t2析构,t2.p被delete,但因为 t2.p 和 t1.p 指向了同一块内存,此块内存已经被delete,所以崩溃。(同一块内存被重复delete)

    解决办法:人为定义自己的拷贝构造函数(深拷贝),程序如下:

     1 #include <iostream>
     2 #include <string>
     3 using namespace std;
     4 
     5 class Text
     6 {
     7 public:
     8     Text() = default;    //默认构造
     9     Text(string s)       //构造函数
    10     {
    11         size = s.length();
    12         p = new string(s);
    13     }
    14     Text(const Text& t)    //需要人为定义自己的深拷贝构造函数
    15     {
    16         this -> size = t.size;
    17         this -> p = new string(*t.p);
    18     }
    19     ~Text()
    20     {
    21         delete p;   //默认析构函数只会销毁成员指针,不会delete成员指针指向的对象,所以此处要人为定义一个析构函数。
    22     }
    23 private:
    24     int size;
    25     string* p;
    26 };
    27 
    28 int main()
    29 {
    30     Text t1("hello");
    31     Text t2 = t1;   //调用拷贝构造函数
    32 }

    三、拷贝赋值运算符

    1与拷贝构造函数一样,如果类未定义自己的拷贝赋值运算符,编译器会为它合成一个默认的拷贝赋值运算符(默认合成的是public的)。  (浅赋值)

    2、除非类中含有指针成员时,否则不要人为定义拷贝赋值运算符,使用编译器默认合成的即可。  (深赋值)

     

    看如下例子:

     1 #include <iostream>
     2 #include <string>
     3 using namespace std;
     4 
     5 class Text
     6 {
     7 public:
     8     Text() = default;    //默认构造
     9     Text(string s)       //构造函数
    10     {
    11         size = s.length();
    12         p = new string(s);
    13     }
    14 
    15     ~Text()
    16     {
    17         delete p;   //默认析构函数只会销毁成员指针,不会delete成员指针指向的对象,所以此处要人为定义一个析构函数。
    18     }
    19 private:
    20     int size;
    21     string* p;
    22 };
    23 
    24 int main()
    25 {
    26     Text t1("hello");
    27     Text t2;
    28     t2 = t1;  //调用赋值运算符
    29 }

    运行时会崩溃,原因与深拷贝原因一样,编译器合成的默认赋值运算符只是拷贝了指针,没有新开辟内存,导致同一对象的内存重复被delete。

    解决方法:深赋值(与深拷贝类似),人为定义一个拷贝赋值运算符

      注意: (1) 要记得释放原左侧对象的内存,之后再把右侧对象赋值给左侧对象。

                  (2) 返回值为类类型的引用,即返回本对象

     1 #include <iostream>
     2 #include <string>
     3 using namespace std;
     4 
     5 class Text
     6 {
     7 public:
     8     Text() = default;    //默认构造
     9     Text(string s)       //构造函数
    10     {
    11         this->size = s.length();
    12         p = new string(s);
    13     }
    14     Text& operator = (const Text& t)
    15     {
    16         auto newp = new string(*t.p);  //拷贝底层的string
    17         delete this->p;                //释放旧内存
    18         this->p = newp;                //从右侧运算对象拷贝数据到本对象
    19         this->size = t.size;
    20         return *this;                  //返回本对象
    21     }
    22 
    23     ~Text()
    24     {
    25         delete p;   //默认析构函数只会销毁成员指针,不会delete成员指针指向的对象,所以此处要人为定义一个析构函数。
    26     }
    27 private:
    28     int size;
    29     string* p;
    30 };
    31 
    32 int main()
    33 {
    34     Text t1("hello");
    35     Text t2;
    36     t2 = t1;  //调用赋值运算符
    37 }
  • 相关阅读:
    pycharm使用常见设置
    LeetCode OJ:Insertion Sort List (插入排序链表)
    LeetCode OJ:Reverse Linked List (反转链表)
    Foundations of Qt Development 学习笔记 Part1 Tips1-50
    TCPL学习毕节:第六章hash表
    TCPL学习笔记:4-12以及4-13。关于使用递归的问题。
    几种常见排序算法的C++描述
    一些灵巧的求并算法
    vs中: 错误,未定义的标识符getline 的解决方法
    QT中给程序加上主界面的图标
  • 原文地址:https://www.cnblogs.com/FengZeng666/p/9340690.html
Copyright © 2011-2022 走看看