匿名函式

匿名函式（Anonymous Function）在計算機程式設計中是指一類無需定義識別符號（函式名）的函式或子程式，普遍存在於多種程式語言中。

1958 年 LISP 首先採用匿名函式，自此之後，越來越多程式語言陸續採用，主流的程式語言如 PHP 和 C++ 也陸續採用。

用途

1.1 排序

嘗試將類按名稱排序：

a = 10, '10', 10.0

a.sort (lambda x,y: cmp (x.class.name, y.class.name))

print a

10.0, 10, '10'

上述 10.0 的類名是 “float”，10 的類名是 “int” 而 '10' 的類名是 “str”，排列後的順序為 “float”“int”，接著是 “str”。

該示例中的匿名函式就是 lambda 表示式：

lambda x,y: cmp (...)

該匿名函式接受兩個變數 x 和 y ，透過內部函式 cmp 返回兩者的比較值，下面的例子將按長度為字串列表排序：

a = 'three', 'two', 'four'

a.sort (lambda x,y: cmp (len (x), len (y)))

print a

'two', 'four', 'three'

語言列表

示例

3.1 Python

Python 用 lambda 語法定義匿名函式，只需用表示式而無需宣告

以下兩種相等同
不使用匿名函式

def f (x):

	return x * x

使用匿名函式

lambda x: x * x

3.2 JavaScript

JavaScript 支援匿名函式。

alert ((function (x){return x*x;})(10)); // 提示 100

小書籤也經常使用這種結構，例如下面的一個小書籤就將當前網頁的標題顯示為其 URL：

javascript:document.title=location.href;

然而，由於該賦值語句返回了一個值（即 URL 本身），很多瀏覽器會同時建立一個新的頁面顯示這個值。

取而代之，下面的匿名函式就可以做到不返回任何值：

javascript:(function {document.title=location.href;});

第一對圓括號中的函式（“(function {document.title=location.href;})”）用作宣告一個匿名函式，而最後的一對圓括號則用來執行這個函式。同等用法有：

javascript:var f = function {document.title=location.href;}; f ;

3.3 PHP

PHP 4.0.1 之前不支援匿名函式。

3.3.1 4.0.1 至 5.3

PHP 4.0.1 新增加了 create_function 函式，這是匿名函式的雛形。該函式能建立一個隨機命名的新函式並以字串形式返回新函式的函式名。

$foo = create_function ('$x', 'return $x*$x;');

$bar = create_function ("\$x", "return \$x*\$x;");

echo $foo (10);

要注意的是，新函式本身及其變數都要放在單引號裡面，如果要放在雙引號之內，美元符號 “$” 則需要轉碼成為 “\$”。

3.3.2 5.3

PHP 5.3 新增加了 Closure 類，以及能使類的例項可被呼叫的 “魔術方法”invoke 。Lambda 函式都是編譯器的一種 “花招”，它能產生新的能被呼叫的 Closure 例項，就像函式能被呼叫一樣。

$x = 3;

$func = function ($z) {return $z *= 2;};

echo $func ($x); // 輸出結果為 6

上述例子中的 $func 是 Closure 類的一個例項，而 echo $func 則相當於是 $func->invoke ($z)。PHP 5.3 模仿使用匿名函式，但並非支援真匿名函式，因為 PHP 的函式仍非第一類函式。

雖然 PHP 5.3 支援閉包，但還需要像這樣明確標識其變數：

$x = 3;

$func = function use (&$x) {$x *= 2;};

$func ;

echo $x; // 輸出結果為 6

$func 引用了變數 $x（&$x），在呼叫的時候就會修改原來的 $x，其結果在函式以外的地方也是可見的。

3.4 C++

3.4.1 C++ 98/03

C++ 98/03 標準並不原生支援匿名函式。不過可以利用 Boost 庫的 Boost.Lambda 來實現一個匿名函式。

3.4.2 C++ 11

C++11 標準提供了匿名函式的支援，在《ISO/IEC 14882:2011》（C++11 標準文件）中叫做 lambda 表示式。一個 lambda 表示式有如下的形式：

capture (parameters) mutable exception attribute -> return_type {body}	

必須用方括號括起來的 capture 列表來開始一個 lambda 表示式的定義。

