关于类的默认生成函数

类有一些特殊的函数,在我们不显式定义的情况下,编译器会自动生成。主要包括以下函数:

  1. 默认构造函数
  2. 默认析构函数
  3. 拷贝(复制)构造函数
  4. 赋值运算符(=)
  5. 地址运算符(&)
#include <iostream>
using namespace std;
class A
{
public:
    A() // 自实现的默认构造函数
    {
        this->a = 100;
        cout << "A()" << endl;
    }
    
    A(int a) // 普通构造函数 
    {
        this->a = a;
        cout << "A(int a)" << endl;
    }
    A(const A &a) // 拷贝(复制)构造函数
    {
        this->a = a.a;
        cout << "A(const A &a)" << endl;
    }
    
    A &operator=(const A &a) // 赋值运算符(重载=,需要返回引用)
    {
        this->a = a.a;
        cout << "A &operator=(A a)" << endl;
        return *this;
    }
    
    ~A()
    {
        cout << "~A()" << endl;
    }
public:
    int GetA()
    {
        return this->a;
    }
    
private:
    int a;
};


int main()
{
    // 默认构造函数 会触发析构
    A a;
    cout << a.GetA() << endl;
    
    // 调用普通带参构造函数 会触发析构
    A b = A(200); 
    cout << b.GetA() << endl;
    
    // 赋值运算符 不会触发析构
    b = a;
    cout << b.GetA() << endl;
    
    // 调用拷贝构造 会触发析构
    A c = a; 
    cout << c.GetA() << endl;
    
    return 0;
}

对于编译器来说,在对象进行按值传递的时候,如函数传参或函数返回对象时,总会调用一次拷贝构造函数。

也就是说,按值传递对象会创建对象的一个副本,程序就会多进行一次构造和析构函数的调用。

如上的例子,实现赋值运算符时,传参类型是引用,返回值也是引用。这就省去了两次拷贝构造和两次析构的调用。

因此,在我们编写程序的时候,对象传参或返回对象时,最好能够进行引用传递,如果不希望修改这个对象被修改,可以加上const。

 

另外,默认拷贝构造函数和赋值运算符进行的都是浅拷贝(值拷贝),而非深拷贝(内容拷贝)。看下面的例子:

#include <iostream>
#include <cstring>
using namespace std;
class A
{
public:
    A()
    {
        a = 100;
        p = new char[100];
        const char *s = "hello C++";
        memcpy(p, s, strlen(s) + 1);
    }
    
    ~A()
    {
        delete []p;
        p = NULL;
    }
int a; char *p; }; int main() { A a; A b = a; cout << a.p << endl; cout << b.p << endl; // 修改a.p的值,期望是不会影响b.p memcpy(a.p, "hello Java", strlen("hello Java") + 1); cout << a.p << endl; cout << b.p << endl; // 代码最终会异常退出,double free or corruption // 因为b.p的值与a.p的值是一样的 // a,b分别对同一块内存进行释放了两次 return 0; }

一般来说,此时需要自己实现拷贝构造函数、赋值运算符、析构函数。

#include <iostream>
#include <cstring>
using namespace std;

const char *cpp = "hello C++";
const char *java = "hello Java";

class A
{
public:
    A()
    {
        len = 100;
        p = new char[len];
        int iLen = len > strlen(cpp) + 1 ? strlen(cpp) + 1 : len;
        memcpy(p, cpp, iLen);
    }
    
    ~A()
    {
          delete []p;
          p = NULL;
    }
    
    A(const A &a)
    {
        this->len = a.len;
        this->p = new char[a.len];
        int iLen = this->len > strlen(a.p) + 1 ? strlen(a.p) + 1 : this->len;
        memcpy(this->p, a.p, iLen);
    }
    
    A& operator =(const A &a)
    {
        if (this == &a)// 避免自我赋值
        {
            return *this;
        }
        delete []this->p; // 删除原先的内容,否则造成内存泄露,这里固定
        this->len = a.len;
        this->p = new char[a.len];
        int iLen = this->len > strlen(a.p) + 1 ? strlen(a.p) + 1 : this->len;
        memcpy(this->p, a.p, iLen);
        return *this;
    }

    int len;
    char *p;
};
int main()
{

    A a;
    A b = a;
    cout << a.p << endl;
    cout << b.p << endl;
    
    // 修改a.p的值,期望是不会影响b.p
    memcpy(a.p, java, strlen(java) + 1);
    
    cout << a.p << endl;
    cout << b.p << endl; 
    
    b = a;
    cout << a.p << endl;
    cout << b.p << endl; 
    
    // 修改a.p的值,期望是不会影响b.p
    memcpy(a.p, cpp, strlen(cpp) + 1);
    
    cout << a.p << endl;
    cout << b.p << endl; 
    
    return 0;
}

以上实现赋值运算符时,有三点需要注意:

  1. 传参 const typename &, 返回typename &
  2. 避免自身赋值 先判断this与传入参数的地址是否是同个对象
  3. 先释放之前动态内存,避免内存泄露

 

posted @ 2019-06-08 15:45  N0b0dy  阅读(495)  评论(0编辑  收藏  举报