C++ 中,對於一個對象或一個表達式,如果可以對其使用調用運算符(()
),則稱它是可調用的。即,如果 e
是可調用的,則可以這樣使用:
e(args)
其中,args
是一個逗號分隔的一個或多個參數的列表。
C++ 中可調用對象除了我們熟悉的函數或函數指針外,還包括函數對象以及 lambda 表達式。
本文重點講述 lambda 表達式。
lambda 表達式表示一個可調用的代碼單元,我們可以將其理解爲一個未命名的內聯函數。一個 lambda 表達式具有如下形式:
[capture list](parameter list) -> return type {function body}
- capture list:捕獲列表,lambda 表達式所在函數中定義的局部變量列表
- parameter list:參數列表
- return type:返回類型
- function body:函數體
lambda 表達式可以忽略參數列表和返回類型,但必須包含捕獲列表和函數體:
auto f = [] {return 10;};
上述代碼中,我們定義了一個可調用對象 f
,它不接受參數,返回 42。
lambda 的調用方式與普通函數的調用方式一樣:
cout << f() << endl;
輸出:
10
向 lambda 傳遞參數
lambda 可以帶上參數,例如:
[](const string &s1, const string &s2) {
return s1.size() < s2.size();
};
上述定義的 lambda 帶上兩個 const string &
類型的參數,lambda 函數體比較兩個 string
的長度並返回 bool
值。
可以使用此 lambda 來調用 std::stable_sort
排序。當 std::stable_sort
需要比較兩個元素時,它就會調用指定的這個 lambda。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
vector<string> words{"leehao.me", "lee", "hao", "leehao"};
std::stable_sort(words.begin(), words.end(),
[](const string &s1, const string &s2) {
return s1.size() < s2.size();
});
for (const auto &word : words) {
cout << word << endl;
}
return 0;
}
輸出:
lee
hao
leehao
leehao.me
lambda 捕獲列表
一個 lambda 可以使用其所在函數中的局部變量,但需要明確指明這些變量。lambda 捕獲列表可以用來聲明這些需要使用的局部變量。例如:
[sz](const string &s) {
return s.size() >= sz;
};
由於此 lambda 捕獲 sz
,因此,可以在函數體中使用 sz
。如果未捕獲 sz
,則會報編譯錯誤:
// 編譯錯誤:sz 未捕獲
[](const string &s) {
return s.size() >= sz;
};
lambda 本質上是函數對象。當向一個函數參數傳遞一個 lambda 時,同時定義了一個新的函數對象類型並生成一個此類型的未命名對象。
因此,從 lambda 生成的函數對象類型包含此 lambda 所捕獲的變量的數據成員,且 lambda 的數據成員在 lambda 對象創建時被初始化。
值捕獲
類似參數傳遞,變量的捕獲方式可以是值或引用。與傳遞參數類似,採用值捕獲的前提是變量可以拷貝。與參數不同,被捕獲的變量的值是在 lambda 創建時拷貝,而不是調用時拷貝:
void fcn1()
{
size_t v1 = 42; // 局部變量
// 將 v1 拷貝到名爲 f 的可調用對象
auto f = [v1] {return v1;};
v1 = 0;
auto j = f(); // j 爲 42,f 保存了 lambda 創建時 v1 的拷貝,即 42
cout << j << endl;
}
輸出:
42
由於被捕獲變量的值是在 lambda 創建時拷貝,因此,隨後對其修改不會影響到 lambda 內對應的值。
引用捕獲
定義 lamda 時可以採用引用方式捕獲變量:
void fcn2()
{
size_t v1 = 42; // 局部變量
// 對象 f2 包含 v1 的引用
auto f2 = [&v1] {return v1;};
v1 = 0;
auto j = f2(); // j 爲 0,f2 保存 v1 的引用,而非拷貝
cout << j << endl;
}
輸出:
0
v1
前的 &
表示 v1
以引用方式捕獲。當我們在 lambda 函數體內使用此變量,實際上使用的是引用所綁定的對象。因此,當 v1
在修改後,對象 f2
引用的 v1
也發生修改。
當以引用方式捕獲一個變量時,必須保證在 lambda 執行時,變量是存在的。
lambda 返回類型
上述例子中,我們並沒有爲 lambda 指定返回類型,這是由於編譯器可以正常推斷出 lambda 的返回類型。
例如,使用標準庫算法 transform
和一個 lambda 來將整型向量中每個負數轉化爲其絕對值:
vector<int> vi {1, -2, 3, 2, -4, 5};
std::transform(vi.begin(), vi.end(), vi.begin(),
[](int i) {return i < 0 ? -i : i;});
transform
第 4 個參數是一個 lambda。
lambda 函數體是單一的 return
語句,我們沒有指定返回類型,這是由於編譯器可以根據條件運算符來推斷出返回類型。
也可以指定 lambda 的返回類型:
vector<int> vi {1, -2, 3, 2, -4, 5};
std::transform(vi.begin(), vi.end(), vi.begin(),
[](int i) -> int
{if (i < 0) return -i; else return i;});
transform
第 4 個參數是一個 lambda,它的捕獲列表爲空,接受一個 int 參數,返回一個 int 值。
可以輸出 vi
元素的值:
for (auto i : vi) {
cout << i << endl;
}
輸出:
1
2
3
2
4
5
參考資料
- 《C++ Primer》,第五版,中文版