周立功教授數(shù)年之心血之作《程序設(shè)計(jì)與數(shù)據(jù)結(jié)構(gòu)》以及《面向AMetal框架與接口的編程(上)》。書(shū)本內(nèi)容公開(kāi)后,在電子行業(yè)掀起一片學(xué)習(xí)熱潮。經(jīng)周立功教授授權(quán),本公眾號(hào)特對(duì)《程序設(shè)計(jì)與數(shù)據(jù)結(jié)構(gòu)》一書(shū)內(nèi)容進(jìn)行連載,愿共勉之。
第二章為程序設(shè)計(jì)技術(shù),本文為2.1.1 函數(shù)指針和2.1.2指針函數(shù)。
>>>>2.1函數(shù)指針與指針函數(shù)
>>>2.1.1函數(shù)指針
變量的指針指向的是一塊數(shù)據(jù),指針指向不同的變量,則取到的是不同的數(shù)據(jù)。而經(jīng)過(guò)編譯后的函數(shù)都是一段代碼,系統(tǒng)隨即為相應(yīng)的代碼分配一段存儲(chǔ)空間,而存儲(chǔ)這段代碼的起始地址(又稱為入口地址)就是這個(gè)函數(shù)的指針,即跳轉(zhuǎn)到某一個(gè)地址單元的代碼處去執(zhí)行。函數(shù)指針指向的是一段代碼(即函數(shù)),指針指向不同的函數(shù),則具有不同的行為。
因?yàn)楹瘮?shù)名是一個(gè)常量地址,所以只要將函數(shù)的地址賦給函數(shù)指針即可調(diào)用相應(yīng)的函數(shù)。如同數(shù)組名一樣,我們用的是函數(shù)本身的名字,它會(huì)返回函數(shù)的地址。當(dāng)一個(gè)函數(shù)名出現(xiàn)在表達(dá)式中時(shí),編譯器就會(huì)將其轉(zhuǎn)換為一個(gè)指針,即類(lèi)似于數(shù)組變量名的行為,隱式地取出了它的地址。即函數(shù)名直接對(duì)應(yīng)于函數(shù)生成的指令代碼在內(nèi)存中的地址,因此函數(shù)名可以直接賦值給指向函數(shù)的指針。既然函數(shù)指針的值可以改變,那么就可以使用同一個(gè)函數(shù)指針指向不同的函數(shù)。如果有以下定義:
int (*pf)(int); // pf函數(shù)指針的類(lèi)型是什么?
C語(yǔ)言的發(fā)明者K&R是這樣解釋的,“因?yàn)?是前置運(yùn)算符,它的優(yōu)先級(jí)低于(),為了讓連接正確地進(jìn)行,有必要加上括號(hào)?!边@未免有些牽強(qiáng)附會(huì)了,解釋來(lái)解釋去反而將人搞暈了。因?yàn)槁暶髦械?、()、[]都不是運(yùn)算符,而運(yùn)算符的優(yōu)先順序在語(yǔ)法規(guī)則中是在其它地方定義的。其詳解如下:
int(*pf)(int a); // pf是指向…的指針
int(*pf)(int a); // pf是指向…的函數(shù)(參數(shù)為int)的指針
int (*pf)(int a); // pf是指向返回int的函數(shù)(參數(shù)為int)的指針
即pf是一個(gè)指向返回int的函數(shù)的指針,它所指向的函數(shù)接受一個(gè)int類(lèi)型的參數(shù)。 “int (*)(int)”類(lèi)型名被解釋為指向返回int函數(shù)(參數(shù)為int)的指針類(lèi)型。如果在該定義前添加typedef,比如:
typedef int (*pf)(int a);
未添加typedef前,pf是一個(gè)函數(shù)指針變量;而添加typedef后,pf就變成了函數(shù)指針類(lèi)型,習(xí)慣的寫(xiě)法是類(lèi)型名pf大寫(xiě)為PF。比如:
typedef int (*PF)(int a);
與其它類(lèi)型的聲明不同,函數(shù)指針的聲明要求使用typedef關(guān)鍵字。另外,函數(shù)指針的聲明與函數(shù)原型的唯一不同是函數(shù)名用(*PF)代替了,“*”在此處表示“指向類(lèi)型名為PF的函數(shù)”。顯然,有了PF類(lèi)型即可定義函數(shù)指針變量pf1、pf2。比如:
PF pf1, pf2;
雖然此聲明等價(jià)于:
int (*pf1)(int a);
int (*pf2)(int a);
但這種寫(xiě)法更難理解。既然函數(shù)指針變量是一個(gè)變量,那么它的值就是可以改變的,因此可以使用同一個(gè)函數(shù)指針變量指向不同的函數(shù)。使用函數(shù)指針必須完成以下工作:
●獲取函數(shù)的地址,比如,pf = add,pf = sub;
●聲明一個(gè)函數(shù)指針,比如,“int (*pf)(int, int);”;
●使用函數(shù)指針來(lái)調(diào)用函數(shù),比如,pf(5, 8),(*pf)(5, 8)。為何pf與(*pf)等價(jià)呢?
●一種說(shuō)法是,由于pf是函數(shù)指針,假設(shè)pf指向add()函數(shù),則*pf就是函數(shù)add,因此使用(*pf)()調(diào)用函數(shù)。雖然這種格式不好看,但它給出了強(qiáng)有力的提示——代碼正在使用函數(shù)指針調(diào)用函數(shù)。
●另一種說(shuō)法是,由于函數(shù)名是指向函數(shù)的指針,那么指向函數(shù)的指針的行為應(yīng)該與函數(shù)名相似,因此使用pf()調(diào)用函數(shù)。因?yàn)檫@種調(diào)用方式既簡(jiǎn)單又優(yōu)雅,所以人們更愿意選擇——說(shuō)明人類(lèi)追隨美好感受的內(nèi)心是無(wú)法抗拒的。
雖然它們?cè)谶壿嬌匣ハ鄾_突,但不同的流派有不同的觀點(diǎn),且容忍邏輯上無(wú)法自圓其說(shuō)的觀點(diǎn),正是人類(lèi)思維活動(dòng)的特點(diǎn)。
在一個(gè)袖珍計(jì)算器中,經(jīng)常需要用到加減乘除開(kāi)方等各種各樣的計(jì)算,雖然其調(diào)用方法都是一樣,但在運(yùn)行中需要根據(jù)具體情況決定選擇調(diào)用支持某一算法的函數(shù)。如果使用如圖 2.1(a)所示的直接調(diào)用方式,則勢(shì)必形成了依賴關(guān)系結(jié)構(gòu),策略會(huì)受到細(xì)節(jié)改變的影響,當(dāng)使用如圖 2.1(b)所示的函數(shù)指針接口倒置(或反轉(zhuǎn))了這種依賴關(guān)系結(jié)構(gòu)時(shí),則使得細(xì)節(jié)和策略都依賴于函數(shù)指針接口,斷開(kāi)了不想要的直接依賴。
當(dāng)將直接訪問(wèn)抽象成函數(shù)指針倒置(或反轉(zhuǎn))了依賴的關(guān)系時(shí),高層模塊不再依賴于低層模塊。高層模塊依賴于抽象,即一個(gè)函數(shù)指針形式的接口,同時(shí)細(xì)節(jié)也依賴于抽象,pf()實(shí)現(xiàn)了這個(gè)接口,即兩者都依賴于函數(shù)指針接口。在C語(yǔ)言中,通常用函數(shù)指針來(lái)實(shí)現(xiàn)DIP(倒置依賴關(guān)系),斷開(kāi)不想要的直接依賴。既可以通過(guò)函數(shù)指針調(diào)用服務(wù)(被調(diào)用代碼),服務(wù)也可以通過(guò)函數(shù)指針回調(diào)用戶函數(shù)。都是一樣,但在運(yùn)行中需要根據(jù)具體情況決定選擇調(diào)用支持某一算法的函數(shù)。如果使用如圖 2.1(a)所示的直接調(diào)用方式,則勢(shì)必形成了依賴關(guān)系結(jié)構(gòu),策略會(huì)受到細(xì)節(jié)改變的影響,當(dāng)使用如圖 2.1(b)所示的函數(shù)指針接口倒置(或反轉(zhuǎn))了這種依賴關(guān)系結(jié)構(gòu)時(shí),則使得細(xì)節(jié)和策略都依賴于函數(shù)指針接口,斷開(kāi)了不想要的直接依賴。
圖 2.1 使用函數(shù)指針倒置依賴關(guān)系
函數(shù)指針是程序員經(jīng)常忽視的一個(gè)強(qiáng)大的語(yǔ)言能力,不僅使代碼更靈活可測(cè),而且對(duì)消除重復(fù)條件邏輯有很大的幫助,同時(shí)還可以使調(diào)用者免于在編譯時(shí)或鏈接時(shí)依賴于某個(gè)特定的函數(shù),其極大地好處是減少了C語(yǔ)言模塊之間的耦合。但函數(shù)指針的使用是有條件的,如果主調(diào)函數(shù)與被調(diào)函數(shù)之間的調(diào)用關(guān)系永遠(yuǎn)不會(huì)發(fā)生改變,則采用直接調(diào)用方式是最簡(jiǎn)單的,在這種情況下,模塊之間耦合是合理的,不僅代碼簡(jiǎn)單直截了當(dāng),而且開(kāi)銷(xiāo)也是最小的。如果需要在運(yùn)行時(shí)使用一個(gè)或多個(gè)函數(shù)指針調(diào)用某一函數(shù),則使用函數(shù)指針是最佳的選擇,通常將其稱之為動(dòng)態(tài)接口,其范例程序詳見(jiàn)程序清單 2.1。
程序清單2.1通過(guò)函數(shù)指針調(diào)用函數(shù)范例程序(1)
1#include
2 int add(int a, int b)
3 {
4 printf("addition function ");
5 return a + b;
6 }
7
8 int sub(int a, int b)
9 {
10 printf("subtration function ");
11 return a - b;
12 }
13
14 int main(void)
15 {
16 int (*pf)(int, int);
17
18 pf = add;
19 printf("addition result:%d ", pf(5, 8));
20 pf = sub;
21 printf("subtration result:%d ", pf(8, 5));
22 return 0;
23 }
由于任何數(shù)據(jù)類(lèi)型的指針都可以給void指針變量賦值,且函數(shù)指針的本質(zhì)就是一個(gè)地址,因此可以利用這一特性,將pf定義為一個(gè)void *類(lèi)型指針,那么任何指針都可以賦值給void *類(lèi)型指針變量。其調(diào)用方式如下:
void * pf = add;
printf("addition result:%d ", ((int (*)(int, int)) pf)(5, 8));
在函數(shù)指針的使用過(guò)程中,指針的值表示程序?qū)⒁D(zhuǎn)的地址,指針的類(lèi)型表示程序的調(diào)用方式。在使用函數(shù)指針調(diào)用函數(shù)時(shí),務(wù)必保證調(diào)用的函數(shù)類(lèi)型與指向的函數(shù)類(lèi)型完全相同,所以必須將void *類(lèi)型轉(zhuǎn)換為((int (*)(int, int)) pf)來(lái)使用,其類(lèi)型為“int (*)(int, int)”。
>>>2.1.2指針函數(shù)
實(shí)際上,指針變量的用途非常廣泛,指針不僅可以作為函數(shù)的參數(shù),而且指針還可以作為函數(shù)的返回值。當(dāng)函數(shù)的返回值是指針時(shí),則這個(gè)函數(shù)就是指針函數(shù)。當(dāng)給定指向兩個(gè)整數(shù)的指針時(shí),如程序清單 2.2所示的函數(shù)返回指向兩個(gè)整數(shù)中較大數(shù)的指針。當(dāng)調(diào)用max時(shí),用指向兩個(gè)int類(lèi)型變量的指針作為參數(shù),且將結(jié)果存儲(chǔ)在一個(gè)指針變量中,其中,max函數(shù)返回的指針是作為實(shí)參傳入的兩個(gè)指針的一個(gè)。
程序清單2.2求最大值函數(shù)(指針作為函數(shù)的返回值)
1 #include
2 int *max(int *p1, int *p2)
3 {
4 if(*p1 > *p2)
5 return p1;
6 else
7 return p2;
8 }
9
10 int main(int argc, char *argv[])
11 {
12 int *p, a, b;
13 a = 1; b = 2;
14 p = max(&a, &b);
15 printf("%d ", *p);
16 return 0;
17 }
當(dāng)然,函數(shù)也可以返回字符串,它返回的實(shí)際是字符串的地址,但一定要注意如何返回合法的地址。既可以返回是靜態(tài)的字符串地址,也可以在堆上分配字符串的內(nèi)存,然后返回其地址。注意,不要返回局部字符串的地址,因?yàn)閮?nèi)存有可能被別的棧幀覆寫(xiě)。
下面我們?cè)賮?lái)看一看,指針函數(shù)與函數(shù)指針變量有什么區(qū)別?如果有以下定義:
int *pf(int *, int); // int *(int *, int)類(lèi)型
int (*pf)(int, int); // int (*)(int, int)類(lèi)型
雖然兩者之間只差一個(gè)括號(hào),但表示的意義卻截然不同。函數(shù)指針變量的本質(zhì)是一個(gè)指針變量,其指向的是一個(gè)函數(shù);指針函數(shù)的本質(zhì)是一個(gè)函數(shù),即將pf聲明為一個(gè)函數(shù),它接受2個(gè)參數(shù),其中一個(gè)是int *,另一個(gè)是int,其返回值是一個(gè)int類(lèi)型的指針。
在指針函數(shù)中,還有一類(lèi)這樣的函數(shù),其返回值是指向函數(shù)的指針。對(duì)于初學(xué)者,別說(shuō)寫(xiě)出這樣的函數(shù)聲明,就是看到這樣的寫(xiě)法也是一頭霧水。比如,下面這樣的語(yǔ)句:
int (*ff (int))(int, int); // ff是一個(gè)函數(shù)
int (* ff (int))(int, int); // ff是一個(gè)指針函數(shù),其返回值是指針
int(* ff (int))(int, int); //指針指向的是一個(gè)函數(shù)
這種寫(xiě)法確實(shí)讓人非常難懂,以至于一些初學(xué)者產(chǎn)生誤解,認(rèn)為寫(xiě)出別人看不懂的代碼才能顯示自己水平高。而事實(shí)上恰好相反,能否寫(xiě)出通俗易懂的代碼是衡量程序員是否優(yōu)秀的標(biāo)準(zhǔn)。當(dāng)使用typedef后,則PF就成為了一個(gè)函數(shù)指針類(lèi)型。即:
typedef int (*PF)(int, int);
有了這個(gè)類(lèi)型,那么上述函數(shù)的聲明就變得簡(jiǎn)單多了。即:
PF ff(int);
下面將以程序清單 2.3為例,說(shuō)明用函數(shù)指針作為函數(shù)返回值的用法。當(dāng)用戶分別輸入d、x和p時(shí),求數(shù)組的最大值、最小值和平均值。
程序清單2.3求最值與平均值范例程序
1 #include
2 #include
3 double getMin(double *dbData, int iSize) //求最小值
4 {
5 double dbMin;
6
7 assert((dbData != NULL) && (iSize > 0));
8 dbMin = dbData[0];
9 for (int i = 1; i < iSize; i++){?
10 if (dbMin > dbData[i]){
11 dbMin = dbData[i];
12 }
13 }
14 return dbMin;
15 }
16
17 double getMax(double *dbData, int iSize) //求最大值
18 {
19 double dbMax;
20
21 assert((dbData != NULL) && (iSize > 0));
22 dbMax = dbData[0];
23 for (int i = 1; i < iSize; i++){
24 if (dbMax < dbData[i]){?
25 dbMax = dbData[i];
26 }
27 }
28 return dbMax;
29 }
30
31 double getAverage(double *dbData, int iSize) //求平均值
32 {
33 double dbSum = 0;
34
35 assert((dbData != NULL) && (iSize > 0));
36 for (int i = 0; i < iSize; i++){
37 dbSum += dbData[i];
38 }
39 return dbSum/iSize;
40 }
41
42 double unKnown(double *dbData, int iSize) //未知算法
43 {
44 return 0;
45 }
46
47 typede double (*PF)(double *dbData, int iSize); //定義函數(shù)指針類(lèi)型
48 PF getOperation(char c) //根據(jù)字符得到操作類(lèi)型,返回函數(shù)指針
49 {
50 switch (c){
51 case 'd':
52 return getMax;
53 case 'x':
54 return getMin;
55 case 'p':
56 return getAverage;
57 default:
58 return unKnown;
59 }
60 }
61
62 int main(void)
63 {
64 double dbData[] = {3.1415926, 1.4142, -0.5, 999, -313, 365};
65 int iSize = sizeof(dbData) / sizeof(dbData[0]);
66 char c;
67
68 printf("Please input the Operation : ");
69 c = getchar();
70 PF pf = getOperation(c);
71 printf("result is %lf ", pf(dbData, iSize));
72 return 0;
73 }
前4個(gè)函數(shù)分別實(shí)現(xiàn)了求最大值、最小值、平均值和未知算法,getOperation()根據(jù)輸入字符得到的返回值是以函數(shù)指針的形式返回的,從pf(dbData, iSize)可以看出是通過(guò)這個(gè)指針調(diào)用函數(shù)的。注意,指針函數(shù)可以返回新的內(nèi)存地址、全局變量的地址和靜態(tài)變量的地址,但不能返回局部變量的地址,因?yàn)楹瘮?shù)結(jié)束后,在函數(shù)內(nèi)部的聲明的局部變量的聲明周期已經(jīng)結(jié)束,內(nèi)存將自動(dòng)放棄。顯然,在主調(diào)函數(shù)中訪問(wèn)這個(gè)指針?biāo)赶虻臄?shù)據(jù),將會(huì)產(chǎn)生不可預(yù)料的結(jié)果。
想學(xué)更多嵌入式課程,請(qǐng)掃描下圖二維碼,馬上學(xué)習(xí)!
-
指針
+關(guān)注
關(guān)注
1文章
475瀏覽量
70457 -
數(shù)據(jù)結(jié)構(gòu)
+關(guān)注
關(guān)注
3文章
568瀏覽量
40030 -
程序設(shè)計(jì)
+關(guān)注
關(guān)注
3文章
261瀏覽量
30317 -
函數(shù)指針
+關(guān)注
關(guān)注
2文章
55瀏覽量
3760 -
指針函數(shù)
+關(guān)注
關(guān)注
0文章
10瀏覽量
2728 -
ametal
+關(guān)注
關(guān)注
2文章
24瀏覽量
11374
原文標(biāo)題:周立功:函數(shù)指針與指針函數(shù)的應(yīng)用
文章出處:【微信號(hào):ZLG_zhiyuan,微信公眾號(hào):ZLG致遠(yuǎn)電子】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論