C++左值右值

C++ 左值右值

  • 左值(lvalue):有名字、有地址的对象,能出现在赋值号左边
  • 右值(rvalue):没有名字、临时存在的值,只能出现在赋值号右边

如何判断

能否被赋值? 能否取地址? 类型
左值
不能 不能(大部分情况) 右值
int x = 42;
&x;         // 左值能取地址
&(x + 1);   // 编译错误,右值不能取地址

常见示例

int a = 10;      // a 是左值(有名字,可以 &a 取地址)
                 // 10 是右值(字面量常量,不能取地址)

int b = a + 5;   // b 是左值(变量,有名字,可以取地址)
                 // a + 5 是右值(表达式结果的临时值)

// ------------------- 一些容易混淆的情况 -------------------

int c = (a);     // (a) 仍然是左值(加括号不影响)

int d = a++;     // a++ 返回右值(自增前的临时值)
                 // a 本身依旧是左值

int e = ++a;     // ++a 返回左值(自增后的 a,本身可以继续取地址)

int f = a * 2;   // a * 2 是右值(临时结果)
                 // f 是左值(变量)

int& foo();      // 假设 foo 返回 int& (引用)
int  bar();      // 假设 bar 返回 int (值)

int& r = foo();  // foo() 返回左值(引用,可取地址)

int x = bar();   // bar() 返回右值(纯值,临时对象)
示例 类型 说明
变量 x 左值 有地址、可以赋值
字面量 10 右值 没地址、不能取地址
x + 1 右值 运算结果是一个临时值
"abc" 右值 字符串字面量是临时值
函数返回值(返回非引用) 右值
函数返回引用 左值

与引用的关系

引用类型 能绑定右值 能绑定左值 用途
T& 不能 修改已有变量
const T& 安全地读取,不可修改
T&& 不能 专门操作右值,进行移动
int a = 10;

int& r1 = a;      // 左值引用绑定左值
int& r2 = 10;     // 错误,不能绑定右值
const int& r3 = 10; // 常量引用可以绑定右值
int&& r4 = 10;    // 右值引用绑定右值

右值引用

语法是 T&&只允许绑定右值(不能绑定有名字的变量)。右值引用让你可以“接住”临时对象的资源,然后偷走它们,而不是复制。

移动语义

std::string a = "hello";
std::string b = std::move(a);  // move 后,a 的资源转移给 b

std::string s1 = "abc";
std::string s2 = s1;           // 拷贝构造(复制内容)

std::string s3 = std::move(s1); // 移动构造(直接窃取 s1 的资源)

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

class MyClass {
public:
    MyClass(MyClass&& other) noexcept {
        // 把 other 的资源“偷”过来
    }

    MyClass& operator=(MyClass&& other) noexcept {
        // 先释放自己资源,再接管 other's
        return *this;
    }
};

强烈推荐给移动构造函数加上 noexcept,因为标准库容器(如 std::vectorstd::string 在内部做扩容时:

  • 优先使用移动构造函数(比拷贝效率高)
  • 但前提是:这个移动构造是 noexcept

否则就退回使用拷贝构造,导致性能变差。

引用限定符

引用限定符(Ref-qualifiers)是 C++11 引入的一种语法,用于区分成员函数对对象引用类型的限定,常见于成员函数声明后面,表示该成员函数只能被左值对象或右值对象调用。

主要用来控制成员函数的调用权限,尤其对重载成员函数行为和资源管理(比如移动语义)非常重要。

  • 左值引用限定符 &:表示该成员函数只能被左值对象调用。
  • 右值引用限定符 &&:表示该成员函数只能被右值对象调用。
  • 无引用限定符:该成员函数可以被左值和右值调用。
struct Foo {
    void foo() & { std::cout << "called on lvalue\n"; }
    void foo() && { std::cout << "called on rvalue\n"; }
};

int main() {
    Foo f;
    f.foo();         // 调用左值版本

    Foo().foo();     // 调用右值版本
}

输出:

called on lvalue
called on rvalue

用途详解

  1. 区分对左值和右值的操作

    允许对左值对象和右值对象提供不同的行为,尤其对支持移动语义的类非常重要。

    • 当操作临时对象(右值)时,可以安全地“偷走”资源(移动构造或移动赋值)。

    • 当操作具名对象(左值)时,通常不希望破坏原对象。

  2. 防止错误的调用

    比如写一个函数只想在临时对象上调用,或者只允许在持久对象上调用,引用限定符可以做到。

  3. 配合移动语义

    比如 std::string 中的 operator+,返回的临时字符串可以调用带 && 限定符的成员函数,避免不必要的复制。

结合 const 使用

引用限定符可以和const一起用:

void foo() const &;   // const左值对象调用
void foo() const &&;  // const右值对象调用
函数签名 调用对象类型限制 备注
void f() 左值和右值均可 默认
void f() & 只能左值调用 用于修改左值的成员函数
void f() && 只能右值调用 用于移动语义或优化
void f() const & 只能常量左值调用 不修改对象的左值
void f() const && 只能常量右值调用 不修改对象的右值
  • 只用于非静态成员函数声明末尾,表示该成员函数只能被特定类型的对象调用。

  • 如果一个成员函数写了引用限定符(比如 &&&),那么所有该函数同名、参数列表完全相同的重载版本,也都建议写上引用限定符

    struct A {
        void foo() & { std::cout << "lvalue\n"; }
        // void foo() && { std::cout << "rvalue\n"; }
        // 如果这里没写 foo() &&,对右值调用 foo() 会找不到匹配版本
        // 要么都写上引用限定符,覆盖所有情况;要么都不写,保持传统行为
    };
    

    引用限定符是函数签名的一部分foo() &foo() 是两种不同的成员函数签名。

posted @ 2025-05-14 18:42  _Sylvan  阅读(43)  评论(0)    收藏  举报