C++11右值

news2024/11/27 8:31:33

C++11右值

  • 类型与值类别
    • 左值
    • 纯右值
    • 将亡值
      • 内置类型
      • 类类型
  • 左值引用和右值引用
    • 内置类型
      • 右值引用:
      • 具有右值引用自身为左值。
      • 右值引用与函数重载
      • 函数返回值
    • 自定义类型
      • 右值引用
      • 构造函数的隐式转换
      • 右值引用与函数重载
      • 类类型作为函数的返回值
  • 右值引用与函数模板
    • 泛化版本,刻画版本
    • 模板
    • 引用型别
  • 右值引用变成左值引用
  • 完美转发和forward
  • 引用叠加

类型与值类别

C++表达式可以按照两个独立的属性分为:类型和值类别。而值类别又分为三种:左值,右值,将亡值,每个之值类别都有与之某种引用类型对应。
在这里插入图片描述
其中将亡值具名就是左值,不具名就是右值,所以呢左值和将亡值合成泛左值,将亡值和纯右值称为右值。

左值

能够取地址的表达式称为左值表达式。

int main() {
	int a=10;
	const int b=20;
	int* ip=&a;
	const int* cp=&b;
	return 0;
}

纯右值

纯右值本身就是字面值,例如:43,false,34.24;
具名变量或对象都是左值,而右值不具名。

int main() {
	int a=10;//可以取地址,左值
	const int b=20;//常性整型,可以取地址,左值
	double dx=12.34;//double类型的值,可以取地址,左值
	int* p=nullptr;//左值
	//字面常量12,20,12.34,nullptr这些不可以取地址,称为右值,不可以改变也成为纯右值
	return 0;
}

将亡值

内置类型

在表达式运行或计算过程中产生的临时变量或临时对象称之为将亡值,临时值可能是字面值,也可能是一个不具名的对象。

int func() {
	int x=10;
	return x;
}
int main() {
	int a=10,b=20;
	int i-0;
	i=a+b;
	++i;
	i++;
	&a;
	a=func();
	return 0;
}

算数表达式,逻辑表达式,比较表达式,取地址表达式等,计算结果相当于字面值,(数据实际存储在CPU的数据寄存器中),所以是将亡值,不可改,所以是纯右值。
对i加1赋值给i,返回值就是i,i具名就是左值。++i的结果也是具名的,所以是左值。
对i++而言,首先对i进行一次拷贝,将得到的副本作为返回结果,再对结果进行+1,由于i++的结果是对i+1前靠别了一份,不具名,所以是将亡值,此将亡值不可写,也是字面量,所以是纯右值。

类类型

自定义类型:

class Int {
    int _value;
public:
    Int(int x = 0) :_value(x) { cout << "Create Int:" << _value << endl; }
    Int(const Int& it) :_value(it._value) { cout << "Copy Create Int:" << this << endl; }
    Int& operator=(const Int& it) {
        if (this != &it) {
            _value = it._value;
            cout << this << "=" << &it << endl;
        }
        return *this;
    }
    ~Int() { cout << "Destory Int:" << _value << endl; }
    void Setvalue(int x) { _value = x; }
    int Getvalue()const { return _value; }
    void Showvalue()const { cout << _value << endl; }
};

以上面自定义Int类型为例执行下面主程序:

int main() {
    Int a(1);//左值
    const Int b(20);//常性左值
    a.Showvalue();
    Int(2).Showvalue();//不具名对象,右值
    Int(4).Setvalue(20);//右值也可以调用成员函数,可以改变其属性,纯右值不可以改变
    Int* ip = &Int(12);//error 右值不能取地址
    return 0;
}

函数返回类型是类类型对象:

Int func(int x) {
    Int tmp(x);
    return tmp;
}
int main() {
    Int a = 1;//构造函数创建对象,左值
    a = func(2);//函数内构造出tmp对象,然后通过拷贝构造产生将亡值,然后释放tmp对象
    //接着将该将亡值都西昂通过运算符赋值赋值给a对象,然后析构将亡值对象。
    Int b=func(3);//直接进行移动拷贝构造。
    Int(3).Setvalue(20);//创建右值调用设置属性。该语句结束析构该右值对象
    func(4).Showvalue();//函数内构造出tmp对象,然后通过拷贝构造产生将亡值,然后释放tmp对象
    //通过该将亡值调用Show函数,最后释放将亡值对象
    func(5).Setvalue(100);//同上,仅仅是改变数据
    &func(7);//error 将亡值不具名是右值,不能取地址。
    return 0;
}

