把形参声明成引用(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 也行
四、常见误区
- "引用传参和传值一样,因为调用写法一样"——效果完全不同。传引用能改原件,传值不能。看函数定义才知道(
int &a是引用)。 - "既然引用更好,那全都用引用"——不好。如果函数不该改参数,就用
const 引用或传值。滥用非 const 引用会让"函数会不会偷改我的变量"变得不可预测。 - "传引用一定比传指针快"——性能上基本相同,选它是为了安全和可读性,不是为了快。
- "传引用不用担心空"——确实不用担心(引用不能为空),这是它优于指针的地方,但也意味着不能用"传空表示失败"——那种场景还得用指针。
- "数组也能用引用传(
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`(不拷贝 + 不会改 + 能接常量)。- ☐ 7.8.3 使用数组作为函数参数(下一节)
相关笔记
- 传递地址 —— 指针版 swap
- 传值调用 —— 三种写法的起点
- 引用概述 —— "不能改绑"为什么在这里是优点
- 指向常量的指针与指针常量 ——
const在这里回归 - 🔷 C++基础 · 索引 —— 全课路线图