lambda 表达式:
Lambda表达式完整的声明格式如下:
[capture list] (params list) mutable exception-> return type { function body }
各项具体含义如下:
- capture list:捕获外部变量列表
- params list:形参列表
- mutable 指示符:用来说用是否可以修改捕获的变量
- exception:异常设定
- return type:返回类型
- function body:函数体
我们这里先不讨论 exception
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体:
1 #include <iostream> 2 using namespace std; 3 4 int main(void){ 5 auto f = [] { return 42; }; 6 auto g = [] {int a = 1; a++; return a;}; 7 cout << f() << endl;//42 8 cout << g() << endl;//2 9 return 0; 10 }
注意:在 lambda 中忽略括号和参数列表等价于指定一个空参数列表。在此列表中,当调用 f 时,参数列表是空的。如果忽略返回类型,lambda 根据函数体中的代码推断出返回类型。在 c++11 标准中,如果 lambda 的函数体包含任何单一 return 语句之外的内容,且未指定返回类型,则返回 void。不过在很多编译器中好像只要有 return 语句就会返回编译器推断的类型~
向 lambda 传递参数:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 using namespace std; 5 6 int main(void){ 7 vector<string> words = {"df", "fsl", "feg", "laf", "fsfl", "jfsoe"}; 8 stable_sort(words.begin(), words.end(), 9 [](const string &a, const string &b) 10 {return a.size() < b.size();}); 11 for(const auto &indx : words){ 12 cout << indx << " "; 13 } 14 cout << endl;//df fsl feg laf fsfl jfsoe 15 return 0; 16 }
其中,空捕获列表表名此 lambda 不使用它所在函数中的任何局部变量。当 stable_sort 需要比较两个元素时,它就会调用给定的这个 lambda 表达式。
注意:通常,实参和形参类型必须匹配。但与普通函数不同,lambda 不能有默认参数。因此,一个 lambda 调用的实参数目永远与形参数目相等。一旦形参初始化完毕,就可以执行函数体了。
使用捕获列表 / find_if / for_each:
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 using namespace std; 5 6 int gg = 2; 7 8 int main(void){ 9 vector<string> words = {"df", "fsl", "feg", "laf", "fsfl", "jfsoe"}; 10 int sz1 = 3, sz2 = 2; 11 auto wc = find_if(words.begin(), words.end(), 12 [sz1, sz2](const string &a){//捕获sz1,sz2变量 13 return a.size() >= sz1 || a.size() >= sz2;//使用捕获的变量sz1,sz2 14 });//find_if返回第一个满足谓词条件的迭代器 15 16 cout << *wc << endl;//df 17 auto count = words.end() - wc; 18 cout << count << endl;//6 19 20 const int sz = 3; 21 auto indx = find_if(words.begin(), words.end(), 22 [](const string &a){ 23 return a.size() >= sz;//常量可以不捕获就使用!!! 24 }); 25 26 int len = 3; 27 // auto indx = find_if(words.begin(), words.end(), 28 // [](const string &a){ 29 // return a.size() >= len;//错误,len变量没有被捕获不能使用 30 // }); 31 32 static int b = 3; 33 auto id = find_if(words.begin(), words.end(), 34 [](const string &a){ 35 return a.size() >= b || a.size() >= gg;//static变量和lambda所在函数之外的变量也可以直接使用 36 }); 37 38 for_each(wc, words.end(), 39 [](const string &s){//对迭代器区间的每个元素都调用一遍谓词 40 cout << s << " "; 41 });//df fsl feg laf fsfl jfsoe 42 cout << endl; 43 44 return 0; 45 }
注意:这里的 lambda 表达式是作为 find_if 和 for_each 的谓词的,lambda 的形参列表接受这两个函数的输入并对输入序列中的元素调用谓词,返回一个能用作条件的值
捕获列表捕获其所在函数中的局部变量,使它们能在 lambda 函数体中被使用
形参列表中的参数也能直接在 lambda 函数体中使用
发现函数体中的 const 变量能直接在 lambda 函数体中直接使用
static 变量和 lambda 函数之外定义的变量也可以直接使用
lambda 捕获和返回:
当定义一个 lambda 时,编译器生成一个与 lambda 对应的新的(未命名的)类类型。当向一个函数传递一个 lambda 时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名对象。类似的,当使用 auto 定义一个用 lambda 初始化的变量时,定义了一个从 lambda 生成的类型的对象。
默认情况下,从 lambda 生成的类都包含一个对应 lambda 所捕获的变量的数据成员。类似任何普通类的数据成员,lambda 的数据成员也在 lambda 对象创建时就被初始化。
值捕获:
类似于参数传递,变量的捕获方式也可以是值或引用。前面的代码我们用的都是值捕获。与传值参数类似,采用值捕获的前提是变量可拷贝。与参数不同的是被捕获的变量的值是在 lambda 创建时拷贝的,而不是调用时拷贝:
1 #include <iostream> 2 #include <fstream> 3 using namespace std; 4 5 int main(void){ 6 size_t v1 = 42; 7 auto f = [v1]{return v1;};//将v1拷贝到名为f的可调用对象 8 v1 = 0; 9 auto j = f(); 10 cout << j << endl;//42 j 保存了我们创建它时v1的拷贝 11 12 ofstream cc; 13 // auto g = [os]{};//错误,ostream类型的对象是不可拷贝的,不能用值捕获 14 auto g = []{cout << 1;};//cout定义在iostream头文件中,可以直接使用 15 16 return 0; 17 }
注意:由于被捕获的变量的值是在 lambda 创建时拷贝,因此随后对其修改不会影响到 lambda 内对应的值
不能被拷贝的对象不能用于 lambda 值捕获
引用捕获:
引用捕获和一般的引用传参行为类似
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 using namespace std; 5 6 void fun(void){ 7 size_t v1 = 42; 8 auto f = [&v1]{return v1;};//对v1引用捕获 9 v1 = 0; 10 auto j = f(); 11 cout << j << endl;//0 12 } 13 14 void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' '){ 15 for_each(words.begin(), words.end(), 16 [&os, c](const string &s){//IO对象只能引用捕获 17 os << s << c; 18 });//fjks fsl fjsl fiwo 19 } 20 21 int main(void){ 22 fun(); 23 vector<string> v = {"fjks", "fsl", "fjsl", "fiwo"}; 24 biggies(v, 4); 25 26 return 0; 27 }
注意:使用捕获一个引用(迭代器,指针)变量时,必须确保被引用的对象在 lambda 执行的时候是存在的
lambda 捕获的都是局部变量,这些变量在函数接受后就不复存在了
如果 lambda 可能在函数结束后执行,捕获的引用指向的局部变量已经消失
对于不能拷贝的对象,如 IO 对象,必须采用引用捕获
隐式捕获:
除了显示列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据 lambda 体中代码来判断我们要使用哪些变量。为了指示编译器判断捕获列表,应该在捕获列表中写一个 & 或 =。& 告诉编译器采用引用的捕获方式,= 则表示值捕获方式:
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 using namespace std; 5 6 void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' '){ 7 for_each(words.begin(), words.end(),//如果我们希望对一部分变量采用值捕获,对其它变量采用引用捕获,可以混合使用隐式捕获和显示捕获 8 [&, c](const string &s){//os隐式捕获,引用捕获方式;c显示捕获,值捕获方式 9 os << s << c; 10 });//df fsl feg laf fsf 11 12 cout << endl; 13 14 for_each(words.begin(), words.end(), 15 [=, &os](const string &s){//os显示捕获,引用捕获方式;c隐式捕获,值捕获方式 16 os << s << c; 17 });//df fsl feg laf fsf 18 19 cout << endl; 20 21 int cc = 1; 22 for_each(words.begin(), words.end(), 23 [&, cc, c](const string &s)mutable{//错误,两个都使用隐式捕获,编译器不能区分 24 os << cc++ << c << s << endl; 25 }); 26 27 // for_each(words.begin(), words.end(), 28 // [cc, c, &](const string &s)mutable{//只能捕获列表中第一个元素可以使用隐式捕获 29 // os << cc++ << c << s << endl; 30 // }); 31 32 // for_each(words.begin(), words.end(), 33 // [=, =](const string &s)mutable{//只能有一个捕获是隐式的 34 // cout << cc++ << c << s << endl; 35 // }); 36 } 37 38 int main(void){ 39 int sz = 3; 40 vector<string> words = {"df", "fsl", "feg", "laf", "fsfl", "jfsoe"}; 41 auto wc = find_if(words.begin(), words.end(), 42 [=](const string &s){//sz为隐式捕获,值捕获方式 43 return s.size() >= sz; 44 }); 45 cout << *wc << endl;//fsl 46 47 biggies(words, words.size()); 48 49 return 0; 50 }
注意:当我们混合使用隐式捕获和显示捕获时,捕获列表中的第一个元素必须是 & 或 =。此符号指定了默认捕获方式为引用或值
当混合使用隐式捕获和显示捕获时,显示捕获的变量必须使用与隐式捕获不同的方式。即,如果隐式捕获是引用方式,则显示捕获必须采用值方式。反之亦然~
捕获列表中只能有一个元素采用隐式捕获,且该变量必须是捕获列表中的第一个元素
可变 lambda:
1 #include <iostream> 2 using namespace std; 3 4 int main(void){ 5 int x = 1; 6 // auto gel = [x](){ 7 // x++;//这里的x不能改变 8 // return x; 9 // }; 10 11 // auto gel = [x] mutable{//错误,加了mutable关键字后不能省略参数列表 12 // x++; 13 // return x; 14 // }; 15 16 auto gel = [x] () mutable{ 17 return ++x; 18 }; 19 cout << gel() << endl;//2 20 21 const int y = 1; 22 // auto cc = [y] () mutable{ 23 // return ++y;//虽然加了mutable关键字,但y本身是const变量,所以不能修改 24 // }; 25 //即在显示值捕获中,拷贝得到的形参仍然是const的 26 27 auto cc = [&x] { 28 return ++x;//对于引用捕获,如果引用绑定的是一个非const对象,则不加mutable也是可变的 29 }; 30 31 // auto cb = [&y] { 32 // return ++y;//y是一个const对象,不可变 33 // }; 34 35 auto cd = [&x] () mutable{ 36 return ++x; 37 }; 38 cout << gel() << endl;//3 39 40 // auto ce = [&y] () mutable{ 41 // return ++y;//加了mytable,但y是const的,不可变 42 // }; 43 44 return 0; 45 }
注意:对于值捕获,若没加 mutalbe 关键字,则其捕获变量一定是不可变的,加了 mutable 关键字则取决于对应变量在函数中声明是否是 const 的。而引用捕获变量是否可变与 mutable 无关,只取决于绑定的对象是否是 const 的。
指定 lambda 返回类型:
1 #include <iostream> 2 #include <algorithm> 3 #include <array> 4 using namespace std; 5 6 int main(void){ 7 array<int, 10> a = {1, -1, 2, -3, 4, -459, -94}; 8 //transform接受3个迭代器和一个可调用对象.前两个迭代器表输入序列,第三个迭代器表目标位置。 9 //算法对输入序列中每个可调用元素调用可调用对象,并将结果写入目的位置 10 transform(a.begin(), a.end(), a.begin(), 11 [] (int i) { 12 return i < 0 ? -i : i; 13 }); 14 15 //如果我们将上面代码改写为看起来等价的if语句,按照c++11标准会编译错误,因为lambda函数体中如果含有多个return语句的话编译器推断是返回void的 16 //不过我用g++11通过了编译欸~ 17 transform(a.begin(), a.end(), a.begin(), 18 [] (int i) { 19 if(i < 0) return -i; 20 else return i; 21 }); 22 23 //符合c++11的标准的写法 24 transform(a.begin(), a.end(), a.begin(), 25 [] (int i) -> int { 26 if(i < 0) return -i; 27 return i; 28 }); 29 30 return 0; 31 }
注意:对于不能正确推断出返回类型的情况,必须使用尾置返回类型指明返回值的类型
在 lambda 中不能访问 protected 成员:
1 #include <cstdio> 2 3 class A{ 4 protected: 5 void Somefunc(){ 6 printf("Hello world!"); 7 } 8 }; 9 10 class B{ 11 public: 12 template<class F> 13 void D(F func){ 14 func(); 15 } 16 }; 17 18 class E : public A{ 19 public: 20 void Myfunc(){ 21 A::Somefunc(); // works 22 B C; 23 // C.D([&](){//错误,lambda的作用域是单独的,不能在其中调用受保护的类成员 24 // A::Somefunc(); // not works 25 // }); 26 27 C.D([&](){ 28 this->Somefunc(); //我们可以利用继承的特性直接使用protected成员嘛~ 29 }); 30 } 31 }; 32 33 int main(){ 34 E F; 35 F.Myfunc(); 36 37 return 0; 38 }
参考:https://segmentfault.com/q/1010000000479642
参数绑定:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 using namespace std; 5 6 int main(void){ 7 int sz = 5; 8 vector<string> v = {"jkfd", "jfkls", "fj", "fjslf", "fjsljfslf"}; 9 auto cnt = find_if(v.begin(), v.end(), 10 [sz] (const string &s) { 11 return s.size() > sz; 12 }); 13 14 cout << *cnt << endl;//fjsljfslf 15 16 return 0; 17 }
对于 sort 中的谓词通常我们可以将其写成一个单独的可调用函数,但是对于上面代码中 find_if 中的谓词,如果我们不将 sz 定义成全局变量显然不能将其写成一个单独函数的形式。因为 find_if 接受的是一个一元谓词,这意味着我们不能给其谓词函数不能有两个形参~
标准库 bind 函数:
我们可以通过使用一个名为 bind 的标准库函数来解决上面提到的问题,它定义在 functional 头文件中。可以将 bind 函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来 "适应" 原对象的参数列表。调用 bind 的一般形式为:
auto newCallable = bind(callable, arg_list);
其中,newCallable 本身是一个可调用对象,arg_list 是一个逗号分隔的参数列表,对于给定的 calloble 的参数。即,当我们调用 newCallable 时,newCallable 会调用 callable,并传递给它 arg_list 中的参数。
arg_list 中的参数可能包含形如 _n 的名字,其中 n 是一个整数。这些参数是 "占位符",表示 newCallable 的参数,它们占据了传递给 newCallable 的参数的 "位置"。数值 n 表示 生成的可调用对象中参数的位置:_1 为 newCallable 的第一个参数,_2 为第二个参数,依此类推。
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <functional> 5 using namespace std; 6 7 bool check_size(const string &s, int sz){ 8 return s.size() >= sz; 9 } 10 11 int main(void){ 12 int sz = 5; 13 vector<string> v = {"jkfd", "jfkls", "fj", "fjslf", "fjsljfslf"}; 14 auto check6 = bind(check_size, std::placeholders::_1, sz);//_n都定义在命名空间placeholders中,且此命名空间又定义在命名空间std中 15 //check6是一个可调用对象,接受一个string类型的参数 16 auto cnt = find_if(v.begin(), v.end(), check6); 17 18 cout << *cnt << endl;//fjslf 19 20 return 0; 21 }
注意:此处 bind 调用只有一个占位符,表示 check6 只接受单一参数。占位符出现在 args_list 的第一个位置,表示 check6 的此参数对应 check_size 的第一个参数。此参数是一个 const string&。因此,调用 check6 必须传递给他一个 string 类型的参数,check6 会将此参数传递给 check_size
_n 都定义在命名空间 placeholders 中,且此命名空间又定义在命名空间 std 中。我们也可以通过 using 来声明:
using std::placeholders::_1;
不过这种声明意味着我们对每个占位符名字都必须提供一个单独的 using 声明。我们可以使用另外一种更方便一些的声明方式:
using namespace namespace_name;
则所有来自 namespace_name 中定义的名字我们都可以直接在程序中直接使用:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <functional> 5 using namespace std; 6 7 using namespace std::placeholders; 8 9 bool check_size(const string &s, int sz){ 10 return s.size() >= sz; 11 } 12 13 int main(void){ 14 int sz = 5; 15 vector<string> v = {"jkfd", "jfkls", "fj", "fjslf", "fjsljfslf"}; 16 auto check6 = bind(check_size, _1, sz); 17 auto cnt = find_if(v.begin(), v.end(), check6); 18 19 cout << *cnt << endl;//fjslf 20 21 return 0; 22 }
注意:使用这种方式需要注意命名冲突问题
bind 参数顺序问题:
1 #include <iostream> 2 #include <algorithm> 3 #include <array> 4 #include <functional> 5 using namespace std; 6 using namespace std::placeholders; 7 8 bool cmp(const string &s1, const string &s2){ 9 return s1.size() < s2.size(); 10 } 11 12 int main(void){ 13 array<string, 5> a = {"fjls", "jf", "jflsjdf", "jfslk", "jfsljflsfjsjfsl"}; 14 15 stable_sort(a.begin(), a.end(), cmp); 16 for(const auto &indx : a){ 17 cout << indx << " "; 18 } 19 cout << endl;//jf fjls jfslk jflsjdf jfsljflsfjsjfsl 20 21 auto rcmp = bind(cmp, _2, _1);//_2对应cmp的第一个形参,_1对应cmp的第二个形参 22 stable_sort(a.begin(), a.end(), rcmp);//传入的第一个参数绑定的是_1,第二个参数绑定_2,即实际上调用的是bind(_1, _2) 23 for(const auto &indx : a){ 24 cout << indx << " "; 25 } 26 cout << endl;//jfsljflsfjsjfsl jflsjdf jfslk fjls jf 27 return 0; 28 }
注意:在 stable_sort(a.begin(), a.end(), rcmp);的调用中,传入 rcmp 中的第一个参数对应 _1,第二个参数对应 _2,即是按照占位符的数字大小顺序对应的,而不是按照占位符在 rcmp 参数列表的顺序对应的。但是 rcmp 中的参数对应 cmp 中的形参列表是按照参数位置顺序一一对应的。因此,在第一个调用中,当 stable_sort 需要比较两个元素 A 和 B 时,它会调用 cmp(A, B)。在第二个 stable_sort 调用时,传递给 cmp 的参数就被交换过来了。即比较 A,B 两个元素时相当于调用的 cmp(B, A)。因此,在第一次 stable_sort 调用的结果是对 a 中字符串按长度升序排列,而第二次调用 stable_sort 调用的结果是对 a 中的字符串按长度降序排列~
绑定引用参数:
默认情况下,bind 的那些不是占位符的参数被拷贝到 bind 返回的可调用对象中:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <functional> 5 using namespace std; 6 using namespace std::placeholders; 7 8 ostream &print(ostream &os, const string &s, char c){ 9 os << s << c; 10 } 11 12 int main(void){ 13 vector<string> v = {"fjs", "fjsl", "fjslf", "fjlsjf"}; 14 ostream &os = std::cout; 15 const char c = ' '; 16 17 for_each(v.begin(), v.end(), 18 [&os, c] (const string &s) { 19 os << s << c; 20 }); 21 cout << endl; 22 23 //用bind实现同样的功能 24 // for_each(v.begin(), v.end(), bind(print, os, _1, c));//错误,非占位符默认是值传递的,os是IO对象,不能拷贝 25 for_each(v.begin(), v.end(), bind(print, ref(os), _1, c));//函数ref返回一个对象,包含给定的引用,次对象是可以拷贝 26 27 return 0; 28 }
注意:对于不能拷贝的对象,需要通过 ref 函数才能用作 bind 的非占位符参数
函数 ref() 返回一个对象,包含给定的引用,此对象是可以拷贝的。标准库中还有一个 cref 函数,生成一个保存 const 引用的类。ref 和 cref 定义在头文件 functional 中。