把一个变量作为实参传进函数后,如果我们在函数内部修改了这个参数的值,函数外面的原变量到底会不会跟着改变呢?
这取决于函数参数采用哪种传递方式。在 C++ 中,常用的参数传递方式有 3 种:① 值传递;② 引用传递;③ const 引用传递。
C++ 值传递
在 C++ 中,如果函数的形参直接写成 int num、double price 这种普通变量的形式,那么它采用的就是 “值传递”。
语法:
返回类型 函数名(数据类型 参数名) {
// 函数体
}说明:
在值传递方式下,当我们调用函数时,程序会把实参的值复制一份交给形参。这就好比你把原件留在手里,只印了一份 “复印件” 交给函数。函数内部不管怎么修改手里的这份复印件,外面的 “原件” 都不会受到任何影响,如下图所示。

示例 1:基本类型的值传递
#include <iostream>
void changeValue(int num) {
num = 99;
std::cout << "函数内部 num = " << num << '\n';
}
int main() {
int x = 10;
changeValue(x);
std::cout << "函数外部 x = " << x << '\n';
return 0;
}运行结果如下。
函数内部 num = 99
函数外部 x = 10分析:
在这个例子中,调用 changeValue(x) 时,程序把实参 x 的值 10 复制了一份交给形参 num。在函数内部执行 num = 99; 后,形参 num 的值变成了 99,因此函数内部打印出 99。
但是 main() 里的原变量 x 并没有被修改,函数执行完毕后再次打印 x,结果依然是 10。对于 int、double、char、bool 等占用内存很小的基本类型,如果不需要修改外部原变量,我们直接使用值传递即可。
C++ 引用传递
有些函数的任务恰恰就是直接修改外面的原变量,比如把某个变量清零,或者交换两个变量的值。如果使用值传递,函数执行完后外面的原变量不会发生任何改变,这时我们就需要改用 “引用传递”。
1. 引用传递的基本用法
在 C++ 中,使用引用传递只需要在参数的数据类型后面加上一个 & 符号,比如写成 int& num。
语法:
返回类型 函数名(数据类型& 参数名) {
// 函数体
}说明:
注意这里的 & 写在类型名后面,并不是按位与运算符,而是表示把该形参声明为外部实参的引用。说白了,就是给外部传入的原变量起了一个别名,下一章我们还会详细介绍。
在函数传参的场景下,小伙伴们只需要记住一句话:加上 & 以后,函数不再复制新副本,而是直接操作外面的原变量本身。在函数内部修改这个形参,就等于直接修改外面的原变量。
示例 2:引用传递修改原变量
#include <iostream>
void changeValue(int& num) {
num = 99;
}
int main() {
int x = 10;
changeValue(x);
std::cout << "修改后的 x = " << x << '\n';
return 0;
}运行结果如下。
修改后的 x = 99分析:
void changeValue(int& num) 在参数类型后面加上了 &,表示形参 num 直接绑定到了 main() 的原变量 x 上。因此在函数内部执行 num = 99; 时,main() 中的 x 就直接从 10 变成了 99。
对比示例 1 和示例 2 可以发现,改用引用传递只需要在函数定义处的类型后面加上 &,而 main() 里的调用语句依然直接写 changeValue(x)。调用引用传递的函数时,大家要注意避开以下两种错误写法:
int x = 10;
// 正确方式
changeValue(x);
// 错误方式
changeValue(10);
changeValue(&x);首先,普通引用参数需要绑定并修改一个实实在在的变量,调用时不能直接传入 10 这样的字面量常数,否则会编译报错。其次,如果小伙伴们之前学过 C 语言,注意调用时直接传入变量名 x 即可,不要在实参前面多加 & 写成 changeValue(&x)。
2. 交换两个变量的值
如果只是修改一个变量,我们让函数把新值 return 回来重新赋给原变量不就行了吗?那什么时候非用引用传递不可呢?
最典型的场景就是:需要一个函数同时修改外面的两个或多个变量。因为一条 return 语句一次只能返回一个值,而借助引用传递,函数可以同时修改多个外部变量。下面我们来看交换两个整数的例子。
示例 3:交换两个变量的值
#include <iostream>
void mySwap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10;
int y = 20;
mySwap(x, y);
std::cout << "x = " << x << ", y = " << y << '\n';
return 0;
}运行结果如下。
x = 20, y = 10分析:
调用 mySwap(x, y) 时,由于两个形参都加上了 &,a 直接对应外面的 x,b 直接对应外面的 y。函数内部借助临时变量 temp 交换 a 和 b 的值,实际上就是直接把 main() 中的 x 和 y 交换了过来。
对于这个例子来说,如果我们漏写了 &,把函数头写成 void mySwap(int a, int b),那么程序就只是在函数内部把两份复印件交换了一下。等函数执行结束回到 main() 时,x 依然是 10,y 依然是 20。
C++ const 引用传递
在实际开发中,引用参数还有一种使用频率非常高的写法:在引用参数前面再加上 const 关键字,比如写成 const std::string& title,通常称为 “常量引用” 或 “常引用传递”。
语法:
返回类型 函数名(const 数据类型& 参数名) {
// 函数体
}说明:
可能有些小伙伴会问:“既然函数内部不需要修改原变量,我们直接使用值传递不就行了吗?为什么要写成 const 引用呢?”
原因在于内存复制的开销。像 int、double 这类基本类型只占几个字节,复制一份几乎没有负担。但如果传给函数的是很长的字符串或包含成千上万个元素的容器,使用值传递就会把整份数据完整复制一遍,非常浪费时间和内存。
使用 const 引用传递,刚好把两边的优点结合了起来:
- 带有 & 不复制副本:直接借用外面的原对象,不需要额外拷贝整份数据,执行效率高。
- 带有 const 只读保护:给参数加上只读限制,禁止函数内部修改原对象,避免不小心把外部数据改坏。
下面我们以传递 std::string 字符串为例,看看 const 引用传递的具体用法。
示例 4:只读传递字符串
#include <iostream>
#include <string>
void showSite(const std::string& title) {
std::cout << "网站信息:" << title << '\n';
}
int main() {
std::string name = "绿叶网";
showSite(name); // 传入 string 变量
showSite("www.lvyenet.com"); // 传入字符串字面量
return 0;
}运行结果如下。
网站信息:绿叶网
网站信息:www.lvyenet.com分析:
showSite() 将参数 title 声明为 const std::string&,既省去了复制整个字符串的开销,又保证了只读安全。如果我们在函数内部写 title = "lvye"; 尝试修改它,编译器就会直接报错。
此外,普通引用参数只能绑定变量,不能直接传入常量。加上 const 只读限制后,我们在调用 showSite() 时既能传入变量 name,也可以直接传入 "www.lvyenet.com" 这样的字符串字面量。
同样地,当我们把前面学过的 vector 容器作为参数传给函数进行统计或遍历时,也推荐使用 const 引用传递。下面我们来看一个例子。
示例 5:只读传递 vector 容器
#include <iostream>
#include <vector>
// 计算容器元素的总和
int sumVector(const std::vector<int>& nums) {
int total = 0;
for (int num : nums) {
total += num;
}
return total;
}
int main() {
std::vector<int> scores = {85, 90, 95};
std::cout << "总分:" << sumVector(scores) << '\n';
return 0;
}运行结果如下。
总分:270分析:
在这个例子中,sumVector() 通过 const std::vector<int>& nums 直接借用了 scores 容器来遍历求和。不管容器里有多少个元素,都不会触发整份复制,而且 const 还能确保原容器的数据不被意外改动。
反过来,如果某个函数确实需要往容器中添加元素或修改某一项的值,我们只需要去掉前面的 const,写成普通引用 std::vector<int>& 就可以了。
如何选择参数传递方式?
在实际开发中编写函数时,小伙伴们可以按照以下 3 条标准来选择参数传递方式:
- 基本类型只读:传递
int、double、char、bool等基本类型且不需要修改原变量时,我们直接使用普通值传递即可,比如写int score。 - 需要修改外部原变量:希望函数直接修改调用者传入的原变量时,我们使用引用传递,比如写
int& score。 - 大型对象只读:传递
std::string、std::vector或后面将学习的自定义类对象,且只读取不修改时,我们优先使用const引用传递,比如写const std::string& text。
