一、lambda表达式概述
- lambda自C++11引入。这带来一种很具为例又十分便利的方法,允许我们提供局部机能,特别是用来具体指明算法和成员函数的细节lambda为C++带来十分重要而深具意义的改善(当我们使用STL时)。因为如今你有了一个直观、易读的方式,可以将独特的行为传递给算法和容器的成员函数
- 一个lambda表达式可表示一个可调用的代码单元,可以将其理解为一个未命名的内联函数
二、格式
- 与任何函数相同:一个lambda包含:返回类型、参数列表、函数体
- 与普通函数不同:lambda使用尾指返回来指定返回类型

- capture list(捕获列表):是一个lambda所在函数中定义的局部变量的列表(通常为空)
- return type:返回类型。如果忽略返回类型,lambda根据函数体的代码自动推断出返回类型
- parameter list:参数列表
- function body:函数体。如果是一条定义语句花括号后面要有分号;。如果是调用则分号可省去
例如
- lambda的参数列表/捕获列表为空,忽略了返回类型
auto f = [] {return 100; };
cout << f()<< endl; //100
三、向lambda传递参数
- lambda的实参与形参类型必须匹配,并且不能有默认参数
auto f = [](int a, int b)->int {return a + b; };
cout << f(1,2)<< endl;
//实现sort的从大到小排序算法
auto f = [](const int& a, const int& b) {return a > b; };
vector<int> vec{ 1,2,10,1545,151 };
sort(vec.begin(), vec.end(),
[](const int& a, const int& b) {return a > b; });
sort(vec.begin(), vec.end(),f);
四、lambda捕获
- lambda的函数体中只能使用捕获列表指定的参数,如果捕获列表没有则不能使用
int sz=10;
int a;
//lambda捕获了sz,但是没有捕获a
[sz](const string &a) {
//a=10; //错误,不能调用a
return a.size() >= sz;//正确
};

值捕获
- 值捕获的传参方式是拷贝传参
- 值的拷贝是在lambda表达式创建时的拷贝,而不是调用时的拷贝
- 因为是值拷贝,所以当捕获的值在外部改变时,该值在lambda内不会跟随变化
- 重点:使用值捕获时,该值在lambda内不能被改变(下面会介绍使用mutable关键字改变)
size_t v1=42;
auto f=[v1]() {return v1;};
//auto f=[v1]() {return ++v1;}; //错误,值捕获不能改变值
v1=0;
auto j=f(); //j=42
引用捕获
- 引用捕获的值是以引用的方式使用该值
- 所以当值在外部发生变化时,该值lambda中也会改变
- 引用捕获可以在lambda内改变该值的值
- 因为采用引用方式捕获一个变量,所以在调用lambda表达式时,必须保证这个值是存在的(例如局部变量在函数执行完之后消失。例如函数返回一个lambda表达式,而该表达式引用函数内的一个局部变量)
size_t v1=42;
auto f=[&v1]() {return v1;};
v1=0;
auto j=f(); //j=0
隐式捕获
- 可以在lambda的[]号中输入符号,让编译器自己捕获的类型。并且可以省去参数
- 如果在捕获列表写一个&:告诉编译器该lambda使用引用捕获。如果在捕获列表写一个=:告诉编译器该lambda使用值捕获
int sz = 2;
vector<string> vec{ "A","ABC","ABCD" };
//sz为隐式捕获:值捕获方式
auto wc = find_if(vec.begin(), vec.end(),
[=](const string &s) {return s.size() >= sz; });
混合隐式捕获方式
- 使用混合隐式捕获时,第一个元素必须是一个&或者=
- 显示捕获的变量必须使用与隐式捕获不同的方式:如果隐式捕获是引用方式(使用了&),则显式捕获命名变量必须采用值方式,因为不能在其名字前使用&。
- 类似的,如果隐式捕获采用的是值捕获(使用了=),则显式捕获命名变量必须采用引用方式(在名字前使用&)
void biggers(vector<string> &words, vector<string>::size_type sz,
ostream &os = cout, char c = ' ')
{
//os隐式捕获:引用捕获;c显式捕获:值捕获方式
for_each(words.begin(), words.end(),
[&, c](const string &s) {os << s << c; });
//os显式捕获:引用捕获;c隐式捕获:值捕获方式
for_each(words.begin(), words.end(),
[=, &os](const string &s) {os << s << c; });
}

