C++ 可調用對象

在使用一些基於範圍的模板函數時(如 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};

隱式捕獲
除了像上面那樣明確指出我們需要捕獲的變量列表外,我們還可以使用隱式捕獲,即直接在函數體內使用我們需要使用的輔助變量而不用在捕獲列表中聲明,但需要指出捕獲方式——值捕獲 or 引用捕獲。

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 函數體中改變捕獲變量的值(即使是採用值捕獲的方式也不行)。如果我們確實有這種需求,可以在函數體前添加 mutable 關鍵字接觸這一限制。

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, _1, base); 
	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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章