文章目錄
- 重載函數調用運算符
- lambda
- lambda等價于函數對象
- lambda等價于類
- 標準庫函數對象
- 可調用對象與function
- 可調用對象
- function
- 函數重載與function
重載函數調用運算符
函數調用運算符必須是成員函數。 一個類可以定義多個不同版本的調用運算符,互相之間應該在參數數量or類型上有所區別。如果類定義了函數調用運算符 ()
,則該類的對象稱作函數對象,因為可以調用這種對象,所以我們說這些對象的行為像函數一樣。
class absInt {
public:int operator()(int val)const {return val < 0 ? -val : val;}
};
lambda
lambda等價于函數對象
對于作為最后一個實參的 lambda
的表達式來說:
//根據單詞的長度對其進行排序,對于長度相同的單詞按照字母表順序排序
stable_sort (words.begin(), words.end(),[](const string &a, const string &b){return a.size () < b.size();});
其等價于下面這個類的一個未命名對象:
class ShorterString {
public:bool operator()(const string &s1, const string &s2) const{ return s1.size() < s2.size(); }
};
默認情況下,lambda
不能改變它捕獲的變量,因此類的函數調用運算符是一個 const
成員函數。
那么我們可以重寫并調用 stable_sort
:
stable_sort(words.begin(), words.end(), ShorterString());
lambda等價于類
- 當一個
lambda表達式
通過引用捕獲變量時,將由程序負責確保lambda
執行時所引用的對象確實存在。因此,編譯器可以直接使用該引用而無須在lambda產生的類
中將其存儲為數據成員。 - 過值捕獲的變量被拷貝到
lambda
中。因此,這種lambda
產生的類必須為每個值捕獲的變量建立對應的數據成員,同時創建構造函數,令其使用捕獲的變量的值來初始化數據成員。
舉個例子,有這樣一個 lambda
,它的作用是找到第一個長度不小于給定值的 string
對象:
//獲得第一個指向滿足條件元素的迭代器,該元素滿足 size() >= sz
auto wc = find_if(words.begin(), words.end(),[sz] (const string &a){ return a.size() >= sz;});
等價于這樣的類:
class SizeComp{size_t sz; // 該數據成員對應通過值捕獲的變量
public:SizeComp (size_t n): sz(n) {} // 該形參對應捕獲的變量// 該調用運算符的返回類型、形參和函數體都與lambda一致bool operator()(const string &s) const { return s.size()>= sz; }
由于這個類不含默認構造函數,因此想要調用它必須提供一個實參,也就起到了 值捕獲 的作用。
標準庫函數對象
表示運算符的函數對象類常用來替換算法中的默認運算符。
例如,在默認情況下排序算法使用 operator<
將序列按照升序排列。如果要執行降序排列的話,我們可以傳入一個 greater
類型的對象。該類將產生一個調用運算符并負責執行待排序類型的大于運算:
// sv是一個vecotr<string>,greater<string>()是一個臨時的函數對象
sort(sv.begin(), sv.end(), greater<string>());
標準庫函數對象適用性是很寬的,甚至適用于指針:
而實際上,STL
中也正是這樣做的:關聯容器使用 less<key_type>
對元素排序,因此我們可以定義一個指針的 set
或者在 map
中使用指針作為關鍵值而無須直接聲明 less
。
可調用對象與function
可調用對象
C++語言中有幾種可調用的對象:函數、函數指針、lambda表達式、bind創建的對象、重載了函數調用運算符的類。
和其他對象一樣,可調用的對象也有類型。例如,每個lambda有它自己唯一的(未命名)類類型;函數及函數指針的類型則由其返回值類型和實參類型決定,等等。
然而,兩個不同類型的可調用對象卻可能共享同一種調用形式(call signature)。 調用形式指明了調用返回的類型以及傳遞給調用的實參類型。一種調用形式對應一個函數類型,例如:
int(int, int)
是一個函數類型,它接受兩個 int
、返回一個 int
。
用具體的例子來講,即:
上面三個可調用對象共享一種調用形式—— int(int, int)
對于調用形式相同的可調用對象,我們可以定義一個函數表(function table)存儲他們的“指針”,對他們進行統一管理。 當程序需要執行特定的 +
、%
、/
時,從表中查找該操作對應的可調用對象。
在 C++
中,函數表很容易通過 map
實現:
// string表示 + 或 % 或 /
// 函數指針 作為 value
map<string, int(*)(int, int)> m;
// 我們可以將 add的指針 加入到 m 中
m.insert({"+", add}); // {"+", add}是一個pair
// 但不可以將 mod 或者 divide 存入 m
m.insert({"%", mod}); // error:mod不是一個函數指針,lambda有自己的類類型
function
function
模板的運用示例:
function<int(int, int)>
// 聲明了一個function類型,表示接受兩個int、返回一個int的可調用對象
// 用function<int(int, int)>表示上面的可調用對象
function<int(int, int)> f1 = add; // 函數指針
function<int(int, int)> f2 = divide(); // 函數對象類的對象
function<int(int, int)> f3 = [](ing i, int j) {return i%j}; // lambdacout << f1(4, 2) << endl; // 打印6
cout << f2(4, 2) << endl; // 打印2
cout << f3(4, 2) << endl; // 打印0
function
也可以和 map
搭配使用:
初始化:
使用:
函數重載與function
我們不能直接將重載函數的名字存入 function
類型的對象中:
int add(int i, int j) { return i + j; }
A add(const A&, const A&);
map<string, function<int(int, int)>> m;
m.insert( {"+", add} ); // ERROR:哪個add?
解決上述二義性問題的一條途徑是存儲函數指針而非函數名字:
int (*fp)(int, int) = add;
m.insert( {"+", fp} );
新版本標準庫中的 function 類與舊版本中的 unary_function 和 binary_function 沒有關聯。后兩個類已經被更通用的 bind 函數替代了。