1. 程式人生 > >C語言中函式名的意義深究

C語言中函式名的意義深究

一、通常的函式呼叫

一個通常的函式呼叫的例子:
/* 自行包含標頭檔案 */
void MyFun(int x); /* 此處的宣告也可寫成:void MyFun(int) */
int main(int argc, char* argv[])
{
   MyFun(10); /* 這裡是呼叫MyFun(10) 函式 */
   return(0);
}
void MyFun(int x) /* 這裡定義一個MyFun函式 */
{
   printf("%d\n",x);
}
這個MyFun函式是一個無返回值的函式,它並不“完成”什麼事情。這種呼叫函式的格式你應該是很熟悉的吧!看主函式中呼叫MyFun函式的書寫格式:
   MyFun(10);
我們一開始只是從功能上或者說從數學意義上理解MyFun這個函式,知道MyFun函式名代表的是一個功能(或是說一段程式碼)。直到——學習到函式指標概念時。我才不得不在思考:函式名到底又是什麼東西呢?

(不要以為這是沒有什麼意義的事噢!呵呵,繼續往下看你就知道了。)

二、函式指標變數的宣告

就象某一資料變數的記憶體地址可以儲存在相應的指標變數中一樣,函式的首地址也以儲存在某個函式指標變數裡的。這樣,我就可以通過這個函式指標變數來呼叫所指向的函數了。
在C系列語言中,任何一個變數,總是要先宣告,之後才能使用的。那麼,函式指標變數也應該要先宣告吧?那又是如何來宣告呢?以上面的例子為例,我來宣告一個可以指向MyFun函式的函式指標變數FunP。下面就是宣告FunP變數的方法:
   void (*FunP)(int) ; /* 也可寫成void (*FunP)(int x)*/
你看,整個函式指標變數的宣告格式如同函式MyFun的宣告處一樣,只不過——我們把MyFun改成“(*FunP)”而已,這樣就有了一個能指向MyFun函式的指標FunP了。(當然,這個FunP指標變數也可以指向所有其它具有相同引數及返回值的函數了。)

三、通過函式指標變數呼叫函式

有了FunP指標變數後,我們就可以對它賦值指向MyFun,然後通過FunP來呼叫MyFun函數了。看我如何通過FunP指標變數來呼叫MyFun函式的:
/* 自行包含標頭檔案 */
void MyFun(int x); /* 這個宣告也可寫成:void MyFun( int )*/
void (*FunP)(int ); /*也可宣告成void(*FunP)(int x),但習慣上一般不這樣。 */
int main(int argc, char* argv[])
{
   MyFun(10); /* 這是直接呼叫MyFun函式 */
   FunP = &MyFun; /* 將MyFun函式的地址賦給FunP變數 */
   (*FunP)(20); /* (★)這是通過函式指標變數FunP來呼叫MyFun函式的。 */
}
void MyFun(int x) /* 這裡定義一個MyFun函式 */
{
   printf("%d\n",x);
}
請看(★)行的程式碼及註釋。執行看看。嗯,不錯,程式執行得很好。哦,我的感覺是:MyFun與FunP的型別關係類似於int 與int * 的關係。函式MyFun好像是一個如int的變數(或常量),而FunP則像一個如int * 一樣的指標變數。
   int i,*pi;
   pi = &i; /* 與FunP = &MyFun比較。*/
(你的感覺呢?)呵呵,其實不然……

四、呼叫函式的其它書寫格式

函式指標也可如下使用,來完成同樣的事情:
/* 自行包含標頭檔案 */
void MyFun(int x);
void (*FunP)(int );/* 宣告一個用以指向同樣引數,返回值函式的指標變數。 */
int main(int argc, char* argv[])
{
   MyFun(10); /* 這裡是呼叫MyFun(10)函式 */
   FunP = MyFun; /* 將MyFun函式的地址賦給FunP變數 */
   FunP(20); /* (★)這是通過函式指標變數來呼叫MyFun函式的。*/
   return 0;
}
void MyFun(int x) //這裡定義一個MyFun函式
{
   printf("%d\n",x);
}
我改了(★)行(請自行與之前的程式碼比較一下)。執行試試,啊!一樣地成功。咦?
   FunP = MyFun;
可以這樣將MyFun值同賦值給FunP,難道MyFun與FunP是同一資料型別(即如同的int 與int的關係),而不是如同int 與int*的關係了?(有沒有一點點的糊塗了?)看來與之前的程式碼有點矛盾了,是吧!所以我說嘛!

請容許我暫不給你解釋,繼續看以下幾種情況(這些可都是可以正確執行的程式碼喲!):
程式碼之三:
int main(int argc, char* argv[])
{
   MyFun(10); /* 這裡是呼叫MyFun(10)函式 */
   FunP = &MyFun; /* 將MyFun函式的地址賦給FunP變數 */
   FunP(20); /* 這是通過函式指標變數來呼叫MyFun函式的。 */
   return 0;
}

