c++11可变模版参数 emplace接口 新的类功能 lambda 包装器 📅 发布时间:2026/9/2 11:23:34 👁 浏览次数: 可变参数模板在模板参数列表中 class...或 typename...指出接下来的参数表示零或多个类型列表 在使用时候 函数参数列表中 类型名后面跟...指出接下来表示零或多个形参对象列表函数参数包可以用左值引用或右值引用表示跟前面普通模板 ⼀样每个参数实例化时遵循引用折叠规则。• 可变参数模板的原理跟模板类似本质还是去实例化对应类型和个数的多个函数。• 可以使用sizeof...运算符去计算参数包中参数的个数如下本质其实是编译器通过这个可变参数模版会生成对应需要的函数再详细一点来看先由可变模版参数推出函数模版 然后再经过引用折叠实例化出对应的函数但是这些我们都不需要考虑这样的过程是由编译器完成的打印时候不能用范围for或者[]下标访问的方式 因为没有支持打印方式1具体展开过程如下那么可不可以用下面这样的方式来实现递归终止呢不可以因为包的展开实际上是在编译阶段进行编译阶段编译器就已经把包一个个展开 直到调用包为空的函数 但是这里的0的判断是在运行的时候才会执行的所以编译阶段包展开找不到无参的函数打印方式2先把参数包传给Print函数 Print函数再把参数包传给Arguments函数 但是打印结果不是该函数完成的 在编译阶段就会把参数包给展开例如最后一行的三个参数会展开正最下面红色框中的内容 然后由GetArg函数来打印 打印之后把其返回值作为一个参数包传给Arguments 在里面不需要进行处理empalce系列接口在c11后容器支持emplace接口 它的参数类型就是可变模版参数push_back 和emplace-back对比他们都支持左值引用和右值引用先在使用上看一下他们的区别下面这种情况他们才有区别因为对于push_back的参数在创建对象实例化的时候就已经确定了 而emplace_back插入数据的时候才会进行推导 这里推出的是const char*类型那么为什么会出现这样的情况呢 在下面自己实现emplace_back后就可以理解emplace_back实现emplace_back在接收参数包后 不会直接把其给扩展开 而是一层层传递给下一个函数直接地用到到了初始化列表这里构造 对于之前的const string*就会直接调用string的构造 所以 emplace_back只调用了一次构造自己使用的时候也和之前的现象一致总结emplace_back和push_back差异的原因是实现上的原因----要理解可变模版参数和隐式类型转换push_back插入的数据固定为了实例化对象时候的类型所以需要先构造这个对象然后把这个对象作为参数传递过去此时有两种方式① 先在函数栈内构建一个左值②直接在参数里面用隐式类型转换构建一个临时的右值。 所以一定需要先构造一次然后在函数里面再把这个参数根据其的左右值属性来选择移动或者拷贝构造来生成尾插的数据对象而emplace_back的参数是可变模版参数所以我们可以直接传入这个实例化对象类型的构造它参数然后在函数里面直接通过这些参数来生成尾插的数据对象。很多情况下emplace_back和push_back是一样的( 函数传入的直接是该类型的对象然后里面根据参数是左值还是右值来决定进行拷贝构造还是移动构造)部分场景下 emplace_back只需要构造 而push_back需要构造移动构造或者拷贝构造( 选择直接通过传递实例化对象时候的类型的构建参数emplace_back直接在()里面填push_back通过{}隐式类型转换先进行一次构造。)所以 在之后使用的时候 我们其实可以用emplace来替代insert和push 部分场景效率会快上一点点c容器里面emplace的接口不支持和initializer_list的结合。除非显示进行insert支持隐式进行新的类功能默认的移动构造和移动赋值原来C类中有6个默认成员函数构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重 载/const 取地址重载最后重要的是前4个默认成员函数就是我们不写编译器 会⽣成⼀个默认的。C11新增了两个默认成员函数移动构造函数和移动赋值运算符重载。如果你没有自己实现移动构造函数且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成⼀个默认移动构造。默认生成的移动构造函数对于内置类型成员会执 行逐成员按字节拷贝(浅拷贝) 对于自定义类型成员 则需要看这个成员是否实现移动构造如果实现了就调用移动构造没有实现就调用拷贝构造。(默认移动赋值跟上⾯移动构造完全类似)• 如果你提供了移动构造或者移动赋值编译器不会⾃动提供拷贝构造和拷贝赋值。成员变量声明时给缺省值可以给类里面成员变量声明的时候给缺省值 在c初始化列表那里学习过了defult和deleteC11可以让你更好的控制要使⽤的默认函数。假设你要使⽤某个默认的函数但是因为⼀些原因 这个函数没有默认生成 比如我们提供了拷贝构造就不会生成移动构造了那么我们可以使用default关键字显示指定移动构造生成。如果能想要限制某些默认函数的生成在C98中是该函数设置成private并且只声明 这样只要其他⼈想要调用就会报错。在C11中更简单只需在该函数声明加上delete即可该语法指示编译器不生成对应函数的默认版本称delete修饰的函数为删除函数例如ostream的构造函数final与override在继承多态那里我们了解过了final可以让父类不能被继承 在父类的虚函数后面加 在子类中就不能对这个虚函数重写了override可以检查派生类是否对父类的虚函数是否被重写STL中⼀些变化下图1圈起来的就是STL中的新容器但是实际最有⽤的是unordered_map和unordered_setSTL中容器的新接口也不少最重要的就是右值引用和移动语义相关的push/insert/emplace系列 接口和移动构造和移动赋值还有initializer_list版本的构造等效率提高上主要就是新容器 unordered_set及unordered_map和右值的移动语义使用上有了initializer_list及范围forlambdalambda 表达式本质是⼀个匿名函数对象跟普通函数不同的是他可以定义在函数内部1.基本语法2.lambda的使用例如此时有下面商品Goods的类 里面有三个成员变量 _namd _prive _evaluate如果此时我们要根据不同的比较逻辑对商品进行排序 此时就需要写多个仿函数但是之前有一些缺陷 如果用到了仿函数 但是并没有注释告诉我们这里仿函数是做什么的 我们就需要找到仿函数 然后再进行观察而如果我们用下面lambda的方式 不仅可以让代码简化 而且在这里就可以直接观察这个仿函数做了什么3.捕捉列表lambda 表达式中默认只能用参数列表中的全局域的和静态的如果想要使用其他的就需要进行捕捉第⼀种捕捉方式是在捕捉列表中显示的传值捕捉和传引⽤捕捉第⼆种捕捉方式是在捕捉列表中隐式捕捉我们在捕捉列表写⼀个表示隐式值捕捉在捕捉列表 写⼀个表示隐式引用捕捉这样我们 lambda 表达式中用了那些变量编译器就会自动捕捉那些 变量第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。当使用混合捕捉时第⼀个元素必须是 或并且混合捕捉时后⾯的捕捉变量必须是值捕捉同理混合捕捉时后面的捕捉变量必须须是引用捕捉。默认情况下 lambda传值捕捉的过来的对象不能修改 mutable加在参数列表的后⾯传值捕捉的对象就可以修改了 但是不会影响实参4.lambda的原理lambda和范围for类似-----其实从汇编指令来看根本不存在范围for lambda这样的东西 范围for的底层是迭代器 而lambda的底层其实就是仿函数 lambda的就是返回了一个仿函数对象仿函数的类名是编译按⼀定规则生成的保证不同的 lambda ⽣成的类名不同lambda参数/返 回类型/函数体就是仿函数operator()的参数/返回类型/函数体 lambda 的捕捉列表本质是生成的仿函数类的成员变量也就是说捕捉列表的变量都是 lambda 类构造函数的实参当然隐式捕捉编译器要看使⽤哪些就传那些对象包装器function介绍及使用function是⼀个类模板也是⼀个包装器 它的实例对象可以包装存储其他的可以调用对象包括函数指针、仿函数、lambda或者之后学习的bindfunction返回值类型(参数列表) 对象 function的参数列表要和包装的调用对象的参数列表相对应通过下面的题目可以感受到它的使用优势传统方式class Solution { public: int evalRPN(vectorstring tokens) { stackint s; for(string x:tokens) { if(x||x-||x*||x/) { int rights.top(); s.pop(); int lefts.top(); s.pop(); switch(x[0]) { case : s.push(leftright);break; case -: s.push(left-right);break; case *: s.push(left*right); break; case /: s.push(left/right); break; } } else s.push(stoi(x)); } return s.top(); } };现代写法class Solution { public: int evalRPN(vectorstring tokens) { mapstring,functionint(int,int)m({ {,[](int a,int b){return ab;}}, {-,[](int a,int b){return a-b;}}, {*,[](int a,int b){return a*b;}}, {/,[](int a,int b){return a/b;}} }); stackints; for(string x:tokens) { if(m.count(x)) { int right s.top(); s.pop(); int left s.top(); s.pop(); s.push(m[x](left,right)); } else s.push(stoi(x)); } return s.top(); } };这种方法用到了map 第一个模版参数是string类型 第二个参数是包装器的方式 相应的运算符对应着相应功能的函数相较于传统方法 这种方式如果增加新的功能会更加方便 更容易维护 如果操作符非常多的情况 这种方法还会更快 因为map底层是红黑树 查找的速度相较于上一种switch依次判断的方式更快 当然只有在运算符非常多的情况下才会有明显差距。包装成员函数function还可以包装成员函数 包装时候需要用类域指定访问的成员函数对于非静态的成员还需要在前面加上(静态成员函数可加ke不加)另外对于非静态的成员函数有隐含的this指针还需要再传一个参数 可以是类类型的指针也可以是类类型bindbind 是⼀个函数模板它也是⼀个可调⽤对象的包装器可以把他看做⼀个函数适配器对接收 的可调⽤对象进⾏处理后返回⼀个可调⽤对象。bind 可以⽤来调整参数个数和参数顺序。bind 也在functional头文件中。调⽤bind的⼀般形式auto newCallable bind(callable,arg_list);其中 newCallable本⾝是⼀个可调用对象arg_list是⼀个逗号分隔的参数列表对应给定的callable的 参数。当我们调用newCallable时newCallable会调⽤callable并传给它arg_list中的参数。arg_list中的参数可能包含形如_n的名字其中n是⼀个整数这些参数是占位符表⽰ newCallable的参数它们占据了传递给newCallable的参数的位置。数值n表示生成的可调⽤对象 中参数的位置_1为newCallable的第⼀个参数_2为第⼆个参数以此类推。_1/_2/_3....这些占 位符放到placeholders的⼀个命名空间中。改变参数的顺序(不常用)改变参数的个数(常用)下面的Sub函数需要传两个参数 用bind对其包装的时候已经固定了第二个参数 此时包装后的函数只需要传一个参数在之前function对类内非静态成员函数使用的时候很麻烦 第一个参数需要创建再传(如果第一个参数是类类型地址)或者第一个参数是类类型(第一个参数是类类型) 用到这里的bind就可以解决这样的问题bind使用的例子如下的func1是一个计算利息的函数 传的时候需要传三个参数(年利率 本金 年数) 如果我们正常使用的时候需要传三个参数才会计算出利息用下面bind结合function就可以实现出专门处理一些常用的处理某一存储年数及其年利率 这样使用的时候只需直接调用对应的函数然后传一个本金参数就可以计算出相应的利息