宣告與函式、函式指標

whatday發表於2013-08-03

概述

  
在很多情況下,尤其是讀別人所寫程式碼的時候,對 C語言宣告的理解能力變得非常重要,而C語言本身的凝練簡約也使得C語言的宣告常常會令人感到非常困惑,因此,在這裡我用一篇的內容來集中闡述一下這個問題。

  問題:宣告與函式

  有一段程式儲存在起始地址為 0的一段記憶體上,如果我們想要呼叫這段程式,請問該如何去做?

  答案

  答案是 (*(void (*)( ) )0)( )。看起來確實令人頭大,那好,讓我們知難而上,從兩個不同的途徑來詳細分析這個問題。

  答案分析:從尾到頭

  首先,最基本的函式宣告: void function (paramList);

  最基本的函式呼叫: function(paramList);

  鑑於問題中的函式沒有引數,函式呼叫可簡化為 function();

   其次,根據問題描述,可以知道 0是這個函式的入口地址,也就是說,0是一個函式的指標。使用函式指標的函式宣告形式是:void (*pFunction)(),相應的呼叫形式是: (*pFunction)(),則問題中的函式呼叫可以寫作:(*0)( )。

  第三,大家知道,函式指標變數不能是一個常數,因此上式中的 0必須要被轉化為函式指標。

  我們先來研究一下,對於使用函式指標的函式:比如 void (*pFunction)( ),函式指標變數的原型是什麼?這個問題很簡單,pFunction函式指標原型是( void (*)( ) ),即去掉變數名,清晰起見,整個加上()號。

  所以將 0強制轉換為一個返回值為void,引數為空的函式指標如下:( void (*)( ) )。

   OK,結合2)和3)的分析,結果出來了,那就是:(*(void (*)( ) )0)( ) 。

  答案分析:從頭到尾理解答案

   (void (*)( )) ,是一個返回值為void,引數為空的函式指標原型。
   (void (*)( ))0,把0轉變成一個返回值為void,引數為空的函式指標,指標指向的地址為0.
   *(void (*)( ))0,前面加上*表示整個是一個返回值為void的函式的名字
   (*(void (*)( ))0)( ),這當然就是一個函式了。

  我們可以使用 typedef清晰宣告如下:

   typedef void (*pFun)( );

  這樣函式變為 (*(pFun)0 )( );

  問題:三個宣告的分析

  對宣告進行分析,最根本的方法還是類比替換法,從那些最基本的宣告上進行類比,簡化,從而進行理解,下面通過分析三個例子,來具體闡述如何使用這種方法。

# 1:int* (*a[5])(int, char*);

   首先看到識別符號名 a,"[]"優先順序大於"*",a與"[5]"先結合。所以a是一個陣列,這個陣列有5個元素,每一個元素都是一個指標,指標指向"(int, char*)",很明顯,指向的是一個函式,這個函式引數是"int, char*",返回值是"int*"。OK,結束了一個。:)

# 2:void (*b[10]) (void (*)());

   b是一個陣列,這個陣列有10個元素,每一個元素都是一個指標,指標指向一個函式,函式引數是"void (*)()"【注10】,返回值是"void"。完畢!

  注意:這個引數又是一個指標,指向一個函式,函式引數為空,返回值是 "void"。

# 3. doube(*)() (*pa)[9];

   pa是一個指標,指標指向一個陣列,這個陣列有9個元素,每一個元素都是"doube(*)()"(也即一個函式指標,指向一個函式,這個函式的引數為空,返回值是"double")。

C語言中的函式指標

函式在記憶體中有一個物理位置,而這個位置是可以賦給一個指標的。一零點函式的地址就是該函式的入口點。因此,函式指標可被用來呼叫一個函式。函式的地址是用不帶任何括號或引數的函式名來得到的。(這很類似於陣列地址的得到方法,即,在只有陣列名而無下標是就得到陣列地址。)

怎樣說明一個函式指標變數呢 ?
為了說明一個變數 fn_pointer 的型別是"返回值為 int 的函式指標", 你可以使用下面的說明語句:
int (*fn_pointer) ();
為了讓編譯器能正確地解釋這句語句, *fn_pointer 必須用括號圍起來。若漏了這對括號, 則:
int *fn_pointer ();
的意思完全不同了。fn_pointer 將是一個函式名, 其返回值為 int 型別的指標。

函式指標變數  

  在C語言中規定,一個函式總是佔用一段連續的記憶體區,   而函式名就是該函式所佔記憶體區的首地址。   我們可以把函式的這個首地址 ( 或稱入口地址 ) 賦予一個指標變數,   使該指標變數指向該函式。然後通過指標變數就可以找到並呼叫這個函式。   我們把這種指向函式的指標變數稱為 " 函式指標變數 "  
