食品 技术支持 东莞网站建设,所有做运动的网站,网站建设检查整改情况报告,三星网上商城下载C11新特性讲解 前言1.列表初始化1.1{ }初始化1.2std::initializer_list 2.类型推导2.1 auto2.2 typeid2.3 decltype 3.范围for4.STL的变化4.1新容器4.2容器的新方法 5.右值引用和移动语义5.1 左值引用和右值引用5.2 左值引用与右值引用比较5.3 右值引用的使用场景5.4 右值、左值… C11新特性讲解 前言1.列表初始化1.1{ }初始化1.2std::initializer_list 2.类型推导2.1 auto2.2 typeid2.3 decltype 3.范围for4.STL的变化4.1新容器4.2容器的新方法 5.右值引用和移动语义5.1 左值引用和右值引用5.2 左值引用与右值引用比较5.3 右值引用的使用场景5.4 右值、左值引用的其它使用场景5.5 万能引用和完美转发 6.新的类功能6.1默认成员函数6.2类成员变量初始化6.3强制、禁止生成默认函数6.4继承和多态中的final与override关键字 7.可变参数模板7.1可变参数的函数模板7.2遍历可变参数包的方式7.3模板参数包的作用 8.lambda表达式8.1lambda的应用8.2lambda表达式的语法8.3lambda表达式的底层 9.包装器9.1function9.2bind 前言
C11打算出三篇第一篇内容最多最杂后面两篇分别是线程库和智能指针。
1.列表初始化
1.1{ }初始化
在C98中标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如
//也算是兼容C
struct Point
{int _x;int _y;
};
int main()
{int array1[] { 1, 2, 3, 4, 5 };int array2[5] { 0 };Point p { 1, 2 };return 0;
}C11扩大了{ }(初始化列表)的使用范围使其可用于所有的内置类型和用户自定义的类型使用初始化列表时可添加等号()也可不添加。
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 p{ 1, 2 };// C11中列表初始化也可以适用于new表达式中int* pa new int[4] { 0 };return 0;
}创建对象时也可以使用列表初始化方式调用构造函数初始化
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 };return 0;
}1.2std::initializer_list
initializer_list是C11新增加的容器底层就是一个数组。下面看看用法
//initializer_list比较特殊只支持了size()和迭代器
//直接用只有遍历
int main()
{//这里初始化其实是构造初始化先不在意底层initializer_listint l {0, 1, 2, 34};initializer_listint::iterator it l.begin();while (it ! l.end()){cout *it ;it;}cout endl;
}initializer_list平常并无作用但C11对{ }有特殊处理
int main()
{auto l { 0, 1, 2, 34 };cout typeid(l).name() endl;//结果是class std::initializer_listint//我们发现 {0, 1, 2, 34}被处理成initializer_list了cout endl;
}initializer_list真正的应用场景是用于构造其它容器
//C11为vector、map等容器都提供了initializer_list做参数的构造
//原理也很简单遍历initializer_list做插入即可
int main()
{//让vector更像C的原生数组了vectorint v { 0, 1, 2, 3, 4, 5 };for (auto e : v) cout e ;cout endl;//让map这些关联式容器也有了初始化插入多个值的手段mapint, int m { {0, 1}, {1, 0}, {10, 9} };for (auto kv : m){cout kv.first : kv.second endl;}cout endl;
}{}什么时候会识别为initializer_list
struct Point
{Point(int x, int y):_x(x),_y(y){}int _x;int _y;
};
int main()
{//这里是调用构造函数而不是识别为initializer_listPoint p { 1, 2 };//下面这种就是识别为initializer_list//下面会报错因为没有支持initializer_list的构造//无法从“initializer list”转换为“Point”Point p2 { 1, 2, 3 };//这里有两层//内部的{left, 左边}其实是调用了pair的构造函数//外部的{}就被识别为 initializer_listmapstring, string m { {left, 左边} };return 0;
}2.类型推导
2.1 auto
在C98中auto是一个存储类型的说明符表明变量是局部自动存储类型但是局部域中定义局部的变量默认就是自动存储类型(进入作用域时创建离作用域时销毁)所以auto就没什么价值了。C11中废弃auto原来的用法将其用于实现自动类型推导。这样要求必须进行显示初始化让编译器将定义对象的类型设置为初始化值的类型。
//auto 的推导依赖于初始化值
int main()
{int i 0;auto p i;auto pf strcpy;mapstring, string dict { {sort, 排序}, {insert, 插入} };//mapstring, string::iterator it dict.begin();auto it dict.begin();//auto除了简化外也可以增加代码灵活性比如下面//没有写死e的类型下面代码可遍历多种容器for(auto e : dict){ //…… }return 0;
}2.2 typeid
typeid是一个运算符typeid(x)会返回一个type_info类型的对象最常用的就是name()方法可以返回表达式x的类型名。
int main()
{int i 0;auto li { 1, 1, 1, 1 };cout i typeid(i).name() endl; //iintcout li typeid(li).name() endl; //liclass std::initializer_listint//typeid(x).name()返回的只是一个字符串不能用来声明定义变量//下面这样会报错typeid(i).name() a 1;int b 1;return 0;
}2.3 decltype
关键字decltype将变量的类型声明为表达式指定的类型。
//和typeid不同decltype是真的可以用来声明定义变量的
// 原理看编译器实现一般是在语法分析和语义分析阶段来确定表达式的类型进行替换
// decltype的一些使用使用场景
templateclass T1, class T2
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret;cout typeid(ret).name() endl;
}
int main()
{const int x 1;double y 2.2;decltype(x * y) ret; // ret的类型是doubledecltype(x) p; // p的类型是int*cout typeid(ret).name() endl;cout typeid(p).name() endl;F(1, a);return 0;
}3.范围for
底层就是替换为迭代器
int main()
{vectorint v { 0, 1, 2, 3, 4 };for (auto e : v){cout e ;}cout endl;//本质就是替换成下面的写法普通数组也支持范围for就直接是原生指针了//auto it v.begin();//while (it ! v.end())//{// cout *it ;// it;//}//cout endl;return 0;
}4.STL的变化
4.1新容器
arrary目的是替代C的原生数组对越界检查更严格实际不爱用。unordered_map和unordered_set底层是哈希表相比map和set查找速度更快但无法保证遍历有序这两个好用。forward_listlist底层是双向链表forward_list是单向链表并且不含尾指针没有尾插接口尾插少的情况下想省点空间可以用。这个意义也不大。initializer_list前面讲过基本不用。
4.2容器的新方法
增加了initializer_list做参数的构造。提供了cbegin和cend方法返回const迭代器原先begin和end就重载了一份返回const迭代器cbegin和cend可读性好一点可以考虑用用。插入接口提供了右值引用版本和可变参数版本这个后面细讲。构造多了移动构造赋值多了移动赋值。后面讲 5.右值引用和移动语义
5.1 左值引用和右值引用
传统的C语法中就有引用的语法而C11中新增了的右值引用语法特性所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用都是给对象取别名。
1什么是左值什么是左值引用 左值是一个表示数据的表达式(如变量名或解引用的指针)我们可以获取它的地址可以对它赋值左值可以出现赋值符号的左边右值不能出现在赋值符号左边。定义时const修饰符后的左值不能给他赋值但是可以取它的地址。左值引用就是给左值的引用给左值取别名 int main()
{//左值最最最重要的点就是可以取地址// 以下的p、b、c、*p都是左值int* p new int(0);int b 1;const int c 2;// 以下几个是对上面左值的左值引用int* rp p;int rb b;const int rc c;int pvalue *p;return 0;
}2什么是右值什么是右值引用 右值也是一个表示数据的表达式如字面常量、表达式返回值函数返回值(这个不能是左值引用返回)等等右值可以出现在赋值符号的右边但是不能出现出现在赋值符号的左边右值不能取地址。右值引用就是对右值的引用给右值取别名。 右值又分为两种
纯右值内置类型右值不是后面重点。将亡值自定义类型右值重点。
int main()
{double x 1.1, y 2.2;// 以下几个都是常见的右值//右值最最最重要的点就是取不到地址10;x y;fmin(x, y);// 以下几个都是对右值的右值引用int rr1 10;double rr2 x y;double rr3 fmin(x, y);// 这里编译会报错error C2106: “”: 左操作数必须为左值10 1;x y 1;fmin(x, y) 1;return 0;
}3需要注意的是右值是不能取地址的但是给右值取别名后会导致右值被存储到特定位置且可以取到该位置的地址也就是说例如不能取字面量10的地址但是rr1引用后可以对rr1取地址也可以修改rr1。如果不想rr1被修改可以用const int rr1 去引用是不是感觉很神奇这个了解一下实际中右值引用的使用场景并不在于此这个特性也不重要。
int main()
{double x 1.1, y 2.2;int rr1 10;const double rr2 x y;rr1 20;rr2 5.5; // 报错return 0;
}5.2 左值引用与右值引用比较
1左值引用
普通的左值引用只能引用左值不能引用右值。const左值引用既可引用左值也可引用右值。不允许修改保证了安全也可以理解为权限的平移
int main()
{// 左值引用只能引用左值不能引用右值。int a 10;int ra1 a; // ra为a的别名//int ra2 10; // 编译失败因为10是右值// const左值引用既可引用左值也可引用右值。const int ra3 10;const int ra4 a;return 0;
}2右值引用
右值引用只能右值不能引用左值。(加了const也无法引用)但是右值引用可以move以后的左值。
int main()
{// 右值引用只能右值不能引用左值。int r1 10;// error C2440: “初始化”: 无法从“int”转换为“int ”// message : 无法将左值绑定到右值引用int a 10;//加了const也是无法引用的int r2 a;const int r3 a;// 右值引用可以引用move以后的左值//这里move函数是对返回值的左值属性进行了修改修改为右值//实际当中是不应该随便使用move的原因后面讲int r3 std::move(a);return 0;
}5.3 右值引用的使用场景
想明白右值引用的作用就得先知道左值的优势和不足这里为了方便观察就自己写了一份string大家无需在意实现(也不完整)只需要关注构造和赋值等后面的打印明白后面代码那些是深拷贝那些是浅拷贝即可。
namespace mystd
{class string{public:string(const char* str ):_size(strlen(str)), _capacity(_size){//cout string(char* str) endl;_str new char[_capacity 1];strcpy(_str, str);}// s1.swap(s2)void swap(string s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// 拷贝构造string(const string s):_str(nullptr){cout string(const string s) -- 深拷贝 endl;string tmp(s._str);swap(tmp);}// 赋值重载string operator(const string s){cout string operator(string s) -- 深拷贝 endl;string tmp(s);swap(tmp);return *this;}//string operator(char ch)string operator(char ch){push_back(ch);return *this;}后面的不重要后面的不重要后面的不重要~string(){delete[] _str;_str nullptr;}void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}}void push_back(char ch){if (_size _capacity){size_t newcapacity _capacity 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] ch;_size;_str[_size] \0;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};
}1左值引用的使用场景 做参数和做返回值都可以提高效率。
void func1(mystd::string s)
{}
void func2(const mystd::string s)
{}
int main()
{mystd::string s1(hello world);// func1和func2的调用我们可以看到左值引用做参数减少了拷贝提高效率的使用场景和价值func1(s1); //fun1传值需要深拷贝一份func2(s1);// string operator(char ch) 传值返回存在深拷贝// string operator(char ch) 传左值引用没有拷贝提高了效率s1 !; //我们实现的是左值引用返回无需深拷贝return 0;
}2左值引用的短板 但是当函数返回对象是一个局部变量出了函数作用域就不存在了就不能使用左值引用返回只能传值返回。例如bit::string to_string(int value)函数中可以看到这里只能使用传值返回传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。
mystd::string random_string()
{mystd::string ret;//中间过程忽略大家当成随机返回一个string好了return ret;
}int main()
{mystd::string s random_string();return 0;
}下面是重点 为什么说左值这点是短板呢因为像那些核心资源在堆上的自定义类型深拷贝一次的代价还是较大的你本身在栈上出了作用域销毁保存在栈的资源我拿不走也不稀罕。但是堆上的资源我需要的现在调用了拷贝构造却不得不释放后重新分配空间。 右值引用和移动语义解决上述问题 在mystd::string中增加移动构造移动构造本质是将参数右值的资源窃取过来那么就不用做深拷贝了所以它叫做移动构造就是窃取别人的资源来构造自己。
// 移动构造
//移动构造效率还是高的只交换个指针就拿到资源了不用开空间拷贝
string(string s):_str(nullptr), _size(0), _capacity(0)
{cout string(string s) -- 移动语义 endl;swap(s); //拿来吧你
}void swap(string s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}⭐不过上面返回值ret其实并非右值这里是编译器的特殊处理把ret识别为将亡值了。 不仅仅有移动构造还有移动赋值这其中原理一致 在mystd::string类中增加移动赋值函数再去调用random_string不过这次是将 random_string返回的右值对象赋值给ret1对象这时调用的是移动赋值。
// 移动赋值
string operator(string s)
{cout string operator(string s) -- 移动语义 endl;swap(s);return *this;
}编译器对连续的构造有优化是因为这些构造操作是相同且连续的可以合并在一起下面这种情况是完全不同的操作函数VS的编译器就没有冒险优化这个多余的拷贝操作。 上面讲的这些是个人理解语法没有规定必须优化
STL中的容器都是增加了移动构造和移动赋值的大家有心可以去查查。 5.4 右值、左值引用的其它使用场景
按照语法右值引用只能引用右值但右值引用一定不能引用左值吗因为有些场景下可能真的需要用右值去引用左值实现移动语义比如明确某个变量不再使用想转移资源。当需要用右值引用引用一个左值时可以通过move函数将左值转化为右值。C11中std::move()函数位于头文件utility中该函数名字具有迷惑性它并不搬移任何东西唯一的功能就是将一个左值强制转化为右值引用然后实现移动语义。
int main()
{mystd::string s1(hello world);// 这里s1是左值调用的是拷贝构造mystd::string s2(s1);// 这里我们把s1 move处理以后, 会被当成右值调用移动构造// 但是这里要注意一般是不要这样用的因为我们会发现s1的// 资源被转移给了s3s1被置空了。// 后面对s1操作会导致野指针问题,因此move的使用应该慎重mystd::string s3(std::move(s1));return 0;
}STL容器插入接口函数也增加了右值引用版本。 5.5 万能引用和完美转发
1万能引用 写模板的时候函数参数需要即接收左值又接收右值这个时候就需要使用万能引用。写模板时T t代表的是万能引用而不是右值引用。
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);
}
int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}2完美转发 下面可能有点绕调用右值引用做参的函数右值传递过去后会变成左值属性原因很简单右值不允许修改内容但移动构造和移动赋值等需要修改数据交换资源故右值传递过去后会变成左值属性。 10确实是右值但传递过去就变成了左值属性故调用时选择了void Fun(int x)。 有时需要保证传递过去后属性不变化用move的话无疑写死了把左值也搞成右值了而且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; }// std::forwardT(t)在传参的过程中保持了t的原生类型属性。
templatetypename T
void PerfectForward(T t)
{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 右值return 0;
}6.新的类功能
6.1默认成员函数
现在C类中有8个默成员函数
1. 构造函数 2. 析构函数 3. 拷贝构造函数 4. 拷贝赋值重载 5. 取地址重载用处不大 6. const 取地址重载用处不大 7. 移动构造C11 8. 移动赋值运算符重载C11 关于移动构造和移动赋值有下面几点要注意
如果没有实现移动构造且没有实现析构、拷贝构造、拷贝赋值重载中的任意一个编译器才会默认生成一份移动构造具体功能为①对内置类型进行浅拷贝 ②对自定义类型如果有实现移动构造就调用移动构造没有就调用拷贝构造。移动赋值运算符重载的机制和移动构造一致。如果提供了移动构造或移动赋值编译器不会生成拷贝构造和拷贝赋值重载。一般移动构造、赋值都是要自己写的并且常常和拷贝构造、赋值一起出现所以编译器默认生成的条件复杂。 6.2类成员变量初始化
C11允许在类定义时给成员变量初始缺省值默认生成构造函数会使用这些缺省值初始化。
class A
{A()//:_a(1) 相当于这样写{}int _a 1;
};6.3强制、禁止生成默认函数
1强制生成默认函数的关键字default: C11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数但是因为一些原因这个函数没有默认生成。比如我们提供了拷贝构造就不会生成移动构造了那么我们可以使用default关键字显示指定移动构造生成。
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(Person p) default;
private:string _name;int _age;
};
int main()
{Person s1;Person s2 s1;Person s3 std::move(s1);return 0;
}2禁止生成默认函数的关键字delete: 如果能想要限制某些默认函数的生成在C98中是该函数设置成private并且只声明不实现这样只要其他人想要调用就会报错。在C11中更简单只需在该函数声明加上delete即可该语法指示编译器不生成对应函数的默认版本称delete修饰的函数为删除函数。
class Person
{
public:Person(const char* name , int age 0):_name(name), _age(age){}Person(const Person p) delete;
private:string _name;int _age;
};
int main()
{Person s1;//下面会报错无法引用已经删除的函数Person s2 s1;Person s3 std::move(s1);return 0;
}6.4继承和多态中的final与override关键字
C对虚函数重写的要求比较严格有时会因为字母次序不同等导致无法构成重写但这种情况编译器不会报错等发现运行结果不对再来矫正就太麻烦了。故C11引入了override和final两个关键字可以帮助用户检测是否重写。
final修饰虚函数表示该虚函数不能再被重写。修饰类被修饰的类不能被继承。
// final用来修饰虚函数用处不大设计虚函数本就是为了让子类重写实现多态
class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() { cout Benz-舒适 endl; } //这里会报错
};//final还可以用来修饰类被修饰的类不能被继承
class A final
{//……
};class B : public A //这里会报错
{};override: 检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错。
class Car {
public:virtual void Drive() {}
};
class Benz :public Car {
public:virtual void Drive() override { cout Benz-舒适 endl; }
};7.可变参数模板
下面会很怪理解不了就记忆吧。
7.1可变参数的函数模板
// Args是一个模板参数包args是一个函数形参参数包
// 声明一个参数包Args...args这个参数包中可以包含0到任意个模板参数。
template class ...Args
void ShowList(Args... args)
{ //计算参数包中元素个数语法规定cout sizeof...(args) endl;//这里结果是4
}int main()
{//可传递不同类型会自动推导ShowList(1, 2.2, x, string(1111));
}7.2遍历可变参数包的方式
1递归展开
// 递归终止函数
template class T
void ShowList(const T t)
{cout t endl;
}
// 展开函数
//本质是编译时的递归推导利用参数T依次进行解析
template class T, class ...Args
void ShowList(T value, Args... args)
{cout value ;ShowList(args...);
}
int main()
{ShowList(1);ShowList(1, A);ShowList(1, A, std::string(sort));return 0;
}2利用数组初始化
templateclass T
int printArg(T t)
{cout t endl;return 0; //必须要返回值初始化数组
}template class ...Args
void ShowList(Args... args)
{//要初始化数组就要解析参数包进行多次函数调用int arr[] { printArg(args)... };
}int main()
{ShowList(1, A, std::string(sort));return 0;
}7.3模板参数包的作用
模板参数包使得传参数得时候更加灵活有时还会带来效率提升最直观的就是C11STL容器增加的empalce系列接口empalce系列对标的就是push系列和insert系列。 empalce系列实现采用的是万能引用故即有左值版本又有右值版本。
//举点简单例子
int main()
{//以下面为例子vectorstring v;//实现了右值版本的push_back下面实际是先隐式类型转化即构造一次//构造出来的是一个右值故扩容完会进行一次移动赋值//即构造 移动赋值v.push_back(11111);//下面的插入就无需隐式类型转换因为参数没写死//也就无需移动构造直接拿11111构造即可//即构造v.emplace_back(11111);//emplace_back会省下一次移动赋值但移动赋值的消耗不大故很少人采用//其次编译器对前面的情况可能有优化
}STL容器中的empalce相关接口函数都增加了放两个在这里 vector list 8.lambda表达式
8.1lambda的应用
lambda表达式最早是python的后面C和JAVA都引入了可见lambda的便利性我举个例子
//使用sort排序时
//如果待排序元素为自定义类型需要用户定义排序时的比较规则
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 } };std::sort(v.begin(), v.end(), ComparePriceLess());std::sort(v.begin(), v.end(), ComparePriceGreater());
}随着C语法的发展人们开始觉得上面的写法太复杂了每次为了实现一个比较算法都要重新去写一个类如果每次比较的逻辑不一样还要去实现多个类特别是相同类的命名命名空间的污染等这些都给编程者带来了极大的不便。因此在C11语法中出现了Lambda表达式。 用lambda解决上面的问题:
int main()
{//只是体会下便利性看不懂无所谓//lambda相当于一个匿名函数vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2,3 }, { 菠萝, 1.5, 4 } };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._price g2._price; });sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) {return g1._evaluate g2._evaluate; });sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) {return g1._evaluate g2._evaluate; });
}8.2lambda表达式的语法
1lambda表达式书写格式 [capture-list] (parameters) mutable - return-type { statement } 说明
[capture-list] : 捕捉列表该列表总是出现在lambda函数的开始位置编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用即转化为成员变量。捕获的规则比较复杂后面讲。(parameters)参数列表。与普通函数的参数列表一致如果不需要参数传递则可以连同()一起省略一般不省略。mutable默认情况下lambda函数总是一个const函数即捕获的变量被视为constmutable可以取消其常量性。使用该修饰符时参数列表不可省略(即使参数为空)。-returntype返回值类型。用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。返回值类型明确情况下也可省略由编译器对返回类型进行推导一般不写。{statement}函数体。在该函数体内除了可以使用其参数外还可以使用所有捕获到的变量。
int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[]{}; // 省略参数列表和返回值类型返回值类型由编译器推导为intint a 3, b 4;[]{return a 3; }; // 省略了返回值类型无返回值类型auto fun1 [](int c){b a c; }; fun1(10)couta bendl;// 各部分都很完善的lambda函数auto fun2 [, b](int c)-int{return b a c; }; coutfun2(10)endl;// 复制捕捉xint x 10;auto add_x [x](int a) mutable { x * 2; return a x; }; cout add_x(10) endl; return 0;
}2捕获列表说明 捕捉列表描述了上下文中那些数据可以被lambda使用以及使用的方式传值还是传引用。
[var]表示值传递方式捕捉变量var[]表示值传递方式捕获所有父作用域中的变量(包括this)[var]表示引用传递捕捉变量var[]表示引用传递捕捉所有父作用域中的变量(包括this)[this]表示值传递方式捕捉当前的this指针
注意点
父作用域指包含lambda函数的语句块语法上捕捉列表可由多个捕捉项组成并以逗号分割。 比如[, a, b]以引用传递的方式捕捉变量a和b值传递方式捕捉其他所有变量 [a, this]值传递方式捕捉变量a和this引用方式捕捉其他变量捕捉列表不允许变量重复传递否则就会导致编译错误。 比如[, a]已经以值传递方式捕捉了所有变量捕捉a重复在块作用域中的lambda函数仅能捕捉父作用域中局部变量捕捉任何非此作用域或者非局部变量都会导致编译报错。例如捕获全局变量会报错lambda表达式之间不能相互赋值即使看起来类型相同后面讲底层。引用捕获比较特殊不需要加mutable就可以修改可认为mutable就是为了值传递设计的虽然值传递修改不会影响原变量但直接支持修改可能会造成误导。
class A
{void fun(){//不能直接捕获a,b因为a,b不是父域变量//可捕获this指针来访问这里编译器有特殊处理//可直接写a,b无需自己解引用auto f1 [this](){cout a b endl;};}int a, b;
};void (*PF)();
int main()
{auto f1 [] {cout hello world endl; };auto f2 [] {cout hello world endl; };// 此处先不解释原因等lambda表达式底层实现原理看完后大家就清楚了//f1 f2; // 编译失败---提示找不到operator()// 允许使用一个lambda表达式拷贝构造一个新的副本// 至于底层全靠编译器大爹这里拷贝构造需要编译器进行变量捕获函数生成等工作// 并不是简单的拷贝构造auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针也是编译器做工作PF f2;PF();return 0;
}8.3lambda表达式的底层
lambda底层就是一个仿函数只是隐藏了类型不代表没有。
//仿函数
struct ComparePriceGreater
{bool operator()(const Goods gl, const Goods gr){return gl._price gr._price;}
};int main()
{auto f1 [] {cout hello world endl; };auto f2 [] {cout hello world endl; };//运行结果f1 - type:class int __cdecl main(void)::2::lambda_1cout f1-type: typeid(f1).name() endl;//运行结果f2 - type:class int __cdecl main(void)::2::lambda_2cout f2-type: typeid(f2).name() endl;//看起来很像实际是完全不同的类型故无法相互赋值//至于拷贝的实现全靠编译器return 0;
}9.包装器
9.1function
概念本质是一个适配器包装的是可调用对象函数指针、仿函数、lambda使用起来和这三者一致。
//std::function在头文件functional
// 类模板原型如下
template class T function; // undefined
template class Ret, class... Args
class functionRet(Args...);
//模板参数说明
//Ret: 被调用函数的返回类型
//Args…被调用函数的形参包装器的使用
// 使用方法如下
#include functional
int f(int a, int b)
{return a b;
}
struct Functor
{
public:int operator() (int a, int b){return a b;}
};
class Plus
{
public:static int plusi(int a, int b){return a b;}double plusd(double a, double b){return a b;}
};
int main()
{// 函数名(函数指针)std::functionint(int, int) func1 f;cout func1(1, 2) endl;// 函数对象std::functionint(int, int) func2 Functor();cout func2(1, 2) endl;// lamber表达式std::functionint(int, int) func3 [](const int a, const int b){return a b; };cout func3(1, 2) endl;// 类的成员函数写法是语法规定std::functionint(int, int) func4 Plus::plusi;cout func4(1, 2) endl;//第一个参数其实不是Plus属于特殊处理因为临时对象是右值取不了地址//使用起来是正常的std::functiondouble(Plus, double, double) func5 Plus::plusd;cout func5(Plus(), 1.1, 2.2) endl;//第一个参数如果写成Plus*就需要一个左值Plus tmp;std::functiondouble(Plus*, double, double) func6 Plus::plusd;cout func6(tmp, 1.1, 2.2) endl;return 0;
}9.2bind
std::bind函数定义在头文件中functional是一个函数模板它就像一个函数包装器(适配器)接受一个可调用对象callable object生成一个新的可调用对象来“适应”原对象的参数列表。一般而言我们用它可以把一个原本接收N个参数的函数fn通过绑定一些参数返回一个接收M个M可以大于N但这么做没什么意义参数的新函数。同时使用std::bind函数还可以实现参数顺序调整等操作。
1调整参数顺序很少用
//bind底层不讲大家会用即可
//bind(1包装对象, 2绑死部分参数..., 3调整剩下参数顺序placeholders::_1...)
int sub(int a, int b)
{return a - b;
}int main()
{//调整参数顺序1、2颠倒了functionint(int, int) f1 bind(sub, placeholders::_2, placeholders::_1);cout sub(10, 5) endl; //结果5cout f1(10, 5) endl; //结果-5return 0;
}2绑死一部分参数调整参数个数真正作用
int sub(int a, int b)
{return a - b;
}class Plus
{
public:double plusd(double a, double b){return a b;}
};int main()
{//可以绑死一部分参数降低系统接口等复杂接口的调用难度//placeholders::_1placeholders::_2这些顺序是除去绑死部分外的顺序functionint(int) f1 bind(sub, 20, placeholders::_1);cout f1(10) endl; //结果为20 - 10 10//用bind对function包装类成员函数进行优化functiondouble(double, double) f2 bind(Plus::plusd, Plus(), placeholders::_1, placeholders::_2);cout f2(20, 10) endl; //结果为20 10 30return 0;
}
文章转载自: http://www.morning.fbfnk.cn.gov.cn.fbfnk.cn http://www.morning.pgkpt.cn.gov.cn.pgkpt.cn http://www.morning.qwbht.cn.gov.cn.qwbht.cn http://www.morning.krzrg.cn.gov.cn.krzrg.cn http://www.morning.shuangxizhongxin.cn.gov.cn.shuangxizhongxin.cn http://www.morning.mnsmb.cn.gov.cn.mnsmb.cn http://www.morning.fwrr.cn.gov.cn.fwrr.cn http://www.morning.qyrnp.cn.gov.cn.qyrnp.cn http://www.morning.ktqtf.cn.gov.cn.ktqtf.cn http://www.morning.fhkr.cn.gov.cn.fhkr.cn http://www.morning.gjlml.cn.gov.cn.gjlml.cn http://www.morning.gkdqt.cn.gov.cn.gkdqt.cn http://www.morning.stprd.cn.gov.cn.stprd.cn http://www.morning.rnzbr.cn.gov.cn.rnzbr.cn http://www.morning.rwjh.cn.gov.cn.rwjh.cn http://www.morning.qfwzm.cn.gov.cn.qfwzm.cn http://www.morning.hjwkq.cn.gov.cn.hjwkq.cn http://www.morning.pjwml.cn.gov.cn.pjwml.cn http://www.morning.rfzzw.com.gov.cn.rfzzw.com http://www.morning.juju8.cn.gov.cn.juju8.cn http://www.morning.drytb.cn.gov.cn.drytb.cn http://www.morning.xkmrr.cn.gov.cn.xkmrr.cn http://www.morning.kzbpx.cn.gov.cn.kzbpx.cn http://www.morning.yhpl.cn.gov.cn.yhpl.cn http://www.morning.kwdfn.cn.gov.cn.kwdfn.cn http://www.morning.mgmyt.cn.gov.cn.mgmyt.cn http://www.morning.dyzbt.cn.gov.cn.dyzbt.cn http://www.morning.rhqr.cn.gov.cn.rhqr.cn http://www.morning.kbkcl.cn.gov.cn.kbkcl.cn http://www.morning.tjjkn.cn.gov.cn.tjjkn.cn http://www.morning.ntyanze.com.gov.cn.ntyanze.com http://www.morning.nrjr.cn.gov.cn.nrjr.cn http://www.morning.rydbs.cn.gov.cn.rydbs.cn http://www.morning.tbksk.cn.gov.cn.tbksk.cn http://www.morning.sthp.cn.gov.cn.sthp.cn http://www.morning.znqztgc.cn.gov.cn.znqztgc.cn http://www.morning.btrfm.cn.gov.cn.btrfm.cn http://www.morning.sgrwd.cn.gov.cn.sgrwd.cn http://www.morning.rxzcl.cn.gov.cn.rxzcl.cn http://www.morning.rgnq.cn.gov.cn.rgnq.cn http://www.morning.yggwn.cn.gov.cn.yggwn.cn http://www.morning.mfcbk.cn.gov.cn.mfcbk.cn http://www.morning.zczkm.cn.gov.cn.zczkm.cn http://www.morning.nftzn.cn.gov.cn.nftzn.cn http://www.morning.gmztd.cn.gov.cn.gmztd.cn http://www.morning.ylkkh.cn.gov.cn.ylkkh.cn http://www.morning.htbgz.cn.gov.cn.htbgz.cn http://www.morning.nlhcb.cn.gov.cn.nlhcb.cn http://www.morning.mrfgy.cn.gov.cn.mrfgy.cn http://www.morning.huxinzuche.cn.gov.cn.huxinzuche.cn http://www.morning.mnmrx.cn.gov.cn.mnmrx.cn http://www.morning.bylzr.cn.gov.cn.bylzr.cn http://www.morning.gfqj.cn.gov.cn.gfqj.cn http://www.morning.gmrxh.cn.gov.cn.gmrxh.cn http://www.morning.sbwr.cn.gov.cn.sbwr.cn http://www.morning.mttck.cn.gov.cn.mttck.cn http://www.morning.cnxpm.cn.gov.cn.cnxpm.cn http://www.morning.nhdw.cn.gov.cn.nhdw.cn http://www.morning.gjlml.cn.gov.cn.gjlml.cn http://www.morning.drbd.cn.gov.cn.drbd.cn http://www.morning.zylrk.cn.gov.cn.zylrk.cn http://www.morning.nkjnr.cn.gov.cn.nkjnr.cn http://www.morning.wptrm.cn.gov.cn.wptrm.cn http://www.morning.prgyd.cn.gov.cn.prgyd.cn http://www.morning.rxfjg.cn.gov.cn.rxfjg.cn http://www.morning.ndcjq.cn.gov.cn.ndcjq.cn http://www.morning.fssmx.com.gov.cn.fssmx.com http://www.morning.htqrh.cn.gov.cn.htqrh.cn http://www.morning.wqbhx.cn.gov.cn.wqbhx.cn http://www.morning.chbcj.cn.gov.cn.chbcj.cn http://www.morning.xqxrm.cn.gov.cn.xqxrm.cn http://www.morning.rppf.cn.gov.cn.rppf.cn http://www.morning.osshjj.cn.gov.cn.osshjj.cn http://www.morning.zcwwb.cn.gov.cn.zcwwb.cn http://www.morning.fbrshjf.com.gov.cn.fbrshjf.com http://www.morning.tkjh.cn.gov.cn.tkjh.cn http://www.morning.brfxt.cn.gov.cn.brfxt.cn http://www.morning.csnch.cn.gov.cn.csnch.cn http://www.morning.rcwbc.cn.gov.cn.rcwbc.cn http://www.morning.clpdm.cn.gov.cn.clpdm.cn