7.10 函數指針(基礎知識)

與數據項相似,函數也有地址。函數的地址是存儲其機器代碼的內存的開始地址。通常,這些地址對用戶而言,既不重要,也沒有什麼用處,但對程序而言,卻很有用。
例如,可以編寫將另一個函數的地址作爲參數的函數。這樣第一個函數將能夠找到第二個函數並運行它。與直接調用另一個函數相比,這種方法很笨拙,但它允許在不同的時間傳遞不同函數的地址,這意味着可以在不同的時間使用不同的函數。
首先通過一個例子來闡釋這一過程。假設要設計一個名爲estimate()的函數,估算編寫指定行數的代碼所需的時間,並且希望不同的程序員都將使用該函數。對於所有的用戶來說,estimate()中的一部分代碼都是相同的,但該函數允許每個程序員提供自己的算法來估算時間。爲實現這種目標,採用的機制是,將程序員要使用的算法函數的地址傳遞給estimate()。爲此,必須能夠完成下面的工作:
 獲取函數的地址;
 聲明一個函數指針;
 使用函數指針來調用函數。

1. 獲取函數的地址

只要使用函數名(後面不跟參數)即可獲取函數的地址。也就是說,如果think()是一個函數,則think就是該函數的地址。要將函數作爲參數進行傳遞,必須傳遞函數名。一定要區分傳遞的是函數地址還是函數的返回值:

process(think);		//passes address of think() to process()
thought(think());		//passes return value of think() to thought()

process()調用使得process()函數能夠在其內部調用think()函數。thought()調用首先調用think()函數,然後將think()的返回值傳遞給thought()函數。

2. 聲明函數指針

聲明指向函數的指針時,必須指定指針指向的函數類型。這意味着聲明應指定函數的返回類型以及函數的特徵標(參數列表)。也就是說,聲明應像函數原型那樣指出有關函數的信息。
例如,假設Pam leCoder編寫了一個估算時間的函數,其原型如下:
double pam(int); //prototype
則正確的指針類型聲明如下:

double (*pf)(int);		
//pf points to a function that takes one int argument and that return type double

這與pam()聲明類似,這是將pam替換爲了(*pf)。由於pam是函數,因此(*pf)也是函數。而如果(*pf)是函數,則pf就是函數指針。

提示:通常,要聲明指向特定類型的函數的指針,可以首先編寫這種函數的原型,然後用(*pf)替換函數名。這樣pf就是這類函數的指針。

爲提供正確的運算符優先級,必須在聲明中使用括號將pf括起。括號的優先級比運算符高,因此*pf(int)意味着pf()是一個返回指針的函數,
而(*pf)(int)意味着pf是一個指向函數的指針:

double (*pf)(int);		//pf points to a function that returns double
double *pf(int);		//pf() a function that returns a pointer-to-double

正確的聲明pf後,便可以將相應函數的地址賦給它:

	double pam(int);
	double (*pf)(int);
	pf = pam;		//pf now points to the pam() function

注意,pam()的特徵標和返回類型必須與pf相同。如果不相同,編譯器將拒絕這種賦值。

	double ned(double);
	int ted(int);
	double (*pf)(int);
	pf= ned;			//invalid – mismatched signature
	pf= ted;			//invalid – mismatched return types

現在回過頭來看一下前面提到的estimate()函數。假設要將將要編寫的代碼行數和估算算法(如pam()函數)的地址傳遞給它,則其原型將如下:

void estimate(int lines, double (*pf)(int));

上述聲明指出,第二個參數是一個函數指針,它指向的函數接受一個int參數,並返回一個double值。要讓estimate()使用pam()函數,需要將pam()的地址傳遞給它:

estimate(50,pam);		//function call telling estimate() to use pam()
顯然,使用函數指針時,比較棘手的是編寫原型,而傳遞地址則非常簡單。

3. 使用指針來調用函數

現在進入最後一步,即使用指針來調用被指向的函數。線索來自指針聲明。(*pf)扮演的角色與函數名相同,因此使用(*pf)時,只需要將它看作函數名即可:

double pam(int);
double (*pf)(int);
pf = pam;		//pf now points to the pam() function
double x =pam(4);	//call pam() using the function name
double y = (*pf)(5);	//call pam() using the pointer pf

實際上,C++也允許像使用函數名那樣使用pf:

double y=pf(5);		//also call pam() using the pointer pf

第一種格式雖然不太好看,但它給出了強有力的提示——代碼正在使用函數指針。

歷史與邏輯

*真是非常棒的語法!爲何pf和(pf)等價呢?一種學派認爲,由於pf是函數指針,而pf是函數,因此應將(pf)()用作函數調用。另一種學派認爲,由於函數名是指向該函數的指針,指向函數的指針的行爲應與函數名相似,因此應將pf()用作函數調用使用。C++進行了折衷——這兩種方式都是正確的,或者至少是允許的,雖然他們在邏輯上是互相沖突的。在認爲這種折衷粗糙之前,應該想到,容忍邏輯上無法自圓其說的觀點正是人類思維活動的特點。

By Suki
From 《C++ Primer Plus》
立一個flag 至少兩天整理一次筆記,儘量做到每天一次!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章