色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

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

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

3天內不再提示

周立功教你學程序設計技術:做好軟件模塊的分層設計,回調函數要這樣寫

AGk5_ZLG_zhiyua ? 來源:未知 ? 作者:電子大兵 ? 2017-08-30 10:24 ? 次閱讀

第二章為程序設計技術,本文為2.1.3 回調函數。

>>>>2.1.3 回調函數

>>>1.分層設計

分層設計就是將軟件分成具有某種上下級關系的模塊,由于每一層都是相對獨立的,因此只要定義好層與層之間的接口,從而每層都可以單獨實現。比如,設計一個保險箱電子密碼鎖,其硬件部分大致包括鍵盤、顯示器、蜂鳴器、鎖與存儲器等驅動電路,因此根據需求將軟件劃分為硬件驅動層、虛擬層與應用層三大模塊,當然每個大模塊又可以劃分為幾個小模塊,下面將以鍵盤掃描為例予以說明。

(1)硬件驅動層

硬件驅動層處于模塊的最底層,直接與硬件打交道。其任務是識別哪個鍵按下了,實現與硬件電路緊密相關的部分軟件,更高級的功能將在其它層實現。雖然通過硬件驅動層可以直達應用層,由于硬件電路變化多樣,如果應用層直接操作硬件驅動層,則應用層勢必依賴于硬件層,則最好的方法是增加一個虛擬層應對硬件的變化。顯然,只要鍵盤掃描的方法不變,則產生的鍵值始終保持不變,那么虛擬層的軟件也永遠不會改變。

(2)虛擬層

它是依據應用層的需求劃分的,主要用于屏蔽對象的細節和變化,則應用層就可以用統一的方法來實現了。即便控制方法改變了,也無需重新編寫應用層的代碼。

(3)應用層

應用層處于模塊的最上層,直接用于功能的實現,比如,應用層對外只有一個“人機交互”模塊,當然內部還可以劃分幾個模塊供自己使用。三層之間數據傳遞的關系非常清晰,即應用層->虛擬層->硬件驅動層,詳見圖 2.2,圖中的實線代表依賴關系,即應用層依賴于虛擬層,虛擬層依賴于硬件驅動層。基于分層的架構具有以下優點:

  • 降低系統的復雜度:由于每層都是相對獨立的,層與層之間通過定義良好接口交互,每層都可以單獨實現,從而降低了模塊之間的耦合度;

  • 隔離變化:軟件的變化通常發生在最上層與最下層,最上層是圖形用戶界面,需求的變化通常直接影響用戶界面,大部分軟件的新老版本在用戶界面上都會有很大差異。最下層是硬件,硬件的變化比軟件的發展更快,通過分層設計可以將這些變化的部分獨立開來,讓它們的變化不會給其它部分帶來大的影響;

  • 有利于自動測試:由于每一層具有獨立的功能,則更易于編寫測試用例;

  • 有利于提高程序的可移植性:通過分層設計將各種平臺不同的部分放在獨立的層里。比如,下層模塊是對操作系統提供的接口進行包裝的包裝層,上層是針對不同平臺所實現的圖形用戶界面。當移植到不同的平臺時,只需要實現不同的部分,而中間層都可以重用。

圖 2.2 三層結構示意

應用層處于模塊的最上層,直接用于功能的實現,比如,應用層對外只有一個“人機交互”模塊,當然內部還可以劃分幾個模塊供自己使用。三層之間數據傳遞的關系非常清晰,即應用層->虛擬層->硬件驅動層,詳見圖 2.2,圖中的實線代表依賴關系,即應用層依賴于虛擬層,虛擬層依賴于硬件驅動層?;诜謱拥募軜嬀哂幸韵聝烖c:

  • 降低系統的復雜度:由于每層都是相對獨立的,層與層之間通過定義良好接口交互,每層都可以單獨實現,從而降低了模塊之間的耦合度;

  • 隔離變化:軟件的變化通常發生在最上層與最下層,最上層是圖形用戶界面,需求的變化通常直接影響用戶界面,大部分軟件的新老版本在用戶界面上都會有很大差異。最下層是硬件,硬件的變化比軟件的發展更快,通過分層設計可以將這些變化的部分獨立開來,讓它們的變化不會給其它部分帶來大的影響;

  • 有利于自動測試:由于每一層具有獨立的功能,則更易于編寫測試用例;

  • 有利于提高程序的可移植性:通過分層設計將各種平臺不同的部分放在獨立的層里。比如,下層模塊是對操作系統提供的接口進行包裝的包裝層,上層是針對不同平臺所實現的圖形用戶界面。當移植到不同的平臺時,只需要實現不同的部分,而中間層都可以重用。

