关于常量引用,左值引用,右值引用的一些理解
简单理解
代码块1:
| 1 | double dval = 3.14; | 
代码块2:
| 1 | double dval = 3.14; | 
上述代码都可以编译通过,看代码块2,在将dval的值赋值到ri上的时候,会生成中间变量,这个变量是一个临时值,也就是一个右值,这时想要创建引用,就必须使用右值引用来接收,代码块1编译通过,证明了这一点。
int &b = dval;会编译错误,不能使用左值引用来接受,是因为C++ 标准规定,非const左值引用不能绑定到临时对象上。这种设计的初衷是为了防止程序员意外修改转换后临时对象的值,或者在引用一个临时对象时产生意外的副作用。
为什么可以用const int temp来接收呢,因为临时量,也就是字面量,或者说右值,可以用来初始化变量,也可以用来初始化常量,int aa  = dval;使用字面量初始化一个变量,编译通过。
为什么const int &rt = temp;就可以使用左值引用来接收,因为const int temp  = dval;,已经将右值固化,有了固定的内存地址,此时temp是左值。两个步骤都必须存在const的原因就是为了延长临时对象的生命周期,安全绑定,避免意外转换造成引用为空。
引用的底层是常量指针,指针本身不可以修改,指向的对象可以修改。常量引用就可以理解为指向常量的常量指针,指针本身以及指向的对象都不可以修改。
所有的隐式转换产生的中间值都是右值吗?
在C++中,隐式转换生成的中间值通常是右值,但存在一些特殊场景需要具体分析。以下是详细解答:
1. 基本规则
隐式转换(如算术类型转换、类类型的构造函数或转换运算符)生成的临时对象通常是右值,因为:
- 它们没有名称,无法被直接寻址(如 3.14转int生成的临时3)。
- 它们的生命周期仅限于当前表达式(除非被引用延长)。
示例1:算术类型转换
| 1 | double dval = 3.14; | 
示例2:类类型转换
| 1 | class A { | 
2. 例外情况
隐式转换的中间值不一定是右值,如果转换结果本身是左值:
- 当隐式转换通过返回左值引用的转换函数完成时,中间值为左值。
示例3:转换函数返回左值引用
| 1 | class B { | 
这里,B 到 int 的隐式转换返回的是对成员 value 的左值引用,因此中间值是左值。
3. 关键区分
- 右值:临时对象、字面量(如 42)、表达式计算结果(如a + b)。
- 左值:具名对象、函数返回左值引用的结果、可通过地址访问的实体。
示例4:隐式转换的中间值类型
| 1 | class C { | 
4. 总结
| 场景 | 中间值类型 | 原因 | 
|---|---|---|
| 算术类型转换 | 右值 | 临时对象无法寻址 | 
| 类构造函数隐式转换 | 右值 | 生成临时对象 | 
| 转换函数返回值类型 | 右值 | 如 operator int() | 
| 转换函数返回左值引用 | 左值 | 如 operator int&() | 
因此:
- 大部分隐式转换生成的中间值是右值。
- 若隐式转换返回左值引用,则中间值为左值(需显式定义相关转换函数)。
详解explicit
在C++中,explicit 是一个关键字,用于修饰构造函数或类型转换运算符,以禁止编译器在某些上下文中进行隐式类型转换。它的主要作用是增强代码的安全性,避免意外的隐式转换导致的潜在问题。
1. explicit 的作用
- 禁止隐式转换:explicit修饰的构造函数或转换运算符只能用于显式转换,不能用于隐式转换。
- 提高代码清晰度:强制程序员明确表达意图,避免隐式转换带来的歧义。
2. explicit 的适用场景
(1) 构造函数
当类的构造函数只有一个参数(或除第一个参数外都有默认值)时,该构造函数可以用于隐式类型转换。如果希望禁止这种隐式转换,可以将构造函数声明为 explicit。
示例1:隐式转换的问题
| 1 | class A { | 
- 这里 func(42)会隐式调用A(int)构造函数,生成一个临时A对象。
- 这种隐式转换可能导致代码难以理解,甚至隐藏潜在的错误。
示例2:使用 explicit 禁止隐式转换
| 1 | class A { | 
- 通过 explicit修饰构造函数,func(42)会编译失败,必须显式调用A(42)。
(2) 类型转换运算符
explicit 也可以用于修饰类型转换运算符,禁止隐式调用转换函数。
示例3:隐式类型转换运算符
| 1 | class B { | 
- 这里 int x = b;会隐式调用operator int(),将b转换为int。
示例4:使用 explicit 禁止隐式转换
| 1 | class B { | 
- 通过 explicit修饰转换运算符,int x = b;会编译失败,必须显式调用static_cast<int>(b)。
3. explicit 的优点
- 避免意外的隐式转换: - 隐式转换可能导致难以发现的错误,例如将 int隐式转换为类类型时,可能产生不符合预期的行为。
- explicit强制显式转换,使代码更安全。
 
- 隐式转换可能导致难以发现的错误,例如将 
- 提高代码可读性: - 显式转换明确表达了程序员的意图,使代码更易于理解和维护。
 
- 防止歧义: - 在某些情况下,隐式转换可能导致函数重载解析的歧义,explicit可以避免这种问题。
 
- 在某些情况下,隐式转换可能导致函数重载解析的歧义,
4. explicit 的注意事项
- 仅适用于单参数构造函数: - 多参数构造函数不能用于隐式转换,因此不需要 explicit。
- 例外:如果多参数构造函数中除第一个参数外都有默认值,则仍可能用于隐式转换。
 
- 多参数构造函数不能用于隐式转换,因此不需要 
- C++11 扩展: - 在 C++11 之前,explicit只能用于构造函数。
- 从 C++11 开始,explicit可以用于类型转换运算符。
 
- 在 C++11 之前,
- 显式转换的方式: - 使用 static_cast或直接调用构造函数(如A(42))。
 
- 使用 
5. 实际应用场景
(1) 防止隐式构造
| 1 | class String { | 
(2) 防止隐式类型转换
| 1 | class Boolean { | 
6. 总结
| 特性 | 隐式转换 | 显式转换( explicit) | 
|---|---|---|
| 构造函数 | 允许隐式调用 | 必须显式调用 | 
| 类型转换运算符 | 允许隐式调用 | 必须显式调用 | 
| 优点 | 代码简洁 | 避免意外行为,提高代码安全性 | 
| 适用场景 | 简单、明确的转换 | 复杂或易出错的转换 | 
- explicit的核心作用:禁止隐式转换,强制显式表达意图。
- 使用建议:在构造函数和转换运算符中,优先使用 explicit,除非有明确的理由允许隐式转换。