c++11 lambda表达式
lambda 表达式(lambda expression)是一个匿名函数,lambda表达式基于数学中的 λ 演算得名。
C++11中的lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。
lambda表达式的基本构成:
①函数对象参数
[],标识一个lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义lambda为止时lambda所在作用范围内可见的局部变量(包括lambda所在类的this)。函数对象参数有以下形式:
n 空。没有使用任何函数对象参数。
n =。函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
n &。函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
n this。函数体内可以使用lambda所在类中的成员变量。
n a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
n &a。将a按引用进行传递。
n a, &b。将a按值进行传递,b按引用进行传递。
n =,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
n &, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
② 操作符重载函数参数
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
③ 可修改标示符
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
④ 错误抛出标示符
exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)
⑤ 函数返回值
->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
⑥ 是函数体
{},标识函数的实现,这部分不能省略,但函数体可以为空。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <memory> #include <functional> #include <vector> #include <map> class Test { public: int i; void func(int x, int y) { auto x1 = []{ return i; }; //err, 没有捕获外部变量 auto x2 = [=]{ return i+x+y; }; //ok, 值传递方式捕获所有外部变量 auto x3 = [=]{ return i+x+y; }; //ok, 引用传递方式捕获所有外部变量 auto x4 = [this]{ return i; }; //ok, 捕获this指针 auto x5 = [this]{ return i+x+y; }; //err, 没有捕获x, y auto x6 = [this, x, y]{ return i+x+y; };//ok, 捕获this指针, x, y auto x9 = [this]{ return i++; }; //ok, 捕获this指针, 并修改成员的值 } }; void mytest() { int a = 0, b = 1; auto f1 = []{ return a; }; //err, 没有捕获外部变量 auto f2 = [=]{ return a; }; //ok, 值传递方式捕获所有外部变量 auto f3 = [=]{ return a++; }; //err, a是以赋值方式捕获的,无法修改 auto f4 = [=]() mutable { return a++; }; //ok, 加上mutable修饰符后,可以修改按值传递进来的拷贝 auto f5 = [&]{ return a++; }; //ok, 引用传递方式捕获所有外部变量, 并对a执行自加运算 auto f6 = [a]{ return a+b; }; //err, 没有捕获变量b auto f9 = [a,&b]{ return a+(b++); }; //ok, 捕获a, &b auto f8 = [=,&b]{ return a+(b++); }; //ok, 捕获所有外部变量,&b // 值传递和引用传递区别 int j = 12; auto by_val_lambda = [=] { return j + 1;}; auto by_ref_lambda = [&] { return j + 1;}; std::cout << "by_val_lambda: " << by_val_lambda() << std::endl; std::cout << "by_ref_lambda: " << by_ref_lambda() << std::endl; std::cout << "by_val_lambda: " << by_val_lambda() << std::endl; std::cout << "by_ref_lambda: " << by_ref_lambda() << std::endl; j++; std::cout << "by_val_lambda: " << by_val_lambda() << std::endl; std::cout << "by_ref_lambda: " << by_ref_lambda() << std::endl; /* 运行结果: by_val_lambda: 13 by_ref_lambda: 13 by_val_lambda: 13 // 第3次调用结果还是13,原因是由于by_val_lambda中,j被视为了一个常量,一旦初始化后不会再改变。 by_ref_lambda: 14 */ return; } int main() { mytest(); system("pause"); return 0; }
lambda与仿函数
通过例子,我们看到,仿函数以round初始化类,而lambda函数也捕获了round变量,其它的,如果在参数传递上,两者保持一致。
除去在语法层面上的不同,lambda和仿函数有着相同的内涵——都可以捕获一些变量作为初始化状态,并接受参数进行运行。
而事实上,仿函数是编译器实现lambda的一种方式,通过编译器都是把lambda表达式转化为一个仿函数对象。因此,在C++11中,lambda可以视为仿函数的一种等价形式。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <memory> #include <functional> #include <vector> #include <map> class MyFunctor { public: MyFunctor(int tmp) : round(tmp) {} int operator()(int tmp) { return tmp + round; } private: int round; }; void mytest() { //仿函数 int round = 2; MyFunctor f1(round);//调用构造函数 std::cout << "result1 = " << f1(1) << std::endl; //operator()(int tmp) //lambda表达式 auto f2 = [=](int tmp) -> int { return tmp + round; }; std::cout << "result2 = " << f2(1) << std::endl; return; } int main() { mytest(); system("pause"); return 0; }
lambda类型
lambda表达式的类型在C++11中被称为“闭包类型”,每一个lambda表达式则会产生一个临时对象(右值)。因此,严格地将,lambda函数并非函数指针。
不过C++11标准却允许lambda表达式向函数指针的转换,但提前是lambda函数没有捕获任何变量,且函数指针所示的函数原型,必须跟lambda函数函数有着相同的调用方式。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <memory> #include <functional> #include <vector> #include <map> void mytest() { //使用std::function和std::bind来存储和操作lambda表达式 std::function<int(int)> f1 = [](int a) { return a; }; std::function<int()> f2 = std::bind([](int a){ return a; }, 123); std::cout << "f1 = " << f1(123) << std::endl; std::cout << "f2 = " << f2() << std::endl; auto f3 = [](int x, int y)->int{ return x + y; }; //lambda表达式,没有捕获任何外部变量 typedef int (*PF1)(int x, int y); //函数指针类型 typedef int (*PF2)(int x); PF1 p1; //函数指针变量 p1 = f3; //ok, lambda表达式向函数指针的转换 std::cout << "p1 = " << p1(3, 4) << std::endl; PF2 p2; //p2 = f3; //err, 编译失败,参数必须一致 decltype(f3) p3 = f3; // 需通过decltype获得lambda的类型 // decltype(f3) p4 = p1; // err 编译失败,函数指针无法转换为lambda return; } int main() { mytest(); system("pause"); return 0; }
lambda优势
lambda表达式的价值在于,就地封装短小的功能闭包,可以及其方便地表达出我们希望执行的具体操作,并让上下文结合更加紧密。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <memory> #include <functional> #include <vector> #include <map> #include <algorithm> // std::for_each std::vector<int> nums; std::vector<int> largeNums; class LNums { public: LNums(int u): ubound(u) {} void operator ()(int i) const { if (i > ubound) { largeNums.push_back(i); } } private: int ubound; }; void mytest() { //初始化数据 for(auto i = 0; i < 10; ++i) { nums.push_back(i); } int ubound = 5; //1、传统的for循环 for (auto itr = nums.begin(); itr != nums.end(); ++itr) { if (*itr > ubound) { largeNums.push_back(*itr); } } //2、使用仿函数 std::for_each(nums.begin(), nums.end(), LNums(ubound)); //3、使用lambda函数和算法for_each std::for_each(nums.begin(), nums.end(), [=](int i) { if (i > ubound) { largeNums.push_back(i); } } ); //4、遍历元素 for_each(largeNums.begin(), largeNums.end(), [=](int i) { std::cout << i << ", "; } ); std::cout << std::endl; return; } int main() { mytest(); system("pause"); return 0; }