🔷 第七章 数组、指针和引用
📄 一句话概括

把形参声明成引用(void swap(int &a, int &b)),就能在函数里直接改原件,而且调用处和传值一模一样——swap(x, y); 既不用写 &,函数里也不用写 *。这是 swap 的第三种、也是最漂亮的写法。引用传参是"想改原件"时最推荐的方案:比指针安全(不会为空),又比传值多一个"能改"的能力。

📝 本节和前面几节的关系

这一节把"改原件"这件事的三种解法凑齐了:6.3.1 传值(改不动)→ 7.6.1 传指针(能改,但要 * 和 &)→ 本节传引用(能改,语法干净)。它还回收了 7.8.1 的铁律:"绑定后不能改绑"在这里恰好是优点——形参 a 绑定的就是调用方那个变量,想改错都难。

一、把 swap 写成最漂亮的样子

void swap(int &a, int &b) {          // 形参是引用
    int t = a; a = b; b = t;         // 直接用 a、b,不需要 *
}

int main() {
    int x = 1, y = 2;
    swap(x, y);                      // 调用和传值一模一样,不需要 &
    // x = 2, y = 1
}

对比一下三种写法(同一件事):

传值 传指针 传引用
函数定义 void f(int a) void f(int *a) void f(int &a)
函数体里改 a = 10;(改不到外面) *a = 10; a = 10;
调用 f(x) f(&x) f(x)
能不能改原件 ❌ ✅ ✅
调用处好看吗 ✅ ❌(多个 &) ✅

引用版把两个"容易漏的地方"都消掉了:调用处不用写 &,函数体里不用写 *。但它和传值长得一模一样——这既是优点(干净),也是唯一的风险点(看代码的人得看一眼函数定义,才知道是传引用)。

二、实例14:通过引用交换数值

#include <iostream>
using namespace std;

void swapByValue(int a, int b) {      // ① 传值:改不动
    int t = a; a = b; b = t;
}

void swapByPtr(int *a, int *b) {      // ② 传指针:能改,但要 * 和 &
    int t = *a; *a = *b; *b = t;
}

void swapByRef(int &a, int &b) {      // ③ 传引用:能改,语法最干净
    int t = a; a = b; b = t;
}

int main() {
    int x = 1, y = 2;

    swapByValue(x, y);
    cout << "传值后:  x = " << x << ", y = " << y << endl;   // 1, 2(没变)

    swapByPtr(&x, &y);
    cout << "传指针后:x = " << x << ", y = " << y << endl;   // 2, 1

    x = 1; y = 2;                                             // 复位
    swapByRef(x, y);
    cout << "传引用后:x = " << x << ", y = " << y << endl;   // 2, 1
    return 0;
}

看到什么算成功:传值后: x = 1, y = 2、传指针后:x = 2, y = 1、传引用后:x = 2, y = 1。

重点对比后两行:结果完全一样,但写法差很多——传指针要 swapByPtr(&x, &y);,传引用只要 swapByRef(x, y);。这就引用存在的意义:把"改原件"这件事做得和传值一样好写。

30 秒实验:写一个 void addOne(int a) { a++; } 和一个 void addOneRef(int &a) { a++; },分别用同一个变量调用,打印结果。前者的变量纹丝不动,后者加了 1。"长得一样,效果差一个 +1"——这就是必须看清函数定义的原因。

三、引用传参的另一个大好处:不拷贝

传值会复制一份。如果参数是个很大的东西(大数组、长字符串、大结构体),复制就很浪费。引用天生避免拷贝:

// 传值:把整段字符串复制一份(浪费)
void print1(string s);
// 传引用:不复制(但函数里能改,不安全)
void print2(string &s);
// 传 const 引用:不复制 + 不能改(最推荐)
void print3(const string &s);

const 引用是函数参数的黄金搭档(7.4.5 的 const 在这里发挥作用):

写法 会拷贝吗 能改原件吗 能接常量实参吗 推荐度
void f(T x) ✅ 拷贝 ❌ ✅ 小类型可用
void f(T &x) ❌ ✅ ❌(可能) 想改时用
void f(const T &x) ❌ ❌ ✅ 只读大对象首选

一句话原则: - 想改原件 → T &x - 只读、且对象大 → const T &x - 只读、且对象小(int、double) → T x 也行

四、常见误区

  1. "引用传参和传值一样,因为调用写法一样"——效果完全不同。传引用能改原件,传值不能。看函数定义才知道(int &a 是引用)。
  2. "既然引用更好,那全都用引用"——不好。如果函数不该改参数,就用 const 引用 或传值。滥用非 const 引用会让"函数会不会偷改我的变量"变得不可预测。
  3. "传引用一定比传指针快"——性能上基本相同,选它是为了安全和可读性,不是为了快。
  4. "传引用不用担心空"——确实不用担心(引用不能为空),这是它优于指针的地方,但也意味着不能用"传空表示失败"——那种场景还得用指针。
  5. "数组也能用引用传(int (&a)[5])"——语法上可以,但用得少。数组传参的常规写法见 7.8.3。
自测三问(点开对答案) 1. 用引用写一个交换函数,定义和调用各长什么样?——`void swap(int &a, int &b) { int t=a; a=b; b=t; }`,调用 `swap(x, y);`(不用 `&`)。 2. 引用传参比指针传参好在哪?——调用处不写 `&`、函数里不写 `*`、也不用判空;不易出错。 3. 函数只读一个大对象,参数怎么写最合适?——`const T &x`(不拷贝 + 不会改 + 能接常量)。

相关笔记