文章目录
一.列表初始化
列表☞的是”{}“;
改变一:单个的内置类型也可以使用{};还可以删除=;
改变二:类对象也可以使用{}来初始化(使用了多参数的式类型转化)
c++11的{}使用隐式类型转化,将{}的内容先调用构造函数,再调用拷贝构造函数将其赋值;
💘证据:
这里可以证明{}内是临时数据;使用了构造函数构造了临时对象;
但是对于隐式类型转化,c++11会进行优化为直接构造;
变化二的拓展:类数组初始化也可以使用{}来表示临时对象
问题
问:这里的1.2.3和4的形式一样吗?
**答:**不一样,4的实参是固定的(是构造函数),而1,2,3的参数是不确定的;
那1.2.3是如何实现的?下一个知识点有讲讲解:
二. std::initializer_list
std::initializer_list的介绍文档:
initializer_list
std::initializer_list是什么类型:
int main()
{
// the type of il is an initializer_list
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
return 0;
}
所以{}就可以表示为一个initializer_list对象;
initializer_list中的数据类型可以被自动识别;
initializer_list的本质
解决上面的问题
解:
在c++11中所有的容器都添加initializer_list的构造方法;
使用vector举例子:
namespace bit
{
template<class T>
class vector {
public:
typedef T* iterator;
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
typename initializer_list<T>::iterator lit = l.begin();
while (lit != l.end())
{
*vit++ = *lit++;
}
//for (auto e : l)
// *vit++ = e;
}
vector<T>& operator=(initializer_list<T> l) {
vector<T> tmp(l);
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_endofstorage, tmp._endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
补充:
c++对于vector可以使用=的容器也添加initializer_list的赋值方法
例子:
三.声明
3.1auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初
始化值的类型。
可能上面的文字过于枯燥,上图:
3.2 decltype
在c++98中只可以使用typeid()函数来得到变量的类型;但是typeid()得到的变量类型的字符串,不能拿来定义;
c++11中添加decltype()函数,该函数可以将变量类型提取出来用来定义变量;
// decltype的一些使用使用场景
template<class 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的类型是double
decltype(&x) p; // p的类型是int*
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
F(1, 'a');
return 0;
}
3.3 nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
NULL的隐患:
四.范围for
这个我们在前面的课程中已经进行了非常详细的讲解,这里就不进行讲解了,请参考C++入门+STL容器部分的课件讲解
五. 智能指针
由于内容过多,将但写一篇,从我的主页中寻找;
六.STL中的一些变化
6.1新容器
用橘色圈起来是C++11中的一些几个新容器,但是实际最有用的是unordered_map和unordered_set。这两个我们前面已经进行了非常详细的讲解,其他的大家了解一下即可。
array—是静态库的分装类;这里主要出现的目的:可以使用【】来查看越界与否;这里可以使用vector;使用很少;能看懂就好;
array和vector的空间位置不同:array在栈中定义;vector在堆中定义;array的空间会随着元素个数的增加而增加;vector在栈的空间只有几个私有的那几个;
forward–是单链表的分装类;出现的目的:为了节省空间(原因:每个结点没有多个指针)
6.2新构造(各个容器中的initializer_list构造)
见知识点二;
6.3 移动构造和移动赋值(后面见6.6.3)
将6.6看完即可懂得;
6.4右值引用版本插入
c++11中的所有容器里面都添加插入版本;
6.5 其他不太重要的(实用)的接口添加 如:cbegin/cend等
cbegin和cend:
如果容器中的内容原本是常量,那就只能使用cbegin和cend;
如果容器中的内容是变量,使用cbegin和cend这里的迭代器只能读不能写;使用begin和end则可以读和写;
6.6右值引用和移动语义
6.6.1 左值和右值
左值:可以被&(取地址)的值;可以是变量也可以是常量
右值:不可被&的值(包括变量和常量和表达式或函数返回值)
多临时变量和常量;
这里的左右不是一定代表左值一定在=的左边
这里i也是左值;所以左值的左是定义在左边的意思;
左值和右值的特性区别:
左值除了const的左值都可修改;
右值都不可修改;
6.6.2左值引用和右值引用
左值引用:&(以前用的全是左值引用)
右值引用:&&(对右值取别名)
不能修改
左值如何给右值取别名
左值不可直接给右值区别别名;
但是可以使用const +类型+&来为右值取别名
右值引用能否给左值取别名?
右值不可直接给左值区别别名;
但是可以move(左值)取别名
注意:move左值变化为右值是返回值是右值;
右值引用的作用和左值引用的对比
左是引用的作用:
6.6.3 移动语义
右值的分类:
1.纯右值 内置类型右值
2.将亡值 自定义类型右值(原因:自定义类型会删除定义的空间)
移动构造
代码:
string( const string & s)
{
string tmp(s._str);//这里建立新的str;
swap(tmp);
}
string (string &s)
{
swap(s);//这里是直接吧s的str给偷过来了;
}
移动赋值
原来的赋值:
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 深拷贝" << endl;
/*string tmp(s);
swap(tmp);*/
if (this != &s)
{
char* tmp = new char[s._capacity+1];
strcpy(tmp, s._str);//进行了深拷贝
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
移动赋值:
将原来的空间直接改为自己的了;
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s)-- 移动赋值" << endl;
swap(s);
return *this;
}
拷贝于拷贝,拷贝与移动之间的优化
拷贝与拷贝优化为直接拷贝构造;
移动和拷贝直接优化为移动构造;
终结移动构造和移动赋值
会将右值的数据转移完;原右值将不会有数据;
注意
1.每个容器都添加移动构造,如何实现
使用vector举例:
2.模板的中的&&不是右值引用,这里表示引用未知,依靠传过来的数据是右值还是左值;
3.上面的forward函数是可以将t转化为所需的左值或右值;
c++11后的默认函数
构造函数;
析构
函数
赋值
6.7 完美转发
6.7.1模板中的&& 万能引用
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; }
// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
template<typename 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;
}
这里使用的了万能引用:&&,&&可以将自动判断参数传来的类型是左值或者右值;
但是这里的的结果,调用右值的函数结果出现的是Fun函数调用的是左值;
出现上述情况的原因是在函数中使用右值引用引用右值后的**右值引用的属性已经改变为左值的数属性;**解决方法:完美转化;
6.7.2std::forward 完美转发在传参的过程中保留对象原生类型属性
属性;
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::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
Fun(std::forward<T>(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;
}
七.新的类功能
默认成员函数
原来C++类中,有6个默认成员函数:
构造函数
析构函数
拷贝构造函数
拷贝赋值重载
取地址重载
const取地址重载
重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。(就是这三个都没实现)那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
没有实现移动构造的情况
没有实现移动赋值的情况
强制生成默认函数的关键字default
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原
因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以
使用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:
bit::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
禁止生成默认函数的关键字delete
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
Person(const Person& p) = delete;
private:
bit::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
八.可变参数列表(先学会基本特性)
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。
template<class…Args>
{
cout<<sizeof…(args)<<endl;
}
int main ()
{
ShowList(1,2,3);
ShowList(1,'a');
ShowList(1,'A',string("sort"));
return 0;
}
如果我们要如何输出这些值;
//结束条件
void _ShowList()
{
cout << endl;
}
// 21:07继续
// 编译时的递归推演
// 第一个模板参数依次解析获取参数值
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...);
}
如何取出这些值;
template <class T>
int PrintArg(T&& t)
{
cout << t << " ";
return 0;
}
template <class ...Args>
void ShowList(Args&&... args)
{
// 要初始化arr,强行让解析参数包,参数包有一个参数,PrintArg就依次推演生成几个
int arr[] = { PrintArg(args)... };//推演的参数会传到arr
cout << endl;
}
STL容器中的empalce相关接口函数:
https://cplusplus.com/reference/vector/vector/emplace_back/
https://cplusplus.com/reference/list/list/emplace_back/
int main()
{
std::list<bit::string> lt;
bit::string s1("1111");
lt.push_back(s1);
lt.push_back(move(s1));
bit::string s2("1111");
lt.emplace_back(s2);
lt.emplace_back(move(s2));
cout << endl;
lt.push_back("xxxx");
lt.emplace_back("xxxx");
return 0;
}
九.lamda表达式
9.1 C++98中的一个例子(仿函数)
在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。
#include <algorithm>
#include <functional>
int main()
{
int array[] = {4,1,8,5,3,7,0,9,2,6};
// 默认按照小于比较,排出来结果是升序
std::sort(array, array+sizeof(array)/sizeof(array[0]));
// 如果需要降序,需要改变元素的比较规则
std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
return 0;
}
如果待排序元素为自定义类型,需要用户定义排序时的比较规则:
```cpp
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()
{
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());
}
随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。
9.2 lambda表达式
lambda 表达式的格式如下:
[capture-list] (parameters) mutable -> return-type { statement }
其中各部分参数的含义如下:
1.【capture-list】:是捕捉列表,该列表总是出现在lambda的开始位置,编器根据【】来判断接下来的代码是否为lambda函数,捕捉列表上下文中变量供lambda函数使用;捕捉列表不可省略,但可以为空。
2.(parameters):参数列表,和普通参数列表一致,如果不需要参数传递,则可以连同()一起省略,但不建议这样做;
3.mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性;(注意:使用该修饰符时,参数列表不可省略,即使参数为空);但实际上mutable很少使用,因为新参改变不会影响实参。
4.->return-type:返回值类型,用追踪返回类型声明函数的返回类型,没有返回值或者返回值类型明确情况下都可以省略,由编译器对返回类型自动推导,但是写上会增加可读性,一般不会写。
5.{staement}:函数体,和普通函数数的函数体一样,不过在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量
注意:在lambda函数定义中,参数列表和返回值类型都是可选部分,即可以省略不写,同时捕捉列表和函数体也可以为空,因此c++11中最简单的lambda函数为:【】【】;但该lambda函数没有意义。
9.3lambda表达式与函数对象
lambda表达式和仿函数一样,本质上也是一个可调用的函数对象(类对象),所以lambda表达式的使用方式和仿函数 不同的是,lambda表达式的类型是由编译器自动生成的,并且带有随机值,所以我们无法写出lambda表达式的类型,只能使用auto进行推导。
struct Compare {
bool operator()(int a, int b) {
return a < b;
}
};
int main() {
int a = 1;
int b = 2;
//仿函数
Compare com;
cout << com(a, b) << endl;
//lambda表达式
auto func = [](int a, int b) ->bool { return a < b; };
cout << func(a, b) << endl;
return 0;
}
实际上,lambdda表达式的底层实现就是一个仿函数(就是一个类包含operator()函数)使用时就用其定义一个对象,调用opoertor()函数完成函数体中的功能;lambda 表达式底层实际上是通过替换为仿函数来完成的;
9.4lambda表达式中的捕捉序列
lambda表达式最厉害的地方在于捕捉列表,捕捉列表可以捕捉父作用域中lambda表达式之前的所有变量,捕捉方式如下:
int a = 1;
int b = 2;
1.[var]:表示值传递方式捕捉变量var,传值捕捉到的参数默认是被const修饰的,所以我们不能在lambda表达式的函数体中修改他们;如果要修改,我们需要使用mutable修饰;但由于传值捕捉修改的是形参(新开的空间),所以一般我们也不会去修改它;
2.【&var】:表示引用传递捕捉变量var,通过引用传递捕捉,我们就可以在lambda表达式函数体中修改实参的值了;
3.【&】:表示引用传递捕捉所有父作用域的变脸(包括this)
4.【=】:表示值传递方式捕捉所有父作用域中的变量(包括this)
除了上面这四种捕捉方式之外,lambda 表达式的捕捉列表还支持混合捕捉,如下;
lambda表达式有如下注意事项:
1.父作用域是指lambda函数的语句块,捕捉列表可以捕捉父作用域中位于lambda函数之前定义的所有变量;
2.语法上捕捉列表由多个捕捉组成,并以逗号分隔;比如:
- [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量;
- [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量;
3 .捕捉列表不允许重复传递,否则就会导致编译错误;
4 .在块作用域中的lambda函数仅能捕捉父作用域中局部变量,辅佐任何非次作用域或者非局部变量都会导致编译错误;
5.lambda表达式之间不能互相赋值,即使看起来类型相同;
最后,我们可以将最开始的排序仿函数使用lambds表达式的方式来实现;
struct Goods {
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
int main() {
vector<Goods> 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; });
}
10.包装器
10.1、function前言
我们通过下面这个例子来引入包装器:
//函数
double f(double i)
{
return i/2;
}
struct Functor{
double operator() (double d)
{
return d/3;
}
};
template<class F,class T>
T useF(F f,T x)
{
static int count =0;
cout<<"count:"<<++count<<endl;
couot<<"count:"<<&count<<endl;
return f(x);
}
int main ()
{
// 函数指针
cout<<useF(f,11.11)<<endl;
// 仿函数
cout<<useF(Functor(),11.11)<<endl;
// lambda 表达式
cout<<useF([](double d)->double{return d/4;},11.11)<<endl;
return 0;
}
通过 count 变量的地址我们可以发现,尽管 T 的类型相同**,但 useF 函数还是被实例化了三份,**这是因为形参 F 会根据实参的不同而实例化出不同的函数,也就是说,形参 F 的类型有多个,那我们能不能让其类型变为一个,从而只实例化出一份函数呢?function 包装器可以解决这个问题 。
function出现之前只能使用模板来统一调用这些函数;过程复杂;
10.2 function包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
在 C++11 标准中引入了 std::function 模板类,其定义在 头文件中。
std::function<返回值类型(参数类型1, 参数类型2, ...)> f;
function 的使用方式类似于普通类,可以先定义一个 function 对象,然后将需要调用的函数赋值给该对象,也可以在定义 function 对象时直接使用可调用对象完成初始化,最后通过 function 对象进行函数调用;如下:
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;
}
int plusd(int a, int b) {
return a + b;
}
};
int main()
{
// 函数名(函数指针)
function<int(int, int)> func1(f);
cout << func1(1, 2) << endl;
// 函数对象
function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;
// lambda 表达式
function<int(int, int)> func3 = [](const int a, const int b) {return a + b; };
cout << func3(1, 2) << endl;
// 类的静态成员函数--本质还是函数指针
function<int(int, int)> func4 = &Plus::plusi;
cout << func4(1, 2) << endl;
// 类的普通成员函数--本质还是函数指针
std::function<int(Plus, int, int)> func5 = &Plus::plusd;
cout << func5(Plus(), 1, 2) << endl;
return 0;
}
需要特别注意的是:当 function 封装的是类的成员函数时,我们需要对该成员函数进行类域的声明,并且还需要在类域前面加一个取地址符,另外,成员函数又分为静态成员函数和非静态成员函数:
-
静态成员函数没有 this 指针,所以 function
类实例化时不需要添加一个成员函数所属类的类型参数,在调用时也不需要传递一个成员函数所属类的对象;
-
但非静态成员函数有隐藏的 this 指针,所以需要传递这两个东西;
这里的第二行的Plus()是匿名对象; -
错误示范:特别注意,这里传递的是类的类型和类的对象,有的同学可能认为它们对应的是 this 指针,所以应该像下面这样传:
std::function<int(Plus*, int, int)> func5 = &Plus::plusd;
cout << func5(&Plus(), 1, 2) << endl;
但其实不是的,因为 this 指针并不能显式传递,同时,function 包装器也是通过类的对象来调用类中的函数的
可以看到,经过上面 function 的包装,使得函数指针 f、仿函数 Functor、lambda 表达式以及类的静态成员函数具有了统一的类型 – function<int(int, int)>;类的普通成员函数我们也可以通过后面的绑定来让它的类型变为 function<int(int, int)>。
现在,我们就可以解决开始那里模板效率低下,实例化多份的问题了:
double f(double i) {
return i / 2;
}
struct Functor {
double operator()(double d) {
return d / 3;
}
};
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);
}
int main() {
// 函数名
std::function<double(double)> func1 = f;
cout << useF(func1, 11.11) << endl;
// 函数对象
std::function<double(double)> func2 = Functor();
cout << useF(func2, 11.11) << endl;
// lamber表达式
std::function<double(double)> func3 = [](double d)->double { return d /4; };
cout << useF(func3, 11.11) << endl;
return 0;
}
包装器还有一些其他场景,比如下面这道OJ题https://leetcode.cn/problems/evaluate-reverse-polish-notation/submissions/
这道题的传统解法是这样的:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int>st;
for(auto &str :tokens)
{
if(str=="+"||str=="-"||str=="*"||str=="/")
{
int right =st.top();
st.pop();
int left =st.top();
st.pop();
switch(str[0])
{
case '+':
st.push(left+right);
break;
case '-':
st.push(left-right);
break;
case '*':
st.push(left*right);
break;
case '/':
st.push(left/right);
break;
default:
break;
}
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
可以看到,我们需要针对不同的操作符进行不同的处理,但是使用 switch case 的方式又很挫,因为一旦我们要新增一种运算符,则很多地方都要跟着修改,所以这里我们可以使用包装器,如下:
class Solution {
public:
int evalRPN(vector<string>& tokens)
{
stack<int>st;
//map<string,function<int(int ,int)>>opFuncMap=
map<string, function<int(int, int)>> opFuncMap =
{
{"+",[](int x,int y){return x+y;}},
{"-",[](int x,int y){return x-y;}},
{"*",[](int x,int y){return x*y;}},
{"/",[](int x,int y){return x/y;}},
};
for(auto &str :tokens)
{
if(opFuncMap.find(str)==opFuncMap.end())
{
st.push(stoi(str));
}
else
{
int right=st.top();
st.pop();
int left=st.top();
st.pop();
st.push(opFuncMap[str](left,right));
}
}
return st.top();
}
};
10.3 bind
bind 是一个函数模板,也定义在 头文件中,它就像一个函数包装器 (适配器),可以接受一个可调用对象(callable object),然后生成一个新的可调用对象来 “适应” 原对象的参数列表;简单来说,bind 的作用就是调整可调用对象的参数 – 包括调整参数顺序和调整参数个数。
bind 的格式如下:
bind(函数指针或可调用对象, 参数1, 参数2, ...)
其中,第一个参数是需要绑定的函数或函数对象的地址,后面的参数是函数或函数对象需要的参数,可以有任意多个,同时也可以使用占位符(placeholders)对参数进行占位,表示该位置的参数需要在调用时再传递进来。
placeholders 是 C++11 引入的一个命名空间域,它包含了一些占位符对象(placeholder objects),用于在使用 bind 绑定函数时,指定某个参数需要在调用时再传递进来。
bind 的使用案例如下:
int Plus(int a, int b) {
return a + b;
}
int Div(int a, int b) {
return a / b;
}
class Sub {
public:
int sub(int a, int b) {
return a - b;
}
};
int main() {
//表示绑定函数plus,参数分别由调用func1的第一,二个参数指定
function<int(int, int)> func1 = bind(Plus, placeholders::_1, placeholders::_2);
//也可以使用auto自动推导bind函数的返回值类型
//表示绑定函数plus,并且指定参数为5和8
auto func2 = std::bind(Plus, 5, 8);
cout << func1(1, 2) << endl;
cout << func2() << endl;
return 0;
}
bind 可以实现调整参数顺序和参数个数的功能。
bind 调整参数顺序
bind 可以通过调整占位符的顺序来调整参数的顺序,如下:
bind 调整参数个数
int Sub(int a, int b)
{
return a - b;
}
int main()
{
// 调整参数个数,有些参数可以bind时写死
function<int(int)> f3 = bind(Sub, 20, placeholders::_1);
cout << f3(5) << endl;
}
11线程库
和智能指针、右值引用和移动语义一样,线程库我们也作为单独的一篇博客进行学习。