精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

周立功手把手教你學嵌入式編程:函數指針與指針函數的應用

AGk5_ZLG_zhiyua ? 來源:ZLG致遠電子 ? 作者:佚名 ? 2017-08-29 11:36 ? 次閱讀

周立功教授數年之心血之作《程序設計與數據結構》以及面向AMetal框架與接口編程(上)。書本內容公開后,在電子行業掀起一片學習熱潮。經周立功教授授權,本公眾號特對《程序設計與數據結構》一書內容進行連載,愿共勉之。

第二章為程序設計技術,本文為2.1.1 函數指針和2.1.2指針函數。

>>>>2.1函數指針與指針函數

>>>2.1.1函數指針

變量的指針指向的是一塊數據,指針指向不同的變量,則取到的是不同的數據。而經過編譯后的函數都是一段代碼,系統隨即為相應的代碼分配一段存儲空間,而存儲這段代碼的起始地址(又稱為入口地址)就是這個函數的指針,即跳轉到某一個地址單元的代碼處去執行。函數指針指向的是一段代碼(即函數),指針指向不同的函數,則具有不同的行為。

因為函數名是一個常量地址,所以只要將函數的地址賦給函數指針即可調用相應的函數。如同數組名一樣,我們用的是函數本身的名字,它會返回函數的地址。當一個函數名出現在表達式中時,編譯器就會將其轉換為一個指針,即類似于數組變量名的行為,隱式地取出了它的地址。即函數名直接對應于函數生成的指令代碼在內存中的地址,因此函數名可以直接賦值給指向函數的指針。既然函數指針的值可以改變,那么就可以使用同一個函數指針指向不同的函數。如果有以下定義:

int (*pf)(int); // pf函數指針的類型是什么?

C語言的發明者K&R是這樣解釋的,“因為*是前置運算符,它的優先級低于(),為了讓連接正確地進行,有必要加上括號。”這未免有些牽強附會了,解釋來解釋去反而將人搞暈了。因為聲明中的*、()、[]都不是運算符,而運算符的優先順序在語法規則中是在其它地方定義的。其詳解如下:

int(*pf)(int a); // pf是指向…的指針

int(*pf)(int a); // pf是指向…的函數(參數為int)的指針

int (*pf)(int a); // pf是指向返回int的函數(參數為int)的指針

即pf是一個指向返回int的函數的指針,它所指向的函數接受一個int類型的參數。 “int (*)(int)”類型名被解釋為指向返回int函數(參數為int)的指針類型。如果在該定義前添加typedef,比如:

typedef int (*pf)(int a);

未添加typedef前,pf是一個函數指針變量;而添加typedef后,pf就變成了函數指針類型,習慣的寫法是類型名pf大寫為PF。比如:

typedef int (*PF)(int a);

與其它類型的聲明不同,函數指針的聲明要求使用typedef關鍵字。另外,函數指針的聲明與函數原型的唯一不同是函數名用(*PF)代替了,“*”在此處表示“指向類型名為PF的函數”。顯然,有了PF類型即可定義函數指針變量pf1、pf2。比如:

PF pf1, pf2;

雖然此聲明等價于:

int (*pf1)(int a);

int (*pf2)(int a);

但這種寫法更難理解。既然函數指針變量是一個變量,那么它的值就是可以改變的,因此可以使用同一個函數指針變量指向不同的函數。使用函數指針必須完成以下工作:

●獲取函數的地址,比如,pf = add,pf = sub

●聲明一個函數指針,比如,“int (*pf)(int, int);”;

●使用函數指針來調用函數,比如,pf(5, 8),(*pf)(5, 8)。為何pf與(*pf)等價呢?

●一種說法是,由于pf是函數指針,假設pf指向add()函數,則*pf就是函數add,因此使用(*pf)()調用函數。雖然這種格式不好看,但它給出了強有力的提示——代碼正在使用函數指針調用函數。

●另一種說法是,由于函數名是指向函數的指針,那么指向函數的指針的行為應該與函數名相似,因此使用pf()調用函數。因為這種調用方式既簡單又優雅,所以人們更愿意選擇——說明人類追隨美好感受的內心是無法抗拒的。

雖然它們在邏輯上互相沖突,但不同的流派有不同的觀點,且容忍邏輯上無法自圓其說的觀點,正是人類思維活動的特點。

在一個袖珍計算器中,經常需要用到加減乘除開方等各種各樣的計算,雖然其調用方法都是一樣,但在運行中需要根據具體情況決定選擇調用支持某一算法的函數。如果使用如圖 2.1(a)所示的直接調用方式,則勢必形成了依賴關系結構,策略會受到細節改變的影響,當使用如圖 2.1(b)所示的函數指針接口倒置(或反轉)了這種依賴關系結構時,則使得細節和策略都依賴于函數指針接口,斷開了不想要的直接依賴。

