目录
- [1. C++简介](#1. C++简介)
- [2. 统一的列表初始化](#2. 统一的列表初始化)
-
- [2.1. { }初始化](#2.1. { }初始化)
- [2.2. std: :initializer_list](#2.2. std: :initializer_list)
- [3. 声明](#3. 声明)
-
- [3.1. auto](#3.1. auto)
- [3.2. decltype](#3.2. decltype)
- [3.3. nullptr](#3.3. nullptr)
- [4. 范围for循环](#4. 范围for循环)
- [5. STL的变化](#5. STL的变化)
- [6. 右值引用和移动语义](#6. 右值引用和移动语义)
-
- [6.1. 左值引用和右值引用比较](#6.1. 左值引用和右值引用比较)
- [6.2. 右值引用使用场景和意义](#6.2. 右值引用使用场景和意义)
-
- [6.2.1. 移动构造](#6.2.1. 移动构造)
- [6.2.2. 移动赋值](#6.2.2. 移动赋值)
- [6.3. 万能引用和完美转发](#6.3. 万能引用和完美转发)
- [7. 默认移动构造、默认移动赋值](#7. 默认移动构造、默认移动赋值)
-
- [7.1. default和delete](#7.1. default和delete)
- [8. 可变参数模板](#8. 可变参数模板)
-
- [8.1. 递归解析参数包、展开参数包](#8.1. 递归解析参数包、展开参数包)
- [8.2. emplace相关的接口函数](#8.2. emplace相关的接口函数)
- [9. lambda表达式](#9. lambda表达式)
-
- [9.1. lambda表达式语法](#9.1. lambda表达式语法)
- [9.2. lambada表达式](#9.2. lambada表达式)
- [9.3. lambda表达式的底层](#9.3. lambda表达式的底层)
- [10. 包装器](#10. 包装器)
-
- [10.1. function包装器](#10.1. function包装器)
- [10.2. bind包装器](#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表达式进行同一的列表初始值设定,使用列表初始化,可添加=,也可不添加=。
cpp
#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
cpp
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=( )的参数,这样对象就可以用大括号进行赋值。
cpp
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;
}
cpp
vector(initializer_list<T> il) //initializer_list作为参数的构造函数
{
reserve(il.size());
for (auto& e : il)
{
push_back(e);
}
}
cpp
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;
}
cpp
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去构造其他类型的piar
pair(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将变量的类型声明为表达式指定的类型。
cpp
int main()
{
const int x = 3;
decltype(x) y = 4; //顶层const,const修饰变量本身,decltype会去掉const
cout << typeid(y).name() << endl;
const int* p = &x;
decltype(p) a1 = nullptr; //底层const,const修饰指针指向的内容,decltype不会去掉const
cout << 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(左值)取别名。
-
引用都是取别名,不开空间存储。底层,引用是用指针实现的,左值引用是存储当前左值的地址,右值引用是将右值拷贝给栈中的一块临时空间,存储这个临时空间的地址。可以对右值引用变量取地址,也可以对它进行修改。
cpp
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. 移动构造
cpp
//移动构造 --- 右值(将亡值)
string(string&& s)
{
cout << "string(string&& s)" << endl;
swap(s);
}
- 右值引用,移动语义:右值引用是对右值的引用,移动语义是将右值的资源直接转移给左值对象,而不需要进行开销较大的深拷贝。移动语义是C++11引入的一个新特性,它允许我们将资源从一个对象转移到另一个对象,以提高效率和性能 ------》 移动构造、移动赋值。
总结:进行深拷贝的类需要进行移动构造、移动赋值,进行浅拷贝的类不需要进行移动构造、移动赋值。
6.2.2. 移动赋值
cpp
//移动赋值
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:是一个函数模板,将左值属性变为右值属性。
cpp
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. 可变参数模板
cpp
//可变参数的函数模板
template<class ...Args> //Args是模板参数包
void ShowList(Args ...args) //args是函数形参数包 参数包里面函数0~N个模板参数
{ //... }
-
带有省略号的称为"参数包", 它里面包含0~N(N>=0)个模板参数。Args是模板参数包 、args是函数形参数包。
-
我们无法直接拿到参数包args中每个参数的类型和值,语法上不支持通过args[i]来获取可变参数,因为这样是运行时解析参数。而此处是模板,是编译时解析参数。
8.1. 递归解析参数包、展开参数包
cpp
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;
}
cpp
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无区别。
cpp
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;
}
cpp
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表达式完全相同。
cpp
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表达式
cpp
#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表达式底层是仿函数。
cpp
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;
}
cpp
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本质是一个类模板,也是一个包装器。
cpp
// 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函数模板实例化了三份。
cpp
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;
}
cpp
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/
cpp
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的第一个参数,以此类推。
cpp
#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;
}
cpp
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;
}