同一个东西的另一个名字
在函数参数一节里,每个实参都会被拷贝进函数。正因为这份拷贝,函数才无法修改调用者的变量:它看到的永远只是自己的副本。引用打破了这堵墙。它是一个别名:绑定到已有变量上的第二个名字,与原变量共享完全相同的内存。
在声明中用 & 创建引用。一旦绑定,引用和原变量就无法区分:
有两条规则让引用安全又可预测:引用在声明时必须初始化(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 这样廉价的类型,直接值传递更简单,速度也一样快。