C++语法系列之右值

article/2025/6/22 1:46:40

前言

本来是想在C++11里写这篇文章的,发现东西很多,就单独列一篇文章了,
右值这个概念是在C++11中提出来的,以前只有左值和左值引用的概念,C++11后提出了右值和右值引用,为什么要提出右值和右值引用?请看讲解


一、什么是右值?

那先想想什么是左值?----出现在等号左边的一定是左值,出现在等号右边的不一定是左值

int p = 5;
int a = p;
int *pa = &a;
int *pb = new int(1);
const int& paa = 5;
//以上的变量都是左值
delete pb;

什么是右值?第一种理解,不能被改变的值,因为他无法出现在等号左边(玩一点花活是可以的,详见最后一条,但是通常这么说);第二种理解:是一种数据的表达式,如常量,表达式的返回值等等,如

10;
double x = 5,y = 6;
x + y;
int func(){return 1;
}

这里的10,x + y,func()都是右值,
有没有左值和右值明显的区分方法?
有的兄弟有的
能取地址的一定是左值,取不了地址的一定是右值
!!!!!!!!!!!!!
注意:这里说的都是一定,不存在特殊情况
在这里插入图片描述
右值分为两种!!!!!!
1.纯右值,表示一个临时对象或者字面值常量
2.将亡值,表示一个即将被释放资源的对象,一般是右值引用或者move()操作,记住这个将亡值,后面会提到
C++文档

二、右值引用

什么是左值引用?这个很熟悉了,用的也很多,不过多解释。
右值引用就是对右值引用,给右值取别名

double x = 5,y = 6;
int && pa = 5;
int && pb = (x + y);

这里的pa和pb都是对右值的引用,那pa,pb是左值还是右值?
在这里插入图片描述
是左值(埋个坑,后面过来填)
//右值取别名后,可以修改

int main()
{double x = 1, y = 2;int&& rr1 = 10;rr1 = 20;return 0;
}

“左值持久,右值短暂”
右值引用只能绑定到临时对象,我们知道:所引用的对象将要被销毁,并且该对象没有其他用户

这两个特性意味着:使用右值引用的代码可以自由的接管所引用的对象的资源(好好看看,为了后面的移动构造埋伏笔)

三、左值引用和右值引用的联系与作用、移动构造、移动赋值

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!比较重要
左值引用可以引用右值-----加const,防止权限的扩大
右值引用也可以引用左值--------使用move(),但有可能会出问题(后面解释)
在这里插入图片描述
在这里插入图片描述
注意:虽然规则在这,但是权限问题还是别瞎玩
比如:

const int a = 5;
int &&pb = move(a);

左值引用和右值引用的作用!!!!!!!!!!!!!!
左值引用的作用?
减少拷贝,增加效率
1.作函数参数.2.作返回值
比如经常写的重载流插入和流提取

ostream& operator<<(ostream& os);

左值引用这么全面为什么要实现右值引用?那左值引用当然是有缺点的了,他没有解决返回临时变量的问题,如果函数返回值是一个临时变量,出了作用域就销毁了,那还能返回左值引用吗?当然不能,只能传值返回,传值返回就要发生拷贝,减少效率,如果是一个int类型还好,如果是vector<vector< int >> func()这种呢?
所以右值引用就是来解决这个问题的。

string func()
{string a("aaaaaaaaaaaaaaaaa")return a;
}
int main()
{string it = func();return 0;
}

这里正常来说是不是两次拷贝构造?返回一个s的临时对象—第一次,构造it—第二次,但是编译器会对其进行优化,连续的构造+拷贝构造会优化成一次,那我们怎么看呢?只能自己搓一个string了。

