做义工的网站,西安计算机培训机构排名前十,免费的自建视频网站,百度关键词seo年度费用目录
一#xff0c;列表初始化
1.1 这是什么#xff1f;
1.2 initializer_list
1.3 在容器的运用
1.4 STL中的变化
二#xff0c;右值引用和左值引用
2.1 是什么#xff1f;
2.2 这两个东西有啥关系#xff1f;
2.3 有啥用#xff1f;
三#xff0c;*移动构…目录
一列表初始化
1.1 这是什么
1.2 initializer_list
1.3 在容器的运用
1.4 STL中的变化
二右值引用和左值引用
2.1 是什么
2.2 这两个东西有啥关系
2.3 有啥用
三*移动构造和移动赋值 3.1 什么是移动构造
3.2 移动赋值 3.3 STL中的变化
3.4 完美转发
编辑
编辑 3.5 类的新功能
3.6 小练习
四可变参数模板
4.1 是什么
4.2 怎么用
4.2.1 打印参数个数
4.2.2 递归获取参数 4.2.3 另一种递归 4.2.4 奇怪的玩法
五lambda表达式
5.1 是什么
5.2 lamdba使用场景
5.3 其他捕捉方法
六function包装器
6.2 包装器使用
6.3 逆波兰OJ题
6.4 bind
七补充
8.1 auto和decltype
8.2 两个被批评的容器
8.3 emplace系列接口 一列表初始化
1.1 这是什么
在C98中标准允许使用花括号进行统一的列表初始化的设定下面两种初始化是一样的
int x1 1;
int x2 { 2 }; 而在C11中扩大了花括号的初始化列表的使用范围使其可以用于所有内置类型和用户自定义的类型其中 也可以省略了
int x3{ 2 };
int x4(1); //调用int的构造
int* pa new int[4] {0};//C11中列表初始化也可以适用于new表达式中 C11后自定义类型也支持列表初始化本质是调用自定义类型的构造函数
class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout 调用Date的构造函数;cout Date(int year, int month, int day) endl;}private:int _year;int _month;int _day;
};void main1()
{Date d1(2022, 11, 22);Date d2 { 2022,11,22 }; //构造拷贝优化成直接构造 //如果不想让自定义类型这样搞在构造函数前面加个explicit表示该构造函数不能隐式转换Date d3{ 2022,11,22 };//C11
} 1.2 initializer_list
STL可以支持不同数量参数初始化因为花括号括起来的常量数组C把它识别成一个类型initializer_list
文档介绍这个是一个类模板用来访问C初始化列表中的的值且此类型的对象由编译器根据初始化列表声明自动构造如下演示代码
void main(
{//STL可以支持不同数量参数初始化因为花括号括起来的常量数组C把它识别成一个类型 initializer_listauto i1 { 1,2,3,4,5,6 };auto i2 { 1,2,5,6 };cout typeid(i1).name() endl;cout typeid(i2).name() endl;initializer_listint::iterator it1 i1.begin();initializer_listint::iterator it2 i2.begin();cout it1 endl;
{ 1.3 在容器的运用
void main()
{Date d1(2022, 11, 22);Date d2 { 2022,11,22 };Date d3{ 2022,11,22 };//C11cout --------------------- endl;vectorint v1 { 1,2,3,4,5,6 }, v2{ 1,2,3,4,5,6 }; //容器都可以用列表初始化listint lt1 { 1,2,3,4,5,6 }, lt2{ 1,2,3,4,5,6 };vectorDate v3 {d1, d2}; //用对象初始化vectorvectorDate v4 { Date(2024,1,29), Date(2024,1,30)}; //用匿名对象初始化vectorDatev5 { {2024,1,1}, {2023,11,11} }; //隐式类型转换string s1 11111; //单参数的构造函数支持隐式类型的转换//支持initializer_list的构造mapstring, string dict { {sort,排序},{insert,插入}};pairstring, string kv { Date,日期 };//赋值重载initializer_listpairconst string,string kvil { { left, 左边 }, { left, 左边 } };dict kvil;
} 1.4 STL中的变化
C11后所有STL容器都会增加一个支持类似list(initializer_listvalue_type il)这样的构造函数下面是vector的新构造函数模拟实现
vector(initializer_listT il)
{/*initializer_listT::iterator it il.begin();while (it ! li.end()){push_back(*it);it;}*/for(auto e : il){push_back(e);
}
总结C11以后一切对象都可以用列表初始化但是我们建议普通对象还是用以前的方式初始化容器如果有需求可以用列表初始化
二右值引用和左值引用
2.1 是什么
先回顾下什么是左值 -- 可以获取它的地址的表达式const修饰的不能赋值其它大部分都可以给它赋值如下代码
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;double x 1.1, y 2.2; 所以右值就是 -- 不能取地址的表达式并且不能出现在赋值符号左边如下代码
10; //cout10endl; 自变量无法打印地址
x y; //cout(xy)endl; 表达式返回的临时变量也无法打印地址
fmin(x, y); //函数返回值//下面就是几个右值引用的例子
int rr1 10;
double rr2 x y;
double rr3 fmin(x,y);
2.2 这两个东西有啥关系
①首先左值引用不能直接给右值取别名
//double r1 x y;
//左值不能引用右值因为这是权限的放大了x y传值时是生成一个临时对象这个临时对象具有常性所以要加const
const double r1 x y;//const左值可以引用右值
②右值引用不能给左值取别名
//int rr5 b;不能
int rr5 move(b);//右值引用可以给move以后的左值取别名
③const左值引用可以引用左值也可以引用右值
void Func(int x) { cout 左值引用 endl; }
void Func(const int x) { cout const 左值引用 endl; }void Func(int x) { cout 右值引用 endl; }
void Func(const int x) { cout const 右值引用 endl; }void main()
{//const左值引用可以引用右值但缺点是无法知道我引用的是左值还是右值//所以右值引用的第一个意义就是在函数传参的时候进行更好的参数匹配int a 0;int b 1;Func(a);Func(a b);
} 2.3 有啥用
引用的核心价值 -- 减少拷贝
左值引用能解决的问题 1做参数 -- ①减少拷贝提高效率 ②做输出型参数 2做返回值 -- ①减少拷贝提高效率 ②可以修改返回对象比如map的operator[]可以修改插入 左值引用不能解决的问题 1传值返回时返回局部对象时局部对象出了函数栈帧作用域时会被销毁这时候引用对应的值就没了引用失效出现类似野指针的野引用 2容器插入接口的对象拷贝问题C以前是void push_back(const T x); 不论T是自定义还是内置类型都会生茶一个对象x然后再拷贝赋值然后再销毁x这样做消耗太大 所以C11以后所有的容器的插入接口都重载了个类似void push_back(T x); 的右值引用接口使自定义类型走右值引用的移动拷贝实现资源转移不拷贝提高效率
所以C11最大的亮点之一就是右值引用和接下来要讲的移动语义这两个东西加起来可以大大提高拷贝赋值的效率
三*移动构造和移动赋值
为了方便演示构造和赋值打印我们先简单模拟实现string
namespace bit
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}string(const char* str ):_size(strlen(str)), _capacity(_size){//cout string(char* str) endl;_str new char[_capacity 1];strcpy(_str, str);}void swap(string s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string s):_str(nullptr){cout string(const string s) -- 拷贝构造(深拷贝) endl;//string tmp(s._str);//swap(s);_str new char[s._capacity 1];strcpy(_str, s._str);_size s._size;_capacity s._capacity;}// 移动构造string(string s):_str(nullptr), _size(0), _capacity(0){cout string(string s) -- 移动构造(资源转移) endl;swap(s);}// 拷贝赋值string operator(const string s){cout string operator(const string s) -- 拷贝赋值(深拷贝) endl;string tmp(s);swap(tmp);return *this;}// 移动赋值 -- 延长了对象内的资源的生命周期 s 将亡值string operator(string s){cout string operator(string s) -- 移动赋值(资源移动) endl;swap(s);return *this;}~string(){delete[] _str;_str nullptr;}char operator[](size_t pos){assert(pos _size);return _str[pos];}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;}string operator(char ch){push_back(ch);return *this;}string operator(char ch){string tmp(*this);tmp ch;return tmp;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity;};
} 3.1 什么是移动构造
bit::string To_String(int value)
{bool flag true;if (value 0){flag false;value 0 - value;}bit::string str;while (value 0){int x value % 10;value / 10;str (0 x);}if (flag false){str -;}std::reverse(str.begin(), str.end());return str;
} 如上图左图在bit::string To_String(int value)函数中可以看到这里只能使用传值返回所以会有两次拷贝构造现在的编译器会把连续构造两次的情况优化为只构造一次
string(const string s):_str(nullptr)
{cout string(const string s) -- 拷贝构造(深拷贝) endl;//string tmp(s._str);//swap(s);_str new char[s._capacity 1];strcpy(_str, s._str);_size s._size;_capacity s._capacity;
}
// 移动构造
string(string s):_str(nullptr), _size(0), _capacity(0)
{cout string(string s) -- 移动构造(资源转移) endl;swap(s);
}bit::string to_string(int value)
{bit::string str;//...return str;
}
int main()
{bit::string ret bit::to_string(-1234);return 0;
} 如上代码to_string的返回值是一个右值用这个右值构造ret如果没有移动构造编译器就会匹配调用拷贝构造因为const左值引用可以引用右值这里就是一个深拷贝
如果同时有拷贝构造和移动构造那么编译器会选择最匹配的参数调用这里就是一个移动构造。而这个更好的参数匹配我们前面讲左值和右值引用的关系的第三点提过
3.2 移动赋值
string operator(string s)
{cout string operator(string s) -- 移动语义 endl;swap(s);return *this;
}void main()
{bit::string s1;s1 to_string1(1234); //C98中先生成对象s1然后to_string返回时再生成一个str对象返回然后拷贝给临时对象然后再拷贝给s1消耗太大 -- 两次深拷贝//C11后直接生成s1然后to_string返回时将返回时临时生成的str识别成将亡值把资源给临时对象然后通过string operator(string s)再移动给s -- 只是单纯的资源转移无任何拷贝大大提高效率//bit::string(hello world); //析构函数添加打印~string的话这一条语句会打印~string//const bit::string ref1 bit::string(hello world); //不打印~string// const引用延长传参时临时对象生命周期是为了解决void push_back(const string s); // 当我们v.push_back(string(1111));时string(1111)的生命周期只有这一行这里用引用的话一旦生命周期过了就没了所以这种场景下必须延长匿名对象生命周期//bit::string s2;//const bit::string ref2 bit::to_string(1234); //如果to_string返回值为const引用出了作用域后调用析构函数销毁了这时候再去访问就造成了类似野指针的野引用//如果to_string是传值返回就可以用const引用接收了因为传值返回时返回的就是str的拷贝
} 3.3 STL中的变化
由于移动语义在提高效率上的飞跃C11后所有容器的拷贝和赋值都增加了右值引用的移动语义重载
void main()
{std::string s1(hello world);std::string s2(s1);//拷贝构造//std::string s3(s1s2);std::string s3 s1 s2;//C98就是拷贝构造C11已经更新这里现在是移动构造std::string s4 move(s1);//s1被move后就成为了一个右值就被弄走了所以调试时走完这一行监视窗口的s1没了
}
并且所有可插入容器的插入接口也都重载了右值引用版本如下代码
void main()
{vectorbit::string v;bit::string s1(hello);v.push_back(s1);cout -------------------------- endl;v.push_back(bit::string(world));//v.push_back(world);//平时喜欢这样写cout endl;listbit::string lt;//bit::string s1(hello);lt.push_back(s1); //左值lt.push_back(move(s1)); //右值cout -------------------------- endl;//lt.push_back(bit::string(world)); //匿名对象是右值也是一次移动构造lt.push_back(world); //但是我们喜欢这样写上面那个不推荐
} 3.4 完美转发
//万能引用/引用折叠 -- t能引用左值也能引用右值如果没有完美转发会被全部搞成左值
templatetypename T
void PerfectForward(T t)
{//完美转发 -- 保持t引用对象属性//Func(std::forwardT(t));Func(t);//没有完美转发时main()里面就全部搞为左值引用
}
//模板中的不代表右值引用代表万能引用它既能接收左值又能接收右值
//但是模板的万能引用只是提供了能够同时接收左值引用和右值引用的能力
//但是引用类型的唯一作用就是限制了接收的类型后续使用的时候会全部退化成左值
//所以我们希望能够在传递过程中保持它的左值或右值的属性就需要用到完美转发 -- std::forwardT()
#includelist.h
void main()
{//如果没有完美转发则下面全部搞成了左值引用PerfectForward(10); // 右值cout ------------------------ endl;int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值cout ------------------------ endl;const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值cout ------------------------ endl;bit::listbit::string lt;bit::string s1(hello);lt.push_back(s1);cout ------------------------ endl;//如果是确定类型就是走拷贝模板就可以走右值引用lt.push_back(world);
}
演示模拟转发需要在list_node类节点里面添加右值引用版本的构造
namespace bit
{templateclass Tstruct list_node{T _data;list_nodeT* _next;list_nodeT* _prev;list_node(const T x T()):_data(x), _next(nullptr), _prev(nullptr){}list_node(T x)//这里也提供一个右值引用:_data(std::forwardT(x))//完美转发, _next(nullptr), _prev(nullptr){}};// typedef __list_iteratorT, T, T* iterator;// typedef __list_iteratorT, const T, const T* const_iterator;// 像指针一样的对象templateclass T, class Ref, class Ptrstruct __list_iterator{typedef list_nodeT Node;typedef __list_iteratorT, Ref, Ptr iterator;//typedef bidirectional_iterator_tag iterator_category;typedef T value_type;typedef Ptr pointer;typedef Ref reference;typedef ptrdiff_t difference_type;Node* _node;// 休息到1702继续__list_iterator(Node* node):_node(node){}bool operator!(const iterator it) const{return _node ! it._node;}bool operator(const iterator it) const{return _node it._node;}// *it it.operator*()// const T operator*()// T operator*()Ref operator*(){return _node-_data;}//T* operator-() Ptr operator-(){return (operator*());}// ititerator operator(){_node _node-_next;return *this;}// ititerator operator(int){iterator tmp(*this);_node _node-_next;return tmp;}// --ititerator operator--(){_node _node-_prev;return *this;}// it--iterator operator--(int){iterator tmp(*this);_node _node-_prev;return tmp;}};templateclass Tclass list{typedef list_nodeT Node;public:typedef __list_iteratorT, T, T* iterator;typedef __list_iteratorT, const T, const T* const_iterator;//typedef __reverse_iteratoriterator, T, T* reverse_iterator;//typedef __reverse_iteratorconst_iterator, const T, const T* //const_reverse_iterator;const_iterator begin() const{return const_iterator(_head-_next);}const_iterator end() const{return const_iterator(_head);}iterator begin(){return iterator(_head-_next);}iterator end(){return iterator(_head);}/*reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}*/void empty_init(){// 创建并初始化哨兵位头结点_head new Node;_head-_next _head;_head-_prev _head;}template class InputIteratorlist(InputIterator first, InputIterator last){empty_init();while (first ! last){push_back(*first);first;}}list(){empty_init();}void swap(listT x)//void swap(list x){std::swap(_head, x._head);}// lt2(lt1)list(const listT lt){empty_init();listT tmp(lt.begin(), lt.end());swap(tmp);}// lt1 lt3listT operator(listT lt){swap(lt);return *this;}~list(){clear();delete _head;_head nullptr;}void clear(){iterator it begin();while (it ! end()){it erase(it);}}void push_back(const T x){//Node* tail _head-_prev;//Node* newnode new Node(x);_head tail newnode//tail-_next newnode;//newnode-_prev tail;//newnode-_next _head;//_head-_prev newnode;insert(end(), x);}void push_back(T x)//右值引用 完美转发不加完美转发就会被折叠成左值引用{insert(end(), std::forwardT(x));}void push_front(const T x){insert(begin(), x);}iterator insert(iterator pos, const T x){Node* cur pos._node;Node* prev cur-_prev;Node* newnode new Node(x);// prev newnode curprev-_next newnode;newnode-_prev prev;newnode-_next cur;cur-_prev newnode;return iterator(newnode);}iterator insert(iterator pos, T x)//右值引用 完美转发不加完美转发就会被折叠成左值引用{Node* cur pos._node;Node* prev cur-_prev;Node* newnode new Node(std::forwardT(x));// prev newnode curprev-_next newnode;newnode-_prev prev;newnode-_next cur;cur-_prev newnode;return iterator(newnode);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator erase(iterator pos){assert(pos ! end());Node* cur pos._node;Node* prev cur-_prev;Node* next cur-_next;prev-_next next;next-_prev prev;delete cur;return iterator(next);}private:Node* _head;};
} 3.5 类的新功能
原来C类中有6个默认成员函数构造析构拷贝构造拷贝赋值重载取地址重载const取地址重载最重要的是前面四个后两个用处不大。
而C11后新增了两个移动构造和移动赋值运算符重载
注意在你没有自己实现析构函数拷贝构造拷贝赋值重载中的任意一个编译器会自动生成默认移动构造和默认移动赋值对于内置类型成员会执行按字节拷贝对于自定义类型成员需要看这个成员是否实现了移动构造和重载没有就调用它拷贝构造和重载
class Person
{
public:Person(const char* name , int age 0):_name(name),_age(age){}一个构造函数可以复用其他构造函数//Person(const char* name)// :Person(name,18) //委托构造//{}/*Person(const Person p):_name(p._name) ,_age(p._age){}*//*Person operator(const Person p){if(this ! p){_name p._name;_age p._age;}return *this;}*/// 如果不想让Person对象拷贝或被拷贝//Person(const Person p) delete; //delete表示禁止生成//强制生成移动构造和移动赋值
// Person(Person p) default;
// Person operator(Person p) default; //default表示强转生成
//
// ~Person()
// {
// cout ~Person() endl;
// }private:bit::string _name; //自定义类型int _age; //内置类型
};void main()
{Person s1(张三,18);Person s2 s1; //拷贝构造Person s3 std::move(s1); //移动构造没有构造也可以调用构造cout endl endl;Person s4;s4 std::move(s2); //调用bit::string的移动拷贝
} 3.6 小练习
题目 -- 要求用delete关键字实现一个类且只能在堆上创建对象
class HeapOnly//指类只能在堆上
{
public:HeapOnly(){_str new char[10];}~HeapOnly() delete;void Destroy(){delete[] _str;operator delete(this);}
private:char* _str;
};void main15()
{//HeapOnly hp1;//static HeapOnly hp2;//无法在堆以外的区域生成对象HeapOnly* ptr new HeapOnly;//delete ptr;析构被限制调不了ptr-Destroy(); //把类里的构造函数new的10个char释放//operator delete(ptr); //把生成的ptr对象释放掉
}
四可变参数模板
4.1 是什么
C11的新特性可变参数模板可以让我们创建接收可变参数的函数模板和类模板相比C98以前的类模板和函数模板只能含固定数量的模板参数。但是这块使用起来比较抽象容易出bug所以对于这一块本文章只点到为止不进行深入学习
template class ...Args
void ShowList(Args ...args)
{} 首先Args是一个模板参数包args是一个函数形参参数包。
我们把带省略号的参数称为“参数包”里面包含了大于0个模板参数。我们无法直接获取参数包args中的每个参数只能通过展开参数包的方式来获取参数包中的每个参数这是参数包的一个重要特征也是最大的难点因为解析参数包过程比较复杂一旦代码变多就容易出bug并且不好排查下面将介绍可变参数模板的解析与使用
4.2 怎么用
4.2.1 打印参数个数
templateclass ...Args
void ShowList1(Args ...args)//(Args ...args)是函数参数包
{cout sizeof ...(args) endl;//打印参数包内参数的个数
}
void main()
{string str(hello);ShowList1();ShowList1(1);ShowList1(1,A);ShowList1(1,A,str);
} 4.2.2 递归获取参数
void ShowList2()//应对0个参数的参数包时
{cout endl;
}
//Args... args代表N个参数包(N 0)
templateclass T, class ...Args
void ShowList2(const T val, Args ...args)
{cout ShowList( val , 包里还剩 sizeof...(args) 个参数) endl;ShowList2(args...); //利用类似递归的方式来解析出参数包
}
void main()
{string str(hello);ShowList2(1, A, str);ShowList2(1, A, str, 2, 3, 5.555);
} 4.2.3 另一种递归
void _ShowList3()
{cout endl;
}
template class T, class ...Args
void _ShowList3(const T val, Args... args)
{cout val ;cout __FUNCTION__ ( sizeof...(args)1 ) endl;_ShowList3(args...);
}
template class ...Args
void ShowList3(Args... args)
{_ShowList3(args...);
}void main()
{string str(hello);ShowList3(1, A, str);ShowList3(1, A, str, 2, 3, 5.555);
} 4.2.4 奇怪的玩法
templateclass T
int PrintArg(T t)
{cout t ; //所有的参数可以在这里获取到return 0;
}
templateclass ...Args
void ShowList4(Args ...args)
{int arr[] { PrintArg(args)... }; //这里利用了编译器自动初始化这里自动初始化这个数组编译器编译的时候对这个数组进行大小确认再开空间就去解析后面这个内容//就把参数包第一个值传给PrintArg的T然后又因为后面是...所以参数包有几个值就要生成几个PrintArg表达式然后生成几个表达式前面的那个数组就开多大//然后PrintArg就拿到了所有参数包的值然后main函数调用后就开始打印cout endl;
}//编译器编译推演生成了一下代码
//void ShowList(char a1, char a2, std::string a3)
//{
// int arr[] { PrintArg(a1),PrintArg(a2),PrintArg(a3) };
// cout endl;
//}void main()
{ShowList4(1, A, string(sort));ShowList4(1, 2, 3);
} 五lambda表达式
5.1 是什么
在C/C中可以像函数那样使用的对象/类型有1函数指针C 2仿函数/函数对象C 3lamdbaC
lamdba表达式又叫匿名函数。具体语法如下
lamdba表达式语法:[capture-list](parameters)mutable - return_type { statrment }捕捉列表 参数列表 返回值类型 函数体实现 ①捕捉列表capture-list该部分出现在lamdba最开始编译器根据[]来判断接下来的代码是否为lamdba函数捕捉列表能够捕捉上下文的变量供lamdba函数使用
②参数列表parameyers与普通函数的参数列表一致如果不需要传参数次()可以省略
③mutable默认情况下lamdba函数总是一个const函数mutable可以取消其常量性。一般用不到省略
④返回值类型retuen_type用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。但是在返回值类型明确情况下仍可省略编译器会自动堆返回类型进行推导
⑤函数体statement与普通函数体一样可以使用参数列表和捕捉列表的变量
下面是lamdba的简单演示
void main()
{//最简单的lamdba表达式无意义[] {};//两个数相加的lambdaauto add1 [](int a, int b)- int {return a b; };//本质来说是一个对象//cout [](int x, int y)-int {return x y; }(1, 2) endl;cout add1(1, 2) endl;//使对象能像普通函数一样调用// 省略返回值auto add2 [](int a, int b){return a b; };cout add2(1, 2) endl;//交换变量的lambdaint x 0, y 1; auto swap1 [](int x1, int x2) //返回值为void一般省略而且参数列表和函数参数一样交换值要用引用不然交换的仅仅是形参{int tmp x1; x1 x2; x2 tmp; };swap1(x, y); cout x : y endl;//捕捉列表//不传参数来交换x y的lambda -- 捕捉列表//默认捕捉过来的变量不能被修改auto swap2 [x, y]()mutable//()mutable使捕捉过来的参数可修改{int tmp x;x y;y tmp;};swap2();//此处不传参 -- 但是mutable仅仅让形参修改不修改实参下面打印后会发现没有交换cout x : y endl;//要捕捉只能传引用捕捉auto swap3 [x, y]{int tmp x;x y;y tmp;};swap3();//此处不传参cout x : y endl;
}
5.2 lamdba使用场景
假设一个场景我们自定义一个商品类型然后这个商品有名字价格评价三种属性然后我们想堆商品进行排序如下代码
struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价//...Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};//struct ComparePriceLess
struct Compare1
{bool operator()(const Goods gl, const Goods gr){return gl._price gr._price;}
};//struct ComparePriceGreater
struct Compare2
{bool operator()(const Goods gl, const Goods gr){return gl._price gr._price;}
};void main24()
{vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2, 3 }, { 菠萝, 1.5, 4 } };/*sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());*///sort(v.begin(), v.end(), Compare1());//sort(v.begin(), v.end(), Compare2());//sort的时候需要传一个比较的对象过去所以需要传一个函数指针或者仿函数过去而这个函数指针或仿函数又需要定义实现在全局// 而且在比较大的项目中有很多人你用你的名字我用我的最后就导致我看不懂你的你看不懂我的容易混乱//所以万一遇到命名或者作用域不一样的问题就很难解决所以lamdba就可以很好地解决这个问题//lamdba是一个局部的匿名函数对象sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) {return g1._name g2._name; });sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) {return g1._name g2._name; });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; });
}
5.3 其他捕捉方法
void main()
{int a 1, b 2, c 3, d 4, e 5;auto f1 []() { //[]里面加全部传值捕捉改成就是全部引用捕捉cout a b c d e endl; };//但是不打印因为f1仅仅只是定义需要调用才会打印f1();//混合捕捉auto f2 [, a]() //表示除a是引用捕捉以外全部传值捕捉{a;cout a b c d e endl;a--;};f2();static int x 0;if (a){ //a使用传值捕捉其他全部用引用捕捉auto f3 [, a]() mutable{a;b;c;d;e;x;//可以捕捉位于静态区的变量cout a b c d e endl;};f3();}//捕捉列表本质是在传参其底层原理和仿函数很相同类似于范围for和迭代器的关系
}
5.4 函数对象与lamdba表达式
函数对象又称仿函数可以像函数一样使用对象是因为在类中重载了operator()运算符的类对象。现在我们观察下列代码和反汇编指令
class Rate
{
public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){cout 调用对象的仿函数 endl;return money * _rate * year; }private:double _rate;
};class lambda_xxxx{};
void main() //lambda不能相互赋值
{// 函数对象double rate 0.49;Rate r1(rate);r1(10000, 2);//调用汇编看先call构造一个仿函数的对象再去call调用operator()//编译器在编译时会把lambda表达式搞成一个空类所以lambda表达式的大小是一个字节cout sizeof(lambda_xxxx{}) endl;// 仿函数lambda_uuid -- uuid是随机生成的不重复唯一标识符当有很多个lanbda时汇编代码就通过uuid获取不同的lambda (uuid是算法算出来的一种唯一字符串)// lambda - lambda_uuid -- 所以对我们来说是匿名的对编译器来说是有名的也导致lambda不能相互赋值auto r2 [](double monty, int year)-double{return monty*rate*year; }; //landba调用时也区call调用一个构造和对象调用是一样的r2(10000, 2);auto r3 [](double monty, int year)-double{return monty*rate*year; };r3(10000, 2);
} 可以发现从使用方式上来看函数对象和lamdba表达式完全一样函数对象将rate作为其成员变量在定义对象时给出初始值即可lamdba表达式通过捕获列表可以直接将该变量捕获到。
另外就是代码注释里提到的lamdba_uuid是随机数截图中我用的是VS2022的反汇编结果是lamdba在该函数作用域中的顺序编号这其实是因为编译器的不同对lamdba的处理方式不同下面是VS2013调用反汇编的情况 六function包装器
6.1 是什么
function包装器也叫适配器在C中本质是一个类模板
#includefunctionaltemplateclass T functiontemplate class Ret, class... Args
classs functionRet(Args...)其中Ret表示被调用函数的返回类型Args...表示被调用函数的形参可变参数包 如下列代码
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 Functor3
{double operator()(double d){return d / 3;}
};void main()
{// 函数名cout useF(f, 11.11) endl;// 函数对象cout useF(Functor3(), 11.11) endl;// lamber表达式cout useF([](double d)-double { return d / 4; }, 11.11) endl;
} 可以看到useF打印count的地址时打印了三个不同的值代表着useF函数模板被实例化了三份。所以一旦类型过多函数指针函数对象lamdba表达式这些都是可调用的类型类型过多就会导致模板的效率低下所以我们需要用到包装器这个东东本质是对可调用对象进行再封装适配
如下代码和现象
void main()
{//函数指针functiondouble(double) f1 f;cout useF(f1, 11.11) endl;// 函数对象functiondouble(double) f2 Functor3();cout useF(f2, 11.11) endl;// lamber表达式对象functiondouble(double) f3 [](double d)-double{ return d / 4; };cout useF(f3, 11.11) endl;
} 用了包装器之后打印的地址就是一样的了
6.2 包装器使用
int _f(int a, int b)
{cout int f(int a, int b) endl;return a b;
}
struct Functor
{
public:int operator()(int a, int b){cout int operator()(int a, int b) endl;return a b;}
};void main()
{int(*pf1)(int, int) _f; //mapstring, 上面两个东东调用起来是一样的都是函数但是类型完全不同所以无法用map同时声明两个functionint(int, int) f1 _f;functionint(int, int) f2 Functor();functionint(int, int) f3 [](int a, int b) {cout [](int a, int b) { return a b;} endl;return a b;};cout f1(1, 2) endl;cout f2(10, 20) endl;cout f3(100, 200) endl;cout --------------- endl;mapstring, functionint(int, int) opFuncMap;opFuncMap[函数指针] f1;opFuncMap[函数对象] Functor();opFuncMap[lamdba] [](int a, int b) {cout [](int a, int b) { return a b;} endl;return a b;};cout opFuncMap[函数指针](1, 2) endl;cout opFuncMap[函数对象](1, 2) endl;cout opFuncMap[lamdba](1, 2) endl;} class Plus1
{
public:Plus1(int rate 2):_rate(rate){}static int plusi(int a, int b) { return a b; }double plusd(double a, double b) { return a b; }
private:int _rate 2;
};void main()
{functionint(int, int) f1 Plus1::plusi;//包装类内部静态函数functiondouble(Plus1, double, double) f2 Plus1::plusd;//包装内部非静态函数要加而且类内部函数还有个this指针所以要传三个参数cout f1(1, 2) endl;cout f2(Plus1(), 20, 20) endl;cout f2(Plus1(), 1.1, 2.2) endl;Plus1 pl(3);cout f2(pl, 20, 20) endl;//为什么不能下面这样用我传个指针为啥不行//functiondouble(Plus1, double, double) f2 Plus1::plusd;//cout f2(Plus1(), 20, 20) endl;//Plus1 pl(3);//cout f2(pl, 20, 20) endl;//用指针是可以的但是后面用的时候只能像这样传指针了不能传匿名对象了左值可以取地址右值不能取地址//如果传的是对象就用对象去调用如果传的是指针就用指针去调用//成员函数不能直接调用需要用对象去调用
} 6.3 逆波兰OJ题
//逆波兰表达式
class Solution
{
public:int evalRPN(vectorstring tokens){stacklong long st; //返回值 参数包mapstring, functionlong long(long long, long long) opFuncMap //这里这样写就不用再用which case语句了简便很多{//列表初始化pair初始化{,[](long long a,long long b) {return a b; }},{-,[](long long a,long long b) {return a - b; }},{*,[](long long a,long long b) {return a * b; }},{/,[](long long a,long long b) {return a / b; }},};for (auto str : tokens){if (opFuncMap.count(str)) //题目传给我们的只有数字和运算字符如果str是字符那么表示这个字符必定在包装器里面直接出栈然后opFuncMap是一个map通过str找到对应关系然后通过lambda得出值{long long right st.top();st.pop();long long left st.top();st.pop();st.push(opFuncMap[str](left, right)); //把上一个运算符算出的结果再入栈}else//操作数直接入栈{st.push(stoll(str)); //stoll字符串转数字函数}}return st.top();}
};
6.4 bind std::bind定义在头文件中也是一个函数模板像一个函数包装器接受一个可调用对象生成一个新的可调用对象来“适应”原对象的参数列表。简单来说我们可以用它把一个原本接收N个参数的函数fn沟通过绑定一些参数返回一个接收M个参数的新函数bind还可以实现参数顺序调整等操作
template class Fn, class... Args
/* unspecified */ bind (Fn fn, Args... args);// with return type (2)
template class Ret, class Fn, class... Args
/* unspecified */ bind (Fn fn, Args... args);
6.4.1 bind调整参数顺序
//调整顺序
// _1,_2是占位符代表绑定函数对象的形参定义在placeholders中
// _1,_2分别代表第一个第二个形参...
using namespace placeholders;
void Print(int a, int b)
{cout void Print(int a, int b) endl;cout a ;cout b endl;
}
void main()
{Print(10, 20);auto RPrint bind(Print, _2, _1);// 是类似占位符_1是第一个参数_2就是第二个参数把第二个参数放到第一个位置去。绑定完后bind会返回一个对象//functionvoid(int, int) RPrint bind(Print, _2, _1);RPrint(10, 20);
} 6.4.2 bind调整参数个数
//调整参数个数
class Sub
{
public:Sub(int rate 0):_rate(rate){}int sub(int a, int b){return a - b;}
private:int _rate;
};void main()
{functionint(Sub, int, int) fSub Sub::sub;fSub(Sub(), 10, 20);//这里要传对象是因为只能通过对象去调用sub//上面两个有点麻烦因为我们还要传个对象过去还有两个参数 -- 麻烦//包装的时候可以绑死某个参数减少调用的时候传多余的参数最好和function一起用//functionint(Sub, int, int) funcSub Sub::sub;//这条语句就可以变成下面这个functionint(int, int) funcSub1 bind(Sub::sub, Sub(), _1, _2);cout funcSub1(10, 20) endl;//这里的sub我们定义在一个类中这里调用的时候传参就要传四个但是我们前面用了绑定了之后就只需要传对应_1和_2的参数了//上面那个是绑死前面要传的对象参数如果我们想绑死中间那个参数呢functionint(Sub, int) funcSub2 bind(Sub::sub, _1, 100, _2);cout funcSub2(10, 20) endl;mapstring, functionint(int, int) opFuncMap {{-,bind(Sub::sub,Sub(),_1,_2)}};cout opFuncMap[-](1, 2) endl;cout endl;} 七补充
7.1 auto和decltype
在C98中auto只是一个存储类型的说明符表示该变量是自动存储类型但是局部域中定义的局部变量默认就是自动存储所以auto就没什么用了。C11中放弃了auto以前的用法将其用于实现自动类型判断让编译器将定义对象的类型设置为初始化值的类型
int main()
{int i 10;auto p i;auto pf strcpy;cout typeid(p).name() endl;cout typeid(pf).name() endl;mapstring, string dict { {sort, 排序}, {insert, 插入} };//mapstring, string::iterator it dict.begin();auto it dict.begin();return 0;
}
decltype关键字的作用是将变量的类型声明为表达式指定的类型
void main2()
{int x 10;//typeid不能定义对象它只是拿到字符串的类型而且拿到这个类型后也不能直接初始化对象只能去打印//typeid(x).name() y 20;//decltype可以定义一个和括号内类型一样的但是和auto不一样哦decltype(x) y1 20.22;auto y2 20.22;cout y1 endl;//打印20cout y2 endl;//打印20.22//vector存储类型跟x*y表达式返回值类型一致//decltype推导的表达式类型然后用这个类型实例化模板参数或者定义对象vectordecltype(x* y1) n;
}
7.2 两个被批评的容器
C11新增了一些容器其中最有用的两个闪光点容器就是unordered_map和unordered_set这两个容器我们前面已经详细讲过。
但是也有两个容器是被批评的也就是几乎没啥用的array和forward_list
void main()
{const size_t N 100;int a1[N];//C语言数组越界检查的缺点:1,越界读根本检查不出来 2,越界写可能会抽查出来a1[N];//a1[N] 1;//a1[N 5] 1;//C11新增的array容器就把上面两个问题全解决了arrayint, N a2;//a2[N];a2[N] 1;a2[N 5] 1;//但实际上array用得非常少一方面大家用c数组用习惯了其次用array不如用vector resize去替代c数组
}
7.3 emplace系列接口
这个系列接口没啥好说的直接看下面两段代码
void main()
{std::listbit::string mylist;bit::string s1(1111);mylist.push_back(s1);mylist.emplace_back(s1); //都打印深拷贝cout ---------- endl;bit::string s2(2222);mylist.push_back(move(s2));mylist.emplace_back(move(s2)); //都打印移动拷贝cout ---------- endl;//上面的没有区别下面的就有区别了和上面两个比起来只有一个移动构造mylist.push_back(3333); // 构造匿名对象 移动构造mylist.emplace_back(3333);// 直接构造vectorint v1;v1.push_back(1);v1.emplace_back(2);//std::vector::emplace_back//templateclass... Args//void emplace_back(Args... args);vectorpairstd::string, int v2;v2.push_back(make_pair(sort, 1));//pair的一次构造再对象的拷贝构造移动构造//std::vector::push_back//void push_back (const value_type val);//void push_back (value_type val);v2.emplace_back(make_pair(sort,1));v2.emplace_back(sort,1);//结论:深拷贝 emplace_back和push_back效率差不多相同
} class Date
{
public:Date(int year 1, int month 1, int day 1):_year(year), _month(month), _day(day){cout Date(int year 1, int month 1, int day 1) endl;}Date(const Date d):_year(d._year), _month(d._month), _day(d._day){cout Date(const Date d) endl;}Date operator(const Date d){cout Date operator(const Date d)) endl;return *this;}Date(Date d){cout Date(Date d);}Date operator(Date d){cout Date operator(Date d) endl;return *this;}private:int _year;int _month;int _day;
};
void main()
{//浅拷贝的类//没区别std::listDate list2;Date d1(2023, 5, 28);list2.push_back(d1);list2.emplace_back(d1);cout ---------- endl;Date d2(2023, 5, 28);list2.push_back(move(d1));list2.emplace_back(move(d2));cout endl;// 有区别cout ---------- endl;list2.push_back(Date(2023, 5, 28));list2.push_back({ 2023, 5, 28 }); //多参数隐式类型转换cout endl;cout ---------- endl;list2.emplace_back(Date(2023, 5, 28)); // 构造移动构造list2.emplace_back(2023, 5, 28); // 直接构造,直接传参数通过可变参数包一直往下传
}