96SEO 2026-02-19 21:15 44
C11作为一次重大的更新引入了许多革命性的特性其中之一便是右值引用和移动语义。

本文将深入探讨其中引入的右值及其相关概念帮助读者更好地理解这一特性从而在编程实践中更有效地利用它。
C98中有引用的用法而C11中新增了右值引用的语法特性之前学习的引用都是左值引用。
不管是左值引用还是右值引用都是给对象取别名。
那什么左值和左值引用还有右值和右值引用
具有固定的地址可以获取它们的地址。
可以出现在赋值符号的左侧对它进行赋值。
当用const修饰左值时不能进行赋值但是可以取地址。
下面的例子中有整型变量x指针变量ptrconst修饰下整型变量y字符串类变量s。
上面的变量都是左值都可以取地址。
右值是一个表示数据的表达式如字面常量、临时对象、表达式返回值、函数返回值左值引用类型返回除外。
没有固定的内存地址不能够获取其地址。
可以出现在赋值符号的右边但是不能出现在赋值符号的左边没有。
左值引用一般情况下只能引用左值。
但加上const修饰之后左值引用可以引用左值还可以引用右值。
字面常量15被引用为整型变量时需要转换类型中间会产生临时变量临时变量具有常性。
如果使用普通引用可以对此引用进行修改会导致权限放大。
string(1111111);//const修饰后可以引用右值const
右值引用一般情况下只能引用右值。
但是右值引用可以引用move之后的左值。
既然已经有左值引用为什么还要搞出一个右值引用的概念这是为什么呢下面是用C简单实现的string类。
如果对字符串类不熟悉可以转到这篇文章http://t.csdnimg.cn/Znclr。
构造函数拷贝构造函数和赋值重载函数内部都有打印函数原型方便查看函数调用情况。
2;reserve(newcapacity);}_str[_size]
左值引用给左值取别名。
传递函数参数或者返回函数值时可以减少拷贝提高效率。
TestNonLeftRef函数参数类型是string类函数参数是实参的临时拷贝如果是自定义类型会调用相应的拷贝构造创建新的变量。
而TestLeftRef函数参数是string类引用相当于给传过来的参数取别名不会调用拷贝构造。
operator[]函数返回值类型是字符类型的引用对字符进行取别名不用进行拷贝。
s(111111);TestNonLeftRef(s);TestLeftRef(s);Rustle::string
s1(xxxxxx);//operator[]返回值类型是//Rustle::char
运行结果如下main函数中创建s变量时调用了构造函数。
接受参数为s的两个函数只有TestNonLeftRef调用了拷贝构造。
最后一个构造是创建s1变量调用的。
当函数返回的值是一个函数内的局部变量时局部变量出了函数作用域就会被销毁,无法使用左值引用返回只能进行传值返回。
如上面代码所示str是一个局部变量出作用域就销毁只能使用传值返回。
下图中一般情况下str变量传值返回时会拷贝此变量来创建一个临时变量如果是自定义类型就会调用拷贝构造函数。
s1会调用拷贝构造函数拷贝临时变量
但是某些编译器进行优化之后只用进行一次拷贝构造函数不产生中间的临时变量。
不过VS2022编译器对这个场景优化的十分厉害。
下面是运行结果示意图只调用了一次构造函数。
说明编译器已经识别s1要使用GetStr返回值进行构造。
相当于str跟s1是同一个变量。
运行结果如下调用了两次构造函数和一个赋值重载函数。
mian函数内创建一个string类对象GetStr函数内创建了一个string类对象。
在返回该对象时本来会产生一个临时对象调用一个拷贝构造不过编译器优化掉这一步骤直接调用赋值重载函数。
通过上面的讲述我们知道左值引用不能解决传值返回的场景大部分编译器起码至少会调用一次拷贝构造这样会降低运行效率。
右值引用的出现就是为了解决这种场景。
右值分为纯右值和将亡值。
其中将亡值指的是那些即将被销毁的对象函数返回值就是将亡值。
右值引用可以引用将亡值虽然左值引用不能当做返回类型但是右值引用可以当做返回类型。
既然右值引用左为函数返回类型string类就要重载一份右值引用版本的拷贝构造函数和赋值重载函数。
这类函数分别叫做移动构造函数和移动赋值函数。
如下面代码移动构造函数的本质是将右值的资源转移或者叫做“窃取”。
这样就不用做深拷贝了所以叫做移动构造就是转移别人的资源进行构造。
前面我们提到右值引用无法直接引用左值但是move函数可以将左值转换成右值从而达到左值被引用。
std::move函数实际上对左值没有转移任何资源只是将左值强制转化成右值。
如下面的代码string类对象正常使用同类进行拷贝构造不会影响被拷贝的对象。
但是被拷贝对象被move之后强制转化成右值就会调用移动版本的构造窃取s1的资源将s1置空。
s1(xxxxxx);//调用普通构造函数Rustle::string
s2(s1);s1[0];//识别s1为右值调用移动构造会转移s1的资源来构造s3//那么s1就被置空了无法用[]访问Rustle::string
左值引用和右值引用本质上都是给变量取别名。
我们看下面的代码s1为左值通过强制转换成右值。
匿名对象本身为右值也可以通过强制转化成左值。
s1(1111111);func(s1);//强制转换成右值func((Rustle::string)s1);//右值func(Rustle::string(11111111));//强制转换成左值func((Rustle::string)Rustle::string(11111111));return
下面是用C简单实现的list容器只包含插入函数相关的部分其他接口函数已经省略。
并且还提供了右值引用版本的插入函数。
如果对list容器不熟悉可以看这篇文章http://t.csdnimg.cn/WWsBs。
T())//匿名对象,调用的是默认构造:_next(nullptr),_prev(nullptr),_data(data){}};templateclass
iterator(newnode);}private:Node*
我们尝试运行下面的代码。
我把代码分为五个部分每个部分都有对应的解释。
{//1.创建一个list类变量ltRustle::listRustle::string
s1(1111111111111111);lt.push_back(s1);//3.匿名对象lt.push_back(Rustle::string(1111111111111111));//4.字符串类隐式类型转换为string类lt.push_back(1111111111111111);//5.通过move函数强制转化s1为右值lt.push_back(move(s1));return
运行结果如下。
第一个string类的构造函数和拷贝构造函数是创建list容器变量lt调用的。
因为list容器有个哨兵位结点内部不存储有效数据用来简化list的插入和删除工作。
list的默认构造函数会开辟一个ListNode类的结点ListNode类的构造函数是全缺省的如果没传参数会使用缺省值缺省值是调用string类的默认构造函数。
参数列表中拷贝data创建ListNode的_data变量会调用拷贝构造函数。
T())//匿名对象,调用的是默认构造:_next(nullptr),_prev(nullptr),_data(data){}list(){_head
正常来说序号3之后的代码中push_back参数都是右值应该调用string类中的移动构造才对但是全部调用的是拷贝构造进行深拷贝。
这是为什么呢
{TestRightRef(Rustle::string(111111111111111));Rustle::string
Rustle::string(1111111111111111);cout
endl;//通过测试发现右值引用本身属性是左值那为什么会是左值呢//因为只有右值引用本身的属性是左值才能传递参数转移他的资源return
观察运行结果发现右值引用本身可以取地址说明右值引用本身属性是左值。
第一种是传递右值参数TestRightRef函数用右值引用接受可以取地址。
第二种是直接对右值引用。
这两种方式都会导致右值被引用之后退化成左值。
只有右值引用本身的属性是左值的情况下才可以进行赋值做函数参数和做函数返回值的操作。
当使用push_back函数插入右值对象时会调用移动插入函数。
在移动插入函数中右值引用参数x本身属性已经退化成左值。
而移动插入函数主要是复用了insert函数完成尾插的操作虽然重载了右值引用版本的insert函数但是由于x属性为左值还是会调用到左值引用版本的insert函数。
T())//匿名对象,调用的是默认构造:_next(nullptr),_prev(nullptr),_data(data){}};templateclass
那我们该怎么解决呢根据右值被引用之后属性退化成左值的问题每次函数使用右值引用参数接收右值后如果需要使用右值引用就必须使用move函数改变右值引用的属性。
那么list容器的push_back函数中需要加上move函数insert函数中也要加上move函数ListNode的构造函数也需要提供一个右值引用版本的。
T())//匿名对象,调用的是默认构造:_next(nullptr),_prev(nullptr),_data(data){}ListNode(T
data)//右值版本:_next(nullptr),_prev(nullptr),_data(move(data)){}};templateclass
前面提到右值引用的属性是左值所以一旦要使用右值引用需要使用move函数强制转换属性。
如list的push_back函数一层一层传递右值每一层都要重载左值引用和右值引用两个版本的函数十分麻烦。
有什么办法可以解决呢那就要介绍C11引入的新特性——完美转发。
Forwarding是C11中引入的一个特性它允许在函数模板中将参数连同其类型信息一起不变地传递给其他函数。
这意味着无论是左值引用还是右值引用都能保持其原有的引用类型在传递过程中不会意外地变成左值引用。
下面的代码就是使用完美转发的效果对比。
其中模版中的不代表右值引用而是万能引用其既能接收左值又能接收右值。
模版函数可以接收左值和右值不过第一个模版函数没有使用完美转发第二个函数使用了完美转发。
{//模版实例化是左值引用保持属性直接传参给Fun//模版实例化是右值引用右值引用属性会退化成左值转换成右值属性在传参给FunFun(forwardT(t));
左值NonPerfectForward(std::move(a));
左值NonPerfectForward(std::move(b));
左值PerfectForward(std::move(a));
左值PerfectForward(std::move(b));
运行结果如下第一个模版函数接收右值后右值引用属性退化成左值调用的还是左值引用类型的函数。
第二个函数使用了完美转发如果模版实例化是左值引用保持属性直接传参给Fun如果实例化后是右值引用会转换成右值属性在传参给Fun。
经过长篇累牍的讲解相信大家对右值引用和移动语义的概念有了初步的认识。
通过对这些特性的学习我们可以编写出更加高效和精炼的代码。
如果亲自上手敲写上述示例代码会有更加深刻的理解。
创作不易希望这篇文章能给你带来启发和帮助如果喜欢这篇文章请留下你的三连你的支持的我最大的动力
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback