首页 > C/C++ > 侯捷-C++面向对象程序设计-学习笔记-07-c++三大函数-拷贝构造函数、拷贝赋值函数和析构函数

侯捷-C++面向对象程序设计-学习笔记-07-c++三大函数-拷贝构造函数、拷贝赋值函数和析构函数

2020年8月12日
分类: C/C++ 标签: ,

首先明确两个概念。

  • 拷贝构造:通过已有对象来【构造】一个新的对象,这个是在对象的构造过程中发生的,会调用拷贝构造函数
  • 拷贝赋值:用另一个对象给已存在的对象赋予新的值,这是为对象整体赋予新值时发生的,会调用拷贝赋值函数
    • 拷贝赋值函数就是类中重载了赋值运算符(=)的那个函数

每个类都拥有拷贝构造函数和拷贝赋值函数,如果我们没有对这两个函数进行重载,编译器会生成默认的函数,其拷贝操作就是简单的【按位拷贝】(也称为【浅拷贝】),把源对象中成员变量的数据原封不动地拷贝到目标对象中。

侯捷老师把c++类分为带指针成员变量的类和不带指针成员变量的类。

对于不含有指针类型成员变量的类,使用编译器提供的默认拷贝函数是没有问题的。

而对于含有指针类型成员变量的类,按位拷贝会造成两个对象拥有相同指针指向的同一块内存空间。如果两者同时操作(读取、写入、释放)这块内存,很可能会造成脏数据、内存泄露等问题。

因此,针对包含指针类型成员变量的类,我们需要自己来实现以下三个函数:拷贝构造函数、拷贝赋值函数和析构函数。这三个函数也称为c++类的Big Three。

字符串类是一个经典的包含指针的类,我们看一下字符串类的构造函数和Big Three函数是如何实现的。

1,String类的声明

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

    private:
        char* m_data;
};

String类有一个m_data指针,用于指向存储字符串的内存空间。

2,构造函数

inline String::String(const char* cstr)
{
    //判断是否为空指针
    if(cstr){
        m_data = new char[strlen(cstr)+1];//多分配一个字节
        strcpy(m_data, cstr);
    }
    else{
        m_data = new char[1];
        *m_data = '\0';
    }
}

构造函数用于从一个c形式的字符串构造一个String对象。对于指针类型的形参,首要的工作就是判断是否为空指针。

为String的m_data分配内存时需要额外分配一个字节,用于存储结束字符。

3,拷贝构造函数

String::String(const String& str){
    m_data = new char[strlen(str.m_data) + 1];
    strcpy(m_data, str.m_data);
}

拷贝构造函数接受一个String类型的对象,为m_data分配内存,并复制源字符串str.m_data所指内存中的数据。这种拷贝称为【深拷贝】。

有两种形式创建String对象时会调用拷贝构造函数。

String s1('Hello');
String s2(s1);
String s3 = s1;

s2和s3都是通过s1来【初始化】,因此都会调用拷贝构造函数。

【还有一些场景会隐式调用拷贝构造函数。比如:

以值的方式传递对象参数;以值的方式返回对象;向容器中存放对象】

4,拷贝赋值(运算符)函数

String& operator=(const String& str){
    //检测自身赋值
    if(this == &str){
        return *this;
    }

    //释放原有内存
    delete []m_data;

    //执行深拷贝
    m_data = new char[strlen(str.m_dadta) + 1];
    strcpy(m_data, str.m_data);

    return *this;
}

赋值运算符的重载有几点细节:

  • 入参为const类型的引用,提高效率
  • 需要判断是否自身赋值,即s=s。这种情况没必要进行后续的内存操作,直接返回即可
  • 需要释放目标对象已有的内存,避免内存泄露
  • 需要重新给m_data分配内存空间,并copy源str.m_data所指内存存放的内容,这里也是【深拷贝】。
  • 返回一个目标对象的引用,这样可以使得赋值运算符应用在连续赋值的场景,即s1=s2=s3

拷贝赋值函数是在已存在的对象之间赋值时才调用的,一定要和构造函数的使用场景区分开来。

String s1("hello"), s2('World');
s2 = s1;

5,析构函数

inline String::~String(){
    delete []m_data;
}

我们看到构造函数、拷贝构造函数和拷贝赋值函数都为m_data分配了内存,那么在对象销毁时,要记得释放这块内存。

这项工作一般在析构函数中实现。编译器会在对象销毁时自动调用析构函数,确保内存得到了释放。

 

————————-笔记分割线——————–

【侯捷老师推荐的书籍】

您可能需要这些【参考资料】:
零一积流|IT参考 原创文章,转载请注明出处: http://www.it-refer.com/2020/08/12/houjie-cplusplus-object-oriented-programming-07-big-three-functions-copy-constructor-copy-assignment-destructor


本文的评论功能被关闭了.