什麼是函數指針

部分 什麼是函數指針

一個指針變量,可以指向一個 int類型變量,可以指向一個常量字符串,可以指向一個指針變量或者一個函數。那麼,什麼是函數指針,他有什麼作用?我們如何使用他 

1.1 函數指針概念

指針函數是指向函數的指針,是變量。 C在編譯時,每一個函數都有一個入口地址,該入口地址就是函數指針所指向的地址。有了指向函數的指針變量後,可用該指針變量調用函數,就如同用指針變量可引用其他類型變量一樣,在這些概念上是一致的。
來看一個這樣一個聲明:
void (*f) ( );
 但由於有括號存在,首先執行的是和 *結合,所以f 是一個指針;接下來執行 ( ),表明f 指向一個函數,而且這個函數不返回任何值。
可以得出這個結論:f是一個指向不接受參數且不返回任何值的函數的指針,簡稱函數指針 (pointer to function)

1.2 函數指針與指針函數,數組指針與指針數組

例如:
函數指針: void (*fun) ( );
指針函數: int *func ();
數組指針: int (*pArray) [10];
指針數組: int *pt[10];
函數指針與指針函數的區別:
1. 函數指針是指針變量,而指針函數本質上是函數。重點在於末尾兩個字是指針還是函數。
2. 函數指針是指針指向函數,指針函數是函數返回指針。
函數指針與數組指針的共同點:
          可以看出,函數指針和數組指針的聲明,指針聲明符 *和標識符都在()內。因爲()的優先級最高。

1.3 函數指針的作用

1.調用函數
2.做函數的參數

第二部分 函數指針的初始化及使用

雖然函數指針指向的是函數,不過函數也有特定類型的,函數的特定類型與返回值類型,形參列表確定,與函數名沒有任何關係。對函數指針初始化時可以採用相同類型函數的函數名或函數指針 (當然還有零指針常量)

2.1 初始化函數指針

          例如:void (*func) (int a);
                    void test (int a);
          對於以上的聲明,我們可以對 func進行如下初始化:
Func = test;
Func = &test;
一般地,我們可以這樣地初始化,事實上, func = *test;也是可以的,至少在vs2010中測試通過了。
          C語言規定函數名會被轉換爲指向這個函數的指針,除非這個函數名作爲  & 操作符或sizeof操作符的操作數 (注意:函數名用於sizeof的操作數是非法的)也就是說f = test; test被自動轉換爲&test,而 f = &test;中已經顯示使用了&test,所以 test就不會再發生轉換了。因此直接引用函數名等效於在函數名上應用  & 運算符,兩種方法都會得到指向該函數的指針。

2.2 函數名究竟是什麼

          爲什麼func = *test是對的?來看以下例子:
#include <stdio.h>

int test(void );

int main(void )
{
    printf( " test=%p\n",test);
    printf( "&test=%p\n",&test);
    printf( "*test=%p\n",*test);
    return 0;
}

int test(void )
{
    printf( "test invoked!");
    return 0;
}
 

運行結果是:


原來他們都表示同一個地址,當然可以調用函數啦。首先來看函數名 test,它與數組名類似(注意:只是類似 ),是一個符號用來標識一個函數的入口地址,在使用中函數名會被轉換爲指向這個函數的指針,指針的值就是函數的入口地址, &test在前面已經說了:顯示獲取函數的地址。對於 *test,可以認爲由於test已經被轉換成了函數指針 指向這個函數,所以*test就是取這個指針所指向的函數名,而又根據函數名會被轉換指向該函數的指針的規則,這個函數也轉變成了一個指針,所以 *test最終也是一個指向函數test的指針。對它們採用 %p格式項輸出,都會得到以 16進制數表示的函數test的入口地址。注意函數的地址在編譯期是未知的,而是在鏈接時確定的。
通過函數指針調用函數
f = test;
f ( );
(*f) ( );   //指針兩側的括號非常重要,表示先對 f解引用,然後再調用相應的函數

ANSI C標準將 f ( )認爲是(*f)( ) 的簡寫形式,並且推薦使用 f ( )形式,因爲它更符合函數調用的邏輯。 要注意的是:如果指向函數的指針沒有初始化,或者具有 0(零指針常量 ),那麼該指針不能在函數調用中使用。只有當指針已經初始化,或被賦值後指向某個函數才能安全地用來調用函數。

2.3typedef可以用於定義函數指針類型

【語法】 typedef可以用於定義函數指針類型
【例如】 typedef  int (*ptf)(int,double);

