在使用一些基於範圍的模板函數時(如 sort()、all_of()、find_if() 等),常常需要我們傳入一個可調用對象,以指明我們需要對範圍中的每個元素進行怎樣的處理。可調用對象可以是函數、函數指針、lambda表達式、bind創建的對象或者重載了函數調用運算符的類。
函數
bool cmp(const int &a, const int &b) {
return a < b; // 從小到大排列
//return a > b; // 從大到小排列
}
vector<int> v = {3, 6, 2, 7, 4, 9, 1};
sort(v.begin(), v.end(), cmp);
函數指針
bool cmp(const int &a, const int &b) {
return a < b; // 從小到大排列
//return a > b; // 從大到小排列
}
bool (*p)(const int &a, const int &b);//創建一個函數指針
p = cmp;//與函數進行綁定
vector<int> v = {3, 6, 2, 7, 4, 9, 1};
sort(v.begin(), v.end(), p);
lambda表達式
使用函數或者函數指針充當可調用對象對函數的形參有嚴格的限制,有時我們想傳入一些輔助變量,使用函數充當可調用對象已無法滿足這一需求,而 lambda 表達式中的捕獲列表(parameter list)可以幫助我們很容易做到這一點。
lambda 表達式具有如下形式:
[caputure list](parameter list) -> return type { function body }
- caputure list :一個 lambda 所在函數中定義的局部變量的列表。
- parameter list:參數列表,需要傳入的形參。
- return type:lambda 表達式的返回類型
- function body:lambda 表達式的函數體
一個簡單的 lambda 表達式的例子:
auto f = [] { return "hello world"; };
cout << f() << endl; // 輸出:hello world
注意:lambda 表達式可以省略參數列表(如果 lambda 表達式不需要傳入任何形參),也可以省略返回類型,但不可以省略捕獲列表和函數體。
使用捕獲列表
下面的函數 larger 判斷一個數組中的元素與指定的 base 的大小關係。
void larger(int base, vector<int> v) {
for_each(v.begin(), v.end(), [base](const int &num){cout << (num > base);});
}
類似函數傳參,上面使用的是指捕獲,可以是使用引用捕獲
[&sz]{sz = 1};
隱式捕獲
除了像上面那樣明確指出我們需要捕獲的變量列表外,我們還可以使用隱式捕獲
void larger(int base, vector<int> v) {
//隱式值捕獲
for_each(v.begin(), v.end(), [=](const int &num){cout << (num > base);});
//隱式引用捕獲
//for_each(v.begin(), v.end(), [&](const int &num){cout << (num > base);});
}
隱式捕獲和顯示捕獲還可以混合使用
[=, identifier_list]
// identifier_list中不得再出現值捕獲
[&, identifier_list]
// identifier_list中不得再出現引用捕獲
可變lambda
有時我們可能需要在lambda內部改變其其捕獲到的變量。(這不同於引用捕獲)
int a = 1;
auto f = [a] () mutable { return ++a; };
int b = f(); // a = 1, b = 2
指定 lambda 的返回類型
有時編譯器難以自動確定 lambda 表達式的返回類型,需要我們明確指定。下面的例子使用 transform 算法將 v 中的數組取絕對值。
vector<int> v = {1, -2, -3, 4}
transform(v.begin(), v.end(), v.begin(),
[](int i) -> int {
if (i < 0)
return -i;
else
return i;
});// v = {1, 2, 3, 4}
bind 創建的對象
lambda 表達式適合在對元素進行一些簡單的處理,在 lambda 表達式中編寫冗長的代碼會使得代碼風格變得很糟糕。進行復雜處理還是使用函數更爲合適,但還是那個問題需要解決:算法模板對傳入的參數有嚴格的數量限制,而有時我們需要傳入一個額外的參數給處理函數。使用 bind 將處理函數進行一次包裝可以很好的解決這一問題。
例如,對於 lambda 表達式[base](const int &num){cout << (num > base);}
,我們可以編寫一個等價的函數
void func(int num, int base) {
cout << (num > base);
}
但我們卻不能將這個 func 直接傳入 for_each() 算法模板中。使用 bind 創建一個可調用對象
void func(int num, int base) {
cout << (num > base);
}
void main() {
vector<int> v = {1, 2, 3, 4, 5};
int base = 3;
auto f = bind(func, base, _1);
for_each(v.begin(), v.end(), f); // 輸出:00011
}
bind函數的參數列表含義:
bind(function, arg_list)
其中 function 爲要包裹的函數,arg_list 與 function 的參數列表按序逐一對應,但其中的有一類特殊的參數,形如 _1
、_2
、_3
等。這些參數即指供算法模板(即這裏的 for_each())使用的參數,而那些沒有使用這種的形式的參數便相當於 lambda 捕獲列表中的參數。 _n
表示這是供算法模板使用的第 n 個參數(從1開始計數)。
bind 函數定義在 functional 頭文件中,_n
定義在 std::placeholders 命名空間下。使用 bind 需要:
#include<functional>
using namespace std::placeholders
重載函數調用運算符的類
當對處理函數非常複雜時,我們可以使用一個實現了調用運算符的類的對象來充當可調用對象。在構造類的對象時將需要捕獲的參數傳入。這是一種終極解決方案。
class Bigger {
private:
int base_;
public:
Bigger(int base) {
base_ = base;
}
void operator() (const int &num) {
cout << (num > base_);
}
};
void main() {
vector<int> v = {1, 2, 3, 4, 5};
int base = 3;
Bigger bigger(base);
for_each(v.begin(), v.end(), bigger);
}