第二十一篇:最佳謂詞函式 --- 函式物件

穆晨發表於2017-01-26

前言

       學習C++標準演算法時,我們知道帶有_if的演算法都帶有一個謂詞函式。此前我們直接使用函式名充當謂詞函式,這樣做有很大的侷限性( 下文會做分析 )。為此本文將介紹C++中一個新的技術:函式物件。用它來充當謂詞函式就能解決這個問題。

什麼是函式物件?

       函式物件就是過載了函式呼叫操作符的物件。我們知道,函式呼叫規則已經非常固定了,那麼為何要過載函式呼叫操作符呢?因為過載後,這個函式呼叫操作符將不再用作一般意義的函式呼叫功能。不能呼叫各種函式的物件那還叫物件嗎?從語義上來講,它不符合物件的設定原則" 對現實事物的虛擬 ",因此我們叫它函式物件以區別於其他物件。那這樣的物件有什麼用?這個問題稍後討論,下面來看一個簡單的用作求絕對值的函式物件實現:

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 /*
 7  * 1. 函式物件的作用是虛擬函式,因此對它的命名也按照函式命名規範。
 8  * 2. 過載了函式呼叫操作符,建構函式也不能用了。當然函式物件只需要()這一個" 函式 ",無需定義其他任何
 9  *    函式,包括建構函式。
10 */
11 class absInt {
12 public:
13     int operator() (int val) {
14         return val < 0? -val : val;
15     }
16 };
17 
18 
19 int main()
20 {
21     absInt absObj;
22     int val1 = -1;
23     int val2 = 1;
24 
25     /*
26      * absObj本質是物件,但它虛擬成了一個返回絕對值的函式。
27     */
28     cout << absObj(val1) << endl;
29     cout << absObj(val2) << endl;
30 
31     return 0;
32 }

       執行結果:

       

       仔細閱讀程式碼便能發現,該程式將物件虛擬化為一個函式來用。

為何要定義函式物件?

       要物件就用物件,函式就用函式,為何費那麼大勁把物件整成函式?相信有人會這麼問。別急,我們先來回顧一個使用標準演算法的例子,這個程式碼返回容器中長度超過6的單詞:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <vector>
 4 #include <string>
 5 
 6 using namespace std;
 7 
 8 bool GT6 (const string &s) {
 9     return s.size() >= 6;
10 }
11 
12 int main()
13 {
14     /*
15      * 構建測試容器
16     */
17     vector<string> words;
18     words.push_back("China");
19     words.push_back("America");
20     words.push_back("England");
21     words.push_back("Japan");
22 
23     // 獲取容器中長度大於6的字串的個數
24     vector<string> :: size_type wc = count_if(words.begin(), words.end(), GT6);
25 
26     // 列印結果
27     cout << wc << endl;
28 
29     return 0;
30 }

       這段程式碼沒有問題,正常地返回了結果:

       

       但是,如果我改變注意了,又想獲得長度大於5的字串,那麼我該怎麼做?修改函式呼叫操作符過載函式是可行的辦法,但是當函式複雜起來的時候,這會是一個很麻煩的工作。好在改用函式物件充當謂詞函式後就能解決這個問題了。

使用函式物件的改進版本

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <vector>
 4 #include <string>
 5 
 6 using namespace std;
 7 
 8 class GT_cls {
 9 public:
10     GT_cls (string::size_type val=0) : bound(val) {};
11     bool operator() (const string &s) {
12         return s.size() >= bound;
13     }
14         
15 private:
16     string::size_type bound;
17 };
18 
19 int main()
20 {
21     /*
22      * 構建測試容器
23     */
24     vector<string> words;
25     words.push_back("China");
26     words.push_back("America");
27     words.push_back("England");
28     words.push_back("Japan");
29 
30     /*
31      * 獲取容器中長度大於6的字串的個數
32      * 如要取長度大於5的,只要將下面語句中的6改成5即可,不用修改過載函式。
33     */
34     vector<string> :: size_type wc = count_if(words.begin(), words.end(), GT_cls(5));
35 
36     // 列印結果
37     cout << wc << endl;
38 
39     return 0;
40 }

       執行結果:

       

       如果要找長度大於8的單詞個數?同理,將函式物件引數改成8即可。

說明

       請細細體會上面的程式碼,分析其執行過程。

相關文章