当前位置: 首页 > news >正文

网站建设的流程 步骤wordpress运行缓慢

网站建设的流程 步骤,wordpress运行缓慢,免费下载策划书的网站,上海模板网站建设哥们哥们#xff0c;把书读烂#xff0c;困在爱里是笨蛋#xff01; 文章目录 一、统一的列表初始化1.统一的{}初始化2.std::initializer_list类型的初始化 二、简化声明的关键字1.decltype2.auto nullptr 三、STL中的一些变化1.新增容器#xff1a;array …哥们哥们把书读烂困在爱里是笨蛋 文章目录 一、统一的列表初始化1.统一的{}初始化2.std::initializer_list类型的初始化 二、简化声明的关键字1.decltype2.auto nullptr 三、STL中的一些变化1.新增容器array forward_list unordered系列容器2.新增接口移动构造/赋值 emplace系列接口 四、右值引用和移动语义重要1.什么是左值和右值2.左值引用和右值引用 的引用规则3.右值引用价值之一右值拷贝或赋值给其他对象移动构造和移动赋值4.右值引用价值之二push_back插入数据补充知识const右值引用5.改造自己写的list实现右值引用版本的push_back6.万能引用和完美转发 函数模板std::forward T () 五、类的新功能1.新增成员函数2.类成员的初始化缺省值defaultdelete 六、lambda表达式叫表达式其实是可调用对象1.lambda表达式的用法和本质2.配合多线程使用lambda表达式 七、可变参数模板1.展开参数包的两种方式递归展开借助数组推开参数包2.对比emplace和insert使用语法 和 插入的效率 八、function包装器1.对学过的所有可调用对象进行包装2.逆波兰表达式求解--包装器的使用3.bind绑定的用法 一、统一的列表初始化 1.统一的{}初始化 1. 以前在C语言的时候{}一般用于初始化数组或结构体例如下面代码的初始化方式数组array1和array2可以在创建的同时进行初始化同样结构体p1和p2也可以在定义的时候初始化p2的_y成员变量默认初始化为0. struct Point {int _x;int _y; }; int main() {int array1[] { 1, 2, 3, 4, 5 };int array2[5] { 0 };Point p1 { 1, 2 };Point p2 { 1 };// _y默认初始化成0了return 0; }2. 然而在C11中扩大了{}列表初始化的范围使其能够初始化所有的自定义类型和内置类型实现统一的列表初始化{}并且在初始化的时候如果不想写赋值符号也可以将其省略只保留{}花括号列表。 例如下面的x1 x2变量在初始化时也可以用{}来初始化并且也可以省略同样的结构体p1和数组array1 array2都可以省略但并不推荐省略花括号因为省略之后确实有些奇怪但如果你非要省略那编译器也拦不住你语法规定既可以省略也可以不省略。 列表初始化当然也可以用在new开空间的时候例如p2指针指向空间在开辟的时候内部元素被初始化为0p3结构体指针指向的结构体数组也可以用统一的列表进行初始化。 以上都是列表作用于C语言的对象上面例如数组 结构体等对象。 struct Point {int _x;int _y; }; int main() {//用初始化列表初始化时可以省略 赋值符号//以前的{}只能初始化结构体和数组现在的{}对于自定义类型和内置类型的对象都可以初始化一切皆可用列表初始化。int x1 1;int x2{ 2 };//不要这么玩我们自己能看懂就行int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p1{ 1, 2 };//对结构对象进行初始化c11可以省略// C11中列表初始化也可以适用于new表达式中int* p2 new int[4]{ 0 };Point* p3 new Point[2]{ {1,2}, {3,4} };return 0; }3. 列表在C中作用于对象时同样也可以初始化对象。当然{}列表在初始化对象的时候会调用对象所属类的构造函数。如果你调试下面代码其实就可以发现光标在初始化对象时会跳到类的构造函数处进行对象的初始化。 class Date { public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout Date(int year, int month, int day) endl;} private:int _year;int _month;int _day; }; int main() {Date d1(2022, 1, 1); // old style// C11支持的列表初始化下面会调用构造函数初始化Date d2{ 2022, 1, 2 };Date d3 { 2022, 1, 3 };//构造一个临时对象然后拷贝构造给d3--编译器优化为直接构造return 0; }2.std::initializer_list类型的初始化 1. 下面对于STL容器初始化的方式实际是使用了C11中新增的一个类叫做initializer_list这个类的对象的形式其实就是下面代码赋值符号右边的部分右边就是initializer_list对象所以下面初始化的方式就是将initializer_list对象拷贝给v和vv对象。 int main() {vectorint v { 1, 2, 3 };vectorint vv { 1, 2, 3, 4, 5, 6 }; }2. 那vector容器实现了initializer_list对象的拷贝构造吗当然实现了在C11中所有的STL容器(适配器不算)都实现了initializer_list对象的拷贝构造包括mapsetlistvectorunordered系列等容器在C11版本中都增加了initializer_list对象的初始化。 3. 那initializer_list大概是一个什么类呢其实我们可以将其认为成是一个常量数组这个类的成员函数也很简单只有构造和两个获取迭代器的接口当然他的底层实现也一定不复杂因为他本质就是一个数组维护的成本很低。 4. 我们自己模拟实现一个用initializer_list对象拷贝构造vector对象的函数也很简单只需要遍历initializer_list对象将每一个元素尾插到vector当中即可。例如下面代码实现的拷贝构造因为initializer_list实现了迭代器所以遍历initializer_list可以用范围for来实现注意取initializer_list内部的迭代器类型时要加typename关键字告诉编译器你取的是类内部的内嵌类型。遍历initializer_lis对象中的每一个元素将其拷贝到vector中这样就实现了vector的initializer_list版本的拷贝构造了实现拷贝赋值我们可以让拷贝构造给我们打工然后交换资源即可即为交换指针这样就实现了拷贝赋值是不是很简单呢 namespace wyn {templateclass Tclass vector {public:typedef T* iterator;//vector的迭代器就是原生指针嘛vector(const initializer_listT l){_start new T[l.size()];_finish _start l.size();_endofstorage _start l.size();iterator vit _start;typename initializer_listT::iterator lit l.begin();//取类模板的内嵌类型记得加typename关键字因为编译器不知道你取的是类型还是静态变量或函数while (lit ! l.end()){*vit *lit;}//for (auto e : l)// *vit e;}vectorT operator(initializer_listT l) {vectorT tmp(l);std::swap(_start, tmp._start);std::swap(_finish, tmp._finish);std::swap(_endofstorage, tmp._endofstorage);return *this;}private:iterator _start;iterator _finish;iterator _endofstorage;}; } int main() {wyn::vectorint v { 1, 2, 3, 4, 5, 6 };//用initializer_list对象进行vector的拷贝构造v { 11, 22, 33, 44, 55, 66 };//用initializer_list对象进行vector的拷贝赋值 }5. 除上面那样较为简单的initializer_list的使用方法外向下面这样初始化vector Date 和mapstring, string对象同样也可以使用initializer_list对象。 下面两行代码赋值符号的右边都是混和用了C11的列表初始化和initializer_list初始化。先用列表{}初始化对象然后再把对象当作常量数组的元素构造出initializer_list对象最后将这个对象拷贝构造给v3和dict对象。 int main() {vectorDate v3 { {1,1,1}, {2,2,2}, {3,3,3} };//用Date对象的一个常量对象数组来初始化构造一个vector// 这里{sort, 排序}会先初始化构造一个pair对象然后用pair对象搞一个常量对象数组来初始化构造一个map//里层是pair类的列表初始化{}会调用pair的构造函数外层是initializer_list类会生成匿名initializer_list对象mapstring, string dict { {sort, 排序}, {insert, 插入} };// 使用大括号initializer_list对象进行vector的赋值stl容器也支持这样的赋值方式return 0; }6. C11新增的{}列表统一初始化和用initializer_list对象初始化容器的这两种方式在初始化形式上都是利用了{}进行初始化。所以从语法角度上讲无论是在C还是在C语言中所有的变量和自定义对象都可以用{}花括号进行初始化了包括STL的容器实现统一的列表{ }初始化。 这里的统一实际上是指使用形式的统一列表初始化是C11新增的语法initializer_list是C新增的类这一点我们要分清楚。 二、简化声明的关键字 1.decltype 1. C11新增了关键字decltype此关键字可以帮助我们简化变量的声明typeid仅仅能获取某一类型的信息但decltype可以获取变量的类型后定义出其他的变量例如在F函数中我们想用t1 * t2的结果类型定义出一个变量ret但我们不清楚t1和t2是什么类型我们不能单独用t1或t2类型定义出变量ret。此时就可以用decltype获取t1 * t2的结果类型并用这个结果类型定义出变量ret。 2. 但实际上我们可以用auto推导出t1*t2的结果类型然后用结果类型定义出ret变量所以decltype看起来花里胡哨的但非常的鸡肋好吧真不如auto好用。 templateclass T1, class T2 void F(T1 t1, T2 t2) {//decltype()拿到括号里的类型可以用类型定义出变量 -- 看起来挺牛逼实际也没啥用decltype(t1 * t2) ret t1 * t2;//用t1*t2的类型 定义变量ret做到了typeid做不到的事情//typeid().name拿到的是类型的字符串无法定义变量只能用来打印cout typeid(ret).name() endl;cout ret endl; } int main() {const int x 1; double y 2.2;decltype(x * y) ret; // 用x * y的类型double定义出ret变量decltype(x) p; // 用x的类型int const*定义出p变量cout typeid(ret).name() endl;cout typeid(p).name() endl;F(1, a);//字符其实就是ascii码F(x, y);return 0; }2.auto nullptr 1. auto是非常常见的一种简化变量或对象声明的一个关键字例如在写范围for迭代遍历容器时我们经常不想写容器元素的类型此时一般都会选择用auto来声明容器元素的类型。 auto也可以进行引用推导即定义一个变量此变量的类型用auto推导并且此变量为引用实体的别名但需注意的是auto在进行引用推导的时候必须加也就是auto rx;这样的形式r就是x的别名x的类型auto会自动推导出来。 所以auto还是一个非常不错的关键字在写代码的时候能帮使代码变得更加简洁一些。 2. 下面是C98中NULL这个宏的定义由于NULL既可表示字面值0也可以表示空指针(void*)0为了便于区分字面值和空指针在C11中引入了关键字nullptr此关键字专门用来表示空指针正好能和字面值0区分开来。 // NULL既能表示为整型常量又能表示为指针常量 //所以出于清晰和安全的角度考虑C11中新增了nullptr用于表示空指针。 #ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif三、STL中的一些变化 1.新增容器array forward_list unordered系列容器 1. C11中新增了容器arrayarray是一个固定大小的序列式容器其模板定义中含有非类型模板参数NN代表数组的大小。 这个容器对标的其实就是C语言的静态数组也就是平常我们定义的定长数组。array容器比我们定义的数组要说强那其实就是强在越界访问检查机制上面了array会进行越界访问的assert断言检查而静态数组对于较远的空间位置的越界访问是有可能不报错的因为较远的空间一般不会被OS分配出去所以此时不会报错。 2. 但其实array也没个p用我们都已经有vector了为什么还要用array呢vector也有断言检查越界啊并且vector还是动态开辟的静态我们就用自己定义的静态数组为什么要用你的array啊所以这个容器和关键字decltype一样都是鸡肋。 template class T, size_t N class array; int main() {// arrayint, 10 a1; vs int a2[10];//array相比静态数组有什么优势arrayint, 10 a1;//堆空间int a2[10];//栈空间//越界读不会报错a2[10];a2[20];// 越界写的位置较近会报错较远时不会报错 -- 系统对于普通数组的越界是一种抽查行为//如果越界的空间位置没有被分配出去那一般访问时不会报错如果被分配出去则进行数据覆盖时会发生越权那就会报错a2[10] 10;//报错a2[20] 20;//不报错a1[20];//a1无论读写都可以检查出来a1是array的对象这里调用operator[]函数内部会进行assert检查。//但其实array也没啥用虽然比C语言的普通数组多了越界的检查但既然我已经有了vector那为什么还要用你的array呢//我的vector的operator[]也可以检查越界啊vectorint v(10);//默认给我们初始化成int()也就是初始化为0不比array用的舒服你array还不给我初始化。return 0; }3. C11新增的forward_list是单链表这个也没啥用库里只实现了forward_list的头插和头删尾插尾删并没有实现因为得找尾找尾的效率很低所以forward_list和list相比更是被虐的渣渣不剩了list是带头双向循环链表啊结构比你单链表牛逼多了我有list为啥用你单链表呢 可能单链表唯一的优势就是占用的内存空间少一点每一个结点能省下一个指针也是比较鸡肋的。 4. 比较有用的就是unordered系列容器了这个容器也是C11新增的其实就是我之前文章讲的哈希表底层是用挂哈希桶的方式实现的哈希桶就是用单链表的方式来实现的单链表在这个地方作为某些数据结构的子结构还是有点用的。 2.新增接口移动构造/赋值 emplace系列接口 1. 移动构造和移动赋值放到第四部分展开讲解。 emplace涉及到可变参数模板和右值引用的知识所以emplace放到第七部分讲解。 2. C11其实还新增了C系列的获取迭代器的接口其实也没必要因为原来的迭代器接口已经实现了const和非const两个版本了但C委员会可能怕有的人看不懂这样的函数重载或者const关键字的用法又专门搞出来C系列的获取迭代器的接口专门给const对象获取const迭代器时进行调用。 四、右值引用和移动语义重要 1.什么是左值和右值 1. 左值就是能够取地址能被赋值的数据表达式比如变量或解引用的指针等解引用后的指针实际也是变量。左值既可以出现在赋值符号的左面也可以出现在赋值符号的右面。 2. 右值不能被取地址不能被赋值右值有字面值表达式的返回值传值返回的函数调用的返回值匿名对象等等都是常见的右值。右值在引用时用来表示右值引用。 2.左值引用和右值引用 的引用规则 1. 左值引用只能引用左值但const左值引用可以引用右值。右值引用只能引用右值但右值引用可以引用move以后的左值因为左值move以后其实就变成右值了。 std命名空间中的move函数可以将左值转为右值move其实就是移动语义move后的左值变成将亡值。右值可以分为纯右值和将亡值纯右值例如字面值表达式返回值将亡值例如匿名对象传值返回的返回值也就是临时对象 什么是将亡值呢例如匿名对象传值返回的函数调用的返回值等因为匿名对象在其所在代码行执行完毕后就会被销毁并且传值返回的函数调用实际利用了中间生成的一个临时变量将返回值从被调用的函数栈帧即将销毁时带出这个临时变量的值一旦被接收也就是拷贝或者引用后此临时变量就会被销毁。所以我们把匿名对象传值返回的函数调用的返回值称为将亡值将亡值也是右值。 int main() {// 左值引用只能引用左值不能引用右值。int a 10;int ra a; // ra为a的别名//int ra2 10; // 编译失败因为10是右值// const左值引用既可引用左值也可引用右值。const int ra3 10;//权限平移const int ra4 a;//权限缩小return 0; }int main() {// 右值引用只能右值不能引用左值。int r1 10;// error C2440: “初始化”: 无法从“int”转换为“int ”// message : 无法将左值绑定到右值引用int a 10;int r2 a;// 右值引用可以引用move以后的左值move会返回右值引用int r3 std::move(a);return 0; }3.右值引用价值之一右值拷贝或赋值给其他对象移动构造和移动赋值 1. 左值引用的意义就是减少拷贝提高效率。例如函数参数是左值引用那就可以减少拷贝提高效率事实上函数的返回值也可以是左值引用但必须要求返回值出了函数作用域未被销毁这样才能用左值引用返回减少拷贝提高效率。 那当某个函数的返回值就是一个临时对象呢我们就用不了传引用返回只能用传值返回。如果返回的是内置类型代价还好如果返回的是一个vector vector int 的二维数组呢比如力扣杨辉三角那个题返回的就是二维数组那就需要深拷贝一个二维数组然后随着栈帧销毁原来的二维数组也会被销毁这样的代价就会非常的大了。如果返回的是一棵红黑树呢我们在深拷贝一棵红黑树吗效率未免也太低了吧 2. 所以此时出现了右值引用其价值之一就是为了补齐左值引用的最后一块儿短板即当返回值为传值传递时有可能带来深拷贝导致程序效率降低的问题。 实际上在以前没有右值引用的时候是通过输出型参数来解决传值返回代价太大的问题即在调用函数之前创建好要返回的对象然后将这个返回的对象通过传引用的方式来传参在函数内部通过改变这个输出型参数的方式来改变函数外面提前创建好的对象或变量。 输出型参数确实解决了问题但由于使用起来有些别扭C11又搞出来了右值引用当然右值引用解决传值返回代价大的问题只是右值引用的价值之一而已右值引用在插入数据时也能提高效率减少深拷贝的次数。 3. 下面是自己实现的一个string类在拷贝构造拷贝赋值等函数内部进行了语句的打印方便我们在测试时看到对应的输出结果。并且还实现了一个专门用于测试传值返回的函数to_string()。 wyn命名空间中的移动构造和移动赋值也实现了实际移动构造和移动赋值的原理很简单就是因为右值是将亡值嘛那既然你都是将亡值了何不直接把资源转移给被拷贝或是被赋值的对象呢这样就不需要进行资源的重新拷贝了啊。用移动拷贝和移动赋值对比拷贝构造和拷贝赋值就可以看出来拷贝构造和拷贝赋值即使参数是左值引用在传参时能够减少拷贝提高效率但只要涉及到资源申请的时候拷贝构造和拷贝赋值内部一定是将资源重新拷贝了一份把重新拷贝的资源给到被拷贝对象或被赋值对象这当然一点问题都没有。 但如果是右值的拷贝或赋值呢因为拷贝构造和拷贝赋值的参数是const左值引用自然可以接收右值的传参在函数内部必然还是要进行资源的重新拷贝那是不是有点太浪费了啊此时就需要移动拷贝和移动赋值来提高这样场景下的效率怎么提高呢很简单我们不再拷贝资源而是直接将右值的资源移动给被拷贝对象或被赋值对象省去一次深拷贝。 而像函数传值返回带来的深拷贝这样的问题此时也就迎刃而解了如果我们要将传值返回的返回值接收也就是用返回值初始化接收值时由于返回值是一个右值此时不再匹配拷贝构造或拷贝赋值而是直接匹配移动构造或移动赋值将右值的空间资源通过交换指针这样的浅拷贝的方式来实现转移这样就不会进行深拷贝提高了传值返回接收时带来的深拷贝的问题。 4. 从打印结果就可以看出当拷贝对象是右值的时候不会再调用深拷贝构造或是深拷贝赋值了而是调用移动构造和移动赋值。 5. 对于string这样涉及资源申请的类实现移动构造和移动赋值是非常有必要的因为某些右值拷贝或赋值的场景下直接进行空间资源的转移而不是进行空间资源的再申请也就是空间资源的拷贝效率会很高。 但对于Date这样不涉及资源申请的日期类就没有必要实现移动构造和移动赋值了因为你没有资源啊压根不会出现深拷贝的场景你所有资源的拷贝都是浅拷贝移动语义的接口和拷贝构造、拷贝赋值这些接口没有区别都是进行数据的浅拷贝。只有当某个类涉及到空间资源申请的时候为了防止不必要的深拷贝我们才会在这样的类里面实现移动构造和移动赋值所以大家要区分好右值引用使用的场景。 6. 我们自己实现的string有移动构造和移动拷贝的接口那库里面有嘛库里面当然有我们可以通过调试窗口看一下移动构造之后s1和s3资源的转移。未执行第三行移动构造代码之前s1内部包含字符串hello world但在执行过后就可以发现s1内部的资源被转移到s3去了s1变成了一个空字符串。 所以在C11中容器也都实现了右值版本的移动构造和移动赋值以便于在某些场景下能够提高效率如果拷贝的是一个右值那么就会对应的调用移动构造和移动赋值将资源进行转移而不是进行拷贝一份此时效率就会提高很多。 7. 最后再说一下关于编译器优化的问题一般来说越新的编译器优化的就越优我们看到的现象也就越不明显所以在测试优化时最好采用较久版本的编译器比如说vs2013这样的编译器我用的是vs2022这款编译器优化的很厉害对于左边的场景一般的编译器就算开了优化之后也需要进行一次拷贝构造但vs2022连这一次的拷贝构造都优化没了我都不知道他是怎么做到的只能说优化太强了但大部分编译器比如说2019之前的版本的编译器他们还是比较正常的对于左边场景下也就是先构造临时变量再拷贝构造ret构造拷贝构造会直接优化为构造ret一般编译器都会调用一次拷贝构造但我的编译器没有调没调就没调吧也不影响我学知识嘛。 对于右边的场景来说编译器是不能优化的因为被赋值对象是已经存在的编译器不能直接构造ret必须以赋值重载的方式来拷贝ret那就必须需要一个临时变量将str的资源保存下来然后将临时变量赋值给ret所以这样的情况下编译器无法做出优化那么在平常写代码的时候尽量写出左边的代码左边的效率要比右边高一些因为左边的场景编译器可以进行优化。 4.右值引用价值之二push_back插入数据补充知识const右值引用 1. 上面所说的右值引用在右值进行拷贝或赋值时能够减少深拷贝而是直接将资源进行转移这其实是右值引用的价值之一实际右值引用在容器插入数据的时候也能提高很多的效率。 在C98里面下面三行代码的效率是一样的都需要先进行对象的构造然后将构造出来的对象作为左值传到链表对象的push_back接口push_back内部一定会new一个结点出来将左值参数里的资源重新拷贝一份放到new出来的结点里面。 但在C11里面后两行的代码效率要高于第一行归根结底就是因为参数不再是左值而是右值第二行和第三行代码插入时传参传的都是匿名对象也就是右值实际STL容器除实现移动构造和移动赋值外还实现了右值引用版本的插入数据接口意思是当插入数据为右值的时候不会再调用C98里面const左值引用版本的插入数据接口了因为C11现在实现了右值引用的版本接口在调用时如果是右值插入则一定优先调用右值引用版本的接口。在push_back内部new结点的时候不会重新拷贝一份资源出来而是直接将右值参数的资源转移到new出来的结点内部减少一次深拷贝提高效率。 int main() {listwyn::string lt;wyn::string s1(111111);lt.push_back(s1);//lt.push_back(move(s1));lt.push_back(wyn::string(111111));lt.push_back(111111);//单参数的隐式类型转换return 0; }2. 通过运行结果也可以看出当wyn::string内部实现了移动构造后list插入数据时如果插入数据是右值在new结点调用struct node结点的构造函数时会调用string类的移动构造我们自己实现的string类恰好实现了移动构造所以对于list的插入接口来说当插入数据为右值时相比原来C98只有const左值引用版本的插入接口对于插入数据涉及到资源申请的时候效率能提升很多。 3. 下面再补充一个知识点。除const左值引用外实际上还有const右值引用有人可能会问右值不是不能被修改嘛你搞一个const右值引用有什么意义啊你说的没错哈右值确实不能被修改但右值引用后的引用对象就变成左值了像下面的rr1和rr2都是右值引用但rr1却可以修改rr2不能被修改。这是为什么呢实际上当右值被右值引用之后引用对象就丢失了右值的属性你可以理解为右值被右值引用之后右值会被存储到名为引用对象的一个空间当中此时rr1和rr2实际就变成了左值丢失了右值的属性rr1和rr2都能够取地址因为他们其实各自有一块空间用来存储右值但rr2是不能被修改的因为rr2在引用时带有const属性。 4. 知道上面的知识后也就能解释为什么移动构造或移动赋值或右值引用版本的插入等等接口的参数都是普通右值引用了因为这些接口都要对右值进行资源的移动也就是改变右值引用的引用对象所以右值引用的时候必须是普通的引用如果用const那就无法实现资源移动了也就无法实现效率的提升那右值引用就失去了他的价值。 5. 所以const右值引用用的肯定不多因为我们用右值引用的目的还是为了转移右值内部资源你都无法修改右值引用的引用实体那还转移啥资源啊。 网上有一种说法是右值引用延长了对象的生命周期其实这句话是错误的对象该什么时候销毁还是什么时候销毁右值对象销毁前进行了资源的转移所以实际上是延长了右值资源的生命周期。 5.改造自己写的list实现右值引用版本的push_back 1. 实现右值引用版本的push_back并不困难我们只要重载一个参数为右值引用的push_back就可以了同样的结点的构造函数以及insert接口也都需要相应的实现右值引用版本这些都不困难。 但非常容易忽略的一个点就是上面我们提到的那个知识点右值被右值引用之后引用实体会丢失右值属性转而变为左值所以在传递引用实体时我们必须加move将其属性再变为右值这样才能调用到下一个右值引用版本的接口所以可以看到push_backinsertlist_node等函数在传递右值引用实体时都需要加move保持其属性依旧为右值这样才能在层层调用的过程中让引用实体一直保持为右值。 list_node(T x):_next(nullptr), _prev(nullptr), _data(move(x)) {} iterator insert(iterator pos, T x) {node* newnode new node(move(x));node* cur pos._pnode;node* prev cur-_prev;//prev newnode curprev-_next newnode;newnode-_prev prev;newnode-_next cur;cur-_prev newnode;return iterator(newnode); } void push_back(T x) {insert(end(), move(x));//如果仅仅只传x则会调到insert的普通版本因为右值引用之后x变成左值所以还需要move//insert(end(), std::forwardT(x));//move是强制转成右值了完美转发是保持属性不变。 }2. 在改造lisi过后从打印结果就可以看到当push_back的元素是右值时我们自己实现的list也能够很好的完成右值引用版本的插入不再进行像const左值引用版本的资源拷贝而是直接移动右值资源下面的四行代码相比C98的插入元素接口减少了四次深拷贝效率很不错。所以C11新增的右值引用还是非常有货的不像decltype那样鸡肋而是实实在在的有用。 6.万能引用和完美转发 函数模板std::forward T () 1. 上面在解决右值在右值引用过后属性丢失但是需要连续给多个接口传参的问题时采用了不断move的方式来解决以此来保持传递参数过程中右值的属性能够保持不变。 但这样的解决方式并不被青睐C11又推出了一种新的解决方式叫做完美转发。在了解完美转发之前还需要了解万能引用。 2. 这样的引用方式我们都知道他是右值引用但在模板中并不代表右值引用他有新的名词叫做万能引用所谓万能引用就是什么类型的参数都可以接收包括普通左值普通右值const左值const右值万能引用都可以接收。 推演实例化的时候也会由于参数的不同相应实例化出不同的函数实体如果传过来的是左值在接收的时候会发生引用折叠也就是由两个变为一个从形式上的右值引用折叠为左值引用。 3. 那如果在函数模板内部要调用Fun函数呢我们想保证在调用的时候依旧可以调用到对应参数类型的函数也就是保持参数的属性不变。 如果此时直接调用Fun并将t参数传过去就会发生我们之前所说的问题右值被右值引用过后属性丢失引用对象重新变为左值那t就会作为左值传递给Fun函数所以就只能调用到const左值引用和左值引用版本的Fun函数无法调到右值版本的函数了。 如果想要调用右值版本的函数就需要借助move左值将左值搞成右值这样就可以调用到右值版本的Fun函数了但这样的话又无法调到左值版本的Fun函数了哎呦真是难搞啊属性老是丢失该怎么办啊 C11此时就提出了完美转发函数模板std::forward T ()此函数可以帮助我们在调用某些函数进行传参时保证参数的原有属性不变。有了完美转发后在上面改造list的时候我们也可以不用不断move左值的方式向下传参可以直接使用完美转发这个函数模板保持参数原有属性不变。 void Fun(int x) { cout 左值引用 endl; } void Fun(const int x) { cout const 左值引用 endl; } void Fun(int x) { cout 右值引用 endl; } void Fun(const int x) { cout const 右值引用 endl; }templatetypename T void PerfectForward(T t) {//*** t可能是左值也可能是右值所以如果Fun在函数模板里面作为中转层无法很好保留参数的属性。Fun(t);//t作为参数往下一传其实就是作为左值传下去了。Fun(move(t));//move一下也不行那就又全变成右值引用了。Fun(std::forwardT(t));//完美转发保持属性 } int main() {//***以下形式的形参都可以传给函数模板的万能引用形参PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值 -- const左值move了以后还带有const的属性return 0; }五、类的新功能 1.新增成员函数 1. 在C98中类的默认成员函数有六个在C11中新增了两个默认成员函数分别为移动构造和移动赋值。 如果你自己没有实现移动构造函数并且没有实现析构函数拷贝构造拷贝赋值这三个函数那么编译器会自动默认生成一个移动构造该移动构造对内置类型完成逐字节拷贝对自定义类型需要看该自定义类型是否实现移动构造如果实现移动构造就调用移动构造如果没有实现就调用拷贝构造。 如果你自己没有实现移动赋值函数并且没有实现析构函数拷贝构造拷贝赋值这三个函数那么编译器会自动默认生成一个移动赋值该移动赋值对内置类型完成逐字节拷贝对自定义类型需要看该自定义类型是否实现移动赋值如果实现移动赋值就调用移动赋值如果没有实现就调用拷贝赋值。 当内置类型涉及资源申请的时候编译器默认生成的移动构造或移动赋值是无法完成任务的所以这种时候需要我们自己写移动构造或移动赋值。 2. 编译器默认生成的移动构造和赋值的要求太严苛了那如果我们不小心破坏了生成的条件但是还想使用编译器默认生成的该怎么办呢也很简单可以用一个关键字default强制编译器生成默认的成员函数。 2.类成员的初始化缺省值defaultdelete 1. 由于构造函数对内置类型不处理所以C11在成员变量打了补丁即允许在类定义的时候给成员变量缺省值这个缺省值会在构造函数的初始化列表使用进行成员变量的初始化。别忘记拷贝构造也是构造函数他们两个构成重载关系拷贝构造也有初始化列表。 2. default关键字在上面的部分已经见到过了他的作用其实就是强制编译器生成默认的成员函数。 假设我们现在要实现一个不能被拷贝的类也就是这个类的对象不允许被拷贝这种场景叫做防拷贝某些类的成员并不想被拷贝。我们该怎么解决这个问题呢 C98中可以采用的方式就是拷贝构造函数设置为私有这样在类外面如果有人想要进行对象的拷贝他肯定是调不到拷贝构造函数的这样的解决方式可以防止类外面进行对象的拷贝。 那如果类内的某个公有函数进行了对象的拷贝呢设置为私有的方式就无法解决了因为访问限定符只能限制类外无法限制类内这样的场景又该怎么办呢其实还有一种方法就是只声明不实现这样就会报链接错误因为编译阶段生成的符号表存的拷贝构造函数的地址是无效的所以链接阶段通过地址找拷贝构造函数的定义时就会发生链接错误所以这样的方式也可以防拷贝。 上面都是C98的解决方式C11中没有这么麻烦只需要delete关键字就可以解决在拷贝构造的声明处加上delete修饰即可如果有人进行对象的拷贝则编译期间就会报语法错误。 六、lambda表达式叫表达式其实是可调用对象 1.lambda表达式的用法和本质 1. C觉得C语言的函数指针太恶心了C进而就搞出来仿函数对象仿函数实际是一个重载了operator()的一个类比如下面进行排序的场景调用库的sort时sort函数模板的第三个参数的缺省值就是less T ()仿函数对象默认排升序如果想要排降序只需要传一个greater T ()仿函数对象即可。 2. 那如果需要比较的性质特别多呢比如要比较商品的名字价格评价等等并且要实现从小到大和从大到小的仿函数那我们就需要实现6个仿函数这样岂不是太繁琐了写6个struct类如果类的命名不太好比如按照1-6来命名类那看代码的人每看到一个仿函数都需要向上去找对应的仿函数看看具体是什么功能这样也太麻烦了吧 C此时觉得光有一个仿函数可调用对象有点不太够啊能不能再搞出一个比仿函数用起来还舒服的对象呢此时lambda表达式就登场了lambda表达式的本质也是一个可调用对象此时就无需再实现仿函数类什么的了我们直接写一个lambda表达式给sort传过去这样就可以一行代码搞定传可调用对象的问题了。 3. lambda表达式书写格式[capture-list] (parameters) mutable - return-type { statement } [capture-list] : 捕捉列表该列表总是出现在lambda函数的开始位置编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用。 (parameters) 参数列表。与普通函数的参数列表一致如果不需要参数传递则可以连同()一起省略 mutable 默认情况下lambda传值捕捉变量时默认是const传值捕捉mutable可以取消其常量 性。使用该修饰符时参数列表不可省略(即使参数为空)。 -returntype 返回值类型。用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。返回值类型明确情况下也可省略由编译器对返回类型进行推导。 {statement} 函数体。在该函数体内除了可以使用其参数外还可以使用所有捕获到的变量 4. sort内部进行排序的时候会依次向后两两比较vector的元素在比较时就会用我们传的可调用对象进行比较然后给可调用对象传两个vector元素过去根据比较结果开始进行排序所以lambda表达式和仿函数对象一样都是可调用对象lambda表达式的参数也和仿函数类一样都是Goods类对象的常引用。 5. 值得注意的是lambda表达式的类型我们是写不出来的这个类型是编译器自己生成的所以这也就注定限制了我们使用lambda表达式的语法像下面代码一样我们只能用auto关键字自动推导lambda类型定义出compare对象或者直接拿lambda这个匿名对象进行调用只有这两种使用方式。 补充知识点: 使用宏的时候换行时需要加续行符因为宏必须是完整的一行连空格都不能有。其他场景不需要加续行符。 6. 捕捉列表可以捕捉lambda外面的所有变量但前提是这些变量都得在lambda表达式的上面。lambda的函数体除能够使用参数列表被别人传过来的值外还可以使用捕捉列表里面所捕捉到的变量。 捕捉变量的方式有两种分为传值捕捉和传引用捕捉传值捕捉是const修饰的所以如果想要修改传值捕捉的变量则可以利用mutable来修饰即取消传值捕捉变量的const属性。传引用捕捉并没有const修饰可以直接修改无需可变关键字。 捕捉列表中只有时代表传值捕捉lambda父作用域中lambda表达式向上的所有变量如果有this指针则也可以捕捉this指针。 捕捉列表中只有时代表传引用捕捉lambda父作用域中lambda表达式向上的所有变量如果有this指针则也可以捕捉this指针。 捕捉列表不允许变量重复传递否则就会导致编译错误。 比如[, a]已经以值传递方式捕捉了所有变量再次捕捉a变量重复。 7. 我们还需要了解一下lambda底层到底是什么其实lambda底层就是仿函数对象编译器自动生成了一个lambda的类并在这个类里面实现了operator()这个类是编译器自己生成的每一个lambda的类都是不一样的执行lambda函数体实际还是执行lambda类里面的operator()所以本质上lambda和仿函数一样只不过仿函数的类是我们自己写的lambda的类是编译器自己随机生成的。 8. C允许使用一个lambda表达式拷贝构造一个新的副本但不允许lambda表达式之间相互赋值。在了解lambda的底层之后我们就可以理解了因为各个lambda表达式的类都不一样所以各个lambda表达式对象都没有关系不能调用拷贝赋值函数。 但拷贝构造还是可以的因为lambda还没初始化出来嘛赋值是已经存在一个lambda对象了拷贝构造就相当于搞出来一个lambda的副本和原来的lambda共用编译器随机生成的同一个类。 void (*PF)(); int main() {auto f1 []{cout hello world endl; };auto f2 []{cout hello world endl; };//f1 f2; // 编译失败---提示找不到operator()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针本质都是可调用对象嘛PF f2;PF();return 0; }2.配合多线程使用lambda表达式 1. 假设我们期望两个线程并发式的从0打印到99我们可以选择实现两个函数然后分别让线程并发的去运行这样的方式其实就是给线程传函数指针函数指针就是可调用对象嘛线程刚好可以执行。 2. 除上面那种方式外我们其实还可以利用lambda表达式在创建线程的同时传可调用对象lambda过去lambda引用捕捉一下i就可以要注意区分参数列表和捕捉列表虽然捕捉列表看上去像是在传参但实际并不是传参仅仅是捕捉变量而已。 3. 下面的使用方式灵活的体现了C面向对象的特性我们将线程当作对象存储到容器vector里面创建线程的同时将lambda可调用对象传给线程这样所有的线程就会同时并发的打印0-99数字。 七、可变参数模板 1.展开参数包的两种方式递归展开借助数组推开参数包 1. C新引入了可变参数模板的语法即函数的参数可为一个参数包这个参数包中可以包含任意个数的函数形参想打印出参数包中参数的个数可以通过sizeof…()函数取到参数包中参数的个数。 2. 下面是第一种展开参数包的方式即递归方式调用ShowList递归结束条件就是参数个数为0的ShowList()函数在不断递归调用ShowList的过程中参数个数会逐渐减少直到args…的个数为0时此时递归结束调用无参的ShowList即可。 3. 下面是第二种展开参数包的方式上面那种方式需要多增加一个模板参数T用T定义出的val来表示单个的参数。 下面是通过辅助数组arr来实现推开参数包在推的过程中调用PrintArg来打印出每个参数是什么每推出来一个参数就会调用对应的PrintArg函数进行参数的打印。 第一种屏蔽的方式就是逗号表达式他会在推参数包的过程中顺便将arr数组初始化为0但其实不初始化也没有关系直接推参数包也行。 2.对比emplace和insert使用语法 和 插入的效率 1. C11新引入的emplace接口既有可变参数模板又有万能引用看起来很牛嘛那他真的比insert接口效率高很多嘛实际上并没有高很多可能也就强那么一点点。 2. 在使用形式上emplace支持直接传参数不用自己构造键值对调用像push_back和insert这样的接口时需要先构造出键值对然后调用移动构造版本的push_back函数进行键值对的插入而emplace直接传pair键值对的俩参数就可以emplace会直接用这个参数包构造出pair对象并将对象插入到mylist里面。 所以在使用形式上emplace比push_back更加简洁一些因为只需要传参数就可以。当然你如果也想构造键值对进行插入emplace也是可以做到的。 3. 在效率上面两者的差距也不大一个是直接构造一个是先直接构造然后再移动构造。所以emplace也没有那么的牛因为移动构造的代价也很低只能说emplace比insert稍微强一点吧emplace对标insertemplace_back对标push_back 但如果string没有实现移动构造的话那两者差距还是挺大的一个是直接构造一个是先直接构造然后再深拷贝。但我们不用担心这一点下面代码是拿我们自己实现的string测试的STL里面的容器哪个没有实现移动构造啊所以这两个接口的效率差距也不大甚至可以忽略不计。 下面是string实现了移动构造的场景 下面是string没有实现移动构造的场景 八、function包装器 1.对学过的所有可调用对象进行包装 1. function就像范围for一样都是语法糖看起来很牛逼底层的实现并不复杂function用起来还是非常香的语法很简单并且很好用C委员会总算干点儿正事了。 function学起来并不困难他其实就是将我们原来所学的可调用对象例如函数指针仿函数对象lambda进行包装使其变成一个新的可调用对象这个可调用对象就是包装器有人说为什么要包装啊以前的可调用对象用起来不是挺好的吗你说的没错但是包装过后无论你是什么类型的可调用对象在使用形式上统一都是包装器定义出来的对象的使用形式在语法上更加的便捷。 2. 对于下面函数模板useF来说如果传函数指针仿函数对象lambda就会导致模板实例化出三份不同的函数实体来导致模板的效率有些低。但如果我们将上面三个可调用对象进行包装那就只会实例化出一份函数实体但是却依靠这一份函数实体实现了三种可调用对象的调用不用像原来一样实例化出三份函数实体分别去调用函数指针仿函数对象lambda这就是包装器带来的价值。 3. 事实上你可以这么理解包装器包装器也是一个仿函数对象他的内部也实现了operator()但他的operator()内部又调用了包装器包装的可调用对象的operator()所以包装器这个类可以理解为他内部封装了三个可调用对象的operator()在调用时根据不同的可调用对象去调用包装器内部对应的operator()。 这里有点像多态可调用对象是函数指针那就调对应封装函数指针的包装器。可调用对象是函数对象那就调对应封装函数对象的包装器。可调用对象是lambda那就调对应封装lambda的包装器。 2.逆波兰表达式求解–包装器的使用 1. 像下面这样命令和动作对应的场景其实就可以用包装器让包装器包装lambda然后把string和包装器对象构成的键值对存储到map里面建立命令和动作的映射关系。 在调用对应的lambda时我们就不用写一长串lambda然后加上(x,y)这样的调用方式了而是直接用function包装器加上(x,y)这样的调用方式。 所以function用起来还是很香的。 3.bind绑定的用法 1. bind绑定其实和function是一种适配器模式就像vector适配出stacklist适配出queue一样。 bind的用法也是花里胡哨的下面列出了两种bind的用法。 一种是调整参数的顺序通过调整占位对象来实现。 另一种是固定绑定参数在绑定类成员函数时function要在模板参数第一个位置加类名在调用的时候也需要先传一个该成员函数所属类的对象平常我们直接传匿名对象了就这样用起来有点烦所以可以在绑定类成员函数的同时固定第一个参数为类的匿名对象这样在使用包装器调用类成员函数的时候就不需要再显示传一个匿名对象了。 2. 下面是绑定在控制参数时的用法我们可以在绑定的同时给可调用对象显示传参数也可以用占位对象_1 _2 _3…等等来替代参数位置等待包装器调用的时候再传参数。
http://www.tj-hxxt.cn/news/216681.html

