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

C++的左值、右值、左值引用和右值引用

目录

  • 左值和右值
  • 左值引用
  • 右值引用

参考《现代C++语言核心特性解析》

以下加粗文字都是摘自本书。

左值和右值

左值和右值得概念在C++98就出现了,根据字面意思理解就是:左值是表达式等号左边的值,右值是表达式等号右边的值。

int x = 1;
int y = 3;
int z = x + y;

其中,x、y和z是左值,1、3、x + y的值是右值。

但是用这种方式去判断太过简单,而且不准确,例如:

int b = a;

其中a是左值不是右值。

摘自《现代C++语言核心特性解析》:在C++中所谓的左值一般是指一个指向特定内存的具有名称的值(具名对象),它有一个相对稳定的内存地址,并且有一段较长的生命周期。而右值则是不指向稳定内存地址的匿名值(不具名对象),它的生命周期很短,通常是暂时性的。基于这一特征,我们可以用取地址符&来判断左值和右值,能取到内存地址的值为左值,否则为右值。还是以上面的代码为例,因为&a和&b都是符合语法规则的,所以a和b都是左值,而&1在GCC中会给出“lvalue required as unary ‘&’ operand”错误信息以提示程序员&运算符需要的是一个左值。

总结:能取到内存地址的值是左值,否则为右值。

但有时候通过直觉去判断是不准确的,比如:

int x = 1;int get_val()
{return x;
}void set_val(int val)
{x = val;
}int main() 
{x++;++x;int y = get_val();set_val(6);
}

在上面的代码中,x++是一个右值,++x是一个左值。因为对于后置++,编译器会生成一份x的临时复制,然后才对x自增,返回临时复制的内容,所以后置++是右值。

int a = &x++; // 编译失败
int a = &++x; // 编译成功

对于get_val函数,x作为全局变量是一个左值,但在返回x值时编译器会产生一份x的临时复制,还是右值。

int a = &get_val(); // 编译失败

对于set_val函数,set_val(6)中的6是一个右值,但是进入函数之后val变成一个左值,所以可以对val取地址。

void set_val(int val)
{int a = &val; // 编译成功x = val;
}

还需要强调一点,通常字面常量都是右值,但是字符串常量除外。

int x = &1; // 编译失败
auto p = &"hello world" // 编译成功

毕竟&"hello world"的语法也很少看到。但是这段代码是可以编译成功的,其实原因仔细想来也很简单,编译器会将字符串字面量存储到程序的数据段中,程序加载的时候也会为其开辟内存空间,所以我们可以使用取地址符&来获取字符串字面量的内存地址。

左值引用

左值引用的出现使C++编程脱离了使用指针的危险,当我们需要将一个对象作为函数的参数时,会使用左值引用,这种方式会免去创建临时对象的操作。

左值引用细分可以分为非常量左值引用常量左值引用

非常量左值只能引用左值,不能引用右值。
常量左值可以引用左值,也可以引用右值。

	int &a = 1; // 编译失败int x = 1;const int& b = 1;	// 常量左值引用右值const int& c = x;	// 常量左值引用左值

请注意,虽然在结果上const int &x =11和const int x = 11是一样的,但是从语法上来说,前者是被引用了,所以语句结束后11的生命周期被延长,而后者当语句结束后右值11应该被销毁。虽然常量左值引用可以引用右值的这个特性在赋值表达式中看不出什么实用价值,但是在函数形参列表中却有着巨大的作用。一个典型的例子就是复制构造函数和复制赋值运算符函数,通常情况下我们实现的这两个函数的形参都是一个常量左值引用,例如:

#include <iostream>class MyClass
{
public:MyClass(){}MyClass(const MyClass&){}MyClass& operator=(const MyClass&){return *this;}
};MyClass MakeMyClass()
{return MyClass();
}int main()
{MyClass x1;MyClass x2(x1);MyClass x3(MakeMyClass());x3 = MakeMyClass();system("pause");return 0;
}

以上代码可以通过编译,但是如果把拷贝构造函数和赋值构造函数形参的常量性去掉,就编译不过了。因为非常量左值无法引用MakeMyClass()返回的右值,所以常量左值引用右值是一条非常棒的特性,但是也有弊端。一旦使用了常量左值,就不能在函数里去修改对象的内容(强制类型转换除外),所以在这种情况下诞生了右值引用。

右值引用

右值引用是一种引用右值且只能引用右值的方法。

