运算符重载允许为自定义类型赋予现有运算符新功能,提升代码可读性。它通过定义以operator为前缀的特殊函数实现,可作为成员或友元函数重载。多数运算符可重载,如算术、关系、位运算、赋值、递增递减、下标、函数调用等;但., .*, ::, ?:, sizeof, typeid不可重载。选择成员函数还是友元函数取决于操作数对称性与访问需求:赋值、下标、函数调用等必须为成员函数;流操作<<、>>及需类型转换的二元运算符宜用友元函数。最佳实践包括保持语义一致、const正确性、用复合赋值实现二元运算符、处理自赋值与资源管理(如copy-and-swap)、避免过度重载。陷阱有行为反直觉、性能损耗、不一致实现等。合理使用可增强表达力,滥用则导致混乱。

C++中的运算符重载,简单来说,就是赋予现有运算符新的功能,让它们能够作用于我们自定义的类类型对象。这就像是给一个老工具箱里的锤子、螺丝刀重新定义用途,让它们也能处理一些新材料,核心目的是提高代码的可读性和直观性,让用户自定义类型的使用体验更接近内置类型。
在C++里,重载运算符其实就是定义一个特殊的函数。这个函数的名称是
operator
Vector
+
重载运算符的函数签名通常是这样的:
返回类型 operator 运算符符号 (参数列表)
=
[]
()
->
+
-
*
/
<<
>>
int + MyClass
#include <iostream>
class MyVector {
public:
int x, y;
MyVector(int x = 0, int y = 0) : x(x), y(y) {}
// 成员函数重载 + 运算符
MyVector operator+(const MyVector& other) const {
return MyVector(x + other.x, y + other.y);
}
// 成员函数重载 - 运算符
MyVector operator-(const MyVector& other) const {
return MyVector(x - other.x, y - other.y);
}
// 成员函数重载 += 运算符
MyVector& operator+=(const MyVector& other) {
x += other.x;
y += other.y;
return *this;
}
// 前置递增运算符
MyVector& operator++() {
++x;
++y;
return *this;
}
// 后置递增运算符 (int 参数是占位符,用于区分前置)
MyVector operator++(int) {
MyVector temp = *this;
++(*this); // 调用前置递增
return temp;
}
// 友元函数重载 << 运算符,用于输出
friend std::ostream& operator<<(std::ostream& os, const MyVector& vec) {
os << "(" << vec.x << ", " << vec.y << ")";
return os;
}
// 友元函数重载 == 运算符
friend bool operator==(const MyVector& v1, const MyVector& v2) {
return v1.x == v2.x && v1.y == v2.y;
}
// 友元函数重载 != 运算符
friend bool operator!=(const MyVector& v1, const MyVector& v2) {
return !(v1 == v2); // 通常基于 == 实现
}
};
int main() {
MyVector v1(1, 2);
MyVector v2(3, 4);
MyVector v3 = v1 + v2; // 使用重载的 +
std::cout << "v1 + v2 = " << v3 << std::endl; // 使用重载的 <<
MyVector v4 = v1 - v2; // 使用重载的 -
std::cout << "v1 - v2 = " << v4 << std::endl;
v1 += v2; // 使用重载的 +=
std::cout << "v1 after += v2 = " << v1 << std::endl;
MyVector v5 = ++v1; // 前置递增
std::cout << "v5 (pre-increment v1) = " << v5 << ", v1 = " << v1 << std::endl;
MyVector v6 = v1++; // 后置递增
std::cout << "v6 (post-increment v1) = " << v6 << ", v1 = " << v1 << std::endl;
MyVector v7(5, 7);
std::cout << "v1 == v7 is " << (v1 == v7 ? "true" : "false") << std::endl;
std::cout << "v1 != v7 is " << (v1 != v7 ? "true" : "false") << std::endl;
return 0;
}在C++中,绝大多数运算符都可以被重载,这给我们自定义类型带来了极大的灵活性。我通常会把它们分成几类来记忆,这样更清晰一些:
立即学习“C++免费学习笔记(深入)”;
可以重载的运算符包括:
+
-
*
/
%
==
!=
<
>
<=
>=
&&
||
!
&&
||
&
|
^
~
<<
>>
=
+=
-=
*=
/=
%=
&=
|=
^=
<<=
>>=
++
--
[]
()
->
new
delete
new[]
delete[]
operator type()
operator int()
然而,有一些运算符是C++明确规定不能被重载的,主要有:
.
.*
::
?:
sizeof
typeid
我个人觉得,这些不可重载的运算符都有其特殊性。例如,
.
sizeof
typeid
这是一个在设计自定义类型时经常需要权衡的问题。我通常会根据运算符的语义和操作数的特性来决定。
成员函数重载的特点:
myObject.operator+(anotherObject)
this
!
++
--
=
[]
()
->
友元函数(非成员函数)重载的特点:
int + MyClass
MyClass + int
std::cout << myObject
std::ostream
MyClass
<<
>>
std::ostream
std::istream
+
-
,
的一种常见且推荐的方式。** 许多人会先在类中实现
,
,
等复合赋值运算符作为成员函数,然后将
,
,
我的选择策略是这样的:
如果运算符必须是成员函数(例如 =
[]
()
如果运算符是一元运算符(例如 !
++
--
如果运算符是二元运算符,且需要支持操作数类型不对称的情况(例如 int + MyClass
<<
>>
+
// MyClass 的成员函数
MyClass& operator+=(const MyClass& rhs) {
// ... 实现加法赋值逻辑 ...
return *this;
}
// 非成员函数(可以是非友元,如果只需要公共接口)
MyClass operator+(MyClass lhs, const MyClass& rhs) {
lhs += rhs; // 利用 += 实现
return lhs;
}这样
operator+
MyClass
MyClass
MyClass
运算符重载虽然强大,但用不好也容易挖坑。我见过不少因为重载而引入的bug,所以有一些经验总结出的陷阱和最佳实践,我觉得挺有用的。
常见陷阱:
+
==
a == b
b == a
operator+
MyClass
MyClass
operator=
obj = obj
==
!=
+
+=
最佳实践:
保持语义一致性: 这是最重要的原则。重载的运算符行为应该尽可能地与内置类型的相应运算符保持一致。例如,
+
==
考虑 const
const
const
// 示例:const 正确性
MyVector operator+(const MyVector& other) const { // const 确保不会修改 *this
return MyVector(x + other.x, y + other.y);
}利用复合赋值运算符实现二元算术运算符: 对于
+
-
*
/
+=
-=
*=
/=
// 成员函数
MyVector& operator+=(const MyVector& other) { /* ... */ return *this; }
// 非成员函数 (可以是非友元,如果只需要公共接口)
MyVector operator+(MyVector lhs, const MyVector& rhs) {
lhs += rhs; // 调用成员函数 +=
return lhs;
}这种模式的好处是:减少代码重复、保证行为一致性,并且利用了传值参数
lhs
operator+
正确实现 operator=
// 假设 MyClass 管理一个动态分配的资源
class MyClass {
int* data;
size_t size;
public:
// 构造函数
MyClass(size_t s = 0) : size(s), data(s > 0 ? new int[s] : nullptr) {}
// 析构函数
~MyClass() { delete[] data; }
// 拷贝构造函数
MyClass(const MyClass& other) : size(other.size), data(other.size > 0 ? new int[other.size] : nullptr) {
if (data) {
std::copy(other.data, other.data + other.size, data);
}
}
// 移动构造函数 (C++11)
MyClass(MyClass&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// 拷贝赋值运算符 (使用 copy-and-swap 惯用法)
MyClass& operator=(MyClass other) { // 注意这里是传值参数,会调用拷贝构造函数
swap(*this, other); // 交换 *this 和 other 的内部状态
return *this;
}
// 友元 swap 函数 (用于 copy-and-swap)
friend void swap(MyClass& first, MyClass& second) noexcept {
using std::swap;
swap(first.data, second.data);
swap(first.size, second.size);
}
// ... 其他成员 ...
};为 <<
>>
std::ostream
std::istream
考虑默认行为: C++11 引入了
default
delete
class SimplePoint {
public:
int x, y;
SimplePoint(int x=0, int y=0) : x(x), y(y) {}
// 编译器会生成默认的拷贝构造、拷贝赋值、移动构造、移动赋值和析构函数
// 如果它们行为正确,就无需手动实现
};这被称为“零法则”(Rule of Zero),即如果你的类不需要自定义析构函数、拷贝构造函数或拷贝赋值运算符,那么它可能也不需要自定义移动构造函数或移动赋值运算符,直接依赖编译器生成的默认行为即可。
总的来说,重载运算符是C++提供的一把双刃剑,它能让代码更富有表现力,但前提是必须谨慎使用,确保其行为符合直觉、高效且正确。
以上就是如何在C++中重载运算符_C++运算符重载规则与实例的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号