namespace myspace
{class string{friend std::ostream& operator<<(std::ostream& os, const myspace::string& s);public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){//cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity;};
}
myspace::string func()
{myspace::string a("aaaaaaaaaaaaaaaaa");return a;
}
int main()
{myspace::string it = func();return 0;
}

一输出????????????怎么啥也没输出,欸你是不是搁这瞎讲啊,别急,这里是NRVO的缘故,当时我也到处问了好久,有一个大佬找的一篇文章很好
建议看下面这篇文章:
NRVO
简单来说就是,我这本来出了func()函数a就销毁了,还得进行两次拷贝构造才到it,那我为什么不直接构造it呢?所以:编译器进行了优化,相当于直接构造it,不经过任何函数
这就是什么都不输出的原因了。
那我就想看怎么办呢?
建议进入DevC++;
点工具->编译选项->在编译时加入下面命令换成这句话-fno-elide-constructors
这样就能看到有两次深拷贝了,这里随着编译器的优化还是减少了很多拷贝的,
有点小跑题,回到右值引用的作用上来,上面讲了右值引用是为了减少拷贝,那怎么减少呢?看原来左值的拷贝构造怎么实现的,搞一个string tmp,然后swap,----深拷贝,那这里的a是不是出了作用域就被销毁了?那我直接把资源“窃取”过来不好吗,还搞什么深拷贝–这里形象的称为移动构造(与拷贝构造相对应),就是不进行深拷贝,直接把数据移动过来,提高效率

string(string&& s):_str(nullptr),_size(0),_capacity(0)
{cout << "string(string&& s) " << endl;swap(s);(这里的s是左值还是右值?好好想想后面会提到)
}

此时再运行代码(还是要关NRVO,不然还是直接构造了)
在这里插入图片描述
这里细心的人可能会有疑问,为什么不是拷贝构造 + 移动构造?这a不是左值吗?
a确实是左值,但是a出了func()是不被销毁了?所以编译器进行优化,与其返回a的构造,直接当作右值调用移动构造很爽啊。
既然有移动构造,就会有移动赋值

