Skip to content

C++ 重载

C++ 中的重载(Overloading)允许您在同一作用域内定义多个同名函数或运算符,前提是它们的参数列表不同(参数数量或类型不同)。这使得接口更直观、更具上下文感知能力。

重载声明必须与同一作用域内具有相同名称的先前声明在参数列表(也称为函数签名,不包括返回类型)上有所区别。仅返回类型不足以区分重载函数。

当您调用重载函数或使用重载运算符时,编译器会执行重载决议(overload resolution),根据您提供的参数确定最佳匹配的定义。

函数重载允许创建多个同名但参数不同的函数。这对于对不同数据类型或使用不同输入集执行类似操作非常有用。

示例:一个带有重载 print 方法的类:

#include <iostream>
#include <string>
class DataPrinter {
public:
// 重载 1: 打印一个整数
void print(int i) {
std::cout << "Printing int: " << i << std::endl;
}
// 重载 2: 打印一个双精度浮点数
void print(double d) {
std::cout << "Printing double: " << d << std::endl;
}
// 重载 3: 打印一个 C 风格字符串
void print(const char* c_str) {
std::cout << "Printing C-string: " << c_str << std::endl;
}
// 重载 4: 打印一个 C++ 字符串对象
void print(const std::string& s) {
std::cout << "Printing std::string: " << s << std::endl;
}
/*
// 这是错误的 - 不能仅凭返回类型重载
int print(int i) {
std::cout << "Trying to overload based on return type" << std::endl;
return i;
}
*/
};
int main() {
DataPrinter printer;
// 编译器根据参数类型确定调用哪个 'print'
printer.print(42); // Calls print(int)
printer.print(3.14159); // Calls print(double)
printer.print("Hello C-style"); // Calls print(const char*)
printer.print(std::string("Hello std::string")); // Calls print(const std::string&)
return 0;
}

编译并执行后,会产生以下输出:

打印 int: 42
打印 double: 3.14159
打印 C 风格字符串: Hello C-style
打印 std::string: Hello std::string

运算符重载(Operator Overloading)允许您为用户定义类型(类或结构体)定义内置运算符(如 +, -, *, /, [], (), << 等)的行为。这可以使涉及对象的代码更具可读性和直观性,模仿内置类型的操作。

重载的运算符被实现为具有特殊名称的函数:operator 后跟运算符符号(例如,operator+, operator<<)。它们可以定义为成员函数或非成员函数(通常是 friend 函数)。

**成员函数重载:**如果将 + 等运算符重载为成员函数:

// 在 Box 类定义内部
Box operator+(const Box& other) const; // 'this' 对象是左操作数

左操作数是调用函数的对象 (this),右操作数作为参数传递。

**非成员函数重载:**如果重载为非成员函数(如果需要访问私有成员,通常需要 friend 访问):

// 在类定义外部(可能在内部声明为 friend)
Box operator+(const Box& left, const Box& right);

两个操作数都作为参数传递。这对于像 + 这样的二元运算符通常是首选的,以保持对称性(例如,允许 Box + Box,如果存在合适的构造函数,可能还允许 int + Box)。流插入 (<<) 和流提取 (>>) 运算符必须重载为非成员函数。

示例:使用成员函数为 Box 类重载 operator+。

#include <iostream>
class Box {
private:
double length = 0.0;
double breadth = 0.0;
double height = 0.0;
public:
// 使用初始化列表的构造函数
Box(double l = 0.0, double b = 0.0, double h = 0.0)
: length(l), breadth(b), height(h) {
std::cout << "Box constructor called." << std::endl;
}
double getVolume() const { // 标记为 const,因为它不修改对象
return length * breadth * height;
}
// 作为成员函数重载 + 运算符
// 接受一个 Box 作为右操作数
// 标记为 const,因为它不修改 *this* Box 对象
Box operator+(const Box& rhs) const {
std::cout << "Operator+ called." << std::endl;
Box result;
result.length = this->length + rhs.length;
result.breadth = this->breadth + rhs.breadth;
result.height = this->height + rhs.height;
return result;
}
// 用于重载流插入运算符 << 的友元函数
friend std::ostream& operator<<(std::ostream& os, const Box& box);
};
// 非成员函数定义 for operator<<
// 如果私有成员是 private,则需要是 'friend' 才能访问私有成员
std::ostream& operator<<(std::ostream& os, const Box& box) {
os << "Box(L:" << box.length << ", B:" << box.breadth << ", H:" << box.height << ")";
return os;
}
int main() {
Box box1(6.0, 7.0, 5.0);
Box box2(12.0, 13.0, 10.0);
Box box3;
std::cout << "Volume of Box1: " << box1.getVolume() << " - " << box1 << std::endl;
std::cout << "Volume of Box2: " << box2.getVolume() << " - " << box2 << std::endl;
// 使用重载的 + 运算符: box3 = box1 + box2;
// 这将调用 box1.operator+(box2)
box3 = box1 + box2;
std::cout << "Volume of Box3 (Box1 + Box2): " << box3.getVolume() << " - " << box3 << std::endl;
return 0;
}

编译并执行后,会产生以下输出:

调用了 Box 构造函数。
调用了 Box 构造函数。
调用了 Box 构造函数。
Box1 的体积: 210 - Box(L:6, B:7, H:5)
Box2 的体积: 1560 - Box(L:12, B:13, H:10)
调用了 Operator+
调用了 Box 构造函数。 // 对于 operator+ 内部的 'result' Box
Box3 的体积 (Box1 + Box2): 5400 - Box(L:18, B:20, H:15)

大多数 C++ 运算符都可以被重载。以下是常见可重载运算符的列表:

算术和位运算符赋值和移位运算符比较和逻辑运算符成员访问和调用运算符内存管理运算符
+-*/%
<>+=-=*=
==!=<=>=<=> (C++20)
->*->() (函数调用)[] (下标)
newdeletenew[]delete[]

不可重载的运算符:

:: (作用域解析).* (指向成员的指针访问). (成员访问)?: (三元条件运算符)sizeoftypeidstatic_castdynamic_castconst_castreinterpret_cast

虽然功能强大,但应谨慎使用运算符重载:

  • 保持语义: 以符合其常见含义的方式重载运算符。将 + 重载为执行减法会造成混淆。
  • 成员函数 vs 非成员函数: 根据对称性和访问需求进行选择。一元运算符通常是成员函数。二元运算符可以是成员函数或非成员函数(通常是友元函数)。像 << 和 >> 这样的运算符必须是非成员函数。
  • 返回类型: 对于像 += 或 ++(前置自增)这样的运算符,考虑通过引用返回 *this 以允许链式调用。
  • 清晰度: 有时命名函数(例如,matrix.multiply(otherMatrix))比重载运算符(matrix * otherMatrix))更清晰。

以下是一些常见的运算符重载场景:

序号运算符常见用例
1算术运算符 (+, -, *, /)用于复数、向量、矩阵等数学类型。
2比较运算符 (==, !=, <, >, <=, >=, <=>)为自定义类型定义顺序和相等性。
3输入/输出运算符 (<<, >>)方便使用流 (std::cin, std::cout, 文件) 读取/写入对象。
4下标运算符 ([])为容器类中的元素提供类似数组的访问方式。
5函数调用运算符 (())创建行为类似于函数的对象(函数对象/仿函数)。
6赋值运算符 (=)通常用于处理深拷贝或资源管理(尽管通常适用三/五/零法则)。
7自增/自减运算符 (++, --)对迭代器类或表示序列的类型很有用。
8解引用/箭头运算符 (*, ->)对于智能指针和迭代器类型至关重要。