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|常量正确性


“重载运算符 = 给类赋予语言的直觉。

但要遵守规则:语义一致、行为可预测、简洁安全。”



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)

🧩 讲解:

  • 重载运算符能让代码更直观、表达语义而非过程。
  • 应用于数学类型、容器类、迭代器等。

return_type operator符号 (参数);
类别示例通常位置
一元运算符operator++()成员函数
二元运算符operator+(T rhs)成员或非成员
比较运算符operator==, operator<非成员(保持对称性)
输入输出运算符operator<<, operator>>友元函数
下标运算符operator[]成员函数
调用运算符operator()成员函数

📦 示例 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 + bb + a 都合法,使用非成员版本
封装性若仅访问成员,可用成员函数
语义清晰不应改变操作数状态(推荐 const

📦 示例 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:推荐模式

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;
}

🧩 设计理念:

  • 避免重复逻辑
  • + 通常基于 += 实现
  • 保持行为一致

<<>> 必须定义为非成员友元函数

因为左侧操作数(如 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;

📦 示例 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;

🧠 讲解:

  • <=> 自动生成 <, <=, >, >=, ==, !=
  • 减少重复定义,提高一致性。

📦 示例 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]; // 读
    

📦 示例 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:点类输出

定义 Pointoperator<<operator>>

练习 3:下标访问

实现一个可读写数组类 Array,支持 arr[i]

练习 4:仿函数

实现 Multiplier 类,使 m(2,3) 返回 6


  • 优先使用 非成员函数 以保持对称性(如 a+b vs b+a)。
  • 所有算术运算符应定义为 const
  • 复合运算符(如 +=)返回引用 (this)。
  • 流运算符 (<<, >>) 必须返回流对象引用以支持链式。
  • 确保运算符语义符合直觉(不要让 a+b 意味“连接日志”)。
  • C++20 以后优先使用 <=> 简化比较逻辑。

运算符推荐位置返回类型备注
+ / - / *非成员保对称性
+= / -=成员引用支持链式
<< / >>友元ostream& / istream&流操作
[]成员引用 / const 引用读写两版
()成员自定义仿函数
<=>非成员自动生成比较C++20

“让类型像 int 一样自然,是 C++ 抽象的最高境界。”


是否希望我继续整理下一讲

👉 Lecture 5.3 — Templates & Class Design

(类模板、偏特化、enable_if、类型萃取与模板元编程)为同样结构?