第21章 运算符重载
运算符重载的基本概念
运算符重载是C++的一项强大特性,允许程序员为自定义类型定义运算符的行为。通过运算符重载,可以使自定义类型的使用更加直观和自然。
什么是运算符重载?
运算符重载是指重新定义运算符的行为,使其能够应用于自定义类型。例如,我们可以为一个Complex(复数)类重载+运算符,使其能够直接进行复数加法运算。
运算符重载的语法
运算符重载的语法与函数定义类似,只是函数名由operator关键字和要重载的运算符组成:
1 2 3
| 返回类型 operator运算符(参数列表) { }
|
可重载的运算符
C++中大部分运算符都可以重载,但有一些例外。以下是可重载和不可重载的运算符:
可重载的运算符
- 算术运算符:
+, -, *, /, %, ++, -- - 关系运算符:
==, !=, <, >, <=, >= - 逻辑运算符:
&&, ||, ! - 位运算符:
&, |, ^, ~, <<, >> - 赋值运算符:
=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= - 其他运算符:
[], (), ->, ->*, new, new[], delete, delete[]
不可重载的运算符
.(成员访问运算符).*(成员指针访问运算符)::(作用域解析运算符)?:(条件运算符)sizeof(大小运算符)typeid(类型信息运算符)const_cast, dynamic_cast, reinterpret_cast, static_cast(类型转换运算符)
成员运算符与非成员运算符
运算符重载可以作为类的成员函数实现,也可以作为非成员函数实现。
成员运算符
成员运算符是作为类的成员函数实现的运算符重载。对于二元运算符,左侧操作数是调用对象,右侧操作数是函数参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class Complex { private: double real; double imag;
public: Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {} Complex operator+(const Complex& other) const { return Complex(real + other.real, imag + other.imag); } Complex operator-(const Complex& other) const { return Complex(real - other.real, imag - other.imag); } Complex operator*(const Complex& other) const { return Complex( real * other.real - imag * other.imag, real * other.imag + imag * other.real ); } Complex& operator+=(const Complex& other) { real += other.real; imag += other.imag; return *this; } void display() const { std::cout << real << " + " << imag << "i" << std::endl; } };
int main() { Complex c1(1.0, 2.0); Complex c2(3.0, 4.0); Complex c3 = c1 + c2; Complex c4 = c1 - c2; Complex c5 = c1 * c2; c1 += c2; std::cout << "c1: "; c1.display(); std::cout << "c2: "; c2.display(); std::cout << "c1 + c2: "; c3.display(); std::cout << "c1 - c2: "; c4.display(); std::cout << "c1 * c2: "; c5.display(); return 0; }
|
非成员运算符
非成员运算符是作为全局函数实现的运算符重载。对于二元运算符,两个操作数都是函数参数。非成员运算符通常需要访问类的私有成员,因此需要声明为类的友元。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| class Complex { private: double real; double imag;
public: Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {} friend Complex operator+(const Complex& c1, const Complex& c2); friend Complex operator-(const Complex& c1, const Complex& c2); friend Complex operator*(const Complex& c1, const Complex& c2); friend std::ostream& operator<<(std::ostream& os, const Complex& c); void display() const { std::cout << real << " + " << imag << "i" << std::endl; } };
Complex operator+(const Complex& c1, const Complex& c2) { return Complex(c1.real + c2.real, c1.imag + c2.imag); }
Complex operator-(const Complex& c1, const Complex& c2) { return Complex(c1.real - c2.real, c1.imag - c2.imag); }
Complex operator*(const Complex& c1, const Complex& c2) { return Complex( c1.real * c2.real - c1.imag * c2.imag, c1.real * c2.imag + c1.imag * c2.real ); }
std::ostream& operator<<(std::ostream& os, const Complex& c) { os << c.real << " + " << c.imag << "i"; return os; }
int main() { Complex c1(1.0, 2.0); Complex c2(3.0, 4.0); Complex c3 = c1 + c2; Complex c4 = c1 - c2; Complex c5 = c1 * c2; std::cout << "c1: " << c1 << std::endl; std::cout << "c2: " << c2 << std::endl; std::cout << "c1 + c2: " << c3 << std::endl; std::cout << "c1 - c2: " << c4 << std::endl; std::cout << "c1 * c2: " << c5 << std::endl; return 0; }
|
成员运算符与非成员运算符的选择
- 成员运算符:适用于一元运算符(如
++, --)和左侧操作数是类对象的二元运算符(如+=, -=)。 - 非成员运算符:适用于需要支持左侧操作数为内置类型的二元运算符(如
10 + Complex(1, 2))和流插入/提取运算符(如<<, >>)。
特殊运算符的重载
赋值运算符
赋值运算符=是一个特殊的运算符,默认情况下,类会生成一个默认的赋值运算符,执行成员级别的复制。但是,当类包含指针或其他需要深度复制的成员时,需要自定义赋值运算符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class String { private: char* data; size_t length;
public: String(const char* str = "") { length = strlen(str); data = new char[length + 1]; strcpy(data, str); } String(const String& other) { length = other.length; data = new char[length + 1]; strcpy(data, other.data); } String& operator=(const String& other) { if (this != &other) { delete[] data; length = other.length; data = new char[length + 1]; strcpy(data, other.data); } return *this; } String& operator=(String&& other) noexcept { if (this != &other) { delete[] data; data = other.data; length = other.length; other.data = nullptr; other.length = 0; } return *this; } ~String() { delete[] data; } const char* c_str() const { return data; } };
|
下标运算符
下标运算符[]用于访问类的元素,通常用于容器类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class Array { private: int* data; size_t size;
public: Array(size_t s) : size(s) { data = new int[size]; } ~Array() { delete[] data; } int& operator[](size_t index) { if (index >= size) { throw std::out_of_range("Index out of range"); } return data[index]; } const int& operator[](size_t index) const { if (index >= size) { throw std::out_of_range("Index out of range"); } return data[index]; } size_t getSize() const { return size; } };
int main() { Array arr(5); for (size_t i = 0; i < arr.getSize(); ++i) { arr[i] = i * 10; } for (size_t i = 0; i < arr.getSize(); ++i) { std::cout << "arr[" << i << "] = " << arr[i] << std::endl; } return 0; }
|
函数调用运算符
函数调用运算符()使类的对象可以像函数一样被调用,这样的对象称为函数对象或仿函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| class Add { private: int value;
public: Add(int v) : value(v) {} int operator()(int x) const { return x + value; } };
class Multiply { private: int factor;
public: Multiply(int f) : factor(f) {} int operator()(int x) const { return x * factor; } };
int main() { Add add5(5); int result1 = add5(10); std::cout << "10 + 5 = " << result1 << std::endl; Multiply multiply3(3); int result2 = multiply3(10); std::cout << "10 * 3 = " << result2 << std::endl; std::vector<int> vec = {1, 2, 3, 4, 5}; std::transform(vec.begin(), vec.end(), vec.begin(), add5); std::cout << "After adding 5: " << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; return 0; }
|
递增和递减运算符
递增运算符++和递减运算符--有前缀和后缀两种形式,需要分别重载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| class Counter { private: int value;
public: Counter(int v = 0) : value(v) {} Counter& operator++() { ++value; return *this; } Counter operator++(int) { Counter temp = *this; ++value; return temp; } Counter& operator--() { --value; return *this; } Counter operator--(int) { Counter temp = *this; --value; return temp; } int getValue() const { return value; } };
int main() { Counter c(5); std::cout << "Initial value: " << c.getValue() << std::endl; ++c; std::cout << "After prefix ++: " << c.getValue() << std::endl; c++; std::cout << "After postfix ++: " << c.getValue() << std::endl; --c; std::cout << "After prefix --: " << c.getValue() << std::endl; c--; std::cout << "After postfix --: " << c.getValue() << std::endl; return 0; }
|
类型转换运算符
类型转换运算符用于将类对象转换为其他类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| class Temperature { private: double celsius;
public: Temperature(double c) : celsius(c) {} double getCelsius() const { return celsius; } operator double() const { return celsius * 9.0 / 5.0 + 32.0; } explicit operator bool() const { return celsius > 0; } };
int main() { Temperature t(25.0); std::cout << "Celsius: " << t.getCelsius() << std::endl; double fahrenheit = t; std::cout << "Fahrenheit: " << fahrenheit << std::endl; if (static_cast<bool>(t)) { std::cout << "Temperature is above freezing" << std::endl; } else { std::cout << "Temperature is at or below freezing" << std::endl; } Temperature t2(-5.0); std::cout << "\nCelsius: " << t2.getCelsius() << std::endl; fahrenheit = t2; std::cout << "Fahrenheit: " << fahrenheit << std::endl; if (static_cast<bool>(t2)) { std::cout << "Temperature is above freezing" << std::endl; } else { std::cout << "Temperature is at or below freezing" << std::endl; } return 0; }
|
运算符重载的最佳实践
1. 保持运算符的语义
重载的运算符应该保持其原始语义,例如,+运算符应该执行加法操作,而不是其他操作。
2. 返回类型的选择
- 一元运算符:返回修改后的对象(如
++, --)。 - 二元运算符:返回一个新的对象(如
+, -, *)。 - 赋值运算符:返回
*this的引用(如=, +=, -=)。
3. 处理自赋值
在重载赋值运算符时,应该检查自赋值情况,避免不必要的操作和潜在的错误。
4. 使用const
对于不修改对象状态的运算符重载,应该使用const修饰符。
5. 避免过度重载
不要为了重载而重载运算符,只有当运算符重载能够使代码更清晰、更直观时才使用。
6. 考虑异常安全
在运算符重载中,应该考虑异常安全,确保在发生异常时,对象的状态保持一致。
运算符重载的应用示例
实现一个简单的向量类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| class Vector { private: double x; double y; double z;
public: Vector(double x = 0.0, double y = 0.0, double z = 0.0) : x(x), y(y), z(z) {} Vector operator+(const Vector& other) const { return Vector(x + other.x, y + other.y, z + other.z); } Vector operator-(const Vector& other) const { return Vector(x - other.x, y - other.y, z - other.z); } Vector operator*(double scalar) const { return Vector(x * scalar, y * scalar, z * scalar); } Vector& operator+=(const Vector& other) { x += other.x; y += other.y; z += other.z; return *this; } Vector& operator-=(const Vector& other) { x -= other.x; y -= other.y; z -= other.z; return *this; } Vector& operator*=(double scalar) { x *= scalar; y *= scalar; z *= scalar; return *this; } double magnitude() const { return sqrt(x * x + y * y + z * z); } Vector normalize() const { double mag = magnitude(); if (mag == 0) { return Vector(); } return *this * (1.0 / mag); } double dot(const Vector& other) const { return x * other.x + y * other.y + z * other.z; } Vector cross(const Vector& other) const { return Vector( y * other.z - z * other.y, z * other.x - x * other.z, x * other.y - y * other.x ); } friend Vector operator*(double scalar, const Vector& vec); friend std::ostream& operator<<(std::ostream& os, const Vector& vec); };
Vector operator*(double scalar, const Vector& vec) { return Vector(vec.x * scalar, vec.y * scalar, vec.z * scalar); }
std::ostream& operator<<(std::ostream& os, const Vector& vec) { os << "(" << vec.x << ", " << vec.y << ", " << vec.z << ")"; return os; }
int main() { Vector v1(1.0, 2.0, 3.0); Vector v2(4.0, 5.0, 6.0); std::cout << "v1: " << v1 << std::endl; std::cout << "v2: " << v2 << std::endl; Vector v3 = v1 + v2; std::cout << "v1 + v2: " << v3 << std::endl; Vector v4 = v1 - v2; std::cout << "v1 - v2: " << v4 << std::endl; Vector v5 = v1 * 2.0; std::cout << "v1 * 2.0: " << v5 << std::endl; Vector v6 = 3.0 * v1; std::cout << "3.0 * v1: " << v6 << std::endl; double dotProduct = v1.dot(v2); std::cout << "v1 · v2: " << dotProduct << std::endl; Vector crossProduct = v1.cross(v2); std::cout << "v1 × v2: " << crossProduct << std::endl; double mag = v1.magnitude(); std::cout << "|v1|: " << mag << std::endl; Vector unit = v1.normalize(); std::cout << "v1 normalized: " << unit << std::endl; std::cout << "|v1 normalized|: " << unit.magnitude() << std::endl; return 0; }
|
总结
运算符重载是C++的一项强大特性,允许程序员为自定义类型定义运算符的行为,使自定义类型的使用更加直观和自然。本章介绍了:
- 运算符重载的基本概念:什么是运算符重载,运算符重载的语法。
- 可重载的运算符:哪些运算符可以重载,哪些不能重载。
- 成员运算符与非成员运算符:如何实现成员运算符和非成员运算符,以及如何选择。
- 特殊运算符的重载:赋值运算符、下标运算符、函数调用运算符、递增/递减运算符、类型转换运算符。
- 运算符重载的最佳实践:保持运算符的语义、返回类型的选择、处理自赋值、使用const、避免过度重载、考虑异常安全。
- 运算符重载的应用示例:实现一个简单的向量类。
通过合理使用运算符重载,可以使C++代码更加简洁、直观和易于理解。然而,运算符重载也需要谨慎使用,只有当它能够真正提高代码质量时才应该使用。