Lesson 0304 · 运算符重载与友元

③ 面向对象程序设计 — 第 4 课

三句话速览
  1. a + b = a.operator+(b),右边当参数。
  2. cout << 对象 只能写成友元,左边不是对象。
  3. 返回 ostream& 才能连续 <<;少用 friend。
上节课(0303):多态 = 同一接口不同行为,virtual + override。
这节课:让 +、==、<< 认识你自己的类——运算符重载;以及给「朋友」开门的 friend。

1. 想写 a + b

你希望这样写:

Vector2 c = a + b;

而不是这样:

Vector2 c = a.add(b);

运算符重载就是给 +、==、<< 这些符号定义「对你的类怎么工作」。它不改变运算符的优先级和结合方向,只改变「作用在你的类型上时做什么」。

2. 重载 +

class Vector2 {
    int x, y;
public:
    Vector2(int x, int y) : x(x), y(y) {}

    Vector2 operator+(const Vector2& v) const {
        return Vector2(x + v.x, y + v.y);   // 两个分量分别相加
    }
};

int main() {
    Vector2 a(1, 2);
    Vector2 b(3, 4);
    Vector2 c = a + b;          // 相当于 a.operator+(b)
    cout << c.x << " " << c.y;   // 4 6
}
记忆:a + b 就是 a.operator+(b)。参数是右边的 b,返回值是新结果。

注意两个细节:参数写成 const Vector2& 是「只读地借用」,不复制;函数末尾的 const 表示 a 自己不被修改——加法本来就不该改左操作数。

3. 重载 ==

bool operator==(const Vector2& v) const {
    return x == v.x && y == v.y;
}

这样就能写 if (a == b),不用再一个个比较分量。

返回 bool 是规矩:比较运算应该给出「是/否」。参数还是 const&,因为它只看不改。

4. 重载 << —— 配合 cout

想让 cout << a 直接打印 (1, 2)。但 cout << a 左边是 cout,不是你的对象,所以不能写成成员函数,要用友元函数:

class Vector2 {
    int x, y;
public:
    Vector2(int x, int y) : x(x), y(y) {}

    friend ostream& operator<<(ostream& out, const Vector2& v);
};

// 友元不是成员函数,但可以访问 private 的 x, y
ostream& operator<<(ostream& out, const Vector2& v) {
    out << "(" << v.x << ", " << v.y << ")";
    return out;              // 返回 out 才能连续 <<
}

两个参数都不能少:第一个是 ostream&(左边那台「输出机器」),第二个是要打印的对象。返回 out 的引用,是为了让 cout << c << endl 这种连续写法成立。

5. friend 是什么

friend 字面意思「朋友」:它允许一个不是成员函数的函数访问你的 private 成员。

写法说明
friend ostream& operator<<(...)声明这个函数是朋友
普通成员函数本来就是自己人,不用 friend
能用成员函数解决就不要 friend。只有 cout << 对象 这类「左边不是你的对象」的情况,才需要 friend。

换句话说:friend 是在封装的墙上开一扇小门,只为那些非开不可的写法准备。门开多了,封装就名存实亡。

6. 完整示例

#include <iostream>
using namespace std;

class Vector2 {
    int x, y;
public:
    Vector2(int x, int y) : x(x), y(y) {}

    Vector2 operator+(const Vector2& v) const {
        return Vector2(x + v.x, y + v.y);
    }

    bool operator==(const Vector2& v) const {
        return x == v.x && y == v.y;
    }

    friend ostream& operator<<(ostream& out, const Vector2& v);
};

ostream& operator<<(ostream& out, const Vector2& v) {
    out << "(" << v.x << ", " << v.y << ")";
    return out;
}

int main() {
    Vector2 a(1, 2), b(3, 4);
    Vector2 c = a + b;

    cout << c << endl;          // (4, 6)
    cout << (a == b) << endl;    // 0(false)

    return 0;
}

读一遍 main:c = a + b 走成员版 operator+;cout << c 走友元版 operator<<;a == b 走成员版 operator==。三个运算符,两条规则:左操作数是你的对象 → 成员;不是 → 友元。

7. 坑点清单

8. 小测

Q1. 重载 + 的成员函数名是什么?
Q2. friend 的作用是什么?
Q3. 为什么 cout << 对象 通常要用 friend?
Q4. a + b 等价于什么?

9. 动手练习

题目:Vector2 完整版

- 用 class 定义 Vector2,成员 int x, y
- 重载 operator+:两个向量相加
- 重载 operator==:判断两个向量是否相同
- 用 friend 重载 operator<<:打印成 (x, y)
- main 里创建 a(1,2) 和 b(3,4),输出 a + b 和 a == b

进阶:再重载 operator*,支持向量乘一个整数(两个分量都放大)。
卡住时的自查顺序
  1. operator+ 的参数是不是 const Vector2&?函数末尾有没有 const?
  2. a + b 的结果里,x 用的是自己的 x 加对方的 v.x 吗?
  3. operator<< 是声明成 friend 的自由函数,而不是成员函数?
  4. 定义里最后有没有 return out;?没有就试试 cout << c << endl;。
  5. 友元的声明和类外定义,参数表、&、const 是否一字不差?
  6. 输出是 (4, 6) 和 0 吗?不是的话先单独打印 c.x、c.y 定位。

本课小结

下一课预告
0401 线性表与顺序表 —— 从这里开始进入数据结构的第一个阶段:数据怎么存,决定了操作有多快。先看最朴素的顺序表(数组 / vector),把「按下标访问 O(1)、中间插删 O(n)」这件事用一张表钉死,再谈它什么时候不该用。
本课练的 Vector2 到 0401 会换成另一个同样叫 Vector 的东西——std::vector,别搞混。

← 上一课:多态与虚函数 · 课程对照表

视频 黑马第 80-105 集对应段 · 书籍 Primer Plus 第 11 章

在线 CodeFather 编程导航 — C++ 教程(补充视频参考)