C++11 面试题插入(左值引用和右值引用的区别)移动构造和移动赋值C++新标准如何解决传值返回但对象销毁问题

C++11 面试题插入(左值引用和右值引用的区别)移动构造和移动赋值C++新标准如何解决传值返回但对象销毁问题

🎬 胖咕噜的稞达鸭个人主页

🔥 个人专栏: 《数据结构《C++初阶高阶》《算法入门》

⛺️技术的杠杆,撬动整个世界!


在这里插入图片描述


在这里插入图片描述

列表初始化

  1. 内置类型初始化
int x{2};int x1=2;
  1. 自定义类型初始化
  • 2.1 直接构造
    本质是先构造一个Date临时对象,
    再拷贝构造d1;但编译器会优化这个过程,直接用列表参数构造d1(即不会调用拷贝构造函数)。
//2.自定义类型(类)的列表初始化// 2.1可以直接构造,本质是先构造一个Date临时对象,// 再拷贝构造d1;但编译器会优化这个过程,直接用列表参数构造d1(即不会调用拷贝构造函数)。 Date d1 ={2025,11,01}; Date d2{2025,05,28};
  • 2.2 绑定引用初始化
    列表{ 2025,12,12 }会先构造一个Date临时对象,然后将这个临时对象的引用绑定到const引用d3(或d4)上。
    因为临时对象的生命周期会被延长,与const引用的生命周期一致,所以这种写法是合法的。
const Date& d3 ={2025,12,12};const Date& d4{2025,12,12};//Date& d4{ 2025,12,12 };// 临时对象 { 2025,12,12} 被绑定到const引用d4,生命周期延长至d4的作用域结束

问题:const Date& d4(2025,10,10)为什么一定要加const,const意义何在?
如果不加const ,就是费const引用,会报错,原因在于非const引用意味着可以修改内部的数据,但是受生命周期影响,被引用&的内容出了作用域就会销毁,再去修改内部的数据,就会报错。加const一来可以防止内部的数据被修改,而来可以让d4一直坚持到生命周期结束再销毁。
“const引用可以延长临时对象的生命周期,使其与引用自身的生命周期一致”,这样就避免了** “对象提前销毁导致引用失效” **的问题。
简单总结一下:
临时对象匿名对象的生命周期都只在一行,const 引用可以延长临时对象+匿名对象的生命周期。

非const引用 + 临时对象 → 语法禁止(因为修改无意义且危险);
const引用 + 临时对象 →语法允许,且延长临时对象生命周期至引用销毁。

注意一个小点:只有{ }初始化才可以省略=
像Date d 2025;一定会报错

结合析构函数(复习巩固)

在这里插入图片描述

局部对象和被const引用延长生命周期的临时对象,析构顺序与构造顺序相反(即 “先构造的后析构,后构造的先析构”)。
在这段代码中,构造顺序大致是:d1 → d2 → d3 → d4;因此析构顺序是:d4 → d3 → d2 → d1,与打印的顺序完全一致。

//构造 vector<int>v1 ={1,2,3,4,5,6}; vector<int>v2{1,2,3,4,5,6};const vector<int>& v3 ={9,8,7,6,5,4};const vector<int>&v4({9,8,7,6,5,4});//构造+拷贝构造+优化 vector<int>v5({6,7,8});//({6,7,8})通过列表构造函数创建临时对象,然后用该临时对象直接构造v5。//编译器会触发拷贝构造优化(返回值优化,RVO),//避免临时对象的拷贝,直接在v5的内存地址上构造对象,最终等价于一次构造。

列表初始化+pair隐式类型转换

map<string,int>map1 ={{"apple",5},{"bule",9}};//map<string, int> & map2({ "apple",5 }, { "bule" ,9 });报错没有匹配的构造函数的类型

右值引用和移动语义

左值和右值的区别:右值不可以取地址,左值可以取地址。

左值引用给左值取别名,右值引用给右值取别名。

//左值引用给左值取别名int& r1 = b;int*& r2 = p;int& r3 =*p; string& r4 = s;char& r5 = s[0];//右值引用给右值取别名int&& rr1 =10;double&& rr2 = x + y;double&& rr3 =fmin(x, y); string&& rr4 =string("11111");

左值不能直接引用右值,但是加了const的左值可以引用;
右值也不能直接引用左值,但是可以引用move左值。
补充:move的本质就是进行强制类型转换。