要說明一下,對於typedef  int (*ptf) (double*,char); 注意不要用#define的思維來看待typedef,如果用 #define的思維來看的話會以爲 (*ptf)(double*, char)int的別名,但這樣的別名看起來好像又不是合法的名字,於是會處於迷茫狀態。實際上,上面的語句把 ptf定義爲一種函數指針類型的別名,它和函數指針類型 int (*) (double*, char);等價,也就是說ptf現在也是一種類型。

          因此,他可以當作指針函數的返回值,如:
ptf  function(double);

下面分析一個函數指針和指針函數結合的例子:

void (*signal (int sig, void (*func) (intsiga)) ) ( int siga );

看上去確實有些複雜,讓我們來分析。現在要分析的是 signal,因爲緊鄰 signal的是優先級最高的括號,首先與括號結合,所以 signal爲一個函數,括號內爲 signal的兩個形參,一個爲 int型,一個爲指向函數的指針。接下來從向左看, *表示指向某對象的指針,它所處的位置表明它是 signal的返回值類型,現在可以把已經分析過的 signal整體去掉,得到 void (*) ( int siga ),很清晰了吧。又是一個函數指針,這個指針與 signal形參表中的第二個參數類型一樣,都是指向接受一個 int型形參且不返回任何值的函數的指針。同樣地,用 typedef可以將這個聲明簡化:

typedef void (*p_sig) (int);

p_sig signal(int sig, p_sig func);

這個signal 函數是C語言的庫函數,在 signal.h中定義,用來處理系統中產生的信號,是 UNIX/Linux編程中經常用到的一個函數,所以在此單獨拿出來講解。

2.4 函數指針數組

還有一種較爲常用的關於函數指針的用法——函數指針數組。假設現在有一個文件處理程序,通過一個菜單按鈕來選擇相應的操作 (打開文件,讀文件,寫文件,關閉文件 )。這些操作都實現爲函數且類型相同,分別爲:

void open( );

void read( );

void write( );

void close( );

現在定義一個函數指針類型的別名

PF typedef void (*PF) ( );

把以上4種操作取地址放入一個數組中,得到:

PF file_options[ ] = {

                 &open,

                 &read,

                 &write,

                 &close

};

這個數組中的元素都是指向不接受參數且不返回任何值的函數的指針,因此這是一個函數指針數組。接下來,定義一個函數指針類型的指針 action並初始化爲函數指針數組的第一個元素: PF* action = file_options;,如果不好理解,可以類比一下 int ia[4] = {0, 1, 2, 3}; int *ip = ia;,這裏PF 相當於int,這樣應該比較好懂了。通過對指針 action進行下標操作可以調用數組中的任一操作,如: action[2]( )會調用 write操作,以此類推。在實際中,指針 action可以和鼠標或者其他 GUI對象相關聯,以達到相應的目的。

2.5 複雜的函數指針聲明

【右左法則】 從未定義的變量名開始閱讀聲明,先向右看,然後向左看。當遇到括號時就調轉閱讀的方向。括號內的所有內容都分析完畢就跳出括號。這樣一直繼續下去,直到整個聲明都被分析完畢。
來分析一個的例子: int * (* (*fp) (int) ) [10]; 
閱讀步驟:
1.從未定義的變量名開始閱讀  --------------------------------------------fp
2.往右看,什麼也沒有,遇到了 ),因此往左看,遇到一個 * ------ 一個指向某對象的指針
3.跳出括號,遇到了(int) ----------------------------------- 一個帶一個int 參數的函數
4.向左看,發現一個* --------------------------------------- (函數)返回一個指向某對象的指針
5.跳出括號,向右看,遇到 [10] ------------------------------  一個10元素的數組
6.向左看,發現一個* --------------------------------------- 一個指向某對象指針
7.向左看,發現int -----------------------------------------int類型
所以 fp是指向函數的指針,該函數返回一個指向數組的指針,此數組有 10int* 型的元素。
第三部分 總結
1. 指針函數是指向函數的指針,是變量
2. ANSI C標準將f ( )認爲是(*f)( )的簡寫形式,並且推薦使用f ( )形式,因爲它更符合函數調用的邏輯。
3.  關於函數指針的加減運算沒有意義。
4. 在使用中函數名會被轉換爲指向這個函數的指針,指針的值就是函數的入口地址,函數的地址在編譯期是未知的,而是在鏈接時確定的。
5.  對於複雜的函數指針聲明,可以用右左法則來進行分析。
發佈了17 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章