時間:2014.06.14
地點:基地
-------------------------------------------------------------------------------
一、函數指針簡介
函數指針指向的是一個函數,而不是一個對象。但函數指針也和其他普通指針一樣,指向特定的函數類型,函數的類型由返回類型和形參類型共同決定,與函數名無關,就像和變量名無關一樣。比如:
bool LengthCompare(const string&,const string& );
上面這個函數的類型即 bool (const string&,const string&),要想聲明一個指向這樣一個函數類型的指針可以這樣:
bool (*pf)(const string&,const string&); //函數指針未初始化
即用一個指針變量名加*替換掉函數名即可。這和聲明普通指針和類型,聲明普通指針時我們也是用一個指針變量名加*放在類型聲明後。在這裏,我們看到pf前面有*,說明pf是一個指針變量名,右側有形參列表,表pf指向的是一個函數,再看左側的返回類型,表明pf函數指針指向的函數返回一個布爾值。這裏聲明一個函數指針時一定記得將 *pf括起來,否則比如這樣:
bool* pf(const string&,const string&);
這將聲明的是一個普通函數,它返回一個bool*
-------------------------------------------------------------------------------
二、函數指針的使用
函數名就像數組名,當我們將函數名最爲一個值使用時,該函數會自動轉換成指針,可直接賦給函數指針。比如下面是等價的:
pf=LengthCompare; //pf指針指向函數LengthCompare
pf=&LengthCompare; //也是一樣的,所以取址符在這裏是可選的
同時,我們還可以使用指向函數的指針直接調用所指向的函數,無需解引用指針,當然解引用也沒錯,下面三種調用方式等價:bool b1=pf("hello","world");
bool b2=(*pf)("hello","world");
bool b3=LngthCompare("hello","world");
總的一點說來:聲明一個函數指針後,給該函數指針變量賦值也用對應類型函數的函數名或對函數名取址都可,調用時,直接用該函數指針變量或對該變量解引用也都可。需要注意的是,函數指針不能發生不同類型之間的轉換,但可賦值nullptr或者0,的整型常量表達式,表示該函數指針沒有指向任何一個函數。注意函數類型的匹配一定是函數返回值和形參類型和個數精確匹配。
說道精確匹配,這裏是要求嚴格一致的精確匹配,當遇到函數重載時,函數指針只能選用精確匹配的那個函數。比如:
void ff(int*);
void ff(unsigned int);
void (*pf1) (unsigned int)=ff/ //這裏pf1指向ff(unsigned int)
-------------------------------------------------------------------------------三、能作爲函數參數的函數指針形參
我們知道,數組是不能作爲函數形參的,函數也一樣,但函數的形參可以是一個指向某個函數的指針,就像函數的形參可以是指向某個數組的指針一樣。也和數組一樣,形參看起來是一個函數類型,但實際上會當成一個指向這樣類型的函數指針。比如:
//下面函數的聲明中第三個參數就爲函數類型,會自動轉換爲指向函數的指針
void Function(const string& s1,const& s2,bool pf(const string&,const string&));
//和上面等價,這裏顯示地將形參定義爲了函數指針
void Function(const string& s1,const string& s2,bool (*pf)(const string &,const string&));
正如我們前面提及的,我們可以直接將函數作爲實參使用,函數名就像數組名一樣,當它傳遞給函數時會自動轉換爲一個指針,比如:Function(s1,s2,LengthCompare);
在上面,我們看到Function的聲明好長,看起來不爽,這是因爲我們在說明一個函數類型時需要給出函數的返回值和函數的形參列表,而爲了說明某種類型,我們在常規場合下可使用typedef來定義類型的別名,在這裏同樣適用。
首先我們可用typedef來定義自己的類型,比如:
typedef bool Func(const string&,const string&); //這裏的Func是一個函數類型
typedef bool(*FuncP)(const string&,const string&); //這裏的FuncP是一個指向函數的指針
啊,typedef的功勞使得這兩句中的Func 和 FuncP是一個類型名,而不是簡單的一個名,但同時,我們還有辦法從變量名中推出類型,比如:
typedef decltype(LengthCompare) Func2; //定義了一個函數類型別名,它和LengthCompare函數的類型一樣
typedef decltype(LengthCompare)* FuncP2; //使用decltype推導出LengthCompare函數的類型,然後加上個* ,於是說明FuncP2是一個指向這種函數類型的指針類型
注意這裏的decltype返回的是函數的類型,因爲我們是推導LengthCompare,它不會將函數類型自動轉換爲函數指針類型,所以還要加上*才能得到指向這個函數類型的指針。現在我們的Function定義可以這樣:
void Function(const string&,const string&,Func);//這裏,Func表示的是函數類型,可編譯器可自動將它轉換爲函數指針,注意只有當Func是一個函數類型時可這樣,簡單的函數名不可以,所以,一定要記得typedef定義函數類型
void Function(const string&,const string&,FuncP2);//FuncP2在這裏是一個顯示的函數指針類型,也是要記得用typedef結合decltype導出一個函數指針的類型,因爲形參是要給出一個類型的嘛?所以typedef在包含有函數指針的函數聲明中騎到了簡化作用
-------------------------------------------------------------------------------四、返回指向函數的指針
我們不僅可以使用函數指針,而且還可以返回函數指針,就像可以返回數組指針那樣。先來討論類型別名的另一種寫法:
using F=int(int*,int); //這裏F是函數類型,不是函數指針類型(啊,函數類型的說明只要給出返回類型,參數列表就OK了)
using FP=int(*)(int*,int);//這裏FP就是一個函數指針類型了
注:我們在聲明一個函數時,不管是參數列表也好還是返回值也好,都對應着一種類型,若是函數指針參數或返回函數指針,那麼它們也對應着要給出類型說明,但直接給出往往顯得難看不美觀,於是我們使用using或typedef定義類型別名。
現在我們知道,形如int(int*,int)是一種函數類型,而形如 int(*)(int*,int)是一種函數指針類型
我們的F是函數類型別名了,FP則是函數指針類型別名。和將函數名傳遞給函數不一樣,函數返回一個函數指針時就不會將函數名轉換爲指針,必須顯式地將返回類型指定爲指針。比如:
FP f1(int); //正確,FP是一個指向函數的指針類型,f1返回指向函數的指針
F f1(int); //錯誤,F是函數類型,f1不能返回一個函數,就是不能返回一個數組一樣
F* f1(int);//正確,顯式地指定了返回類型是指向函數的指針
這樣完全寫也行:
int(*f1(int))(int*,int);
這個看起來比較糾結,從裏往外看,f1有形參列表是一個函數,f1前有*,說明這個f1函數返回一個指針,再看,返回的指針類型本身也有形參列表,因爲是一個指向函數的指針,再看這個函數指針的類型爲 int(int* ,int)。更美觀的我們是應該使用尾置返回類型的方式聲明一個返回函數指針的函數:auto f1(int)->int(*)(int*,int);
牢記,當decltypee作用於某個函數時,返回的是函數類型,而不是函數指針類型,一定要顯式地加上*以表明所需要的返回指針或聲明函數時的函數指針形參類型。
-------------------------------------------------------------------------------
五、練習題
編寫4個函數,它們都分別對兩個int值進行加減乘除,將指向這四個函數的函數指針存放在vector中,調用vector中的每個元素並輸出結果:
#include<iostream>
#include<vector>
using namespace std;
int Func(int a, int b);
int Add(int a, int b)
{
return a + b;
}
int Sub(int a, int b)
{
return a - b;
}
int Multi(int a, int b)
{
return a*b;
}
int Div(int a, int b)
{
return a / b;
}
int main()
{
using FuncType = decltype(Func);
vector<FuncType*> vec{ Add, Sub, Multi, Div }; //記得這裏要加*
int a, b;
cin >> a >> b;
for (auto f : vec)
cout << f(a, b) << endl;
}