zoukankan      html  css  js  c++  java
  • 深拷贝与浅拷贝、空类与空数组

    一、深拷贝与浅拷贝

    说得简单点,假设一个类有指针成员,如果在拷贝的时候顺带连指针指向的内存也分配了,就称为深拷贝,如下图(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++编程规范

  • 相关阅读:
    .NET开源工作流RoadFlow-表单设计-组织机构选择
    .NET开源工作流RoadFlow-表单设计-按钮
    .NET开源工作流RoadFlow-表单设计-标签(label)
    git客户端使用
    简单几句概括join
    算法笔记:线段树
    通常情况下的中国剩余定理
    NOIP2016:Day2解题报告
    关于jzyzoj——P1341:被污染的牛奶的题解探讨
    关于错位排列
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8471019.html
Copyright © 2011-2022 走看看