公司网站如何备案,做网站需要学编程吗,模板包下载网站,wordpress 头像手机旋转C11特性梳理 1. 列表初始化2. auto decltype3. 右值引用3.1. 左右值引用比较3.2. 右值引用的意义3.3. 万能引用与完美转发3.4. 移动构造与移动赋值 4. default delete5. 可变参数模板6. push_back 与 emplace_back7. lambda表达式7.1. 捕捉列表 8. function包装器8… C11特性梳理 1. 列表初始化2. auto decltype3. 右值引用3.1. 左右值引用比较3.2. 右值引用的意义3.3. 万能引用与完美转发3.4. 移动构造与移动赋值 4. default delete5. 可变参数模板6. push_back 与 emplace_back7. lambda表达式7.1. 捕捉列表 8. function包装器8.1. bind C11是继C98之后第二个真正意义上的标准。C11增加的语法特性非常多这里主要介绍实际比较实用的语法。 可以从C官网对C11版本特性进行全面的了解https://en.cppreference.com/w/cpp/11
1. 列表初始化
C11中的{}不仅可以用来初始化数组已经扩展到可以用来初始化C11的一切对象而且使用时等号()可有可无。
void Test1()
{int a1 { 1 };int a2{ 2 };vectorint v1 { 1,2,3,4,5 };vectorint v2{ 1,2,3,4,5 };mapint, string m1 { {1, one}, {2, two} };mapint, string m2{ {1, one}, {2, two} };// 也可以用在new表达式中int* p new int[4]{ 0 };
}注意一点列表初始化如果出现类型截断是会报警告或者错误的。short c 65535; short d { 65535 }; // err STL中的很多容器能支持{}的初始化其实是因为增加了std::initializer_list作为构造函数参数的设计这样使容器的初始化更方便了。operator同理。 std::initializer_list又是什么类型呢
void Test2()
{auto il { 1,2,3 };cout typeid(il).name() endl;
}可以看到{}对象其实就是std::initializer_list类型的对象。 这里建议如果在使用容器时有大括号{}初始化的需求可以选择使用其它地方尽量不要使用来保持语言风格的一致性。
2. auto decltype
auto在C98中是一个存储类型的说明符但实质上没有什么价值。所以C11中auto就弃用了之前的用法将其用于实现自动类型推导。auto此时仅仅只是占位符编译阶段编译器根据初始化表达式推演出实际类型之后会替换auto。
void Test3()
{int i 10;auto p i;auto pf strcpy;cout typeid(p).name() endl;cout typeid(pf).name() endl;
}decltype关键字可以将对象的类型声明为表达式生成的类型。
void Test4()
{char c a;int i 1;decltype(c * i) d1;decltype(strcpy) d2;cout typeid(d1).name() endl;cout typeid(d2).name() endl;
}3. 右值引用
有右值引用自然也有左值引用。其实C11之前所学的引用都叫做左值引用。但无论左值引用还是右值引用都是给对象取别名。 对于左值我们可以取它的地址对它赋值。但经过const修饰的左值可以取它的地址却不能对它赋值。 对于右值是不能取其地址的。 左值可以出现在赋值符号左边右值可以出现在赋值符号右边但反过来就不行。
void Test5()
{int i 1;int* pi i;// 左值引用给左值取别名int ri i; // 变量int* rpi pi; // 变量int r *pi; // 指针解引用double d 2.2;// 右值引用给右值取别名int rr1 10; // 字面常量double rr2 i d; // 表达式返回值
}虽然右值不能被取地址但右值引用后会导致右值被存储到特定位置从而可以取到该位置的地址。
void Test6()
{int rr1 10; // 右值引用cout rr1 endl;cout rr1 endl; // 可以取地址rr1 20; // 可以赋值cout rr1 endl;cout rr1 endl; // 可以取地址
}3.1. 左右值引用比较
左值引用只能引用左值不能引用右值但const左值引用既可以引用左值也可以引用右值。 右值引用只能引用右值不能引用左值但右值引用可以引用move以后的左值。
void Test7()
{int i 10;int r1 i;//int r2 10; // errconst int r3 i;const int r4 10;//int r5 i; // errint r6 10;int r7 move(i);
}3.2. 右值引用的意义
左值引用既可以引用左值又可以通过const修饰引用右值那为什么还要提出右值引用呢 右值引用的加入其实是对左值引用一些缺陷上的补足。 比如说stack的pop接口返回值是voidvoid pop();。但是如果要将返回值进行修改使pop的同时返回其弹出的元素那该如何设计其返回值类型呢 如果设计成传值返回当返回值需要深拷贝时就会对效率造成影响。 如果设计成传引用返回如果是左值引用当出了函数作用域返回的对象就不存在了即引用也失效了。 此时只能设计成右值引用返回。设计成右值引用把将要被销毁的对象的资源转移出来可以继续使用而且不需要担心深拷贝的问题填补了左值引用使用上的一些缺陷。 右值引用可以通过移动构造或移动赋值对返回值是右值将亡值的资源进行转义减少拷贝延长资源生命周期。
3.3. 万能引用与完美转发
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)
{Fun(t);
}void Test8()
{int i 1;PerfectForward(i); // 左值PerfectForward(move(i)); // 右值const int ci 2;PerfectForward(ci); // const 左值PerfectForward(move(ci)); // const 右值
}// 如果想要在传递过程中保持最初的左值或右值属性就需要用到完美转发
templatetypename T
void PerfectForward(T t)
{Fun(forwardT(t));
}3.4. 移动构造与移动赋值
C11STL容器中都是增加了移动构造和移动赋值的。 移动构造和移动赋值是C11新增的两个默认成员函数。 当没有自己实现移动构造函数且没有实现析构、拷贝构造、拷贝赋值中的任何一个函数。那么编译器会自动生成一个默认的移动构造。其对于内置类型成员会执行按字节拷贝(值拷贝)对于自定义类型成员会看这个成员是否实现了移动构造如果实现了就调用没有实现就调用拷贝构造。移动赋值同理。
4. default delete
default和delete的使用可以让我们更好地控制默认函数的生成。 default可以强制某个默认函数的生成。比如提供拷贝构造后移动构造就不生成了那么可以使用default显示指定移动构造生成。 如果不想要某个默认函数生成只需要在函数声明处加上delete即可。delete修饰的函数也称为删除函数。
// 要求delete关键字实现一个类只能在堆上创建对象
class HeapOnly
{
public:HeapOnly(){_str new char[10];}~HeapOnly() delete;void Destroy(){delete[] _str;operator delete(this);}
private:char* _str;
};
void Test9()
{HeapOnly* ptr new HeapOnly;ptr-Destroy();
}5. 可变参数模板
相比C98固定的模板参数C11可变参数模板的的引入无疑是一个巨大的改进。
// 一个基本的函数可变参数模板定义
// Args是一个模板参数包args是一个函数形参参数包包含有大于等于0个参数
templateclass ...Args
void ShowList(Args... args)
{}参数args前面有省略号表示它是一个可变模板参数。把带有省略号的参数称为“参数包”里面包含了N(N0)个模板参数。由于语法不支持使用args[i]这样的方式获取参数需要使用一些特别的方法来一一获取参数包中的参数。
递归展开参数包
void ShowList() {} // 递归终止函数template class T, class ...Args
void ShowList(const T val, Args... args)
{cout ShowList( val , sizeof...(args) 个参数) endl;ShowList(args...);
}void Test10()
{ShowList(1, A, string(one));
}2. 构建数组展开参数包
templateclass T
int PrintArg(const T val)
{cout val ;return 0;
}template class ...Args
void ShowList(Args... args)
{int a[] { PrintArg(args)... };cout sizeof(a) / sizeof(int) endl;
}void Test11()
{ShowList(1, A, string(one));
}{ PrintArg(args)... }将会展开成 { PrintArg(arg1), PrintArg(arg2), PrintArg(arg3), ... }最终会创建一个元素值都为PrintArg返回值的数组。也就是说在构建数组的过程中参数包就展开了而这个数组构建的目的纯粹就是为了在数组构建的过程中展开参数包。
6. push_back 与 emplace_back push_back 与 emplace_back都是在end位置插入一个值。 可以注意到emplace接口支持可变模板参数并且使用了万能引用。那么相对传统的插入接口emplace接口优势到底在哪里呢 在用法上emplace支持可变参数拿到参数后会自己去创建对象简化使用。
void Test12()
{vectorpairint, int v;v.push_back(make_pair(1,1));v.push_back({ 2,2 });v.emplace_back(3, 3);
}在底层上emplace_back是直接构造push_back是先构造再拷贝构造/移动构造。 emplace_back直接构造确实会效率高一点但实际上emplack_back和push_back的使用效果是差别不大的。
7. lambda表达式
lambda表达式实际是一个匿名函数。 lambda表达式语法[capture-list](parameters)mutable-return-type{statement}。
[capture-list]捕捉列表。捕捉列表能够捕捉上下文中的变量供lambda使用同时编译器也会根据[]来判断接下来的代码是否为lambda函数。(parameters)参数列表。和普通函数的参数列表使用方式一致。如果不需要传参()也可以省略。mutable默认情况下lambda函数是一个const函数而mutable可以取消其常量性。在使用该修饰符时参数列表不可省略(即使参数列表为空)。-returntype返回值类型声明。无返回值时可省略返回值类型明确的情况下也可省略交由编译器自行推导返回值类型。{statement}函数体。在函数体内可以使用参数列表的参数和捕捉列表捕获的变量。 从lambda表达式定义中可以知道参数列表和返回值是可选部分捕捉列表和函数体是可为空部分。所以最简单的lambda函数是[]{}。
7.1. 捕捉列表
[var]表示传值方式捕捉变量var[]表示传值方式捕捉父作用域中的所有变量(包括this)[var]表示传引用方式捕捉变量var[]表示传引用方式捕捉父作用域中的所有变量(包括this)[this]表示传值方式捕捉this指针
void Test13()
{int x 1;int y 2;// 默认捕捉(传值捕捉)的变量不能修改// auto swap2 []auto swap2 []()mutable{int tmp x;x y;y tmp;};swap2();cout x y endl;
}即使对于传值捕捉的变量进行了修改也不会对外部实际的变量造成修改。
所谓父作用域是指与lambda所在栈帧平行的栈帧空间。
static int si 1;
void Test14()
{int i 2;auto a [] {cout si endl; cout i endl; };a();
}捕捉列表可由多个项组成并以逗号分割。
void Test15()
{int a, b, c, d, e;a b c d e 1;// 全部传值捕捉auto f1 [](){cout a b c d e endl;};f1();cout a endl;// 混合捕捉auto f2 [, a](){a;cout a b c d e endl;};f2();cout a endl;auto f3 [, a]()mutable{a;cout a b c d e endl;};f3();cout a endl;
}捕捉列表不允许变量重复传递。比如[, this]this捕捉会重复从而导致编译错误。 lambda表达式之间不能相互赋值。
void Test16()
{auto f1 [] {cout hello world; };auto f2 [] {cout hello world; };//f2 f1; // errcout typeid(f1).name() endl;cout typeid(f2).name() endl;
}lambda对象不能相互赋值本质原因是因为类型不同不能相互赋值。 这也说明lambda在使用者角度是匿名的在底层还是有名的。 实际在底层编译器对于lambda表达式的处理完全是按照函数对象的方式处理的。即如果定义了一个lambda表达式编译器会自动生成一个类(为其生成lambda_uuid的类型名)在该类中会重载operator()。
8. function包装器
C中的function本质是一个类模板。 那为什么要有包装器呢
templateclass F, class T
T useF(F f, T x)
{static int count 0;cout count: count endl;cout count: count endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};void Test17()
{// 函数指针cout useF(f, 11.11) endl;// 函数对象cout useF(Functor(), 11.11) endl;// lambda表达式对象cout useF([](double d)-double { return d / 4; }, 11.11) endl;// 函数指针 | 仿函数/函数对象 | lambda(匿名函数) - 都能像函数一样被使用
}可以发现useF函数模板实例化了三份。但如果使用包装器就可以很好地优化上面的问题。 Ret被调用函数的返回类型 Args…被调用函数的形参
void Test18()
{// 函数指针functiondouble(double) f1 f;cout useF(f1, 11.11) endl;// 函数对象functiondouble(double) f2 Functor();cout useF(f2, 11.11) endl;// lambda表达式对象functiondouble(double) f3 [](double d)-double {return d / 4; };cout useF(f3, 11.11) endl;
}8.1. bind
可以把bind看做是函数适配器。bind的作用主要是可以接收一个可调用对象(callable object)同时生成一个新的可调用对象来“适应”原对象的参数列表。使用bind我们可以把一个原本接收 n 个参数的函数 f通过绑定一些参数然后返回一个接收 m 个参数的新函数同时可以做到对参数顺序的调整。 bind的一般使用形式auto newCallable bind(callable, arg_list)。 newCallable本身是一个可调用对象arg_list是一个逗号分隔的参数列表对应callable的参数。当调用newCallable时newCallable会调用callable并传递给它arg_list中的参数。 arg_list中的参数可能包含形如 _n (n 是一个整数)的名字这些参数是“占位符”表示newCallable的参数。这些“占位符”占据了传递给newCallable的参数的“位置”。例如_1为newCallable的第一个参数_2为第二个参数…
class Plus
{
public:static int plusi(int a, int b){return a b;}double plusd(double a, double b){return a b;}
};void Test19()
{functionint(int, int) f1 Plus::plusi;functiondouble(Plus, double, double) f2 Plus::plusd;cout f1(1, 2) endl;cout f2(Plus(), 1.1, 2.2) endl;// 调整参数个数绑死固定参数functionint(int, int) f3 bind(Plus::plusi, placeholders::_1, placeholders::_2);functiondouble(double, double) f4 bind(Plus::plusd, Plus(), placeholders::_1, placeholders::_2);cout f3(1, 2) endl;cout f4(1.1, 2.2) endl;
}