传值与传引用
困难8传值与传引用:复印照片还是共享电子文档?
给好朋友分享一张照片,你有两种方式:一种是把它复印一份给他,他可以在复印件上涂鸦、剪裁,但你的原版照片丝毫不变;另一种是直接把电子版发给他,你们俩同时查看同一个文件,他修改后你也会看到变化。C++ 里的函数参数传递也遵循同样的道理——传值就像复印照片,传引用就像共享电子文档。
传值(pass by value):复制一份,互不影响
默认情况下,C++ 函数的参数采用传值方式。调用函数时,实参的值会被复制一份给形参。函数内部对形参的任何修改,都只作用于那个副本,外面的原变量纹丝不动。
看下面的例子,我们想给一个数加 1,但主函数里的变量并没有被改变:
#include <iostream>
using namespace std;
void addOne(int x) { // 传值,x 是 num 的副本
x = x + 1; // 修改的是副本
cout << "函数内x = " << x << endl;
}
int main() {
int num = 5; // 定义变量num,初始值5
addOne(num); // 调用函数,传入num的副本
cout << "主函数中num = " << num << endl; // 仍然是5!
return 0;
}
输出:
函数内x = 6
主函数中num = 5
生活中的例子:你给同桌抄作业,抄的是你写的复印件。他在复印件上改来改去,你自己的作业本一个字都没变。传值就是这种“给了复制品”的方式。
适用场景:当你只需要读取变量的值,而不希望函数意外修改它时,用传值更安全。比如计算成绩的总分,只需要传入成绩的副本,函数内部怎么算都不会改掉原成绩。
传引用(pass by reference):共用同一份,改谁都变
如果希望函数直接修改外部的变量,就要用传引用。在函数形参的类型后面加一个 & 符号(称为引用符号),就像给出一个共享链接。此时形参就是实参的别名,对形参的任何操作都等同于对原变量的操作——你改了,我也看到了。
上面那个加 1 的例子,改成传引用看看:
#include <iostream>
using namespace std;
void addOneRef(int &x) { // 加上&表示传引用,x是num的别名
x = x + 1; // 改别名等于改原变量
cout << "函数内x = " << x << endl;
}
int main() {
int num = 5; // 定义变量num
addOneRef(num); // 传引用,不带&符号,但函数接收时是引用
cout << "主函数中num = " << num << endl; // 变成6了!
return 0;
}
输出:
函数内x = 6
主函数中num = 6
生活中的例子:你和朋友在同一份在线文档里写班级活动计划。他添加一条“买零食”,你刷新页面也立刻看到新增内容。传引用就是这种“共享同一份文档”的方式。
适用场景:当函数需要修改传入的变量,或者要避免复制大型数据(比如数组、结构体)带来的性能开销时,用传引用。比如编写一个 swap 函数交换两个变量的值,就必须用传引用。
传值与传引用的核心区别
| 对比项 | 传值 | 传引用 |
|---|---|---|
| 是否复制 | 复制一份副本 | 不复制,共用原变量 |
| 修改形参影响实参? | 不影响 | 影响 |
| 安全性 | 安全(不会意外修改外部变量) | 有风险(可能误改原变量) |
| 效率(对大型数据) | 慢(复制开销大) | 快(不复制) |
| 语法 | 普通类型参数 | 类型后加 & |
新手常见错误
错误1:在需要修改原变量的地方忘记加 &
很多同学写交换函数时,误用了传值:
// 错误示范:传值无法交换
void swapBad(int a, int b) {
int temp = a; // 交换的是副本
a = b;
b = temp;
}
// 调用后原变量不变
正确的写法必须加上 &,让形参成为实参的别名。
错误2:传引用却传入常量或表达式
传引用要求实参必须是一个可修改的左值(即变量)。不能直接传入常量(如 5)或表达式(如 a+b),因为常量没有存储地址,无法被引用。
void setZero(int &x) { x = 0; }
// 调用:
setZero(10); // 错误!10是常量
setZero(a + b); // 错误!表达式不是左值
错误3:用传引用仅仅为了读取,但无意中修改了原数据
如果你只是读取数据(比如打印成绩),用传值或 const 引用更安全。不小心用普通引用,可能会在函数里意外改掉数据:
void printScore(int &score) {
score = score + 100; // 误操作:把原成绩改了!
cout << "成绩:" << score << endl;
}
正确的做法:如果只读,用 const int &(常引用)或传值。
完整示例:用传引用实现交换函数
下面是一个完整的可运行程序,演示如何通过传引用交换两个变量的值。这是 C++ 初学函数时最经典的例子。
#include <iostream>
using namespace std;
// 交换两个整数,使用传引用
void swapValues(int &a, int &b) { // a和b是传入变量的别名
int temp = a; // 用临时变量保存a的值
a = b; // 将b的值赋给a(修改原变量x)
b = temp; // 将原a的值赋给b(修改原变量y)
}
int main() {
int first = 10; // 定义变量first,初始值10
int second = 20; // 定义变量second,初始值20
cout << "交换前:first = " << first << ", second = " << second << endl;
swapValues(first, second); // 传引用,实际交换了first和second
cout << "交换后:first = " << first << ", second = " << second << endl;
return 0;
}
输出:
交换前:first = 10, second = 20
交换后:first = 20, second = 10
小窍门:当你需要函数修改传入的变量时,用传引用。如果只是读取数据,用传值更安全(不会意外改掉原数据)。试试自己写一个 swap 函数(交换两个数),用传引用实现。
相关知识点指引
- const 引用:如果你只想读取数据又不想复制(提高效率),可以写成
void show(const int &x),这样既不能修改原变量,又没有复制开销。 - 指针与引用:指针也能达到类似“修改原变量”的效果,但语法更复杂(需要取地址和解引用)。引用是 C++ 更安全的替代品。
- 引用作为返回值:函数可以返回引用,比如
int& getElement(int arr[], int index),这样可以对数组元素直接赋值。 - 函数重载:传值和传引用可以构成重载(参数类型不同),但要注意重载决议的细节。
例题精讲
以下代码输出什么? void modify(int x) { x = 10; } int main() { int a = 5; modify(a); cout << a; return 0; }
以下程序试图交换两个整数的值,但结果不正确。请在划线处填入合适的修改方式(使用引用参数):
void swap(___ x, ___ y) {
int temp = x;
x = y;
y = temp;
}
int main() {
int a = 3, b = 7;
swap(a, b);
// 期望输出 a=7, b=3
}下面的代码中,函数`fun`通过传引用修改了全局数组的第一个元素。 int arr[] = {1,2,3}; void fun(int (&r)[3]) { r[0] = 100; } int main() { fun(arr); cout << arr[0]; // 输出100 return 0; }
以下关于传值和传引用的说法,哪一项是正确的?
以下程序希望在函数中返回两个数的和并同时通过参数返回差值。请完成函数定义,使用引用参数。
void calc(int a, int b, int &sum, ___ &diff) {
sum = a + b;
diff = a - b;
}
int main() {
int s, d;
calc(10, 3, s, d);
cout << s << ' ' << d; // 输出13 7
}