>>>2.隔離變化

(1)好萊塢原則(Hollywood)

類似鍵盤掃描這樣的模塊,其共性是各層之間的調用關系,不可能隨著時間而改變,即便上下層之間形成依賴關系,采用直接調用方式是最簡單的。為了降低層與層之間的耦合,層與層之間的通信必須按照一定的規則進行。即上層可以直接調用下層提供的函數,但下層不能直接調用上層提供的函數,且層與層之間絕對不能循環調用。因為層與層之間的循環依賴會嚴重妨礙軟件的復用性和可擴展性,使得系統中的每一層都無法獨立構成一個可復用的組件。雖然上層也可以調用相鄰下層提供的函數,但不能跨層調用。即下層模塊實現了在上層模塊中聲明并被高層模塊調用的接口,這就是著名的好萊塢(Hollywood)擴展原則:“不要調用我,讓我調用你?!碑斚聦有枰獋鬟f數據給上層時,則采用回調函數指針接口隔離變化。通過倒置依賴的接口所有權,創建了一個更靈活、更持久和更易于修改的結構。

實際上,由上層模塊(即調用者)提供的回調函數的表現形式就是在下層模塊中通過函數指針調用另一個函數,即將回調函數的地址作為實參初始化下層模塊的形參,由下層模塊在某個時刻調用這個函數,這個函數就是回調函數,詳見圖 2.3。其調用方式有兩種:

  • 在上層模塊A調用下層模塊B的函數中,直接調用回調函數C;

  • 使用注冊的方式,當某個事件發生時,下層模塊調用回調函數。

圖 2.3 回調函數的使用

在初始化時,上層模塊A將回調函數C的地址作為實參傳遞給下層模塊B。在運行中,當下層模塊需要與上層模塊通信時,調用這個回調函數。其調用方式為A→B→C,上層模塊A調用下層模塊B,在B的執行過程中,調用回調函數將信息返回給上層模塊。對于上層模塊來說,C不僅監視B的運行狀態,而且干預B的運行,其本質上依然是上層模塊調用下層模塊。由于增加了回調函數,即可在運行中實現動態綁定,下面將以標準的冒泡排序函數對一個任意類型的數據進行排序為例予以說明。

(2)數據比較函數

假設待排序的數據為int型,即可通過比較相鄰數據的大小,做出是否交換數據的處理。當給定兩個指向int型變量的指針e1和e2時,則比較函數返回一個數。如果*e1小于*e2,那么返回的數為負數;如果*e1大于*e2,那么返回的數為正數;如果*e1等于*e2,那么返回的數為0,詳見程序清單 2.4。

程序清單2.4 compare_int()數據比較函數

1 int compare_int(const int *e1, const int *e2)

2 {

3 return *e1 - *e2;//升序比較

4 }

5

6 int compare_int(const int *e1, const int *e2)

7 {

8 return *e2 - *e1; //降序比較

9 }

由于任何數據類型的指針都可以給void*指針賦值,因此可以利用這一特性,將void*指針作為數據比較函數的形參。當函數的形參聲明為void *類型時,雖然bubbleSort()冒泡排序函數內部不知道調用者會傳遞什么類型的數據過來,但調用者知道數據的類型和對數據的操作方法,那就由調用者編寫數據比較函數。

由于在運行時調用者要根據實際情況才能決定調用哪個數據比較函數,因此根據比較操作的要求,其函數原型如下:

typedef int (*COMPARE)(const void *e1, const void *e2);

