C++ 運算符重載全面深度解析
一、運算符重載的基本概念
1.1 什麼是運算符重載?
運算符重載 是 C++ 的一項核心特性,它允許程序員為自定義類型(類)定義運算符的行為。通過重載運算符,可以使自定義類型像內置類型一樣使用標準的運算符語法,從而增強代碼的可讀性和直觀性。
1.2 運算符重載的本質
從本質上講,運算符重載是一種特殊的函數重載。運算符被重載時,編譯器將其轉換為相應的函數調用。例如,a + b可能被轉換為 a.operator+(b)或 operator+(a, b)。
1.3 運算符重載的限制
- 不能創建新的運算符
- 不能改變運算符的優先級和結合性
- 不能改變運算符的操作數個數
- 部分運算符不能重載(
::、.、.*、?:、sizeof、typeid等) - 至少有一個操作數是用户定義類型
二、運算符重載的基本語法
2.1 成員函數形式
class Complex {
private:
double real, imag;
public:
// 成員函數形式的運算符重載
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// 一元運算符重載
Complex operator-() const {
return Complex(-real, -imag);
}
// 複合賦值運算符
Complex& operator+=(const Complex& other) {
real += other.real;
imag += other.imag;
return *this;
}
};
2.2 非成員函數形式
class Complex {
private:
double real, imag;
public:
// 聲明為友元以便訪問私有成員
friend Complex operator+(const Complex& a, const Complex& b);
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
};
// 非成員函數形式的運算符重載
Complex operator+(const Complex& a, const Complex& b) {
return Complex(a.real + b.real, a.imag + b.imag);
}
// 流插入運算符通常重載為非成員函數
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.real << " + " << c.imag << "i";
return os;
}
三、各種運算符重載詳解
3.1 算術運算符
二元算術運算符:
class Rational {
int numerator, denominator;
public:
// 加法
Rational operator+(const Rational& other) const {
return Rational(
numerator * other.denominator + other.numerator * denominator,
denominator * other.denominator
).simplify();
}
// 減法
Rational operator-(const Rational& other) const {
return Rational(
numerator * other.denominator - other.numerator * denominator,
denominator * other.denominator
).simplify();
}
// 乘法
Rational operator*(const Rational& other) const {
return Rational(
numerator * other.numerator,
denominator * other.denominator
).simplify();
}
// 除法
Rational operator/(const Rational& other) const {
return Rational(
numerator * other.denominator,
denominator * other.numerator
).simplify();
}
};
一元算術運算符:
class Number {
int value;
public:
// 一元正號(通常返回原值)
Number operator+() const {
return *this;
}
// 一元負號
Number operator-() const {
return Number(-value);
}
// 前綴遞增
Number& operator++() {
++value;
return *this;
}
// 後綴遞增
Number operator++(int) { // int參數用於區分前綴和後綴
Number temp = *this;
++value;
return temp;
}
// 前綴遞減
Number& operator--() {
--value;
return *this;
}
// 後綴遞減
Number operator--(int) {
Number temp = *this;
--value;
return temp;
}
};
3.2 關係運算符
class String {
char* data;
size_t length;
public:
// 相等比較
bool operator==(const String& other) const {
if (length != other.length) return false;
return strcmp(data, other.data) == 0;
}
// 不等比較
bool operator!=(const String& other) const {
return !(*this == other);
}
// 小於比較
bool operator<(const String& other) const {
return strcmp(data, other.data) < 0;
}
// 大於比較
bool operator>(const String& other) const {
return other < *this;
}
// 小於等於
bool operator<=(const String& other) const {
return !(*this > other);
}
// 大於等於
bool operator>=(const String& other) const {
return !(*this < other);
}
// 三路比較運算符(C++20)
#if __cplusplus >= 202002L
auto operator<=>(const String& other) const {
return strcmp(data, other.data) <=> 0;
}
#endif
};
3.3 賦值運算符
class Vector {
int* elements;
size_t size;
public:
// 拷貝賦值運算符
Vector& operator=(const Vector& other) {
if (this != &other) { // 防止自賦值
delete[] elements; // 釋放舊資源
size = other.size;
elements = new int[size];
std::copy(other.elements, other.elements + size, elements);
}
return *this;
}
// 移動賦值運算符(C++11)
Vector& operator=(Vector&& other) noexcept {
if (this != &other) {
delete[] elements;
elements = other.elements;
size = other.size;
other.elements = nullptr;
other.size = 0;
}
return *this;
}
// 複合賦值運算符
Vector& operator+=(const Vector& other) {
if (size != other.size) {
throw std::invalid_argument("向量大小不匹配");
}
for (size_t i = 0; i < size; ++i) {
elements[i] += other.elements[i];
}
return *this;
}
// 下標賦值
int& operator[](size_t index) {
if (index >= size) {
throw std::out_of_range("索引超出範圍");
}
return elements[index];
}
// 常量版本的下標運算符
const int& operator[](size_t index) const {
if (index >= size) {
throw std::out_of_range("索引超出範圍");
}
return elements[index];
}
};
3.4 流運算符
class Date {
int year, month, day;
public:
// 聲明為友元以便訪問私有成員
friend std::ostream& operator<<(std::ostream& os, const Date& d);
friend std::istream& operator>>(std::istream& is, Date& d);
};
// 輸出運算符
std::ostream& operator<<(std::ostream& os, const Date& d) {
os << std::setw(4) << std::setfill('0') << d.year << '-'
<< std::setw(2) << std::setfill('0') << d.month << '-'
<< std::setw(2) << std::setfill('0') << d.day;
return os;
}
// 輸入運算符
std::istream& operator>>(std::istream& is, Date& d) {
char dash1, dash2;
is >> d.year >> dash1 >> d.month >> dash2 >> d.day;
if (dash1 != '-' || dash2 != '-') {
is.setstate(std::ios::failbit);
}
return is;
}
3.5 函數調用運算符
class Adder {
int value;
public:
Adder(int v) : value(v) {}
// 函數調用運算符 - 使對象可調用
int operator()(int x) const {
return value + x;
}
// 重載版本
double operator()(double x) const {
return value + x;
}
// 多參數版本
int operator()(int a, int b) const {
return value + a + b;
}
};
// 使用示例
Adder add5(5);
int result1 = add5(10); // 15
double result2 = add5(3.14); // 8.14
int result3 = add5(2, 3); // 10
3.6 類型轉換運算符
class SmartBool {
bool value;
public:
SmartBool(bool v) : value(v) {}
// 顯式類型轉換運算符(C++11)
explicit operator bool() const {
return value;
}
// 轉換為int
operator int() const {
return value ? 1 : 0;
}
// 轉換為string
operator std::string() const {
return value ? "true" : "false";
}
};
// 使用示例
SmartBool sb(true);
if (sb) { // 需要顯式轉換,因為operator bool是explicit的
std::cout << "true" << std::endl;
}
int n = sb; // 隱式轉換為int
std::string s = sb; // 隱式轉換為string
3.7 成員訪問運算符
class SmartPointer {
int* ptr;
public:
SmartPointer(int* p = nullptr) : ptr(p) {}
~SmartPointer() { delete ptr; }
// 解引用運算符
int& operator*() {
if (!ptr) throw std::runtime_error("空指針解引用");
return *ptr;
}
const int& operator*() const {
if (!ptr) throw std::runtime_error("空指針解引用");
return *ptr;
}
// 成員訪問運算符
int* operator->() {
return ptr;
}
const int* operator->() const {
return ptr;
}
// 下標運算符
int& operator[](size_t index) {
if (!ptr) throw std::runtime_error("空指針");
return ptr[index];
}
const int& operator[](size_t index) const {
if (!ptr) throw std::runtime_error("空指針");
return ptr[index];
}
};
四、特殊運算符重載
4.1 內存管理運算符
class MemoryPool {
static const size_t POOL_SIZE = 1024;
static char pool[POOL_SIZE];
static size_t offset;
public:
// 重載 new 運算符
void* operator new(size_t size) {
if (offset + size > POOL_SIZE) {
throw std::bad_alloc();
}
void* ptr = pool + offset;
offset += size;
return ptr;
}
// 重載 delete 運算符
void operator delete(void* ptr) noexcept {
// 在這個簡單的內存池中,我們不實際釋放內存
}
// 重載 new[] 運算符
void* operator new[](size_t size) {
return operator new(size);
}
// 重載 delete[] 運算符
void operator delete[](void* ptr) noexcept {
operator delete(ptr);
}
// 定位 new
void* operator new(size_t size, void* where) {
return where;
}
};
4.2 逗號運算符
class CommaExample {
int value;
public:
CommaExample(int v) : value(v) {}
// 逗號運算符重載
CommaExample operator,(const CommaExample& other) {
// 逗號運算符返回右側操作數
return other;
}
int getValue() const { return value; }
};
// 使用示例
CommaExample a(1), b(2), c(3);
CommaExample d = (a, b, c); // d 的值為 3
五、運算符重載的設計原則
5.1 一致性原則
// 好的設計:保持與內置類型一致的行為
class Rational {
int num, den;
public:
// 算術運算符返回新對象
Rational operator+(const Rational& other) const {
return Rational(num * other.den + other.num * den, den * other.den);
}
// 複合賦值運算符返回引用
Rational& operator+=(const Rational& other) {
num = num * other.den + other.num * den;
den *= other.den;
return *this;
}
// 關係運算符返回bool
bool operator==(const Rational& other) const {
return num * other.den == other.num * den;
}
};
5.2 對稱性原則
class Complex {
double real, imag;
public:
// 非成員函數實現對稱性
friend Complex operator+(const Complex& a, const Complex& b);
friend Complex operator+(const Complex& a, double b);
friend Complex operator+(double a, const Complex& b);
};
// 支持 Complex + double 和 double + Complex
Complex operator+(const Complex& a, double b) {
return Complex(a.real + b, a.imag);
}
Complex operator+(double a, const Complex& b) {
return Complex(a + b.real, b.imag);
}
六、現代C++中的運算符重載
6.1 移動語義與運算符重載
class Matrix {
double* data;
size_t rows, cols;
public:
// 移動賦值運算符
Matrix& operator=(Matrix&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
rows = other.rows;
cols = other.cols;
other.data = nullptr;
other.rows = other.cols = 0;
}
return *this;
}
// 移動加法運算符
Matrix operator+(Matrix&& other) && { // 右值引用限定符
// 如果this是右值,可以重用其存儲
for (size_t i = 0; i < rows * cols; ++i) {
data[i] += other.data[i];
}
other.~Matrix(); // 顯式銷燬other
return std::move(*this);
}
};
6.2 三路比較運算符(C++20)
class Version {
int major, minor, patch;
public:
// 自動生成所有比較運算符
auto operator<=>(const Version& other) const = default;
// 或者自定義實現
/*
std::strong_ordering operator<=>(const Version& other) const {
if (auto cmp = major <=> other.major; cmp != 0) return cmp;
if (auto cmp = minor <=> other.minor; cmp != 0) return cmp;
return patch <=> other.patch;
}
bool operator==(const Version& other) const {
return (*this <=> other) == 0;
}
*/
};
七、運算符重載的性能優化
7.1 返回值優化
class Matrix {
public:
// 返回右值引用以啓用移動語義
Matrix operator+(const Matrix& other) const & { // 左值版本
Matrix result = *this;
result += other;
return result; // 可能會觸發NRVO
}
Matrix operator+(const Matrix& other) && { // 右值版本
*this += other;
return std::move(*this); // 移動當前對象
}
};
7.2 表達式模板
// 表達式模板示例(簡化版)
template<typename E1, typename E2>
class MatrixSum {
const E1& e1;
const E2& e2;
public:
MatrixSum(const E1& a, const E2& b) : e1(a), e2(b) {}
double operator()(size_t i, size_t j) const {
return e1(i, j) + e2(i, j);
}
};
template<typename E1, typename E2>
MatrixSum<E1, E2> operator+(const E1& a, const E2& b) {
return MatrixSum<E1, E2>(a, b);
}
八、運算符重載的常見陷阱
8.1 自賦值問題
class Array {
int* data;
size_t size;
public:
// 錯誤的賦值運算符,沒有處理自賦值
Array& operator=(const Array& other) {
delete[] data; // 如果this == &other,這裏就出問題了
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
return *this;
}
// 正確的版本
Array& operator=(const Array& other) {
if (this != &other) { // 檢查自賦值
delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
};
8.2 異常安全性
class SafeArray {
int* data;
size_t size;
public:
// 強異常安全的賦值運算符
SafeArray& operator=(const SafeArray& other) {
if (this != &other) {
int* new_data = new int[other.size]; // 先分配新內存
std::copy(other.data, other.data + other.size, new_data);
delete[] data; // 再釋放舊內存
data = new_data;
size = other.size;
}
return *this;
}
};
九、運算符重載的最佳實踐
9.1 選擇成員函數還是非成員函數
// 規則:
// 1. 賦值運算符 (=, +=, -= 等) 必須是成員函數
// 2. 函數調用運算符 (()) 必須是成員函數
// 3. 下標運算符 ([]) 必須是成員函數
// 4. 成員訪問運算符 (->) 必須是成員函數
// 5. 類型轉換運算符必須是成員函數
// 6. 對稱運算符 (+, -, *, /, ==, != 等) 應該是非成員函數
// 7. 流運算符 (<<, >>) 必須是非成員函數
9.2 實現關係運算符的技巧
// 只實現 < 和 ==,其他自動推導
class Comparable {
int value;
public:
bool operator<(const Comparable& other) const {
return value < other.value;
}
bool operator==(const Comparable& other) const {
return value == other.value;
}
// 其他關係運算符可以自動推導
bool operator!=(const Comparable& other) const { return !(*this == other); }
bool operator>(const Comparable& other) const { return other < *this; }
bool operator<=(const Comparable& other) const { return !(other < *this); }
bool operator>=(const Comparable& other) const { return !(*this < other); }
};
十、高級應用示例
10.1 多維數組的下標運算符
template<typename T, size_t Rows, size_t Cols>
class Matrix2D {
T data[Rows][Cols];
public:
// 代理類,用於實現a[i][j]語法
class RowProxy {
T* row;
public:
RowProxy(T* r) : row(r) {}
T& operator[](size_t col) { return row[col]; }
const T& operator[](size_t col) const { return row[col]; }
};
RowProxy operator[](size_t row) {
return RowProxy(data[row]);
}
const RowProxy operator[](size_t row) const {
return RowProxy(const_cast<T*>(data[row]));
}
// C++23 支持的多維下標運算符
#if __cplusplus >= 202302L
T& operator[](size_t row, size_t col) {
return data[row][col];
}
const T& operator[](size_t row, size_t col) const {
return data[row][col];
}
#endif
};
10.2 智能指針的完整實現
template<typename T>
class UniquePtr {
T* ptr;
// 刪除器
struct DefaultDeleter {
void operator()(T* p) const {
delete p;
}
};
public:
// 構造函數
explicit UniquePtr(T* p = nullptr) : ptr(p) {}
// 禁止拷貝
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
// 移動語義
UniquePtr(UniquePtr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {
reset(other.release());
}
return *this;
}
// 析構函數
~UniquePtr() {
if (ptr) {
DefaultDeleter()(ptr);
}
}
// 運算符重載
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
// 轉換為bool
explicit operator bool() const { return ptr != nullptr; }
// 獲取原始指針
T* get() const { return ptr; }
// 釋放所有權
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
// 重置指針
void reset(T* p = nullptr) {
if (ptr != p) {
DefaultDeleter()(ptr);
ptr = p;
}
}
};
十一、總結
11.1 運算符重載的核心原則
- 保持直觀性:運算符行為應該符合用户的直覺
- 保持一致性:與內置類型和標準庫的行為保持一致
- 提供完整接口:相關運算符應該一起重載
- 注意性能:儘量減少不必要的拷貝
- 保證異常安全:正確處理異常情況
11.2 現代C++最佳實踐
class ModernClass {
std::vector<int> data;
public:
// 使用默認的比較運算符(C++20)
auto operator<=>(const ModernClass&) const = default;
// 使用noexcept移動操作
ModernClass& operator=(ModernClass&&) noexcept = default;
// 使用引用限定符
ModernClass& operator+=(const ModernClass&) & {
// 只能用於左值
return *this;
}
ModernClass operator+(const ModernClass& other) const & {
ModernClass result = *this;
result += other;
return result;
}
ModernClass operator+(const ModernClass& other) && {
*this += other;
return std::move(*this);
}
};
11.3 使用場景總結
- 數學類:向量、矩陣、複數、有理數
- 容器類:自定義數組、列表、映射
- 智能指針:自定義內存管理
- 迭代器:自定義遍歷行為
- 字符串類:自定義文本處理
- 日期時間類:自定義時間計算
- 文件流:自定義I/O操作
- 表達式計算:自定義語法解析
運算符重載是C++強大表達能力的體現,正確使用可以顯著提高代碼的可讀性和可用性。但同時也需要謹慎使用,避免過度設計和不直觀的行為。通過遵循最佳實踐和設計原則,可以創建出既強大又易於使用的類接口。