对象生存期:

Int fun(int x) {
	Int tmp(x);
	return tmp;
}
int main() {
	Int(1).Showvalue();
	func(2).Showvalue();//将亡值调用
	return 0;
}

左值引用和右值引用

内置类型

右值引用:

int main() {
    int x = 10;
    int& a = x;//左值引用
    const int& b = x;//常左值引用,全能引用
    int** rc = x;//error 右值引用不能引用左值
    int&& rd = 20;//右值引用引用纯右值
    const int& e = 10;//常左值引用,万能引用
    return 0;
}

具有右值引用自身为左值。

int main() {
	int &&a=10;
	int&& b=a;//error 右值引用具名就变成了左值
	int& c=a;
	const int& cd=a;//万能引用
	return 0;
}

右值引用与函数重载

以前的博客写了很多次了,这里展示一下代码:

void func(int& val) {//A
    cout << "L value reference" << endl;
}
void func(const int& val) {//B
    cout << "const L value reference" << endl;
}
void func(int&& val) {//C
    cout << "R value reference" << endl;
}
int main() {
    int a = 10;
    const int b = 20;
    func(a);//1.A 2.B 
    func(b);//B
    func(10);//1.C 2.B
    return 0;
}

函数返回值

返回值为值对象,返回将亡值对象。如果是返回左值引用,不能赋值给右值,如果是右值引用不可以赋值给左值。但是都可以赋值给左值常引用。虽然可以编译通过,但是呢我觉得如果返回值的生存周期受函数影响,迟早在运行过程中会出错。

int funa() {//A
    int x = 10;
    return x;
}

int& funb() {//A
    int x = 10;
    return x;
}

int&& func() {//A
    int x = 10;
    return int(10);
}
int main() {
    int a = funa();
    int& b = funa();//error funa返回右值
    const int& c = funa();
    int&& d = funa();

    int e = funb();
    int& f = funb();
    const int& g = funb();
    int&& h = funb();//左值无法赋值给右值

    int i = func();
    int& j = func();//无法将右值给左值
    const int& k = func();
    int&& l = func();

    return 0;
}

自定义类型

右值引用

还是上面的Int类为例:

int main() {
    Int a = Int(1);
    Int& b = Int(2);//error 不具名对象为右值,
    const Int& c = Int(3);
    Int&& d = Int(4);
    a.Showvalue();
    //b.Showvalue();
    c.Showvalue();
    d.Showvalue();
    return 0;
}

无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名,通过右值引用的声明,该右值又重获新生,其生命周期与右值引用类型名的生命周期一样,只要改右值引用名话或者,改右值临时两就一直存活下去。

构造函数的隐式转换

将内置类型隐式转换为Int类型,初始值为1,这就是构造函数的一个作用

int main() {
	const Int& a=1;
	Int &&b=2;
	Int &&c=b;//error b变成了左值
	Int&dr=b;
	return 0;
}

此处用了转化多了一步,从1转换为Int类型,在转换为常性,

int main() {
    const Int& a = 1;
    //const int& a=(Int)(1);
    Int&& b = 2;
    //Int&& b=(Int)(2);
    Int&& c = b;//error具名变成左值
    Int& dr = b;
    return 0;
}

在构造函数中加入explicit明确关键字,组织构造函数的隐式转换:

explicit Int(int x = 0) :_value(x) {
        cout << "Create Int:" << _value << endl;
    }

右值引用与函数重载

void func(Int& val) {//A
    cout << "L value reference" << endl;
}
void func(const Int& val) {//B
    cout << "const L value reference" << endl;
}
void func(Int&& val) {//C
    cout << "R value reference" << endl;
}
int main() {
    Int a(1);
    const Int b(2);
    func(a);
    func(b);
    func(Int(3));
}

