Lambda表達(dá)式,也稱為匿名函數(shù)样眠、閉包函數(shù)友瘤,在別的編程語言很早就有了。
C++ 11開始檐束,也支持了這個(gè)功能辫秧。而后續(xù)的C++ 版本又陸陸續(xù)續(xù)做了些改進(jìn)。
整理了編筆記被丧,把lambda表達(dá)式的用法試驗(yàn)盟戏、記錄一下。
lambda表達(dá)式的語法如下:
[ captures ] ( params ) specs -> return-type { body }
captures:捕獲列表甥桂,用來捕獲當(dāng)前作用域的變量柿究,然后可以在lambda表達(dá)式內(nèi)部使用。支持按值捕獲黄选、按引用捕獲蝇摸。用法和行為,跟普通的C++函數(shù)調(diào)用很像办陷。
params:參數(shù)列表探入,可選。在調(diào)用lambda表達(dá)式時(shí)懂诗,額外傳遞的參數(shù)。
specs:限定符苗膝,可選殃恒。比如說mutable,后面的試驗(yàn)會(huì)用到它辱揭。在后續(xù)的C++ 17离唐、20、23等版本问窃,還添加了constexpr亥鬓、consteval、static這些域庇。
return-type:返回類型嵌戈,可選覆积。
body:函數(shù)體
整個(gè)看起來,跟普通的C++函數(shù)很像:
lambda的捕獲列表 + 參數(shù)列表 <--> 函數(shù)的參數(shù)列表
lambda的限定符 <--> 函數(shù)的限定符
lambda的返回類型 <--> 函數(shù)的返回類型
lambda的函數(shù)體 <--> 函數(shù)的函數(shù)體
就是函數(shù)的函數(shù)名熟呛,在lambda里面不需要定義宽档。所以也把lambda表達(dá)式稱為匿名函數(shù)。
這是一個(gè)簡單的lambda表達(dá)式例子:
#include <iostream>
int main()
{
int x = 1;
// Define a simple lambda
auto add_func = [x] (int y) {
return x + y;
};
// Call lambda
int ans = add_func(10);
std::cout << ans << std::endl;
return 0;
}
運(yùn)行結(jié)果很淺白庵朝,會(huì)輸出:11
add_func表達(dá)式在定義時(shí)吗冤,把作用域的x變量捕獲,然后其函數(shù)體內(nèi)使用x九府、和額外傳遞的參數(shù)y進(jìn)行運(yùn)算椎瘟,最后返回計(jì)算結(jié)果。
那么我們把代碼稍作修改:
#include <iostream>
int main()
{
int x = 1;
// Define a simple lambda
auto add_func = [x] (int y) {
return x + y;
};
// Call lambda
int ans = add_func(10);
std::cout << ans << std::endl;
// Modify x outside
x = 2;
// Call lambda again
ans = add_func(10);
std::cout << ans << std::endl;
return 0;
}
在第一次調(diào)用add_func之后侄旬,我們把外部的x變量修改了肺蔚,然后,再次調(diào)用add_func勾怒。
運(yùn)行這個(gè)代碼婆排,會(huì)發(fā)現(xiàn)輸出:
11
11
外部x變量的修改,并沒有傳遞到lambda內(nèi)部笔链。
這是因?yàn)椋篊++在編譯期間段只,編譯器自動(dòng)為lambda表達(dá)式生成一個(gè)閉包ClosureType類。在lambda表達(dá)式被定義的地方鉴扫,實(shí)例化該類赞枕,生成實(shí)例add_func,并對(duì)被其捕獲的成員變量進(jìn)行賦值:
add_func.__x = x
所以坪创,按值捕獲的變量炕婶,在lambda定義時(shí),它在lambda內(nèi)部的值已經(jīng)被確定下來莱预。后續(xù)外部對(duì)變量x的修改柠掂,不會(huì)再影響到lambda內(nèi)部的__x。
那么依沮,如果需要修改按值捕獲的變量涯贞,應(yīng)該怎么做呢?修改完以后危喉,lambda內(nèi)外的變量會(huì)發(fā)生什么變化呢宋渔?
#include <iostream>
int main()
{
int x = 1;
// Define lambda to modify value captured
auto modify_func = [x] () {
x++;
};
return 0;
}
像這樣,直接對(duì)按值捕獲的變量進(jìn)行修改辜限,編譯器會(huì)報(bào)錯(cuò):
error: increment of read-only variable 'x'
x++;
需要用到一開始說的mutable限定符皇拣,改為這樣就可以了:
auto modify_func = [x] () mutable {
加上一些輸出信息之后,代碼變成了這樣:
#include <iostream>
int main()
{
int x = 1;
// Define lambda to modify value captured
auto modify_func = [x] () mutable {
std::cout << "x inside lambda is: " << x << std::endl;
x++;
};
std::cout << "Before calling lambda, x out of lambda is: " << x << std::endl;
modify_func();
std::cout << "After calling lambda, x out of lambda is: " << x << std::endl;
modify_func();
std::cout << "After calling lambda again, x out of lambda is: " << x << std::endl;
return 0;
}
運(yùn)行這段代碼薄嫡,可以得到這些輸出:
Before calling lambda, x out of lambda is: 1
x inside lambda is: 1
After calling lambda, x out of lambda is: 1
x inside lambda is: 2
After calling lambda again, x out of lambda is: 1
這里可以看出兩個(gè)信息:
按值捕獲之后氧急,lambda內(nèi)外的變量已經(jīng)沒有關(guān)系颗胡,各自有各自的數(shù)值。
修改lambda實(shí)例的成員變量之后态蒂,該修改會(huì)一直生效杭措,直到lambda實(shí)例的生命周期結(jié)束。
第一點(diǎn)信息钾恢,前面已經(jīng)解釋過手素。第二點(diǎn)信息,跟第一點(diǎn)信息的原理也密切相關(guān)瘩蚪。
可以這么理解泉懦,閉包ClosureType類的實(shí)例modify_func,根據(jù)捕獲的變量疹瘦,內(nèi)部相應(yīng)創(chuàng)建了成員變量__x崩哩。Lambda內(nèi)部的x++,其實(shí)是modify_func.__x++言沐。所以邓嘹,下次再次調(diào)用modify_func時(shí),其成員變量__x保留了上次調(diào)用的數(shù)值险胰。
以上兩點(diǎn)信息汹押,只要理解了lambda表達(dá)式其實(shí)是個(gè)ClosureType類,由編譯器根據(jù)捕獲的變量起便,自動(dòng)生成對(duì)應(yīng)的成員變量棚贾。然后在lambda表達(dá)式定義的地方被實(shí)例化、初始化成員變量榆综。而后的lambda表達(dá)式調(diào)用妙痹,本質(zhì)上是調(diào)用了該實(shí)例的成員函數(shù)。那么這些行為就很自然而然了鼻疮。
接下來怯伊,按引用捕獲變量。
按引用捕獲判沟,用法震贵、行為跟普通函數(shù)的按引用傳遞沒什么區(qū)別。只需要在捕獲的變量前水评,加上&符號(hào)即可。
#include <iostream>
int main()
{
int x = 1;
// Define lambda to capture by reference
auto ref_func = [&x] () {
std::cout << "x inside lambda is: " << x << std::endl;
x++;
};
std::cout << "Before calling lambda, x out of lambda is: " << x << std::endl;
ref_func();
std::cout << "After calling lambda, x out of lambda is: " << x << std::endl;
x = 5;
std::cout << "Now change x out of lambda to: " << x << std::endl;
ref_func();
std::cout << "After calling lambda again, x out of lambda is: " << x << std::endl;
return 0;
}
x變成&x媚送,變成了按引用捕獲變量中燥,之后叛本,lambda內(nèi)部和外部首尼,共享同一個(gè)變量雁佳。一方的修改缸废,將反應(yīng)到另一方上面。
所以咱扣,上面的代碼將輸出:
Before calling lambda, x out of lambda is: 1
x inside lambda is: 1
After calling lambda, x out of lambda is: 2
Now change x out of lambda to: 5
x inside lambda is: 5
After calling lambda again, x out of lambda is: 6
另外绽淘,如果需要按值捕獲外部的所有變量,通過[=]即可闹伪。
而通過[&]沪铭,可以按引用捕獲外部的所有變量。
最后一點(diǎn)偏瓤,針對(duì)外部的全局變量或者局部static變量杀怠,可以在lambda表達(dá)式內(nèi)部直接使用、修改厅克;內(nèi)外共享一個(gè)變量赔退。比如下面的代碼:
#include <iostream>
int global_val = 1;
int main()
{
// Define lambda to use global param
auto global_func = [] () {
std::cout << "global_val inside lambda is: " << global_val << std::endl;
global_val++;
};
std::cout << "Before calling lambda, global_val out of lambda is: " << global_val << std::endl;
global_func();
std::cout << "After calling lambda, global_val out of lambda is: " << global_val << std::endl;
global_val = 5;
std::cout << "Now change global_val out of lambda to: " << global_val << std::endl;
global_func();
std::cout << "After calling lambda again, global_val out of lambda is: " << global_val << std::endl;
return 0;
}
這段代碼將輸出:
Before calling lambda, global_val out of lambda is: 1
global_val inside lambda is: 1
After calling lambda, global_val out of lambda is: 2
Now change global_val out of lambda to: 5
global_val inside lambda is: 5
After calling lambda again, global_val out of lambda is: 6
可以看到,不用顯式捕獲全局變量证舟,lambda表達(dá)式內(nèi)部可以直接使用硕旗;lambda內(nèi)部和外部,共享同一個(gè)全局變量女责。一方的修改漆枚,將反應(yīng)到另一方上面。
綜上所述鲤竹,lambda表達(dá)式有這些特點(diǎn):
按值捕獲的變量浪读,在lambda定義時(shí),它在lambda內(nèi)部的值已經(jīng)被確定下來辛藻。之后碘橘,外部對(duì)該變量的修改,不會(huì)再影響到lambda內(nèi)部的那一份吱肌。
在lambda內(nèi)部痘拆,修改捕獲的變量之后,該修改會(huì)一直生效氮墨,直到lambda實(shí)例的生命周期結(jié)束纺蛆。
按引用捕獲的變量,lambda內(nèi)部和外部规揪,共享同一份桥氏。一方的修改,將反應(yīng)到另一方猛铅。
不用顯式捕獲全局變量字支,lambda表達(dá)式內(nèi)部可以直接使用;lambda內(nèi)部和外部,共享同一份全局變量堕伪。一方的修改揖庄,將反應(yīng)到另一方。
掌握了這些知識(shí)欠雌,就足以滿足常見的lambda表達(dá)式應(yīng)用了蹄梢。