當將直接訪問抽象成函數指針倒置(或反轉)了依賴的關系時,高層模塊不再依賴于低層模塊。高層模塊依賴于抽象,即一個函數指針形式的接口,同時細節也依賴于抽象,pf()實現了這個接口,即兩者都依賴于函數指針接口。在C語言中,通常用函數指針來實現DIP(倒置依賴關系),斷開不想要的直接依賴。既可以通過函數指針調用服務(被調用代碼),服務也可以通過函數指針回調用戶函數。都是一樣,但在運行中需要根據具體情況決定選擇調用支持某一算法的函數。如果使用如圖 2.1(a)所示的直接調用方式,則勢必形成了依賴關系結構,策略會受到細節改變的影響,當使用如圖 2.1(b)所示的函數指針接口倒置(或反轉)了這種依賴關系結構時,則使得細節和策略都依賴于函數指針接口,斷開了不想要的直接依賴。

圖 2.1 使用函數指針倒置依賴關系

函數指針是程序員經常忽視的一個強大的語言能力,不僅使代碼更靈活可測,而且對消除重復條件邏輯有很大的幫助,同時還可以使調用者免于在編譯時或鏈接時依賴于某個特定的函數,其極大地好處是減少了C語言模塊之間的耦合。但函數指針的使用是有條件的,如果主調函數與被調函數之間的調用關系永遠不會發生改變,則采用直接調用方式是最簡單的,在這種情況下,模塊之間耦合是合理的,不僅代碼簡單直截了當,而且開銷也是最小的。如果需要在運行時使用一個或多個函數指針調用某一函數,則使用函數指針是最佳的選擇,通常將其稱之為動態接口,其范例程序詳見程序清單 2.1。

程序清單2.1通過函數指針調用函數范例程序(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 }

由于任何數據類型的指針都可以給void指針變量賦值,且函數指針的本質就是一個地址,因此可以利用這一特性,將pf定義為一個void *類型指針,那么任何指針都可以賦值給void *類型指針變量。其調用方式如下:

void * pf = add;

printf("addition result:%d ", ((int (*)(int, int)) pf)(5, 8));

在函數指針的使用過程中,指針的值表示程序將要跳轉的地址,指針的類型表示程序的調用方式。在使用函數指針調用函數時,務必保證調用的函數類型與指向的函數類型完全相同,所以必須將void *類型轉換為((int (*)(int, int)) pf)來使用,其類型為“int (*)(int, int)”。

>>>2.1.2指針函數

實際上,指針變量的用途非常廣泛,指針不僅可以作為函數的參數,而且指針還可以作為函數的返回值。當函數的返回值是指針時,則這個函數就是指針函數。當給定指向兩個整數的指針時,如程序清單 2.2所示的函數返回指向兩個整數中較大數的指針。當調用max時,用指向兩個int類型變量的指針作為參數,且將結果存儲在一個指針變量中,其中,max函數返回的指針是作為實參傳入的兩個指針的一個。

程序清單2.2求最大值函數(指針作為函數的返回值)

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 }

當然,函數也可以返回字符串,它返回的實際是字符串的地址,但一定要注意如何返回合法的地址。既可以返回是靜態的字符串地址,也可以在堆上分配字符串的內存,然后返回其地址。注意,不要返回局部字符串的地址,因為內存有可能被別的棧幀覆寫。

下面我們再來看一看,指針函數與函數指針變量有什么區別?如果有以下定義:

int *pf(int *, int); // int *(int *, int)類型

int (*pf)(int, int); // int (*)(int, int)類型

雖然兩者之間只差一個括號,但表示的意義卻截然不同。函數指針變量的本質是一個指針變量,其指向的是一個函數;指針函數的本質是一個函數,即將pf聲明為一個函數,它接受2個參數,其中一個是int *,另一個是int,其返回值是一個int類型的指針。

在指針函數中,還有一類這樣的函數,其返回值是指向函數的指針。對于初學者,別說寫出這樣的函數聲明,就是看到這樣的寫法也是一頭霧水。比如,下面這樣的語句:

int (*ff (int))(int, int); // ff是一個函數

int (* ff (int))(int, int); // ff是一個指針函數,其返回值是指針

int(* ff (int))(int, int); //指針指向的是一個函數

這種寫法確實讓人非常難懂,以至于一些初學者產生誤解,認為寫出別人看不懂的代碼才能顯示自己水平高。而事實上恰好相反,能否寫出通俗易懂的代碼是衡量程序員是否優秀的標準。當使用typedef后,則PF就成為了一個函數指針類型。即:

typedef int (*PF)(int, int);

有了這個類型,那么上述函數的聲明就變得簡單多了。即:

PF ff(int);

下面將以程序清單 2.3為例,說明用函數指針作為函數返回值的用法。當用戶分別輸入d、x和p時,求數組的最大值、最小值和平均值。

程序清單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); //定義函數指針類型

48 PF getOperation(char c) //根據字符得到操作類型,返回函數指針

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個函數分別實現了求最大值、最小值、平均值和未知算法,getOperation()根據輸入字符得到的返回值是以函數指針的形式返回的,從pf(dbData, iSize)可以看出是通過這個指針調用函數的。注意,指針函數可以返回新的內存地址、全局變量的地址和靜態變量的地址,但不能返回局部變量的地址,因為函數結束后,在函數內部的聲明的局部變量的聲明周期已經結束,內存將自動放棄。顯然,在主調函數中訪問這個指針所指向的數據,將會產生不可預料的結果。