类类型作为函数的返回值

左值常引用是万能引用,可以接收左值,右值,常性左值和常性右值,普通左值不能接收右值。

Int func(int x) {
    Int tmp(x);
    return tmp;
}
int main() {
    Int a = func(1);
    Int x(0);
    x = func(2);
    Int& b = func(3);//右值给左值
    const Int& c = func(4);
    Int&& d = func(5);
}

右值引用与函数模板

泛化版本,刻画版本

template<class T>//A
class Test {//Test<int> Test<int*>...一切类型都可以接收

};
template<class T>//B
class Test<T*> {//Test<int*>...指针类型

};
template<>//C
class Test<const char*>{//Test<const char*>

};

A:泛化版本 B:部分刻画版本 C:刻画版本

模板

template<class _Ty>
struct my_remove_reference {
    using type = _Ty;
    my_remove_reference() {
        type x;
        cout << "_Ty" << endl;
    }
};
template<class _Ty>
struct my_remove_reference <_Ty&> {
    using type = _Ty;
    my_remove_reference() {
        type x;
        cout << "_Ty&" << endl;
    }
};
template<class _Ty>
struct my_remove_reference {
    using type = _Ty;
    my_remove_reference()<_Ty &&> {
        type x;
        cout << "_Ty&&" << endl; 
    }
};
int main() {
    my_remove_reference<int>();//A
    my_remove_reference<int&>();//B
    my_remove_reference<int&&>();//C
    my_remove_reference<const int&>();//D
    return 0;
}

上面代码主函数中,调用的参数不同使用不同的模板,但是其中_Ty类型是什么类型呢?
A:int B:int C:int D:const int

template<class _Ty>
using my_remove_reference_t =
    typename my_remove_reference<_Ty>::type;

int main() {
    my_remove_reference_t<int>a;
    my_remove_reference_t<int&>b;
    my_remove_reference_t<int&&>c;
    my_remove_reference_t<const int&>d;
    return 0;
}

思考上面代码运行结果。

引用型别

代码应用如下:

template<class T>
void func(T&& x) {//未定义的引用类型,被什么值类型初始化就是什么值
    int e = 10;
    T y = e;
}
int main() {
    int a = 10;
    const int b = 20;
    int&& c = 30;
    int& v = a;
    func(a);//左值
    func(v);//左值引用
    func(b);//常性左值
    func(30);//右值
    return 0;
}

右值引用变成左值引用

void Print(int& val) {//A
    cout << "L value reference" << endl;
}
void Print(const int& val) {//B
    cout << "const L value reference" << endl;
}
void Print(int&& val) {//C
    cout << "R value reference" << endl;
}
template<class T>
void TestRvalue(T&& x) {
    Print(x);
    Print(std::forward<T>(x));
}
int main() {
    int a = 1;
    const int& b = 2;
    int&& c = 3;
    TestRvalue(a);
    TestRvalue(b);
    TestRvalue(c);
    TestRvalue(10);
    return 0;
}

运行上面代码我们会发现因为具名所以导致了输出结果全为左值或者常性左值。
在这里插入图片描述

完美转发和forward

我们在写代码过程中会发现和上面情况一样,右值引用变成左值引用:右值没有办法让他不具名,但是他具名之后又会转成左值,所以出现了完美转发:

void Print(int& val) {//A
    cout << "L value reference" << endl;
}
void Print(const int& val) {//B
    cout << "const L value reference" << endl;
}
void Print(int&& val) {//C
    cout << "R value reference" << endl;
}
template<class T>
void TestRvalue(T&& x) {
    Print(std::forward<T>(x));
}
int main() {
    int a = 1;
    const int& b = 2;
    int&& c = 3;
    TestRvalue(a);
    TestRvalue(b);
    TestRvalue(c);
    TestRvalue(10);
    return 0;
}

运行结果:在这里插入图片描述

引用叠加

右值引用与右值引用叠加是右值引用
右值引用与左值引用叠加是左值引用
右值引用与左值常引用叠加式是左值常引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/607986.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java005——idea编写和运行第一个Java程序HelloWorld

