当前位置: 首页 > news >正文

C++左值右值

左值右值

什么是左值右值

​ 左值右值的概念我们一般理解为赋值号左边或者右边的值,这样理解也是有一定的道理的,但是实际上,左值右值绝不只是简简单单的定义,用好它绝对会大大提高编程效率。首先,严格意义上的左值右值取决于其是否能取地址,上代码:

#include <iostream>

int main() {
    int a = 5;
    std::cout << &a << std::endl;       // 输出:0x7ffe7580b204
    std::cout << &5 << std::endl;       // 报错:lvalue required as unary ‘&’ operand
}

报错内容就是:不能将&运算符应用于右值。之所以称呼为左值右值,是因为左值大多可以用赋值号修改(除了用const修饰的)。当然,除了这些最简单的变量,还有很多比较奇怪的左值右值,这里也列举一下:

#include <iostream>

int x;

int& func_ref() {
    return x;
}

class obj{
    double member;
}

int main() {
    int a = 10;         // a 是左值
    int* p = &a;        // 正确,a 可寻址
    a = 20;             // 正确,左值可修改
    int& ref = a;		// ref是左值
    
    int array[3] = {1, 2, 3};  // 数组元素是左值
    
    obj.member = 5;		// 类成员是左值

    int b = a + 5;      // a 作为右值使用(隐式转换)

    func_ref() = 30;    // 正确,func_ref() 返回左值引用,实际上func_ref()就是变量x的引用
    std::cout << func_ref() << std::endl;		// 输出30

    const int c = 40;
    // c = 50;          // 错误,c 是 const 左值,不可修改
    
    const char *ptr = &"hello"[0]; // 正确,“hello”是左值 
}

​ 这里有一个比较难以理解,就是hello竟然是左值,而且可以取地址,这将在下面特性介绍中提到。

左值右值的特性

​ 首先,就是我们上面提的,左值可以使用取地址符&,我们称之为可寻址性,那么右值也就具有不可寻址性。其次,左值的生存周期通常超出当前语句的作用域,这句话也很好理解,比如int x = 5,那么在这句代码后仍然可以使用变量x,而这里的右值5,超过这句话后,就不存在了,下一次即使出现相同的5也和它时不一个量了。

​ 除此之外,还有他们的储存位置也不尽相同。对于左值而言,它通常存放在中,比如int x = 5,这里的x就存放在栈中,除此之外,还有可能存放在静态区等位置,而右值会存放在寄存器栈临时空间等位置,甚至无储存,很明显就是右值没有可以持久的储存位置。

​ 这里我们解释一下上面提到的hello为什么是左值,作为C++字符串而言,它的设计继承了C语言的特性,C语言中的设计也是如此,当我们使用字符串比如hello时,它通常是具有静态存储期的 const char[N] 数组对象,与其他类型的字面量如5不同。对于这一点我觉得没有必要深究原因,留个心眼即可。

右值的引入

​ 实际上,C++98/03是只有左值,没有右值这个概念的,之所以C++11引入这个概念是出于对性能的考虑,这也就是我在文首说的,它绝不仅仅只是个定义这么简单。上代码,注意下面的std::move()函数:

#include <iostream>
#include <vector>
#include <chrono>

int main() {
    const int DATA_SIZE = 10000000;  // 一个包含 1000 万个 double 的向量

    // 创建一个大型 vector(模拟需要拷贝的数据)
    std::vector<double> src(DATA_SIZE, 3.14);

    // 方法 1:深拷贝(拷贝构造函数)
    auto start_copy = std::chrono::high_resolution_clock::now();
    std::vector<double> dest_copy = src;  // 深拷贝
    auto end_copy = std::chrono::high_resolution_clock::now();
    auto duration_copy = std::chrono::duration_cast<std::chrono::microseconds>(end_copy - start_copy).count();

    // 方法 2:移动语义(std::move)
    auto start_move = std::chrono::high_resolution_clock::now();
    std::vector<double> dest_move = std::move(src);  // 移动语义
    auto end_move = std::chrono::high_resolution_clock::now();
    auto duration_move = std::chrono::duration_cast<std::chrono::microseconds>(end_move - start_move).count();

    // 输出结果
    std::cout << "深拷贝耗时: " << duration_copy << " 微秒" << std::endl;
    std::cout << "移动语义耗时: " << duration_move << " 微秒" << std::endl;

    return 0;
}

