函數(shù)對象是C++中以參數(shù)形式傳遞函數(shù)的一個很好的方法庐椒,我們將函數(shù)包裝成類抡四,并且利用()運算符重載實現(xiàn)宠漩。
typedef class function
{
public:
void operator()(double x)
{
cout << x << endl;
}
} FUNCTION;
function是一個類,我們可以實例化一個對象function func;懊直,然后通過func(3.14)的方式來調(diào)用這個類的成員函數(shù)扒吁,如果某個函數(shù)需要這個函數(shù)作為回調(diào)函數(shù),則可以將這個function類的對象傳入即可
因為這是一個類的定義室囊,因此我們完全可以在其中定義一些包含額外信息的成員和一些構(gòu)造函數(shù)雕崩,讓這個函數(shù)對象可以做更多不同的可定制的任務(wù),最終的行為實際上只是調(diào)用了這個()運算符重載函數(shù)融撞。這種做法比C++函數(shù)指針要容易理解得多盼铁,也不容易寫錯。
C++對于變量聲明期的控制在新標準中完全向前兼容尝偎,也就是局部變量一定在退出代碼塊時被銷毀饶火,而不是觀察其是否被引用鹏控。因此,盡管C++的Lambda表達式中允許引用其代碼上下文中的值肤寝,但是實際上并不能夠保證引用的對象一定沒有被銷毀
C++對于變量聲明期的控制在新標準中完全向前兼容当辐,也就是局部變量一定在退出代碼塊時被銷毀,而不是觀察其是否被引用鲤看。因此缘揪,盡管C++的Lambda表達式中允許引用其代碼上下文中的值,但是實際上并不能夠保證引用的對象一定沒有被銷毀
Lambda表達式的基本語法是:
[外部變量訪問方式說明符] mutable (參數(shù)表) -> 返回值類型
{
語句塊
}
//mutable參數(shù)根據(jù)需要選擇
[=] (int x, int y) -> bool {return x%10 < y%10; }
“外部變量訪問方式說明符”可以是=或&义桂,表示{}中用到的找筝、定義在{}外面的變量在{}中是否允許被改變。=表示不允許慷吊,&表示允許袖裕。當然,在{}中也可以不使用定義在外面的變量罢浇÷礁常“-> 返回值類型”可以省略。
=表示值傳遞嚷闭,&表示引用傳遞攒岛,例如,&s就表示s變量采用引用傳遞胞锰,不同的說明項之間用逗號分隔灾锯,可以為空,但是方括號不能夠省略嗅榕。第一項可以是單獨的一個=或者&顺饮,表示,所有上下文變量若無特殊說明一律采用值傳遞/引用傳遞凌那,什么都不寫默認為值傳遞兼雄。
Lambda表達式和TR1標準對應(yīng)的 function<返回類型 (參數(shù)表)>對象 是可以互相類型轉(zhuǎn)換的,這樣帽蝶,我們也可以將Lambda表達式作為參數(shù)進行傳遞赦肋,也可以作為返回值返回。
#include <iostream>
#include <string>
#include <functional> //這是TR1的頭文件励稳,定義了function類模板
using namespace std;
typedef class hello {
public:
void operator()(double x) {
cout << x << endl;
}
} hello; //函數(shù)對象的定義佃乘,也是非常常用的回調(diào)函數(shù)實現(xiàn)方法
void callhello(string s, hello func) {
cout << s;
func(3.14);
} //一個普通的函數(shù)
void callhello(string s, const function<void (double x)>& func) {
cout << s;
func(3.14);
} //這個函數(shù)會接受一個字符串和一個Lambda表達式作為參數(shù)
void callhello(string s, double d) {
[=] (double x) {
cout << s << x << endl;
}(d);
} //這個函數(shù)體內(nèi)定義了一個Lambda表達式并立即調(diào)用
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表達式是無法感知的驹尼,調(diào)用這句語句前s在Lambda表達式中的值已經(jīng)確定了
return f;
} //這個函數(shù)接受了一個值傳遞的字符串變量s趣避,我們將Lambda表達式作為返回值返回
function<void (double x)> returnLambda2(string& s) {
cout << s << endl;
function<void (double x)> f = ([&s/*這里可以使用引用傳遞,因為s是引用方式傳入的新翎,不隨函數(shù)返回而消亡*/] (double x) {
cout << s << x << endl;
});
s = "changed"; //這里對s的修改Lambda表達式是可以感知的程帕,因為s以引用方式參與到Lambda表達式上下文中
return f;
} //這個函數(shù)接受了一個引用傳遞的字符串變量s住练,將Lambda表達式作為返回值返回
int main()
{
hello h;
callhello("hello:", h); //用函數(shù)對象的方式實現(xiàn)功能
callhello("hello lambda:", -3.14); //這個函數(shù)體內(nèi)定義了一個Lambda表達式并立即調(diào)用
int temp = 6;
callhello("hello lambda2:", [&] (double x) -> void {
cout << x << endl;
cout << temp++ << endl;
}); //這個函數(shù)會接受一個字符串和一個Lambda表達式作為參數(shù)
cout << temp << endl;
function<void (double x)> f = returnLambda("lambda string"); //這個函數(shù)接受了一個值傳遞的字符串變量s,我們將Lambda表達式作為返回值返回
f(3.3);
string lambdastring2 = "lambda string2"; //這個變量在main函數(shù)返回時才被銷毀
f = returnLambda2(lambdastring2); //這個函數(shù)接受了一個引用傳遞的字符串變量s骆捧,將Lambda表達式作為返回值返回
f(6.6);
system("pause");
}
值捕獲的坑:
關(guān)于值捕獲要注意的地方是澎羞,與參數(shù)傳值類似,值捕獲的前提是變量可以拷貝敛苇,不同之處則在于妆绞,被捕獲的變量在 lambda 表達式被創(chuàng)建時拷貝,而非調(diào)用時才拷貝枫攀,例如:
void main()
{
int x = 10;
auto val_lam = [=]() {return x + 1; };
x = 20;
printf("a is %d\n", val_lam()); //lambda函數(shù)的值為11括饶,而不是21
}
我們看到lambda函數(shù)的值為11,而不是21来涨。因為值捕獲在創(chuàng)建時就傳入了图焰。
值捕獲還有一個需要注意的地方,不容易理解蹦掐,也是其特性技羔,就是外部值的連續(xù)性:
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
}
看到?jīng)],多次調(diào)用lambda函數(shù)卧抗,發(fā)現(xiàn)其記住了外部變量的值藤滥。幾年前學(xué)習到這個特性的時候,也是很不能理解社裆,后來用多了lua的閉包函數(shù)和upvalue拙绊,我現(xiàn)在可以會心的一笑了。事實上利用這個特性可以實現(xiàn)高階函數(shù)