目录
1. C++简介2. 统一的列表初始化2.1. { }初始化2.2. std: :initializer_list 3. 声明3.1. auto3.2. decltype3.3. nullptr 4. 范围for循环5. STL的变化6. 右值引用和移动语义6.1. 左值引用和右值引用比较6.2. 右值引用使用场景和意义6.2.1. 移动构造6.2.2. 移动赋值 6.3. 万能引用和完美转发 7. 默认移动构造、默认移动赋值7.1. default和delete 8. 可变参数模板8.1. 递归解析参数包、展开参数包8.2. emplace相关的接口函数 9. lambda表达式9.1. lambda表达式语法9.2. lambada表达式9.3. lambda表达式的底层 10. 包装器10.1. function包装器10.2. bind包装器
1. C++简介
C++11标准相比于C++98/03标准,带来了数量可观的变化,对C++03作了约600个补正修订,增加了约400个新特性,eg:final、override、unordered系列容器、default、delete等。C++11能够更好的应用系统开发和库开发,语法更加泛化和简单化、更加安全和稳定、提升开发效率,项目开发应用较多。2. 统一的列表初始化
2.1. { }初始化
一切皆列表初始化。
C++11标准,允许对变量、数组、结构体、用户自定义类型、new表达式进行同一的列表初始值设定,使用列表初始化,可添加=,也可不添加=。
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>#include<string>using namespace std;struct Person {int age;int height;};class Date {public:Date(int year, int month, int day):_year(year),_month(month),_day(day){ }private:int _year;int _month;int _day;};int main(){//一切皆列表初始化,=可加、也可不加int a1 = { 10 }; //变量int a2{ 10 };Person p1 = { 18, 100 }; //结构体Person p2{ 18, 100 };int arr1[10] = { 1, 2, 3 }; //数组int arr2[10]{ 1, 2, 3 };//C++98、C++11都支持//多参数构造函数支持隐式类型转化,构造+拷贝构造,新的编译器会优化成直接构造Date d1 = { 2022, 1, 1 }; //用户自定义类型Date d2{ 2022, 1, 1 };//单参数构造函数支持隐式类型转化,构造+拷贝构造,新的编译器会优化成直接构造string s = "lala";//new表达式Date d1(2022, 2, 1);Date d2(2022, 4, 1);Date d3(2022, 3, 1);Date* p3 = new Date[3]{d1, d2, d3}; //拷贝构造Date* p4 = new Date[3]{ {2024, 4, 15}, {2024, 4, 16},{2024, 4, 17} }; //构造+拷贝构造,优化直接构造Date* p5 = new Date(2023, 1, 1); //构造Date* p5 = new Date{ 2024, 1, 1 }; //构造+拷贝构造,优化直接构造 //Date d5 = (2024, 2, 1); //不支持}
2.2. std: :initializer_list
int main(){auto il = { 1, 2, 3, 4 };cout << typeid(il).name() << endl;return 0;}
initializer_list是C++11引入的一个模板类,它允许我们用{ }初始化一个对象,和vector一样,可以表示某种特定类型值的数组,成员函数有无参构造、迭代器begin、end、size。
initializer_list限制:
a.它是只读的,不能修改它的内容。
b.它是轻量级的,只存储元素指针和大小,不存储元素本身,initializer_list对象大小为8字节,只存储了两个指针,一个指针指向数组的开始,一个指针指向数组的末尾。
c.临时数组的声明周期和initializer_list对象生命周期一样。
initializer_list一般是用作于构造函数的参数,STL中有不少的容器增加了std: :initializer_list作为参数的构造函数,这样初始化对象更方便,也可以作为operator=( )的参数,这样对象就可以用大括号进行赋值。int main(){ //两者都是构造+拷贝构造,编译器直接优化为直接构造Date d = { 2024, 4, 16 }; //参数个数是受限制的vector<int> v = { 1, 2, 3, 4 }; //参数个数不受限制for (auto& e : v){cout << e << ' ';} cout << endl; vector<int> v1({ 1, 2, 3 }); //直接构造return 0;}
vector(initializer_list<T> il) //initializer_list作为参数的构造函数{reserve(il.size());for (auto& e : il){push_back(e);}}
int main(){ pair<string, string> p1("sort", "排序"); pair<string, string> p2("left", "左边"); map<string, string> m1 = { p1, p2}; //initializer_list<pair>作为参数构造+拷贝构造,编译器优化为直接构造 map<string, string> m2 = { {"right", "右边"}, {"count", "数目"} }; /*pair<const char*, const char*>与pair<const string, string>是不同的类型,需要先调用pair构造(在pair类中套了其他模板, 用于支持不同类型pair的构造)。其次initializer_list<pair>作为参数构造+拷贝构造,编译器优化为直接构造*/return 0;}
template<class T1, class T2>struct pair {pair(const T1& first, const T2& second) :_first(first),_second(second){ }template<class U, class V> //pair允许在内部套其他大的模板,达到用不同类型的pair去构造其他类型的piarpair(pair<U, V>& p):_first(p._first) ,_second(p._second){ }T1 _first;T2 _second;};
3. 声明
3.1. auto
C++98中auto是自动存储类型的说明符,表示变量是局部自动存储类型,但是在局部域定义的局部对象默认自动存储类型,所以在C++11中就摒弃了auto原来的作用,此处auto只用于实现自动类型推导,要求必须显示初始化,让编译器将定义对象的类型设置为初始值的类型。3.2. decltype
decltype将变量的类型声明为表达式指定的类型。
int main(){ const int x = 3; decltype(x) y = 4; //顶层const,const修饰变量本身,decltype会去掉constcout << typeid(y).name() << endl;const int* p = &x;decltype(p) a1 = nullptr; //底层const,const修饰指针指向的内容,decltype不会去掉constcout << typeid(a1).name() << endl;return 0;}
顶层const,const修饰变量本身,decltype会去掉const。底层const,const修饰指针指向的内容,decltype不会去掉const。
typeid( ).name( )求变量的类型,并进行打印。
3.3. nullptr
因为NULL被定义为字面量0,NULL既能表示为指针常量,又能表示为整形常量。C++11将nullptr定义为空指针。4. 范围for循环
STL容器只要支持迭代器,就支持范围for,打印容器中的元素。5. STL的变化
新的容器:unordered_map、unordered_set、单链表(forward_list)、静态数组(array)。
新接口:
a.一些无关重要的方法,eg:cbegin、cend返回const迭代器,而begin、end也可以返回const迭代器。
b.initializer_list系列的构造。
c.push、insert、emplace等增加了右值引用版本,提高了效率。
d.容器新增的移动构造、移动赋值,它们可以减少拷贝,提高效率。
6. 右值引用和移动语义
6.1. 左值引用和右值引用比较
在C语言中,左值是可以被修改的,而右值不能被修改。而C++中这样定义是不准确的。
左值是表达式,常见形态有变量、指针、指针解引用后的值、函数返回值(传引用返回)、const修饰的变量。右值是表达式,常见形态有字面常量、表达式的返回值(运算)、函数返回值(传值返回)。
左值可以被取地址,一般可以对它进行赋值,const修饰后的左值,不能给它赋值,可以出现在赋值符号的左边。右值不可以被取地址,不能对它进行修改,不可以出现在赋值符号的左边。
左值引用是给左值取别名,左值引用不能给右值取别名,但是const左值引用可以给右值取别名。右值引用是给右值取别名,右值引用不能给左值取别名,但是右值引用可以给move(左值)取别名。
引用都是取别名,不开空间存储。底层,引用是用指针实现的,左值引用是存储当前左值的地址,右值引用是将右值拷贝给栈中的一块临时空间,存储这个临时空间的地址。可以对右值引用变量取地址,也可以对它进行修改。
int main(){ //左值:a、p、*p、c、传引用返回int a = 0; int* p = new int(10);const int c = 6;fun1();//左值可以给左值取别名int* ptr = &a;//左值引用:给左值取别名int& aa = a;int*& pp = p;const int& cc = c;int& ret1 = fun1();//右值:10、运算、传值返回 注意:右值不能取地址,不能出现在左边10;a + c;fun2();//右值引用:给右值取别名int&& d = 10;int&& e = a + c;int&& ret2 = fun2(); //左值引用不能给右值取别名,const左值引用可以给右值取别名const int& g = 10;//右值引用不能给左值取别名,右值引用可以给move(左值)取别名int&& h = move(a); return 0;}
6.2. 右值引用使用场景和意义
6.2.1. 移动构造
//移动构造 — 右值(将亡值)string(string&& s){cout << "string(string&& s)" << endl;swap(s);}
右值引用,移动语义:右值引用是对右值的引用,移动语义是将右值的资源直接转移给左值对象,而不需要进行开销较大的深拷贝。移动语义是C++11引入的一个新特性,它允许我们将资源从一个对象转移到另一个对象,以提高效率和性能 ——》 移动构造、移动赋值。 总结:进行深拷贝的类需要进行移动构造、移动赋值,进行浅拷贝的类不需要进行移动构造、移动赋值。
6.2.2. 移动赋值
//移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s)" << endl;swap(s);return *this;}
6.3. 万能引用和完美转发
万能引用:是一种既能接收左值、又能接收右值的引用,它的形式为T&&(T为模板参数),传左值,它就为左值引用、传右值,它就为右值引用,它不是C++的一个新特性,而是利用模板类型推导和引用折叠的规则来实现的功能。
引用折叠:是C++出现的新概念,用于处理引用的引用情况。在C++中,当创建一个引用的引用时,引用规则会将其中的引用消除,只保留一个单引用。& 、& -> & ;& 、&& -> & ;&& 、&& -> &&。
完美转发:forward(t), 是C++引入的新特性,在函数模板中保持参数的原属性,本身为左值,属性不变; 本身为右值(但经右值引用引用后,属性变为左值),将属性转变为右值,相当于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; }/*1.引用折叠:&、&&->&、&&、&&->&& 不能单纯的把下面的模板理解为右值引用的模板* 2.万能引用:传左值,它就为左值引用、 传右值,它就为右值引用* 3.完美转发forward<T>(t):保持属性,本身是左值,属性不变、 本身是右值(被右值引用引用后,属性为左值),转化为右值,相当于move以下* 4.move:已知其为右值(将亡值),右值引用引用后,属性为左值,将左值属性变为右值属性*/template<class T> void PerfectForward(T&& t) //万能引用{Fun(forward<T>(t)); //完美转发}int main(){int a = 10; PerfectForward(a); //左值PerfectForward(move(a)); //右值const int b = 6; PerfectForward(b); //const左值PerfectForward(move(b)); //const右值return 0;}
7. 默认移动构造、默认移动赋值
C++类有八大默认成员函数:构造函数、拷贝构造函数、拷贝赋值重载、析构函数、移动构造函数、移动赋值运算符重载、取地址重载、const取地址重载。默认成员函数就是我们不写,编译器默认生成。重要的为前6个。
默认移动构造:如果我们没有显示实现移动构造,且没有显示实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,编译器就会自动生成一个默认移动构造。默认生成的移动构造构分为两部分,对于内置类型完成值拷贝、对于自定义类型,看它是否实现了移动构造,实现了就调用移动构造,没有实现,就调用拷贝构造。
默认移动赋值:如果我们没有显示实现移动赋值,且没有显示实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,编译器就会自动生成一个默认移动赋值。默认生成的移动赋值分为两部分,对于内置类型完成值拷贝、对于自定义类型,看它是否实现了移动赋值,实现了就调用移动赋值,没有实现,就调用拷贝赋值。
7.1. default和delete
default关键字指示编译器强制生成对应的默认函数。
限制某些默认成员函数的生成:方法一:在C++98中,将该函数设置为private,只声明不实现。原因:将函数的定义设置为private,尽管类外访问不到,但在类中可以进行访问、若只声明,设置为公有,会出现链接错误,很可能在外面别人手动帮你实现该函数的定义(函数可以在类中声明,类外定义)。 方法二:在函数的声明后加上=defaule即可。delete关键字指示编译器不生成对应函数的默认版本,被=delete修饰的函数称为删除函数。
8. 可变参数模板
//可变参数的函数模板template<class ...Args> //Args是模板参数包 void ShowList(Args ...args) //args是函数形参数包 参数包里面函数0~N个模板参数{ //... }
带有省略号的称为"参数包", 它里面包含0~N(N>=0)个模板参数。Args是模板参数包 、args是函数形参数包。
我们无法直接拿到参数包args中每个参数的类型和值,语法上不支持通过args[i]来获取可变参数,因为这样是运行时解析参数。而此处是模板,是编译时解析参数。
8.1. 递归解析参数包、展开参数包
void _ShowList() //递归终止函数{cout << endl;}template<class T, class ...Args>void _ShowList(const T& val, Args ...args) {cout << val << " ";_ShowList(args...);}//拿到每个参数值和类型,编译时递归解析template<class ...Args>void ShowList(Args ...args){_ShowList(args...);}void _ShowList(const string& s){cout << s << " ";_ShowList();}//实例化以后,推演生成的过程void _ShowList(const char& ch, string s){cout << ch << " ";_ShowList(s);}void _ShowList(const int& val, char ch, string s){cout << val << " ";_ShowList(ch, s);}void ShowList(int val, char ch, string s){_ShowList(val, ch, s);}int main(){ShowList();ShowList(1);ShowList(1, 'A');ShowList(1, 'A', string("sort"));return 0;}
template <class T>int PrintArg(T t) {cout << t << " ";return 0;}//展开函数template <class ...Args>void Show(Args... args){//在数组构造的过程中,展开参数包int arr[20] = { PrintArg(args)... }; //列表初始化,最终创建出一个元素值都为0的数组 cout << endl;}int main(){Show();Show(10);Show(10, 'B');Show(10, 'B', string("Talent")); return 0;}
在数组创建的时候,展开参数包。 { PrintArg(args)… }为列表初始化,用来初始化一个变长数组,它将会展开成(PrintArg(args1))、(PrintArg(args2) . . . (PrintArg(argsn),最终创建出一个元素值都为0的数组。8.2. emplace相关的接口函数
emplace_back、push_back都是C++标准库中的成员函数,都是在容器的尾部添加一个新元素。push_back接受一个值作为参数,并将这个值的副本添加到容器的末尾,可能会导致额外的性能开销,因为要复制整个对象。emplace_back是在容器的尾部直接构造新的元素,它使用对象的构造函数来创建对象。
emplace系列,直接写插入对象参数:对于浅拷贝类的对象,减少(一次)拷贝构造,效率提高 、深拷贝类的对象,减少(一次)移动构造,效率提升不是很明显,与push_back无区别。
void test1(){//直接写插入对象参数,深拷贝类的对象,减少(一次)移动构造,效率提升不是很明显,与push_back无区别list<bit::string> lt1;bit::string s1("xxxx");lt1.push_back(s1);lt1.push_back(move(s1));cout << endl;bit::string s2("vvvvvv");lt1.emplace_back(s2);lt1.emplace_back(move(s2));cout << endl;lt1.push_back("lala");lt1.emplace_back("haha"); //深拷贝类的对象,有移动构造、赋值,右值cout << "========================================" << endl;list<pair<bit::string, bit::string>> lt2;pair<bit::string, bit::string> kv1("xxxx", "yyyy");lt2.push_back(kv1);lt2.push_back(move(kv1));cout << endl;pair<bit::string, bit::string> kv2("xxxx", "yyyy");lt2.emplace_back(kv2);lt2.emplace_back(move(kv2));cout << endl;lt2.emplace_back("xxxx", "yyyy"); //cout << "=============================================" << endl;//直接写插入对象参数,浅拷贝类的对象,减少(一次)拷贝构造,效率提高list<Date> lt3;Date d1(2022, 10, 2);lt3.push_back(d1);lt3.push_back(move(d1));cout << endl;Date d2(2024, 10, 1);lt3.emplace_back(d2);lt3.emplace_back(move(d2));cout << endl;lt3.emplace_back(2023, 10, 1); //浅拷贝对象,无移动构造、赋值,右值cout << "=============================================" << endl;}int main(){test1();return 0;}
template<class T> //节点 struct ListNode { //struct类未用访问限定符修饰的变量为public,在类外指定类域就可以直接进行访问ListNode* _prev; //带头双向循环链表ListNode* _next;T _data; //左值ListNode(const T& val = T()) //缺省值-》防止无参调用,因无默认构造函数,又显示写了构造函数,编译器会报错:_prev(nullptr),_next(nullptr),_data(val){ }//右值ListNode(T&& val) :_prev(nullptr), _next(nullptr), _data(forward<T>(val)) //右值引用引用右值,它的属性变为左值 { }template<class ...Args> //可变参数模板ListNode(Args&& ...args) //万能引用+完美转发:_prev(nullptr), _next(nullptr), _data(forward<Args>(args)...) //右值引用引用右值,它的属性变为左值{ }};template<class T> //链表-带头双向循环链表,存储的元素为节点class list { //class类未用访问限定符修饰的变量为private,在类外不可以访问public:typedef ListNode<T> Node; //左值void push_back(const T& val) //尾插 {insert(end(), val);}//右值void push_back(T&& val) {insert(end(), forward<T>(val)); //右值引用引用左值,它的属性变为左值}//尾插、直接调用构造函数template<class ...Args> //可变参数模板void emplace_back(Args&& ...args) //万能引用+完美转发{emplace(end(), forward<Args>(args)...);}//左值iterator insert(iterator position, const T& val){Node* newnode = new Node(val);Node* cur = position._node; //struct中public变量访问可以 对象.变量名Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return newnode; //有返回值,与erase匹配}//右值iterator insert(iterator position,T&& val){Node* newnode = new Node(forward<T>(val)); //右值引用引用左值,它的属性变为左值Node* cur = position._node; //struct中public变量访问可以 对象.变量名Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return move(newnode); //有返回值,与erase匹配}template<class ...Args> iterator emplace(iterator position, Args&& ...args) //可变参数模板{Node* newnode = new Node(forward<Args>(args)...); //万能引用+完美转发Node* cur = position._node; //struct中public变量访问可以 对象.变量名Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return move(newnode); //有返回值,与erase匹配}}
9. lambda表达式
9.1. lambda表达式语法
lambda表达式书写格式:[capture-list] (paramerters) mutable -> return-types { statement }。
表达式各部分说明:a. [capture-list]:捕捉列表,出现在lambda函数的开始位置,让编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉其所在作用域中的变量、静态存储期的变量(静态变量、全局变量),供lambda函数使用。捕捉列表说明什么数据能被lambda使用,以及捕捉的方式是传值还是传引用。
b. (paremerters):参数列表,与普通的参数列表一致,如果不需要参数传递,则可以连同( )一起省略。
c. mutable:默认情况下,一般lambda函数为const函数,mutablek可以取消其常量性 (捕捉列表以传值方式捕捉,被捕捉到的变量不能修改,若想要修改它,需要添加mutable),使用mutable,参数列表不能省略(即使参数为空)。
d. ->return-type:返回值类型。如果没有返回值,这部分可以省略、如果返回值类型是明确的,此时返回值也可以省略,由编译器对返回类型进行推导。
e. {statement}:函数体。在{ }内,只能使用参数、捕捉列表捕捉的变量。
注意:参数列表、返回值类型可以省略,捕捉列表、函数体不可以省略,即使为空。C++11最简单的lambda函数为[ ]{ },该lambda函数表示什么事情都不做。
捕捉列表说明:a. 捕捉列表可以由多个捕捉项组成,以逗号分隔:当前作用域为包含lambda函数的语句块{ } 。[ val1 ]:传值捕捉变量var1、[ &val2 ]:传引用捕捉变量val2、[ & ]:传引用捕捉当前作用域内的所有变量,包括this、[ = ]:传值捕捉当前作用域内的所有变量,包括this、[ this ]:传值捕捉当前的this、[ & , n ]:传值捕捉n,传引用捕捉当前作用域的其他变量、[ =、&a , &b ]:传引用捕捉a、b,传值捕捉当前作用域的其他变量。
b.捕捉列表不允许变量重复传递,即:不允许某个变量被以相同的方式捕捉,否则编译器会报错。eg:[ = , a ]、[ & , &b ]。
c. 在块作用域以外的lambda函数捕捉列表必须为空:"在块作用域以外"是指在全局作用域定义的变量、在其它非直接封闭作用域定义的变量,如果它们具有静态存储期,即使lambda函数没有明确的捕捉列表(捕捉列表为空),lambda函数仍可以访问和使用它们。
d. lambda函数只能捕捉到当前作用域内的变量,如果捕捉了其他作用域内的变量(不具有静态存储期)、非全局变量,编译器都会报错。
e. lambda表达式之间不能相互赋值,即使lambda表达式完全相同。
int main(){//test1();void (*PF)(); //与lambda表达式具有相同类型的函数指针 auto f1 = [] {cout << "hello" << endl; }; auto f2 = [] {cout << "hello" << endl; };//f1 = f2; 不允许lambda表达式之间相互赋值auto f3(f2); //可以用lambda表达式进行拷贝构造, lambda对象被禁掉了构造函数f3();PF = f2; //赋值PF();return 0;}
可以用lambda表达式进行拷贝构造, lambda对象被禁掉了构造函数。可以将lambda表达式赋值给具有相同类型的函数指针。 9.2. lambada表达式
#include <algorithm>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(){int array[] = { 4,1,8,5,3,7,0,9,2,6 };// 默认按照小于比较,排出来结果是升序std::sort(array, array + sizeof(array) / sizeof(array[0]));for (auto& e : array) cout << e << ' ';cout << endl;// 如果需要降序,需要改变元素的比较规则std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>()); //sort函数模板,传的是仿函数对象for (auto& e : array) cout << e << ' ';cout << endl;vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess()); //匿名对象sort(v.begin(), v.end(), ComparePriceGreater());//lambda表达式,局部的匿名函数对象,没有类型,该函数无法直接调用,需要借助auto将其赋值给一个变量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; });return 0;
sort为函数模板,需要的是仿函数对象,默认情况下,仿函数为less(),按照小于的方式进行比较,升序。若需要降序,需要改变函数的比较规则,手动传仿函数greater()。 ?Tips:lambda表达式为局部的匿名函数对象,语法层无类型,其类型是编译器去在编译时根据uuid算法随机生成一个唯一的、匿名的函数对象类型。
9.3. lambda表达式的底层
仿函数:也叫做函数对象,像函数一样使用对象,在类中重载operator()运算符,创建仿函数类对象,通过对象去调用operator( )运算符。
在使用方式上, lambda与函数对象,无区别,函数对象将rate定义为成员变量,在定义对象时给初始值即可,lambda通过捕捉列表可以将rate变量捕捉到。在底层,编译器将lambda表达式处理方式,是按照函数对象的方式进行处理的,如果定义了一个lambda表达式,编译器会自动生成一个类,在类中重载了operator()运算符。类名是编译器根据uuid算法随机生成的,唯一性,匿名性。——》lambda表达式底层是仿函数。
class Rate{public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){return money * _rate * year;}private:double _rate;};int main(){// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lambda表达式auto r2 = [=](double money, int year)->double { return money * rate * year; };r2(10000, 2);//在使用方式上,lambda与函数对象,无区别,函数对象将rate定义为成员变量,在定义对象时给初始值即可,lambda通过捕捉列表可以将rate变量捕捉到/*在底层,编译器将lambda表达式处理方式,是按照函数对象的方式进行处理的,如果定义了一个lambda表达式,编译器会默认生成一个类,在类中重载了operator()运算符。类名是编译器根据uuid算法随机生成的,唯一性,匿名性*/return 0;}
int main(){void (*PF)(); //与lambda表达式具有相同类型的函数指针 auto f1 = [] {cout << "hello" << endl; }; auto f2 = [] {cout << "hello" << endl; };f1();f2();//f1 = f2; 不允许lambda表达式之间相互赋值auto f3(f2); //可以用lambda表达式进行拷贝构造, lambda对象被禁掉了构造f3();PF = f2; //赋值PF();return 0;}
10. 包装器
10.1. function包装器
function包装器:也叫做适配器,C++中的function本质是一个类模板,也是一个包装器。
// functional包装器的使用方法如下:#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(){function<int(int, int)> f1 = f; //函数指针(普通函数)cout << f1(3, 4) << endl;function<int(int, int)> f2 = Functor(); //函数对象(仿函数)cout << f2(1, 2) << endl;function<int(int, int)> f3 = &Plus::plusi; //类中的静态成员函数cout << f3(2, 3) << endl;function<double(Plus, double, double)> f4 = &Plus::plusd; //类中的成员函数cout << f4(Plus(), 1.1, 2.2) << endl;function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus ps;cout << f5(&ps, 2.2, 3.3) << endl;return 0;}
ret = fun( x ) , 此处的fun可能是 函数指针(函数名)、仿函数对象(函数对象)、lambda表达式对象,这些都是可调用的类型,但可能会导致模板的效率低下,因为函数模板可能会实例化为多份。下面可知useF函数模板实例化了三份。 template<class 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;}};int main(){// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;}
int main(){//function包装器,也叫做适配器,将可调用的类型封装为一个类,统一类型,解决了函数模板实例化多份的问题导致的效率低下问题function<double(double)> f1 = f; //函数指针function<double(double)> f2 = Functor(); //函数对象function<double(double)> f3 = [](double d)->double {return d / 4; }; //lambda表达式useF(f1, 11.1);useF(f2, 11.1);useF(f3, 11.1);return 0;}
https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/
class Solution { //function包装器,lambda表达式(匿名函数对象,无类型)public: int evalRPN(vector<string>& tokens) { stack<int> st; //栈用来存储操作数 map<string, function<int(int, int)>> m = { {"+", [](int left, int right)->int{ return left + right;} }, {"-", [](int left, int right)->int{ return left - right;} }, {"*", [](int left, int right)->int{ return left * right;} }, {"/", [](int left, int right)->int{ return left / right;} } }; for(int i = 0; i < tokens.size(); i++) { if(m.count(tokens[i])) { int right = st.top(); st.pop(); int left = st.top(); st.pop(); st.push(m[tokens[i]](left, right)); // } else st.push(stoi(tokens[i])); } return st.top(); }};
10.2. bind包装器
bind包装器:定义在#include头文件中,是一个函数模板,就像函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来"适应"原对象的参数列表。对可调用对象,调整参数:调整参数的顺序、调整参数的个数。
bind的一般形式:auto newCallale = bind(callable, args_list) 。
newCallable本身是一个可调用对象,args_list是一个逗号分割的参数列表,对应给定callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它args_list中的参数。
args_list的参数可能包含形如_n的名字,n是个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable参数的"位置"。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数、_2为newCallable的第一个参数,以此类推。
#include<functional>int Sub(int a, int b){return a - b;}int main(){auto fc1 = bind(Sub, placeholders::_1, placeholders::_2);cout << fc1(2, 3) << endl;auto fc2 = bind(Sub, placeholders::_2, placeholders::_1);cout << fc2(2, 3) << endl;}
int main(){//调整参数的个数//bind返回值是个新的可调用对象, plusd为可调用对象,成员函数默认第一个参数为this,所以传Plus()function<double(double, double)> f1 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);cout << f1(2, 3) << endl;//某些参数绑死function<double(double, double)> f2 = bind(&Plus::plusd, Plus(), placeholders::_1, 3);cout << f2(2, 3) << endl;}