 string& operator=(string&& s){cout << "string& operator=(string&& s)" << endl;swap(s);return *this;}

这样是不是很好啊
来的是个左值,就去正常深拷贝,这是避免不了的,来的是个右值,我直接移动赋值,将资源窃取过来,就不用拷贝了。

STL里的一些函数比如说push_back,insert在C++11后都提供了右值引用的接口,也是为了减少拷贝,增加效率
在这里插入图片描述
tips:区分移动和拷贝的重载函数通常有一个版本接受const T&,而另一个版本接受一个T&&.

四、noexcept

noexcept是异常学的,回忆一下noexcept,是我们承诺一个函数不抛出异常的方法,和这有什么关系呢?
注意:移动操作“窃取”资源,它通常不分配任何资源,所以,移动操作通常不会抛出异常
tips:不抛出异常的移动构造函数和移动赋值运算符必须标记为noexcept
为什么?
举一个例子:在标准库中的vector,如果调用push_back产生异常,自身的行为没有发生改变,push_back可能会使vector重新分配空间,但是到了自己写,如果重新分配的过程中使用了移动构造,移动了一部分后抛出了一个异常,是不就出问题了?旧空间中的移动源元素已经被改变,而新空间中没构造的元素可能尚且不存在,vector本身是不就变了?这与库里的不符合,所以要标记为noexcept

五、move

上面讲了右值引用可以引用左值----加上move,被move的对象有什么变化呢?可以猜到move—移动,就是把这个对象给移动了,
注意1.不要对常量变量进行move
在这里插入图片描述
2.不要使用move返回局部变量
在这里插入图片描述
所以move干啥了呢?通过一段代码演示一下
在这里插入图片描述
和我们猜想的一样,把资源“移动”了,所以被move的对象就会被置空,所以move确实可以加快效率,但是要确保安全,看下面这个图片
在这里插入图片描述

六、完美转发

官方解释
好高级的名字
先看一段代码

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}
int main()
{PerfectForward(10);           // 右值int a;PerfectForward(a);            // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b);      // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}

模板中的既有左值传过来,又有右值传过来,那我怎么区分呢?所以,我们规定:
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
但是如果不使用完美转发,t的属性还是左值,会退化回去,看运行结果
在这里插入图片描述
密码的怎么全给我干左值上了,其实对上面细心看的人可以发现问题了,这里的t可以取地址,这t就是左值啊,那我右值不是被吞了,所以C++11搞出了完美转发来处理这种情况,使用forward关键字
在这里插入图片描述
通过这个就可以看出怎么写了Fun(forward<T>(t));
forward< T >(t)在传参的过程中保持了t的原生类型属性。
在这里插入图片描述
比如自己实现vector / list等等的时候,要想保证使用原生属性就要使用forward

七、类中默认生成的移动构造和移动赋值函数

先看结论:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。(移动赋值同理)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

class X {int i;//内置类型可以移动std::string s;//string定义了自己的移动操作
};
class hasX {X mem; //X有合成的移动操作
};
int main()
{X x,x2 = std::move(x);//使用合成的移动构造函数hasX hx, hx2 = std::move(hx);//使用合成的移动构造函数return 0;
}

在这里插入图片描述
上面的可以自行验证
只有当一个类没有定义任何自己版本的拷贝控制成员,且它的所有数据成员都能移动构造或移动赋值时,编译器才会为它合成移动构造函数或移动赋值运算符。

default(和delete区分开)
作用:假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成
比如上面的移动构造,就可以声明为default
在这里插入图片描述
关于删除的一些要点:
1.与拷贝构造函数不同,移动构造函数被定义为删除的函数的条件是:有类成员定义了自己的拷贝构造函数且未定义移动构造函数,或者是有类成员未定义自己的拷贝构造函数且编译器不能为其合成移动构造函数。移动赋值运算符的情况类似。
2.如果有类成员的移动构造函数或移动赋值运算符被定义为删除的或是不可访问的,则类的移动构造函数或移动赋值运算符被定义为删除的。
3.类似拷贝构造函数,如果类的析构函数被定义为删除的或不可访问的,则类的移动构造函数被定义为删除的。
4.类似拷贝赋值运算符,如果有类成员是 const 的或是引用,则类的移动赋值运算符被定义为删除的。
!!!!!!!!!!!!!!!
这里面说了这么多法则那我怎么记呢?
看下面

八、三五法则

所有五个拷贝控制成员(拷贝构造,赋值运算符重载,析构函数,移动赋值,移动构造)应该看作一个整体:一般来说,如果一个类定义了任何一个拷贝操作,他就应该定义所有五个操作,某些类必须定义拷贝构造函数、拷贝赋值运算符和析构函数才能正确工作,这些类通常具有一个资源,而拷贝成员必须拷贝此资源,一般来说,拷贝一个资源会导致一些额外开销,在这种拷贝并非必要的情况下,定义了移动构造函数和移动赋值运算符的类就可以避免此问题。
简单来说:如果只是个简单的日期类,这五个成员也没必要写,编译器自己生成的就够用,如果像string这种,当然每个就得实现,讲深浅拷贝的时候就有这个要点

九、右值和左值引用成员函数

先看这段代码

int main()
{string s1 = "a value", s2 = "another";auto n = (s1 + s2).find('a');s1 + s2 = "wow";return 0;
}

在这里插入图片描述
这种是不很怪异的用法啊,右值是不也出现在了左边啊,为了维护向后的兼容性,新标准库类仍然允许向右值赋值,但是我们不希望在自己的类中出现这种用法,我们希望强制左侧操作对象是一个左值,所以C++里用一种方式与const类似
即:在参数列表后放置一个引用限定符

class Foo {
public:Foo& operator= (const Foo&other);Foo& retFoo();//调用是一个左值Foo retval();//调用是一个右值};
Foo& Foo::operator=(const Foo& other) {//....return *this;
}
int main()
{Foo i, j;i = j;i.retFoo() = j;i.retval() = j;return 0;
}

在这里插入图片描述
经过&限定的函数,我们只能将他用于左值,对于&&限定的函数,只能用于右值;

一个函数可以同时用const和引用限定,此情况下,引用限定符必须跟在const之后,另外,它就像一个成员函数一样可以根据是否有const来区分重载
比如:

class Foo {
public:Foo(vector<int>&da) {for (int i = 0; i < da.size(); ++i)data.push_back(da[i]);}Foo sorted()&&;Foo sorted()const &;Foo& operator= (const Foo&other)&;Foo& retFoo() {return *this;}//调用是一个左值Foo retval() {Foo ret(this->data);return ret;}//调用是一个右值
private:vector<int> data;
};
Foo Foo::sorted()&& {sort(data.begin(), data.end());cout << "Foo Foo::sorted()&&" << endl;for (const auto& e : data){cout << e << " ";cout << endl;}return *this;
}
Foo Foo::sorted()const & {Foo ret(*this);sort(ret.data.begin(), ret.data.end());cout << "Foo Foo::sorted()const &" << endl;for (const auto& e : ret.data){cout << e << " ";cout << endl;}return ret;
}int main()
{vector<int>v1({ 3,1,2 }), v2({ 1,2,3 });Foo i(v1), j(v2);i.retval().sorted();cout << endl;i.retFoo().sorted();return 0;
}

这里的sorted()& 和 sorted()const& 构成了重载,编译器会根据调用sorted的对象的左值或者右值属性来区分哪个使用sorted版本,看运行结果
在这里插入图片描述
当我们对一个右值进行sorted的时候,它可以安全的直接排序,对象是一个右值,意味着没有其他用户,因此我们可以改变对象,当对一个const右值或者一个左值进行sorted的时候,我们不能改变对象,所以需要拷贝data
注意:当我们定义const成员时,可以定义两个版本,一个有一个没有,引用限定则不一样,如果我们想重载多个函数,就必须对所有函数都加上引用限定符,或者都不加

class Foo {
public:Foo sorted()&&;Foo sorted()const;//errorusing Comp = bool(const int&, const int&);Foo sorted(Comp*);//正确,不同的参数列表Foo sorted(Comp*)const;//正确,两个版本都没有引用限定符
};

在这里插入图片描述

//这样会发生什么呢?自己去验证,还是很好想的

Foo Foo::sorted()const& {Foo ret(*this);return ret.sorted();
}

总结

。。。。。。。。。。。。。。。。。。。。。。。。。。。累死了,一个右值写了快五个小时,这个真的走心了,查阅了很多资料,经过理解才写出来的,(这个法则以及很多看着就很正式的话也不是我编的,都是来自权威著作上的)。


http://www.hkcw.cn/article/DKaiUgxHIH.shtml

相关文章

day17 常见聚类算法

目录 准备操作 聚类评估指标介绍 1.轮廓系数&#xff08;Sihouette Score&#xff09; 2.CH指数&#xff08;Calinski-Harabasz Index&#xff09; 3.DB指数&#xff08;Davies-Bounldin Index&#xff09; KMeans聚类 算法原理 确定簇数的方法&#xff1a;肘部法 KMeans算法的…

LCS 问题解释

最长公共子序列问题&#xff08;Longest Common Subsequence&#xff09;&#xff0c;该问题可以表述为&#xff0c;在 A , B A,B A,B 中找出一段子序列 x x x&#xff0c;使得 x x x 既是 A A A 的子序列&#xff0c;又是 B B B 的子序列。 你可以理解为&#xff0c;在两…

Windows最快速打开各项系统设置大全

目录 一、应用背景 二、设置项打开方法 2.1 方法一界面查找&#xff08;最慢&#xff09; 2.2 方法二cmd命令&#xff08;慢&#xff09; 2.3 方法三快捷键&#xff08;快&#xff09; 2.4 方法四搜索栏&#xff08;快&#xff09; 2.5 方法五任务栏&#xff08;最快&am…

OTSU算法原理与Python实现:图像二值化的自动阈值分割

1 引言 图像二值化是计算机视觉中的基础操作&#xff0c;它将灰度图像转换为黑白图像&#xff0c;常用于文档扫描、目标检测等任务。OTSU算法&#xff08;大津法&#xff09;是一种自动确定二值化阈值的算法&#xff0c;无需人工干预&#xff0c;通过最大化类间方差来分离前景和…

python:批量创建文件

#需求&#xff1a;在指定路径下批量创建3000#可以先弄个10个文本文件&#xff0c;文件格式为序号——物资类别——用户识别码组成 #1.序号从0001到3000 #2.物资类别包括&#xff1a;水果&#xff0c;烟酒&#xff0c;粮油&#xff0c;肉蛋&#xff0c;蔬菜 #3.用户识别码为9位的…

kafka学习笔记(三、消费者Consumer使用教程——配置参数大全及性能调优)

本章主要介绍kafka consumer的配置参数及性能调优的点&#xff0c;其kafka的从零开始的安装到生产者&#xff0c;消费者的详解介绍、源码及分析及原理解析请到博主kafka专栏 。 1.消费者Consumer配置参数 配置参数默认值含义bootstrap.servers无&#xff08;必填&#xff09;…

静态综合实验

题目 1.划分IP地址 因为所有网段基于192.168.1.0/24&#xff0c;所以需要自己进行合理的划分。如图&#xff0c;我已经划分完成。 2.启动 3.给五个路由器进行改名 4.给网关写入IP地址 R1 R2 R3 R4 5.完成网段的声明和环回接口的创建 6.在R1上进行ping&#xff0c;观察是否…

流媒体基础解析:音视频封装格式与传输协议

在视频处理与传输的完整流程中&#xff0c;音视频封装格式和传输协议扮演着至关重要的角色。它们不仅决定了视频文件的存储方式&#xff0c;还影响着视频在网络上的传输效率和播放体验。今天&#xff0c;我们将深入探讨音视频封装格式和传输协议的相关知识。 音视频封装格式 什…

保持本地 Git 项目副本与远程仓库完全同步

核心目标&#xff1a; 保持本地 Git 项目副本与 GitHub 远程仓库完全同步。 关键方法&#xff1a; 定期执行 git pull 命令。 操作步骤&#xff1a; 进入项目目录&#xff1a; 在终端/命令行中&#xff0c;使用 cd 命令切换到你的项目文件夹。执行拉取命令&#xff1a; 运行…

Go语言的context

Golang context 实现原理 本篇文章是基于小徐先生的文章的修改和个人注解&#xff0c;要查看原文可以点击上述的链接查看 目前我这篇文章的go语言版本是1.24.1 context上下文 context被当作第一个参数&#xff08;官方建议&#xff09;&#xff0c;并且不断的传递下去&…

2025年全国青少年信息素养大赛复赛C++算法创意实践挑战赛真题模拟强化训练(试卷3:共计6题带解析)

2025年全国青少年信息素养大赛复赛C++算法创意实践挑战赛真题模拟强化训练(试卷3:共计6题带解析) 第1题:四位数密码 【题目描述】 情报员使用4位数字来传递信息,同时为了防止信息泄露,需要将数字进行加密。数据加密的规则是: 每个数字都进行如下处理:该数字加上5之后除…

NeRF PyTorch 源码解读 - 体渲染

文章目录 1. 体渲染公式推导1.1. T ( t ) T(t) T(t) 的推导1.2. C ( r ) C(r) C(r) 的推导 2. 体渲染公式离散化3. 代码解读 1. 体渲染公式推导 如下图所示&#xff0c;渲染图像上点 P P P 的颜色值 c c c 是累加射线 O P → \overrightarrow{OP} OP 在近平面和远平面范围…

Sentiment analysis integrating LangGraph and large-scale conceptual models

Sentiment analysis integrating LangGraph and large-scale conceptual models 核心目标&#xff1a; 让电脑更聪明地理解大量用户评论&#xff08;比如邮件、社交媒体、调查问卷&#xff09;&#xff0c;自动分析出大家是夸还是骂&#xff08;情感分析&#xff09;&#xff…

DeepSeek R1-0528:深度思考能力的重大跃升与技术突破全解析

引言 2025年5月28日&#xff0c;DeepSeek再次以其标志性的"深夜发布"方式&#xff0c;悄然推出了R1模型的最新版本——DeepSeek-R1-0528。这次被官方定义为"小版本升级"的更新&#xff0c;实际上带来了令人瞩目的性能提升。新版本不仅在数学、编程与通用逻…

Python 训练营打卡 Day 40

训练和测试的规范写法 一、黑白图片的规范写法&#xff0c;以MNIST数据集为例 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms # 用于加载MNIST数据集 from torch.utils.data import DataLoader # 用于创建…

题海拾贝:P8598 [蓝桥杯 2013 省 AB] 错误票据

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》 欢迎点赞&#xff0c;关注&#xff01; 1、题…

AI炼丹日志-26 - crawl4ai 专为 AI 打造的爬虫爬取库 上手指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇&#xff1a; MyBatis 更新完毕目前开始更新 Spring&#xff0c;一起深入浅出&#xff01; 大数据篇 300&#xff1a; Hadoop&…

homework 2025.03.31 chinese(class 3)

homework 2025.03.31 chinese&#xff08;class 3&#xff09; 三年级语文&#xff0c;古代十二时辰 ➠1. 子时&#xff08;23-1时&#xff09; “月落乌啼霜满天&#xff0c;江枫渔火对愁眠。姑苏城外寒山寺&#xff0c;夜半钟声到客船。” — 张继《枫桥夜泊》 子时夜深人静&…

若依框架定制化服务搭建

1.背景 若依框架是1套微服务框架&#xff0c;该服务在应用过程中少不了新增微服务来应对业务的需求&#xff0c;本次文档主要是针对若依框架的定制化微服务的搭建进行步骤的拆解。 2.ruoyi-api模块新建模块【report】 2.1 右键【ruoyi-api】&#xff0c;New一个Module 2.2 新…

【HW系列】—溯源与定位—Linux入侵排查

文章目录 一、Linux入侵排查1.账户安全2.特权用户排查&#xff08;UID0&#xff09;3.查看历史命令4.异常端口与进程端口排查进程排查 二、溯源分析1. 威胁情报&#xff08;Threat Intelligence&#xff09;2. IP定位&#xff08;IP Geolocation&#xff09;3. 端口扫描&#x…