其中的e1、e2是指向2個需要進行比較的值的指針。當返回值< 0時,表示e1 < e2;當返回值= 0時,表示e1 = e2;當返回值> 0時,表示e1 > e2。

當用typedef聲明后,COMPARE就成了函數指針類型,有了類型就可以定義該類型的函數指針變量。比如:

COMPARE compare;

此時,只要將函數名(比如,compare_int)作為實參初始化函數的形參,即可調用相應的數據比較函數。比如:

COMPARE compare=compare_int;

雖然編譯器看到的是一個compare,但調用者實現了多種不同類型的compare,即可根據接口函數中的類型改變函數的行為方式,通用數據比較函數的實現詳見程序清單 2.5。

程序清單 2.5 compare數據比較函數的實現

1 int compare_int(const void *e1, const void *e2)

2 {

3 return (*((int *)e1) - *((int *)e2)); //升序比較

4 }

5

6 int compare_int_invert(const void *e1, const void *e2)

7 {

8 return *(int *)e2 - *(int *)e1; //降序比較

9 }

10

11 int compare_vstrcmp(const void *e1, const void *e2)

12 {

13 return strcmp(*(char**)e1, *(char**)e2); //字符串比較

14 }

注意,如果e1是很大的正數,而e2是大負數,或者相反,則計算結果可能會溢出。由于這里假設它們都是正整數,從而避免了風險。

由于該函數的參數聲明為void *類型,因此數據比較函數不再依賴于具體的數據類型。即可將算法的變化部分獨立出來,無論是升序還是降序或字符串比較完全取決于回調函數。注意,之所以不能直接用strcmp()作為字符串的比較,因為bubbleSort()傳遞的是類型為char **的數組元素的地址&array[i],而不是類型為char*的array[i]。

(3)bubbleSort()冒泡排序函數

標準函數bubbleSort()是C中使用函數指針的經典示例,該函數是對一個具有任意類型的數組進行排序,其中單個元素的大小和要比較的元素的函數都是給定的。其原型初定如下:

bubbleSort(參數列表);

既然bubbleSort()是對數組中的數據排序,那么bubbleSort()必須有一個參數保存數組的起始地址,且還有一個參數保存數組中元素的個數。為了通用還是在數組中存放void *類型的元素,這樣一來就可以用數組存儲用戶傳入的任意類型的數據,因此用void *類型參數保存數組的起始地址。其函數原型如下:

bubbleSort(void *base, size_t nmemb);

由于數組的類型是未知的,那么數組中元素的長度也是未知的,同樣也需要一個參數來保存。其函數原型進化為:

bubbleSort(void *base, size_t nmemb, size_t size);

其中,size_t是C標準庫中預定義的類型,專門用于保存變量的大小。參數base和nmemb標識了這個數組,分別用于保存數組的起始地址和數組中元素的個數,size存儲的是打包時單個元素的大小。

此時,如果將指向compare()的指針作為參數傳遞給bubbleSort(),即可“回調”compare()進行值的比較。由于排序是對數據的操作,因此bubbleSort()沒有返回值,其類型為void,bubbleSort()函數接口詳見程序清單 2.6。

程序清單2.6bubbleSort()冒泡排序函數接口(bubbleSort.h)

1 #pragma once;

2 void bubbleSort(void *base, size_t nmemb, size_t size, COMPARE compare);

雖然大多數初學者也會選擇回調函數,但又經常用全局變量保存中間數據。這里提出的解決方法就是給回調函數傳遞一個稱為“回調函數上下文”的參數,其變量名為base。為了能接受任何數據類型,選擇void *表示這個上下文?!吧舷挛摹钡囊馑季褪钦f,如果傳進來的是int類型值,則回調int型數據比較函數;如果傳進來的是字符串,則回調字符串比較函數。

