cs106l-lecture-5-2-operators-and-overloading
Course: 斯坦福大学: C++标准库编程 | C++ Standard Library Programming
🎮 CS106L Lecture 5.2 — Operators and Overloading
🏛 课程:Stanford CS106L — Standard C++ Programming Language
📍 主题:运算符与重载(Operators & Overloading)
🎯 目标:理解 C++ 运算符重载的机制、设计原则与语义一致性,掌握如何让自定义类型“像内建类型一样自然”。
🧭 课程定位
本讲是 类与抽象设计(OOP) 单元的第二部分。
前一讲我们学习了“如何定义类与 const 约束”;
本讲进入更高层的“如何让类像语言的一部分那样工作”。
“当类表现得像 int 一样自然时,C++ 才真正成为你的语言。”
🏷 关键词速记
Operator Overloading|运算符重载
Member vs Non-member|成员/非成员函数
Chaining|链式操作
<< / >>|流运算符
[]|下标运算符
()|函数调用运算符
Friend Function|友元函数
Return by Reference|按引用返回
Const Correctness|常量正确性
🎮 30 秒课程摘要
“重载运算符 = 给类赋予语言的直觉。
但要遵守规则:语义一致、行为可预测、简洁安全。”
📚 核心内容讲解
🧩 1️⃣ 为什么需要运算符重载?
C++ 允许为自定义类型定义运算符行为。
例如让 Vector3D 支持加法:
v1 + v2;而不是:
v1.add(v2);📦 示例 1:语义化接口
class Vector {
public:
double x, y;
Vector(double x, double y) : x(x), y(y) {}
Vector operator+(const Vector& rhs) const {
return Vector(x + rhs.x, y + rhs.y);
}
};📤 示例输出:
Vector a(1,2), b(3,4);
Vector c = a + b; // (4,6)
🧩 讲解:
- 重载运算符能让代码更直观、表达语义而非过程。
- 应用于数学类型、容器类、迭代器等。
🧩 2️⃣ 运算符重载的语法规则
return_type operator符号 (参数);| 类别 | 示例 | 通常位置 |
|---|---|---|
| 一元运算符 | operator++() | 成员函数 |
| 二元运算符 | operator+(T rhs) | 成员或非成员 |
| 比较运算符 | operator==, operator< | 非成员(保持对称性) |
| 输入输出运算符 | operator<<, operator>> | 友元函数 |
| 下标运算符 | operator[] | 成员函数 |
| 调用运算符 | operator() | 成员函数 |
🧩 3️⃣ 成员函数 vs 非成员函数
📦 示例 2:成员版本
class Point {
public:
Point(double x, double y) : x(x), y(y) {}
Point operator+(const Point& rhs) const {
return Point(x + rhs.x, y + rhs.y);
}
private:
double x, y;
};📦 示例 3:非成员版本(对称运算符)
class Point {
public:
double x, y;
Point(double x, double y) : x(x), y(y) {}
friend Point operator+(const Point& lhs, const Point& rhs) {
return Point(lhs.x + rhs.x, lhs.y + rhs.y);
}
};🧩 设计规则:
| 原则 | 说明 |
|---|---|
| 对称性 | 若 a + b 与 b + a 都合法,使用非成员版本 |
| 封装性 | 若仅访问成员,可用成员函数 |
| 语义清晰 | 不应改变操作数状态(推荐 const) |
🧩 4️⃣ 链式操作与引用返回
📦 示例 4:运算符链式调用
class Counter {
public:
Counter(int n) : n(n) {}
Counter& operator+=(int x) {
n += x;
return *this; // 返回自身引用以支持链式
}
int get() const { return n; }
private:
int n;
};
Counter c(0);
c += 5 += 3; // ✅ 等价于 c.operator+=(5).operator+=(3)
🧠 讲解:
返回引用是链式操作的关键。
“返回 *this,意味着操作可叠加。”
🧩 5️⃣ 复合与基本运算符实现
通常建议只重载复合版本(如 +=),
再利用它定义基本版本(如 +)。
📦 示例 5:推荐模式
class Vec {
public:
Vec(double x, double y) : x(x), y(y) {}
Vec& operator+=(const Vec& rhs) {
x += rhs.x; y += rhs.y;
return *this;
}
};
Vec operator+(Vec lhs, const Vec& rhs) {
lhs += rhs;
return lhs;
}🧩 设计理念:
- 避免重复逻辑
+通常基于+=实现- 保持行为一致
🧩 6️⃣ 流运算符(I/O Overload)
<< 和 >> 必须定义为非成员友元函数,
因为左侧操作数(如 std::cout)不是你的类对象。
📦 示例 6:输出运算符
#include <iostream>
class Point {
public:
Point(double x, double y) : x(x), y(y) {}
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << "(" << p.x << ", " << p.y << ")";
}
};📦 示例 7:输入运算符
friend std::istream& operator>>(std::istream& is, Point& p) {
return is >> p.x >> p.y;
}📤 使用示例:
Point p(1,2);
std::cout << p; // 输出 (1,2)
std::cin >> p; // 输入两个数更新 p
🧠 讲解:
operator<<必须返回ostream&以支持链式输出:std::cout << p1 << p2 << p3;
🧩 7️⃣ 比较运算符
📦 示例 8:== 与 <
class Point {
public:
bool operator==(const Point& rhs) const {
return x == rhs.x && y == rhs.y;
}
bool operator<(const Point& rhs) const {
return (x < rhs.x) || (x == rhs.x && y < rhs.y);
}
private:
double x, y;
};📦 示例 9:C++20 三向比较(Spaceship Operator)
#include <compare>
auto operator<=>(const Point& lhs, const Point& rhs) = default;🧠 讲解:
<=>自动生成<, <=, >, >=, ==, !=。- 减少重复定义,提高一致性。
🧩 8️⃣ [] 下标运算符
📦 示例 10:可读写版本
class Array {
public:
Array(int size) : data(size) {}
int& operator[](int idx) { return data[idx]; }
const int& operator[](int idx) const { return data[idx]; }
private:
std::vector<int> data;
};🧩 讲解:
提供两个版本:普通 + const
允许:
Array arr(10); arr[0] = 42; // 写 const Array a2(10); std::cout << a2[0]; // 读
🧩 9️⃣ () 调用运算符(Functor)
📦 示例 11:仿函数类
class Adder {
public:
int operator()(int a, int b) const {
return a + b;
}
};
Adder add;
std::cout << add(3, 4); // 输出 7
🧩 讲解:
()可让类表现得像函数。- 用于 STL 算法中的自定义比较器与谓词。
🧩 🔟 常见错误与陷阱
| 错误 | 说明 |
|---|---|
忘记 const | 导致无法操作常量对象 |
| 错误返回值类型 | 导致链式调用失败 |
| 重载语义不一致 | 破坏直觉(如定义非交换加法) |
| 滥用重载 | 不要为非数学语义重载算术符 |
不使用 friend | 无法访问私有成员 |
🧠 原则:
“能清楚表达意图的才值得重载;
不清楚的重载只会制造混乱。”
🧠 记忆口诀
“符号不乱用,语义守初衷;
左右要对称,引用保链通。”
🧪 实践练习
✅ 练习 1:向量加法
实现 Vector2D 的 +, += 与 ==。
✅ 练习 2:点类输出
定义 Point 的 operator<< 与 operator>>。
✅ 练习 3:下标访问
实现一个可读写数组类 Array,支持 arr[i]。
✅ 练习 4:仿函数
实现 Multiplier 类,使 m(2,3) 返回 6。
🪄 创作与工程建议
- 优先使用 非成员函数 以保持对称性(如
a+bvsb+a)。 - 所有算术运算符应定义为
const。 - 复合运算符(如
+=)返回引用 (this)。 - 流运算符 (
<<,>>) 必须返回流对象引用以支持链式。 - 确保运算符语义符合直觉(不要让
a+b意味“连接日志”)。 - C++20 以后优先使用
<=>简化比较逻辑。
✅ 小结表
| 运算符 | 推荐位置 | 返回类型 | 备注 |
|---|---|---|---|
+ / - / * | 非成员 | 值 | 保对称性 |
+= / -= | 成员 | 引用 | 支持链式 |
<< / >> | 友元 | ostream& / istream& | 流操作 |
[] | 成员 | 引用 / const 引用 | 读写两版 |
() | 成员 | 自定义 | 仿函数 |
<=> | 非成员 | 自动生成比较 | C++20 |
“让类型像 int 一样自然,是 C++ 抽象的最高境界。”
是否希望我继续整理下一讲
👉 Lecture 5.3 — Templates & Class Design
(类模板、偏特化、enable_if、类型萃取与模板元编程)为同样结构?