前言
在C++11新标准中,语言本身和标准库都增加了很多新内容,本文只涉及了一些皮毛。不过我相信这些新特性当中有一些,应该成为所有C++开发者的常规装备。本文主要介绍了C++11中lambda、std::function和std:bind,下面来一起看看详细的介绍吧。
lambda 表达式
C++11中新增了lambda 表达式这一语言特性。lambda表达式可以让我们快速和便捷的创建一个”函数”。
下面是lambda表达式的语法:
[ capture-list ] { body } [ capture-list ] ( params ) { body } [ capture-list ] ( params ) -> ret { body } [ capture-list ] ( params ) mutable exception attribute -> ret { body }</div>
这其中:
- - capture-list 是需要捕获的变量列表,用逗号分隔。其详细说明见下文。
- - params 是lambda表达式需要的参数列表,写法和函数参数一样,不过这里不支持默认参数。
- - ret 指明了lambda表达式的返回值。通过return语句,如果编译器能够推断出返回值的类型。或者表达式没有返回值,“-> ret”可以省略。
- - body 函数体。
- - mutable 当捕获列表是以复制(见下文)的形式捕获时,默认这些复制的值是const的,除非指定了mutable。
- - exception 提供了异常的说明。
- - attribute 对于attribute的描述可以参见这里:http://en.cppreference.com/w/cpp/language/attributes,这里不多说明。
下面,我们通过经典的Hello World示例来看一下lambda表达式:
auto lambda1 = [] {std::cout << "Hello, World!\n";}; lambda1();</div>
这个lambda表达式将打印出字符串“Hello, World!”。
同时,我们将这个表达式赋值给“lambda1”这个变量,然后像调用函数一样,调用这个lambda表达式。
使用lambda表达式,可以让我们省却定义函数的麻烦,以inline的方式写出代码,这样的代码通常更简洁。
并且,由于阅读代码时不用寻找函数定义,这样的代码也更易读。
下面,我们来看另外一个例子。这个例子的需求是:
分两次,打印出一个vector集合中,所有:
1. 模 5 = 0
2. 大于 20
的数字。
现假设已有这个集合的定义如下:
vector<int> numbers { 1, 2, 3, 4, 5, 10, 15, 20, 25, 35, 45, 50 };
我们最先想到的方法自然是定义两个函数,分别按照上面的要求打印出需要的数字,它们的定义如下:
void printNumber1(vector<int>& numbers) { for (const int& i : numbers) { if (i % 5 == 0) { cout<<i<<endl; } } } void printNumber1(vector<int>& numbers) { for (const int& i : numbers) { if (i % 5 == 0) { cout<<i<<endl; } } }</div>
然后,我们在需要的地方,调用它们:
printNumber1(numbers); printNumber2(numbers);</div>
这里逻辑上并没有问题,但是:
1. 这里我们必须先定义这个函数,才能使用。而这样的函数,可能实际上我们只会使用一次。
2. 当工程大到一定程度,我们可能不记得每个函数的实现(所以函数命名很重要,原谅我这里给函数起了很含糊的名字,你在实际上工程中,请不要这样做),为了知道每个函数的实现,我们不得不查看函数的定义,这无疑给代码的阅读造成了一定的麻烦。
下面,我们来看看使用lambda表达式如何改善上面说的问题。
使用lambda表达式,我们可以这样写:
for_each(numbers.begin(), numbers.end(), [] (int i) { if(i % 5 == 0) { cout<<i<<endl; } }); for_each(numbers.begin(), numbers.end(), [] (int i) { if(i > 20) { cout<<i<<endl; } });</div>
这里,我们不用单独定义函数,直接以inline的方式解决了问题。并且,这段代码一气呵成,你很直观的看到了执行的逻辑。
下面,我们再详细看一下lambda表达式中的捕获列表的语法,它可能是以下几种情况中的一种:
- [] 不捕获任何变量
- [&] 以引用的方式捕获所有变量
- [=] 以复制的方式捕获所有变量
- [=, &foo] 以引用的方式捕获foo变量,但是以复制的方式捕获其他变量
- [bar] 以复制的方式捕获bar变量,不再捕获任何其他变量
- [this] 捕获this指针
下面,我们再以一个例子说明捕获列表的用法。
这里,我们的需求是:
打印出一个vector<int>的所有数字之和
同样的,我们先以函数的方式来解决这个问题,这个函数的定义可以是这样的:
void printSum(vector<int>& numbers) { int sum = 0; for (const int& i : numbers) { sum += i; } cout<<sum<<endl; }</div>
然后,我们在需要的地方调用这个函数:
vector<int> numbers { 1, 2, 3, 4, 5, 10, 15, 20, 25, 35, 45, 50 }; printSum (numbers);</div>
而假设我们用lambda表达式来写,这样写就可以了:
vector<int> numbers { 1, 2, 3, 4, 5, 10, 15, 20, 25, 35, 45, 50 }; int sum = 0; std::for_each(numbers.begin(), numbers.end(), [&sum] (const int& i) { sum += i;}); cout<<sum<<endl;</div>
这里,我们用 [&sum]以引用的形式捕获了sum这个变量,并且在lambda表达式中修改了这个变量。
这样写,是不是比定义函数的方式简洁了很多?
对于这种,能够捕获其定义时上下文变量的函数,我们称之为“闭包”,下文还将提到。
std::function
上文中,对于分两次,打印出一个vector集合中,所有:
1. 模 5 = 0
2. 大于 20
的数字。
这个需求,我们的实现其实还不够好。
回头看一下printNumber1和printNumber2这两个函数,这两个函数大部分都是重复的:它们都需要遍历集合,都需要做if判断,然后打印出结果。
实际上,我们在项目中经常遇到这个的问题:
两(多)个函数,有大部分的代码都是一样的,其中只有一两行代码有不一样的地方。
其实,我们可以对这个不一样的地方,再做一个抽象,把它们共通起来。
具体到这个例子就是:无论是“模 5 = 0”还是“大于 20”都是满足“某种条件”。
而很自然的会想到,我们是否可以通过一个类似这样的函数来做这个判断:
bool func(int i)
然后实现两个函数,通过函数指针的形式来完成判断就好了。
但是,我们马上又意识到,这两个函数会很小,并且也是只会用一遍而已,定义一个函数又太“浪费”了。 很自然的,我们就会想lambda。但是,lambda似乎没法转成函数指针。。。
C++11中,提供了一个通用的描述方法,就是std::function。 std::function可以hold住任何可以通过“()”来调用的对象,包括:
- 普通函数
- 成员函数
- lambda
- std::bind(见下文)后的结果
std::function的语法是这样:
template <class Ret, class... Args> class function<Ret(Args...)>;
例如:function<bool (int)> filter
就表达了我们前面需要的那个函数:这个函数接受一个int值作为参数,同时返回一个bool作为判断的结果。但同时,我们可以用lambda表达式直接传递进去。
因此,上面的代码可以改写成这样:
void printNumber(vector<int>& number, function<bool (int)> filter) { for (const int& i : number) { if (filter(i)) { cout<<i<<endl; } } }</div>
然后在需要的地方,这样调用即可:
printNumber(numbers, [] (int i){ return i % 5 == 0;}); printNumber(numbers, [] (int i){ return i > 20;});</div>
这种做法,是不是又简洁了不少?