文章目录
- [1. 运算符重载](#1. 运算符重载)
-
- [1.1. 运算符重载的意义](#1.1. 运算符重载的意义)
- [1.2 运算符重载应当有意义](#1.2 运算符重载应当有意义)
- [1.3 运算符重载的定义](#1.3 运算符重载的定义)
- [1.4 运算符重载的使用注意事项](#1.4 运算符重载的使用注意事项)
- [1.5 运算符重载的创建](#1.5 运算符重载的创建)
-
- [1.5.1 创建重载运算符" == "](#1.5.1 创建重载运算符“ == ”)
- [1.6 运算符重载的调用](#1.6 运算符重载的调用)
- [1.7 重载为成员函数的调用](#1.7 重载为成员函数的调用)
-
- [1.7.1 隐式调用](#1.7.1 隐式调用)
- [1.7.2 显式调用](#1.7.2 显式调用)
- [1.7.3 成员函数重载的关键点](#1.7.3 成员函数重载的关键点)
- [1.8 调用常见问题:访问限制](#1.8 调用常见问题:访问限制)

1. 运算符重载
1.1. 运算符重载的意义
内置运算符主要作用于内置类型和指针等。对于类类型对象,C++ 允许通过运算符重载为运算符指定新的含义,使自定义类型也能像内置类型一样参与运算。当运算符作用于类类型对象时,编译器会进行重载解析:候选包括成员 operator@、非成员 operator@ 以及内置运算符。若找到最佳匹配,则调用对应函数或内置运算符;若找不到可行候选,则编译报错。内置类型之间的运算符不可重载,且运算符的优先级、结合性和操作数个数不能改变。
所有运算符重载不是"修改运算符本身的含义",而是为特定类类型或枚举类型提供运算符函数,让自定义类型能像内置类型一样参与运算,并融入 C++ 的重载解析和泛型体系
1.2 运算符重载应当有意义
运算符重载应当有意义。一个类需要重载哪些运算符,不是看哪些运算符"能"重载,而是看哪些运算符作用于该类对象时具有自然、直观、符合领域惯例的语义。判断时要同时考虑操作数类型和返回类型,不能只看运算符符号。
以 Date 类为例:
-
Date - Date 返回两个日期相差的天数,有意义;
-
Date + int 返回新日期,表示日期向后推移若干天,有意义;
-
Date - int 返回新日期,表示日期向前推移若干天,有意义;
-
Date + Date 没有自然含义,无意义。
因此,operator+ 并非一律不能重载;真正无意义的是 Date + Date。若某个运算没有自然语义,就不应重载该运算符,而应使用普通函数,如 addDays()、daysBetween() 等。
1.3 运算符重载的定义
-
运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
-
重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
1.4 运算符重载的使用注意事项
1. 重载运算符时,operator 后面必须跟一个 C++ 中已经存在的、允许重载的运算符符号。不能自己发明一个不存在的运算符符号。不要把 operator 和任意符号拼在一起,假装它是个新运算符。C++ 不允许创造新运算符。
另外要注意:我们写的 operator@,这里的 @ 只是一个占位符,表示"某个运算符",并不是说真的可以写 operator@。例如书上说"运算符重载函数形式为 operator@",实际写代码时要把它替换成具体符号,比如 operator+、operator-、operator==。
运算符重载只能"给已有运算符赋予新含义",不能"创造新运算符"。operator@ 中的 @ 只是书本里的占位符,不是真的能写进代码。
2. 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致:不能改变优先级、结合性、操作数个数a + b * c 仍然先算 * ,不会因为你重载了 + 就改变。
3. 至少一个操作数必须是类类型或枚举类型,不能重载 int operator+(int, int) 这种纯内置类型之间的运算。
4. 有些运算符不能重载如:.、.*、::、?:、sizeof、typeid、alignof、noexcept、各种 cast 等。
1.5 运算符重载的创建
以Date类为例:
cpp
class Date{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
1.5.1 创建重载运算符" == "
要让d1 == d2,就让d1的所有成员变量 == d2所有成员变量。返回值设置为bool类型。传递参数两个Date类类型。
cpp
#include <iostream>
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
bool operator==(const Date& x1 , const Date& x2)
{
return x1._year == x2._year
&&x1._month == x2._month
&&x1._day == x2._day;
}
注意:运算符重载函数的参数中,至少有一个必须是类类型或枚举类型。不能为两个内置类型重新定义运算符。不能改变 int、double 等内置类型之间运算符的原有含义。
所以下面这个函数是非法的:
cpp
int operator+(int x, int y)
{
return x - y;
}
原因:两个参数 x、y 都是 int,没有类类型或枚举类型参数。它试图改变内置类型 int 的 + 的含义,把加法变成减法。C++ 编译器会直接报错,不允许这种重载。
1.6 运算符重载的调用
1. 显式调用
因为运算符重载本质上就是函数,所以可以像普通函数一样直接写函数名并传参。
直接把重载后的运算符看作是一个函数,传入参数,如下:
cpp
int main()
{
Date d1(2025,2,23);
Date d2(2025,3,26);
operator==(d1,d2);
return 0;
}
但要区分两种情况:operator== 是非成员函数还是成员函数。
非成员函数版:可以这样写
如果 operator== 定义为非成员函数那么下面这种显式调用是合法的 :operator==(d1, d2); // 显式调用非成员 operator==
成员函数版:不能写成 operator==(d1, d2)
如果 operator== 是成员函数:
cpp
class Date {
public:
bool operator==(const Date& x) const {
return _year == x._year
&& _month == x._month
&& _day == x._day;
}
private:
int _year, _month, _day;
};
那么显式调用形式是: d1.operator==(d2);而不是: operator==(d1, d2); // 错误:没有这个非成员函数,因为成员函数的左操作数是调用对象 d1,右操作数是参数 d2。
总结:运算符重载可以显式调用:非成员函数写 operator==(d1, d2),成员函数写 d1.operator==(d2)。它的本质就是普通函数调用,但通常直接写 d1 == d2 更自然、更可读。
2. 隐式调用
隐式调用就是:不写 operator==(d1, d2),而是直接写运算符表达式 d1 == d2,由编译器自动把它翻译成对应的 operator@ 函数调用。这是运算符重载最正常、最常用的使用方式。
非成员函数版
cpp
bool operator==(const Date& x1, const Date& x2)
{
return x1._year == x2._year
&& x1._month == x2._month
&& x1._day == x2._day;
}
int main()
{
Date d1(2025, 2, 23);
Date d2(2025, 3, 26);
bool b = d1 == d2; // 隐式调用
// 等价于:bool b = operator==(d1, d2);
}
成员函数版
cpp
class Date {
public:
bool operator==(const Date& x) const {
return _year == x._year
&& _month == x._month
&& _day == x._day;
}
private:
int _year, _month, _day;
};
int main()
{
Date d1(2025, 2, 23);
Date d2(2025, 3, 26);
bool b = d1 == d2; // 隐式调用
// 等价于:bool b = d1.operator==(d2);
}
总结:隐式调用就是直接写 d1 == d2 这样的运算符表达式,编译器自动把它翻译成 operator==(d1, d2) 或 d1.operator == (d2)。它是运算符重载最自然、最常用的调用方式。
1.7 重载为成员函数的调用
当运算符重载为成员函数时,它本质上是类的一个成员函数,函数名是 operator@。调用时,左操作数自动成为调用对象(this),右操作数作为函数参数。
cpp
class T
{
public:
返回类型 operator@(参数列表)
{
// ...
}
};
对于二元运算符 :a @ b; // 等价于 a.operator@(b)
a 是左操作数,也是调用对象。b 是右操作数,作为参数传入。函数内部通过 this 访问 a 的成员。
对于一元运算符 :@a; // 等价于 a.operator@()
a 是操作数,也是调用对象。没有显式参数(后置 ++/-- 除外)。
1.7.1 隐式调用
直接写运算符表达式,编译器自动翻译为成员函数调用。
cpp
class Date {
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& x) const {
return _year == x._year
&& _month == x._month
&& _day == x._day;
}
private:
int _year, _month, _day;
};
int main() {
Date d1(2025, 2, 23);
Date d2(2025, 3, 26);
bool b = d1 == d2; // 隐式调用:d1.operator==(d2)
}
自动转换成显示调用,d1传递给this指针,d2传递给x
1.7.2 显式调用
直接写成员函数调用形式:d1.operator==(d2); // 显式调用成员 operator==
也可以用于其他运算符:
cpp
a.operator+(b); // a + b
a.operator+=(b); // a += b
a.operator++(); // ++a
a.operator++(0); // a++
a.operator[](i); // a[i]
a.operator()(x); // a(x)
1.7.3 成员函数重载的关键点
1. 左操作数必须是本类对象
成员函数重载要求左操作数是当前类类型(或可隐式转换为当前类类型)。例如 d1 == d2 中,d1 必须是 Date 对象。
2. 右操作数作为参数
参数类型可以是 const T&、T、其他类型,取决于语义。
3. this 指针
在成员函数内部,this 指向左操作数。如果函数不修改对象,应加 const。
cpp
bool operator==(const Date& x) const;
4. 返回值
-
复合赋值 +=、-=:返回 * this 的引用,支持链式。
-
前置 ++/--:返回 * this 的引用。
-
后置 ++/--:返回旧值,通常返回值。
-
比较运算符:返回 bool。
-
算术运算符:返回新对象。
5. 必须是成员的运算符
=、\[\]、()、->、类型转换运算符只能重载为成员函数。
6. 不能是成员的运算符
<<、>> 通常必须是非成员,因为左操作数是流对象,不是本类对象。
总结:重载为成员函数时,左操作数自动成为调用对象 this,右操作数作为参数;隐式调用写 a @ b,显式调用写 a.operator@(b)。必须为成员的运算符有 =、\[\]、()、-> 等。
1.8 调用常见问题:访问限制
在运算符重载的调用中,访问限制(public / protected / private)同样起作用。无论是隐式调用 d1 == d2,还是显式调用 d1.operator == (d2),编译器都会检查对应的 operator@ 函数是否可访问。若不可访问,直接编译报错。
解决办法:
1. 最挫的方法:成员变量公有化
2. 仿JAVA常用方法:由类提供get()函数
3. 后面会讲:友元函数
4. 最好的方法:重载为成员函数
1. 成员变量公有化:最差
cpp
class Date {
public:
int _year, _month, _day;
};
优点:非成员 operator== 能直接访问。
缺点:彻底破坏封装,外部可随意修改,对象可能变成非法状态。
结论:不推荐,几乎不应使用。
2. 提供 get() 函数:在对象外调用getyear就可以通过返回值间接得到_year。
cpp
int Getyear()
{
return _year;
}
优点:保持封装,外部通过接口访问。
缺点:成员多时要写很多 get,运算符实现也变啰嗦。
结论:比公有化好,但不是运算符重载的首选。
3. 友元函数:C++ 常用手段
cpp
class Date {
friend bool operator==(const Date&, const Date&);
public:
// ...
private:
int _year, _month, _day;
};
bool operator==(const Date& a, const Date& b) {
return a._year == b._year
&& a._month == b._month
&& a._day == b._day;
}
优点:非成员运算符也能访问私有成员,写法直接。
缺点:增加类与函数的耦合,友元过多会削弱封装。
结论:需要非成员运算符时,友元是合理选择。
4. 重载为成员函数:很多场景下最好
cpp
class Date {
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
优点:直接访问私有成员,写法简洁,左操作数就是本类对象。
缺点:左操作数必须是本类对象,不能处理 2 + a、cout << a 这类情况。
结论:左操作数是本类对象时,成员函数通常最好。
总结:优先成员函数;必须非成员时用友元;尽量别用 get() 堆接口;绝对不要公有化成员变量。