五、可变lambda(mutable关键字)
- 默认情况下:lambda值捕获时,该值在lambda内不能被改变
- 使用mutable关键字之后:该值可以在lambda表达式内被改变。但是lambda内的值仍与外界值的改变无关
int val=1;
auto f = [val]() mutable {return ++val; };
val = 0; //将val变为0
auto j = f(); //j=2
cout << j << endl;
六、指定lambda的返回类型
- 一般情况下,lambda会自动推断出表达式的返回值类型
- 但是某些情况下需要显式的给出返回值类型,返回值类型使用尾指返回类型
//隐式的返回类型int
[](int i) {return i < 0 ? -i : i; }
//显式给出返回类型int
[](int i)->int
{
if (i < 0) return -i;
else return i;
};
七、lambda的类型
- lambda的类型,是个不具名的function object(或functor)
- 如果你想使用lambda的类型声明对象,可以借助于template std::function<>或auto
- 如果你想写下该类型,则可以使用decltype()(见下面“十一”的演示案例②)
演示案例
#include <iostream>
#include <functional>
std::function<int(int, int)> returnLambda()
{
return [](int x, int y) {return x*y; };
}
int main()
{
auto f = returnLambda();
std::cout << f(3, 7); //21
}
八、lambda与binder
- 例如我们在前面一篇文章中使用函数对象和函数适配器binder来书写代码,如下所示:
#include <iostream>
#include <functional>
using namespace std::placeholders;
int main()
{
auto plus10 = std::bind(std::plus<int>(), _1, 10);
//相等于调用std::plus<int>(7,10)
std::cout << "7+10=" << plus10(7) << std::endl;
auto plus10times2 = std::bind(std::multiplies<int>(),
std::bind(std::plus<int>(), _1, 10),
2);
//相等于调用std::multiplies<int>(std::plus<int>(10,10),2)
std::cout << "(10+10)*2=" << plus10times2(10) << std::endl;
//相等于调用std::multiplies<int>(std::multiplies<int>(7,7),7)
auto pow3 = std::bind(std::multiplies<int>(),
std::bind(std::multiplies<int>(), _1, _1),
_1);
std::cout << "7*7*7=" << pow3(7) << std::endl;
//相等于调用std::divides<double>(7,49)
auto inversDivide = std::bind(std::divides<double>(), _2, _1);
std::cout << "7/49=" << inversDivide(49, 7) << std::endl;
}

- 对于上面的binder,代码比较复杂,如果使用lambda则可以如下所示:
//头文件同上
int main()
{
auto plus10 = [](int i) {return i + 10; };
std::cout << "7+10=" << plus10(7) << std::endl;
auto plus10times2 = [](int i) {return (i + 10) * 2; };
std::cout << "(10+10)*2=" << plus10times2(10) << std::endl;
auto pow3 = [](int i) {return i*i*i; };
std::cout << "7*7*7=" << pow3(7) << std::endl;
auto inversDivide = [](double d1, double d2) {return d2 / d1; };
std::cout << "7/49=" << inversDivide(49, 7) << std::endl;
}

九、lambda与带有状态的函数对象
演示案例①
- lambda是不带状态的
- 例如下面我们修改C++(标准库):31文章中的演示案例:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> coll{ 1,2,3,4,5,6,7,8 };
long sum = 0;
for_each(coll.begin(), coll.end(), [&sum](int elem) {sum += elem; });
double mv = static_cast<double>(sum) / static_cast<double>(coll.size());
}
演示案例②
- 例如我们修改C++(标准库):31文章中“七”的演示案例。如下:
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
int main()
{
std::list<int> coll{ 1,2,3,4,5,6,7,8,9,10 };
for (const auto elem : coll)
std::cout << elem << " ";
std::cout << std::endl;
std::list<int>::iterator pos;
int count = 0;
pos=remove_if(coll.begin(),coll.end(),
[count](int)mutable {return ++count == 3; }
);
coll.erase(pos, coll.end());
for (const auto elem : coll)
std::cout << elem << " ";
}
- 上面运行的代码与那篇文章演示案例运行的结果一样,第3和第6个元素都会被移除:
- lambda使用了mutable,remove_if()算法在执行过程中复制了一份,于是存在两个lambda对象都移除第三元素,导致重复行为

- 如果你已by reference方式传递实参,那么结果就正确了,只移除第三个元素:
pos=remove_if(coll.begin(),coll.end(),
[&count](int) {return ++count == 3; }
);