lambda 函式的形參表比普通函式的形參表多了 3 條限制：

引數不能有預設值
不能有可變長引數列表
不能有無名引數

如果 lambda 函式沒有形參且沒有 mutable、exception 或 attribute 宣告，那麼引數的空圓括號可以省略。但如果需要給出 mutable、exception 或 attribute 宣告，那麼引數即使為空，圓括號也不能省略。

如果函式體只有一個 return 語句，或者返回值型別為 void，那麼返回值型別宣告可以被省略：

capture (parameters){body}

一個 lambda 函式的例子如下：

(int x, int y) {return x + y;} // 從 return 語句中隱式獲得的返回值型別

(int& x) {++x;} // 沒有 return 語句 -> lambda 函式的返回值為 void

 { ++global_x;} // 沒有引數，僅僅是訪問一個全域性變數

{++global_x;} // 與前者相同， 可以被省略

在上面的第一個例子中這個無名函式的返回值是 decltype (x+y)。如果 lambda 函式體的形式是 return expression，或者甚麼也沒返回，或者所有返回語句用 decltype 都能檢測到同一型別，那麼返回值型別可以被省略。

返回值型別可以顯式指定，如下所示：

(int x, int y) -> int {int z = x + y; return z;}

在這個例子中，一個臨時變數，z，被建立來儲存中間過程。與一般的函式一樣，中間值在呼叫的前後並不存在。甚麼也沒有返回的 lambda 表示式無需顯式指定返回值，沒有必要寫 -> void 程式碼。

lambda 函式可以捕獲 lambda 函式外的具有 automatic storage duration 的變數，即函式的區域性變數與函式形參變數。因而，具有 static storage duration 的變數不能被 lambda 捕獲，只能直接使用，這包括靜態區域性變數。函式體與這些變數的集合合起來稱做閉包。這些外部變數在宣告 lambda 表示式時列在在方括號和中。空的方括號表示沒有外界變數被 capture 或者按照預設方式捕獲外界變數。這些變數被傳值捕獲或者引用捕獲。對於傳值捕獲的變數，預設為只讀（這是由於 lambda 表示式生成的為一個函式物件，它的 operator 成員預設有 const 屬性）。修改這些傳值捕獲變數將導致編譯報錯。但在 lambda 表示式的引數表的圓括號後面使用 mutable 關鍵字，就允許 lambda 函式體內的語句修改傳值捕獲變數，這些修改與 lambda 表示式（實際上是用函式物件實現）有相同的生命期，但不影響被傳值捕獲的外部變數的值。lambda 函式可以直接使用具有 static 儲存期的變數。如果在 lambda 函式的捕獲列表中給出了 static 儲存期的變數，編譯時會給出警告，仍然按照 lambda 函式直接使用這些外部變數來處理。因此具有 static 儲存期的變數即使被宣告為傳值捕獲，修改該變數實際上直接修改了這些外部變數。編譯器生成 lambda 函式對應的函式物件時，不會用函式物件的資料成員來保持被 “捕獲” 的 static 儲存期的變數。示例：

// 沒有定義任何變數，但必須列出空的方括號。在 Lambda 表示式中嘗試使用任何外部變數都會導致編譯錯誤。

x, &y //x 是按值傳遞，y 是按引用傳遞

& // 任何被使用到的外部變數都按引用傳入。

= // 任何被使用到的外部變數都按值傳入。

&, x //x 按值傳入。其它變數按引用傳入。

=, &z //z 按引用傳入。其它變數按值傳入。

下面這個例子展示了 lambda 表示式的使用：

std::vector some_list {1, 2, 3, 4, 5};

int total = 0;

std::foreach (begin (somelist), end (some_list),

&total (int x) {total += x;}

);

在類的非靜態成員函式中定義的 lambda 表示式可以顯式或隱式捕捉 this 指標，從而可以引用所在類物件的資料成員與函式成員。物件的 this 指標必需顯式捕獲宣告。因此，被捕獲的類的資料成員總是用 this 指標來訪問，如果 this 所指的物件不存在，則 this 是空懸指標。解決辦法是在定義 lambda 之前用區域性變數複製一份資料成員的值，然後捕獲這個區域性變數的值。

lambda 函式的函式體中，可以訪問下述變數：

