15. 引用:引用概念、引用与指针区别、引用作为函数参数
引用这个话题,说实话是很多C++初学者容易「卡住」的地方。我记得自己刚学那会儿,总觉得引用和指针差不多,干嘛要搞两个东西出来?后来在项目里踩了几次坑,才真正体会到——引用这东西,用好了是真香。
引用的基本概念
引用,说白了就是给一个变量起个「别名」。你想想看,一个人有大名也有小名,叫哪个都是在叫同一个人。引用也是这个道理。
int a = 10;
int& ref = a; // ref 是 a 的引用
ref = 20; // 实际上修改的是 a
cout << a; // 输出 20
这里有个关键点:引用必须在定义时初始化,而且一旦绑定到某个变量,就不能再改绑到别的变量上。这一点和指针很不一样。
核心要点:引用就是变量的别名,操作引用就是操作原变量本身。
引用与指针的区别
很多初学者会问:引用和指针到底有什么区别?我建议你从这几个维度去理解:
| 对比维度 | 引用 | 指针 |
|---|---|---|
| 初始化 | 必须初始化,不能为空 | 可以不初始化,可以为空 |
| 重新绑定 | 不能改绑 | 可以指向不同对象 |
| 内存占用 | 不额外占用(编译器优化) | 占用指针大小的内存 |
| 语法操作 | 像普通变量一样使用 | 需要解引用 * 操作 |
| 空值风险 | 不存在空引用 | 可能为 nullptr,需检查 |
我在项目中遇到过这样一个场景:有个同事用指针传参,结果忘了检查空指针,程序直接崩了。后来改成引用,这种问题就再也没出现过。嗯,这就是引用的一个好处——天生安全。
我的建议:能用引用就别用指针。引用更安全,语法也更简洁。只有在需要「重新指向」或者「可能为空」的场景下,才考虑用指针。
引用作为函数参数
引用最常见的用途就是作为函数参数。为什么要用引用传参?说白了就两个原因:避免拷贝和修改实参。
1. 避免拷贝,提升性能
// 值传递:会拷贝整个对象
void processValue(string s) {
// 操作 s
}
// 引用传递:没有拷贝,直接操作原对象
void processRef(string& s) {
// 操作 s
}
你想想看,如果传的是一个很大的结构体或者类对象,值传递会触发拷贝构造函数,性能开销可不小。引用传递就只是传了个别名,几乎没有开销。
2. 修改实参
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int x = 5, y = 10;
swap(x, y); // x 变成 10,y 变成 5
这个例子很经典。如果不传引用,swap 函数里改的只是副本,外面的 x 和 y 纹丝不动。传引用才能「动真格」的。
3. const 引用:只读不写
void printData(const vector<int>& data) {
for (int v : data) {
cout << v << " ";
}
// data.push_back(100); // 编译错误!const 引用不能修改
}
const 引用是个好东西。它既避免了拷贝,又保证了函数内部不会意外修改实参。我个人习惯是:只要不需要修改参数,一律用 const 引用。
注意:不要返回局部变量的引用!局部变量在函数结束后就销毁了,返回它的引用会导致悬空引用,程序行为未定义。
// 错误示例
int& badFunction() {
int local = 42;
return local; // local 已销毁,返回的是悬空引用
}
引用与右值引用
C++11 引入了右值引用(&&),这又是另一个话题了。简单提一句:右值引用主要用于移动语义和完美转发,能进一步提升性能。不过初学者先掌握左值引用就够了,右值引用后面再深入。
知识体系图
下面这张图帮你梳理了引用的核心知识脉络:
避坑指南
最后分享几个我踩过的坑:
- 我曾经在写代码时把引用和取地址符 & 搞混了。记住:
int& ref = a;是引用,int* ptr = &a;是取地址。 - 我曾经在函数里返回了局部变量的引用,结果程序跑着跑着就崩了。从那以后,我每次写返回引用都会多看一眼——是不是返回了栈上的东西。
- 我建议新手先别碰右值引用,先把左值引用用熟。基础打牢了,后面学移动语义才不费劲。
引用这个东西,用多了就习惯了。你只要记住:引用就是别名,操作引用就是操作原变量,大部分场景都能想明白。