想學更多嵌入式課程,請掃描下圖二維碼,馬上學習!

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 指針
    +關注

    關注

    1

    文章

    480

    瀏覽量

    70512
  • 數據結構
    +關注

    關注

    3

    文章

    573

    瀏覽量

    40095
  • 程序設計
    +關注

    關注

    3

    文章

    261

    瀏覽量

    30368
  • 函數指針
    +關注

    關注

    2

    文章

    56

    瀏覽量

    3775
  • 指針函數
    +關注

    關注

    0

    文章

    10

    瀏覽量

    2741
  • ametal
    +關注

    關注

    2

    文章

    24

    瀏覽量

    11395

原文標題:周立功:函數指針與指針函數的應用

文章出處:【微信號:ZLG_zhiyuan,微信公眾號:ZLG致遠電子】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    手把手教你VC

    手把手教你VC
    發表于 08-20 15:02

    手把手教你CPLD/FPGA與單片機聯合設計》-興華

    。此外,為了幫助讀者掌握單片機與CPLD/FPGA的聯合設計,還介紹了51單片機的基本知識及單片機c語言編程的基礎知識,并通過實例設計進行詳解。《手把手教你CPLD/FPGA與單片機
    發表于 12-29 17:10

    手把手教你CPLD/FPGA與單片機聯合設計》-興華

    。此外,為了幫助讀者掌握單片機與CPLD/FPGA的聯合設計,還介紹了51單片機的基本知識及單片機c語言編程的基礎知識,并通過實例設計進行詳解。《手把手教你CPLD/FPGA與單片機
    發表于 01-06 17:21

    手把手教你FPGA 編程規范篇

    手把手教你FPGA 編程規范篇
    發表于 02-02 11:32

    手把手教你STM32單片機

    整理資料來源【正點原子】 手把手教你STM32單片機教學視頻 嵌入式 之 F103-基于新戰艦V]NANO_STM32F103開發指南-HAL庫版本_V1.0.pdf其它網絡操作等詞
    發表于 08-19 07:08

    手把手教你DSP28335_張卿杰

    手把手教你DSP28335張卿杰百度云分享手把手教你DSP28335張卿杰百度云分享
    發表于 01-11 11:45 ?177次下載

    指針作為函數參數

    手把手教你C語言難點編程,很好的C語言編程基礎資料,歡迎下載學習。
    發表于 03-25 15:28 ?2次下載

    手把手教你電子書制作

    手把手教你電子書制作,可以自己DIY電子書
    發表于 09-13 11:26 ?0次下載

    c語言函數指針定義,指針函數函數指針的區別

     往往,我們一提到指針函數函數指針的時候,就有很多人弄不懂。下面就由小編詳細為大家介紹C語言中函數指針
    發表于 11-16 15:18 ?3619次閱讀

    手把手教你如何開始DSP編程

    手把手教你如何開始DSP編程
    發表于 04-09 11:54 ?12次下載
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b>如何開始DSP<b class='flag-5'>編程</b>

    手把手教你LabVIEW視覺設計

    手把手教你LabVIEW視覺設計手把手教你LabVIEW視覺設計
    發表于 03-06 01:41 ?3117次閱讀

    理解函數指針函數指針數組、函數指針數組的指針

    理解函數指針函數指針數組、函數指針數組的指針
    的頭像 發表于 06-29 15:38 ?1.5w次閱讀
    理解<b class='flag-5'>函數</b><b class='flag-5'>指針</b>、<b class='flag-5'>函數</b><b class='flag-5'>指針</b>數組、<b class='flag-5'>函數</b><b class='flag-5'>指針</b>數組的<b class='flag-5'>指針</b>

    嵌入式秘術】手把手教你如何劫持RTOS(下)

    在《【嵌入式秘術】手把手教你如何劫持RTOS(上)》中,我們做了簡單的熱身——介紹了一種在你擁有某一個庫的源代碼或者.lib文件時,如何...
    發表于 01-25 18:51 ?7次下載
    【<b class='flag-5'>嵌入式</b>秘術】<b class='flag-5'>手把手</b><b class='flag-5'>教你</b>如何劫持RTOS(下)

    手把手教你FPGA仿真

    電子發燒友網站提供《手把手教你FPGA仿真.pdf》資料免費下載
    發表于 10-19 09:17 ?2次下載
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b><b class='flag-5'>學</b>FPGA仿真

    面試常考+1:函數指針指針函數、數組指針指針數組

    嵌入式開發領域,函數指針指針函數、數組指針指針
    的頭像 發表于 08-10 08:11 ?720次閱讀
    面試常考+1:<b class='flag-5'>函數</b><b class='flag-5'>指針</b>與<b class='flag-5'>指針</b><b class='flag-5'>函數</b>、數組<b class='flag-5'>指針</b>與<b class='flag-5'>指針</b>數組