1.列表初始化1.1传统{ }初始化c98的{ }初始化主要是用于数组以及结构体1.2c11{ }初始化1.让内置类型和自定义类型都可以用{ }实现多个数据初始化而自定义类型的实现原理是类型转换没优化的版本是先构造临时对象然后拷贝构造2.我们在使用{ }时也可以省略掉直接用{ }场景主要用于传递多参数进行初始化1.3initializer_listinitiallizer_list是一个库中实现的类底层是由一个指向常量数组的指针包含头指针和尾指针和数据个数封装的类使用场景主要用于写模板类等底层场景的任意数量数据的构造vector(initializer_listT l) { for (auto e : l) { push_back(e) } }这里就以vector的任意数量数据的构造为例通过initiallizer_list我们就实现了任意数量的数据的构造2.右值引用与移动语义2.1左值与右值的概念左值是变量表达式一般有持久状态可以取地址常见左值举例// 左值可以取地址 int* p new int(0);//指针 int b 1;//整形变量 const int c b;//const整形变量 string s(111111);//字符串变量右值一般是字面值常量临时对象匿名对象不能取地址不能放在赋值符号左边。常见右值举例//右值不能取地址 double x 1.1, y 2.2; //下面为常见右值 10;//字面值常量 x y;//临时变量 fmin(x, y);//临时对象 string(11111);//匿名对象2.2左值引用与右值引用引用就是给变量取别名右值引用也是一样的。只不过左值引用引用的是左值右值引用引用的是右值。左值引用1引用左值2引用右值const //左值引用 //引用左值 int a 0; int b a; //引用右值 const int c 10;右值引用1引用右值2引用左值将左值move为右值类型再用//右值引用 //引用右值 int d 10; //引用左值 int e move(a);move的底层就是强制类型转换可看成将左值move强制类型转换为右值然后用右值引用。变量表达式都是左值属性所以绑定右值的右值引用变量也是左值属性2.3引用延长变量生命周期我们知道临时变量匿名对象临时对象都是生命周期为当前行的数据。而此时使用右值引用去绑定该数据后他们的生命周期就被延长到引用的生命周期了10;//生命周期为当前行 int f 10;//生命周期为f的生命周期2.4左值右值参数匹配在c中我们的函数参数匹配通常是遵守匹配度优先的原则这里的右值引用参数左值引用参数匹配也是一样优先匹配更匹配的接下来我们实现了左值引用const左值引用右值引用三种参数匹配函数分别测试三种类型的匹配结果这里就是按照原本的数据类型进行匹配接下来我们将右值引用注释掉再次查看参数匹配结果我们发现此时右值被const左值引用绑定了这说明不能完全匹配的时候才会考虑其他的类型匹配2.5左值引用使用场景左值引用使用场景1.通过传引用减少拷贝2.可以通过引用的改变来影响实参左值引用没有解决的场景若需要返回的资源存在局部变量中只能靠拷贝构造返回左值引用也没用因为局部变量出函数就已经被销毁了那右值引用有没有用其实也没有用因为只要是引用就是取别名他引用的对象都被销毁了返回的就是一个悬空引用。2.5.1移动构造与移动赋值我们解决左值引用不足的方法就是使用移动语义。具体来说就是多写一个移动构造和移动赋值通过右值引用作为参数去接收生命周期快结束不一定本身是右值的数据然后延长他们的生命周期到移动函数结束时通过直接swap的方式将右值数据的内容接收下来。移动构造对于右值匹配时直接匹配对于左值匹配前先用move将左值类型转换为右值然后再匹配// 移动构造 string(string s) { cout 移动构造 endl; swap(s); }添加了移动构造之后对于效率的提升是很明显的既没有多余空间申请也没有时间上多余的消耗移动赋值// 移动赋值 string operator(string s) { cout 移动赋值 endl; swap(s); return *this; }移动赋值也是一样的直接将资源交换即可2.5.2解决传值返回多余拷贝问题场景1右值对象构造只有拷⻉构造没有移动构造的场景图示不优化情况下进行两次拷贝构造优化情况进行一次拷贝构造这里直接越过了临时对象的构建场景2右值对象构造有拷⻉构造也有移动构造的场景图示不优化情况下进行两次移动构造因为str是生命周期即将结束的变量所以使用移动语义可以有效提高效率优化1情况下进行一次移动构造这里也是省略掉临时对象优化2情况下会将str的构造str给临时对象的拷贝构造临时对象给main中对象的拷贝构造合并为对main中对象的构造。场景3右值对象赋值只有拷⻉构造和拷⻉赋值没有移动构造和移动赋值的场景图示不优化情况下进行一次拷贝构造一次拷贝赋值。优化情况下进行一次拷贝赋值优化的部分是提前构造出临时对象然后str是临时对象的引用最后让临时对象移动赋值给main中对象场景4右值对象赋值既有拷⻉构造和拷⻉赋值也有移动构造和移动赋值的场景图示不优化情况下进行一次移动构造一次移动赋值。优化情况下进行一次移动赋值总结在有编译器优化的情况下没有移动构造和移动赋值也没有问题但是如果没有编译器的优化此时就需要我们写移动构造和移动赋值来提高效率2.6面试相关1move有什么用原理是什么2什么情况下要用移动构造和移动赋值3.引用折叠在模板中我们不可避免的会存在引用的引用的情况但是如果我们直接int a;这样来定义的话会直接报错。而我们可以通过模板或typedef来达成目的引用折叠原则右值引用叠加右值引用为右值引用其他情况都为左值引用//引用折叠 int n 0; typedef int l; typedef int r; l a n;//左值叠左值-》左值引用 l b n;//左值叠右值-》左值引用 r c n;//右值叠左值-》左值引用 r d 1;//右值叠右值-》右值引用接下来我们看看用const左值引用和用const右值引用当函数参数有什么不同//const左值引用 templateclass T void f1(const T a) { }//const右值引用 templateclass T void f2(const T b) { }左值引用当参数的时候传的参数无论是左值还是右值根据引用折叠的规则他的结果都是左值引用右值引用当参数的时候传的参数是左值那么最终折叠完就是左值。传的参数是右值最终折叠完就是右值我们发现我们传的参数是什么类型最终的结果就是什么类型的引用。所以由实参决定T类型的右值引用当参数又被称为万能引用也就是template要直接在该方法前面T由参数决定我们思考一下在一个类中的const T属不属于万能引用不属于。假设我们用的是vector中的push_back方法这个方法中的T的类型不是由实参决定的而是由vector类决定的所以这里不存在引用折叠直接就是一个确定的引用类型。4.完美转发虽然我们前面利用引用折叠弄出了一个万能引用但是在万能引用模板中我们就不能使用move这个方法去强制类型转换变量了因为我们不确定变量是左值引用的还是右值引用的。为了能够正确的匹配对应的函数我们会使用一个叫完美转发的方法来保持变量的属性原本是左值就保持左值是右值就保持右值完美转发forwardT(变量名)其中T表示实参的数据类型templateclass T void f2(const T b) { f1(forwardT(b)); }完美转发的底层是一个模板函数对左值引用和右值引用有不同的函数体进行变量属性控制。而我们在这里这样使用相当于显式实例化函数模板根据这个实例化出的函数的逻辑对右值move从而保持变量属性。在这里就保持了b的属性值是左值或右值。5.可变参数模板在c中有一种模板是支持从0到任意个参数的也就是可变参数模板。可变数目参数被称为参数包参数包又分两种第一种是函数参数包第二种是模板参数包我们利用...来指出接下来的参数是一个包函数参数中类型名后面接的参数表示0到n个形参模板参数中typename...、class...后面接的参数表示0到n个类型参数。5.1基本语法格式//可变模板参数 templateclass... Args void function(Args... args) { }模板位置的写法就是class...然后接可变模板类型名函数参数位置的写法就是类型名...接形参名。本质上是实现了类型泛型基础上的个数泛型补充sizeof...args可以求可变参数的个数5.2包扩展void Extend() { // 参数包无参数需要扩展时匹配这个函数 cout 扩展结束 endl; } template class T, class ...Args void Extend(T x, Args... args) { cout x ; // args是N个参数的参数包 // 调⽤Extened参数包的第⼀个参数传给x剩下N-1个参数的参数包传给第⼆个参数包 Extend(args...); } // 编译时递归推导解析参数 template class ...Args void Print(Args... args) { Extend(args...); }我们可以利用递归函数来对包进行扩展这里的extend就是递归扩展函数。第一步扩展就是实例化出一个对应的Extend函数该函数先将包中的第一个参数给到Extend的x参数然后将剩下的包给到Extend的第二个包参数如此递归进行。直到包中数据为0我们就匹配无参数的Extend函数。注意与普通递归函数不同的是包扩展的递归是编译时进行的而不是运行时进行的接下来我们看看另外一个包扩展方法template class T const T GetArg(const T x) { cout x ; return x; } template class ...Args void Arguments(Args... args) {} template class ...Args void Print(Args... args) { Arguments(GetArg(args)...); }注意1.getarg必须有返回值因为他的所有返回值又构成了一个参数包给到argument2.由于进入一个函数之前要先处理参数参数中有函数的话我们就先进入参数中的函数在参数函数处理完之后我们再进入函数体3.这里的GetArg(args)...可以看为GetArgx1GetArgx2GetArgx3其中x1x2x3都是包中的参数5.3emplace系列接口在c的库中所有的容器都添加了emplace系列的接口他的参数部分是由万能引用叠加可变模板参数组成的。整体而言他的效率要比普通的接口要高接下来我们看看在什么场景下emplace系列效率要更高liststring li; li.push_back(11111); li.emplace_back(111111);首先我们看看push_back的参数要求由于push_back的模板参数是在对象实例化的时候就确定了所以value_type的类型是string。而我们这里传的是右值所以匹配的就是类型string。可是11111的类型是const char*参数匹配类型不一样所以我们要进行类型转换创建一个临时变量然后再匹配参数。此时我们就需要进行一次构造然后是emplace_back的参数要求由于emplace_back的模板参数是根据传递的参数决定的所以函数参数被实例化为const char*,我们可以直接进行参数匹配不用进行构造综上push_back需要传递参数时要进行一次构造而emplace_back不需要进行构造这里就体现出emplace_back的效率更高注意1.emplace系列传递多参数不用加{}因为参数是可变模板参数且如果我们加了{}会导致编译器无法识别参数类型pair中的变量是什么类型是不明确的。6.lambda6.1表达式语法他是一个匿名函数对象可以定义在函数体内部。不过他没有语法层面的数据类型我们一般用auto去充当接收类型接下来我们写一个加法功能的lambda//Lambda auto y [](int a, int b)-int {return a b; }; cout y(1, 2) endl;右侧的lambda实际上就是一个对象。[ ]:方括号中的就是捕捉列表主要用于确定需要使用的lambda函数外部的参数():小括号中的就是函数参数列表可省略-:后面的是函数返回值类型可省略{}:大括号内是函数体简化lambda//简化Lambda auto x [] {cout 简化lambda endl; }; x();使用方式和使用普通函数的方法一样6.2捕捉列表lambda的表达式默认只能使用参数和函数体内的变量若我们需要使用这两个位置之外的变量就需要通过捕捉列表进行捕捉第一种捕捉方式传值捕捉与传引用捕捉传值捕捉传的值在lambda中不能被修改传引用捕捉的值在lambda中可以被修改-//传值捕捉与传引用捕捉 int a 1, b 2; auto e [a, b] { b; return b; };这里的a在lambda中无法修改b在lambda中可以修改且由于是引用所以实参b也被修改了第二种捕捉方式隐式值捕捉与隐式引用捕捉这两种捕捉方式又叫全捕捉因为只要用了他们就可以使用其他所有变量需要注意的是这两种捕捉方式不能同时使用因为这样就不知道到底是要值还是引用了//隐式值捕捉与隐式引用捕捉 int a 1, b 2; auto e [] {//隐式引用捕捉 b; return b; }; auto f [] {//隐式值捕捉 return a; };这种情况下我们在函数中用到了哪些变量就会捕捉哪些变量而不是真正的全都捕捉下来第三种捕捉方式混合捕捉这里我们就可以使用隐式值捕捉/隐式引用捕捉引用捕捉/值捕捉//混合捕捉 int c 3, d 4; auto g [, c] {//隐式引用混合值捕捉 d; return 0; }; auto g [, d] {//隐式值混合引用捕捉 d; return 0; };注意1.隐式值/引用捕捉只能出现一个不能同时出现。2.隐式值/引用捕捉需要放在第一个位置不能放在后面3.隐式值后面只能接引用捕捉同理隐式引用后面只能接值捕捉1当lambda定义在函数局部域中可以捕捉在他之前定义的局部变量不能捕捉静态局部变量和全局变量但是他可以直接使用这两种变量。也就是说如果lambda定义在全局域中捕捉列表必须为空。2传值过来的变量默认有const修饰所以不可修改但是如果加了mutable修饰就可以把const属性去掉然后我们就可以修改传值捕捉的变量了。但是他实际上还是一个拷贝变量改变的只是形参而不是实参6.3应用我们思考一个场景当我们需要用水果的某个指标进行排序此时我们可以使用仿函数控制排序逻辑但是这样我们需要在全局域定义一个新的仿函数且该仿函数唯一作用也就是这个地方此时会导致代码全局域冗余。那么我们有没有什么其他方法控制排序逻辑使得我们可以不在全局域上定义新的仿函数此时我们就可以使用lambda将sort的仿函数传参位置写上一个lambda对象对对应逻辑进行控制//比较场景 struct fruit { int price; string colour; fruit(int prices,string colours) :price(prices) ,colour(colours) { } }; vectorfruit v { {1,red},{2,yellow},{3,blue}}; sort(v.begin(), v.end(), [](const fruit a, const fruit b){return a.price b.price; } );这里我们创建一个水果结构体然后通过lambda实现按照水果的价格进行排序注意在调试的时候如果我们把断点打在了sort上面会直接进入sort中不方便查看lambda的功能此时我们可以进入一次sort后就直接将sort的断点取消掉6.4原理简介lambda和范围for很像他们实际上都是封装出来的lambda底层就是一个仿函数的类捕捉列表实际上就是生成在仿函数中的成员变量参数列表operator()中的传参返回值operator的返回值函数体operator的函数体7.新的类功能7.1默认的移动构造和移动赋值默认生成移动构造的前提没有自己实现移动构造且没有实现析构函数 、拷⻉构造、拷⻉赋值重载中的任意⼀个。默认生成移动赋值的前提没有⾃⼰实现移动赋值重载函数且没有实现析构函数 、拷⻉构造、拷⻉赋值重载中的任意⼀个7.2default和deletedefault的作用是显式指定编译器去实现默认的特殊成员函数一共有六个分别是构造拷贝构造赋值重载移动构造移动赋值析构函数用法以移动构造的指定生成为例Person(Person p) default;delete的作用和default刚好相反他是可以阻止某个默认特殊函数的生成用法以默认生成移动构造的阻止为例Person(Person p) delete;这里其实就相当于将默认特殊函数的函数体外的部分写在前面然后接个和修饰符来控制默认特殊函数的生成和阻止生成7.3面试相关1default和delete的作用8.包装器8.1function(处于functionnal头文件)在c中有很多的可调用对象比如函数指针仿函数lambdabind等。他们的使用是分开来使用的而function的作用就是将他们统一包装起来在返回值的参数都一样的前提下function包装的对象称为function的目标这里我们看到他的模板参数中有ret返回值args可变模板参数格式function返回值类型(参数) fn[] {}这里的function返回值类型(参数)就是一个类型接下来我们看看使用样例来理解他的优势这里我们就是将返回值为int参数都为两个整形的加减乘法包装在function中从而实现根据索引调用不同的函数的目的也就是我们把分开的加减乘三个函数统一到了function函数中。包装可调用对象// 包装各种可调⽤对象 functionint(int, int) f1 f; functionint(int, int) f2 Functor(); functionint(int, int) f3 [](int a, int b) {return a b; };包装静态成员函数// 成员函数要指定类域并且前⾯加才能获取地址 functionint(int, int) f4 Plus::plusi; cout f4(1, 1) endl;其实静态成员函数是不用加的但是普通成员函数需要所以为了统一我们就一起都加上包装普通成员函数// 普通成员函数还有⼀个隐含的this指针参数所以绑定时传对象或者对象的指针过去都可以 functiondouble(Plus*, double, double) f5 Plus::plusd;//传指针 Plus pd; cout f5(pd, 1.1, 1.1) endl; functiondouble(Plus, double, double) f6 Plus::plusd;//传左值引用 cout f6(pd, 1.1, 1.1) endl; cout f6(pd, 1.1, 1.1) endl; functiondouble(Plus, double, double) f7 Plus::plusd;//传右值引用 cout f7(move(pd), 1.1, 1.1) endl; cout f7(Plus(), 1.1, 1.1) endl;疑问为什么普通成员函数的包装多了一个传递参数因为成员函数本身就隐含了一个指向调用该函数的对象的指针也就是this指针。在调用成员函数时编译器会自动把调用对象的地址当作第一个参数传递给成员函数。使用指针调用成员函数借助-运算符来调用成员函数其实质是将指针所指向的对象地址传递给成员函数。使用引用调用成员函数通过.运算符调用成员函数编译器会把引用转换为对象的地址然后传递给成员函数。所以本质上我们都是为了成员函数能借助我们传递的对象地址正确调用函数接下来我们看看function的整合优势mapstring, functionint(int, int) opFuncMap { {, [](int x, int y){return x y;}}, {-, [](int x, int y){return x - y;}}, {*, [](int x, int y){return x * y;}}, {/, [](int x, int y){return x / y;}} };这里我们就把加减乘除的字符串和对应的运算函数通过map的k-v特性联系起来了而在这里function充当了调用函数类型的作用正是有了function我们才能实现将字符串和对应函数联系起来的功能8.2bindbind的作用是调整可调用对象的参数个数与顺序然后返回一个调整后的新的可调用对象实例1调整参数位置#includefunctional using placeholders::_1; using placeholders::_2; using placeholders::_3; int Sub(int a, int b) { return (a - b) * 10; }bind也是包含在头文件functionnal中的然后placeholders作用域中的_1,_2,_3等等就是占位符。这里我们把第一个参数和第二个参数的传递顺序改变了也就是我们传递的参数a是4参数b是3从而得出结果为10.实例2调整参数个数绑定一些参数这里我们就把第一个参数a给绑定为100从而实现调整参数个数的目标。我们在绑定参数的时候是按照原本的参数顺序绑定的因为绑定了参数a为100所以只剩下一个参数需要传递所以后面只剩下_1占位符传递给参数b。总结有几个参数就有几个占位符绑定参数是按照原本的顺序绑定的我们是否可以将function和bind结合使用从而简化代码functiondouble(double, double) f7 bind(Plus::plusd, Plus(), _1, _2); cout f7(1.1, 1.1) endl;这里我们本来是对普通函数进行function包装需要传递对象指针/引用但是每次使用包装函数都要传一次就比较麻烦所以我们直接用bind绑定对象引用然后返回绑定后的调用对象从而以后每次使用都不用再传递对象的引用了8.3面试相关1lambdafunctionbind会在什么场景下用到2final关键字的作用3using 和typedef的区别4为什么用nullptr而不是NULL5enum和enum class 的区别