函式指標變數定義的一般形式為:  
型別說明符  (* 指標變數名 )();  
其中 " 型別說明符 " 表示被指函式的返回值的型別。 "(*  指標變數名 )" 表示 "*" 後面的變數是定義的指標變數。   最後的空括號表示指標變數所指的是一個函式。  
例如:  int (*pf)(); 
表示 pf 是一個指向函式入口的指標變數,該函式的返回值 ( 函式值 ) 是整型。  
下面通過例子來說明用指標形式實現對函式呼叫的方法。  
int max(int a,int b){ 
if(a>b)return a; 
else return b; 

main(){ 
int max(int a,int b); 
int(*pmax)(); 
int x,y,z; 
pmax=max; 
printf("input two numbers:/n"); 
scanf("%d%d",&x,&y); 
z=(*pmax)(x,y); 
printf("maxmum=%d",z); 

  從上述程式可以看出用,函式指標變數形式呼叫函式的步驟如下:

1.  先定義函式指標變數,如後一程式中第 9  int (*pmax)(); 定義 pmax 為函式指標變數。  

2. 
把被調函式的入口地址 ( 函式名 ) 賦予該函式指標變數,如程式中第 11  pmax=max; 

3. 
用函式指標變數形式呼叫函式,如程式第 14  z=(*pmax)(x,y);  呼叫函式的一般形式為:  (* 指標變數名 ) ( 實參表 ) 使用函式指標變數還應注意以下兩點:  

a. 
函式指標變數不能進行算術運算,這是與陣列指標變數不同的。陣列指標變數加減一個整數可使指標移動指向後面或前面的陣列元素,而函式指標的移動是毫無意義的。  

b. 
函式呼叫中 "(* 指標變數名 )" 的兩邊的括號不可少,其中的 * 不應該理解為求值運算,在此處它只是一種表示符號。  

指標型函式  

前面我們介紹過,所謂函式型別是指函式返回值的型別。   在C語言中允許一個函式的返回值是一個指標 ( 即地址 )   這種返回指標值的函式稱為指標型函式。  
定義指標型函式的一般形式為:   
型別說明符  * 函式名 ( 形參表 )  
{  
...... /*
函式體 */ 
}  
其中函式名之前加了 "*" 號表明這是一個指標型函式,即返回值是一個指標。型別說明符表示了返回的指標值所指向的資料型別。  
如:  
int *ap(int x,int y) 

...... /*
函式體 */ 

  表示 ap 是一個返回指標值的指標型函式,   它返回的指標指向一個整型變數。下例中定義了一個指標型函式  day_name ,它的返回值指向一個字串。該函式中定義了一個靜態指標陣列 name name  陣列初始化賦值為八個字串,分別表示各個星期名及出錯提示。形參 n 表示與星期名所對應的整數。在主函式中,   把輸入的整數 i 作為實參,   printf 語句中呼叫 day_name 函式並把 i 值傳送給形參  n day_name 函式中的 return 語句包含一個條件表示式,  n  值若大於 7 或小於 1 則把 name[0]  指標返回主函式輸出出錯提示字串 "Illegal day" 。否則返回主函式輸出對應的星期名。主函式中的第 7 行是個條件語句,其語義是,如輸入為負數 (i<0) 則中止程式執行退出程式。 exit 是一個庫函式, exit(1) 表示發生錯誤後退出程式,  exit(0) 表示正常退出。  

  應該特別注意的是函式指標變數和指標型函式這兩者在寫法和意義上的區別。如 int(*p)() int *p() 是兩個完全不同的量。 int(*p)() 是一個變數說明,說明 是一個指向函式入口的指標變數,該函式的返回值是整型量, (*p) 的兩邊的括號不能少。 int *p()  則不是變數說明而是函式說明,說明 p 是一個指標型函式,其返回值是一個指向整型量的指標, *p 兩邊沒有括號。作為函式說明,   在括號內最好寫入形式引數,這樣便於與變數說明區別。   對於指標型函式定義, int *p() 只是函式頭部分,一般還應該有函式體部分。  
main(){ 
int i; 
char *day_name(int n);  
printf("input Day No:/n"); 
scanf("%d",&i); 
if(i<0) exit(1); 
printf("Day No:%2d-->%s/n",i,day_name(i)); 

char *day_n

ame(int n){ 
static char *name[]={ "Illegal day", 
"Monday", 
"Tuesday", 
"Wednesday", 
"Thursday", 
"Friday", 
"Saturday", 
"Sunday"}; 
return((n<1||n>7) ? name[0] : name[n]); 

  本程式是通過指標函式,輸入一個 1 7 之間的整數,   輸出對應的星期名。指標陣列的說明與使用一個陣列的元素值為指標則是指標陣列。   指標陣列是一組有序的指標的集合。   指標陣列的所有元素都必須是具有相同儲存型別和指向相同資料型別的指標變數。  
  指標陣列說明的一般形式為:   型別說明符 * 陣列名 [ 陣列長度 ]  
  其中型別說明符為指標值所指向的變數的型別。例如:  int *pa[3]  表示 pa 是一個指標陣列,它有三個陣列元素,   每個元素值都是一個指標,指向整型變數。通常可用一個指標陣列來指向一個二維陣列。   指標陣列中的每個元素被賦予二維陣列每一行的首地址,   因此也可理解為指向一個一維陣列。圖 6—6 表示了這種關係。  
int a[3][3]={1,2,3,4,5,6,7,8,9}; 
int *pa[3]={a[0],a[1],a[2]}; 
int *p=a[0]; 
main(){ 
int i; 
for(i=0;i<3;i++) 
printf("%d,%d,%d/n",a[i][2-i],*a[i],*(*(a+i)+i)); 
for(i=0;i<3;i++) 
printf("%d,%d,%d/n",*pa[i],p[i],*(p+i)); 

  本例程式中, pa 是一個指標陣列,三個元素分別指向二維陣列 a 的各行。然後用迴圈語句輸出指定的陣列元素。其中 *a[i] 表示 i 0 列元素值; *(*(a+i)+i) 表示 i i 列的元素值; *pa[i] 表示 i 0 列元素值;由於 p a[0] 相同,故 p[i] 表示 0 i 列的值; *(p+i) 表示 0 i 列的值。讀者可仔細領會元素值的各種不同的表示方法。   應該注意指標陣列和二維陣列指標變數的區別。   這兩者雖然都可用來表示二維陣列,但是其表示方法和意義是不同的

相關文章