如果類重載了函數調用運算符,則可以像使用函數一樣使用該類的對象,因為這樣的類同時也能存儲狀態,所以與普通函數相比它們更加靈活。
struct absInt{
int operator()(int val) const{
return val < 0 ? -val : val;
}
};
上面的類只定義了一種操作:函數調用運算符,它負責接受一個int類型的形參,然后返回該實參的絕對值。
int i = -42;
absInt absObj;
int ui = absObj(i); //i被傳遞給absObj.operator()
即使 absObj
是一個對象而非函數,也能調用該對象,調用對象實際上是在運行重載的調用運算符。
函數調用運算符必須是成員函數,一個類可以定義多個不同版本的調用運算符,相互之間在參數數量和參數類型上應該有所區別。
如果類定義了調用運算符,則該類的對象稱作函數對象,因為可以調用這種對象,所以這些對象的行為像函數一樣。
含有狀態的函數對象類
函數對象除了 operator()
之外也可以包含其他成員。
class PrintString{
public:
PrintString(ostream &o = cout,char c = ' '):os(o),sep(c){ }
void operator()(const string &s)const{os<<s<<sep;}
private:
ostream &os;
char sep;
};
使用:
PrintString printer;
printer(s); //cout中打印s,后面跟一個空格
PrintString errors(cerr,'\n');
errors(s); //cerr中打印s,后面跟一個換行符
函數對象通常是作為泛型算法的實參:
for_each(vs.begin(),vs.end(),PrintString(cerr,"\n"));
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 &a,const string &b)
{return a.size() < b.size();}
};
使用上面的類重寫 stable_sort
:
stable_sort(words.begin(),words.end(),ShorterString());
表示 lambda 及相應捕獲行為的類
auto wc = find_if(words.begin(),words.end(),[sz](const string &a)
{return a.size() > = sz;})
該 lambda 表達式產生的類將形如:
class SizeComp
{
SizeComp(size_t n):sz(n) { }
bool operator()(const string &s)const {return s.size() >= sz;}
private:
size_t sz;
};
auto wc = find_if(words.begin(),words.end(),SizeComp(sz))
標准庫定義的函數對象
標准庫定義了一組表示算術運算符、關系運算符和邏輯運算符的類,每個類分別定義了一個執行命名操作的調用運算符。
plus<int> intAdd; //可執行int加法的函數對
negate<int> intNegate; //可對int值取反的函數對象
int sum = intAdd(10,20); //sum=30
sum = intAdd(10,intNegate(10)); //sum=0
定義在 functional
頭文件中的函數對象:
在算法中使用標准庫函數對象
// 傳入一個臨時函數對象用於執行兩個 string 對象的比較運算
sort(svec.begin(),svec.end(),greater<string>());
標准庫規定其函數對象對於指針同樣適用:
vector<string *> nameTable; //指針的vector
//錯誤:nameTable 中的指針彼此之間沒有任何關系,所以 < 將產生未定義的行為
sort(nameTable.begin(),nameTable.end(),[](string *a,string *b){return a < b;}) ;
//正確,標准庫規定指針的 less 定義是良好的
sort(nameTable.begin(),nameTable.end(),less<string*>());
可調用對象與 function
C++ 中的可調用對象包括:函數、函數指針、lambda
表達式、bind
創建的對象以及重載了函數調用運算符的類。
可調用對象也有類型,labmda 有自己唯一的未命名類型,函數及函數指針的類型由其返回值類型和實參類型決定。兩個不同類型的可調用對象卻可能共享同一種調用形式,調用形式指明了返回類型以及傳遞給調用的實參類型,一種調用形式對應一個函數類型:
int (int,int) //是一個函數類型,它接受兩個int,返回一個int
不同的類型可能具有相同類型的調用方式
int add(int i,int j){return i + j;}
auto mod = [](int i,int j){return i % j;}
struct divide{
int operator()(int denminator,int divisor){
return denminator / divisor;
}
};
盡管這些可調用對象對其參數執行了不同的算術運算,盡管它們的類型各不相同,但是共享一種調用形式:
int (int,int)
構建實現不同運算的函數表:
map<string,int(*)(int,int)> binops;
binops.insert({"+",add});
binops.insert({"%",mod}); //錯誤,mod不是函數指針
標准庫 function 類型
function
定義在 functional
頭文件中:
function
是一個模板,創建具體的 function
類型時需要提供額外的信息。
function<int(int,int)>
function<int(int,int)> f1 = add; //函數指針
function<int(int,int)> f2 = divide; //函數對象類的對象
function<int(int,int)> f3 = [](int i,int j){return i * j;} //lambda
//調用
cout<<f1(4,2)<<endl;
cout<<f2(4,2)<<endl;
cout<<f3(4,2)<<endl;
使用 function
重新定義上面的函數表:
map<string,function<int(int,int)>> binops =
{
{"+",add}, //函數指針
{"-",std::minus<int>()}, //標准庫函數對象
{"/",divide}, //自定義函數對象
{"*",[](int i,int j){return i * j;}}, //未命名lambda表達式
{"%",mod}, //命名lambda表達式
};
調用:
binops["+"](10,5);
binops["-"](10,5);
binops["/"](10,5);
binops["*"](10,5);
binops["%"](10,5);
重載的函數與 function
不能直接將重載函數的名字存入 function 類型的對象中。
int add(int i,int j){return i + j;}
Sales_data add(const Sales_data&,const Sales_data&);
map<string,funciton<int(int,int)>> binops;
binops.insert({"+",add}); //錯誤,不能區分是哪個add
解決上面問題的有效途徑是存儲函數指針而非函數名字:
int (*fp) (int,int) = add;
binops.insert("add",fp); //正確,fp指向正確的add版本
同樣,也可以使用 lambda 來指定希望使用的 add 版本:
binops.insert({"+"},[](int a,in b){return add(a,b);});