在日常生活中,一个人除了身份证上的正式名字之外,往往还会有一个小名。比如正式名字叫 “张小明”,小名叫 “明明”。不管别人喊哪一个名字,指的其实都是同一个人,如下图所示。

在 C++ 中,“引用(reference)” 也是同样的道理。简单来说,引用就是给一个已经存在的变量起一个 “别名”。一旦绑定成功,引用名和原变量名代表的就是内存中的同一个变量,程序不会把原变量的值重新复制一份。
C++ 引用的基本使用
在 C++ 中,我们可以使用 & 符号来定义一个引用,让它绑定到某个已经存在的变量上。
语法:
类型名& 引用名 = 原变量名;说明:
在上面的语法中,类型名& 表示对应类型的引用。比如 int& 表示整型引用,而 double& 表示双精度浮点型引用。
需要注意的是,这里的 & 跟在数据类型后面,作用是声明引用,并不是执行按位与运算。虽然写成 int &ref = score 在语法上也合法,但我们更推荐写成 int& ref = score。这样可以把 int& 看作一个整体,直观看出它是整型引用。
提示 在阅读 C++ 代码时,我们经常会看到使用 ref 或 r 作为引用的名字。其中,ref 就是英文 “reference(引用)” 的缩写。
既然引用和原变量代表内存中的同一个变量,那么通过引用不仅可以直接读取原变量的值,修改引用时原变量的值也会同步改变。
示例 1:定义并使用引用
#include <iostream>
int main() {
int score = 80;
int& ref = score; // 给 score 起别名
std::cout << score << ' ' << ref << '\n';
ref = 95; // 通过引用修改值
std::cout << score << '\n';
score = 100; // 直接修改原变量
std::cout << ref << '\n';
return 0;
}运行结果如下。
80 80
95
100分析:
int& ref = score; 表示给整型变量 score 起了一个别名 ref。这里小伙伴们不要把 ref 理解成 “又新开辟了一块内存保存 80”,实际上 score 和 ref 访问的是内存中的同一块空间。
执行 ref = 95; 时,修改 ref 就相当于直接修改 score,因此输出 score 的结果变成了 95。随后执行 score = 100; 修改原变量,再去读取 ref 时,得到的结果也跟着变成了 100。
这和我们在范围 for 循环中写 for (auto& x : nums) 是一个道理。加上 & 之后,x 就成了当前元素的引用。在循环体内修改 x,原容器里的元素也会直接改变。
C++ 引用的注意事项
在使用 C++ 引用时,我们需要严格遵守以下 3 条语法规则:
- 定义时必须同时初始化:我们在声明引用时必须立即指明它绑定哪个变量,不能先声明一个空引用再去赋值。此外,普通的非
const引用只能绑定有名字的变量,不能直接绑定字面量。 - 引用类型必须与原变量一致:比如
int类型的变量只能使用int&绑定,不能使用double&绑定。这是因为不同数据类型占用的字节数和存储格式不同。 - 一旦绑定就不能改绑其他变量:引用在初始化绑定某个变量后,就会一直与该变量绑定在一起,中途不能再改成其他变量的别名。
对于前两条规则,我们可以对比一下正确写法与常见的错误写法。
int age = 20;
// 正确方式
int& ref1 = age;
// 错误方式 1
int& ref2;
// 错误方式 2
int& ref3 = 100;
// 错误方式 3
double& ref4 = age;在上面的代码中,错误方式 1 定义引用时没有初始化,错误方式 2 尝试把普通引用直接绑定到字面量 100 上,错误方式 3 则使用 double& 去绑定 int 变量。这 3 种写法都会直接导致编译报错。
为什么普通引用不能绑定字面量 100 呢?这是因为普通引用允许修改目标的值。假如允许绑定 100 并执行 ref3 = 200,就相当于要把常数 100 本身改成 200,这显然是不合逻辑的。
至于第 3 条规则 “一旦绑定就不能改绑其他变量”,初学者最容易把它和普通的变量赋值搞混。看到 r = b; 这样的代码时,不少人会误以为这是把引用 r 改绑到了变量 b 身上。下面我们通过一个例子来验证一下。
示例 2:给引用重新赋值
#include <iostream>
int main() {
int a = 10;
int b = 20;
int& r = a; // r 绑定到变量 a
r = b; // 把 b 的值赋给 a
b = 30;
std::cout << "a = " << a << '\n';
std::cout << "b = " << b << '\n';
std::cout << "r = " << r << '\n';
return 0;
}运行结果如下。
a = 20
b = 30
r = 20分析:
从运行结果可以看出,虽然把 b 的值修改成了 30,但最后输出 r 的结果依然是 20,与 a 的值完全一致。
这是因为 int& r = a; 已经让引用 r 固定绑定了变量 a,后续执行 r = b; 并不会改变 r 的绑定对象。对于这个例子来说,下面 2 种方式是等价的。
// 方式 1
r = b;
// 方式 2
a = b;也就是说,r = b; 只是把 b 当前的值 20 赋给了 a,而 r 始终还是 a 的别名。如果我们在程序中确实需要 “先关联变量 a,中途再换成关联变量 b”,单靠引用是做不到的,需要借助指针来实现。
引用作为函数参数
在同一个函数内部,我们直接读写原变量名就可以了,平时很少特意给局部变量再起一个别名。在实际开发中,引用最主要的用武之地是作为函数的形参。
普通的值传递只会把实参复制一份,在函数内修改形参不会影响外部变量。而在函数的引用传递中,引用形参直接绑定外部实参,可以直接修改外部变量的值。
除此之外,C++ 函数的 return 语句一次只能返回一个结果。如果我们希望函数一次算出最小值和最大值等多个结果,也可以通过多个引用参数把结果直接写回外部变量中。
示例 3:通过引用参数传回多个结果
#include <iostream>
void getMinMax(int a, int b, int& minVal, int& maxVal) {
if (a < b) {
minVal = a;
maxVal = b;
} else {
minVal = b;
maxVal = a;
}
}
int main() {
int small = 0;
int large = 0;
getMinMax(35, 18, small, large);
std::cout << "最小值:" << small << '\n';
std::cout << "最大值:" << large << '\n';
return 0;
}运行结果如下。
最小值:18
最大值:35分析:
在这个例子中,getMinMax() 的前两个形参 a 和 b 采用普通的值传递,用来接收待比较的两个整数。后两个形参 minVal 和 maxVal 则是引用,用来把计算结果传回函数外部。
调用 getMinMax(35, 18, small, large) 时,minVal 绑定了实参 small,maxVal 绑定了实参 large。因此在函数内给 minVal 和 maxVal 赋值 18 和 35,就直接改写了 main() 里的 small 和 large。
常量引用(const 引用)
在定义引用时,如果在类型名前面加上 const 关键字,就叫做 “常量引用”(平时也常简称为 const 引用)。因为引用本身就不能改绑其他变量,所以加上 const 的作用只有一个:禁止通过该引用修改原变量的值,相当于给目标起了一个 “只读别名”。
语法:
const 类型名& 引用名 = 目标值;说明:
定义了 const int& ref = score; 之后,我们可以通过 ref 读取 score 的值,但不能写 ref = 90 去修改它。需要注意的是,const 引用只是锁住了 ref 这条修改通道。如果原变量 score 本身是普通变量,我们依然可以通过 score = 90 直接修改原变量。
此外,const 引用不仅可以绑定普通变量,还可以绑定常量或字面量(如 60)。因为 const 引用只能读取、不能修改,不会破坏原有的常量值。遇到字面量时,编译器会自动创建一个临时只读空间来配合绑定,所以这种写法是完全合法的。
示例 4:定义并使用 const 引用
#include <iostream>
int main() {
int score = 80;
const int& ref = score; // 绑定普通变量
std::cout << ref << '\n';
score = 90; // 直接修改原变量
std::cout << ref << '\n';
const int MAX_SCORE = 100;
const int& r1 = MAX_SCORE; // 绑定 const 常量
const int& r2 = 60; // 绑定整数字面量
std::cout << r1 << ' ' << r2 << '\n';
return 0;
}运行结果如下。
80
90
100 60分析:
const int& ref = score; 为普通变量 score 创建了一个只读别名 ref。直接执行 score = 90; 修改原变量后,再次读取 ref 也会同步得到最新的 90。同时,只读引用 r1 和 r2 也顺利绑定了常量 MAX_SCORE 与整数字面量 60。
反过来,如果我们尝试通过 const 引用修改目标值,或者使用普通引用 int& 去绑定 const 常量,都会直接导致编译报错。下面这两种写法就是错误的。
// 错误方式 1
ref = 90;
// 错误方式 2
int& r3 = MAX_SCORE;可能有些小伙伴会问:“既然直接写 const int r2 = 60 也能定义常量,那 const 引用到底有什么用呢?”
其实在日常项目开发中,const 引用最常见的用途是作为函数的 “只读形参”,用来传递 string 字符串或 vector 容器等占用内存较大的数据。
示例 5:函数的 const 引用参数
#include <iostream>
#include <string>
void printWelcome(const std::string& name) {
std::cout << "欢迎你," << name << '\n';
}
int main() {
std::string user = "Jack";
printWelcome(user); // 传入 string 变量
printWelcome("Lucy"); // 直接传入字符串字面量
return 0;
}运行结果如下。
欢迎你,Jack
欢迎你,Lucy分析:
在这个例子中,我们将 printWelcome() 的形参定义为 const std::string& name,这样做有以下 3 个明显的好处:
- 省去复制开销:传入
user时直接绑定原字符串,不需要把整段文本重新复制一份,运行效率更高。 - 防止误改原数据:加上
const后,函数内部只能读取name。如果不小心写了修改代码,编译器会直接报错提醒。 - 传参更加灵活:既能接收现成的
std::string变量(如user),也能直接接收字符串字面量(如"Lucy"),省去了先定义临时变量的麻烦。如果换成普通引用std::string&,调用printWelcome("Lucy")就会编译报错。
因此在 C++ 实际开发中,大家只需要记住一个实用原则:当函数只需要读取 std::string、std::vector 等较大数据时,形参优先写成 const 引用。而传递 int、double 这类占内存很小的基本类型时,直接按值传递即可。