當bubbleSort()將base聲明為一個void *類型時,即允許bubbleSort()用相同的代碼支持不同類型的數據比較實現排序,其關鍵之處是type類型域,它允許在運行時根據數據的類型調用不同的函數。這種在運行時根據數據的類型將函數體與函數調用相關聯的行為稱為動態綁定,因此將一個函數的綁定發生在運行時而非編譯期,就稱該函數是多態的。顯然,多態是一種運行時綁定機制,其目的是將函數名綁定到函數的實現代碼。一個函數的名字與其入口地址是緊密相連的,入口地址是該函數在內存中的起始地址,因此多態就是將函數名動態地綁定到函數入口地址的運行時綁定機制,bubbleSort()的接口與實現詳見程序清單 2.7和程序清單 2.8。

程序清單2.7bubbleSort()接口(bubbleSort.h)

1 #pragma once

2 #include

3

4 typedef int(*COMPARE)(const void * e1, const void *e2);

5 void bubbleSort(void * base, size_t nmemb, size_t size, COMPARE compare);

程序清單2.8bubbleSort()接口的實現(bubbleSort.c)

1 #include"bubbleSort.h"

2

3 void byte_swap(void *pData1, void *pData2, size_t stSize)

4 {

5 unsigned char *pcData1 = pData1;

6 unsigned char *pcData2 = pData2;

7 unsigned char ucTemp;

8

9 while (stSize--){

10 ucTemp = *pcData1; *pcData1 = *pcData2; *pcData2 = ucTemp;

11 pcData1++; pcData2++;

12 }

13 }

14

15 void bubbleSort(void * base, size_t nmemb, size_t size, COMPARE compare)

16 {

17 int hasSwap=1;

18

19 for (size_t i = 1; hasSwap&&i < nmemb; i++) {

20 hasSwap = 0;

21 for (size_t j = 0; j < numData - 1; j++) {

22 void *pThis = ((unsigned char *)base) + size*j;

23 void *pNext = ((unsigned char *)base) + size*(j+1);

24 if (compare(pThis, pNext) > 0) {

25 hasSwap = 1;

26 byte_swap(pThis, pNext, size);

27 }

28 }

29 }

30 }

靜態類型和動態類型

類型的靜態和動態指的是名字與類型綁定的時間,如果所有的變量和表達式的類型在編譯時就固定了,則稱之為靜態綁定;如果所有的變量和表達式的類型直到運行時才知道,則稱之為動態綁定。

假設要實現一個用于任意數據類型的冒泡排序函數并簡單測試,其要求是同一個函數既可以從大到小排列,也可以從小到大排列,且同時支持多種數據類型。比如:

int array[] = {39, 33, 18, 64, 73, 30, 49, 51, 81};

顯然,只要將比較函數的入口地址compare_int傳遞給compare,即可調用bubbleSort():

int array[] = {39, 33, 18, 64, 73, 30, 49, 51, 81};

bubbleSort(array, numArray , sizeof(array[0]), compare_int);

在數量不大時,所有排序算法性能差別不大,因為高級算法只有在元素個數多于1000時,性能才出現顯著提升。其實90%以上的情況下,我們存儲的元素個數只有幾十到幾百個,冒泡排序可能是更好的選擇,bubbleSort()的實現與使用范例程序詳見程序清單 2.9。

程序清單 2.9 bubbleSort()冒泡排序范例程序

1 #include

2 #include

3 #include"bubbleSort.h"

4

5 int compare_int(const void * e1, const void * e2)

6 {

7 return *(int *)e1 - *(int *)e2;

8 }

9

10 int compare_int_r(const void * e1, const void * e2)

11 {

12 return *(int *)e2 - *(int *)e1 ;

13 }

14

15 int compare_str(const void * e1, const void *e2)

16 {

17 return strcmp(*(char **)e1, *(char **)e2);

18 }

19

20 void main()

