深入浅出:理解C++中的左值和右值

引言

在日常开发中,表达式求值是编写高效代码的核心之一,而表达式中值的分类直接影响着代码的行为和性能。在C++中,理解左值(lvalue)和右值(rvalue)的概念是掌握表达式求值逻辑的关键。这两个简单的术语不仅决定了对象的存储方式,还影响了变量的生命周期和资源管理。

什么是左值和右值

很多文章盲目的将等号左边的叫做左值,等号右边的叫做右值,那么看接下来的代码。

int main()
{
    int a;
    int b = a;
    int c = 10;
    return 0;
}

这里a在等号的右边能说a是右值吗?请接着往下看,答案马上揭晓。

先说左值

左值:左值的名字绑定到内存地址,表示持久存在的对象,可以被赋值或取地址。

其实很简单,就是编译器在内存上划定了一块空间,用左值的名字来指代那块空间,如果需要赋值或者修改直接使用左值名字即可。

左值就像你家里的一个房间。房间有固定的位置,你可以通过房间名(左值的名字),主卧或者北次卧,随时找到它并进入,改变里面的布置(修改变量的值)。这个房间是一直存在的,直到你搬家或者拆房子(程序结束)它才会消失。

右值:通常不绑定到内存地址,是临时存在的值,无法持久保存。

右值就有点意思了,这里准备从底层的角度讲一下程序在硬件层面是怎么运行的。

当你运行一个C++程序时,操作系统将这个可执行文件加载到内存中。CPU从内存中读取指令和相关数据,这些指令通常存储在指令缓存中,以提高执行效率。数据也会被加载到数据缓存中。CPU的执行过程为,CPU从指令缓存中取出一条指令,并将其放入指令寄存器中;如果指令需要数据操作(如加法运算),CPU会从数据缓存或直接从内存中取出数据,将其加载到数据寄存器中;CPU的算术逻辑单元(ALU)从寄存器中取出数据,按照指令执行操作;最后执行完指令后,结果可能需要写回到数据寄存器中,或者返回内存。这样就是一次完整的一条指令的执行。

我们可以发现执行完指令后,结果又两种返回方式一种是数据寄存器,一种是返回内存。在实际开发中我们可以简单的将这两种方式,分类为右值当中的纯右值和将亡值。

纯右值:表示完全临时的值,不绑定到内存地址,生命周期极短,通常在计算结束时就被丢弃。

将亡值:表示即将被销毁但仍持有资源的对象,常用于移动语义,允许资源的高效转移。

纯右值非常好理解,上面代码中的int c = 10;这个10就是纯右值,是一个完全可以存在于寄存器中的值,赋值运算结束就被丢弃了。

将亡值和纯右值几乎没有区别,但是它有一个重要的特性就是在销毁前允许其资源被安全转移(即移动语义)。其实也很好理解,在实际开发过程中,我们可能申请好了一块空间,作为返回值,返回语句结束,那么申请好的空间也就要被释放掉了,这时候发明C++的那一批人就在想,那已经申请好了的一块空间,能不能直接利用将资源转移呢?这里就是大名鼎鼎的移动语义。

#include <iostream>
#include <string>

class StringWrapper {
public:
    StringWrapper(const std::string& str) : data(new std::string(str)) {
        std::cout << "Constructed: " << *data << std::endl;
    }

    ~StringWrapper() {
        delete data;
        std::cout << "Destroyed" << std::endl;
    }

    // 移动构造
    StringWrapper(StringWrapper&& other) noexcept : data(other.data) {
        other.data = nullptr; 
        std::cout << "Moved" << std::endl;
    }

private:
    std::string* data;
};

StringWrapper createWrapper() {
    StringWrapper wrapper("Hello, World!"); // 临时对象
    return wrapper; // 返回将亡值
}

int main() {
    StringWrapper myWrapper = createWrapper(); // 调用移动构造
    return 0;
}

createWrapper() 函数:创建一个临时的StringWrapper对象,并返回它作为将亡值。

而在main函数中,通过调用createWrapper函数就得到了这个临时对象将亡值,那么已经申请好了,不利用是不是就浪费了。这里就使用了移动语义来利用这块资源。

左值和右值的用途

左值的用途

赋值操作的左侧:左值可以出现在赋值语句的左侧,如int x = 10;中的x就是左值。

取地址操作:左值可以取地址,例如&x,因为它有一个固定的内存位置。

int a = 5;       // a 是一个左值
int* p = &a;     // 可以取a的地址
a = 10;          // 可以给a赋值

纯右值的用途

赋值操作的右侧:右值通常出现在赋值语句的右侧,如int y = 5;中的5就是右值。

传递数据:右值常用于表达式计算的中间结果或传递数据。

int b = 3 + 4;   // 3 + 4 的结果 7 是右值
int c = 7;       // 7 是右值

将亡值的用途上面也已经说了一部分,剩下的在右值引用这篇文章中介绍。

左值和右值的用途对比

左值

  • 用于表示变量、数组元素、对象等可以修改或多次引用的实体。
  • 在需要持久化、可修改的内存存储时使用。

右值

  • 用于临时值、字面量、表达式结果等只需要一次性使用的场合。
  • 在需要计算结果、传递参数或临时值时使用。

总结

左值实际上就是有内存空间,有名字的实体。右值可分为纯右值和将亡值,纯右值就是一次性使用的,可存储在寄存器当中的。将亡值就是在“死亡”之前允许资源被安全转移的。如果通篇读下来,相信你对于左值右值能有更加深刻的理解,对于以后的开发之路也会走的更加通畅。让我们下篇再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值