天天看點

c 函數指針

函數指針是什麼?

先來看函數調用是怎麼回事。一個函數占用一段連續記憶體。當調用一個函數時,實際上是跳轉到函數入口位址,執行函數體的代碼,完成後傳回。如何找到對應的入口位址?這是由函數名來标記的,實際上,函數名就是函數的入口位址。

函數指針是一種特殊類型的指針,它指向一個函數的入口位址。

注意:除了void類型指針是無類型的指針外,其他所有指針都是有對應類型的,例如int *pint、struct studentdata *psdata等,隻有指明了指針所指的資料類型,編譯器才能為指針配置設定或預計配置設定相應大小的存儲空間,指針的算術運算如pint++等才是有意義的。是以,定義了某種類型的指針之後,除非使用強制類型轉換,那麼它隻能指向相應資料類型的變量或常量,不同類型的指針或資料之間不可混用。是以指針的類型實際上是一種身份标志的作用。

函數指針如何表明自己的身份呢?為了避免混亂,必須也要作出相應規定,不同函數的函數指針不能混用。例如,int func1(int arg11, char arg12)與int func2(char arg)的函數指針就不能混用,要定義可以指向func1的函數指針應該這樣:

int (*pfunc1)(int, char) = func1;

定義可以指向func2的函數指針則該如下:

int (*pfunc2)(char) = func2;

從函數指針的定義可以看出,函數指針的類型實際上是由函數簽名決定的。函數簽名就象是函數的身份證,一個函數的函數簽名是獨一無二的,具有相同函數簽名的函數實際上就是同一函數。函數簽名包括函數名、函數形參類型的有序清單和函數傳回值類型。

一個函數指針的定義規定了它隻能指向特定類型的函數。如果兩個函數的形參清單和傳回值類型相同,隻有函數名和函數體不同,則可以使用相同類型的函數指針。例如,如果還有一個函數int func3(char arg),則上面定義的可以指向函數func2的函數指針也可以用于指向func3,即:

pfunc2 = func3;

再使用pfunc2(char ARG)就可以調用函數func3,這時指令計數器(PC)指向函數入口,從此開始執行函數體代碼。

注意:對函數指針進行算術運算也是沒有意義的。

如何使用函數指針?

①定義合适類型的函數指針變量;

int (*pfunc)(int, int);

②給函數指針變量指派,使它指向某個函數入口;

int example(int, int);

pfunc = example;

或者:pfunc = &example;

③使用函數指針來調用相應的函數;

retval = pfunc(10, 16);

或者:retval = (*pfunc)(10, 16);

上面兩句都與retval = example(10, 16);等價。

了解:一個指針變量p實際上也和普通的變量一樣,要占存儲空間(通常與平台的虛拟位址一樣寬),也有其自身的存儲位址&p;不同的是,在指針變量 p的值有特殊的意義,它是另外一個變量或常量的位址值,也就是說,在位址為&p的存儲單元上存放着另外一個資料的位址。是以,*p實際上是将p看作它指向的資料的位址來使用,*操作符是引用相應位址中的資料,也就是對位址為p的存儲單元中存放的資料進行操作。

一個函數指針變量則更為特殊。比如上面的例子,pfunc變量本身的值是函數example()的入口位址。是以pfunc可以代替其所指函數的函數名來使用。至于*pfunc,如果按照上面的了解,它實際上是位址pfunc的内容,也即函數example()的入口位址的内容,就有點含糊了。不過,從另一方面來了解,如果使用pfunc = &example來初始化pfunc,則*pfunc == *(&example) == example,又與pfunc等價。是以,就有了兩種使用函數指針來調用相應函數的形式。

值得注意的是,不可用*pfunc來對pfunc的值初始化。即*pfunc = example的寫法是錯誤的。

為什麼要使用函數指針?

前面介紹了函數指針的基本知識和使用規範。下面介紹函數指針的實際用途。不過首先要對前面的知識再做一個補充,因為下面的應用很可能用到這一特性。前面指出,除函數名之外的函數簽名内容(函數傳回值類型和形參清單)決定了函數指針的類型。實際上還有一種特殊的或說通用的函數指針,在定義這類函數指針時,隻需要指定函數傳回值類型,而留白形參清單,這樣就可以指向傳回值類型相同的所有函數。例如:

int (*pfunc)();

這樣定義的pfunc就可以指向前面提到的func1和func2,因為他們都傳回整型值。

注意: int (*pfunc)()與int (*pfunc)(void)不是一回事,後者不允許接受任何參數。