在这里插入图片描述


在这里插入图片描述


注意:
从语义和内存本质的角度,左值引用和右值引用都是 “别名”,本身不额外开辟新的对象存储空间,在底层实现上通常以指针的形式存在(汇编层面会体现为指针操作)
**右值引用可以给右值起别名,但右值引用变量本身是左值属性。**所以变量表达式都是左值属性。

引用延长生命周期

总结:
右值引用可以延长被引用对象的生命周期,被引用对象可以通过非const的引用修改;
左值引用不能延长对象生命周期,但是const左值引用可以延长生命周期,
被引用对象不能通过到const的引用修改。

在这里插入图片描述
intmain(){ std::string s1 ="happy"; std::string&& a1 = std::move(s1);const std::string& a2 = s1 + s1;//到const的左值引用延长生命周期//a2 += "happy";//error:没有与这些操作数匹配的 "+=" 运算符//错误:不能通过到 const 的引用修改 std::cout << a2 <<'\n';//print:happyhappy std::string&& a3 = s1 + s1;// 右值引用延长生存期 a3 +="exersice";// 能通过到非 const 的引用修改 std::cout << a3 <<'\n';//print:happyhappyexersicereturn0;}

左值和右值的参数匹配

C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配f(const左值引用),实参是右值会匹配f(右值引用)。

#include<iostream>#include<string>usingnamespace std;voidf(int& x){ cout <<"左值引用重载调用"<< endl;}voidf(constint& x){ cout <<"const左值引用重载调用"<< endl;}voidf(int&& x){ cout <<"左值引用重载调用"<< endl;}intmain(){int i =1;constint ci =2;int&& x =3;f(1);//print:左值引用重载调用f(ci);//print:const左值引用重载调用f(x);//print:左值引用重载调用f(std::move(x));// print:右值引用重载调用(原则上,但是编译器版本太新了会做优化调用左值引用return0;}

右值引用和移动语义的使用场景

面试题插入:左值引用和右值引用的区别:

左值引用是 “对象的别名”,用于共享访问和避免拷贝;
右值引用是 “临时资源的所有权标识”,用于移动语义和完美转发,是 C++11 后提升性能的关键特性。
左值引用和右值引用的目的都是减少拷贝,提高效率。

优点:
左值引用可以修改参数/返回值,方便使用;
缺点:
但是在有些场景下,左值引用在对象函数栈帧中结束了会销毁,不能使用左值引用返回,当前函数局部对象,出了当前函数作用域生命周期到了就销毁了,不能用左值返回,只能传值返回。
问题:
那么如何解决只能传值返回,但是返回对象在函数栈帧结束之后会销毁的问题?

解决场景:
方案一:
不用返回值,用输出型参数。不足:一定程度上牺牲了可读性;
方案二:
编译器优化
方案三:
新标准新语法处理(右值引用),而右值引用可以延长函数对象的生命周期

编译器不优化的场景是拷贝构造+拷贝赋值,VS2019debug版本传参有优化,,对于ret赋值的没有优化到还是有拷贝构造和拷贝赋值,二代优化只有一次拷贝赋值,没有出现临时对象,没有拷贝构造。将构造和拷贝构造合二为一。
C++11之后彻底不优化,但是调用了移动赋值和移动构造,这样会直接调用移动构造和移动赋值,传值返回的代价约等于0.

总结:

在没有移动构造和移动赋值的情况下:

构造场景:

编译器如果不优化:
先产生一个临时对象,str函数哈哈栈帧结束后,临时对象接收str中存储的数据(一次拷贝构造),临时对象又会将内部存储的值拷贝构造给main()函数栈帧中的ret.总体上来说是两次拷贝构造。

在这里插入图片描述
string addstring1(string nums1,string nums2){ string str;int end1=nums1.size()-1,end2=nums2.size()-1;int next=0;... str+=('0'+ret);...return str;}intmain(){//构造场景 string ret=addstring1("xndx","lzdx"); cout<<ret.c_str()<<endl;return0;}

不优化:会调用很多次构造先构造参数,每一次参数的构造完成,还会调用拷贝构造;
一代优化:合二为一,没有临时对象的产生,只产生了一次拷贝构造,构造+拷贝构造。
二代优化:合三为一,直接构造。

赋值场景:

在这里插入图片描述
string addstring1(string nums1,string nums2){ string str;int end1=nums1.size()-1,end2=nums2.size()-1;int next=0;... str+=('0'+ret);...return str;}intmain(){//赋值场景 string ret; ret=addstring1("xndx","lzdx"); cout<<ret.c_str()<<endl;return0;}

不优化版本下:
产生一个临时对象,str在函数栈帧结束之前将内部资源拷贝构造给临时对象,由临时对象拷贝赋值给main函数中的ret。参数构造+拷贝构造,最后还有一次拷贝赋值

一代优化:多个参数构造,一次拷贝构造+一次拷贝赋值;
二代优化:拷贝构造和构造合二为一,最后一次拷贝赋值。

有移动构造和移动赋值C++11环境下:

构造场景:

在这里插入图片描述

不优化版本:str先将内部资源移动构造给临时对象,main函数中ret接收临时对象中的资源,也是移动构造,每一个参数构造+移动构造;

1代优化:没有产生临时对象,编译器识别到 str 是 “即将被返回的局部对象”,会将其视为右值;执行 return str; 时,触发移动构造:直接将 str 的资源 “转移” 给临时对象(而非拷贝);
进一步优化中,临时对象和 main 中的 ret 会 “合二为一”(省略临时对象的构造),最终 str 的资源直接移动构造到 ret 中。
此过程仅触发一次移动构造,资源转移效率极高(无额外拷贝)。构造参数+最终一次移动构造

2代优化(比如在VS2022编译器下)中甚至没有产生str,只有ret,也就是说str本质是ret对象的引用,实际上没有产生str,一旦产生,函数栈帧销毁,局部对象str销毁,ret就是野引用,所以str没有产生,其底层使用指针形式实现,这个时候打印出str和ret的地址cout<<&ret<<endl;cout<<&str<<endl;会发现他们的地址是相同的。
这时候会直接构造,没有移动构造。

赋值的场景:

在这里插入图片描述
在这里插入图片描述
string addstring1(string nums1,string nums2){ string str;int end1=nums1.size()-1,end2=nums2.size()-1;int next=0;... str+=('0'+ret);...return str;}intmain(){//赋值场景 string ret; ret=addstring1("xndx","lzdx"); cout<<ret.c_str()<<endl;return0;}

不优化版本下:
产生一个临时对象,str在函数栈帧结束之前将内部资源拷贝构造给临时对象,由临时对象拷贝赋值给main函数中的ret。参数构造+移动构造,最后还有一次移动赋值
一代优化:多次构造,一次移动构造+一次移动赋值;
二代优化:构造和移动构造合二为一,只有构造和移动赋值。

总体来说:
如果代码中有拷贝构造和拷贝赋值,也有移动构造和移动赋值,编译器一定会优先执行移动构造和移动赋值,因为选择的都是效率高的。
当编译器升级+C++支持移动构造和移动赋值,传值返回的效率变高。

问题:那么移动构造和移动赋值的效率这么高,需不需要在每一个类型中都实现?
对于深拷贝的自定义类型(vector/string/map…),实现移动构造和移动赋值的价值很大,一定程度上比拷贝构造和拷贝赋值的效率高很多。
对于浅拷贝的自定义类型(如Date/pair<int,int>…)不需要额外实现移动构造和 移动赋值,因为浅拷贝的传值返回和移动构造移动赋值相比,浅拷贝没有指向资源,拷贝代价不大,效率差不多。

类别:

在这里插入图片描述


在这里插入图片描述


引用折叠

类型别名的引用折叠:

intmain(){typedefint& lref;//lref 是 “int 的左值引用” 类型typedefint&& rref;//rref 是 “int 的右值引用” 类型。int n =0; lref& r1 = n;// r1 的类型是 int& lref&& r2 = n;// r2 的类型是 int& rref& r3 = n;// r3 的类型是 int& rref&& r4 =1;// r4 的类型是 int&&return0;}

总结:
只要嵌套中存在左值引用(&),最终结果就是左值引用;只有纯右值引用(&&)嵌套时,才是右值引用。
二、模板函数的引用行为

// 由于引用折叠限定,f1实例化以后总是一个左值引用template<classT>voidf1(T& x)//模板参数是左值引用,实例化的时候只能接受左值(n),无论T是什么类型,x始终是左值引用(无折叠空间,因为参数已固定为& )。{}// 由于引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用template<classT>voidf2(T&& x)//模板参数是万能引用(T&&){}

f1(T& x):模板参数是左值引用,实例化时只能接受左值(如 n),且无论 T 推导为何,x 始终是左值引用(无折叠空间,因为参数已固定为 &)。
f2(T&& x):模板参数是万能引用(T&&)
实例化时:
传入左值(如 n),T 推导为 int&,x 类型为 int& && → 折叠为 int&(左值引用)。
传入右值(如 1),T 推导为 int,x 类型为 int&&(右值引用)。

结论:f1 只能处理左值引用,f2 可通过引用折叠同时处理左值和右值引用(万能引用的特性)。

在这里插入图片描述


f1(T& x) 本质是左值引用参数,显式实例化后仍为左值引用,非const时仅接受左值,const时可接受左值和右值。

在这里插入图片描述


f2(T&& x) 是万能引用,显式实例化后通过引用折叠可变为左值或右值引用,需严格匹配实参的左值 / 右值属性。

总结:左值只能绑定左值引用,右值引用可以绑定右值引用和const左值引用。

// 由于引用折叠限定,f1实例化以后总是一个左值引用template<classT>voidf1(T& x)//模板参数是左值引用,实例化的时候只能接受左值(n),无论T是什么类型,x始终是左值引用(无折叠空间,因为参数已固定为& )。{}// 由于引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用template<classT>voidf2(T&& x)//模板参数是万能引用(T&&){}intmain(){typedefint& lref;//lref 是 “int 的左值引用” 类型typedefint&& rref;//rref 是 “int 的右值引用” 类型。 lref& r1 = n;// r1 的类型是 int& lref&& r2 = n;// r2 的类型是 int& rref& r3 = n;// r3 的类型是 int& rref&& r4 =1;// r4 的类型是 int&&int n =0;//没有折叠->实例化为void f1(int& x) f1<int>(n);//n是左值,可绑定到int&; f1<int>(0);//0是右值,无法绑定到非const的int& → 报错。// 折叠 f1<int&>(n);//n是左值,可以绑定到int& & f1<int&>(0);// 0是右值,无法绑定,改正:f1<const int&>(0);// 折叠 f1<int&&>(n);//T=int&& → 参数类型为(int&&)&(引用折叠为int&)。n(左值)可绑定到int& ; f1<int&&>(0);// //0(右值)不可 → 后者报错。// 折叠->实例化为void f1(const int& x) f1<constint&>(n);//T=const int& → 参数类型为const int&(const左值引用)。 f1<constint&>(0);//左值(n)和右值(0)都能绑定到const int& → 均合法。// 折叠 f1<constint&&>(n);//const右值引用 f1<constint&&>(0);// 没有折叠->实例化为void f2(int&& x) f2<int>(n);// T = int → 参数类型为int && (右值引用)。 f2<int>(0);// 0是右值,参数类型是int &&(右值引用)// 折叠->实例化为void f2(int& x) f2<int&>(n);//T = int& &&,n是左值,左值引用和左值,不报错 f2<int&>(0);//左值引用和0会报错,左值无法引用右值,报错改正:f2<const int&>(0);// 折叠->实例化为void f2(int&& x) f2<int&&>(n);// T=int&& → 参数类型为(int&&)&&(折叠为int&&,右值引用)。n(左值)无法绑定到int&& ; f2<int&&>(0);//0(右值)可绑定 → 前者报错。return0;}

Function(T&& t)函数模板程序中,假设实参是int右值,模板参数T的推导int,实参是int左值,模板参数T的推导int&,再结合引用折叠规则,就实现了
实参是左值,实例化出左值引用版本形参的Function,
实参是右值,实例化出右值引用版本形参的Function。

在这里插入图片描述
template<classT>voidFunction(T && t)//模板参数是万能引用(T&&){int a =0; T x = a;//x++;// 所以Function内部会编译报错,x不能++,error:不能给常量赋值 cout <<&a << endl; cout <<&x << endl << endl;}intmain(){Function(10);// 10是右值,推导出T为int,模板实例化为void Function(int&& t)int a;Function(a);// a是左值,推导出T为int&,引用折叠,模板实例化为void Function(int& t)Function(std::move(a));// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)constint b =8;Function(b);//b是左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int&& t)Function(std::move(b));// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&t)return0;}
在这里插入图片描述

Read more

Java 中间件:Dubbo 服务降级(Mock 机制)

Java 中间件:Dubbo 服务降级(Mock 机制)

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java中间件这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * Java 中间件:Dubbo 服务降级(Mock 机制) * 什么是服务降级? * Dubbo Mock 机制简介 * Mock 的触发条件 * Dubbo Mock 的配置方式 * 1. XML 配置方式 * 2. 注解配置方式(推荐) * 3. 自定义 Mock 类 * 4. 强制 Mock(force) * Mock 机制的工作原理 * 实战案例:电商系统中的服务降级 * 场景描述 * 1. 定义服务接口 * 2. 实现

By Ne0inhk
【AI应用开发工程师】-分享Java 转 AI成功经验

【AI应用开发工程师】-分享Java 转 AI成功经验

Java 转 AI:别再死磕书本了,老司机带你飞! 文章目录 * Java 转 AI:别再死磕书本了,老司机带你飞! * ⭐AI 大模型应用开发全方位成长路线⭐ * 一、Java 老兵的 AI 转型焦虑:书本,你真的跟不上时代了! * 二、AI 导师,你的专属学习外挂! * 三、抱紧大腿,和 AI 大佬一起成长! * 四、拓展方案一:开源社区,你的 AI 练兵场! * 五、拓展方案二:小步快跑,项目实战是王道! * 六、拓展方案三:知识管理,告别“学了就忘”的魔咒! * 七、总结:转型 AI,一场充满乐趣的冒险!

By Ne0inhk
收藏!32岁果断转行AI大模型,从传统IT月薪8k到25k,我的5个逆袭转折点

收藏!32岁果断转行AI大模型,从传统IT月薪8k到25k,我的5个逆袭转折点

32岁这年,我做了一个让身边所有人都意外的决定:告别稳定的传统行业,一头扎进AI大模型赛道。 在此之前,我一直在传统制造企业做IT运维,日常就是维护服务器、排查网络故障、解决各类使用问题。工作安稳、没什么大风大浪,但也能一眼望到头,完全看不到成长空间。 当时月薪8000,扣掉房贷、车贷,再加上孩子的学费和日常开销,每个月都所剩无几。看着身边同龄人不断升职加薪、职业越走越宽,再对比自己原地踏步的状态,心里满是焦虑和不甘。 直到某天,我在知乎刷到一篇关于AI大模型的分享,里面明确提到:AI大模型是下一个时代的核心趋势,会彻底重构各行各业。就是这一瞬间,我好像抓住了改变人生的机会。 今天,我把自己转行路上的5个关键转折点完整分享出来,句句都是实战经验,迷茫想转行的朋友一定要看完。 一、认清趋势:AI大模型不是风口,是刚需 2023年ChatGPT横空出世,直接引爆全球AI大模型热潮;2024年GPT-4发布,能力实现跨越式升级;2025年,国内大模型更是全面爆发,百度文心一言、阿里通义千问、华为盘古大模型等持续迭代,生态越来越成熟。 如今AI大模型的应用早已遍地开花:智能客

By Ne0inhk
Java WebFlux集成DeepSeek大模型:流式接入完整实现(含代码+优化+避坑)

Java WebFlux集成DeepSeek大模型:流式接入完整实现(含代码+优化+避坑)

Java WebFlux集成DeepSeek大模型:流式接入完整实现(含代码+优化+避坑) 前言:随着大模型技术的普及,Java后端接入DeepSeek等大模型时,传统同步阻塞式调用已无法满足高并发、低延迟的业务需求。本文基于Spring WebFlux响应式框架,详细讲解大模型流式接入的技术方案、完整实现代码、性能优化技巧及常见问题解决方案,全程干货,可直接落地到生产环境。 关键词:Java WebFlux;DeepSeek;流式接入;SSE;响应式编程;大模型集成 一、技术背景与需求分析 在Java后端开发中,接入DeepSeek等大模型进行AI推理时,传统同步HTTP调用模式存在诸多痛点,而流式处理结合WebFlux的响应式特性,成为解决该问题的最优路径。 1.1 传统AI模型接入的局限性 传统Java应用接入AI推理模型,普遍采用同步阻塞式HTTP请求(如OkHttp、RestTemplate同步调用),这种模式在对接DeepSeek等大模型时,瓶颈尤为突出,具体表现为三点: * 高延迟导致线程阻塞:DeepSeek等大模型单次推理耗时通常在1-5秒

By Ne0inhk