​ 这里我们定义了一个包含1000万个double类型的向量,并用传统的赋值方式拷贝至另一个向量,记录下拷贝所需时间,同时用移动语义的方法拷贝相同的数组,记录下所需时间,并输出,输出结果如下:

深拷贝耗时: 41368 微秒
移动语义耗时: 0 微秒

​ 性能差别就是这么大,我们解释一下。这里的深拷贝其实就是一个一个复制这个数据,转到需要拷贝的向量中,而移动语以就是只偷这块内存的指针,为我所用,所以其时间复杂度为O(1),当然,在这之后,原本的数据就为空了(但并不违法),如果我们在上面的代码中分别输出拷贝后的向量大小,就会发现,第一个仍然是一千万个变量,而移动后就归零了。

​ 举个例子就是需要将一个快递寄给另一个地方,第一种做法是,把快递拿出来,按照原样再做一个,再装进另一个快递箱中,再贴上一个新的快递单,第二种做法就是撕掉之前的快递单,贴上新的快递单,(之前的快递单贴在了一个空箱子上)。

​ 那么,这和左值右值有什么关系呢?事实上,向量src在使用函数std::move()后,它就从左值变为右值了,因为只有右值存在移动语义,因此这里的std::move()函数并不是真的移动,它只是将左值变为右值。因此,对于一些容器的拷贝操作,我们应该能想到右值的存在。

附录

附录一

填坑:右值引用

​ 上篇博客中我们提到了右值引用的概念,并且给出了定义方式&&,这里,我们详细说说右值引用究竟有什么用。实际上,我们不太会去定义一个右值引用,当然,如果你强行使用一个右值引用一个常量,也并不会有什么问题,而且还会将这个右值的生命周期延长到引用的作用域,但是这着实是tuokuzifangpi。右值引用的作用也正是上面提到的拷贝中的优点,至于&&符号,别忘了,函数参数可以使用得到,这里上一个简单的代码尝尝鲜:

#include <iostream>

// 定义一个函数,接收 int 的右值引用参数
void processInt(int&& x) {
    std::cout << "Received rvalue: " << x << std::endl;
}

int main() {
    int a = 100;

    processInt(std::move(a));

    // 直接传递一个右值字面量也可以
    processInt(200);

    return 0;
}

相关文章:

  • vscode 配置服务器远程连接
  • VLLM专题(三十一)—架构概述
  • doris:审计日志
  • C#通过SignalR直接返回流式响应内容
  • 【RabbitMQ】RabbitMQ中死信交换机是什么?延迟队列呢?有哪些应用场景?
  • 【vue3+vant】移动端 - 部门树下拉选择组件 DeptTreeSelect 开发
  • Vue3 界面设计插件 microi-pageengine 入门教程一
  • MyBatis 学习经验分享
  • 责任链模式:优雅处理请求的设计艺术
  • Docker运行Mysql异常:Operation not permitted
  • OceanBase 读写分离最佳实践
  • ADB三个模块介绍
  • C# HTTP认证方式详解与代码实现
  • Docker 最佳实践(MySQL)
  • [spring] Spring JPA - Hibernate 多表联查 1
  • K8S学习之基础三十三:K8S之监控Prometheus部署程序版
  • 【蓝桥杯python研究生组备赛】005 数学与简单DP
  • windows安装金仓V9初始化数据库失败
  • Grid 布局实现三栏布局
  • Vue3:构建高效用户界面的利器
  • 湖南娄底市长曾超群,已任娄底市委书记
  • 对外投资增长、消费市场持续升温,中国经济砥砺前行
  • 哈工大赵杰:人形机器人要拓展人的能力而非一味复制,未来产业要做成至少10年
  • 经济日报刊文:积极应对稳住外贸基本盘
  • 印度加大应对力度,吊销所有巴基斯坦公民签证
  • 国防部:希望美方不要有“受迫害妄想症”,总拿别人当借口