1. 什么是lambda表达式?
int main() {
[](){};
return 0;
}
括号三巨头(中括号、小括号、大括号)就是一个lambda表达式。这个lambda表达式没有函数名、没有参数、也没有函数体,但这三个括号对应了lambda表达式三个非常重要的组成部分。
C++中的lambda表达式可以看作是一个匿名函数,也可以当作一个内联函数来使用。内联函数的意义在这里不展开讨论,而lambda表达式作为匿名函数在很多地方是大有用处的。
2. lambda表达式的结构
C++中的lambda表达式的结构如下所示:
[capture_list] (parameter_list) mutable exception -> return_type {function_body}
1.capture_list 捕获列表,列表中的局部变量可以被lambda表达式使用。
2.parameter_list 参数列表,相当于函数的参数列表。
3.mutable 用来说明捕获列表中的变量能否被修改。
4.exception 声明异常。
5.return_type 返回值类型。
6.function_body函数体。
下面写一个最简单的lambda表达式(也就是在一个lambda表达式中,其他都可以选用,但是[]和{}必须要有)
auto L=[]{cout<<"lambda"<<endl;};
调用时,只需要:
L();
或者直接这样:
[]{cout<<"lambda"<<endl;} ();//直接调用
捕获列表
捕获的两种类型
1.显式捕获:当需要使用到非静态局部变量的时候,需要显式地进行捕获,将该变量列入到捕获列表中。
2.隐式捕获:对于静态变量和外部变量,系统可以根据我们的使用情况推断出需要捕获的变量,因此不需要显式地将其列入捕获列表中。
捕获的两种模式
捕获也有两种方式:值捕获和引用捕获。下面两种形式分别表示值捕获和引用捕获。
1.[=(identifier)]
值捕获 ;
2.[&(identifier)]
引用捕获 ;
继续看下面:
1.[var]表示值传递方式捕捉变量var;
2.[=]表示值传递方式捕捉所有父作用域的变量(包括this,当然,“this”只能用于非静态成员函数内部);
3.[&var]表示引用传递捕捉变量var;
4.[&]表示引用传递方式捕捉所有父作用域的变量(包括this);
5.[this]表示值传递方式捕捉当前的this指针。
上面提到了一个父作用域,也就是包含Lambda函数的语句块。
上面的捕捉列表还可以进行组合,例如:
1.[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;
2.[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。
不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。例如:
3.[=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;
4.[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复,会报错的。
先看一个例子:
int main()
{
int id1 = 0;
int id2 = 0;
int id3 = 0;
auto f = [id1,id2,id3]()mutable
{
cout << id1++ << endl;
};
id1 = 42;
f();
f();
f();
cout << id1 << endl;
system("pause");
return 0;
}
分析:首先id1通过值传递的方式进入lambda表达式的函数体中,id1被视为一个常量,一旦初始化后不会再改变(可以认为之后只是一个跟父作用域中id1同名的常量),所以对其进行改变时必须加mutable关键字。所以最终的结果如上图所示。(如果不加mutable就在lambda表达式函数体中改变id1的值,显然会报错)
int main()
{
int id1 = 0;
int id2 = 0;
int id3 = 0;
auto f = [&id1,id2,id3]() //不加mutable
{
cout << id1++ << endl;//ok
//cout<<id2++<<endl;//报错
//cout<<id3++<<endl;//报错
};
id1 = 42;
f();
f();
f();
cout << id1 << endl;
system("pause");
return 0;
}
可以发现,通过引用传递时,不加mutable也可以对引用传递的参数进行修改。
lambda表达式一个很重要的作用就是配合STL库中的各种算法,使得其结构更加简洁紧凑,功能也更加的强大。
lambda表达式是一种可调用对象。C++中可调用对象还有函数、函数指针和重载了"()"运算符的类即functor等。
1.使用functor
#include<iostream>
#include<vector>
#include<bitset>
#include<algorithm>
using namespace std;
class my_functor
{
public:
my_functor(int aa, int bb) :a(aa), b(bb) {}
bool operator()(int n)const
{
return a < n&&n < b;
}
private:
int a;
int b;
};
int main()
{
vector<int>vi = { 5,28,50,83,70,590,245,59,24 };
for (int&elem : vi)
{
cout << elem << " ";
}
cout << endl;
int x = 30, y = 100;
vi.erase(remove_if(vi.begin(), vi.end(), my_functor(x, y)), vi.end());
for (int&elem : vi)
{
cout << elem << " ";
}
cout << endl;
system("pause");
return 0;
}
2.使用lambdas表达式代替原来的functor
顺便说一下remove_if这个算法,实际做法是, remove_if()将所有应该移除的元素都移动到了容器尾部并返回一个分界的迭代器。移除的所有元素仍然可以通过返回的迭代器访问到。为了实际移除元素,你必须对容器自行调用erase()以擦除需要移除的元素。
#include<iostream>
#include<vector>
#include<bitset>
#include<algorithm>
using namespace std;
int main()
{
vector<int>vi = { 5,28,50,83,70,590,245,59,24 };
int x = 30, y = 100;
for (int&elem : vi)
{
cout << elem << " ";
}
cout << endl;
//remove_if()将所有应该移除的元素都移动到了容器尾部并返回一个分界的迭代器
vector<int>::iterator ret_it = remove_if(vi.begin(), vi.end(), [x, y](int n) {return x < n&&n < y; });
for (int&elem : vi)
{
cout << elem << " ";
}
cout << endl;
vi.erase(ret_it, vi.end());
//或者直接合在一起写成下面的句子:
//vi.erase(remove_if(vi.begin(), vi.end(), [x, y](int n) {return x < n&&n < y; }), vi.end());
for (int&elem : vi)
{
cout << elem << " ";
}
cout << endl;
system("pause");
return 0;
}
再看一个例子:
int main() {
int val = 10;
vector<int> vec = { 1, 2, 3, 4, 5 };
for_each(vec.begin(), vec.end(), [=](int& elem)mutable{ elem += (--val); });
for (auto i : vec) cout << i << " ";
cout << val;
system("pause");
return 0;
}
最后一个比较重要的例子:
以前我们向set中传自定义的比较函数时,都是用functor的,现在可以用lambda表达式了。
1.先看functor形式:
class Customer
{
public:
string fname;
string lname;
long no;
Customer(const string _fname, const string _lname, long _no) :fname(_fname), lname(_lname), no(_no) {}
};
class mycompare
{
public:
bool operator()(const Customer&c1, const Customer&c2)const
{
return c1.no < c2.no;
}
};
int main() {
set<Customer, mycompare>myset;
myset.insert(Customer("shen","hang",1));
myset.insert(Customer("zhang", "san", 2));
/*template<class Key,
class Compare=less<Key>
class Alloc=alloc>*/
system("pause");
return 0;
}
2.再看lambda表达式形式:
class Customer
{
public:
string fname;
string lname;
long no;
Customer(const string _fname, const string _lname, long _no) :fname(_fname), lname(_lname), no(_no) {}
};
int main() {
auto mycompare = [](const Customer&c1, const Customer&c2)//lambda表达式
{
return c1.no < c2.no;
};
set<Customer, decltype(mycompare)>myset(mycompare);
myset.insert(Customer("shen","hang",1));
myset.insert(Customer("zhang", "san", 2));
/*template<class Key,
class Compare=less<Key>
class Alloc=alloc>*/
system("pause");
return 0;
}