10. 引用:引用概念、引用与指针区别、常引用、引用作为函数参数与返回值
引用,是 C++ 里一个非常有意思的特性。说实话,我刚从 C 转到 C++ 时,觉得引用就是个「语法糖」,没啥大不了的。后来写项目多了才发现——嗯,这玩意儿真香。
今天我们就来彻底搞懂引用。我会把引用概念、和指针的区别、常引用、以及怎么用引用做参数和返回值,一次讲清楚。
10.1 引用的概念
引用,说白了就是给一个变量起个「别名」。你叫张三,你朋友叫你「三哥」,你妈叫你「儿子」——都是指同一个人。引用就是这个道理。
语法很简单:
int a = 10;
int &b = a; // b 是 a 的引用
这里 b 就是 a 的别名。操作 b 就是操作 a,它们俩在内存里是同一个地址。
核心要点:引用必须在定义时初始化,而且一旦绑定,就不能再绑定到其他变量。
我刚开始学的时候犯过一个低级错误:以为引用可以像指针一样重新赋值。结果代码跑起来全是 bug。后来我记住了——引用从一而终,不能「劈腿」。
10.2 引用 vs 指针
很多初学者会问:引用和指针到底有啥区别?我直接给你列个表,一目了然。
| 对比项 | 引用 | 指针 |
|---|---|---|
| 初始化 | 必须初始化 | 可以不初始化 |
| 重新绑定 | 不可以 | 可以 |
| 空值 | 不能为空 | 可以为 nullptr |
| 内存占用 | 不额外占用(编译器优化) | 占用 4/8 字节 |
| 使用方式 | 像普通变量一样用 | 需要解引用 * |
| 安全性 | 更安全 | 容易出野指针 |
我个人习惯是:能用引用就用引用。指针只有在需要「重新指向」或者「可能为空」时才用。你想想看,引用不用判空,不用解引用,多省心。
避坑指南:我曾经在一个大型项目里,因为用了指针判空漏了一个分支,导致线上崩溃。后来重构时全改成引用,这类问题再没出现过。
10.3 常引用
常引用,就是 const T&。它表示:通过这个引用,你不能修改原变量的值。
int a = 10;
const int &b = a;
// b = 20; // 编译错误!不能通过常引用修改
常引用最大的用处是——保护数据。你想想看,如果你写一个函数,只想读取数据,不想修改它,那就用常引用。
还有一个隐藏特性:常引用可以绑定临时对象。
const int &r = 42; // 合法!临时对象被延长生命周期
// int &r2 = 42; // 编译错误!普通引用不能绑定临时对象
这个特性在函数传参时特别有用。比如你传一个字符串字面量或者表达式结果,常引用能接住,普通引用就不行。
小技巧:函数参数如果只读,优先用 const T&。这既避免了拷贝开销,又保证了数据安全。我在写代码时,90% 的引用参数都是常引用。
10.4 引用作为函数参数
引用做参数,最大的好处就是——避免拷贝。尤其是传大型对象时,拷贝的开销非常大。
// 值传递:拷贝整个对象
void func1(MyClass obj) { ... }
// 引用传递:只传别名,不拷贝
void func2(MyClass &obj) { ... }
// 常引用传递:不拷贝,且不能修改
void func3(const MyClass &obj) { ... }
我做过一个测试:传一个 1MB 的结构体,值传递耗时约 1.2ms,引用传递不到 0.001ms。差距上千倍。所以,大对象一定要用引用传参。
引用传参还有一个用途——输出参数。函数可以通过引用修改外部变量。
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
swap(x, y); // x=2, y=1
这个例子很经典。用指针也能实现,但引用写起来更自然,调用时不用取地址。
注意:如果函数不需要修改参数,一定要加 const。不加 const 的话,调用方会担心数据被改,而且不能传常量进去。
10.4 引用作为函数返回值
引用做返回值,可以避免返回时拷贝。但这里有个大坑——不能返回局部变量的引用。
// 错误!返回局部变量的引用
int& badFunc() {
int a = 10;
return a; // a 在函数结束时销毁,引用变成悬空引用
}
那什么时候用引用返回值呢?常见场景是:返回类成员变量、返回全局变量、或者返回静态变量。
class Array {
int data[100];
public:
int& operator[](int index) {
return data[index]; // 返回引用,可以修改
}
};
Array arr;
arr[0] = 42; // 通过引用修改成员
这个例子中,operator[] 返回引用,所以 arr[0] = 42 才能正常工作。如果返回值传递,就只能读不能写了。
还有一个经典用法——链式调用。
class Counter {
int count = 0;
public:
Counter& add(int n) {
count += n;
return *this; // 返回对象本身的引用
}
};
Counter c;
c.add(5).add(3).add(2); // 链式调用
这种写法在流操作、建造者模式中非常常见。返回 *this 的引用,就能实现连续调用。
避坑指南:我曾经在返回引用时,不小心返回了一个临时对象的引用。程序跑起来偶尔崩溃,查了两天才找到原因。记住:返回引用时,确保对象的生命周期比引用长。
10.5 知识体系总览
下面这张图,把引用的核心知识点串起来了。你可以把它当作复习提纲。
引用这个特性,说难不难,说简单也不简单。关键是要理解它的本质——别名。一旦你理解了「引用就是另一个名字」,很多用法就顺理成章了。
我个人觉得,引用是 C++ 里设计得最优雅的特性之一。它让代码更安全、更简洁。你写多了就会爱上它。
公众号:蓝海资料掘金营,微信deep3321