通用鏈表的實現


一.一般鏈表的局限性.

        在我們學習數據結構時,鏈表的操作大同小異,雖然數據結構使用抽象數據類型描述算法,但是實現方法的本身特點就造成了鏈表的基本操作和用戶自定義數據類型(ElemType)產生了高度的耦合,數據類型和鏈表的操作這種"綁定",降級了代碼的重用性,每次將鏈表應用到新的場合時,都要修改源代碼來保證鏈表與新的數據類型"綁定",大量的重復操作,難免會出現各種錯誤.我們希望有一種具有通用型性的鏈表,將數據類型與鏈表操作分離開,這就需要所謂的通用鏈表(姑且這樣命名).

鏈表的實現來源於nginx,但是源代碼並非我親自閱讀,而是通過視頻獲知的,感謝視頻作者@飛哥(雖然並不能@到..).

視頻中出現了一些小錯誤,我會在文中改正.

二.何謂通用鏈表

所謂通用鏈表 , 即鏈表具有與用戶自定義的數據類型(節點類型)的無關性.我們可以一次編寫處處使用

優點:具有通用性,開銷低,

缺點:不負責內存管理,大量采用宏,無類型檢查

三.通用性原理.

以前使用一般的鏈表,也曾有過這個煩惱,也曾思考過,如何能做到"通用",直到了解到這個方法,不禁拍案叫絕,此法步步為營,暗藏玄機,可謂智慧之結晶,c語言之佳作...(此處省略10000字)

進入正題,首先要說明的是,完成這個鏈表,只需要一個頭文件.

首先,此法定義一個結構體

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;
}

五.結語

並沒有什么結語

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM