Menu

C++ 引用教程:引用传递、const& 与别名

讲解 C++ 引用:参数中的 & 如何创建别名,为什么引用传递能避免拷贝,并让函数修改调用者的变量,什么时候该用 const&,以及引用与返回值的取舍。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

同一个东西的另一个名字

在函数参数一节里,每个实参都会被拷贝进函数。正因为这份拷贝,函数才无法修改调用者的变量:它看到的永远只是自己的副本。引用打破了这堵墙。它是一个别名:绑定到已有变量上的第二个名字,与原变量共享完全相同的内存。

在声明中用 & 创建引用。一旦绑定,引用和原变量就无法区分:

有两条规则让引用安全又可预测:引用在声明时必须初始化(int& r; 会导致编译错误),而且之后永远不能重新绑定到别的东西上。给引用赋值,写入的始终是它最初绑定的那个对象。

引用传递:让函数反过来修改调用者

引用真正的用武之地是函数。在参数上加 &,函数收到的就是调用者实参的别名,而不是拷贝。这样,函数内部的修改在外部也能看到:

去掉 &,addBonus 只会给一份用完即弃的拷贝加分,total 仍然是 100。一个字符就是全部的区别。想写一个返回多个结果,或者原地修改输入的函数,这就是标准写法。经典例子是交换两个变量:

如果没有引用,swapValues 只会交换局部副本,x/y 依然是 1 2。(标准库已经有 std::swap,但自己写一遍,能清楚地看到引用参数到底带来了什么。)

const 引用:读得快,并承诺不修改

引用传递还能避免拷贝,而对一个大对象来说,拷贝可能代价不小。但普通的 T& 参数传达的是“我可能会修改它”,当你只想读取时,这就会误导别人。解决办法是 const T&:你既得到引用免拷贝的速度,又得到编译器强制保证的承诺:函数不会修改这个实参。

非 const 引用只能绑定到可修改的变量,而 const 引用还能绑定到字面量和临时对象,所以 greet("literal works too") 才能编译通过。选择参数类型的实用经验法则:

void f(int x)              // cheap type, read-only -> just copy it
void f(const string& s)   // heavy type, read-only -> const reference
void f(string& s)         // you intend to modify the caller's object

对于只读取的任何类类型(string、vector、你自己的结构体),默认用 const T&,只有确实打算写回时才用非 const 引用。

返回引用

函数也可以返回引用,把一个已经存在的东西的别名交给调用者。这在类似容器的代码中很常见:v[i] = 5 能成立靠的就是它,运算符重载在底层做的也是这件事:

因为 at 返回 int&,调用表达式 at(data, 1) 本身就是一个可以赋值的左值。如果改成返回普通的 int,at(data, 1) = 42 就会编译失败,因为你是在给一份临时拷贝赋值。

大坑:悬空引用

引用并不拥有任何东西,它只是指向别处的内存。如果那块内存在引用仍在使用时就被销毁了,你得到的就是一个悬空引用,通过它读取是未定义行为:可能打印出垃圾值,可能崩溃,也可能看起来一切正常,直到在生产环境里毁掉你的一天。经典错误是返回局部变量的引用:

int& broken() {
    int local = 42;
    return local;   // BUG: local is destroyed when broken() returns
}                   // the returned reference dangles

int main() {
    int& r = broken();
    cout << r << "\n";   // UNDEFINED BEHAVIOR - reads dead memory
}

broken 一返回,局部变量 local 就没了,所以这个引用指向的是已被回收的栈空间。只返回比这次调用活得更久的东西的引用:通过引用传进来的参数、数据成员或者静态变量。如果值是在函数内部算出来的,就按值返回,让编译器把拷贝优化掉。范围循环以及任何绑定到临时对象的引用都有同样的陷阱:永远不要在引用所指之物的生命周期结束后还持有它。

下一节:函数重载

引用让每个参数多了一个选项:拷贝还是别名,可变还是 const,而这个选项和下一个主题直接相关。接下来,函数重载让你可以定义多个同名但参数列表不同的函数,编译器会根据实参类型挑出正确的那个,包括它们是按值、按引用还是按 const 引用传递的。

常见问题

C++ 中的引用是什么?

引用是一个已有变量的别名,也就是同一块内存的另一个名字。在声明中用 & 创建引用:int& r = x;。此后 r 和 x 可以互换使用,改变其中一个,另一个也随之改变。引用必须在声明时初始化,而且永远不能重新绑定去引用另一个变量。

C++ 中值传递和引用传递有什么区别?

值传递(void f(int x))会拷贝实参,所以函数操作的是它自己的副本,调用者的变量不受影响。引用传递(void f(int& x))让函数直接访问调用者的变量,所以调用之后修改依然可见,而且不会产生拷贝,这对大对象很重要。

C++ 中什么时候该用 const 引用参数?

当函数只需要读取参数,而参数类型拷贝起来又很昂贵时(string、vector、大结构体),用 const T&。你既能得到引用免拷贝的速度,又能得到编译器的保证:函数不会修改调用者的值。对于 int 或 double 这样廉价的类型,直接值传递更简单,速度也一样快。

Coddy 编程语言插图

和 Coddy 一起学编程

开始