相关文章:

  • 做单页网站vip视频网站如何做
  • 云南网站优化公司织梦修改网站主页
  • 在百度怎么创建自己的网站中山快速做网站公司
  • 网站建设j基本步骤湛江网站制作江网站制作
  • 湖北立方建设工程有限公司网站山东网站建设报价
  • 有哪些网站是可以做会计题目的网上如何做广告
  • 电子商务网站建设与管理课后答案江苏无锡网站推广及优化
  • 网站建设服务周到许昌市网站建设科技
  • 网站置顶jq苏州网站建设公司有哪几家还可以的
  • 网站优化设计方案怎么做室内设计最好的公司
  • 手机网站开发怎么收费上海软件系统开发公司
  • 营销网站设计实验网络推广 网站制作
  • 省建设干部培训中心网站wordpress里面主题文档怎么编辑
  • 电子商务网站建设策划书宣传网站建设的意义
  • 自己怎么创建一个网站公交建设公司的官网
  • 拥有域名后怎么搭建网站网站推广方案怎么写的
  • 各类网站排名手机版cad简单制图
  • 台北网站建设贪玩游戏官网
  • 网站建设的基础深圳网站建设服务电话
  • dede网站mip西安易扬众和网站建设
  • 注册网站要求北京高端网站建设飞沐
  • 酒店 网站构建遂宁网站建设公司哪家好
  • 简述jsp网站架构wordpress用户名和密码
  • wix做的网站 网址是什么云主机推荐
  • 牛皮纸东莞网站建设技术支持胶州网站优化
  • 企业网站建立庆云县有几家苏州知名网站建设设计公司
  • 图标的网站网站优化哪里可以做
  • 饰品网站建设规划书一个域名多个网站
  • 网站备案时间湛江人怎么样
  • c2c网站 多钱怎么改网站标题