匿名函数

**匿名函式**（英語：Anonymous Function）在電腦編程中是指一類無需定義識別碼（函式名）的函式或子程式，普遍存在於多種程式語言中。

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

## 用途

### 排序

嘗試將類按名稱排序：

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

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

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

## 語言列表

## 範例

### Python

Python 用 lambda 語法定義匿名函式，只需用表達式而無需聲明

### JavaScript

JavaScript 支援匿名函式。

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

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

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

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

### PHP

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

#### 4.0.1 至 5.3

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

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

#### 5.3

PHP 5.3 新增加了 `Closure` 類，以及能使類別的實例可被呼叫的「魔術方法」`__invoke()`。Lambda 函式都是編譯器的一種「花招」，它能產生新的能被呼叫的 `Closure` 實例，就像函式能被呼叫一樣。

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

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

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

### C++

#### C++ 98/03

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

#### C++ 11

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

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

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

1. 參數不能有預設值
2. 不能有可變長參數列
3. 不能有無名參數

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

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

一個 lambda 函式的例子如下：

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

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

在這個例子中，一個臨時變數，`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 表達式的使用：

在類的非靜態成員函式中定義的 lambda 表達式可以顯式或隱式捕捉 `this` 指標，從而可以參照所在類物件的資料成員與函式成員。物件的 this 指標必需顯式擷取聲明。因此，被擷取的類的資料成員總是用 this 指標來存取，如果 this 所指的物件不存在，則 this 是空懸指標。解決辦法是在定義 lambda 之前用局部變數複製一份資料成員的值，然後擷取這個局部變數的值。

lambda 函式的函式體中，可以存取下述變數：

* 函式參數
* 局部聲明的變數
* 類資料成員：要求 lambda 表達式聲明在類別成員函式中，物件的 this 指標必需顯式擷取聲明。
* 具有靜態儲存期的變數（如全域變數）。一般情況下，lambda 是用來擷取局部變數的，如果用其來擷取全域變數或者靜態變數，那麼編譯器會報 warning
* 被擷取的外部變數
  * 顯式擷取的變數
  * 隱式擷取的變數，使用預設擷取模式（傳值或參照）來存取。

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

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

#### C++14

C++11 中擷取機制的局限

1. lambda 擷取的是局部變數或形參，不管是按值還是按參照擷取，這些都是具名的左值物件。而右值物件是匿名物件，無法被擷取。
2. 按值擷取時，左值是被複製到閉包中的。如果被擷取的物件是個只移動類型的物件時，因其無法被複製，就會出錯。
3. 如果被擷取的物件如果是一個占用記憶體較大的物件時，按值擷取顯然效率很低。

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

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

### Visual Basic.NET

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

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

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

## 參考資料

## 外部連結

* 《深入理解 PHP 核心》第四章 函式的實現 第四節 匿名函式及閉包（頁面存檔備份，存於網際網路檔案館）