• 深拷贝与浅拷贝、空类与空数组


    一、深拷贝与浅拷贝

    说得简单点,假设一个类有指针成员,如果在拷贝的时候顺带连指针指向的内存也分配了,就称为深拷贝,如下图(v2 从 v 拷贝而来):


    如果只是分配指针本身的内存,那就是浅拷贝,如下图:


    浅拷贝造成的问题是有两个指针指向同块内存,delete 其中一个指针,那么剩下的指针将成为野指针。编译器合成的默认拷贝构造函数和赋值运算符是浅拷贝的,如果只是普通成员的赋值,浅拷贝也是可以的。

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    #ifndef _STRING_H_
    #define _STRING_H_

    class String
    {
    public:
        String(char *str = "");
        ~String();
        String(const String &other);
        String &operator=(const String &other);



        void Display();

    private:
        char *AllocAndCpy(char *str);

        char *str_;
    };

    #endif // _STRING_H_
     C++ Code 
    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 "String.h"
    //#include <string.h>
    #include <cstring>
    #include <iostream>
    using namespace std;

    String::String(char *str/* = */)
    {
        str_ = AllocAndCpy(str);
    }

    String::~String()
    {
        delete[] str_;
    }

    String::String(const String &other)
    {
        str_ = AllocAndCpy(other.str_);
    }

    String &String::operator =(const String &other)
    {
        if (this == &other)
            return *this;

        delete[] str_;
        str_ = AllocAndCpy(other.str_);
        return *this;
    }

    char *String::AllocAndCpy(char *str)
    {
        int len = strlen(str) + 1;
        char *tmp = new char[len];
        memset(tmp, 0, len);
        strcpy(tmp, str);
        return tmp;
    }

    void String::Display()
    {
        cout << str_ << endl;
    }
     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    #include "String.h"

    int main(void)
    {
        String s1("AAA");
        s1.Display();
        String s2 = s1;     // 调用拷贝构造函数
        // 系统提供的默认拷贝构造函数实施的是浅拷贝 s2.str_ = s1.str_

        String s3;
        s3.Display();
        s3 = s2;            // 调用等号运算符
        // 系统提供的默认等号运算符实施的是浅拷贝 s3.str_ = s2.str_;
        // s3.operator=(s2);
    s3.Display();
        // 要让对象是独一无二的,我们要禁止拷贝
        // 方法是将拷贝构造函数与=运算符声明为私有,并且不提供它们的实现
        return 0;
    }

    上面程序中String 类有一个char* str_ 成员,故实现了深拷贝,这样不会造成内存被释放两次的错误,或者修改指针指向的内存会影响另一个对象的错误。此外,如果我们想让对象是独一无二的,需要禁止拷贝,只需要将拷贝构造函数和等号运算符声明为私有,并且不提供它们的实现。

    注意:在编写派生类的赋值函数时,不要忘记对基类的数据成员重新赋值,可以通过调用基类的赋值函数来实现,比如在

    Derived& Derived::operator=(const Derived& other) { } 中调用Base::operator=(other);

    注:尽量不要把 string 类型变量当作结构体成员,因为有些函数对整个结构体进行拷贝时默认就是 memcpy,即对于 string 变量来说只拷贝了指针,这可能会造成一些莫名其妙的bug,比如指针地址被重用而导致指针指向的内容可能被覆盖重写。--踩过的坑


    二、空类与空数组

    空类默认产生的成员:

    class Empty {};
    Empty(); // 默认构造函数 Empty( const Empty& ); // 默认拷贝构造函数 ~Empty(); // 默认析构函数 Empty& operator=( const Empty& );  // 默认赋值运算符 Empty* operator&();               // 取址运算符 const Empty* operator&() const;    // 取址运算符 const

     C++ Code 
    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
     
    #include <iostream>
    using namespace std;

    class Empty
    {
    public:
        Empty *operator&()
        {
            cout << "AAAA" << endl;
            return this;
        }

        const Empty *operator&() const
        {
            cout << "BBBB" << endl;
            return this;
        }
    };

    int main(void)
    {
        Empty e;
        Empty *p = &e;      // 等价于e.operator&();

        const Empty e2;
        const Empty *p2 = &e2;

        cout << sizeof(Empty) << endl;

        return 0;
    }


    单步调试一下,可以看到分别调用了两个取地址运算符函数,而且空类的大小为1个字节。

    体会一下下面的程序结果:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     
    #include<iostream>
    using namespace std;
    int main()
    {
        int a[0];
        class B {};
        struct C
        {
            int  m;
            int  n;
            char buffer[];
        };
        class D
        {
            int  s[0];
        };

        cout << "sizeof(a)=" << sizeof(a) << endl; //0
        cout << "B{}=" << sizeof(B) << endl; //1
        cout << "C=" << sizeof(C) << endl; //8
        cout << "D=" << sizeof(D) << endl; //0

        return 0;
    }


    参考:

    C++ primer 第四版
    Effective C++ 3rd
    C++编程规范

  • 相关阅读:
    Leetcode练习(Python):链表类:第206题:反转链表:反转一个单链表。
    Leetcode练习(Python):链表类:第203题:移除链表元素:删除链表中等于给定值 val 的所有节点。
    Leetcode练习(Python):链表类:第160题:相交链表:编写一个程序,找到两个单链表相交的起始节点。
    Leetcode练习(Python):链表类:第141题:环形链表:给定一个链表,判断链表中是否有环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
    Leetcode练习(Python):链表类:第83题:删除排序链表中的重复元素:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
    【Java基础总结】数据库编程
    【Java基础总结】多线程
    特迷茫的大三时期
    解决忘记了开机密码,无法进入系统的方法
    一开机未通过输入密码登录,就出现用户名或密码错误??
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8471019.html
Copyright © 2020-2023  润新知