想要搶先看後面的章節?打賞本文10元,即可獲得帶插圖全本下載地址!

打賞完成記得私信我哦 :p

11.2.2 利用函數對象記住狀態數據

雖然函數對象也可以像函數一樣被用來表達一個數據處理過程,但它更大的意義在於,函數對象具有「記憶力」,它可以記住函數執行過程中的狀態數據,從而使它可以應用在那些需要記住函數上次執行的狀態數據的場景下。對於普通函數而言,函數只是用來表達一個運算的過程,它無法記住運算過程中的一些狀態數據。函數就像一個漏斗,數據可以從這個漏洞中流過,發生某些變化,但是這個漏斗什麼都不會留下。在大多數情況下,「漏斗式」的普通函數已經完全可以滿足需要了,但在某些特殊情況下,下一次的函數執行是在上一次函數執行的結果基礎上進行的。這時,函數就需要記住上一次的執行狀態數據以備下一次函數執行使用。例如,用一個函數統計容器中所有Student對象的身高,就需要在上次的統計結果的基礎上進行累加,從而將所有身高都累加到一起。如果函數無法記住上一次的統計結果,每次都從零開始統計,這樣始終都無法完成身高的統計,成了函數永遠無法完成的任務。

函數對象的出現就是用來彌補函數的這個缺陷的。利用函數對象自身的成員變數,函數可以記住在每次執行過程中的狀態數據,找回失去的記憶。例如,可以利用函數對象來替函數完成它那永遠也無法完成的任務:

// 定義一個函數對象類
// 用於統計容器中所有Student對象的身高
class AverageHeight
{
public:
// 構造函數,對類的成員變數作合理的初始化
AverageHeight()
: m_nCount(0), m_nTotalHeight(0) {};
// 定義函數調用操作符「()」,
// 在其中完成統計的功能
void operator () ( const Student& st )
{
// 將當前對象的身高累加到總身高中
// 這裡的m_nTotalHeight記錄了上次累加的結果,
// 這就是函數那失去的記憶
m_nTotalHeight += st.GetHeight();
// 增加已經統計過的Student對象的數目
++m_nCount;
}
// 介面函數,獲得所有統計過的Student對象的平均身高
float GetAverageHeight()
{
if ( 0 != m_nCount )
return (float)GetTotal()/GetCount();
}
// 獲得函數對象類的各個成員變數
int GetCount() const
{
return m_nCount;
}
int GetTotal() const
{
return m_nTotalHeight;
}
// 函數對象類的成員變數,
// 用來保存函數執行過程中的狀態數據
private:
int m_nCount; // 記錄已經統計過的對象的數目
int m_nTotalHeight; // 記錄已經統計過的身高總和
};

為了讓函數對象完成身高統計的功能,我們在函數對象類中添加了兩個成員變數來記錄函數每次執行過程中的狀態數據:m_nCount用於記錄已經統計過的對象的數目,也就是當前已經統計了多少個Student對象的身高數據;m_nTotalHeight則用來記錄已經統計過的身高總和,這樣,每一次函數對象的執行就可以在上一次執行的結果數據的基礎上進行累加,函數也就不會再失憶了。

現在有了這個可以找回記憶的函數對象類,就可以創建該類的函數對象,並將它應用到for_each()演算法中來完成統計身高的任務了:

// …
// 創建函數對象
AverageHeight ah;
// 將函數對象應用到for_each()演算法中以完成統計
ah = for_each( vecStu.begin(), vecStu.end(), ah);
// 從函數對象中獲取它的記憶作為結果輸出
cout<<ah.GetCount()<<"個學生的平均身高是:"
<<ah.GetAverageHeight()<<endl;

在這裡,創建了一個函數對象ah並將它應用到for_each()演算法中,for_each()演算法在執行的時候,會逐個地以容器中的Student對象作為實際參數來對這個函數對象的「( )」操作符進行調用。這樣,函數對象ah就有機會訪問到容器中的每一個Student對象,自然也就可以把這些對象的身高累加到它自己的m_nTotalHeight成員變數上,同時它還會記錄已經統計過的對象數目。最後,for_each()演算法會將完成統計後的函數對象作為結果返回,而這時的函數對象ah已經是一個保存了統計結果的函數對象了。通過函數對象提供的介面函數,可以輕鬆地獲得統計結果並進行輸出。

更直接地,可以在函數對象類中定義一個類型轉換函數,將函數對象直接轉換為所需要的目標結果。例如:

class AverageHeight
{
// …
// 定義類型轉換函數
// 將函數對象轉換為float類型,直接返回計算結果
operator float ()
{
return GetAverageHeight();
}
};

現在,就可以直接從for_each()演算法中獲得計算結果了:

// 從for_each()演算法返回的函數對象被直接轉換為float類型數據float fAH = for_each( vecStu.begin(), vecStu.end(), ah );

穿上函數對象這個馬甲,函數就不再僅僅是一個過程,而是有了自己的記憶,成了一個有故事的人。

知道更多:STL中的函數對象

為了減少定義函數對象類的工作,STL中預定義了許多常用的函數對象類,主要包括:

? 算術操作

這類函數對象類可以用於常見的算術運算,例如,加(plus)、減(minus)、乘(multiplies)、除(divides)、取餘(modules)和取負(negate)等。

? 比較操作

這類函數對象類可以用於進行數據的比較,例如,等於(equal_to)、不等於(not_equal_to)、大於(greater)、小於(less)、大於等於(greater_equal)、小於等於(less_equal)。

? 邏輯操作

這類函數對象類可以用於邏輯運算,例如,邏輯與(logical_and)、邏輯或(logical_or)、 邏輯非(logical_not)。

STL中的這些函數對象類都是一些類模板,在使用的時候,我們需要根據所處理的數據提供具體的類型參數。例如,我們需要將一個容器中的數據全部取負,就可以用negate函數對象類來完成:

vector<int> v = {54,65,-59,96,-61};
// 利用negate函數對象對數據取負
// 這裡的「negate<int>()」得到的是一個臨時的函數對象
transform(v.begin(),v.end(),v.begin(),negate<int>());

推薦閱讀:

相關文章