C++中的函数对象与Lambda表达式
函数对象是C++中以参数形式传递函数的一个很好的方法,我们将函数包装成类,并且利用()运算符重载实现。
typedef class hello { public: void operator()(double x) { cout << x << endl; } } hello;
这时候hello是一个类,我们可以实例化一个对象hello h;,然后通过h(3.14)的方式来调用这个类的成员函数,如果某个函数需要这个函数作为回调函数,则可以将这个hello类的对象传入即可。
因为这是一个类的定义,因此我们完全可以在其中定义一些包含额外信息的成员和一些构造函数,让这个函数对象可以做更多不同的可定制的任务,最终的行为实际上只是调用了这个()运算符重载函数。这种做法比C++函数指针要容易理解得多,也不容易写错。
而Lambda表达式则是C++中的新语法,实现了许多程序员渴望的部分闭包特性。C++中Lambda表达式可以被视为一种匿名函数,这样,对于一些非常短,而且不太可能被其他地方的复用的小函数,可以通过Lambda表达式提高代码的可读性。
在Lambda表达式中对于变量生命期的控制还是与完全支持闭包的JavaScript非常不同,总而言之,C++对于变量声明期的控制在新标准中完全向前兼容,也就是局部变量一定在退出代码块时被销毁,而不是观察其是否被引用。因此,尽管C++的Lambda表达式中允许引用其代码上下文中的值,但是实际上并不能够保证引用的对象一定没有被销毁。
Lambda表达式对于上下文变量的引用有值传递和引用传递两种方式,实际上,无论是哪种方式,在产生Lambda表达式对象时,这些上下文值就已经从属于Lambda表达式对象了,也就是说,代码运行至定义Lambda表达式处时,通过值传递方式访问的上下文变量值已经被写入Lambda表达式的栈中,而引用方式传递的上下文变量地址被写入Lambda表达式的栈中。因此,调用Lambda表达式时得到的上下文变量值就是定义Lambda表达式时这些变量的值,而引用的上下文变量,如果已经被销毁,则会出现运行时异常。
Lambda表达式的基本语法是:
[上下文变量说明](Lambda表达式参数表) -> 返回类型 { 语句块 }
上下文变量说明部分就是说明对于上下文变量的引用方式,=表示值传递,&表示引用传递,例如,&s就表示s变量采用引用传递,不同的说明项之间用逗号分隔,可以为空,但是方括号不能够省略。第一项可以是单独的一个=或者&,表示,所有上下文变量若无特殊说明一律采用值传递/引用传递,什么都不写默认为值传递。
Lambda表达式和TR1标准对应的function<返回类型 (参数表)>对象是可以互相类型转换的,这样,我们也可以将Lambda表达式作为参数进行传递,也可以作为返回值返回。
下面看一个Lambda表达式各种使用方法的完整例子:
// compile with: /EHsc #include <iostream> #include <string> #include <functional> //这是TR1的头文件,定义了function类模板 using namespace std; typedef class hello { public: void operator()(double x) { cout << x << endl; } } hello; //函数对象的定义,也是非常常用的回调函数实现方法 void callhello(string s, hello func) { cout << s; func(3.14); } //一个普通的函数 void callhello(string s, const function<void (double x)>& func) { cout << s; func(3.14); } //这个函数会接受一个字符串和一个Lambda表达式作为参数 void callhello(string s, double d) { [=] (double x) { cout << s << x << endl; }(d); } //这个函数体内定义了一个Lambda表达式并立即调用 function<void (double x)> returnLambda(string s) { cout << s << endl; function<void (double x)> f = ([=/*这里必须使用值传递,因为s变量在returnLambda返回后就被销毁*/] (double x) { cout << s << x << endl; }); s = "changed"; //这里对s的修改Lambda表达式是无法感知的,调用这句语句前s在Lambda表达式中的值已经确定了 return f; } //这个函数接受了一个值传递的字符串变量s,我们将Lambda表达式作为返回值返回 function<void (double x)> returnLambda2(string& s) { cout << s << endl; function<void (double x)> f = ([&s/*这里可以使用引用传递,因为s是引用方式传入的,不随函数返回而消亡*/] (double x) { cout << s << x << endl; }); s = "changed"; //这里对s的修改Lambda表达式是可以感知的,因为s以引用方式参与到Lambda表达式上下文中 return f; } //这个函数接受了一个引用传递的字符串变量s,将Lambda表达式作为返回值返回 int main() { hello h; callhello("hello:", h); //用函数对象的方式实现功能 callhello("hello lambda:", -3.14); //这个函数体内定义了一个Lambda表达式并立即调用 int temp = 6; callhello("hello lambda2:", [&] (double x) -> void { cout << x << endl; cout << temp++ << endl; }); //这个函数会接受一个字符串和一个Lambda表达式作为参数 cout << temp << endl; function<void (double x)> f = returnLambda("lambda string"); //这个函数接受了一个值传递的字符串变量s,我们将Lambda表达式作为返回值返回 f(3.3); string lambdastring2 = "lambda string2"; //这个变量在main函数返回时才被销毁 f = returnLambda2(lambdastring2); //这个函数接受了一个引用传递的字符串变量s,将Lambda表达式作为返回值返回 f(6.6); system("pause"); }