C++ 值简述

C++的值类别系统是理解现代C++特性的基础。本文将介绍值类别的概念和引用类型

值类别 (Value Categories)

  • glvalue(广义左值/generalized lvalue): 有地址的值,包括 lvalue 或 xvalue
  • rvalue(右值): 可移动的值,包括 prvalue 和 xvalue
cpp 复制代码
// 1. 字面量(prvalue)- 天然可移动
42;                    // 可以移动
"hello";              // 可以移动

// 2. 函数返回值(prvalue)- 天然可移动
int getValue() { return 100; }
getValue();            // 可以移动

// 3. std::move 转换后的值(xvalue)- 强制可移动
std::string str = "hello";
std::move(str);       // 可以移动

// 4. 右值引用(xvalue)- 天然可移动
int&& rref = 42;
rref;                 // 可以移动

// 5. 临时对象(prvalue)- 天然可移动
std::string("temp");  // 可以移动
std::vector<int>{1,2,3}; // 可以移动
  • lvalue(左值): 有地址不可移动
cpp 复制代码
// 变量名
int x = 42;
int& ref = x;        // x 是 lvalue
int* ptr = &x;       // 可以取地址

// 数组元素
int arr[5];
arr[0] = 10;         // arr[0] 是 lvalue
&arr[0];             // 可以取地址

// 解引用指针
int* p = &x;
*p = 20;             // *p 是 lvalue
&(*p);               // 可以取地址

// 类成员访问
struct Point { int x, y; };
Point pt{1, 2};
pt.x = 3;            // pt.x 是 lvalue
&pt.x;               // 可以取地址

// 函数调用返回引用
int& getRef() { static int val = 42; return val; }
getRef() = 100;      // getRef() 是 lvalue
&getRef();           // 可以取地址

// 字符串字面量(特殊)
"hello"[0] = 'H';   // "hello"[0] 是 lvalue(C++17前)
  • xvalue (eXpiring/将亡值): 有地址可移动
cpp 复制代码
// std::move 转换
int x = 42;
auto moved = std::move(x);  // std::move(x) 是 xvalue
&std::move(x);              // 可以取地址

// 函数返回右值引用
int&& getRvalueRef() { static int val = 100; return std::move(val); }
auto rref = getRvalueRef(); // getRvalueRef() 返回 xvalue

// 右值引用的成员访问
std::string&& getString() { static std::string s = "hello"; return std::move(s); }
getString().size();         // getString() 是 xvalue,.size() 访问成员

// 数组右值的元素访问
int&& getArray() { static int arr[3] = {1,2,3}; return std::move(arr[0]); }
getArray();                 // getArray() 返回 xvalue

// 类成员指针访问右值
struct Data { int value; };
Data&& getData() { static Data d{42}; return std::move(d); }
getData().value;            // getData() 是 xvalue,.value 访问成员

// 条件表达式(某些情况)
int a = 1, b = 2;
auto result = (a < b ? std::move(a) : std::move(b)); // 条件表达式是 xvalue
  • prvalue (纯右值): 无地址可移动
cpp 复制代码
// 字面量
42;                  // 整数字面量是 prvalue
3.14;               // 浮点字面量是 prvalue
'x';                // 字符字面量是 prvalue
"hello";            // 字符串字面量是 prvalue

// 函数返回值(非引用)
int getValue() { return 100; }
getValue();          // 函数返回值是 prvalue

// 算术运算结果
int a = 10, b = 20;
a + b;              // 算术表达式结果是 prvalue
a * b;              // 乘法表达式结果是 prvalue

// 比较运算结果
a == b;             // 比较表达式结果是 prvalue
a < b;              // 比较表达式结果是 prvalue

// 逻辑运算结果
a && b;             // 逻辑表达式结果是 prvalue
!a;                 // 逻辑非表达式结果是 prvalue

// 类型转换
static_cast<int>(3.14);  // 类型转换结果是 prvalue
(int)3.14;              // C风格转换结果是 prvalue

// 临时对象构造
std::string("hello");    // 临时对象是 prvalue
std::vector<int>{1,2,3}; // 临时对象是 prvalue

// lambda 表达式
[]{ return 42; };        // lambda 表达式是 prvalue

// prvalue 无地址(不能取地址)
// &42;              // 错误:不能取字面量地址
// &getValue();      // 错误:不能取函数返回值地址
// &(a + b);        // 错误:不能取算术表达式地址

// prvalue 可移动
void h(int&&);
h(42);              // 字面量可以绑定到右值引用
h(getValue());      // 函数返回值可以绑定到右值引用
h(a + b);           // 算术表达式结果可以绑定到右值引用

值类别检测

cpp 复制代码
#include <type_traits>
#include <utility>

template<typename T>
void check_value_category(const char* name, T&& expr) {
    std::cout << name << ":\n";
    std::cout << "  is_lvalue: " << std::is_lvalue_reference_v<decltype((expr))> << "\n";
    std::cout << "  is_xvalue: " << std::is_rvalue_reference_v<decltype((expr))> << "\n";
    std::cout << "  is_prvalue: " << !std::is_reference_v<decltype((expr))> << "\n";
}

int main() {
    int x = 42;
    check_value_category("x", x);           // lvalue
    check_value_category("42", 42);         // prvalue
    check_value_category("std::move(x)", std::move(x)); // xvalue
}

引用类型

左值引用 (Lvalue Reference)