1、首相需要了解什么是IDE&#xff08;集成开发环境&#xff09;&#xff1f; 2、了解Java有哪些主流的开发工具&#xff1f; 一、创建一个文件夹用来存放Java项目 二、创建一个空项目 点击idea首页新建项目按钮 创建一个空项目 这样空项目就创建好了 三、在空项目中新建一…

leetcode 数据库题 584,585,586,595,596,601,602,607,608,610

leetcode 数据库题 第三弹 584. 寻找用户推荐人585. 2016年的投资586. 订单最多的客户595. 大的国家596. 超过5名学生的课601. 体育馆的人流量602. 好友申请 II &#xff1a;谁有最多的好友607. 销售员608. 树节点610. 判断三角形小结 584. 寻找用户推荐人 https://leetcode.cn…

<数据结构>NO7.二叉树(附Oj练习题)

&#x1f447;二叉树的完整代码在&#x1f447; syseptember的gitee仓库&#xff1a;二叉树https://gitee.com/syseptember/data-structure/tree/1513789167062c75dc172366199ce7a6b0577cc7/BinaryTree2/BinaryTree2 目录 树的概念及结构 0x01.树的概念 0x02.树的相关概念 …

快速定位接口问题,JMeter方法论,跨线程组接口关联测试!

目录 【前言】 【步骤】 1. 准备工作 2. 创建线程组 3. 创建HTTP请求 4. 添加正则表达式提取器 5. 添加HTTP请求关联控制器 6. 设置断言和验证器 7. 运行测试 【代码】 【结论】 【前言】 在进行接口测试时&#xff0c;接口之间的关联经常是我们需要重点测试的点之…

如何手写网络协议栈

哈喽&#xff0c;我是子牙&#xff0c;一个很卷的硬核男人。喜欢研究底层&#xff0c;聚焦做那些大家想学没地方学的课程&#xff1a;手写操作系统、手写虚拟机、手写编程语言…目前做了两门课&#xff1a;手写OS、手写JVM 今天想跟大家聊一个黑科技&#xff1a;手写网络协议栈…

Python的编码规范

目录 1、每个 import 语句只导入一个模块&#xff0c;尽量避免一次导入多个模块&#xff0c; 2、不要在行尾添加分号&#xff0c;也不要用分号将两条命令放在同一行&#xff0c; 3、建议每行不超过 80 个字符&#xff0c; 4、使用必要的空行可以增加代码的可读性 5、通常情…

让百万大学生崩溃的在线OJ,如何破局?

目录 一、在线OJ的的原理 二、在线OJ的使用规则 三、注意事项 1.关于作弊 2.如何防止作弊 3.输入输出格式 4.换行问题 四、经典在线OJ坑人题目以及博主被坑经历 五、提交不成功及解决方法 六、如何得心应手的拿下OJ系统 七、在线OJ的骗分技巧 在线OJ&#xff08;Onl…

OpenGL简介

1.简介 一般它被认为是一个API&#xff0c;包含了一系列可以操作图形、图像的函数。然而&#xff0c;OpenGL本身并不是一个API&#xff0c;它仅仅是一个由Khronos组织制定并维护的规范(Specification)。OpenGL规范严格规定了每个函数该如何执行&#xff0c;以及它们的输出值。…

为何要用分布式锁Redis实现分布式锁

为何要用分布式锁 一、为什么要使用分布式锁 为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行&#xff0c;在传统单体应用单机部署的情况下&#xff0c;可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。但是&#xff0c;随着业务…

《论文阅读》连续前缀提示Prompt:table-to-text和摘要生成 ACL2021

《论文阅读》连续前缀提示Prompt:table-to-text和摘要生成 ACL2021 前言相关知识Table-to-Text Generation自编码语言模型自回归语言模型简介任务定义部分参数更新代码实验结果前言 你是否也对于理解论文存在困惑? 你是否也像我之前搜索论文解读,得到只是中文翻译的解读后…

高级算法工程师的工作职责(合集)

