简单的看了lambda函数,感觉跟三目运算符有些类似,都是只需要进行一些简单的计算,不需要具体的函数名,是一个匿名函数,即没有函数名的函数。
lambda的函数模型为:[&count] (int x) {count += (x % 13 ==0)};
比较函数指针、函数符和Lambda函数:
1. 函数指针
先看一个示例,假如要生成一个随机整数列表,并判断其中多少个整数被3整除,多少个整数可被13整除。
生成这样的列表很简单,一种方案是使用vector存储数字,并使用STL算法generate()在其中填充随机数。
#include <vector>
#include <cmath>
#include <algorithm>
std::vector<int> numbers(1000);
std::generate(numbers.begin(), numbers.end(), std::rand);
函数generate()接收一个区间(有两个参数指定),并将每个元素设置为第三个参数返回的值,而第三个参数是一个不接受任何参数的函数对象。在上述示例中,该函数对象是一个指向标准函数rand()的指针。
通过使用count_if()算法,很容易计算出有多少个数可被3整除,函数前两个参数应指定区间,而第三个参数是一个返回true或false的函数对象。函数count_if()计算这样的元素数,即它使得指定的函数对象返回true。为判断元素是否能被3整除,可使用下面的函数定义
bool f3(int x) {return x %3 == 0;}
同样,为判断元素能否被13整除,可以使用函数定义:
bool f13(int x) {return x % 13 == 0;}
定义完上述函数之后,就可以计算符合条件的元素数了,如下:
int count3 = count_if(numbers.begin(), numbers.end(), f3);
cout<<"count of numbers divisble by 3:"<<count3<<endl;
int count13 = count_if(numbers.begin(), numbers.end(), f13);
count<<"count of numbers divisble by 13:"<<count13<<endl;
2. 函数符
下面看一下怎么使用函数符来完成这个任务。函数符是一个类对象,并非只能想函数名那样使用。这要归功于类方法operator()()。就这个示例而言,函数符的优点之一是可使用同一个函数符来完成这两项计数任务。下面是一种定义:
class f_mod
{
private:
int dv;
public:
f_mod(int x = 1):dv(x);
bool operator()(int x){return x % dv == 0;}
}
可以使用构造函数创建存储特定整数值的f_mod对象
f_mod obj(3); //f_mod.dv set to 3`
而这个对象可以使用方法ooerator()来返回一个bool值:
bool is_div_by3 = obj(7); //same as obj.operator()(7)
构造函数本身可用作注入count_if()等函数的参数:
count3 = std::count_if(numbers.begin(), numbers.end(), f_mod(3));
参数f_mod(3)创建一个对象,它存储了3,而count_if()使用该对象来调用operator()(),并将参数x设置为numbers的一个元素。要计算有多少个数字可被13整除,只需将第三个参数设置为f_mod(13)
3. lambda函数
lambda名称来自于lambda calculus(λ演算)而得名——一种定义和应用函数的数学系统。这个系统能让人使用匿名函数——即无需给函数命名。在C++11中,对于接受函数指针或函数符的函数可以使用匿名函数定义(lambda)作为其参数。与上面函数f3对应的lambda如下:
[](int x){return x % 3 == 0;}
这与函数f3的定义很像:bool f3(int x) { reruen x % 3 == 0;}
其中有两处不同:第一个是使用[]代替函数名(匿名的由来),第二个是没有声明返回类型。返回类型相当于使用decltyp根据返回值的类型推断得到的,这里为bool。如果lambda不包含返回语句,推断出的返回类型将为 void。这个视力还可以使用一下的方法使用该lambda:
count3 = count_if(numbers.begin(), numbers.end(), [](int x){return x % 3 == 0;});
也就是说,使用整个lambda表达式提花函数指针或函数符构造函数。
仅当lambda表达式完全由一条返回语句组成是,自动类型推断才管用。否则需要使用新增的返回类型后置语法:
[](int x)->double{double y = x; return y % 3 == 0;}; //return type is double
为何使用lambda
使用lambda的原因由四条:距离、简洁、效率和功能。
很多程序员细化配置两个显示器(甚至三个),这样有个好处就是使用其他位置的代码不需要切换页面去看(分散精力,个人很讨厌),让函数定义与使用的地方附近这会更舒服,无需翻页浏览不同页面的源代码。
从简洁的角度看,函数符代码比函数和lambda更繁琐。函数和lambda的简介程度相当,一个是显而易见的例外是,需要使用同一个lambda两次:
count1 = std::count_if(n1.begin(), n1.end(), [](int x){return x % 3 == 0;});
count2 = std::count_if(n2.begin(), n2.end(), [](int x){return x % 3 == 0;});
其实也不需要编写lambda两次,可以给lambda指定一个名称,并使用该名称两次:
auto mod3 = [](int x){return x % 3 == 0;} //mod3 a name for the lambda
count1 = std::count_if(n1.begin(), n1.end(), mod3);
count2 = std::count_if(n2.begin(), n2.end(), mod3);
甚至还可以像使用常规函数那样使用lambda:
bool result = mod3(z); // result is true if z % 3 == 0
然而不同于常规函数,可在函数内部定义有名称的lambda。mod3的实际类型随实现而异,它取决于编译器使用什么类型来跟踪lambda。
这三种方法相对效率取决于编译器内联的东西。函数指针方法阻止了内联,因为编译器传统上不会内联其地址被获取的函数,因为函数地址的概念意味着非内联函数。而函数符和lambda通常不会阻止内联。
lambda捕获列表:
[] | 不捕捉任何变量 |
---|---|
[&] | 捕捉外部作用域所有变量,并作为引用在函数体内使用,函数体内引用的变量可修改(仅限被引用的变量) |
[=] | 捕捉外部作用域所有变量 ,在函数体内作为副本被使用,拷贝的副本在匿名函数体内只是可读的 |
[=,&foo] | 按值捕获外部作用域中所有变量,并按照引用捕获外部变量foo |
bar | 按值捕获bar变量,同时不捕获其他变量 |
&bar | 按引用捕获bar变量,同时不捕获其他变量 |
this | 捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限,如果已经使用了&或者=,默认添加此选项 |
例如:
class Test
{
public:
int m_number;
public:
Test(int num = 100){m_number = num;}
void example(int x, int y)
{
//auto x1 = [] {return m_number;}; //error, m_number不是可修改的左值
auto x2 = [=] {return m_number + x + y;}; //ok
//auto x3 = [=]{return x++;}; //error
auto x4 = [&]()->int{m_number++; return m_number + x + y;} //ok
auto x5 = [this] {return m_number;}; //ok
auto x6 = [this] {return m_number++;}; ok
auto x7 = [this] {return m_number + x + y;}; //error
auto x8 = [this, x, y] {return m_number + x + y;} //ok
}
};
lambda配合for_each()使用
之前我们输出数组所有的元素,可能是这种编写:
先使用generate()函数在容器中存入随机元素:
vector<int> vec(10);
std::generate(vec.begin(), vec.end(), std::rand);
打印容器中的所有元素:
/*1. c风格*/
for (int idx = 0; idx < test.size(); idx++)
cout << vec[idx] << " ";
或是这种编写风格:
/*2. 使用迭代器*/
vector<int>::iterator it;
for (it = test.begin(); it != test.end(); it++)
cout << *it << " ";
使用lambda和for_each函数会更加简单,一行代码就可以搞定:
for_each(vec.begin(), vec.end(), []{int x}{cout<<x<<" ";});
mutable
使用 lambda 表达式捕获列表捕获外部变量,如果希望去修改按值捕获的外部变量,这就需要使用 mutable 选项,被mutable修改是lambda表达式就算没有参数也要写明参数列表,并且可以去掉按值捕获的外部变量的只读(const)属性。
int a = 0;
auto f1 = [=] {return a++; }; // error, 按值捕获外部变量, a是只读的
auto f2 = [=]()mutable {return a++; }; // ok
值拷贝的方式捕获的外部变量是只读的:
lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。 按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。mutable 选项的作用就在于取消 operator () 的 const 属性。
因为 lambda 表达式在 C++ 中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操作lambda表达式:
#include <iostream>
#include <functional>
using namespace std;
int main(void)
{
// 包装可调用函数
std::function<int(int)> f1 = [](int a) {return a; };
// 绑定可调用函数
std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);
// 函数调用
cout << f1(100) << endl;
cout << f2(200) << endl;
return 0;
}
对于没有捕获任何变量的 lambda 表达式,还可以转换成一个普通的函数指针:
using func_ptr = int(*)(int);
// 没有捕获任何外部变量的匿名函数
func_ptr f = [](int a)
{
return a;
};
// 函数调用
f(1314);
参考:勿在浮沙築高臺
C++ Primer Plus第六版