✅ AI优化后的笔记


📌 一、左值(Lvalue)和右值(Rvalue)

✳️ 左值(Lvalue)

  • 表示持久存在的对象,有确定的内存地址

  • 可以出现在赋值号(=)左边(虽然这不是定义本身,只是它的一个特性)

  • 可以用 & 取地址

  • 变量、数组元素、返回左值的函数等

int a = 5;     // a 是左值
int* p = &a;   // 可以取地址

✳️ 右值(Rvalue)

  • 通常是临时对象没有名字,也没有明确的内存地址

  • 不能取地址(非 const 引用不能绑定)

  • 不能出现在赋值号左边

  • 字面量、表达式结果、返回右值的函数等

int b = 3 + 2; // 3 + 2 是右值
int x = 10;    // 10 是右值

📌 二、右值的分类

类型

示例

能否取地址

可否赋值

左值

int x

✅可以

✅可以

纯右值

42, x + 1

❌不可以

❌不可以

亡值

std::move(x)

✅可以

❌不可以

✅ 补充解释:

  • 纯右值(prvalue):表示纯粹计算结果,如字面量、算式。3 + 4"hello"SomeClass() 是纯右值。

  • 亡值(xvalue, eXpiring value):将要被“搬走”的资源对象,生命周期即将结束,比如 std::move(obj)、返回右值引用的函数。


📌 三、引用种类及绑定规则

🟢 左值引用(T&

  • 只能绑定到左值

  • 延长左值的生命周期

  • 可用于修改原值

int a = 10;
int& ref = a;   // ok
ref = 20;       // a 被修改

🔴 右值引用(T&&

  • 只能绑定到右值(包括亡值)

  • 不会延长右值生命周期

  • 主要用于移动语义(move semantics)

int&& r = 10;         // ok: 绑定右值
int&& r2 = std::move(a); // ok: 将 a 转成右值

📌 四、std::move 的真相

✅ 功能

  • 不是“移动”,只是将左值强制转为右值

  • 触发移动构造 / 移动赋值

  • 用于资源转移(节省拷贝成本)

🔎 实现原理(简化)

template<typename T>
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept {
    return static_cast<std::remove_reference_t<T>&&>(t);
}

❗ 注意

  • std::move 不会改变对象状态

  • 被 move 的对象应视为“有效但未指定状态”,不要再用它除非重赋值


📌 五、引用折叠(高级补充)

如果你深入泛型编程,会遇到这个概念:

template<typename T>
void func(T&& arg); // 万能引用 / 转发引用

// 若传入左值 => T = int&,T&& = int& && => int&
// 若传入右值 => T = int,T&& = int&&

这就是 引用折叠规则(reference collapsing),现代 C++ 的重要技巧。


✅ 总结:一张图理解

表达式值分类:
  ┌──────────────┐
  │    表达式    │
  └──────┬───────┘
         │
   ┌─────▼───────┐
   │    左值     │   可取地址,可修改,绑定左值引用
   └─────┬───────┘
         │
   ┌─────▼──────────┐
   │     右值       │
   └─────┬──────┬───┘
         │      │
     ┌───▼──┐ ┌──▼────┐
     │纯右值│ │ 亡值  │
     └──────┘ └───────┘