文章目录
- 说在前面
- 花括号{}初始化
- new的列表初始化
- STL相关容器的列表初始化
- 相关语法格式
- 容器列表初始化的底层原理
- forward_list和array
- 与类型相关的新特性
- decltype
- 左值引用和右值引用
- 什么是左值,什么是右值
- 左值和右值的本质区别
- 右值引用
- 如何理解右值引用
- std::move
- 移动构造,移动赋值
- 万能引用和完美转发
- 模板的可变参数
- default和delete的新用法
- lambda表达式
- 语法格式
- 捕获列表
- 底层原理
- 包装器
- std::bind
- 总结
说在前面
作为新年的第一篇博客。在这里先祝大家新年快乐!今天我们要介绍的是C++11 首先,如同一个人从一个一无所知的婴儿成长成为一个社会的精英是需要不断发展的。C++自从诞生的那一刻起,一直都在不断进步,不停更新。对于C++来说,有两个比较重要的时间点。一个就是C++98,这个版本的C++引入了强大的STL。而另外的一个时间点就是我们接下来介绍的C++11 接下来,我们就来看一看C++11更新了哪些有用的东西
花括号{}初始化
在C++11里面,所有的东西都可以使用{}进行初始化,你可能会看到很多有关C++11的书籍里面可能都会有如下的demo代码
#include<iostream>
/*
* C++11 特性
* */
void test1()
{
//C/C++98里面定义变量常用的方式
int i=5;
//C++11定义变量常用方式
int x={3};
int y{3};
}
int main()
{
test1();
return 0;
}
不仅仅是内置类型,自定义类型也可以使用如上的方式进行初始化
//自定义类型也可以支持{}初始化
struct Date
{
Date(int year=2023,int month=1,int day=22)
:_year(year)
,_month(month)
,_day(day)
{}
int _year;
int _month;
int _day;
};
void test2()
{
//C++98
Date d1(2022,1,23);
//c++11
Date d3{2022,1,25};
}
实际上,花括号初始化的本质也是调用了构造函数,我们可以对构造函数添加打印语句:
//自定义类型也可以支持{}初始化
struct Date
{
explicit Date(int year=2023,int month=1,int day=22)
:_year(year)
,_month(month)
,_day(day)
{
std::cout<<"Date(int year,int month,int day)"<<std::endl;
}
int _year;
int _month;
int _day;
};
void test2()
{
//C++98
Date d1(2022,1,23);
//C++11
// Date d2={2022,1,24};
//还可以这样写
Date d3{2022,1,25};
}
而因为增加了{}初始化的方式,那么就能够解决在C++98里面使用new构造一块连续的空间的同时无法顺带初始化的问题!
new的列表初始化
在C++98里面,对于一块连续空间的申请和初始化,我们只能分开进行:
void test3()
{
//C++98里面申请空间和初始化必须分开
//int* p=new int[4];
//for(int i=0;i<4;++i) p[i]=i+1;
//C++11允许这么做,这里直接在
int* p=new int[4]{1,2,3,4};
for(int i=0;i < 4;++i)
{
std::cout<<p[i]<<" ";
}
std::cout<<std::endl;
delete[] p;
}
从C++11以后,我们就可以使用花括号来初始化对应new的连续的数组空间!不过相对来说,这种方式用的还是相对比较少。毕竟更多的情况下,我们都是使用的STL提供的容器。所以这里我们知道有这样一种用法就好了
STL相关容器的列表初始化
STL里面的容器也提供了对应的列表初始化的方式,真可谓是一切都可列表初始化,所以我们才会说到了C++11以后。一切皆可使用列表初始化! 下面我们就来看一看相应的语法格式。
相关语法格式
这里我们用vector来进行演示。基本上我们之前学习的容器都可以使用这样的初始化方式!
//自定义类型也可以支持{}初始化
struct Date
{
Date(int year=2023,int month=1,int day=22)
:_year(year)
,_month(month)
,_day(day)
{
std::cout<<"Date(int year,int month,int day)"<<std::endl;
}
//为了方便观察效果
void PrintDate()
{
std::cout<<_year<<" "<<_month<<" "<<_day<<'\n';
}
int _year;
int _month;
int _day;
};
那么,为什么能够这样给容器初始化呢?在处理容器的{}初始化的时候,编译器做了哪些工作呢?下面我们就来探究一下:
容器列表初始化的底层原理
首先,官方文档就是我们学习C++11最好的资料之一,我们先来看看C++11种vector对应发生了什么变化:
首先,上面两个对应的构造函数是后面的知识点。实际上,真正能够让我们使用花括号初始化对应容器元素的就是最后一个构造函数! 下面我们就通过代码来看一看{}究竟被编译器做了怎样的处理:
void test()
{
auto l={1,2,3,4,5,77};
//获取l变量的类型,并以字符串的形式打印
std::cout<<typeid(l).name()<<std::endl;
}
这段代码是在Linux上获取的,不过对应的就是文档手册里面的新的构造函数的参数的类型initializer_list<T>! 在C++11以后,{}被编译器识别成为新的initializer_list类型,只要支持这个构造函数就可以使用花括号进行初始化! 所以为了让我们自己实现的vector也能够支持{}初始化,我们也添加这个构造函数
namespace chy
{
//vector是一个模板类
template<typename T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//...
//给我们自己实现的vector添加对应的构造函数
//这个时候我们自己实现的vector也就可以支持{}初始化的方式了!
vector( std::initializer_list<T> li)
{
//initiallizer支持范围for
for(auto& e: li)
{
push_back(e);
}
}
private:
iterator _start;
iterator _finish;
iterator _end_of_stroge;
};
}
其他的容器也是一样的道理,这里我就不一一演示了。 另外,C++11还新增加了几个容器。我们之前介绍的哈希相关的容器就是C++11新增的,接下来我们将介绍两个C++11里面存在感比较低的两个容器—> forward_list和array
forward_list和array
C++11新增加了forward_list和array这两个新容器,下面我们先来看forward_list。首先先来看文档对应的说明:
首先,不得不说有的时候C++对于类的命名不是特别能够让人见名知义。这个forward_list其实就是我们之前在数据结构那里学习的单链表!下面,我们就来简单使用以下forward_list
//需要包含这个头文件
#include<forward_list>
void test()
{
std::forward_list<int> fl;
int a[]={1,2,3,4,5};
//没有提供尾插接口
for(auto e:a)
{
fl.push_front(e);
}
//也支持范围for
for(auto e:fl)
{
std::cout<<e<<" ";
}
std::cout<<'\n';
}
说实话,这个容器的作用远远不及list,即使是我们手写一个简单的单链表也是可以的!不过这个容器的倒是可以作为哈希桶悬挂的子节点结构!
接下来,我们来看一看另外一个容器array.首先我们先来看文档对应的说明:
从文档来看,array就是一个固定大小的泛型线性数组。下面我们就来简单使用一下array:
void test()
{
//array的简单使用
std::array<int,5> a={1,2,3,4,5};
for(auto e:a)
{
std::cout<<e<<" ";
}
std::cout<<'\n';
}
使用array要注意如下的几个问题:
1.array是一个静态数组,不能对array插入元素
2.array会对越界进行检查
说实话,C++11这个array确实是没什么太大的作用,它所有的功能,vector基本都有!所以这个容器大家仅仅就是作为一个了解就可以了。不用过分去深究这个容器,相应 的forward_list也是如此。
与类型相关的新特性
C++11还增加了一些有关类型推断的新特性。比如我们经常使用的auto就是在C++11以后才增加的类型推导的能力。 接下来介绍一个C++11新增加的一个和类型相关的特性的一个操作符---->decltype
decltype
这个操作符能够把表达式的类型返回给一个变量,测试的代码如下:
void test()
{
decltype(10+20)x=10;
std::cout<<"x变量的类型为: "<<typeid(x).name()<<std::endl;
}
可以看到这个操作符确实把类型返回了。注意,这个操作符和sizeof操作符一样,并不会去计算对应括号的表达式,仅仅就是返回对应的表达式的类型
左值引用和右值引用
前面讲的特性大多是是C++11相对来说属于锦上添花的。而右值引用可以说式C++11相对来说非常具有革命性意义的一个新特性。它让C++的高效又上了一个台阶! 在正式介绍右值引用之前,我们有必要来明确一个概念:什么是左值 ? 什么是右值?
什么是左值,什么是右值
首先,从字面上的意思来看:左值就是出现在表达式左边的值,右值就是出现在表达式右边的值。比如下面的代码:
//左值和右值
void test()
{
//从字面意义上来看,这里的a是左值
int a=10+20;
//10+20就是一个右值
//但是下面怎么解释
int b=a;//a不是左值?怎么出现在了右边
}
最后一个例子不难看出:这样的理解是不正确的,如果这样子理解左值和右值就太肤浅了。 那么左值和右值的本质区别到底是什么呢?或者说,左值具有什么特征,右值又有什么特征呢?
左值和右值的本质区别
明确地给出一个结论:左值是可以取地址地值!右值是不能取地址的值!也就是说:通过能否取地址就可以看出:左值是真正分配了计算机内存空间的值!而右值仅仅只是一个临时量!
右值引用
在C++98以前,我们所使用的引用都叫做左值引用。从C++11开始,我们就要把引用分为左值引用和右值引用了。下面我们就来通过代码来看这两个引用:
void test()
{
int x=10;
//rx是x的别名,是左值
int& rx=x;
//右值引用使用两个&&
int&& rrx=10; //10是右值,所以rrx是右值引用
}
无论右值引用还是左值引用,只要是引用,那么都是被引用对象的别名!而右值引用自身是一个左值,因为我们可以对右值引用的那个别名取地址了!
void test()
{
int x=10;
//rx是x的别名,是左值
int& rx=x;
//右值引用使用两个&&
int&& rrx=10; //10是右值,所以rrx是右值引用
std::cout<<"rrx的地址是:" <<&rrx<<std::endl;
}
如何理解右值引用
从前面的结论可以看出:本来即将消亡的右值,通过右值引用的方式,计算机也确实为了它开辟一块内存空间存储对应的值! 也就是这个别名本质是一个左值!所以右值引用是一个左值,这个左值是右值的别名! 左值引用只能引用左值!而右值引用只能引用右值
std::move
那么有的时候,我们需要把左值转换成为右值。c++11的设计者也考虑到这个点了,所以提供了一个接口:std::move—>可以把左值转换成右值。下面我们来看一段代码:
void fun(int& rx)
{
std::cout<<"fun(int&)"<<std::endl;
}
void fun(int&& rx)
{
std::cout<<"fun(int&&)"<<std::endl;
}
void test()
{
int x=10;
fun(x);
fun(std::move(x));
}
所以我们就可以总结如下:
左值引用只能引用左值,const左值引用可以引用左值也可以引用右值
右值引用可以引用右值和move以后的左值
移动构造,移动赋值
那么C++11推出右值引用难道仅仅是我们前面那样使用吗?答案显然不是!在C++98的时候,C++一直都很害怕这样的场景:
class Solution {
private:
//获取 对应的keyboard里面的行
size_t getRow(char ch,vector<string>& v)
{
for(int i=0;i<v.size();++i)
{
if(v[i].find(tolower(ch))!=string::npos)
{
return i;
}
}
return v.size();
}
public:
vector<string> findWords(vector<string>& words)
{
vector<string> keyboard;
keyboard.push_back("qwertyuiop");
keyboard.push_back("asdfghjkl");
keyboard.push_back("zxcvbnm");
vector<string> res;
for(int i=0;i<words.size();++i)
{
size_t pos=getRow(words[i][0],keyboard);
//pos=3,说明不在这一行,继续寻找下一行
//cout<<pos<<" ";
if(pos==3) continue;
else
{
// cout<<pos<<" ";
//找到了对应行
bool flag=true;
for(char ch:words[i])
{
if(keyboard[pos].find(tolower(ch)) == string::npos)
{
flag=false;
break;
}
}
if(flag)
res.push_back(words[i]);
}
}
return res;
}
};
通过前面的学习我们知道,传值返回会发生拷贝。而对于vector这样的类,发生的是深拷贝!一旦这个vector管理的对象特别多,并且每个对象又要进行深拷贝的情况下。传值返回的效率就是一场灾难! C++是一门十分注重效率的编程语言,而右值引用便是为了解决这个问题应运而生的!
我们知道右值编译器是没有为其分配空间的,那么对于自定义类型的右值,C++又将其称为将亡值 既然是将亡值,那么就意味着这个自定义类型的对象会马上调用析构函数清理自己了。但是这个时候这个自定义类型身上的资源又是我所需要的。下面就有如下两种做法:
1.在对象析构之前,我深拷贝一个副本,然后这个对象自行销毁
2.既然这个将亡对象有我想要的资源,那么不妨把它的资源为我所用!然后再让其自行销毁即可!
显然我们都会选择第二种方式。那么c++11以后,就新增加了两个相应的默认成员函数:移动构造和移动赋值
class A
{
public:
A()
:_p(nullptr)
,_size(0)
{}
A(int size)
:_p(nullptr)
,_size(size)
{
_p=new int[size];
}
//拷贝构造
A(const A& a)
:_p(nullptr)
{
_p=new int[a._size];
for(int i=0;i < a._size;++i)
{
_p[i]=a._p[i];
}
_size=a._size;
std::cout<<"A(const A& a)"<<std::endl;
}
//移动构造
A(A&& aa)
:_p(nullptr)
{
std::swap(_p,aa._p);
std::swap(_size,aa._size);
std::cout<<"A( A&& aa)"<<std::endl;
}
~A()
{
delete _p;
_p=nullptr;
}
private:
int* _p;
int _size;
};
接下来我们使用gdb进行调试观察结果:
接下来就要开始构造a3了,使用的是移动构造:
可以看到,这里的a3和a1的成员发生了交换。相比于先前,我们只能,这种场景我们只能进行深拷贝。当对象很大的时候,效率非常低!而有了C++11的移动构造以后,传值返回的效率得到了大幅度的提升!
而对于传值返回的情况,现代的编译器也会进行一定的大胆的优化,假设我们现在有一个to_string函数:
//一个我们自己写的to_string函数
namespace chy
{
string to_string(int val)
{
string str;
while(val)
{
//...复杂的转换逻辑
}
return str;
}
}
那么在C++98的时候,传值返回的情况如下:
那么C++11有了移动构造了以后,编译器的传值返回情况又不一样了
对应的有了移动构造,就会有移动赋值:
A& operator=(A&& aa)
{
std::swap(_p,aa._p);
std::swap(_size,aa._size);
return *this;
}
既然是作为默认成员函数,那么在特定的条件下,编译器就会生成对应的默认移动构造和移动赋值,需要满足如下三个特性:
1.没有显式提供拷贝构造函数
2.没有显式提供赋值重载
3.没有显式提供析构函数
只有满足如上三条件,编译器才会生成默认的移动构造和移动赋值函数!
默认生成的移动构造对于内置类型浅拷贝,对于自定义类型,如果提供了移动构造函数,那么就会调用移动构造 不过,多数情况下生成的默认移动构造没什么价值,所以这两个默认成员函数一般都需要手动提供。
需要注意的是:在c++11以后,原先将单个元素插入容器的函数接口都提供了对应的右值版本,目的就是为了能够进一步提高运行效率!
万能引用和完美转发
而当模板和右值引用结合起来的时候,又会发生不一样的效果:
template<typename T>
void solve(T&& t)
{
std::cout<<"调用!"<<std::endl;
}
void test()
{
A a1;
A& a2=a1;
// 左值引用传参
solve(a2);
// 右值引用传参
solve(std::move(a1));
}
我们可以看到,无论是左值还是右值,这个模板函数都能匹配! 这个就是右值引用和模板结合到了一起以后。既能够接受左值,也可以接受右值,因此我们就把这个特性叫做万能引用。 但是这个万能引用有一个很不好的缺陷,接下来我们来通过一段代码进行演示:
template<typename T>
void solve(T&& t)
{
T tmp(t);
}
void test()
{
A a1;
// 右值引用传参
solve(std::move(a1));
}
我们发现,move以后的a1本来应该是一个右值,结果传参了以后变成了一个左值,万能引用就会出现引用折叠的问题。使用万能引用了以后,所有的传递的T&&参数都会变成左值!
,但是有的时候我们希望能够把原来的特性保持下去,所以c++11又提供了一种新的方式---->完美转发
template<typename T>
void solve(T&& t)
{
//使用forward保持特性
T tmp(std::forward<T>(t));
}
void test()
{
A a1;
solve(std::move(a1));
}
这里依旧调用的是移动构造函数,很好保持了原来右值的特性。所以如果要保持对应的原来的值的特性,最好使用forward完美转发。
模板的可变参数
接下来我们来讲一讲模板的可变参数,在C语言阶段,我们经常使用如下的函数:
int printf(const char* format,...);
函数声明里面带有省略号的我们称之为可变参数!在C++11里面,引入了可变模板参数!也就是说,以后一个模板函数或者模板类,不仅类型可变,参数的数量也是可以变化的! 下面我们就来看demo代码:
template<typename T>
void PrintArgs(const T& val)
{
std::cout<<val<<std::endl;
}
template<typename T,typename... Args>
void PrintArgs(const T& val,Args... args)
{
std::cout<<val<<" typeinfo is "<<typeid(val).name()<<std::endl;
//可以使用sizeof获取传递参数的个数
std::cout<<"param num is "<<sizeof...(args)<<std::endl;
//参数解包
PrintArgs(args...);
}
void test()
{
// A a1;
// solve(std::move(a1));
//可以传任意类型,任意数目的参数
PrintArgs(1,2.3,'c',std::string("hello world"));
std::cout<<'\n';
PrintArgs(1.1,2);
}
可变参数模板是通过递归实现的,允许有0个或者是1个递归参数包,我们这里使用的是模板的最佳匹配原则来确定递归的出口,如果这类换成一个最佳匹配的函数也可以!
前面这种写法相对比较繁琐,有的大神还设计出了这样的写法:
//大神的写法
template<class T>
void PrintArgs(T t)
{
std::cout<<std::endl;
}
template<class ... Args>
void PrintArgs(Args... args)
{
std::cout<<"param num is "<<sizeof...(args)<<std::endl;
//使用数组进行解包 ,利用逗号表达式的特性进行参数包解析
int a[]={(PrintArgs(args...),0)};
}
void test()
{
PrintArgs(1,2.3,'c',std::string("hello world"));
}
注意:可变参数递归解包是在编译的时候完成的,不能通过运行时的if逻辑来递归!
有了可变模板参数的知识铺垫,那么我们就可以很好讲解c++11里面容器新增的emplace_back和emplace接口:
emplace系列接口使用的都是可变参数模板,对应如果传递的参数能够匹配到自定义类型的构造函数,就会变成直接构造!提高效率,不过有了移动构造以后,emplace接口带来的效率提升也不是特别明显,所以这个接口了解即可
default和delete的新用法
在C++11里面,又对default和delete这两个关键字进行了功能的扩展。在类和对象章节我们介绍过,只有在不提供任何构造函数的情况下,编译器才会生成默认构造函数! 但是在c++11如果已经提供了其他构造函数的情况下,仍然想使用编译器生成的默认构造函数,可以使用default关键字
class A
{
public:
//强制编译器生成默认构造函数
A()=default;
A(int size)
:_p(nullptr)
,_size(size)
{
_p=new int[size];
}
//拷贝构造
A(const A& a)
:_p(nullptr)
{
_p=new int[a._size];
for(int i=0;i < a._size;++i)
{
_p[i]=a._p[i];
}
_size=a._size;
std::cout<<"A(const A& a)"<<std::endl;
}
//移动构造
A(A&& aa)
:_p(nullptr)
{
std::swap(_p,aa._p);
std::swap(_size,aa._size);
std::cout<<"A( A&& aa)"<<std::endl;
}
~A()
{
delete _p;
_p=nullptr;
}
private:
int* _p;
int _size;
};
不仅是默认构造函数,其他构造函数都可以使用default强制编译器默认生成
不仅仅可以强制编译器生成,我们还可以删除默认成员函数:使用delete关键字
比如删除拷贝构造函数:
class A
{
public:
//强制编译器生成默认构造函数
A() = default;
A(int size)
:_p(nullptr)
, _size(size)
{
_p = new int[size];
}
//拷贝构造
A(const A& a) = delete;
//移动构造
A(A&& aa)
:_p(nullptr)
{
std::swap(_p, aa._p);
std::swap(_size, aa._size);
std::cout << "A( A&& aa)" << std::endl;
}
~A()
{
delete _p;
_p = nullptr;
}
private:
int* _p;
int _size;
};
int main()
{
A a2(a1);
return 0;
}
lambda表达式
接下来我们来讲lambda表达式。最早引进lambda表达式的语言式python,后面其他语言看到这个语言很好用,于是都纷纷加到了标准库里面。c++11以后同样也支持这样的用法
比如我们上京东购物,有时想按照商品价格排序,有时候想按照实际排序,有各种各样的排序需求。在c++98里面,我们就要写不同的仿函数来实现,一旦随着项目的扩展,这些仿函数就会越来越多,非常难维护
所以我们就希望能够达到一种排序规则专为一种排序方法所用!而lambda表达式恰好就是这样的一个工具,下面我们就来看lambda表达式的语法
语法格式
lambda表达式子的语法特征:
//lambda表达式的语法格式
[捕捉列表](函数参数){ 函数体}
接下来我们就结合具体的样例进行分析
struct Student
{
Student(int age,int score,const std::string& name)
:_age(age)
,_score(score)
,_name(name)
{}
int _age;
int _score;
std::string _name;
void print()
{
std::cout<<_name<<" " << _age <<" "<<_score<<std::endl;
}
};
void test()
{
Student a[] ={{15,23,"aa"},{14,55,"cc"},{13,88,"bb"} };
//显式使用lambda
auto l=[](const Student& s1,const Student& s2){ return s1._age < s2._age ;};
std::sort(a,a+3,l);
for(auto e : a)
{
e.print();
}
}
但其实,lambda更多情况下用的是这样的:
std::sort(a,a+3,[](const Student& s1,const Student& s2){ return s1._score < s2._score ;});
for(auto e : a)
{
e.print();
}
捕获列表
首先我们要知道一个点。lambda是一个局部匿名函数,既然是函数,那么就代表者,对应的内部是一个全新的作用域!内部作用域不能直接使用外部的变量!
如果我们想要使用对应的变量,就需要进行捕获!
void test()
{
//lambda捕获列表
//可以使用[]捕获对应的元素
int a=1,b=2;
auto l=[a,b](){ return a+b; };
std::cout<<l()<<std::endl;
}
这种值的捕获方式是值捕获,默认是对捕获的值带了一个const,想要更改值捕获的话就要使用mutable关键字,不过即使更改内部的a,b也对外面没有影响!所以这个mutable几乎没有
,不过我们还可以使用引用捕获
int a=1,b=2;
int ret=0;
//auto l=[a,b](){ return a+b; };
//std::cout<<l()<<std::endl;
//引用捕获
auto l=[&ret,a,b](){ret =a+b; return 0;};
l();
std::cout<<"ret="<<ret<<std::endl;
引用捕捉和值捕捉可以混合来,下面这三种写法都是可以的
//=表示用值捕捉所有变量
auto l1=[=](){};
//表示所有变量引用捕获
auto l2=[&](){};
//ret引用捕获,其他变量值捕获
auto l3=[&ret,=](){};
关于lambda的介绍我们就暂时讲到这里。下面我们来看一看这个lambda对于编译器来说究竟是什么?
底层原理
我们将编译的汇编代码进行研究:
可以看到,lambda函数被编译器处理以后就是一个仿函数!这个仿函数是lambda+一个唯一的uuid!
所以即使两个lambda在我们看来一模一样。它们也是不同的类型 值得一提的是,lambda函数还可以和函数指针相互赋值,不过我们并不建议这么去做!
包装器
学到现在,c++11有三个可以进行调用的东西:
1.函数指针
2.仿函数对象
3.lambda
而如果当这些函数统统编程模板的时候。同样的模板就会实例化出三分代码,显然这三份代码还是有一定的冗余!毕竟对于我们都是希望使用对应的()的功能! 因此C++11提供了函数包装器:
void func()
{
std::cout<<"func pointer"<<std::endl;
}
struct Func
{
void operator()()
{
std::cout<<"Func()"<<std::endl;
}
};
auto f=[]() -> void {std::cout<<"lambda"<<std::endl;};
void test()
{
std::vector<std::function<void()>> fuctions;
fuctions.push_back(func);
fuctions.push_back(Func());
fuctions.push_back(f);
for(auto& uf : fuctions)
{
uf();
}
std::cout<<'\n';
}
也就是说:但凡是返回值和参数都相同的函数,我们都可以直接使用一个function对象来接收!接下来我们改造一下曾经的逆波兰表达式的题目,用C++11的方式改造:
class Solution {
private:
//纯c++11的方式
unordered_map<string,function<int(int,int)>> hash=
{
{"+" ,[](int a,int b){return a+b;}},
{"-" ,[](int a,int b){return a-b;}},
{"*",[](int a,int b){return a*b;}},
{"/",[](int a,int b){return a/b;}}
};
public:
int evalRPN(vector<string>& tokens)
{
stack<long long > st;
for(const auto& str:tokens)
{
if(hash.find(str)==hash.end())
{
st.push(stoll(str));
}
else
{
long long right=st.top();
st.pop();
long long left=st.top();
st.pop();
long long ret=hash[str](left,right);
st.push(ret);
}
}
return static_cast<int>(st.top());
}
};
std::bind
最后我们介绍的是C++11引入的bind函数,对应的函数原型如下
:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
这个函数有如下的两个作用:
1.将一个参数固定,也就是绑定一个固有的参数
2.调整参数的顺序
接下来我们来看示例代码:
int Sub(int a,int b)
{
return a-b;
}
class A
{
public:
int ASub(int a,int b)
{
return a-b;
}
};
void test()
{
std::function<int(int,int)> f(&A::Asub);
}
这段代码是错误的 ! 原因是:成员函数有一个隐含的this指针!那么有没有方式可以不需要这个this指针呢?答案是肯定的,解决的方法就是使用std::bind函数
class A
{
public:
A(int a=0)
:_a(a)
{}
int ASub(int x,int y)
{
return (x-y)*_a;
}
private:
int _a;
};
void test()
{
// std::bind(&A::ASub,A(),std::placeholders::_1,std::placeholders::_2);
// std::function<int(int,int)> f(&A::ASub); 错误,非静态成员函数有this指针
// 解决方式:使用std::bind绑定this指针
// 为了演示效果,给A加一个成员
std::function<int(int,int)> f1(std::bind(&A::ASub,A(),std::placeholders::_1,std::placeholders::_2));
std::cout<<f1(2,3)<<std::endl;
std::function<int(int,int)> f2(std::bind(&A::ASub,A(2),std::placeholders::_1,std::placeholders::_2));
std::cout<<f2(2,3)<<std::endl;
}
bind还有一个作用就是改变参数的位置。而我们代码里面的_1和_2就是我们要显式传递的参数的位置,也就是占位符!可以通过占位符来改变参数顺序:
void test()
{
std::function<int(int,int)> f1(std::bind(Sub,std::placeholders::_1,std::placeholders::_2));
std::function<int(int,int)> f2(std::bind(Sub,std::placeholders::_2,std::placeholders::_1));
std::cout<<f1(2,3)<<std::endl;
std::cout<<f2(2,3)<<std::endl;
}
不过相对而言,这个调整顺序并不是很常用。
总结
以上就是C++11的部分新特性的介绍。而C++11更多的新特性,例如智能指针我们会在后续的文章里面重点介绍。