"Good code is its own best documentation." - Steve McConnell “好代碼本身就是最好的文檔。” —— 史蒂夫·麥克康奈爾 0x00 大綱 0x01 前言 數據與結構的解耦 在上篇文章,我們通過將鏈表的節點放在具體數據類型的結構體內,這樣,抽象 ...
"Good code is its own best documentation." - Steve McConnell
“好代碼本身就是最好的文檔。” —— 史蒂夫·麥克康奈爾
0x00 大綱
目錄0x01 前言
數據與結構的解耦
在上篇文章,我們通過將鏈表的節點放在具體數據類型的結構體內,這樣,抽象(鏈表結構)不再依賴於細節(數據類型),細節(數據類型)依賴於抽象(鏈表結構),利用依賴倒置的思想,完成了數據與結構的解耦,進而實現了通用的鏈表。
offsetof
offsetof
是定義在C標準庫頭文件<stddef.h>
中的一個巨集,它會生成一個類型為size_t
的無符號整型,代表一個結構成員相對於結構體起始的位元組偏移量(offset)。
container_of
cotainer_of
返回的是結構體成員所在結構體的起始地址(指針),它的原理是用成員變數的起始地址減去成員變數在結構體內的偏移量(用offsetof
求得)。
通用鏈表
有了上面三個理論基礎,我們就具備了創建通用鏈表的條件。下麵將通過具體的代碼來演示如何構造和使用這樣的鏈表結構,全程圖解,包你學會。
0x02 鏈表節點
我們的通用鏈表是一個雙向迴圈鏈表,它由一個鏈表頭節點list_head
和若幹個位於結構體中的中間節點list_node
(註意不包括數據域部分)構成。
我們定義了一個名為struct list_head
的結構體類型作為我們的鏈表節點,它包含一個指向前驅節點的指針*prev
和一個指向後繼節點的指針*next
。同時,為了方便後續的編碼和增強代碼的可讀性,又定義了list_head
和list_node
兩個結構體類型別名,它們是同一種數據類型的不同名稱。
typedef struct list_head
{
struct list_head *next, *prev;
} list_head, list_node;
下麵的代碼簡單說明瞭這種方法給我們帶來的語義上的便利,後面的代碼示例將延續這樣的風格。
list_head head;// 等價於 struct list_head head;
list_node node;// 等價於 struct list_head node;
0x03 創建鏈表
/**
* 初始化一個鏈表(頭)節點,它的前驅和後繼指針都指向自己
* @param list: 需要初始化的節點的指針
* @return void
**/
static inline void list_init(list_head *list)
{
list->next = list;
list->prev = list;
}
0x04 插入節點
任意位置的插入
/**
* 將節點entry插入到prev和next之間
* @param entry: 新節點的指針
* @param prev: 指向插入位置前驅節點的指針
* @param next: 指向插入位置後繼節點的指針
* @return void
**/
static inline void __list_add(list_node *entry,
list_node *prev,
list_node *next)
{
next->prev = entry;
entry->next = next;
entry->prev = prev;
prev->next = entry;
}
插入到最前
/**
* 將節點entry插入到頭節點之後
* 頭插,新節點成為第一個節點
* @param entry: 指向新節點的指針
* @param head: 指向頭節點的指針
* @return void
**/
static inline void list_add_head(list_node *entry,
list_head *head)
{
__list_add(entry, head, head->next);
}
插入到最後
/**
* 將節點entry插入到頭節點之前
* 尾插,新節點成為最後一個節點
* @param entry: 指向新節點的指針
* @param head: 指向頭節點的指針
* @return void
**/
static inline void list_add_tail(list_node *entry,
list_head *head)
{
__list_add(entry, head->prev, head);
}
0x05 刪除節點
/**
* 刪除節點
* @param prev: 被刪除節點的前驅指針
* @param head: 被刪除節點的後繼指針
* @return void
**/
static inline void __list_del(list_node * prev,
list_node * next)
{
next->prev = prev;
prev->next = next;
}
/**
* 刪除節點,並將其前驅指針和後繼指針指向NULL
* @param prev: 指向被刪除節點的指針
* @return void
**/
static inline void list_del(list_node *entry)
{
__list_del(entry->prev, entry->next);
entry->prev = entry->next = NULL;
}
可以看到,由於節點本身並不存儲數據,所以,在刪除鏈表節點的時候,也就不用考慮對數據域進行記憶體釋放的操作。
0x06 替換節點
/**
* 替換節點
* @param old: 指向被替換節點的指針
* @param entry: 指向新節點的指針
* @return void
**/
static inline void list_replace(list_node *old,
list_node *entry)
{
entry->next = old->next;
entry->next->prev = entry;
entry->prev = old->prev;
entry->prev->next = entry;
}
0x07 遍歷和獲取節點數據
遍歷鏈表
/**
* 快速遍歷鏈表(不可進行刪除操作)
* @param pos: 指向當前節點位置的指針
* @param head: 指向頭節點的指針
* @return void
**/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* 遍歷鏈表(可進行刪除操作)
* @param pos: 指向當前節點位置的指針
* @param n: 指向下一節點位置的指針
* @param head: 指向頭節點的指針
* @return void
**/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
獲取節點數據
/**
* 獲得節點所在數據結構體的起始地址(指針)
* @param ptr: 指向節點的指針
* @param type: 數據結構體類型
* @param member: 節點在數據結構體中被定義的變數名稱
* @return void
**/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
0x08 小結
將上述的所有基本操作彙總後,得到我們的通用鏈表定義文件list.h
(你可以在Linux內核的源碼中找到它,這裡的代碼稍微作了一點修改):
#ifndef LIST_H
#define LIST_H
#include <stddef.h>
#define container_of(ptr, type, member) \
((type *)((char *)(ptr)-offsetof(type,member)))
typedef struct list_head
{
struct list_head *next, *prev;
} list_head, list_node;
/**
* 初始化一個鏈表(頭)節點,它的前驅和後繼指針都指向自己
* @param list: 需要初始化的節點的指針
* @return void
**/
static inline void list_init(list_head *list)
{
list->next = list;
list->prev = list;
}
/**
* 將節點entry插入到prev和next之間
* @param entry: 新節點的指針
* @param prev: 指向插入位置前驅節點的指針
* @param next: 指向插入位置後繼節點的指針
* @return void
**/
static inline void __list_add(list_node *entry,
list_node *prev,
list_node *next)
{
next->prev = entry;
entry->next = next;
entry->prev = prev;
prev->next = entry;
}
/**
* 將節點entry插入到頭節點之後
* 頭插,新節點成為第一個節點
* @param entry: 指向新節點的指針
* @param head: 指向頭節點的指針
* @return void
**/
static inline void list_add_head(list_node *entry,
list_head *head)
{
__list_add(entry, head, head->next);
}
/**
* 將節點entry插入到頭節點之前
* 尾插,新節點成為最後一個節點
* @param entry: 指向新節點的指針
* @param head: 指向頭節點的指針
* @return void
**/
static inline void list_add_tail(list_node *entry,
list_head *head)
{
__list_add(entry, head->prev, head);
}
/**
* 刪除節點
* @param prev: 被刪除節點的前驅指針
* @param head: 被刪除節點的後繼指針
* @return void
**/
static inline void __list_del(list_node * prev,
list_node * next)
{
next->prev = prev;
prev->next = next;
}
/**
* 刪除節點,並將其前驅指針和後繼指針指向NULL
* @param prev: 指向被刪除節點的指針
* @return void
**/
static inline void list_del(list_node *entry)
{
__list_del(entry->prev, entry->next);
entry->prev = entry->next = NULL;
}
/**
* 替換節點
* @param old: 指向被替換節點的指針
* @param entry: 指向新節點的指針
* @return void
**/
static inline void list_replace(list_node *old,
list_node *entry)
{
entry->next = old->next;
entry->next->prev = entry;
entry->prev = old->prev;
entry->prev->next = entry;
}
/**
* 判斷迴圈雙鏈表是否為空(只有頭節點)
* @param head: 指向頭節點的指針
* @return void
**/
static inline int list_empty(const list_head *head)
{
return head->next == head;
}
/**
* 快速遍歷鏈表(不可進行刪除操作)
* @param pos: 指向當前節點位置的指針
* @param head: 指向頭節點的指針
* @return void
**/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* 遍歷鏈表(可進行刪除操作)
* @param pos: 指向當前節點位置的指針
* @param n: 指向下一節點位置的指針
* @param head: 指向頭節點的指針
* @return void
**/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/**
* 獲得節點所在數據結構體的起始地址(指針)
* @param ptr: 指向節點的指針
* @param type: 數據結構體類型
* @param member: 節點在數據結構體中被定義的變數名稱
* @return void
**/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
#endif // LIST_H