C++ 复制构造函数 与 赋值运算符

在C++中,将一个对象赋给另外一个对象,编译器将提供赋值运算符的定义。

有两种情况,下面假设catman是Monster的一个实例

第一种:初始化

Monster golblen= catman;

第二种:普通赋值

Monster golblen;

golblen= catman;

复制构造函数

其中,第一种情况,系统将调用复制构造函数,其原型为

Monster(const Monster& monster);

如果Monster里没有提供该函数,编译器将自动提供,编译器自动提供时

Monster monster3 = monster1;

相当于

Monster monster3;
monster3. name = monster1.name

由于monster3的name实际上共享monster1的name,所以这种复制叫做“浅复制”,两个对象共享同一份成员变量,在析构时会报错。

解决错误的方式是自己定义一个复制构造函数,并且自己为name开辟空间,将monster1的name使用strcpy_s的方式拷贝到自己开辟的空间里

顺带一提,monster2是用new创建的,new出来的对象存活于堆中,其他两个对象存活于栈中,系统只会自动释放栈内空间,而堆内空间需要用户自己维护。

Monster类声明

#pragma once
#include <iostream>;

using std::ostream;
using std::cin;
using std::cout;
using std::endl;

class Monster {

private:
    char* name;

public:
    Monster();
    Monster(const char* name);
    Monster(const Monster& monster);
    ~Monster();
    friend ostream& operator<<(ostream& os, const Monster& monster);
};

Monster类定义

#include "Monster.h";

Monster::Monster()
{
    cout << "默认构造函数执行完毕" << endl;
}

Monster::Monster(const char* name)
{
    this->name = new char[strlen(name) + 1];
    strcpy_s(this->name, strlen(name) + 1, name);
    cout << "构造函数执行完毕" << endl;
}

//复制构造函数,如果没有定义,编译器将自动提供浅复制方式的复制构造函数
Monster::Monster(const Monster& monster)
{
    this->name = new char[strlen(monster.name) + 1];
    strcpy_s(this->name, strlen(monster.name) + 1, monster.name);
    cout << "复制构造函数执行完毕" << endl;
}

Monster::~Monster()
{
    cout << name << "has been destroyed." << endl;
    delete[] name;
}
ostream& operator<<(ostream& os, const Monster& monster)
{
    return os << monster.name;
}

int main(void)
{
    {
        Monster monster1("Golblen");
        cout << "monster1: " << monster1 << " online" << endl;
            
        //下面这种初始化方式,将会调用复制构造函数
        Monster monster2 = monster1;
        cout << "monster2: " << monster2 << " online" << endl;

        Monster monster3;
        monster3 = monster1;
        cout << "monster3: " << monster2 << " online" << endl;
    }
    cin.get();
    return 0;
}

运行结果

报错了,看来赋值的时候,并没有调用自定义的复制构造函数,所以释放name时出错了

赋值运算符

解决上面报错的问题,自需要增加一个成员函数,

函数原型

Monster& operator=(const Monster& monstere);

函数定义

Monster& Monster::operator=(const Monster& monster)
{
    if (this == &monster)
    {
        return *this;
    }
    delete[] this->name;
    int length = strlen(monster.name);
    name = new char[length+1];
    strcpy_s(name, length + 1, monster.name);
    cout << "赋值运算符调用完毕" << endl;
    return *this;
}

定义赋值运算符时,必须要做的三件事情:

1.由于目标对象已经引用了之前分配的数据,所以一定要使用delete[]来释放这些数据,否则将出现内存泄漏

2.函数应当避免将对象赋给自身,否则做第1步操作释放数据时,会将对象的数据也释放掉

3.函数应当返回一个指向当前对象的调用

另外,赋值运算符只能由类成员函数重载,为什么呢 (见https://bbs.csdn.net/topics/392049284?page=1 )

1:对于赋值操作符(=)--比较特别,因为任何类如果不提供显示的拷贝赋值(即重载=),则编译器会隐式地提供一个。这样的话,如果你再通过友元声明,进行全局的定义会造成调用二义性(即使允许,编译也会出错)

2:对于所有楼主提到的操作符(=,[],(),->),只能声明为成员函数是为了避免不合法的书写通过编译(这是推测出的原因,更深层的可能要研究 C++ 的设计了)

原文地址:https://www.cnblogs.com/heben/p/9411035.html