在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++ 的设计了)