轉載時請註明出處和作者連絡方式
文章出處:http://www.limodev.cn/blog
作者連絡方式:李先靜 <xianjimli at hotmail dot com>
在專用雙向鏈表中,dlist_printf的實現非常簡單,如果裡面存放的是整數,用”%d”列印,存放的字串,用”%s”列印。現在的麻煩在於雙向鏈表是通用的,我們無法預知其中存在的資料類型,也就是我們要面對資料類型的變化。怎麼辦呢?初學者常見的做法有:
1.實現多個函數,需要哪個就用哪個。比如實現的有dlist_print_int用來列印存放整數的雙向鏈表,dlist_print_string用來列印存放字串的雙向鏈表,如此等等,其它類型都有自己的列印函數。
這種做法的缺點有:一是每個函數的實現方式類似,造成大量重複的代碼。二是資料類型的種類不確定,每種資料類型都要寫一個print函數,當要存放新的資料類型時,需要修改dlist的實現。
2.傳入一個附加參數來決定如何列印。比如傳入1表示按整數方式列印,傳入2表示按字串方式列印,以此類推。
這種做法比第一種好一點,至少不會造成大量重複的代碼。但是同樣存在增加新類型時要修改dlist_print函數的問題。
3調用dlist的介面函數擷取每一個位置的資料並列印出來。
它可以避免前面兩種方法的缺點,而且是一種很直觀的方式。奇怪的是偏偏很少有人這樣去做,原因可能有兩個,其一是太拘泥於傳統的實現方式而沒有想到這一種。其二是擔心效能問題,因為通過索引取值,每一次都從頭開始定位,其效能開銷為O(n*n)。
其實這種方法是可以接受的,dlist_print是用於輔助測試,我們並不在乎它的效能開銷,而且很少在鏈表中存放成千上萬的資料,它帶來的效能影響也沒有想的那樣嚴重。
不過在這裡我們要介紹一種新的方法:
dlist_print的大體架構為:
DListNode* iter = thiz->first; while(iter != NULL) { print(iter->data); iter = iter->next; }
在上面代碼中,我們主要是不知道如何?print(iter->data);這行代碼。可是誰知道呢?很明顯,調用者知道,因為調用者知道 裡面存放的資料類型。OK,那讓調用者來做好了,調用者調用dlist_print時提供一個函數給dlist_print調用,這種回調調用者提供的函 數的方法,我們可以稱它為回呼函數法。
調用者如何提供函數給dlist_print呢?當然是通過函數指標了。變數指標指向的是一塊資料,指標指向不同的變數,則取到的是不同的資料。函 數指標指向的是一段代碼(即函數),指標指向不同的函數,則具有不同的行為。函數指標是實現多態的手段,多態就是隔離變化的秘訣,這裡只是一個開端,後面 我們會逐步的深入學習。
回到正題上,我們看如何?dlist_print:
定義函數指標類型:
typedef DListRet (*DListDataPrintFunc)(void* data);
聲明dlist_print函數:
DListRet dlist_print(DList* thiz, DListDataPrintFunc print);
實現dlist_print函數:
DListRet dlist_print(DList* thiz, DListDataPrintFunc print){ DListRet ret = DLIST_RET_OK; DListNode* iter = thiz->first; while(iter != NULL) { print(iter->data); iter = iter->next; } return ret;}
調用方法
static DListRet print_int(void* data){ printf("%d ", (int)data); return DLIST_RET_OK;}…dlist_print(dlist, print_int);
所有問題都解決了,是不是很簡單? 我以前寫過一篇關於函數指標的BLOG,文中聲稱不懂函數指標就不要自稱是C語言高手,現在我仍然堅持這個觀點。函數指標的概念本身很簡單,關鍵在於靈活應用,這裡是一個最簡單的應用,希望讀者仔細體會一下,後面將會有大量篇幅介紹。
我寫了一個簡單的樣本,它的實現並不完善,不過用來示範我們到目前為止學到的內容已經夠了。有興趣的讀者請到這裡下載。