十、lambda调用全局函数和成员函数
演示案例(调用全局函数)
- 下面的演示案例是《C++标准库》p490页演示案例的lambda版本。代码如下:
#include <iostream>
#include <string>
#include <locale>
#include <algorithm>
using namespace std;
char myTopper(char c)
{
std::locale loc;
return std::use_facet<std::ctype<char>>(loc).toupper(c);
}
int main()
{
string s("Internationlization");
string sub("Nation");
string::iterator pos;
pos=search(s.begin(),s.end(),sub.begin(),sub.end(),
[](char c1, char c2) {return myTopper(c1) == myTopper(c2); }
);
if (pos != s.end())
std::cout << "\"" << sub << "\" is part of \"" << s << "\"" << std::endl;
}
演示案例(调用成员函数)
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class Person
{
private:
std::string name;
public:
Person(const std::string& _name) :name(_name) {}
void print()const { std::cout << name << std::endl; }
void print2(const std::string& prefix)const { std::cout << prefix << name << std::endl; }
};
int main()
{
std::vector<Person> vec{ Person("Tick"),Person("Trick"),Person("Track") };
for_each(vec.begin(), vec.end(), [](const Person& p) {p.print(); });
std::cout << std::endl;
for_each(vec.begin(), vec.end(), [](const Person& p) {p.print2("Person:"); });
std::cout << std::endl;
return 0;
}
十一、lambda作为Hash函数、排序准则或相等准则
演示案例①
- 例如,下面我们使用deque<>存储自定义的Person对象,然后调用sort()算法对deque<>内的Person对象进行排序,其中排序的时候使用lambda设计排序准则
#include <iostream>
#include <deque>
#include <algorithm>
using namespace std;
class Person
{
public:
std::string firstname()const { return _firstName; }
std::string lastname()const { return _lastName; }
private:
std::string _firstName;
std::string _lastName;
};
int main()
{
std::deque<Person> coll;
sort(coll.begin(), coll.end(),
[](const Person& p1, const Person& p2) {return p1.firstname() < p2.firstname() || (p1.firstname() == p2.firstname() && p1.lastname() < p2.lastname()); }
);
}
演示案例②
- 例如,下面使用lambda指定unordered_set<>的hash函数和等价准则
#include <iostream>
#include <unordered_set>
using namespace std;
class Customer
{
public:
Customer(const std::string& fn, const std::string& ln, long n) :fname(fn), lname(ln), no(n) {}
std::string firstname()const { return fname; }
std::string lastname()const { return lname; }
long number()const { return no; }
private:
std::string fname;
std::string lname;
long no;
};
int main()
{
auto hash = [](const Customer& c) {
//这个hash_val()是自定义函数,可以参阅《C++标准库》的hashval.hpp
return hash_val(c.firstname(), c.lastname());
};
auto eq = [](const Customer& c1, const Customer& c2) {
return c1.number() == c2.number();
};
unordered_set<Customer, decltype(hash), decltype(eq)> custset(10, hash, eq);
custset.insert(Customer("nico", "josuttis", 42));
}
- 因为hash和eq都是lambda,因此需要使用decltype()产生lambda的类型,然后再传递给容器
- 此外你也必须传递一个hash函数和相等准则给构造函数,构造构造函数会调用hash函数和相等准则的默认构造函数,而那对lambda而言是未定义的
十二、lambda的局限
- 局限1:例如,我们想使用lambda作为关联式容器set的排序准则,但是不能直接将lambda表达式声明为set<>的模板参数2,而是要使用decltype()声明lambda的类型
class Person{};
//使用set的默认排序规则存储Person对象
std::set<Person> coll;
//使用自定义的lambda准则排序set中的Person对象
auto cmp = [](const Person& p1, const Person& p2) {return p1.firstname() < p2.firstname() || (p1.firstname() == p2.firstname() && p1.lastname() < p2.lastname()); };
std::set<Person, decltype(cmp)> coll;
- 局限2:另外,你也必须把lambda对象传给coll的构造函数,构造coll会调用被传入的排序准则的默认构造函数,而lambda没有默认构造函数,也没有赋值运算符
- 基于这些局限,“以class定义某个函数对象作为排序准则”说不定该比较直观些
- 局限3:它们无法拥有“跨越多次调用”都能被保存下来的内部状态,如果你需要这样的状态,必须在外围作用域中声明一个对象或变量,将其以by-reference方式传入lambda
- 与此相比,函数对象允许你封装内部状态(见前面几篇“函数对象”文章的介绍)
- 尽管如此,你还是可以使用lambda为无序容器指出一个hash函数或一个等效准则(见上面“十一”中的演示案例②)