函式引數
區域性宣告的變數
類資料成員：要求 lambda 表示式宣告在類成員函式中，物件的 this 指標必需顯式捕獲宣告。
具有靜態儲存期的變數（如全域性變數）。一般情況下，lambda 是用來捕獲區域性變數的，如果用其來捕獲全域性變數或者靜態變數，那麼編譯器會報 warning
被捕獲的外部變數
顯式捕獲的變數
隱式捕獲的變數，使用預設捕獲模式（傳值或引用）來訪問。

lambda 函式的資料型別是函式物件，儲存時必須用 std::function 模板型別或 auto 關鍵字。

例如：

include

double eval (std::function f, double x = 2.0)

{return f (x);

}

int main

{std::function f0 = (double x){return 1;};

	auto f1 = (double x){return x;};

	decltype (f0) fa3 = {f0,f1,(double x){return x*x;}};

	std::vector fv = {f0,f1};

	fv.push_back ((double x){return x*x;});

	for (int i=0;i

一個 lambda 函式的捕捉表示式為空，則可以用普通函式指標儲存或呼叫。例如：

auto alambdafunc = (int x) {/.../};

void (* funcptr)(int) = alambda_func;

func_ptr (4); //calls the lambda.

3.4.3 C++14

C++11 中捕獲機制的侷限

lambda 捕獲的是區域性變數或形參，不管是按值還是按引用捕獲，這些都是具名的左值物件。而右值物件是匿名物件，無法被捕獲。
按值捕獲時，左值是被複制到閉包中的。如果被捕獲的物件是個只移動型別的物件時，因其無法被複制，就會出錯。
如果被捕獲的物件如果是一個佔用記憶體較大的物件時，按值捕獲顯然效率很低。

C++14 增加了廣義捕獲 (Generalized capture)，或稱 “初始化捕獲”。即在捕獲子句（capture clause）中增加並初始化新的變數，該變數不需要在 lambda 表示式所處的閉包域（enclosing scope）中存在；即使在閉包域中存在也會被新變數覆蓋 (override)。新變數型別由它的初始化表示式推導。也就是說可以建立新的變數並在捕獲子句中對其進行初始化。這種方式稱之為帶有初始化程式的捕獲或者廣義 lambda 捕獲。一個用途是可以從閉包域中捕獲只供移動的變數並使用它。形如 mVar1 = localVar1, mVar2 = std::move (localVar2);

C++14 還允許 lambda 函式的形參使用 auto 關鍵字作為其型別，這實質上是函式物件的 operator 成員作為模板函式；並且允許可變引數模板。

auto alambdafunc = data1=101 (int x) {/.../}; // 廣義捕獲，實質上是在函式物件中增加了資料成員 data1 並初始化

auto ptr = std::makeunique (10); //See below for std::makeunique

auto lambda1 = ptr = std::move (ptr) {return *ptr;}

// 大致等效於：

auto lambda2 = ptr = std::make_unique (10) {return *ptr;}

auto lambda3 = (auto x, auto y) {return x + y;} //lambda 函式的形參型別為 auto

struct unnamed_lambda // 這相當於函式物件：

{

template

auto operator (T x, U y) const {return x + y;}

};

auto lambda4 = (auto&&... params) // 可變引數的函式模板

{return (foo (std::forward (params)...));

}

3.5 Visual Basic.NET

匿名函式或 lambda 表示式即無名的函式或過程，作為表示式的值。可以寫為一行或多行。例如：

Dim func1=Function (i As integer) i+10

Dim action = sub

End Sub

Dim func2 = Function

End Function

可以在宣告匿名函式的同時呼叫它。單行的 lambda 表示式不能使用 Return 關鍵字，其返回型別是自動推導得出；其引數要麼都是用 As 關鍵字指明型別，要麼全部是自動推匯出型別。

lambda 表示式在定義時可以使用所在上下文（context，即 C++ 語言的閉包 closure）的區域性變數、引數、屬性、Me 等等的值，即使 lambda 表示式離開了定義時所在的 context，這些被使用的區域性變數等的值仍然有效。這是因為 lambda 表示式在定義時把所用到的 context 的值儲存到它自己的定義類中。lambda 表示式可以巢狀定義。