函数对象是C++中以参数形式传递函数的一个很好的方法,我们将函数包装成类,并且利用()运算符重载实现。
typedef class function
{
public:
void operator()(double x)
{
cout << x << endl;
}
} FUNCTION;
function是一个类,我们可以实例化一个对象function func;,然后通过func(3.14)的方式来调用这个类的成员函数,如果某个函数需要这个函数作为回调函数,则可以将这个function类的对象传入即可
因为这是一个类的定义,因此我们完全可以在其中定义一些包含额外信息的成员和一些构造函数,让这个函数对象可以做更多不同的可定制的任务,最终的行为实际上只是调用了这个()运算符重载函数。这种做法比C++函数指针要容易理解得多,也不容易写错。
C++对于变量声明期的控制在新标准中完全向前兼容,也就是局部变量一定在退出代码块时被销毁,而不是观察其是否被引用。因此,尽管C++的Lambda表达式中允许引用其代码上下文中的值,但是实际上并不能够保证引用的对象一定没有被销毁
C++对于变量声明期的控制在新标准中完全向前兼容,也就是局部变量一定在退出代码块时被销毁,而不是观察其是否被引用。因此,尽管C++的Lambda表达式中允许引用其代码上下文中的值,但是实际上并不能够保证引用的对象一定没有被销毁
Lambda表达式的基本语法是:
[外部变量访问方式说明符] mutable (参数表) -> 返回值类型
{
语句块
}
//mutable参数根据需要选择
[=] (int x, int y) -> bool {return x%10 < y%10; }
image.png
“外部变量访问方式说明符”可以是=或&,表示{}中用到的、定义在{}外面的变量在{}中是否允许被改变。=表示不允许,&表示允许。当然,在{}中也可以不使用定义在外面的变量。“-> 返回值类型”可以省略。
=表示值传递,&表示引用传递,例如,&s就表示s变量采用引用传递,不同的说明项之间用逗号分隔,可以为空,但是方括号不能够省略。第一项可以是单独的一个=或者&,表示,所有上下文变量若无特殊说明一律采用值传递/引用传递,什么都不写默认为值传递。
Lambda表达式和TR1标准对应的 function<返回类型 (参数表)>对象 是可以互相类型转换的,这样,我们也可以将Lambda表达式作为参数进行传递,也可以作为返回值返回。
#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");
}
值捕获的坑:
关于值捕获要注意的地方是,与参数传值类似,值捕获的前提是变量可以拷贝,不同之处则在于,被捕获的变量在 lambda 表达式被创建时拷贝,而非调用时才拷贝,例如:
void main()
{
int x = 10;
auto val_lam = [=]() {return x + 1; };
x = 20;
printf("a is %d\n", val_lam()); //lambda函数的值为11,而不是21
}
我们看到lambda函数的值为11,而不是21。因为值捕获在创建时就传入了。
值捕获还有一个需要注意的地方,不容易理解,也是其特性,就是外部值的连续性:
void main()
{
int x = 10;
auto val_lam = [=]() mutable {x++; return x + 1; };
printf("a is %d\n", val_lam()); //12
printf("a is %d\n", val_lam()); //13
printf("a is %d\n", val_lam()); //14
printf("x is %d\n", x); //此时x还是10
}
看到没,多次调用lambda函数,发现其记住了外部变量的值。几年前学习到这个特性的时候,也是很不能理解,后来用多了lua的闭包函数和upvalue,我现在可以会心的一笑了。事实上利用这个特性可以实现高阶函数
网友评论