程式碼之四:
int main(int argc, char* argv[])
{
   MyFun(10); /* 這裡是呼叫MyFun(10)函式 */
   FunP = MyFun; /* 將MyFun函式的地址賦給FunP變數 */
   (*FunP)(20); /*這是通過函式指標變數來呼叫MyFun函式的。*/
   return 0;
}
真的是可以這樣的噢!(哇!真是要暈倒了!)還有吶!看——
int main(int argc, char* argv[])
{
   (*MyFun)(10); /*看,函式名MyFun也可以有這樣的呼叫格式*/
   return 0;
}
你也許第一次見到吧:函式名呼叫也可以是這樣寫的啊!(只不過我們平常沒有這樣書寫罷了。)那麼,這些又說明了什麼呢?

呵呵!依據以往的知識和經驗來推理本篇的“新發現”,我想就連“福爾摩斯”也必定會由此分析並推斷出以下的結論:
1)其實,MyFun的函式名與FunP函式指標都是一樣的,即都是函式指標。MyFun函式名是一個函式指標常量,而FunP是一個函式數指標變數,這是它們的關係。
2)但函式名呼叫如果都得如(*MyFun)(10)這樣,那書寫與讀起來都是不方便和不習慣的。所以C語言的設計者們才會設計成又可允許MyFun(10)這種形式地呼叫(這樣方便多了並與數學中的函式形式一樣,不是嗎?)。
3)為統一起見,FunP函式指標變數也可以FunP(10)的形式來呼叫。
4)賦值時,即可FunP = &MyFun形式,也可FunP = MyFun。

上述程式碼的寫法,隨便你愛怎麼著!請這樣理解吧!這可是有助於你對函式指標的應用嘍!最後 ——

補充說明一點,在函式的宣告處:
   void MyFun(int); /*不能寫成void (*MyFun)(int)。*/
   void (*FunP)(int); /*不能寫成void FunP(int)。*/
(請看註釋)這一點是要注意的。

五、定義某一函式的指標型別

就像自定義資料型別一樣,我們也可以先定義一個函式指標型別,然後再用這個型別來宣告函式指標變數。
我先給你一個自定義資料型別的例子。
typedef int* PINT; /* 為int* 型別定義了一個PINT的別名*/
int main()
{
   int x;
   PINT px = &x; /* 與“int *px=&x;”是等價的。PINT型別其實就是int * 型別 */
   *px = 10; /* px就是int*型別的變數 */
   return 0;
}
根據註釋,應該不難看懂吧!(雖然你可能很少這樣定義使用,但以後學習Win32程式設計時會經常見到的。)下面我們來看一下函式指標型別的定義及使用:(請與上對照!)
/* 自行包含標頭檔案 */
void MyFun(int x); /*此處的宣告也可寫成:void MyFun( int )*/
typedef void (*FunType)(int); /*(★)這樣只是定義一個函式指標型別*/
FunType FunP; /*然後用FunType型別來宣告全域性FunP變數*/
int main(int argc, char* argv[])
{
   FunType FunP; /*函式指標變數當然也是可以是區域性的 ,那就請在這裡聲明瞭。 */
   MyFun(10);
   FunP = &MyFun;
   return 0;
}
void MyFun(int x)
{
   printf("%d\n",x);
}
看(★)行:
首先,在void (*FunType)(int)前加了一個typedef 。這樣只是定義一個名為FunType函式指標型別,而不是一個FunType變數。
然後,“FunType FunP;”這句就如“PINT px;”一樣地宣告一個FunP變數。

其它相同。整個程式完成了相同的事。這樣做法的好處是:
有了FunType型別後,我們就可以同樣地、很方便地用FunType型別來宣告多個同類型的函式指標變量了。如下:
   FunType FunP2;
   FunType FunP3;
   /* . . . */

六、函式指標作為某個函式的引數

既然函式指標變數是一個變數,當然也可以作為某個函式的引數來使用的。所以,你還應知道函式指標是如何作為某個函式的引數來傳遞使用的。

給你一個例項:
要求:我要設計一個CallMyFun函式,這個函式可以通過引數中的函式指標值不同來分別呼叫MyFun1、MyFun2、MyFun3這三個函式(注:這三個函式的定義格式應相同)。
實現:程式碼如下:
/* 自行包含標頭檔案 */
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int ); /* ②. 定義一個函式指標型別FunType,與①函式型別一致 */
void CallMyFun(FunType fp,int x);
int main(int argc, char* argv[])
{
   CallMyFun(MyFun1,10); /* ⑤. 通過CallMyFun函式分別呼叫三個不同的函式 */
   CallMyFun(MyFun2,20);
   CallMyFun(MyFun3,30);
}
void CallMyFun(FunType fp,int x) /* ③. 引數fp的型別是FunType。*/
{
   fp(x);/* ④. 通過fp的指標執行傳遞進來的函式,注意fp所指的函式是有一個引數的。 */
}
void MyFun1(int x) /* ①. 這是個有一個引數的函式,以下兩個函式也相同。 */
{
   printf("函式MyFun1中輸出:%d\n",x);
}
void MyFun2(int x)
{
   printf("函式MyFun2中輸出:%d\n",x);
}
void MyFun3(int x)
{
   printf("函式MyFun3中輸出:%d\n",x);
}
輸出結果:略分析:看我寫的註釋。你可按我註釋的①②③④⑤順序自行分析。