运算符重载基础
定义与作用
运算符重载允许为自定义类型定义运算符行为,使自定义类型像内置类型一样自然使用运算符:
Complex a(1, 2), b(3, 4);
Complex c = a + b; // 而非 a.add(b)
std::cout << a; // 而非 a.print()
if (a == b) { /* ... */ } // 而非 a.equals(b)
它解决的核心痛点是可读性——数学类、容器类、智能指针类等,使用运算符比命名函数更直观自然。
核心原理
可重载运算符全景
成员 vs 非成员的选择
| 运算符 | 推荐形式 | 原因 |
|---|---|---|
= [] () -> | 成员 | 语言强制 |
+= -= *= 等复合赋值 | 成员 | 修改左操作数自身 |
+ - * / 等二元算术 | 非成员 | 支持左操作数隐式转换 |
== != < 等关系 | 非成员 | 对称性 |
<< >> 流操作 | 非成员 | 左操作数是 ostream 非本类 |
++ -- 自增自减 | 成员 | 修改对象自身 |
完整示例
示例一:复数类的运算符重载
场景说明:设计复数类,支持完整的算术运算、比较和流输出。
#include <iostream>
#include <cmath>
class Complex {
public:
Complex(double real = 0, double imag = 0) : real_(real), imag_(imag) {}
// ---- 成员形式:复合赋值 ----
Complex& operator+=(const Complex& rhs) {
real_ += rhs.real_;
imag_ += rhs.imag_;
return *this;
}
Complex& operator-=(const Complex& rhs) {
real_ -= rhs.real_;
imag_ -= rhs.imag_;
return *this;
}
Complex& operator*=(const Complex& rhs) {
double r = real_ * rhs.real_ - imag_ * rhs.imag_;
double i = real_ * rhs.imag_ + imag_ * rhs.real_;
real_ = r;
imag_ = i;
return *this;
}
// ---- 成员形式:自增 / 自减 ----
Complex& operator++() { // 前置 ++
++real_;
return *this;
}
Complex operator++(int) { // 后置 ++(int 哑元)
Complex tmp = *this;
++real_;
return tmp;
}
// ---- 非成员形式:二元算术(声明为友元以访问私有成员)----
friend Complex operator+(const Complex& a, const Complex& b);
friend Complex operator-(const Complex& a, const Complex& b);
friend Complex operator*(const Complex& a, const Complex& b);
// ---- 非成员形式:等于 / 不等于 ----
friend bool operator==(const Complex& a, const Complex& b);
friend bool operator!=(const Complex& a, const Complex& b);
// ---- 非成员形式:流输出 ----
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
double modulus() const {
return std::sqrt(real_ * real_ + imag_ * imag_);
}
private:
double real_, imag_;
};
// 二元算术:非成员 + 友元
Complex operator+(const Complex& a, const Complex& b) {
return Complex(a.real_ + b.real_, a.imag_ + b.imag_);
}
Complex operator-(const Complex& a, const Complex& b) {
return Complex(a.real_ - b.real_, a.imag_ - b.imag_);
}
Complex operator*(const Complex& a, const Complex& b) {
return Complex(a.real_ * b.real_ - a.imag_ * b.imag_,
a.real_ * b.imag_ + a.imag_ * b.real_);
}
// 关系运算符
bool operator==(const Complex& a, const Complex& b) {
return a.real_ == b.real_ && a.imag_ == b.imag_;
}
bool operator!=(const Complex& a, const Complex& b) {
return !(a == b); // 用 == 实现 !=,减少重复
}
// 流输出
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << "(" << c.real_;
if (c.imag_ >= 0)
os << " + " << c.imag_ << "i)";
else
os << " - " << -c.imag_ << "i)";
return os;
}
int main() {
std::cout << "===== 复数运算 =====" << std::endl;
Complex a(3, 4); // 3 + 4i
Complex b(1, -2); // 1 - 2i
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
Complex c = a + b;
std::cout << "a + b = " << c << std::endl;
Complex d = a * b;
std::cout << "a * b = " << d << std::endl;
// 复合赋值
a += Complex(1, 1);
std::cout << "a += (1+1i) → " << a << std::endl;
// 自增
std::cout << "++a = " << ++a << std::endl;
std::cout << "a++ = " << a++ << std::endl;
std::cout << "after a++ → " << a << std::endl;
// 比较
std::cout << std::boolalpha;
std::cout << "a == b? " << (a == b) << std::endl;
std::cout << "a != b? " << (a != b) << std::endl;
// 模
std::cout << "|a| = " << a.modulus() << std::endl;
return 0;
}
预期输出:
===== 复数运算 =====
a = (3 + 4i)
b = (1 - 2i)
a + b = (4 + 2i)
a * b = (11 - 2i)
a += (1+1i) → (4 + 5i)
++a = (5 + 5i)
a++ = (5 + 5i)
after a++ → (6 + 5i)
a == b? false
a != b? true
|a| = 7.81025
逐段分析:
- 成员重载:
+=-=*=修改左操作数,++修改自身——都是成员 - 非成员 + 友元:
+-*二元运算返回新对象,用非成员实现支持对称隐式转换 operator++()(无参)是前置,operator++(int)(哑元 int)是后置——后置返回值而非引用operator==用非成员实现保持对称性;operator!=基于==实现,避免重复operator<<第一个参数是std::ostream——只能是友元非成员,不能是成员
示例二:轻量级字符串类
#include <iostream>
#include <cstring>
#include <algorithm>
#include <utility>
class String {
public:
String() : data_(new char[1]{'\0'}), size_(0) {}
String(const char* str) : size_(std::strlen(str)) {
data_ = new char[size_ + 1];
std::copy(str, str + size_ + 1, data_);
}
// 拷贝
String(const String& other) : size_(other.size_) {
data_ = new char[size_ + 1];
std::copy(other.data_, other.data_ + size_ + 1, data_);
}
// 移动
String(String&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
// 拷贝赋值
String& operator=(const String& other) {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new char[size_ + 1];
std::copy(other.data_, other.data_ + size_ + 1, data_);
}
return *this;
}
// 移动赋值
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
~String() { delete[] data_; }
// 下标访问(可读可写)
char& operator[](size_t index) { return data_[index]; }
// 下标访问(只读)
const char& operator[](size_t index) const { return data_[index]; }
// 拼接
friend String operator+(const String& a, const String& b);
// 比较
friend bool operator==(const String& a, const String& b);
friend bool operator<(const String& a, const String& b);
// 流输出
friend std::ostream& operator<<(std::ostream& os, const String& s);
size_t size() const { return size_; }
const char* c_str() const { return data_; }
private:
char* data_;
size_t size_;
};
String operator+(const String& a, const String& b) {
String result;
delete[] result.data_;
result.size_ = a.size_ + b.size_;
result.data_ = new char[result.size_ + 1];
std::copy(a.data_, a.data_ + a.size_, result.data_);
std::copy(b.data_, b.data_ + b.size_ + 1, result.data_ + a.size_);
return result;
}
bool operator==(const String& a, const String& b) {
return std::strcmp(a.data_, b.data_) == 0;
}
bool operator<(const String& a, const String& b) {
return std::strcmp(a.data_, b.data_) < 0;
}
std::ostream& operator<<(std::ostream& os, const String& s) {
return os << s.data_;
}
int main() {
std::cout << "===== 自定义字符串类 =====" << std::endl;
String s1("Hello");
String s2(" World");
std::cout << "s1 = " << s1 << std::endl;
std::cout << "s2 = " << s2 << std::endl;
String s3 = s1 + s2;
std::cout << "s1 + s2 = " << s3 << std::endl;
// 下标修改
s3[0] = 'h';
std::cout << "s3[0] = 'h' → " << s3 << std::endl;
// 比较
std::cout << std::boolalpha;
std::cout << "s1 == \"Hello\"? " << (s1 == "Hello") << std::endl;
std::cout << "s1 < s2? " << (s1 < s2) << std::endl;
return 0;
}
预期输出:
===== 自定义字符串类 =====
s1 = Hello
s2 = World
s1 + s2 = Hello World
s3[0] = 'h' → hello World
s1 == "Hello"? true
s1 < s2? true
易错场景与面试考点
易错场景
| 场景 | 错误表现 | 正确做法 |
|---|---|---|
= 重载但忘记自赋值检查 | 自我赋值时资源被释放 | if (this != &rhs) |
后置 ++ 返回引用 | 返回临时对象的悬垂引用 | 后置返回值(非引用) |
+ 作为成员重载 | 不支持左操作数隐式转换 | 非成员函数 |
| 滥用运算符 | 违反直觉语义(如 + 做减法) | 运算符保留自然语义 |
&& 或 ` | ` 重载 |
常见面试问题
哪些运算符必须重载为成员函数?哪些不能重载?——必须成员:
=[]()->。不可重载:::..*?:。运算符重载选择成员还是非成员?——修改左操作数的(
=+=++)用成员。对称操作(+==)用非成员,支持两侧隐式转换。流操作(<<>>)必须非成员。前置和后置
++如何区分?——前置无参operator++(),后置有 int 哑元operator++(int)。前置返回引用,后置返回值。为什么
<<和>>通常声明为友元?——它们的左操作数是std::ostream/std::istream,不可能是本类对象,所以只能是自由函数。需要访问私有成员时声明为友元。重载
&&和||有什么问题?——会失去短路求值特性,所有操作数都会被求值。标准不建议重载这两个运算符。
小结
- 运算符重载提升可读性——让自定义类型像内置类型一样自然
- 选择原则:修改自身用成员,对称操作用非成员,流操作用友元非成员
- 四个必须成员重载:
=[]()->;四个不可重载:::..*?: - 后置
++/--用 int 哑元区分,返回的是值而非引用 - 语义一致性:重载的运算符应保持直觉行为