C++中的仿函數(function object)是一個重載了函數調用運算符(operator())的類或結構體,在使用時可以像函數一樣調用。通過仿函數,C++程式員可以更加靈活地實現自己的演算法。 ...
仿函數
1.什麼是仿函數
1.定義和作用
仿函數是一種重載了函數調用運算符(operator())的類或結構體,它可以像函數一樣被調用。仿函數可以在很多STL演算法中使用,例如sort、for_each、transform等,可以自定義排序規則、操作、條件等等。通過仿函數,C++程式員可以更加靈活地實現自己的演算法。
與普通函數不同,仿函數可以保存狀態,因此在使用仿函數時可以靈活地傳遞參數併進行計算,非常適用於一些複雜的演算法和數據結構的實現。
2.仿函數與函數指針的區別
在C++中,函數指針可以作為參數傳遞和返回值,但是函數指針只能指向函數,無法指向類成員函數和lambda表達式。而仿函數可以作為一種通用的函數封裝,可以指向函數、類成員函數以及lambda表達式,並且可以保存狀態。因此,仿函數比函數指針更加靈活和可擴展。
3.仿函數的調用方式
仿函數可以像函數一樣被調用
class MyFunctor {
public:
void operator() (int i) {
cout << i << endl;
}
};
MyFunctor myFunctor;
myFunctor(123);
//結果
//123
在STL演算法中,仿函數也可以作為函數參數傳遞
vector<int> vec{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
sort(vec.begin(), vec.end(), greater<int>());
for_each(vec.begin(), vec.end(), MyFunctor());
其中,greater()是一個內置的仿函數對象,用於實現降序排列。
2.仿函數的分類
1.一元仿函數和二元仿函數
一元仿函數是指只有一個參數的仿函數
template <typename T>
struct MyFunctor1 {
void operator() (T val) {
cout << val << endl;
}
};
template <typename T>
struct MyFunctor2 {
T operator() (T val) {
return val * val;
}
};
二元仿函數是指有兩個參數的仿函數
template <typename T>
struct MyFunctor3 {
bool operator() (T val1, T val2) {
return val1 < val2;
}
};
在STL演算法中,一元仿函數和二元仿函數通常用於排序、查找、遍歷等操作。
2.函數對象與謂詞
- 函數對象:返回值為任意類型的仿函數,例如std::plus,std::minus
- 謂詞:返回值為bool類型的仿函數,例如std::less,std::greater
3.函數適配器
函數適配器是一種特殊的仿函數,它用於將一個仿函數適配到另一個仿函數或函數對象上。STL中常用的函數適配器有:bind1st、bind2nd、not1、not2、logical_and、logical_or等
template <typename T>
struct MyFunctor4 {
bool operator() (T val) {
return val > 0;
}
};
vector<int> vec{3, -1, 4, -1, 5, -9, 2, -6, 5, 3, -5};
count_if(vec.begin(), vec.end(), not1(MyFunctor4<int>()));
//結果
//3
其中,not1是一個函數適配器,它將MyFunctor4適配成一個返回相反值的仿函數對象,從而統計出vec中小於等於0的元素個數。
3.仿函數的實現
1.重載函數調用運算符
仿函數的實現首先要重載函數調用運算符(operator()),並根據需要定義參數和返回值
template <typename T>
struct MyFunctor1 {
void operator() (T val) {
cout << val << endl;
}
};
template <typename T>
struct MyFunctor2 {
T operator() (T val) {
return val * val;
}
};
其中,MyFunctor1是一個一元仿函數,用於輸出參數值,而MyFunctor2是一個一元仿函數,用於計算參數的平方。
2.使用模板類
仿函數通常是一個模板類,可以支持不同的參數類型
template <typename T>
struct MyFunctor3 {
bool operator() (T val1, T val2) {
return val1 < val2;
}
};
其中,MyFunctor3是一個二元仿函數,用於比較兩個參數的大小。
3.使用函數適配器
仿函數也可以使用函數適配器來實現。例如,使用bind1st函數適配器將一個二元仿函數適配成一個一元仿函數
template <typename T>
struct MyFunctor4 {
bool operator() (T val1, T val2) {
return val1 < val2;
}
};
MyFunctor4<int> myFunctor;
bind1st(myFunctor, 10)(5);
//結果
//false
其中,bind1st將myFunctor適配成一個只有一個參數的仿函數,第一個參數被綁定為10,然後用5調用這個仿函數,返回false。
4.實現一個簡單的加法仿函數
class AddFunctor{
public:
AddFunctor(int n):m_n(n){}
int operator()(int x) const{
return x + m_n;
}
private:
int m_n;
}
在上面的代碼中,我們定義了一個AddFunctor類,它帶有一個整型參數n,表示每次調用要加上的值。在調用運算符()時,返回x加上m_n的結果。
使用AddFunctor類,可以有如下示例
int main()
{
AddFunctor add(3);
int result = add(5); //結果為8
return 0;
}
4.仿函數的應用
在STL中,許多演算法都需要使用仿函數。例如:
- std::sort():對給定的序列進行排序,需要提供一個比較函數,用於指定排序的規則
- std::for_each():對給定的序列中的每個元素執行指定的操作,需要提供一個函數對象,用於指定操作
- std::find_if():在給定的序列中查找符合指定條件的第一個元素,需要提供一個謂詞,用於指定條件
通過使用仿函數,我們可以將演算法和數據結構解耦,是的演算法更加通用和靈活。
5.仿函數的註意事項
在使用仿函數時需要註意以下幾點
- 仿函數的效率問題:由於仿函數在調用時需要進行對象的構造和析構,因此在一些需要頻繁調用的場景中,使用仿函數可能會影響演算法的效率
- 仿函數的線程安全性:由於仿函數中可能會保存狀態,因此在多線程環境下使用時需要註意線程安全性問題
6.仿函數的優化
1.使用constexpr
在C++11中,可以使用constexpr關鍵字來聲明一個函數或變數是常量表達式,可以在編譯時計算。如果一個仿函數的operator()是一個常量表達式,可以使用constexpr來進行優化
template <typename T>
struct MyFunctor1 {
constexpr T operator() (T val) const {
return val * val;
}
};
這樣,當使用MyFunctor1時,如果參數是一個常量表達式,那麼編譯器就可以在編譯時計算出結果,從而提高程式的執行效率。
2.使用inline
仿函數可以使用inline關鍵字來聲明為內聯函數,從而在編譯時將函數體直接嵌入到調用處,避免了函數調用的開銷
template <typename T>
struct MyFunctor2 {
inline T operator() (T val) const {
return val * val;
}
};
3.使用lambda表達式
C++11引入了lambda表達式,可以方便地定義一個匿名仿函數。與普通的仿函數相比,使用lambda表達式可以減少代碼的冗餘,從而提高程式的可讀性和維護性
vector<int> vec{3, -1, 4, -1, 5, -9, 2, -6, 5, 3, -5};
auto count = count_if(vec.begin(), vec.end(), [](int val) { return val > 0; });
其中,lambda表達式[] (int val) { return val > 0; }定義了一個匿名仿函數,用於統計vec中大於0的元素個數。
本文來自博客園,作者:Vergissmeinnicht_z,轉載請註明原文鏈接:https://www.cnblogs.com/Vergissmeinnicht-rj/p/17196619.html