程序員的成長

轉載時請註明出處和作者聯繫方式
文章出處: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語言高手,現在我仍然堅持這個觀點。函數指針的概念本身很簡單,關鍵在於靈活應用,這裏是一個最簡單的應用,希望讀者仔細體會一下,後面將會有大量篇幅介紹。

我寫了一個簡單的示例,它的實現並不完善,不過用來演示我們到目前爲止學到的內容已經夠了。有興趣的讀者請到這裏下載。

發佈了17 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章