C++进阶:C++11(2)
◆博主名称少司府欢迎来到少司府的博客☆*: .. o(≧▽≦)o ..:*☆⭐数据结构系列个人专栏初阶数据结构_少司府的博客-CSDN博客⭐C基础个人专栏C初阶_少司府的博客-CSDN博客⭐琢玉成器终有时笔底生花夺锦归目录1. 可变模板参数1.1 基本语法1.2 包扩展2. 移动构造和移动赋值2.1 概念2.2 实现3. emplace系列接口4. 新的类功能4.1 默认的移动构造和移动赋值4.2 成员变量声明时给缺省值4.3 defult和delete4.4 final和override5. lambda表达式5.1 lambda表达式基本语法5.2 捕捉列表5.3 lambda的应用6. 包装器6.1 function6.2 bind1. 可变模板参数1.1 基本语法C11支持可变参数模板即支持参数数量可变的函数模板和类模板。可变数目的参数被称为参数包分为函数参数包和模板参数包表示0或多个函数参数/模板参数。我们用省略号来指出一个模板参数或函数参数表示一个包。在模板参数列表中class...或typename...指出接下来表示0或者多个类型列表在函数参数列表中类型名后面跟...指出接下来表示0或者多个形参对象列表函数参数包可以左值引用或者右值引用表示遵守引用折叠的规则。namespace test_01 { // 可变模板参数 templateclass ...Args // 0到n个参数 void Print(Args... args) { cout sizeof...(args) endl; // sizeof...计算参数包内有几个参数 } }这里我们可以用运算符 sizeof... 来计算参数包里面有几个参数1.2 包扩展对于一个参数包我们除了能计算它的参数个数还能扩展它。namespace test_02 { // 包扩展(解析出参数包的内容) void ShowList() { // 编译时递归的终止条件参数包是0个时直接匹配这个函数 cout endl; } template class T, class ...Args void ShowList(T x, Args... args) { // 运行时逻辑不行包展开是编译时逻辑 //if (sizeof...(args) 0) // return; cout x ; // args是N个参数的参数包 // 调用ShowList参数包的第一个传给x剩下N-1个传给第二个参数包 ShowList(args...); } template class ...Args void Print(Args... args) { ShowList(args...); } }当然我们也可以这样获取参数包namespace test_03 { 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) { // 注意GetArg必须返回或者得到对象这样才能组成参数包给Arguments Arguments(GetArg(args)...); // 实参求值顺序未定义C 包展开表达式求值顺序未规定 } }2. 移动构造和移动赋值2.1 概念移动构造函数是一种构造函数类似于拷贝构造要求第一个参数是该类类型的右值引用若还有其他参数必须给出缺省值移动赋值是一个赋值运算符重载和拷贝赋值构成函数重载要求第一个参数是该类类型的右值引用。2.2 实现// 移动构造 string(string s) { cout string(string s) -- 移动构造 endl; swap(s); } // 移动赋值 string operator(string s) { cout string operator(string s) -- 移动赋值 endl; swap(s); return *this; }对于string/vector这样深拷贝的类或者包含深拷贝的成员变量的类移动构造和移动赋值才有意义其本质就是窃取右值对象的资源不用一个一个地去拷贝提高效率。3. emplace系列接口C11以后STL容器新增了emplace系列接口该系列接口均为模板可变参数功能上兼容push和insert系列并且假设容器containerTemplace支持直接插入构造T对象的参数在有些场景下会更高效些。void Test() { listrmove::string lt; // 传左值跟push_back一样走拷贝构造 rmove::string s1(1111111111); rmove::string s2(1111111111); lt.emplace_back(s1); cout ****************** endl; lt.push_back(s2); cout ****************** endl; cout endl; // 传右值,跟push_back一样走移动构造 lt.emplace_back(move(s1)); cout ****************** endl; lt.push_back(move(s2)); cout ****************** endl; cout endl; // 直接传参隐式类型转换 lt.emplace_back(111111111); // 没有隐式类型转换直接构造 cout ****************** endl; lt.push_back(111111111); // 有隐式类型转换需要临时对象深拷贝构造移动构造对于浅拷贝的类型就是构造拷贝构造 cout ****************** endl; }第二个不同void Test_02() { listpairstring, int lt1; // lt1.emplace_back({苹果, 1}); 不能这样写 lt1.emplace_back(苹果, 1); // emplace_back本身支持传参数包 cout ****************** endl; lt1.push_back({ 苹果,1 }); // listpair隐式类型转换写{ } cout ****************** endl; }总结emplace系列兼容push系列和insert系列部分场景下emplace可以直接构造push和insert是构造移动构造或构造拷贝构造//list接口更新 namespace cxr { templateclass T struct ListNode { // 节点构造完美转发就地构造_data支持emplace template class... Args ListNode(Args... args) : _next(nullptr) , _prev(nullptr) , _data(std::forwardArgs(args)...) {} T _data; ListNodeT* _next; ListNodeT* _prev; }; templateclass T class list { public: using Node ListNodeT; // 容器的emplace_back templateclass... Args void emplace_back(Args... args) { insert(end(), std::forwardArgs(args)...); } // 核心 emplace insert在pos迭代器位置就地构造节点 templateclass... Args void insert(iterator pos, Args... args) { // 直接new节点把参数转发给Node构造函数Node内部再转发给T的构造 Node* newnode new Node(std::forwardArgs(args)...); Node* cur pos._node; Node* prev cur-_prev; prev-_next newnode; newnode-_prev prev; newnode-_next cur; cur-_prev newnode; _size; } // push_back 复用emplace_back void push_back(const T val) { emplace_back(val); } void push_back(T val) { emplace_back(std::move(val)); } iterator end() { return iterator(_head); // _head是哨兵头结点 } private: Node* _head; size_t _size; }; }4. 新的类功能4.1 默认的移动构造和移动赋值1、除了原本的6个默认成员函数构造、析构、拷贝构造、拷贝赋值重载、取地址重载、const取地址重载外C11新增2个默认成员函数移动构造和移动赋值2、若没有自己实现移动构造且没有实现析构、拷贝构造、拷贝赋值重载中任意一个那么编译器就会自动生成一个默认移动构造。3、默认生成的移动构造对于内置类型成员会逐字节拷贝对于自定义类型成员若其实现了移动构造则调用移动构造没有实现就调用拷贝构造4、默认生成的移动赋值的规则同上class Person { public: Person(const char* name , int age 0) :_name(name) , _age(age) { } //Person(const Person p) // :_name(p._name) // ,_age(p._age) //{ } //Person operator(const Person p) //{ // if(this ! p) // { // _name p._name; // _age p._age; // } // return *this; //} //~Person() //{ } private: string _name; int _age; }; int main() { Person s1; Person s2 s1; Person s3 std::move(s1); Person s4; s4 std::move(s2); return 0; }4.2 成员变量声明时给缺省值成员变量声明时给出缺省值用于初始化列表。这里在类和对象部分有讲就不多赘述了。4.3 defult和delete1、在C11中我们可以通过关键字defult来强制生成成员函数的默认版本。class A { public: A() default; // 强制生成默认无参构造 A(const A a) default; // 强制生成默认拷贝构造 A operator (const A a) default; // 强制生成默认拷贝赋值 ~A() default; // 强制生成默认析构 A(A a) default; // 强制生成默认移动构造 };2、在C11中我们利用delete来强制不生成成员函数的默认版本。class A { public: A(const A a) delete; // 强制不生成默认拷贝构造 };4.4 final和override这个在多态一节有讲详情可看C进阶多态-CSDN博客5. lambda表达式5.1 lambda表达式基本语法1、lambda表达式本质是一个匿名函数对象跟普通函数不同的是它可以定义在函数内部。2、就语法而言lambda表达式在使用层没有类型因此我们一般用auto去接收lambda对象。3、基本格式[ capture-list ] ( parameters ) - return type { function body }auto add1 [](int x, int y)-int {return x y; }; // 匿名函数对象 cout add1(1, 2) endl;4、[ capture-list ]捕捉列表出现在lambda函数开始位置编译器通过 [ ] 来判断下面代码是否为lambda函数捕捉列表能捕捉上下文的变量供lambda函数使用。5、( parameters )参数列表若不需要参数则可以连同一起省略。6、- return type返回值类型没有返回值时可以省略一般返回值类型明确的情况下也可以省略由编译器自动推导。7、{ function body }函数体和普通函数一样。8、lambda函数还有以下规则// 1、捕捉为空也不能省略 // 2、参数为空可以省略 // 3、返回值可以省略可以通过返回对象⾃动推导 // 4、函数体不能省略 auto func1 [] { cout hello bit endl; return 0; }; func1(); int a 0, b 1; auto swap1 [](int x, int y) { int tmp x; x y; y tmp; }; swap1(a, b); cout a : b endl;5.2 捕捉列表1、lambda表达式中默认只能用函数体和参数中的变量若想用外层作用域的变量就需要捕捉。2、第一种捕捉方式在捕捉列表中显示的传值捕捉和传引用捕捉捕捉多个变量用逗号分隔。int y0; int main() { // 只能用当前lambda局部域和捕捉的对象和全局对象 int a 0, b 1, c 2, d 3; auto func1 [a, b](int x) // a是传值捕捉b是传引用捕捉 { // 值捕捉的变量不能修改引用捕捉的变量可以修改 //a; b; int ret a b x y; // 全局域的变量不用捕捉可以直接用 return ret; }; cout func1(1) endl; return 0; }3、第二种捕捉方式在列表中隐式捕捉表示隐式值捕捉表示隐式引用捕捉。// 隐式值捕捉 // ⽤了哪些变量就捕捉哪些变量 auto func2 [] { int ret a b c; return ret; }; cout func2() endl; // 隐式引⽤捕捉 // ⽤了哪些变量就捕捉哪些变量 auto func3 [] { a; c; d; }; func3(); cout a b c d endl;4、第三种捕捉方式混合捕捉。使用混合捕捉时第一个元素必须是或第一个是后面的必须是值捕捉第一个是后面的必须是引用捕捉。// 混合捕捉1 auto func4 [, a, b] // a、b值捕捉其余引用捕捉 { //a; //b; c; d; return a b c d; }; func4(); cout a b c d endl; // 混合捕捉2 auto func5 [, a, b] { a; b; /*c; d;*/ return a b c d; }; func5(); cout a b c d endl;5、lambda表达式可以直接使用全局变量和静态变量不用捕捉。若lambda表达式定义在全局域则捕捉列表必须为空。// 局部的静态和全局变量不能捕捉也不需要捕捉 static int m 0; auto func6 [] { int ret x m; return ret; };6、默认情况下lambda捕捉列表是被const修饰的也就是说传值捕捉过来的对象不能修改mutable加在参数列表后面可以取消其常属性但是可以修改的对象只是形参。// 传值捕捉本质是⼀种拷⻉,并且被const修饰了 // mutable相当于去掉const属性可以修改了 // 但是修改了不会影响外⾯被捕捉的值因为是⼀种拷⻉ auto func7 []()mutable { a; b; c; d; return a b c d; }; cout func7() endl; cout a b c d endl;5.3 lambda的应用在学lambda表达式之前我们使用的可调用对象就只有函数指针和仿函数对象。有了lambda表达式在某些场景下就可以不用额外写仿函数直接定义可调用对象struct Goods { string _name; // 名字 double _price; // 价格 int _evaluate; // 评价 // ... Goods(const char* str, double price, int evaluate) :_name(str) , _price(price) , _evaluate(evaluate) { } }; struct ComparePriceLess { bool operator()(const Goods gl, const Goods gr) { return gl._price gr._price; } }; struct ComparePriceGreater { bool operator()(const Goods gl, const Goods gr) { return gl._price gr._price; } }; int main() { vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2, 3}, { 菠萝, 1.5, 4 } }; // 类似这样的场景我们实现仿函数对象或者函数指针⽀持商品中 // 不同项的⽐较相对还是⽐较⿇烦的那么这⾥lambda就很好⽤了 sort(v.begin(), v.end(), ComparePriceLess()); sort(v.begin(), v.end(), ComparePriceGreater()); sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) { return g1._price g2._price; }); sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) { return g1._evaluate g2._evaluate; }); // 直接写lambda表达式省去了写仿函数对象的麻烦 return 0; }6. 包装器6.1 functiontemplate class T class function; // undefined template class Ret, class... Args class functionRet(Args...);1、std::function 是一个类模板也是一个包装器。std::function 的实例对象可以包装存储其他的可调用对象包括函数指针、仿函数、lambda、bind表达式等存储的可调用对象被称为std::function 的目标。2、若std::function 不含目标则称它为空。调用空std::function 的目标导致抛出 std::bad_function_call 的异常具体链接std::bad_function_call3、这个是官方文件链接std::function4、函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同 std::function 的优势就是统 ⼀类型对他们都可以进⾏包装这样在很多地⽅就方便声明可调⽤对象的类型。// 包装器 #includefunctional int f(int a, int b) { return a b; } struct Functor { public: int operator() (int a, int b) { return a b; } }; class Plus { public: Plus(int n 10) :_n(n) { } static int plusi(int a, int b) { return a b; } double plusd(double a, double b) { return (a b) * _n; } private: int _n; }; int main() { // 包装各种可调⽤对象 functionint(int, int) f1 f; // 包装函数指针 functionint(int, int) f2 Functor(); // 包装仿函数对象 functionint(int, int) f3 [](int a, int b) {return a b; }; // 包装lambda表达式 cout f1(1, 1) endl; cout f2(1, 1) endl; cout f3(1, 1) endl; // 包装静态成员函数 // 成员函数要指定类域并且前⾯加才能获取地址 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; return 0; }6.2 bind1、bind是一个函数模板它也是一个可调用对象的包装器可以看作函数适配器。2、bind可以用来调整参数个数和参数顺序。// bind绑定 using placeholders::_1; using placeholders::_2; using placeholders::_3; int Sub(int a, int b) { return (a - b) * 10; } int SubX(int a, int b, int c) { return (a - b - c) * 10; } int main() { // bind 本质返回的⼀个仿函数对象 auto sub1 bind(Sub, _1, _2); // 绑定函数指针,_1代表第一个实参_2代表第二个实参 cout sub1(10, 5) endl; auto sub2 bind(Sub, _2, _1); // 绑定函数指针交换参数 cout sub2(10, 5) endl; // 调整参数个数 常⽤ auto sub3 bind(Sub, 100, _1); // 绑死参数a cout sub3(5) endl; auto sub4 bind(Sub, _1, 100); cout sub4(5) endl; // 分别绑死第123个参数 auto sub5 bind(SubX, 100, _1, _2); cout sub5(5, 1) endl; auto sub6 bind(SubX, _1, 100, _2); cout sub6(5, 1) endl; auto sub7 bind(SubX, _1, _2, 100); cout sub7(5, 1) endl; // 成员函数对象进⾏绑死就不需要每次都传递了 //functiondouble(Plus, double, double) f6 Plus::plusd; //Plus pd; //cout f6(move(pd), 1.1, 1.1) endl; //cout f6(Plus(), 1.1, 1.1) endl; // bind⼀般⽤于绑死⼀些固定参数 functiondouble(double, double) f7 bind(Plus::plusd, Plus(), _1, _2); cout f7(1.1, 1.1) endl; // 计算复利的lambda // 前一年的本金利息下一年的本金 // 复利公式本金*1利率^年数-本金 auto func1 [](double rate, double money, int year)-double { double ret money; for (int i 0; i year; i) { ret ret * rate; } return ret - money; }; cout func1(0.05, 100000, 3) endl; // 绑死⼀些参数实现出⽀持不同年华利率不同⾦额和不同年份计算出复利的结算利息 functiondouble(double) func3_1_5 bind(func1, 0.015, _1, 3); // 3年期1.5%利率_1表示传入的本金 functiondouble(double) func5_1_5 bind(func1, 0.015, _1, 5); functiondouble(double) func10_2_5 bind(func1, 0.025, _1, 10); cout func3_1_5(1000000) endl; cout func5_1_5(1000000) endl; cout func10_2_5(1000000) endl; return 0; }本期的分享就到这里如果觉得博主的文章比较对胃口的话可以点一个小小的关注~您的三连是我持续更新的动力~