一.通常鏈表的侷限性.nginx
在咱們學習數據結構時,鏈表的操做大同小異,雖然數據結構使用抽象數據類型描述算法,可是實現方法的自己特色就形成了鏈表的基本操做和用戶自定義數據類型(ElemType)產生了高度的耦合,數據類型和鏈表的操做這種"綁定",降級了代碼的重用性,每次將鏈表應用到新的場合時,都要修改源代碼來保證鏈表與新的數據類型"綁定",大量的重複操做,不免會出現各類錯誤.咱們但願有一種具備通用型性的鏈表,將數據類型與鏈表操做分離開,這就須要所謂的通用鏈表(姑且這樣命名).算法
鏈表的實現來源於nginx,可是源代碼並不是我親自閱讀,而是經過視頻獲知的,感謝視頻做者@飛哥(雖然並不能@到..).數據結構
視頻中出現了一些小錯誤,我會在文中改正.學習
二.何謂通用鏈表測試
所謂通用鏈表 , 即鏈表具備與用戶自定義的數據類型(節點類型)的無關性.咱們能夠一次編寫到處使用spa
優勢:具備通用性,開銷低,指針
缺點:不負責內存管理,大量採用宏,無類型檢查code
三.通用性原理.視頻
之前使用通常的鏈表,也曾有過這個煩惱,也曾思考過,如何能作到"通用",直到了解到這個方法,不由拍案叫絕,此法步步爲營,暗藏玄機,可謂智慧之結晶,c語言之佳做...(此處省略10000字)blog
進入正題,首先要說明的是,完成這個鏈表,只須要一個頭文件.
首先,此法定義一個結構體
typedef struct list_s List, *Plist; struct list_s{ Plist prev, next; };
這個結構體,就是貫穿全劇的線索.使用這個鏈表須要在結構體內定義一個 List類型的數據,就像這樣:
typedef struct{ int a; List list; }TEST,*pTEST;
整個鏈表的結構,就靠list串聯起來
其核心思想是 咱們對list(紅色部分)進行操做,就能夠邏輯上從鏈表上刪除,插入某個元素(因此說,內存此鏈表的實現無關內存管理)
實際上,咱們至關於管理了這樣一個鏈表,這個鏈表的元素只是本身的前驅後後繼指針
看上去彷佛不錯,雖然list成員能夠"攜帶"其餘數據在鏈表中找到合適的位置,可是,咱們如何訪問這些數據呢?
咱們管理的是list成員,能夠知道的信息就是list的地址,藍色箭頭指向的地方就是list成員就是地址了,要想訪問數據,咱們就得知道整個結構體的首地址,
紅色三角形的位置就是整個結構體的首地址了.咱們不妨舉個例子
在這種狀況下,data的大小能夠經過 2004H - 2000H = 4H來計算,同時,這也是list成員的偏移量
知道了偏移量,知道了list首地址,就知道告終構體的首地址,等等!彷佛哪裏不對- -! 你根本不知道那個2000H嘛,知道了還算個毛線!
好吧,確實是個問題,可是下邊的方法確實是好極了!
typedef struct{ int a; List list; }TEST,*pTEST; TEST t = {1234,{NULL, NULL}}; Plist l = &t.list; printf("%d\n", &((pTEST)0)->list);//得到偏移量! printf("%d", ((pTEST)((char*)l - (char*)&((pTEST)0)->list))->a);
關鍵是獲取到偏移量,剩下的就是順水推舟了,
&((pTEST)0)->list
很神奇的代碼,不是麼,咱們來分析一下吧
0 將0視做地址
((pTEST)0 強制轉換爲自定義結構體類型
((pTEST)0)->list
&((pTEST)0)->list 取得list成員的地址
因爲地址從0開始,所以list的地址就是其在結構體中的偏移量
瞭解了上述原理以後,咱們就掌握了通用鏈表的核心方法.
四,實現
list.h
#ifndef _LIST_H_ #define _LIST_H_ typedef struct list_s List, *Plist; struct list_s{ Plist prev, next; }; //初始化 h:頭節點指針 #define ListInit(h) \ do{ \ (h)->prev = NULL; \ (h)->next = NULL; \ }while(0) //h:頭節點指針 l:自定義結構體中List類型成員的指針 #define ListHeadInsert(h,l) \ do{ \ (l)->prev = (h); \ (l)->next = (h)->next; \ (h)->next = (l); \ }while(0) // #define ListTailInsert(h,l) \ do{ \ Plist t = h; \ while(t->next) t = t->next; \ (l)->prev = (t); \ (l)->next = (t)->next; \ (t)->next = (l); \ }while(0) //l:要讀取的節點的list成員的指針 //struct_type:自定義結構體的類型名 //struct_field: List 類型成員的成員名稱(變量名) #define ListGetData(l,struct_type, struct_field)\ ((struct_type*)((char*)(l)-offsetof(struct_type,struct_field))) //要刪除節點的list的指針 #define ListDelet(l) \ do{ \ if((l)->next == NULL) \ { \ (l)->prev->next = (l)->next; \ (l)->prev = NULL; \ }else{ \ (l)->prev->next = (l)->next; \ (l)->next->prev = (l)->prev; \ } \ }while(0) #endif
用於測試的代碼:
#include <stdio.h> #include "list.h" typedef struct{ int a; List list; }TEST,*pTEST; int main(int argc, char** argv) { /* TEST t = {1234,{NULL, NULL}}; Plist l = &t.list; //int n = &((TEST)0).list; &((pTEST)0)->list; printf("%d\n", &((pTEST)0)->list); //printf("%d", ((pTEST)((char*)l - (char*)&((pTEST)0)->list))->a); printf("%d", ((pTEST)((char*)l - offsetof(TEST,list)))->a); */ TEST test1 = {1234,{NULL, NULL}}; TEST test2 = {2234,{NULL, NULL}}; TEST test3 = {3234,{NULL, NULL}}; List head; ListInit(&head); ListTailInsert(&head,&test1.list); ListTailInsert(&head,&test2.list); ListTailInsert(&head,&test3.list); List* temp = head.next; for(temp = head.next; temp != NULL; temp = temp->next) { pTEST data_addr = ListGetData(temp,TEST,list); printf("%d\n", data_addr->a); } //printf("%p, %p \n", test2.list.prev, test2.list.next); ListDelet(&test3.list); for(temp = head.next; temp != NULL; temp = temp->next) { pTEST data_addr = ListGetData(temp,TEST,list); printf("%d\n", data_addr->a); } return 0; }
五.結語
並無什麼結語