九.C++ 对引用的学习
一.基本概念
引用即内存的别名
int a = 10;
int& b = a;
引用本身不占用内存,并非实体,对引用的所有操作都是在对目标内存进行操作
引用必须初始化,且不能更换对象
int c = 5;
b = c; // × 仅仅是在对引用的目标内存进行赋值
#include <iostream>
using namespace std;int main() {int mm1 = 20; // 1. 定义整型变量mm1并初始化为20int& mm2 = mm1; // 2. 定义整型引用mm2,绑定到mm1int mm3 = 15; // 3. 定义整型变量mm3并初始化为15mm2 = mm3; // 4. 将mm3的值赋给mm2(即mm1)cout << "mm1: " << mm1 << endl; // 输出 15cout << "mm2: " << mm2 << endl; // 输出 15cout << "mm3: " << mm3 << endl; // 输出 15return 0;
}
详细执行过程:
-
int mm1 = 20;
-
定义一个整型变量
mm1
,并将其初始值设为20
。 -
内存中:
mm1
存储的值是20
。
-
-
int& mm2 = mm1;
-
定义一个整型引用
mm2
,并将其绑定到mm1
。 -
引用
mm2
并不是一个新的变量,而是mm1
的别名(另一个名字)。 -
此时,
mm2
和mm1
指向同一块内存,修改mm2
就是修改mm1
,反之亦然。
-
-
int mm3 = 15;
-
定义一个整型变量
mm3
,并将其初始值设为15
。 -
内存中:
mm3
存储的值是15
。
-
-
mm2 = mm3;
-
将
mm3
的值(15
)赋给mm2
。 -
由于
mm2
是mm1
的引用,这实际上是将15
赋给mm1
。 -
执行后:
-
mm1
的值变为15
。 -
mm2
的值也变为15
(因为它是mm1
的引用)。 -
mm3
的值仍然是15
(不受影响)。
-
-
最终结果:
-
mm1
的值:15
(被mm2 = mm3
修改) -
mm2
的值:15
(因为它是mm1
的引用) -
mm3
的值:15
(保持不变)
输出:
mm1: 15
mm2: 15
mm3: 15
关键点总结:
-
引用(
&
)的本质:-
引用是变量的别名,不是独立的变量。
-
int& mm2 = mm1;
表示mm2
是mm1
的另一个名字,两者共享同一块内存。
-
-
赋值操作的影响:
-
mm2 = mm3;
实际上是将mm3
的值赋给mm1
(因为mm2
是mm1
的引用)。 -
引用本身没有独立的内存空间,赋值操作会直接影响它绑定的变量。
-
-
与指针的区别:
-
如果是
int* p = &mm1; *p = mm3;
,效果和引用类似,但引用更简洁且不能重新绑定。 -
引用一旦绑定后不能更改绑定的对象,而指针可以指向不同的对象。
-
引用本身一旦绑定后不能重新绑定到另一个对象,引用不能改变
关于 “引用不能改变” 的说法需要更精确的解释。实际上,引用本身一旦绑定后不能重新绑定到另一个对象,但 引用绑定的对象的内容是可以改变的。这是两个不同的概念:
1. 引用的“不可变性”(不能重新绑定)
-
引用一旦初始化后,不能指向其他变量:
-
引用在声明时必须初始化,并且之后不能更改它绑定的对象。
-
例如:
-
int a = 10;
int b = 20;
int& ref = a; // ref 绑定到 a
ref = b; // 这是修改 ref 绑定的对象 a 的值,不是改变 ref 的绑定!// ref = &b; // 错误!不能重新绑定 ref 到 b(C++ 不允许)
-
关键点:
ref = b
是把b
的值赋给a
(因为ref
是a
的引用),而不是让ref
指向b
。
2. 引用绑定的对象的内容可以改变
引用本身是别名,修改引用就是修改它绑定的对象
-
例如:
int a = 10;
int& ref = a; // ref 是 a 的别名
ref = 20; // 修改 ref 就是修改 a,a 现在是 20
常见误区澄清
-
误区:“引用不能改变” → 实际上是指 引用不能重新绑定,但可以修改它绑定的对象的内容。
正确说法:
-
引用 不能重新绑定(不能指向其他对象)。
-
引用 可以修改它绑定的对象的内容(因为它是别名)。
-
总结
-
引用一旦绑定后不能重新绑定(不能指向其他变量),这是它的“不可变性”。
-
引用绑定的对象的内容可以改变(通过引用直接修改)。
mm2 = mm3
-
mm2
是mm1
的引用,所以mm2 = mm3
就是mm1 = mm3
。 -
修改的是
mm1
的值,mm2
仍然绑定到mm1
(不能重新绑定)。
二.引用的常属性必须和目标的常属性“一致”(个别情况也可以不一致)
const int e = 10;
int& f = e; // ×
const int& g = e; // √
可以限定更加严格(别名可以比真名更加严格)
int a = 10;const int& h = a; // OK
三.const 修饰一个常量(常引用)
在 C++ 中,const
引用(const T&
)是一种非常重要的特性,它结合了引用的效率和 const
的安全性。
1. 基本概念
-
const
引用:-
是一个对常量对象的引用,不能通过该引用修改所绑定的对象。
-
语法:
const T& ref = obj;
-
作用:提供对对象的只读访问,同时避免拷贝(与普通引用类似)。
与普通引用的区别:
-
特性 | 普通引用 (T& ) | const 引用 (const T& ) |
---|---|---|
能否修改对象 | 可以修改绑定的对象 | 不能修改绑定的对象 |
绑定对象类型 | 必须绑定可修改的对象 | 可以绑定 const 或非 const 对象 |
安全性 | 可能意外修改数据 | 提供只读访问,更安全 |
2. 核心知识点
(1) const
引用可以绑定到 const
和非 const
对象
-
可以绑定到非
const
对象(提供只读访问):
int x = 10;
const int& ref = x; // ref 是 x 的 const 引用
// ref = 20; // 错误!不能通过 ref 修改 x
-
可以绑定到
const
对象(合法且安全):
const int y = 20;
const int& ref = y; // ref 是 y 的 const 引用
// ref = 30; // 错误!y 本身就是 const
-
普通引用不能绑定到
const
对象:
const int z = 30;
int& ref = z; // 错误!普通引用不能绑定 const 对象
(2) const
引用可以延长临时对象的生命周期
-
临时对象(如函数返回值、表达式结果)通常只能绑定到
const
引用 -
用途:避免不必要的拷贝,同时保证安全性(不能修改临时对象)
10; // 声明周期很短,正常执行过了封号后 10这个内存地址就不在了int& ri = 10; // 报错 非常引用的初始值必须为左值,普通引用不能绑定临时对象const int& ri = 10; // 正确,有了这个别名之后,声明周期就变长了,会跟着别名的生命周期改变
了解一个左值和右值的概念:
具体名字的内存,能够取地址 --》 左值 (非常左值[无const修复] | 常左值[有const修复])
匿名内存,不能取地址值 --》 右值 (认为直接更改右值没有意义) 比如 10;
常引用可以作为任何东西的别名
特点:
1.引用可以延长右值的生命周期
2.常引用 即 万能引用
3.引用的生命周期不能长于目标
int a = 10;int& ra = a; // OK 引用ra可以是a的别名
const int& ri = a; // OK 常引用 ri 可以是 a的别名const int b = 20;int& bb = b; // ERROR const int& bb = b; // OK 常引用bb可以是常量b的别名const int& rf = 10; // OK 常引用rf可以是常量10的别名int foo(){}const int& hg = foo(); // OK
(3) const
引用作为函数参数(推荐用法)
-
传递大对象时避免拷贝,同时防止函数内部修改参数:
void printValue(const std::string& str) {// str = "new value"; // 错误!不能修改std::cout << str << std::endl;
}int main() {std::string s = "Hello";printValue(s); // 传递 const 引用,避免拷贝
}
优势:
高效(无拷贝)。
安全(函数不能意外修改参数)。
(4) const
引用与返回值
-
函数返回
const
引用:-
可以避免返回临时对象的拷贝,同时防止调用者修改返回值:
-
const std::string& getString() {static std::string s = "Hello";return s; // 返回 static 变量的 const 引用
}
注意:如果返回局部变量的引用,会导致悬空引用(未定义行为)!
const std::string& getLocalString() {std::string s = "Hello";return s; // 错误!s 是局部变量,函数结束后被销毁
}
(5) const
引用与重载
-
const
引用可以区分重载函数:
void func(const int& x) { std::cout << "const ref" << std::endl; }
void func(int& x) { std::cout << "non-const ref" << std::endl; }int main() {int a = 10;const int b = 20;func(a); // 调用非 const 版本func(b); // 调用 const 版本
}
-
编译器会根据参数是否为
const
选择正确的重载版本。
(6) const
引用与移动语义
-
const 引用不能用于移动语义:
-
移动语义需要修改源对象(“窃取”资源),而
const
引用禁止修改:
-
std::string s = "Hello";
const std::string& ref = s;
// std::string moved = std::move(ref); // 错误!ref 是 const
正确做法:传递非 const
引用或值:
std::string moved = std::move(s); // 正确
3. 最佳实践
-
优先使用
const
引用传递参数:-
避免拷贝,同时保证函数不会意外修改参数。
-
示例:
-
void process(const std::vector<int>& data) { ... }
-
返回
const
引用时注意生命周期:
-
只能返回全局/静态对象的引用,不能返回局部变量的引用。
-
区分
const
和非const
重载:
-
提供更灵活的接口(如
std::vector
的operator[]
有const
和非const
版本)。
-
避免滥用
const
引用:
-
如果需要修改参数,使用普通引用。
-
如果需要返回可修改的对象,返回值或非
const
引用。
4. 常见误区
-
误区 1:“
const
引用不能绑定到临时对象”-
纠正:
const
引用可以绑定到临时对象(普通引用不能)。
-
-
误区 2:“
const
引用可以修改绑定的对象”-
纠正:
const
引用不能修改绑定的对象(这是它的核心特性)。
-
-
误区 3:“
const
引用总是比值传递好”-
纠正:如果对象很小(如
int
),值传递可能更高效(避免引用开销)。
-
5. 总结
特性 | const 引用 (const T& ) |
---|---|
绑定对象 | 可以绑定 const 或非 const 对象 |
能否修改对象 | 不能(提供只读访问) |
临时对象绑定 | 可以绑定临时对象(普通引用不能) |
函数参数 | 推荐用于大对象,避免拷贝且保证安全性 |
返回值 | 可以返回 const 引用(但需注意生命周期) |
重载区分 | 可以与非 const 引用重载 |
移动语义 | 不能用于移动语义(因为不能修改源对象) |
核心原则:
-
const
引用 = 高效 + 安全(避免拷贝,防止意外修改)。 -
普通引用 = 高效 + 可修改(需要修改参数时使用)。
-
值传递 = 简单 + 安全(小对象或需要独立副本时使用)。
建议:合理使用 const
引用可以显著提升代码的性能和安全性!