cpp 复制代码
int x = 42;
int& ref = x;        // 左值引用
ref = 100;           // 修改原值

// 数组元素 lvalue
int arr[5] = {1, 2, 3, 4, 5};
int& arr_ref = arr[0];  // 绑定到数组元素 lvalue

// 解引用指针 lvalue
int* ptr = &x;
int& ptr_ref = *ptr;    // 绑定到解引用 lvalue

// 类成员 lvalue
struct Point { int x, y; };
Point pt{1, 2};
int& member_ref = pt.x;  // 绑定到成员 lvalue

// 函数返回引用 lvalue
int& getRef() { static int val = 42; return val; }
int& func_ref = getRef();  // 绑定到函数返回的 lvalue

特点

  • 只能绑定到 lvalue
  • 可以修改被引用的对象
  • 生命周期与引用对象相同

右值引用 (Rvalue Reference)

cpp 复制代码
// 绑定到 prvalue(字面量)
int&& rref1 = 42;        // 绑定到整数字面量 prvalue
std::string&& str_ref = "hello";  // 绑定到字符串字面量 prvalue

// 绑定到 prvalue(函数返回值)
int getValue() { return 100; }
int&& rref2 = getValue();  // 绑定到函数返回值 prvalue

// 绑定到 prvalue(算术表达式)
int a = 10, b = 20;
int&& rref3 = a + b;      // 绑定到算术表达式结果 prvalue

// 绑定到 xvalue(std::move 转换)
std::string str = "world";
std::string&& rref4 = std::move(str);  // 绑定到 xvalue

// 绑定到 xvalue(函数返回右值引用)
int&& getRvalueRef() { static int val = 200; return std::move(val); }
int&& rref5 = getRvalueRef();  // 绑定到函数返回的 xvalue

// 绑定到 xvalue(临时对象)
std::vector<int>&& vec_ref = std::vector<int>{1, 2, 3};  // 绑定到临时对象 prvalue

特点

  • 只能绑定到 rvalue(prvalue 或 xvalue)
  • 用于移动语义
  • 延长临时对象生命周期

常量左值引用 (Const Lvalue Reference)

cpp 复制代码
// 绑定到 lvalue
int x = 42;
const int& cref1 = x;    // 绑定到 lvalue

// 绑定到 prvalue(字面量)
const int& cref2 = 42;   // 绑定到整数字面量 prvalue
const std::string& str_cref = "hello";  // 绑定到字符串字面量 prvalue

// 绑定到 prvalue(函数返回值)
int getValue() { return 100; }
const int& cref3 = getValue();  // 绑定到函数返回值 prvalue

// 绑定到 prvalue(算术表达式)
int a = 10, b = 20;
const int& cref4 = a + b;  // 绑定到算术表达式结果 prvalue

// 绑定到 xvalue
std::string str = "world";
const std::string& cref5 = std::move(str);  // 绑定到 xvalue

// 绑定到临时对象 prvalue
const std::vector<int>& vec_cref = std::vector<int>{1, 2, 3};

特点

  • 可以绑定任何值(lvalue、prvalue、xvalue)
  • 不能修改被引用的对象
  • 常用于函数参数,避免拷贝

移动语义 (Move Semantics)

移动语义是C++11引入的重要特性,通过避免不必要的拷贝来提高性能。

移动构造函数和移动赋值运算符

cpp 复制代码
class MyClass {
public:
    // 移动构造函数
    MyClass(MyClass&& other) noexcept 
        : data_(std::move(other.data_)) {
        other.data_ = nullptr;
    }
    
    // 移动赋值运算符
    MyClass& operator=(MyClass&& other) noexcept {
        if (this != &other) {
            delete data_;
            data_ = std::move(other.data_);
            other.data_ = nullptr;
        }
        return *this;
    }
    
private:
    int* data_;
};

std::move 和 std::forward

  • std::move 将左值改为右值
  • std::forward 会根据传入参数的值类别,决定转发为左值引用还是右值引用
cpp 复制代码
#include <utility>

template<typename T>
void process(T&& arg) {
    // std::move: 无条件转换为右值
    auto moved = std::move(arg);
    
    // std::forward: 保持值类别
    auto forwarded = std::forward<T>(arg);
}
相关推荐
两颗泡腾片7 小时前
C++提高编程学习--模板
c++·学习
你好!蒋韦杰-(烟雨平生)9 小时前
扫雷游戏C++
c++·单片机·游戏
monicaaaaan9 小时前
搜索二维矩阵Ⅱ C++
c++·线性代数·矩阵
zh_xuan10 小时前
duiLib 自定义资源目录
c++·ui
西红柿煎蛋10 小时前
C++11的可变参数模板 (Variadic Templates) 是如何工作的?如何使用递归解包一个参数包 (parameter pack)?
c++
源代码•宸11 小时前
深入浅出设计模式——创建型模式之原型模式 Prototype
c++·经验分享·设计模式·原型模式
晨曦学习日记11 小时前
Leetcode239:滑动窗口最大值,双端队列的实现!
数据结构·c++·算法
wait a minutes12 小时前
【c++】leetcode763 划分字母区间
开发语言·c++
菜还不练就废了12 小时前
7.25 C/C++蓝桥杯 |排序算法【下】
c语言·c++·排序算法
饭碗的彼岸one12 小时前
重生之我在10天内卷赢C++ - DAY 2
linux·开发语言·c++·笔记·算法·vim