匿名函數(shù)是許多編程語言都支持的概念,有函數(shù)體,沒有函數(shù)名。1958年,lisp首先采用匿名函數(shù),匿名函數(shù)最常用的是作為回調(diào)函數(shù)的值。正因?yàn)橛羞@樣的需求,c++引入了lambda 函數(shù),你可以在你的源碼中內(nèi)聯(lián)一個(gè)lambda函數(shù),這就使得創(chuàng)建快速的,一次性的函數(shù)變得簡單了。例如,你可以把lambda函數(shù)可在參數(shù)中傳遞給std::sort函數(shù)。
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned N) {
std::sort(x, x + N,
// Lambda expression begins
[](float a, float b) {
return std::abs(a) < std::abs(b);
});
}
你可能會問,使用函數(shù)對象不是也可以嗎?是的,函數(shù)對象當(dāng)然沒問題,自己寫的回調(diào)函數(shù),你可以傳個(gè)函數(shù)指針也沒有問題。他們有優(yōu)點(diǎn)也有缺點(diǎn)。函數(shù)對象能維護(hù)狀態(tài),但語法開銷大,而函數(shù)指針語法開銷小,卻沒法保存范圍內(nèi)的狀態(tài)。如果你覺得魚和熊掌不可兼得,那你可錯(cuò)了。lambda函數(shù)結(jié)合了兩者的優(yōu)點(diǎn),讓你寫出優(yōu)雅簡潔的代碼。
基本lambda語法
基本形式如下:
[capture](parameters) mutable exception attribute ->return-type {body}
capture 指定了在可見域范圍內(nèi) lambda 表達(dá)式的代碼內(nèi)可見得外部變量的列表,具體解釋如下:
[a,&b] a變量以值的方式唄捕獲,b以引用的方式被捕獲。
[this] 以值的方式捕獲 this 指針。
[&] 以引用的方式捕獲所有的外部自動變量。
[=] 以值的方式捕獲所有的外部自動變量。
[] 不捕獲外部的任何變量。
params 指定 lambda 表達(dá)式的參數(shù)。
mutable 修飾符說明 lambda 表達(dá)式體內(nèi)的代碼可以修改被捕獲的變量,并且可以訪問被捕獲對象的 non-const 方法。
exception 說明 lambda 表達(dá)式是否拋出異常(noexcept),以及拋出何種異常,類似于void f() throw(X, Y)。
attribute 用來聲明屬性
->return-type表示返回類型,如果沒有返回類型,則可以省略這部分。如果 lambda 代碼塊中包含了 return 語句,則該 lambda 表達(dá)式的返回類型由 return 語句的返回類型確定。
lambda函數(shù)的類型是std:function,并非函數(shù)指針。不過C++11標(biāo)準(zhǔn)允許lambda函數(shù)向函數(shù)指針的轉(zhuǎn)換,但前提是lambda函數(shù)沒有捕捉任何變量,且函數(shù)指針?biāo)镜暮瘮?shù)原型,必須跟lambda函數(shù)有著相同的調(diào)用形式。
// 類型為function<int(int,int)>
auto add [](int x, int y) {return x+y;}
add(1,2) // 3
除去語法層面的不同,lambda和仿函數(shù)有著相同的內(nèi)涵——都可以捕捉一些變量作為初始狀態(tài),并接受參數(shù)進(jìn)行運(yùn)算。而事實(shí)上,仿函數(shù)是編譯器實(shí)現(xiàn)lambda的一種方式。在現(xiàn)階段,編譯器都會把lambda函數(shù)轉(zhuǎn)化為一個(gè)仿函數(shù)對象。因此,在C++11中,lambda可以視為仿函數(shù)的一種等價(jià)形式,或者稱為仿函數(shù)的“語法甜點(diǎn)”。
lambda函數(shù)在C++11標(biāo)準(zhǔn)中默認(rèn)是內(nèi)聯(lián)的。
使用lambda函數(shù)時(shí),捕獲列表按值傳遞和按引用傳遞的效果是不一樣的。對于按值傳遞的捕獲列表,其傳遞的值在lambda函數(shù)定義的時(shí)候就已經(jīng)決定了。而按引用傳遞的捕獲列表,其傳遞的值則等于lambda函數(shù)調(diào)用時(shí)的值。
int i = 1;
auto by_val_lambda = [=] { return j+1;}
auto by_ref_lambda = [&] { return j+1;}
cout<<"by_val_lambda: "<<by_val_lambda()<<endl; //12
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl; //12
i++;
cout<<"by_val_lambda: "<<by_val_lambda()<<endl; //12
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl; //13
如果需要捕捉的值稱為lambda函數(shù)的常量,我們通常會使用按值傳遞的方式捕捉;而需要捕捉的值稱為lambda函數(shù)運(yùn)行時(shí)的變量(類似于參數(shù)的效果),則應(yīng)采用按引用傳遞的方式進(jìn)行捕捉。
lambda函數(shù)的使用場景
- 一些短小的函數(shù),但只使用一次的。
- 配合STL泛型算法使用。
- 回調(diào)函數(shù)。
lambda函數(shù)被設(shè)計(jì)的目的,就是要就地書寫,就地使用。使用lambda的用戶,更傾向于在一個(gè)屏幕里看到所有的代碼,而不是依靠代碼瀏覽工具在文件間找到函數(shù)的實(shí)現(xiàn)。而在封裝的思維層面上,lambda只是一種局部的封裝,以及局部的共享。而需要全局共享的代碼邏輯,我們還是需要用函數(shù)(無狀態(tài))或者仿函數(shù)(有初始狀態(tài))封裝起來。