目录
[三、operator 关键字](#三、operator 关键字)
[六、参数为什么用 const 引用?](#六、参数为什么用 const 引用?)
[2.使用 const,防止修改右操作数](#2.使用 const,防止修改右操作数)
[七、为什么返回 Date&?](#七、为什么返回 Date&?)
[八、为什么返回 *this?](#八、为什么返回 *this?)
一、为什么需要运算符重载?
在C++中,内置类型可以直接使用运算符。
例如:
cpp
int a = 10;
int b = 20;
a = b;
cout << (a == b) << endl;
对于 int、double、char 这些内置类型,编译器知道他们应该怎么赋值、怎么比较、怎么相加。
但是对于自定义类型,比如日期类:
cpp
class Date {
private:
int _year;
int _month;
int _day;
};
如果直接对两个日期对象进行计算,编译器不知道该怎么处理。
比如:
cpp
Date d1;
Date d2;
d1 == d2;
对于日期对象来说,判断相等应该比较年、月、日是否都相等。
但是这个规则需要我们告诉编译器。
这就需要用到运算符重载。
运算符重载的作用是:
让自定义类型也能像内置类型一样使用运算符。
二、什么是运算符重载?
运算符重载就是给已有运算符赋予适应自定义类型的新含义。
例如:
cpp
d1 == d2;
可以判断两个日期是否相等。
cpp
d1 = d2;
可以表示把 d2 的年月日赋值给 d1。
这样写比普通函数调用更直观。
比如:
cpp
d1 = d2;
就比下面这种写法自然:
cpp
d1.Copy(d2);
三、operator 关键字
C++中使用 operator 关键字进行运算符重载。
基本格式:
cpp
返回值类型 operator运算符(参数列表) {
// 函数体
}
例如,重载赋值运算符:
cpp
Date& operator=(const Date& d) {
// 赋值逻辑
return *this;
}
这里函数名是:
cpp
operator=
表示重载 = 运算符。
四、成员可以写成成员函数,也可以写成全局函数。
当前阶段先重点掌握成员函数形式。
例如:
cpp
class Date {
public:
Date& operator=(const Date& d) {
_year = d._year;
_monht = d._month;
_day = d._day;
return *this;
}
private:
int _year;
int _month;
int _day;
};
当我们写:
cpp
d1 = d2;
编译器会自动把它转换成类似:
cpp
d1.operator=(d2);
也就是说:
cpp
左操作数 d1 通过 this 指针传递
右操作数 d2 作为参数传递
所以成员函数形式中,双目运算符一般只写一个显示参数。
五、赋值运算符重载
赋值运算符重载用于处理两个已存在对象之间的赋值。
例如:
cpp
Date d1(2024, 5, 1);
Date d2(2025, 6, 1);
d1 = d2;
这里 d1 和 d2 都已经存在。
执行:
cpp
d2 = d1;
本质上把 d2 的内容赋值给 d1。
对应的赋值运算符重载可以这样写:
cpp
Date& operator=(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
完整示例:
cpp
#include <iostream>
using namespace std;
class Date {
public:
Date(int year = 2024, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
Date& operator=(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2024, 5, 1);
Date d2(2025, 6, 1);
d1 = d2;
d1.Print();
d2.Print();
return 0;
}
运行结果:
cpp
2025-6-1
2025-6-1
六、参数为什么用 const 引用?
赋值运算符一般写成:
cpp
Date& operator=(const Date& d)
这里参数使用 const Date&,有两个原因:
1.使用引用,避免拷贝
如果写成传值:
cpp
Date& operator=(Date d)
传参时会产生一次对象拷贝,效率较低。
使用引用可以避免不必要的拷贝。
2.使用 const,防止修改右操作数
赋值时,右边的对象只是被读取,不应该被修改。
例如:
cpp
d1 = d2;
这里应该把 d2 的值赋给 d1,不因该修改 d2。
所以加上const更安全。
七、为什么返回 Date&?
赋值运算符的返回类型通常写成:
cpp
Date&
原因是为了支持连续赋值。
比如:
cpp
d1 = d2 = d3;
赋值运算符是从右往左结合的,所以实际执行顺序可以理解为:
cpp
d1 = (d2 = d3);
如果 d2 = d3 执行完之后没有返回值,外层 d1 = ...,就无法继续进行。
所以 operator= 需要返回当前对象。
八、为什么返回 *this?
再成员函数中,this 指针指向的是当前对象。
对于:
cpp
d1 = d2;
调用形式可以理解为:
cpp
d1.operator=(d2);
此时:
cpp
this 指向 d1
所以:
cpp
*this
就是当前对象 d1 本身。
因此赋值运算符最后通常返回:
cpp
return *this;
这样就能支持连续赋值:
cpp
d1 = d2 = d3;
九、自我赋值检查
有时候可能会出现自己给自己赋值:
cpp
d1 = d1;
对于日期类之中只有普通变量的类,即使不检查通常也不会出问题。
但对于含有动态资源的类,比如栈类,如果赋值时要先释放旧的空间,再申请新的空间,就必须注意自我复制的问题。
常见写法是:
cpp
Date& operator=(const Date& d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
这里:
cpp
this != &d
表示判断当前对象和右操作数是否是同一个对象。
如果是同一个对象,就不需要再赋值。
十、拷贝构造和赋值运算符的区别
可以这样记:
cpp
对象还没创建:拷贝构造
对象已经存在:赋值运算符
对比表:
| 写法 | 含义 | 调用 |
|---|---|---|
Date d2(d1); |
用 d1 初始化新对象 d2 | 拷贝构造 |
Date d2 = d1; |
用 d1 初始化新对象 d2 | 拷贝构造 |
d2 = d1; |
d2 已经存在,把 d1 赋给 d2 | 赋值运算符 |
十一、运算符重载的基本规则
运算符重载不是随便定义的,需要遵守一些规则。
1.不能创造新的运算符
C++中没有的运算符不能自己创造。
比如不能写:
cpp
operator@
只能重载C++已经存在的运算符。
2.不能改变操作数个数
比如 + 是双目运算符,重载后仍然需要两个操作数。
不能把它变成三个操作数。
3.不能改变优先级和结合性
运算符重载不能改变原来的运算符优先级。
比如:
cpp
a + b * c
仍然是先算 *,再算 +。
4.至少有一个操作数是自定义类型
不能给内置类型乱改规则。
例如不能把:
cpp
1 + 2
重载结果为 100。
运算符重载主要是为了让自定义类型更好用。
5.不能重载的运算符
有些运算符不能重载,常见有:
cpp
:: 作用域限定符
sizeof 计算大小
?: 三目运算符
. 成员访问运算符
.* 成员指针访问运算符
这些运算符有特殊语义,不能被用户重载。
十二、小结
本篇主要学习了运算符重载基础和赋值运算符重载。
需要记住:
- 运算符重载可以让自定义类型使用已有运算符;
- 运算符重载通过 operator 关键字实现;
- 成员函数形式中,左操作数通过 this 指针传递;
- d1 = d2 会转换成 d1.operator(d2);
- 赋值运算符重载用于两个已存在的对象之间赋值;
- 参数一般使用 cosnt 类名&;
- 返回值一般使用 类名&;
- 返回 *this 是为了方便支持连续赋值;
- 拷贝构造和赋值运算符的区别在于对象是否已经存在;
- 运算符重载不能创建新运算符,也不能改变优先级和操作数个数。
赋值运算符重载十运算符重载中的重要内容。理解它之后,后面学习日期类中的比较运算符、加减运算符、自增自减运算符会更容易。