编写派生的赋值运算符重载函数

📅 2026/7/12 23:47:22 👁️ 阅读次数 📝 编程学习
编写派生的赋值运算符重载函数

编写派生的赋值运算符重载函数

一、核心概念

赋值运算符operator=用于将一个已存在的对象赋值给另一个已存在的对象。在继承关系中,派生类的赋值运算符需要负责赋值基类部分派生类新增部分

二、赋值运算符 与 拷贝构造函数

对比项拷贝构造函数赋值运算符
调用时机创建新对象时已有对象赋值时
目标对象不存在(新创建)已存在
是否需要释放旧资源不需要可能需要
语法Student b(a)Student b = ab = a

三、派生类赋值运算符的调用规则

3.1 规则总结

派生类赋值运算符调用规则 1. 派生类赋值运算符必须赋值基类部分 ↓ 2. 如果派生类没有定义赋值运算符,编译器会自动生成 ↓ 3. 编译器生成的赋值运算符会自动调用基类的赋值运算符 ↓ 4. 如果派生类自定义了赋值运算符,编译器不会自动调用基类版本 → 必须显式调用!

四、四种场景详细分析

4.1 场景一:基类和派生类都没有定义赋值运算符

classPerson{int_idPerson;public:Person(intid=1):_idPerson(id){}// 没有定义 operator= → 编译器自动生成按位赋值};classStudent:publicPerson{int_snum;public:Student(intid=1,ints=1):Person(id),_snum(s){}// 没有定义 operator= → 编译器自动生成// 编译器生成的赋值运算符会自动调用基类的赋值运算符};intmain(){Studentstuda(111111,22222);Studentstudb(333333,44444);studb=studa;// 使用编译器生成的赋值运算符return0;}

编译器生成的代码(概念上)

Student&operator=(constStudent&other){if(this!=&other){Person::operator=(other);// 自动调用基类赋值运算符_snum=other._snum;// 按位赋值派生类成员}return*this;}

4.2 场景二:基类定义了赋值运算符,派生类没有定义

classPerson{private:int_idPerson;public:Person(intid=1):_idPerson(id){}// 基类定义了赋值运算符Person&operator=(constPerson&per){if(this!=&per){_idPerson=per._idPerson;}cout<<"Person::operator="<<endl;return*this;}};classStudent:publicPerson{private:int_snum;public:Student(intid=1,ints=1):Person(id),_snum(s){}// 没有定义 operator= → 编译器自动生成// 编译器会自动调用基类的赋值运算符};intmain(){Studentstuda(111111,22222);Studentstudb(333333,44444);cout<<"=== 赋值前 ==="<<endl;studa.PrintStudent();studb.PrintStudent();studb=studa;// 调用编译器生成的赋值运算符cout<<"\n=== 赋值后 ==="<<endl;studa.PrintStudent();studb.PrintStudent();return0;}

输出

Person(int) Student(int, int) Person(int) Student(int, int) === 赋值前 === 身份证号:111111 学号:22222 身份证号:333333 学号:44444 Person::operator= ← 自动调用基类赋值运算符 === 赋值后 === 身份证号:111111 学号:22222 身份证号:111111 学号:22222

4.3 场景三:基类和派生类都定义了赋值运算符(正确做法)

classPerson{private:int_idPerson;public:Person(intid=1):_idPerson(id){}Person&operator=(constPerson&per){if(this!=&per){_idPerson=per._idPerson;}cout<<"Person::operator="<<endl;return*this;}};classStudent:publicPerson{private:int_snum;public:Student(intid=1,ints=1):Person(id),_snum(s){}// 正确:显式调用基类赋值运算符Student&operator=(constStudent&stud){if(this!=&stud){Person::operator=(stud);// 关键!调用基类赋值运算符_snum=stud._snum;}cout<<"Student::operator="<<endl;return*this;}};intmain(){Studentstuda(111111,22222);Studentstudb(333333,44444);studb=studa;return0;}

输出

Person::operator= ← 显式调用基类赋值运算符 Student::operator=

4.4 场景四:派生类定义了赋值运算符,但没有调用基类版本(危险!)

classPerson{private:int_idPerson;public:Person(intid=1):_idPerson(id){}Person&operator=(constPerson&per){if(this!=&per){_idPerson=per._idPerson;}cout<<"Person::operator="<<endl;return*this;}};classStudent:publicPerson{private:int_snum;public:Student(intid=1,ints=1):Person(id),_snum(s){}// 错误!没有调用基类赋值运算符Student&operator=(constStudent&stud){if(this!=&stud){// Person::operator=(stud); // 遗漏了!_snum=stud._snum;}cout<<"Student::operator="<<endl;return*this;}};intmain(){Studentstuda(111111,22222);Studentstudb(333333,44444);studb=studa;// 问题:studb 的基类部分(_idPerson)没有被赋值!// studb._idPerson 仍然是 333333,而不是 111111return0;}

五、四种场景对比总结

场景基类有 operator=派生类有 operator=派生类是否调用基类版本结果
1编译器生成编译器生成自动调用正确
2用户定义编译器生成自动调用正确
3用户定义用户定义显式调用正确
4用户定义用户定义未调用错误(基类部分未赋值)

六、错误写法与正确写法对比

6.1 错1:忘记调用基类赋值运算符

Student&operator=(constStudent&stud){if(this!=&stud){// 忘记调用 Person::operator=(stud)_snum=stud._snum;}return*this;}// 问题:基类成员没有被赋值!

6.2 错2:无穷递归引起栈溢出

Student&operator=(constStudent&stud){if(this!=&stud){operator=(stud);// 死递归!调用的是自己_snum=stud._snum;}return*this;}

6.3 错3:错误的类型转换

Student&operator=(constStudent&stud){if(this!=&stud){((Person*)this)->operator=(stud);// ! 可以但不推荐_snum=stud._snum;}return*this;}

6.4 正确写法

Student&operator=(constStudent&stud){if(this!=&stud){Person::operator=(stud);// 显式调用基类版本_snum=stud._snum;}cout<<"Student::operator="<<endl;return*this;}

七、关键要点总结

要点说明
必须调用基类赋值运算符派生类赋值运算符必须显式调用基类版本
语法Person::operator=(stud);
不调用的后果基类成员不会被赋值,导致对象状态不完整
编译器自动生成如果派生类没有定义,编译器会自动生成并调用基类版本
与拷贝构造的区别拷贝构造自动调用基类版本;赋值运算符需要显式调用
自赋值检查必须先检查this != &other