左值引用是在类型后面加&,右值引用是在类型后面加&&。

	int x = 1;int& a = x;		// 左值引用int&& k = 2;	// 右值引用

右值引用最大的特点是延长了右值的生命周期。

举例:

#include <iostream>class BigMemoryPool
{
public:BigMemoryPool(){std::cout << "普通构造函数" << std::endl;}~BigMemoryPool(){std::cout << "析构函数" << std::endl;}BigMemoryPool(const BigMemoryPool &other){std::cout << "拷贝构造函数" << std::endl;}void show(){std::cout << "调用函数" << std::endl;}
};BigMemoryPool Make()
{BigMemoryPool resultA;return resultA;
}int main()
{BigMemoryPool &&my_pool = Make();my_pool.show();return 0;
}

请注意,用GCC编译以上代码需要加上命令行参数-fno-elide-constructors用于关闭函数返回值优化(RVO)。因为GCC的RVO优化会减少复制构造函数的调用,不利于语言特性实验。

但是我用G++编译,命令行是

g++ -std=c++11 -fno-elide-constructors main.cpp -o main

以上代码会调用三次构造函数,注释中的1、2、3会各调用一次构造函数。

输出结果:

PS C:\Users\zh'n\Desktop\新建文件夹> g++ -std=c++11 -fno-elide-constructors main.cpp -o main
PS C:\Users\zh'n\Desktop\新建文件夹> ./main
普通构造函数
拷贝构造函数
析构函数
拷贝构造函数
析构函数
调用函数
析构函数

如果我们将"3“改为

BigMemoryPool &&my_pool = Make();

会调用两次构造函数,第一次是Make函数中resultA的默认构造,第二次是return resultA引发的复制构造。不同的是,由于x2是一个右值引用,引用的对象是函数Make返回的临时对象,因此该临时对象的生命周期得到延长,所以我们可以在BigMemoryPool &&my_pool = Make();语句结束后继续调用show函数而不会发生任何问题。

输出结果

PS C:\Users\zh'n\Desktop\新建文件夹> g++ -std=c++11 -fno-elide-constructors main.cpp -o main
PS C:\Users\zh'n\Desktop\新建文件夹> ./main
普通构造函数
拷贝构造函数
析构函数
调用函数
析构函数

对性能敏感的读者应该注意到了,延长临时对象生命周期并不是这里右值引用的最终目标,其真实目标应该是减少对象复制,提升程序性能。

http://www.lryc.cn/news/258524.html

相关文章:

  • 罗技鼠标使用接收器和电脑重新配对
  • 高项备考葵花宝典-项目进度管理输入、输出、工具和技术(下,很详细考试必过)
  • GumbleSoftmax感性理解--可导式输出随机类别
  • ROS gazebo 机器人仿真,环境与robot建模,添加相机 lidar,控制robot运动
  • 人体关键点检测3:Android实现人体关键点检测(人体姿势估计)含源码 可实时检测
  • 踩坑记录:uniapp中scroll-view的scroll-top不生效问题;
  • YOLOX 学习笔记
  • 第3节:Vue3 v-bind指令
  • Token 和 N-Gram、Bag-of-Words 模型释义
  • 【go语言实践】基础篇 - 流程控制
  • Linux:gdb的简单使用
  • NestJS的微服务实现
  • Debian 终端Shell命令行长路径改为短路径
  • Ansible变量是什么?如何实现任务的循环?
  • 随机梯度下降的代码实现
  • 渐进推导中常用的一些结论
  • 网络安全等级保护V2.0测评指标
  • java中list的addAll用法详细实例?
  • 关于学习计算机的心得与体会
  • LLM之RAG理论(一)| CoN:腾讯提出笔记链(CHAIN-OF-NOTE)来提高检索增强模型(RAG)的透明度
  • Android studio:打开应用程序闪退的问题2.0
  • Spring IoC如何存取Bean对象
  • 【开源】基于Vue.js的实验室耗材管理系统
  • Datawhale聪明办法学Python(task2Getting Started)
  • 量化交易怎么操作?量化软件怎么选择比较好?(散户福利,建议收藏)
  • 什么是 AWS IAM?如何使用 IAM 数据库身份验证连接到 Amazon RDS(上)
  • Python从入门到精通七:Python函数进阶
  • uniapp踩坑之项目:使用过滤器将时间格式化为特定格式
  • webpack学习-2.管理资源
  • 658. 找到 K 个最接近的元素