C++ 重载
C++ 函数和运算符重载
Section titled “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' BoxBox3 的体积 (Box1 + Box2): 5400 - Box(L:18, B:20, H:15)可重载和不可重载的运算符
Section titled “可重载和不可重载的运算符”大多数 C++ 运算符都可以被重载。以下是常见可重载运算符的列表:
| 算术和位运算符 | 赋值和移位运算符 | 比较和逻辑运算符 | 成员访问和调用运算符 | 内存管理运算符 |
|---|---|---|---|---|
| + | - | * | / | % |
| < | > | += | -= | *= |
| == | != | <= | >= | <=> (C++20) |
| ->* | -> | () (函数调用) | [] (下标) | |
| new | delete | new[] | delete[] |
不可重载的运算符:
| :: (作用域解析) | .* (指向成员的指针访问) | . (成员访问) | ?: (三元条件运算符) | sizeof | typeid | static_cast | dynamic_cast | const_cast | reinterpret_cast |
|---|
运算符重载注意事项:
Section titled “运算符重载注意事项:”虽然功能强大,但应谨慎使用运算符重载:
- 保持语义: 以符合其常见含义的方式重载运算符。将
+重载为执行减法会造成混淆。 - 成员函数 vs 非成员函数: 根据对称性和访问需求进行选择。一元运算符通常是成员函数。二元运算符可以是成员函数或非成员函数(通常是友元函数)。像
<<和>>这样的运算符必须是非成员函数。 - 返回类型: 对于像
+=或++(前置自增)这样的运算符,考虑通过引用返回*this以允许链式调用。 - 清晰度: 有时命名函数(例如,
matrix.multiply(otherMatrix))比重载运算符(matrix * otherMatrix))更清晰。
以下是一些常见的运算符重载场景:
| 序号 | 运算符 | 常见用例 |
|---|---|---|
| 1 | 算术运算符 (+, -, *, /) | 用于复数、向量、矩阵等数学类型。 |
| 2 | 比较运算符 (==, !=, <, >, <=, >=, <=>) | 为自定义类型定义顺序和相等性。 |
| 3 | 输入/输出运算符 (<<, >>) | 方便使用流 (std::cin, std::cout, 文件) 读取/写入对象。 |
| 4 | 下标运算符 ([]) | 为容器类中的元素提供类似数组的访问方式。 |
| 5 | 函数调用运算符 (()) | 创建行为类似于函数的对象(函数对象/仿函数)。 |
| 6 | 赋值运算符 (=) | 通常用于处理深拷贝或资源管理(尽管通常适用三/五/零法则)。 |
| 7 | 自增/自减运算符 (++, --) | 对迭代器类或表示序列的类型很有用。 |
| 8 | 解引用/箭头运算符 (*, ->) | 对于智能指针和迭代器类型至关重要。 |