21 {

22 int arrayInt[] = { 39, 33, 18, 64, 73, 30, 49, 51, 81 };

23 int numArray = sizeof(arrayInt) / sizeof(arrayInt[0]);

24 bubbleSort(arrayInt, numArray, sizeof(arrayInt[0]), compare_int);

25 for (int i = 0; i

26 printf("%d ", arrayInt[i]);

27 }

28 printf("\n");

29

30 bubbleSort(arrayInt, numArray, sizeof(arrayInt[0]), compare_int_r);

31 for (int i = 0; i

32 printf("%d ", arrayInt[i]);

33 }

34 printf("\n");

35

36 char * arrayStr[] = { "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday" };

37 numArray = sizeof(arrayStr) / sizeof(arrayStr[0]);

38 bubbleSort(arrayStr, numArray, sizeof(arrayStr[0]), compare_str);

39 for (int i = 0; i < numArray; i++) {?

40 printf("%s\n", arrayStr[i]);

41 }

42 }

由此可見,調用者main()與compare_int()回調函數都同屬于上層模塊,bubbleSort()屬于下層模塊。當上層模塊調用下層模塊bubbleSort()時,將回調函數的地址compare_int作為參數傳遞給bubbleSort(),進而調用compare_int()。顯然,使用參數傳遞回調函數的方式,下層模塊不必知道需要調用上層模塊的哪個函數,從而減少了上下層之間的聯系,這樣上下層可以獨立修改,而不影響另一層代碼的實現。這樣一來,在每次調用bubbleSort()時,只要給出不同的函數名作為實參,則bubbleSort()不必做任何修改。

使用回調函數的最大優點就是便于軟件模塊的分層設計,降低軟件模塊之間的耦合度。即回調函數可以將調用者與被調用者隔離,調用者無需關心誰是被調用者。當特定的事件或條件發生時,調用者將使用函數指針調用回調函數對事件進行處理。

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

    關注

    38

    文章

    130

    瀏覽量

    37672
  • 回調函數
    +關注

    關注

    0

    文章

    87

    瀏覽量

    11583
  • 單片機程序設計

    關注

    0

    文章

    2

    瀏覽量

    6331

原文標題:周立功:做好軟件模塊的分層設計必須掌握的回調函數

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