函數指針最常見的三個用途是:

①作為參數傳遞給其他函數。

這樣可以把多個函數用一個函數體封裝起來,得到一個具有多個函數功能的新函數,根據傳遞的函數指針變量值的不同,執行不同的函數功能。這是函數嵌套調用難以實作的。參數的傳遞可以由程式員設定,也可以由使用者輸入讀取,是以具有較大的靈活性和互動性。

另外還可以用于回調函數。使用void配合,還可以将對不同資料類型的資料進行相同處理的多個函數封裝為一個函數,增強函數的生命力。

②用于散轉程式。

這種程式首先建立一個函數表(實際上是一個函數指針數組),表中存放了各個函數的入口位址(或函數名),根據條件的設定來查表選擇執行相應的函數。這樣也可以将多個函數封裝為一個函數或者程式,散轉分支條件可以由程式員設定,也可以由使用者輸入讀取,甚至是外設的某種特定狀态(這種狀态可以是不受人為控制的)。

③實作C的面向對象的類的封裝。

C語言中的struct與C++中的class有很大不同,除了預設的成員屬性外(struct的成員預設為public的,可随意使用,而class成員預設為private的),struct還很難實作類成員函數的封裝。struct的成員一般都是資料成員,而非函數成員。是以,為了在C語言中,為某個struct定義一套自己的函數對結構資料成員進行操作,可以在struct結構體中增加函數指針變量成員,在初始化時使它指向特定函數即可。

應用舉例:

①假設定義了四個函數:add(int, int)、sub(int, int)、mul(int, int)、div(int, int),可以将其封裝為一個四則運算電腦函數:

double calculator(int x, int y, int (*pfunc)(int, int)) {

double result;

result = pfunc(x, y);

return result;

}

又例如,在一個連結清單查詢程式中,要通過比較節點的特征值來查詢節點,不同類型的資料的比較方式不一樣,整型等可以直接比較,字元串卻要用專門的字元串操作函數,為了使代碼可重用性更高,可以使用一個比較函數來代替各種不同資料類型的直接比較代碼,同時,比較函數也必然是資料類型相關的,是以要使用void 指針和函數指針來轉換為類型無關的比較函數,根據相應的資料類型,調用相應的函數(傳遞相應的函數指針)。一個執行個體是:

int (*compare)(void const *, void const *);

這個函數指針可以接受任意類型的資料的指針參數,同時傳回int值作為比較結果标志。一個比較整型資料的比較函數是:

int compare_ints(void const *a, void const *b) {

if( *(int *)a == *(int *)b )

return 0;

else

return 1;

}

②散轉程式。通過一個轉移表(函數指針數組)來實作。還是上面定義的四個四則運算函數,可以建立這樣一個轉移表(注意初始化該轉移表的語句前面應有add等相應函數原型聲明或定義):

double (*calculator[])(int, int) = {

add, sub, mul, div

};

這樣,calculator[0] == add, calculator[1] == sub, ...

使用result = calculator[oper](x, y);就可以代替下面整個switch語句:

switch( oper ) {

case 0: result = add(x, y); break;

case 1: result = sub(x, y); break;

...

}

③C的面向對象化。一個對象包括資料和對資料的操作。C語言中的struct隻有資料成員,是以要增加一些“僞資料成員”即函數指針來實作對資料的操作。例如:

#ifndef C_Class

#define C_Class struct

#endif

C_Class student{

C_Class student *student_this

char name;

int height;

int gender;

int classnum;

...

void (*Oper)( C_Class student *student_this );

...

}

原文位址:http://blog.chinaunix.net/u/26710/showart_209517.html

照上面的我自己寫了個酷點的^_^

typedef struct {

char *name;

Function *func;

char *doc;

} COMMAND;

COMMAND commands[] = {

{ "cd", com_cd, "Change to directory DIR" },

{ "delete", com_delete, "Delete FILE" },

{ "help", com_help, "Display this text" },

{ "?", com_help, "Synonym for `help'" },

{ "list", com_list, "List files in DIR" },

{ "ls", com_list, "Synonym for `list'" },

{ "pwd", com_pwd, "Print the current working directory" },

{ "quit", com_quit, "Quit using Fileman" },

{ "rename", com_rename, "Rename FILE to NEWNAME" },

{ "stat", com_stat, "Print out statistics on FILE" },

{ "view", com_view, "View the contents of FILE" },

{ (char *)NULL, (Function *)NULL, (char *)NULL }

};

使用的時候:

COMMAND *command;

(*(command->func)) (word)