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

【Python】家庭用电数据的时序分析

【Python】家庭用电数据的时序分析

Household Electricity Consumption | Kaggle 目录 数据简介 探索分析 数据清洗 用电占比 趋势分析 序列分解 周期分析 周期分解 分析小结 数据简介 240000-household-electricity-consumption-records数据集包含了一个家庭6个月的用电数据,收集于2007年1月至2007年6月。这些数据包括总有功功率、总无功功率、电压、全球强度、分项计量1(厨房)、分项计量2(洗衣房)和分项计量3(电热水器和空调)等信息。该数据集共有260,640个测量值。 列名说明Date日期Time时间Globalactivepower该家庭所消耗的总有功功率(千瓦)Globalreactivepower该家庭消耗的总无功功率(千瓦)Voltage向家庭输送电力的电压(伏特)Global_intensity输送到家庭的平均电流强度(安培)Submetering1厨房消耗的有功功率(千瓦)Submetering2洗衣房所消耗的有功功率(千瓦)Submetering3电热水器和空调所消耗的有功功

By Ne0inhk

【python】macos环境升级自己安装的python3

macOS 系统 Python 环境升级完整指南(你的场景:2.7内置+3.8升级至3.14✅) 你的需求非常明确:macOS系统,保留系统内置Python2.7(绝对不能动),把自己安装的Python3.8升级到最新的Python3.14版本,我会给你 两种最主流、最安全的升级方案(按推荐优先级排序),全部实操命令、避坑要点、环境验证、多版本共存管理都讲清楚,新手也能一步到位操作成功,全程不会破坏你的系统环境! ✅ 前置重要纠正:Python 目前最新稳定正式版是 3.13.1,3.14 是开发预览版(beta),不建议生产使用,下文所有命令统一用「最新稳定版」为准,安装后就是Python3的最高可用正式版。 ⚠️ 【重中之重】3个核心原则(必须遵守,否则必出问题!) 你这个场景的所有操作,都基于这3个原则,也是macOS

By Ne0inhk
Python 操作 Excel 高阶技巧:用 openpyxl 玩转循环与 Decimal 精度控制

Python 操作 Excel 高阶技巧:用 openpyxl 玩转循环与 Decimal 精度控制

目录 * Python 操作 Excel 高阶技巧:用 openpyxl 玩转循环与 Decimal 精度控制 * 一、 为什么你的 Excel 数据处理总是“差一点”? * 二、 精度之痛:用 Decimal 拯救你的财务数据 * 1. 浮点数的“陷阱” * 2. Decimal 模块的引入 * 三、 效率革命:Openpyxl 的高效循环策略 * 1. 最慢的写法:逐行写入并保存 * 2. 进阶写法:使用 `append()` 批量写入 * 3. 高阶写法:内存优化与公式填充 * 4. 终极加速:只读模式与公式缓存 * 四、 综合实战:构建一个高精度报表生成器 * 五、 总结与避坑指南 专栏导读

By Ne0inhk
Python + BS4实战:手把手带你爬取商业数据

Python + BS4实战:手把手带你爬取商业数据

目录 一、bs4篇 1.bs4介绍 1.1 什么是BeautifulSoup4? 1.2 为什么选择BeautifulSoup4?       核心优势 2.bs4详解 2.1 首先下载bs4 2.2 接下来引入一个使用bs4的例子让我们快速熟悉它 2.3 运行结果 3.bs4使用实战案例 3.1 完整代码 3.2 为什么会影响翻页 3.3 反爬机制 3.4 已知信息 3.5 解决思路 3.6 结果展示 3.7 容易混淆的一点 3.8 图片爬虫 🌟 Hello,

By Ne0inhk