收藏 人收藏

    評論

    相關推薦

    程序設計與數據結構》立功數十年心血力作

    為了將實際開發過程中總結的有價值的技術應用分享給大家,立功及其團隊整理出《程序設計與數據結構》這本書,其內容如同培訓講師的教案,是
    發表于 05-26 10:06 ?29次閱讀

    立功程序設計與數據結構”:深度解剖動態分布內存的free()函數與realloc()函數

    立功教授數年之心血之作《程序設計與數據結構》,書本內容公開后,在電子行業掀起一片學習熱潮。
    的頭像 發表于 08-25 14:22 ?1.5w次閱讀

    立功教你C語言編程與程序設計這樣函數指針數組最好用

    立功教授數年之心血之作《程序設計與數據結構》以及《面向AMetal框架與接口的編程(上)》,電子版已無償性分享到電子工程師與高校群體,在公眾號回復【編程】即可在線閱讀。
    的頭像 發表于 08-31 14:06 ?6926次閱讀
    <b class='flag-5'>周</b><b class='flag-5'>立功</b><b class='flag-5'>教你</b><b class='flag-5'>學</b>C語言編程與<b class='flag-5'>程序設計</b>:<b class='flag-5'>這樣</b><b class='flag-5'>寫</b><b class='flag-5'>函數</b>指針數組最好用

    算法的泛化問題,這些坑你可能都經歷過!|立功教你軟件設計

    立功教授數年之心血之作《程序設計與數據結構》,電子版已無償性分享到電子工程師與高校群體,在公眾號回復【程序設計】即可在線閱讀。書本內容公開后,在電子行業掀起一片學習熱潮。經
    的頭像 發表于 09-01 09:18 ?6150次閱讀

    μCOS-II程序設計基礎 立功公司編著

    μCOS-II程序設計基礎立功公司編著
    發表于 08-20 13:41

    新書創作談:立功教授數十年之心血力作《程序設計與數據結構》

    ` 近日,立功教授公開了數十年之心血力作《程序設計與數據結構》,此書在4月28日落筆,電子版已無償性分享到電子工程師與高校群體,在致遠電子公眾號后臺回復關鍵字【程序設計】可在線閱讀。
    發表于 05-15 18:04

    【完整資料】《程序設計與數據結構》立功數十年心血力作

    ,如何有效提高技術人員軟技能,避免蠻力開發現象,甚至成為一位閱讀程序者。為了將實際開發過程中總結的有價值的技術應用分享給大家,立功及其團隊
    發表于 05-16 16:43

    調函數程序開發中有何作用呢

    調函數程序開發中是一個非常重要的概念,所謂的調其實就是不同
    發表于 03-01 07:13

    μCOS-II程序設計基礎_立功公司編著

    μCOS-II程序設計基礎 立功公司編著
    發表于 12-28 15:01 ?13次下載

    立功:動態分布內存——malloc()函數與calloc()函數

    立功教授數年之心血之作《程序設計與數據結構》,電子版已無償性分享到電子工程師與高校群體,在公眾號回復【程序設計】即可在線閱讀。書本內容公開后,在電子行業掀起一片學習熱潮。經
    的頭像 發表于 08-22 17:01 ?4859次閱讀

    LabWindows/CVI 程序 調函數設計

    調函數是系統框架設計中非常重要的一種手段,所謂調函數(callback )是指一個通過
    發表于 05-03 16:54 ?1.1w次閱讀
    LabWindows/CVI <b class='flag-5'>程序</b> <b class='flag-5'>回</b><b class='flag-5'>調</b><b class='flag-5'>函數</b>設計

    調函數的詳細資料說明

    異步事件的處理,首先將異步事件發生時需要執行的代碼編寫成一個函數,并將該函數注冊成為調函數,這樣
    發表于 02-28 08:00 ?6次下載
    <b class='flag-5'>回</b><b class='flag-5'>調</b><b class='flag-5'>函數</b>的詳細資料說明

    C語言函數調函數

    來源:嵌入式客棧 1 什么是調函數?首先什么是調呢? 我的理解是:把一段可執行的代碼像參數傳遞那樣傳給其他代碼,而這段代碼會在某個時刻被
    的頭像 發表于 09-11 09:57 ?4152次閱讀

    c語言調函數的使用及實際作用詳解

    大家好,我是無際。今天給大家講一下芯片/模塊廠家SDK必須會使用的一種技術調函數。
    發表于 11-20 19:51 ?13次下載
    c語言<b class='flag-5'>回</b><b class='flag-5'>調</b><b class='flag-5'>函數</b>的使用及實際作用詳解

    函數指針和調函數的使用方法

    了解開發語言的朋友應該都會對調函數有所了解,在很多的程序開發語言中都能看到調的身影。很多場景
    的頭像 發表于 04-10 15:08 ?1118次閱讀
    主站蜘蛛池模板: 中文字幕中文字幕永久免费| 国产人妻人伦精品无码.麻豆 | 大岛优香久久中文字幕| 日本一区精品久久久久影院| 国产AV一区二区三区传媒| 乌克兰肛交影视| 久久4k岛国高清一区二区| 99国产精品白浆在线观看免费| 日韩精品久久日日躁夜夜躁影视 | 国产手机在线亚洲精品观看| 野花日本韩国视频免费高清观看| 曼谷av女郎| 国产精品1区2区| 曰本xxⅹ孕妇性xxx| 人妻美妇疯狂迎合| 国产永久免费高清在线观看| 69丰满少妇AV无码区| 乳交高H糙汉宠文| 久久精视频| 国产99RE在线观看69热| 在线观看免费视频播放视频| 色小姐.com| 老师我好爽再深一点老师好涨| 国产成人免费在线观看| 中文字幕在线不卡精品视频99| 色欲av蜜臀av高清| 老师掀开短裙让我挺进动态| 国产精品久久久久久AV免费不卡 | 2017最新伦理伦理片67| 无码99久热只有精品视频在线| 看黄色片子| 国产亚洲精品久久综合阿香| GOGOGO高清在线播放免费| 亚洲永久精品ww47app| 青青青手机视频| 久久精品国产亚洲AV忘忧草蜜臀| 国产成人精品s8p视频| 99久久久无码国产精品不卡按摩 | 国产色综合久久无码有码| cctv论坛| 最近中文字幕完整版高清|