高级算法工程师的工作职责1 职责&#xff1a; 1、调研跟踪智能穿戴产品相关算法&#xff0c;为公司产品决策提供参考; 2、设计开发智能穿戴产品的计步、睡眠、心率、血氧饱和度、血压、心率变异性等相关参数的提取算法; 3、撰写算法开发文档&#xff0c;包括算法流程、测试方案…

小黑西安归来,政审完毕,眼睛手术做完一直在家躺着第5天的leetcode之旅:852. 山脉数组的峰顶索引

小黑代码 class Solution:def peakIndexInMountainArray(self, arr: List[int]) -> int:# 数组长度n len(arr)# 寻求山峰for i in range(n-1):if arr[i1] < arr[i]:return ireturn -1小黑二分模版 class Solution:def peakIndexInMountainArray(self, arr: List[int])…

Linux——内存和DMA

目录 本章目标&#xff1a; 一、内存组织 二、按页分配内存 三、slab分配器 四、不连续内存页分配 五、per-CPU变量 本章目标&#xff1a; 在前面的所有例子中&#xff0c;我们使用的都是全局变量或在栈上分配的内存。本章我们将先讨论如何动态分配内存和per-CPU变量。类…

实战-k8s中部署tomcat(四)

先下载资源&#xff1a;k8s中安装Tomcat 测试 k8s 集群中部署 tomcat 服务 #把 tomcat.tar.gz 上传到 k8s-node1&#xff0c;手动解压 [rootk8s-node1 ~]# ctr images import tomcat.tar.gz [rootk8s-master ~]# kubectl apply -f tomcat.yaml apiVersion: v1 #pod属于k8…

Augmentation Matters:一种简单而有效的半监督语义分割方法(CVPR2023)

文章目录 Augmentation Matters: A Simple-yet-Effective Approach to Semi-supervised Semantic Segmentation摘要本文方法Random Intensity-based AugmentationsAdaptive Label-aided CutMix 实验结果 Augmentation Matters: A Simple-yet-Effective Approach to Semi-superv…

迅为RK3568开发板2800页手册+220集视频

iTOP-3568开发板采用瑞芯微RK3568处理器&#xff0c;内部集成了四核64位Cortex-A55处理器。主频高达2.0Ghz&#xff0c;RK809动态调频。集成了双核心架构GPU&#xff0c;ARM G52 2EE、支持OpenGLES1.1/2.0/3.2OpenCL2.0、Vulkan 1.1、内高性能2D加速硬件。 内置NPU 内置独立NP…

港联证券|AI概念股继续活跃 科创50指数逆势走高

周三&#xff0c;A股市场出现极致分化态势。得益于存储芯片为代表的硬科技股的强势&#xff0c;科创50指数逆势走高。但创业板指、深证成指等主要股指仍然跌跌不休&#xff0c;沪指险守3200点关口。AI概念股继续逆势活跃&#xff0c;国资云、数据方向领涨&#xff0c;算力概念股…

坂田杨美自然村旧改,已立项,直接签约。

这个将由佳兆业与信义地产联合改造的坂田杨美自然村城市更新&#xff0c;已立项测绘&#xff0c;也公布了草案和村屋认定范围&#xff0c;正启动签约收楼中。名校附近的改造&#xff0c;深圳实验学校坂田校区附近&#xff0c;项目地处杨美地铁附近&#xff0c;还有佳兆业上品雅…

【企业化架构部署】Nginx网站服务

文章目录 一、Nginx 概述1.什么是 Nginx2.Nginx 的特点3.Nginx 应用场景 二、Nginx 服务基础1.编译安装 Nginx 服务1.1 布置环境1.2 安装依赖包1.3 创建运行用户、组1.4 编译安装 2.Nginx 的运行控制2.1 检查配置文件2.2 启动、停止 Nginx2.3 日志分割以及升级 Nginx 服务2.4 添…

39 KVM管理设备-管理快照

文章目录 39 KVM管理设备-管理快照39.1 概述39.2 操作步骤 39 KVM管理设备-管理快照 39.1 概述 虚拟机在使用过程中可能由于病毒对系统的破坏、系统文件被误删除或误格式化等原因造成虚拟机系统损坏导致系统无法启动。为了使损坏的系统快速恢复&#xff0c;openEuler提供了存…