很多人在學習完鏈表之後幾乎沒有怎麼用過,有這麼幾個原因可能導致大家對於直接寫鏈表有一種恐懼心理:
1.在學習完C++之後,尤其是學習完STL之後,會接觸到一種鏈表容器,這個容器可以很簡單的實現大家所排斥的鏈表的創建,增刪改查,以及一些常用的基礎算法,比如鏈表的逆序,鏈表的排序都有很直接的類似於“java”一樣方便的接口出來,可以直接去調用,就省得出現很多bug。
2.其實最主要的還是對於鏈表節點的“不適用性”而感到麻煩。這主要集中在,鏈表節點無法代表全部的結構體內容,就比如說,我要做一個聊天室,那麼在聊天室中,我肯定要有一個用戶鏈表,在這個用戶鏈表中很多屬性是不同的,就比如說,超級用戶、管理員和普通用戶,他們的權限,以及基本的屬性肯定都是不同的。再或者說,如果是一個教務管理系統,然後其中有老師,有學生,他們的屬性也是不同,甚至於導致他們的結構體的大小“sizeof()”都是不同的,那麼問題就來了,如果在這種情況下,我們還需要將不同大小的節點都聯繫在一起,怎麼處理?
首先看一段來自於linux內核的代碼:
在內核Mkregtale.c文件中- /*
- * This is a simple doubly linked list implementation that matches the
- * way the Linux kernel doubly linked list implementation works.
- */
- struct list_head {
- struct list_head *next; /* next in chain */
- struct list_head *prev; /* previous in chain */
- };
/*
* This is a simple doubly linked list implementation that matches the
* way the Linux kernel doubly linked list implementation works.
*/
struct list_head {
struct list_head *next; /* next in chain */
struct list_head *prev; /* previous in chain */
};
這個不含數據域的鏈表,可以嵌入到任何數據結構中,例如可按如下方式定義含有數據域的鏈表:
- struct score
- {
- int num;
- int English;
- int math;
- struct list_head list;//鏈表鏈接域
- };
- struct list_head score_head;//所建立鏈表的鏈表頭
struct score
{
int num;
int English;
int math;
struct list_head list;//鏈表鏈接域
};
struct list_head score_head;//所建立鏈表的鏈表頭
INIT_LIST_HEAD(&score_head);//初始化鏈表頭 完成一個雙向循環鏈表的創建上面的紅色部分初始化一個已經存在的list_head對象,score_head爲一個結構體的指針,這樣可以初始化堆棧以及全局區定義的score_head對象。調用INIT_LIST_HEAD()宏初始化鏈表節點,將next和prev指針都指向其自身,我們就構造了一個空的雙循環鏈表。
初始化一個空鏈表:其實就是鏈表頭,用來指向第一個結點!定義結點並且初始化!然後雙向循環鏈表就誕生了
static 加在函數前,表示這個函數是靜態函數,其實際上是對作用域的限制,指該函數作用域僅侷限於本文件。所以說,static 具有信息隱蔽的作用。而函數前加 inline 關鍵字的函數,叫內聯函數,表 示編譯程序在調用這個函數時,立即將該函數展開。
- /* Initialise a list head to an empty list */
- static inline void INIT_LIST_HEAD(struct list_head *list)
- {
- list->next = list;
- list->prev = list;
- }
/* Initialise a list head to an empty list */
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
list_add:在鏈表頭插入節點
- /**
- * list_add - add a new entry
- * @new: new entry to be added
- * @head: list head to add it after
- *
- * Insert a new entry after the specified head.
- * This is good for implementing stacks.
- */
- static inline void list_add(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head, head->next);
- }
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
- /*
- * Insert a new entry between two known consecutive entries.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
- #ifndef CONFIG_DEBUG_LIST
- static inline void __list_add(struct list_head *new,
- struct list_head *prev,
- struct list_head *next)
- {
- next->prev = new;
- new->next = next;
- new->prev = prev;
- prev->next = new;
- }
- #else
- extern void __list_add(struct list_head *new,
- struct list_head *prev,
- struct list_head *next);
- #endif
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
#ifndef CONFIG_DEBUG_LIST
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
#else
extern void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next);
#endif
list_add_tail:在鏈表尾插入節點
- /**
- * list_add_tail - add a new entry
- * @new: new entry to be added
- * @head: list head to add it before
- *
- * Insert a new entry before the specified head.
- * This is useful for implementing queues.
- */
- static inline void list_add_tail(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head->prev, head);
- }
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
用法示例:
struct score
{
int num;
int English;
int math;
struct list_head list;//鏈表鏈接域
};
struct list_head score_head;//所建立鏈表的鏈表頭
//定義三個節點 然後插入到鏈表中
struct score stu1, stu2, stu3;
list_add_tail(&(stu1.list), &score_head);//使用尾插法
Linux 的每個雙循環鏈表都有一個鏈表頭,鏈表頭也是一個節點,只不過它不嵌入到宿主數據結構中,即不能利用鏈表頭定位到對應的宿主結構,但可以由之獲得虛擬的宿主結構指針。
list_del:刪除節點
- /* Take an element out of its current list, with or without
- * reinitialising the links.of the entry*/
- static inline void list_del(struct list_head *entry)
- {
- struct list_head *list_next = entry->next;
- struct list_head *list_prev = entry->prev;
- list_next->prev = list_prev;
- list_prev->next = list_next;
- }
/* Take an element out of its current list, with or without
* reinitialising the links.of the entry*/
static inline void list_del(struct list_head *entry)
{
struct list_head *list_next = entry->next;
struct list_head *list_prev = entry->prev;
list_next->prev = list_prev;
list_prev->next = list_next;
}
list_entry:取出節點
- /**
- * list_entry - get the struct for this entry
- * @ptr:the &struct list_head pointer.
- * @type:the type of the struct this is embedded in.
- * @member:the name of the list_struct within the struct.
- */
- #define list_entry(ptr, type, member) \
- container_of(ptr, type, member)
/**
* list_entry - get the struct for this entry
* @ptr:the &struct list_head pointer.
* @type:the type of the struct this is embedded in.
* @member:the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
- /**
- * container_of - cast a member of a structure out to the containing structure
- * @ptr: the pointer to the member.
- * @type: the type of the container struct this is embedded in.
- * @member: the name of the member within the struct.
- *
- */
- #define container_of(ptr, type, member) ({ \
- const typeof(((type *)0)->member)*__mptr = (ptr); \
- (type *)((char *)__mptr - offsetof(type, member)); })
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
list_for_each:遍歷鏈表
- #define list_for_each(pos, head) \
- for (pos = (head)->next; prefetch(pos->next), pos != (head); \
- pos = pos->next)</span></span>
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)</span></span>
可以看出,使用了輔助指針pos,pos是從第一節點開始的,並沒有訪問頭節點,直到pos到達頭節點指針head的時候結束。
而且 這種遍歷僅僅是找到一個個結點的當前位置,那如何通過pos獲得起始結點的地址,從而可以引用結點的域?
list.h 中定義了 list_entry 宏:
#define list_entry( ptr, type, member ) \
( (type *) ( (char *) (ptr) - (unsigned long) ( &( (type *)0 ) -> member ) ) )
分析:(unsigned long) ( &( (type *)0 ) -> member ) 把 0 地址轉化爲 type 結構的指針,然後獲取該
結構中 member 域的指針,也就是獲得了 member 在type 結構中的偏移量。其中 (char *) (ptr) 求
出的是 ptr 的絕對地址,二者相減,於是得到 type 類型結構體的起始地址,即起始結點的地址。使用方法非常的巧妙!
在看linux的代碼中,我們可以瞭解到,其實在真正的項目中,鏈表要和業務模塊要分開,也就是說,鏈表要做到,插入刪除,創建等等的操作獨立。
我寫了一個小例子,大家應該可以看明白,是一個什麼意思:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Teacher
{
int age;
int id;//如果id是1就是代表是Teacher1如果id是2就代表是Teacher2
}Teacher;
typedef struct Teacher1
{
int age;
int id;
int num1;
}Teacher1;
typedef struct Teacher2
{
int age;
int id;
int num2;
}Teacher2;
int main(int argc,char**argv)
{
Teacher1 *t1 = (Teacher1*)malloc(sizeof(Teacher1));
Teacher2*t2 = (Teacher2*)malloc(sizeof(Teacher2));
t1->age = 22;
t1->id = 1;
t1->num1 = 33;
t2->age = 44;
t2->id = 2;
t2->num2 = 55;
int*address = &(t1->age);
if(((Teacher*)address)->id == 1)
{
printf("num = %d\n", ((Teacher1*)address)->num1);
}
//printf("%d\n", ((Teacher*)address)->id);//這就是企業級鏈表得使用方法
system("pause");
return 0;
}
我們可以看到,如果在結構體的首地址申明一個“節點”,讓他們去組成鏈表,然後再去使用強制類型轉化的方法去將一個節點轉化爲一個新的內容,從而實現一種類似多態的思想。
那麼接下來,我們需要做的事情就是將鏈表操作的方法做成接口,然後將接口進行一個包裝,放入DLL,或者SO中,讓外部使用,代碼如下:
//接口拋出
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define offscfof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define container_of(ptr,type,member) (type *)((char *)ptr-offscfof(type,member))
typedef struct _node{
struct _node *pNext;
}Node;
typedef struct _student1{
int num;
char name[20];
Node mynode;
}Student1;
//說明:結構體也可以寫成以下模式
//typedef struct _student1{
// Node mynode;
// int num;
// char name[20];
//}Student1;
//創建鏈表
int SList_Create(Node **pout/*out*/);
//獲取鏈表長度
int Get_List_Len(Node *pin/*in*/);
//查找指定位置節點
int FindNode(Node *pin/*in*/, Node **pdel/*out*/, int pos/*in*/);
//插入指定位置節點
int InsertOption(Node *pin/*in*/, Node *pnode/*in*/, int pos/*in*/);
//刪除指定節點
int RemoveNode(Node *pin/*in*/, Node **pdel/*out*/, int pos/*in*/);
void main(){
//創建鏈表指針
Node *phead;
int i = 0,j=0;
int ret = SList_Create(&phead);
//說明:爲什麼我要創建一個無用的頭節點
//理由①:不創建一個頭節點,那麼初始化函數SList_Create()就沒有必要存在
//理由②:插入第一個節點的時候無法插入,以爲沒有頭結點,所以插不進去第一個節點(這是主要理由)
if (ret!=0)
{
printf("創建鏈表頭節點失敗!\n");
}
//添加新節點
Student1 *pa = (Student1 *)malloc(sizeof(Student1));
pa->num = 1;
strcpy(pa->name, "小米");
pa->mynode.pNext = NULL;
ret=InsertOption(phead, &pa->mynode, Get_List_Len(phead));
if (ret != 0)
{
printf("添加新節點a失敗!\n");
goto END;
}
Student1 *pb = (Student1 *)malloc(sizeof(Student1));
pb->num = 1;
strcpy(pb->name, "小明");
pb->mynode.pNext = NULL;
ret = InsertOption(phead, &pb->mynode, Get_List_Len(phead));
if (ret != 0)
{
printf("添加新節點b失敗!\n");
goto END;
}
//打印出所有的節點
for (j = 0; j < Get_List_Len(phead); j++)
{
Node *temp = NULL;
Student1 *temp2 = NULL;
FindNode(phead, &temp, j);
if (temp==NULL)
{
printf("查詢節點失敗\n");
}
else{
temp2 = container_of(temp, Student1, mynode);
printf("學生的編號:%d;學生的姓名%s\n", temp2->num, temp2->name);
}
}
END:
//刪除所有鏈表節點
while (Get_List_Len(phead)){
Node *temp = NULL;
Student1 *temp2 = NULL;
RemoveNode(phead, &temp, 0);
temp2 = container_of(temp, Student1, mynode);
if (temp == NULL)
{
printf("節點刪除失敗!\n");
}
else{
if (temp2 != NULL)
{
free(temp2);
}
}
}
//釋放頭節點
if (phead==NULL)
{
free(phead);
}
system("pause");
}
//創建鏈表(順序創建鏈表)
int SList_Create(Node **pout/*out*/){
int ERRO_MSG = 0;
if (pout==NULL)
{
ERRO_MSG = 1;
printf("pout==NULL erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
Node *pM = (Node *)malloc(sizeof(Node));
pM->pNext = NULL;
*pout = pM;
return ERRO_MSG;
}
//獲取鏈表長度
int Get_List_Len(Node *pin/*in*/){
Node *pHead = NULL, *pCurrent = NULL;
int index = 0;
pCurrent = pin->pNext;
while (pCurrent){
pCurrent = pCurrent->pNext;
index++;
}
return index;
}
//查找指定位置節點
int FindNode(Node *pin/*in*/, Node **pnode/*out*/, int pos/*in*/){
int ERRO_MSG = 0;
if (pin == NULL || pnode == NULL)
{
ERRO_MSG = 1;
printf("pin == NULL || pnode==NULL erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
Node *pHead = NULL, *pCurrent = NULL, *pMalloc = NULL, *pPrior = NULL;
pCurrent = pPrior = pin->pNext;
if (pCurrent==NULL)
{
ERRO_MSG = 2;
printf("鏈表中暫時沒有數據 erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
int index = 0;
while (pCurrent){
if (index==pos)
{
*pnode = pCurrent;
break;
}
pPrior = pCurrent;
pCurrent = pCurrent->pNext;
index++;
}
if (*pnode==NULL)
{
ERRO_MSG = 3;
printf("鏈表中沒有找到該節點 erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
return ERRO_MSG;
}
//插入指定位置節點
int InsertOption(Node *pin/*in*/, Node *pnode/*in*/, int pos/*in*/){
int ERRO_MSG = 0;
if (pin == NULL || pnode==NULL)
{
ERRO_MSG = 1;
printf("pin == NULL || pnode==NULL erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
Node *pHead = NULL, *pCurrent = NULL, *pMalloc = NULL,*pPrior=NULL;
pHead = pPrior = pin;
pCurrent = pin->pNext;
pMalloc = pnode;
if (pCurrent==NULL)
{
if (pos==0)
{
pHead->pNext = pMalloc;
return ERRO_MSG;
}
else{
ERRO_MSG = 2;
printf("鏈表爲空,無法在指定位置插入節點\n", ERRO_MSG);
return ERRO_MSG;
}
}
int index = 0;
while (pCurrent){
if (pos == index)
{
pPrior->pNext = pMalloc;
pMalloc->pNext = pCurrent;
return ERRO_MSG;
}
pPrior = pCurrent;
pCurrent = pCurrent->pNext;
index++;
}
pPrior->pNext = pMalloc;
return ERRO_MSG;
}
//刪除指定節點
int RemoveNode(Node *pin/*in*/, Node **pdel/*out*/, int pos/*in*/){
int ERRO_MSG = 0;
if (pin == NULL || pdel==NULL)
{
ERRO_MSG = 1;
printf("pin == NULL || pdel==NULL erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
Node *pHead = NULL, *pCurrent = NULL, *pMalloc = NULL, *pPrior = NULL;
pHead = pPrior = pin;
pCurrent = pin->pNext;
if (pCurrent==NULL)
{
ERRO_MSG = 2;
printf("你要刪除的鏈表爲空! erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
int index = 0, flag = 0;
while (pCurrent){
if (index == pos)
{
pPrior->pNext = pCurrent->pNext;
*pdel = pCurrent;
break;
}
pPrior = pCurrent;
pCurrent = pCurrent->pNext;
index++;
}
if (*pdel==NULL)
{
ERRO_MSG = 3;
printf("鏈表中沒有該位置的節點! erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
return ERRO_MSG;
}