前些天买了本《程序设计语言理论》,因为硕士阶段的研究方向就是这个,看了简介,Lambda 演算贯穿整个理论,尤其在函数式语言中具有重要作用。C++11 中也加入了 Lambda 表达式,下面做个总结。
1、一个简单的 Lambda 表达式如下:
[] {};
这就定义了一个对象,这个对象匿名,记住,Lambda 表达式是对象,不是类型,这很重要!本例中,该对象的类型是 ‘anonymous-namespace‘::<lambda0>,这是编译器给它设的一个类型名。
2、一般地,编译器实现 Lambda 表达式时,将其转化为函数对象(仿函数 functor)。比如上面的例子将转化为
struct A { public: void operator()() const {} }; A();
其中 A() 是一个函数对象,而 A 是类型,这些要明确区分。
3、仿函数和 Lambda 表达式是可以有状态的,也就是说它们可以含数据成员,而函数指针就无状态可言,比如下面这种情况就无法用函数指针,只能用仿函数或 Lambda 表达式。
/******************************************************************** created: 2014/05/07 22:31 filename: main.cpp author: Justme0 (http://blog.csdn.net/justme0) purpose: 涉及状态时,只能用仿函数或 Lambda 表达式,无法用函数指针 *********************************************************************/ #include <iostream> #include <vector> #include <algorithm> using namespace std; /* ** 输出 vec 中大于 standard 的元素 */ void print_use_lambda(const vector<int> &vec, int standard) { for_each(vec.begin(), vec.end(), [standard](int elem) { if (standard < elem) { cout << elem << ‘ ‘; } }); } struct Printor { private: int m_standard; public: Printor(int standard) : m_standard(standard) {} void operator()(int elem) const { if (m_standard < elem) { cout << elem << ‘ ‘; } } }; /* ** 输出 vec 中大于 standard 的元素 */ void print_use_functor(const vector<int> &vec, int standard) { for_each(vec.begin(), vec.end(), Printor(standard)); } int main(int argc, char **argv) { int arr[] = {5, 2, 1, 4, 5, 6, 7, 2}; int size = sizeof arr / sizeof *arr; vector<int> vec(arr, arr + size); cout << "需要输出大于几的数?" << endl; int standard; cin >> standard; print_use_functor(vec, standard); cout << endl; print_use_lambda(vec, standard); cout << endl; system("PAUSE"); return 0; }
程序中调用 for_each 时,只输出大于某个数的元素,而这个数事先不知道,这个数代表了仿函数的状态,而若用函数指针则无法获得这个数(状态),注意 for_each 的第三个参数是一元的。
4、按值方式传递捕捉列表与按引用方式传递列表不同,以下面的程序为例:
/******************************************************************** created: 2014/05/06 21:01 filename: main2.cpp author: Justme0 (http://blog.csdn.net/justme0) purpose: Lambda 的捕捉列表 *********************************************************************/ #include <iostream> using namespace std; int main(int argc, char **argv) { int i = 0; auto by_val_lambda = [i] { return i; }; auto by_ref_lambda = [&i] { return i; }; cout << by_val_lambda() << endl; // 0 cout << by_ref_lambda() << endl; // 0 ++i; cout << by_val_lambda() << endl; // 0 cout << by_ref_lambda() << endl; // 1 system("PAUSE"); return 0; }要解释这个问题,把它们转化为仿函数(编译器的实现)就一目了然了:
/******************************************************************** created: 2014/05/06 20:39 filename: main.cpp author: Justme0 (http://blog.csdn.net/justme0) purpose: 用 Functor 代替 Lambda *********************************************************************/ #include <iostream> using namespace std; struct Value { private: int m_i; public: Value(int i) : m_i(i) {} int operator()() const { return m_i; } }; struct Reference { private: int &m_i; public: Reference(int &i) : m_i(i) {} // 必须用初始化列表 int operator()() const { return m_i; } }; struct Pointer { private: int *m_pi; public: Pointer(int *pi) : m_pi(pi) {} int operator()() const { return *m_pi; } }; int main(int argc, char **argv) { int i = 0; auto by_val_functor = Value(i); auto by_ref_functor = Reference(i); auto by_ptr_functor = Pointer(&i); cout << by_val_functor() << endl; // 0 cout << by_ref_functor() << endl; // 0 cout << by_ptr_functor() << endl; // 0 ++i; cout << by_val_functor() << endl; // 0 cout << by_ref_functor() << endl; // 1 cout << by_ptr_functor() << endl; // 1 system("PAUSE"); return 0; }
值传递时 Lambda 保存了一个副本,与形参就无关了。引用传递时,Lambda 保存的是形参的引用,所以后面改变形参 ++i 时,Lambda 内保存的引用指向的 i 变了(同一个 i),注意指针与引用类似,用指针来解释引用再好不过了。
5、Lambda 捕捉列表仅能捕捉父作用域的自动变量。
标准是这么定义的,但有的编译器不遵守,笔者对具体的规则也存有疑惑。但总的来说,Lambda 的思想是局限于一个局部作用域中使用,若需全局共享,则用函数指针(无状态)或仿函数(有状态)。
参考资料:《深入理解 C++11》