C++ 值传递与引用传递

把一个变量作为实参传进函数后,如果我们在函数内部修改了这个参数的值,函数外面的原变量到底会不会跟着改变呢?

这取决于函数参数采用哪种传递方式。在 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。
给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号