函數(shù)指針就是函數(shù)的指針。它是一個指針,指向一個函數(shù)。(即函數(shù)在內存中的起始位置地址)
實際上,所有的函數(shù)名在表達式和初始化中,總是隱式地退化為指針。
例:
int r , (*fp)( ) , func( ) ;
fp= func ; //函數(shù)名退化為指針
r= (*fp)( ) ; //等價于r=fp( ) ;
無論fp是函數(shù)名還是函數(shù)指針,都能正確工作。因為函數(shù)總是通過指針進行調用的!
--
例:
int f(int) ; //函數(shù)聲明
int (*fp)(int) = &f ;//此取地址符是可選的。編譯器就把函數(shù)名當做函數(shù)的入口地址。
int ans ;
//以下三種方式可調用函數(shù)
ans= f(25) ;
ans= (*fp)(25) ;
ans= fp(25) ;
函數(shù)名就是一個函數(shù)指針常量,函數(shù)調用操作符(即一對括號)相當于解引用。
函數(shù)的執(zhí)行過程:
函數(shù)名首先被轉換為一個函數(shù)指針常量,該指針指定函數(shù)在內存中的位置。然后函數(shù)調用操作符調用該函數(shù),執(zhí)行開始于這個地址的代碼。
void fun() { printf("Call fun "); }
int main(void)
{
void(*p)( ) ;
*(int*)&p = (int)fun ;
(*p)() ;
return 0 ;
}
指針的強制類型轉換只不過是改變了編譯器對二進制位的解釋方法罷了。
*(int *)&p = (int)fun ;中的fun是一個函數(shù)地址,被強制轉換為int數(shù)字。 左邊的(int *)&p是把函數(shù)指針p轉換為int型指針。
*(int *)&p = (int)fun ;表示將函數(shù)的入口地址賦值給指針變量p。
(*p)( ) ;表示對函數(shù)的調用。
1,轉移表(轉移表就是一個函數(shù)指針數(shù)組)
即可用來實現(xiàn)“菜單驅動系統(tǒng)”。系統(tǒng)提示用戶從菜單中選擇一個選項,每個選項由不同的函數(shù)提供服務。
【若每個選項包含許多操作,用switch操作,會使程序變得很長,可讀性差。這時可用轉移表的方式】
例:
void(*f[3])(int) = {function1, function2, function3} ; //定義一個轉移表
(*f[choice])( ) ; //根據用戶的選擇來調用相應的函數(shù)
2,回調函數(shù)
(用函數(shù)指針做形參,用戶根據自己的環(huán)境寫個簡單的函數(shù)模塊,傳給回調函數(shù),這樣回調函數(shù)就能在不同的環(huán)境下運行了,提高了模塊的復用性)
【回調函數(shù)實現(xiàn)與環(huán)境無關的核心操作,而把與環(huán)境有關的簡單操作留給用戶完成,在實際運行時回調函數(shù)通過函數(shù)指針調用用戶的函數(shù),這樣其就能適應多種用戶需求】
例:C庫函數(shù)中的快速排序函數(shù)
void qsort(void *base, int nelem, size_t width, int (*fcmp)(void*, void*) );//fcmp為函數(shù)指針。
這樣,由用戶實現(xiàn)fcmp的比較功能(用戶可根據需要,寫整型值的比較、浮點值的比較,字符串的比較 等)這樣qsort函數(shù)就能適應各種不同的類型值的排序。
使用函數(shù)指針的好處在于:
可以將實現(xiàn)同一功能的多個模塊統(tǒng)一起來標識,這樣一來更容易后期維護,系統(tǒng)結構更加清晰?;蛘邭w納為:便于分層設計、利于系統(tǒng)抽象、降低耦合度以及使接口與實現(xiàn)分開。
函數(shù)指針數(shù)組的指針
例:char *(*(pf)[3])(char )
這個指針指向一個數(shù)組,這個數(shù)組里存儲的都是指向函數(shù)的指針。它們指向的是一種返回值為字符指針,參數(shù)為字符指針的函數(shù)。
從外到內,層層剝開,先找核心,再向右看
找到核心變量后,從右向左讀。
簡單的例子:
int *f() ; // f: 返回指向int型的指針
步驟:
1)找標識符f:讀作”f是…”
2)向右看,發(fā)現(xiàn)”()”讀作”f是返回…的函數(shù)”
3)向右看沒有什么,向左看,發(fā)現(xiàn)*,讀作”f是返回指向…的指針的函數(shù)”
4)繼續(xù)向左看,發(fā)現(xiàn)int,讀作”f是返回指向int型的指針的函數(shù)”int (pf)() ; // pf是一個指針——指向返回值為int型的函數(shù)
1)標識符pf,讀作“pf是…”
2)向右看,發(fā)現(xiàn)),向左看,發(fā)現(xiàn)\,讀作 “pf是指向…的指針”3)向右看,發(fā)現(xiàn)”()”,讀作“pf是指向返回…的函數(shù)的指針”4)向右看,沒有,向左看發(fā)現(xiàn)int,讀作”pf是指向返回int型的函數(shù)的指針
復雜指針的舉例:void (*b[10]) (void (*)());
首先找到核心:b是一個數(shù)組,這個數(shù)組有10個元素,每一個元素都是一個指針,指針指向一個函數(shù),函數(shù)參數(shù)是“void(*)()”【 這個參數(shù)又是一個指針,指向一個函數(shù),函數(shù)參數(shù)為空,返回值是“void”】 返回值是“void”。完畢!
“建立一個類型別名的方法很簡單,在傳統(tǒng)的變量聲明表達式里用類型名替代變量名,然后把關鍵字typedef加在該語句的開頭”。
舉例:
1、
void (*b[10]) (void (*)());
typedef void (*pfv)(); //先把上式的后半部分用typedef換掉
typedef void (*pf_taking_pfv)(pfv); //再把前半部分用typedef換掉
pf_taking_pfv b[10]; //整個用typedef換掉
跟void (*b[10]) (void (*)());的效果一樣!
2、
doube(*)() (*pa)[9];
typedef double(*PF)(); //先替換前半部分
typedef PF (*PA)[9]; //再替換后半部分
PA pa;
跟doube(*)() (*pa)[9];的效果一樣!
1、我們?yōu)槭裁葱枰羔槪?/strong>
因為我們要訪問一個對象,我們要改變一個對象。要訪問一個對象,必須先知道它在哪,也就是它在內存中的地址。地址就是指針值。
所以我們有
函數(shù)指針:某塊函數(shù)代碼的起始位置(地址)
指針的指針:因為我要訪問(或改變)某個變量,只是這個變量是指針罷了
2、為什么要有指針類型?
因為我們訪問的對象一般占據多個字節(jié),而代表它們的地址值只是其中最低字節(jié)的地址,我們要完整的訪問對象,必須知道它們總共占據了多少字節(jié)。而指針類型即向我們提供這樣的信息。
注意:一個指針變量向我們提供了三種信息:
①一個首字節(jié)的地址值(起始位置)
②這個指針的作用范圍(步長)
③對這個范圍中的數(shù)位的解釋規(guī)則(解碼規(guī)則)
【編譯器就像一個以步數(shù)測量距離的盲人。故你要告訴它從哪開始走,走多少步,并且告訴他如何理解這里面的信息】
3、強制類型轉換的真相?
學過匯編的人都知道,什么指針,什么char,int,double,什么數(shù)組指針,函數(shù)指針,指針的指針,在內存中都是一串二進制數(shù)罷了。只是我們賦予了這些二進制數(shù)不同的含義,給它們設定一些不同的解釋規(guī)則,讓它們代表不同的事物。
(比如1000 0000 0000 0001 是內存中某4個字節(jié)中的內容,如果我們認為它是int型,則按int型的規(guī)則解釋它為-2^31+ 1;如果我們認為它是unsigned int ,則被解釋為2^31+ 1;當然我們也可把它解釋為一個地址值,數(shù)組的地址,函數(shù)的地址,指針的地址等)
如果我們使用匯編編程,我們必須根據上下文需要,用大腦記住這個值當前的代表含義,當程序中有很多這樣的值時,我們必須分別記清它們當前代表的含義。這樣極易導致誤用,所以編譯器出現(xiàn)了,讓它來幫我們記住這些值當前表示的含義。
當我們想讓某個值換一種解釋的方案時,就用強制類型轉換的方式來告訴編譯器,編譯器則修改解釋它的規(guī)則,而內存中的二進制數(shù)位是不變的(涉及浮點型的強制轉換除外,它們是舍掉一些位,保留一些位)。
更多建議: