← 目录 / 第八章 · 函数 / 8.2 函数参数详解
参数是函数与外界沟通的桥梁。本节讲清楚值传递、引用传递、指针传递这三种"数据怎么送进函数"的方式,以及能让调用更省事的默认参数。
8.2.1
C++ 中函数参数共有三种传递方式:值传递、引用传递和指针传递。三者的核心区别在于:函数内对参数的操作,是否能影响到调用方的原始变量。
int xf(a)int &xf(a)(同值传递,自动绑定)* 修改原变量。int *xf(&a)(需取地址)8.2.2
最常用的默认方式。调用时将实参的值复制一份传给形参,两者互不干扰。适合传递小型数据(int、double、char 等)且不需要修改原值的场景。
| 1 | void TryDouble(int x) // x 是副本 |
| 2 | { |
| 3 | x = x * 2; // 只改了副本 |
| 4 | cout << "函数内 x = " << x; // 10 |
| 5 | } |
| 6 | |
| 7 | int main() |
| 8 | { |
| 9 | int a = 5; |
| 10 | TryDouble(a); // 传入 a 的副本 |
| 11 | cout << "函数外 a = " << a; // 仍是 5,原变量未变 |
| 12 | return 0; |
| 13 | } |
int 这样的小型类型开销极小;但若传递大型结构体或 string,拷贝成本较高,此时应改用引用传递。8.2.3
在参数类型后加 &,函数接收的是原变量的别名——形参和实参指向同一块内存,对形参的任何操作都直接作用于原变量。调用时写法和值传递完全相同,& 只写在函数定义处。
| 1 | void RealDouble(int &x) // & 表示引用,x 就是原变量的别名 |
| 2 | { |
| 3 | x = x * 2; // 直接修改原变量 |
| 4 | cout << "函数内 x = " << x; // 10 |
| 5 | } |
| 6 | |
| 7 | // 经典用途:交换两个变量 |
| 8 | void Swap(int &a, int &b) |
| 9 | { |
| 10 | int tmp = a; |
| 11 | a = b; b = tmp; |
| 12 | } |
| 13 | |
| 14 | int main() |
| 15 | { |
| 16 | int a = 5; |
| 17 | RealDouble(a); // 调用写法与值传递相同 |
| 18 | cout << "函数外 a = " << a; // 10,原变量已修改 |
| 19 | |
| 20 | int x = 3, y = 7; |
| 21 | Swap(x, y); |
| 22 | cout << x << " " << y; // 7 3 |
| 23 | return 0; |
| 24 | } |
const int &x。编译器会阻止你在函数内修改 x,既安全又高效。8.2.4
将原变量的内存地址传入函数,形参是一个指针变量(int *x)。函数内需要用解引用运算符 *x 才能访问或修改原变量的值。调用时须用取地址运算符 &a 传入地址。
指针传递与引用传递效果相同(都能修改原变量),但写法更繁琐。在 C++ 中优先使用引用传递;指针传递主要出现在与 C 语言兼容、或需要传递数组的场景。
| 1 | void PtrDouble(int *x) // x 是指向 int 的指针 |
| 2 | { |
| 3 | *x = *x * 2; // 解引用:通过地址修改原变量 |
| 4 | cout << "函数内 *x = " << *x; // 10 |
| 5 | } |
| 6 | |
| 7 | // 经典用途:指针版 Swap |
| 8 | void PtrSwap(int *a, int *b) |
| 9 | { |
| 10 | int tmp = *a; |
| 11 | *a = *b; *b = tmp; |
| 12 | } |
| 13 | |
| 14 | int main() |
| 15 | { |
| 16 | int a = 5; |
| 17 | PtrDouble(&a); // 传地址:&a |
| 18 | cout << "函数外 a = " << a; // 10,原变量已修改 |
| 19 | |
| 20 | int x = 3, y = 7; |
| 21 | PtrSwap(&x, &y); // 注意:传 &x 不是 x |
| 22 | cout << x << " " << y; // 7 3 |
| 23 | return 0; |
| 24 | } |
*x 就是"顺着箭头走过去",操作箭头指向的 anullptr(空指针)。因此指针比引用更灵活,但也更危险——解引用空指针会导致程序崩溃。8.2.5
| 对比项 | 值传递 | 引用传递 | 指针传递 |
|---|---|---|---|
| 形参写法 | int x |
int &x |
int *x |
| 调用写法 | f(a) |
f(a) |
f(&a) |
| 函数内访问 | x(操作副本) |
x(操作原变量) |
*x(解引用操作原变量) |
| 能否修改原变量 | ❌ 不能 | ✅ 能 | ✅ 能 |
| 是否产生拷贝 | ✅ 产生(开销大) | ❌ 不产生 | ❌ 不产生(仅传地址) |
| 可以为空 | — | ❌ 引用必须绑定有效变量 | ✅ 可以是 nullptr |
| 推荐使用场景 | 读取小型数据、不需修改原值 | 需修改原值,或传递大对象(加 const) | 与 C 语言兼容、传递数组、动态内存 |
const 引用(最优雅)8.2.6
有些参数在大多数情况下都传同一个值,只有极少数情况才需要改。C++ 允许在定义函数时,给形参预设一个默认值——调用时如果省略这个实参,就自动使用默认值;如果传了,就用传入的值覆盖默认值。
| 1 | void PrintLine(string msg, char sep = '-') // sep 有默认值 '-' |
| 2 | { |
| 3 | for (int i = 0; i < 20; i++) cout << sep; |
| 4 | cout << endl << msg << endl; |
| 5 | } |
| 6 | |
| 7 | int main() |
| 8 | { |
| 9 | PrintLine("Hello"); // 省略 sep,自动用默认值 '-' |
| 10 | PrintLine("World", '*'); // 传入 '*',覆盖默认值 |
| 11 | return 0; |
| 12 | } |
一旦某个参数有了默认值,它右边的所有参数也必须有默认值,不能中间空出一个没有默认值的参数——因为调用时省略的实参只能从右边开始省略,编译器没法猜中间空缺的是哪个。
| 1 | // ✅ 正确:默认参数从右往左连续 |
| 2 | void F1(int a, int b = 1, int c = 2); |
| 3 | |
| 4 | // ❌ 错误:a 有默认值,但右边的 b 没有——编译不通过 |
| 5 | void F2(int a = 1, int b); |
如果函数既有声明又有定义(见 8.1.3 节),默认值只能写在其中一处,通常写在声明里,定义处不再重复写默认值——写两遍反而会导致编译错误。
F1(int a, int b=1, int c=2),调用 F1(5, , 10) 是非法的,必须写全 F1(5, 1, 10)。int b = 1,定义处又写一遍 int b = 1,编译器会报"重复默认参数"错误——定义处直接写 int b 即可。void F(int a) 和 void F(int a, int b = 5),调用 F(10) 时两个函数都能满足调用,编译器不知道选哪个,会报错。下一节「函数重载」会详细展开这个陷阱。