在C++中,运算符重载允许我们为自定义类型重新定义已有运算符的行为,让自定义类型的对象可以像内置类型一样使用运算符参与运算。带多个参数的运算符重载场景在实际开发中十分常见,同时运算符重载也存在不少限制和实用技巧需要掌握。

带多个参数的运算符重载实现
运算符重载本质上是函数定义,大部分运算符既可以重载为类的成员函数,也可以重载为全局函数。当运算符需要操作多个参数时,重载方式的选择会影响参数数量的定义。
重载为成员函数的情况
如果重载为类的成员函数,左侧操作数会隐式绑定到当前对象this指针,因此参数数量会比运算符实际的操作数少一个。比如二元运算符重载为成员函数时只需要一个显式参数。
下面是一个自定义向量类重载加法运算符的例子,加法运算符需要两个向量参数,重载为成员函数时只需要一个显式参数:
#include <iostream>
using namespace std;
class Vector {
public:
int x, y;
Vector(int x = 0, int y = 0) : x(x), y(y) {}
// 重载+运算符,作为成员函数,右侧操作数作为显式参数
Vector operator+(const Vector& other) const {
return Vector(x + other.x, y + other.y);
}
};
int main() {
Vector v1(1, 2);
Vector v2(3, 4);
Vector v3 = v1 + v2; // 等价于v1.operator+(v2)
cout << "v3.x: " << v3.x << ", v3.y: " << v3.y << endl;
return 0;
}
重载为全局函数的情况
如果重载为全局函数,运算符的所有操作数都需要作为显式参数传入,因此参数数量和运算符的实际操作数一致。这种方式适合需要让左侧操作数为内置类型的场景。
比如我们想让整数和自定义向量类相加,整数作为左侧操作数,这时候重载为成员函数无法实现,因为左侧操作数必须是Vector类型,只能重载为全局函数:
#include <iostream>
using namespace std;
class Vector {
public:
int x, y;
Vector(int x = 0, int y = 0) : x(x), y(y) {}
};
// 重载+运算符,作为全局函数,两个参数都是显式传入
Vector operator+(int num, const Vector& vec) {
return Vector(num + vec.x, num + vec.y);
}
int main() {
Vector v(1, 2);
Vector v2 = 5 + v; // 调用全局的operator+(5, v)
cout << "v2.x: " << v2.x << ", v2.y: " << v2.y << endl;
return 0;
}
运算符重载的限制规则
运算符重载虽然灵活,但C++也规定了不少限制,违反这些限制会导致编译错误。
- 不能重载内置类型的运算符,比如不能重新定义
int类型的+运算符行为。 - 不能创建新的运算符,只能重载C++已有的运算符,比如不能定义
**作为幂运算运算符。 - 重载后的运算符操作数数量不能改变,比如二元运算符重载后还是只能有两个操作数,三元运算符
?:不能被重载。 - 部分运算符必须重载为成员函数:
=(赋值)、[](下标)、()(函数调用)、->(成员访问)这四个运算符不能被重载为全局函数。 - 重载运算符不能改变运算符原有的优先级和结合性,比如重载后的
+还是比*优先级低。
运算符重载的实用技巧
成对重载相关运算符
很多运算符是成对出现的,比如==和!=、<和>、+和+=,建议成对重载,避免用户使用时出现不符合预期的行为。比如重载了==之后,最好也重载!=,实现方式可以直接复用==的结果取反。
class MyString {
private:
char* data;
int len;
public:
// 重载==运算符
bool operator==(const MyString& other) const {
if (len != other.len) return false;
for (int i = 0; i < len; i++) {
if (data[i] != other.data[i]) return false;
}
return true;
}
// 重载!=运算符,复用==的结果
bool operator!=(const MyString& other) const {
return !(*this == other);
}
};
复合赋值运算符返回引用
重载+=、-=这类复合赋值运算符时,建议返回当前对象的引用,这样可以支持链式调用,符合内置类型的运算符行为。
class Counter {
public:
int value;
Counter(int v = 0) : value(v) {}
// 重载+=运算符,返回引用支持链式调用
Counter& operator+=(int num) {
value += num;
return *this;
}
};
int main() {
Counter c(1);
c += 2 += 3; // 链式调用,等价于c.value = 1+2+3
return 0;
}
输入输出的重载建议用全局函数
重载<<和>>运算符时,因为左侧操作数必须是ostream或istream对象,无法重载为自定义类的成员函数,所以建议重载为全局函数,并且如果是访问私有成员,可以将该函数声明为类的友元函数。
#include <iostream>
using namespace std;
class Point {
private:
int x, y;
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
// 声明全局函数为友元,允许访问私有成员
friend ostream& operator<<(ostream& os, const Point& p);
friend istream& operator>>(istream& is, Point& p);
};
// 重载输出运算符
ostream& operator<<(ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
// 重载输入运算符
istream& operator>>(istream& is, Point& p) {
is >> p.x >> p.y;
return is;
}
int main() {
Point p;
cin >> p;
cout << p << endl;
return 0;
}
常见问题注意事项
重载带多个参数的运算符时,要注意参数顺序和const修饰符的使用,避免不必要的拷贝,提升性能。同时不要过度使用运算符重载,只有当重载后的运算符行为符合用户对原有运算符的直觉认知时才使用,否则会让代码可读性下降。比如不要用+运算符来实现两个对象的拼接之外的其他无意义操作。