連結串列-雙向通用連結串列
阿新 • • 發佈:2020-10-16
[toc]
---
## 前言
* 20201014
* 在閱讀 RTOS LiteOS 核心原始碼時發現該核心使用的連結串列是**通用連結串列**,而 FreeRTOS 核心使用的是**非通用連結串列**,所以,有必要記錄一下關於連結串列實現的筆記。
* 以下內容為個人筆記,涉及一些非官方詞彙,敬請諒解,謝謝。
## 概念
* 正常表達
* 連結串列:
* 連結串列為 C 中一種基礎的資料結構。
* 看成環形晾衣架即可。
* 節點:
* 節點組成連結串列
* **非通用連結串列**自理解概念:**節點攜帶資訊**
* 連結串列:圓形的晾衣架
* 節點:掛鉤
* 包含上一個
* 下一個
* 鉤子等其它需要的資訊
* 襪子:掛在到 **鉤子** 的東西
* 包含**被鉤子**
* 襪子攜帶的資訊
* **通用連結串列**自理解概念:**資訊攜帶節點**
* 連結串列:圓形的晾衣架
* 節點:晾衣架圓形框的一截
* 僅包含上一個
* 下一個
* 襪子:擺到晾衣架圓形框的一截上,使得節點成為襪子的一個成員指標變數
* 襪子攜帶的資訊
* 資訊中包含**節點**
* **通用連結串列與非通用連結串列的區別**
* 通用連結串列節點內容很少一般只有 **上一個** 和 **下一個**。
* 通用連結串列節點被放到資訊結構體中,通過偏移找到所在的結構體(即是通過偏移找到襪子頭)
* 而非通用連結串列是在節點中攜帶資訊結構體的指標的(即是節點就攜帶資訊)。
* ***別人通俗理解,讀者不必理會本小點***
* 通用連結串列是把襪子放到晾衣架的圓形圈上,襪子與圓形圈接觸部分為襪子接待的節點。(***資訊攜帶節點***)
* 非通用連結串列是。(***節點攜帶資訊***)
* 通用連結串列的 **鏈-線** 穿插於襪子中(***襪子即是資訊***)
* 非通用連結串列的 **鏈-線** 連在鉤子,再由鉤子鉤襪子
## 筆錄草稿
## 雙向連結串列
* 雙向連結串列理解圖
![](https://img2020.cnblogs.com/blog/2085252/202010/2085252-20201010151036592-974377954.png)
### 節點、連結串列及資訊訪問 **
* **節點**
* 成員僅有是一個和下一個
![](https://img2020.cnblogs.com/blog/2085252/202010/2085252-20201015215900799-894349035.png)
```c
/*
*Structure of a node in a doubly linked list.
*/
typedef struct LSS_LIST
{
struct LSS_LIST *pstPrev; /**< Current node's pointer to the previous node*/
struct LSS_LIST *pstNext; /**< Current node's pointer to the next node*/
} LSS_LIST;
typedef struct LSS_LIST listItem_t;
```
* **連結串列**
* 多個**節點**組成**連結串列**
![](https://img2020.cnblogs.com/blog/2085252/202010/2085252-20201015215917630-763589721.png)
* **資訊訪問**
* **操作通用連結串列的最核心、最重要部分是通過偏移獲得資訊控制代碼**(*襪子頭*)
* 如下圖 **C** 中的長度就是節點與資訊控制代碼的偏移長度,只需知道 **節點地址、資訊型別(結構體型別)及成員名字(即是當前節點在結構體中的成員名字)**即可獲得資訊控制代碼
![](https://img2020.cnblogs.com/blog/2085252/202010/2085252-20201015215929400-498315553.png)
```c
/*
* @param item Current node's pointer.
* @param type Structure name of type.
* @param member Member name of the doubly linked list in the structure.
*/
#define LSS_LIST_ENTRY(item, type, member) \
((type *)((char *)(item) - (unsigned long)(&((type *)0)->member)))
```
### 操作程式碼及闡述
* 以下只是通用連結串列的一些擴充套件例子,更多的可以自己象限+實現。
#### 1. 初始化連結串列
* 上一個指向自己
* 下一個指向自己
```c
/**
* @brief 連結串列初始化
* @param pstList:需要初始化的連結串列(節點)指標
* @retval none
* @author lzm
*/
void listInit(listItem_t *pstList)
{
pstList->pstNext = pstList;
pstList->pstPrev = pstList;
}
```
#### 2. 獲取第一個節點
* 指向當前節點的下一個節點
* *第一個即是下一個*
```c
/**
* @brief 獲取第一個節點
* @param pstObject:當前節點指標
* @retval none
* @author lzm
*/
#define listGetFirst(pstObject) ((pstObject)-> pstNext)
```
#### 3. 插入一個節點(頭)
* 插入當前節點後面
* 先處理需要插入的節點 **外指向**
* 再處理需要插入的節點 **內指向**
```c
/**
* @brief 插入當前節點後面
* @param pstList:連結串列(也是當前節點)
* @param pstNode:節點(需要插入的節點)
* @retval none
* @author lzm
*/
void listAdd(LSS_LIST *pstList, LSS_LIST *pstNode)
{
pstNode->pstNext = pstList->pstNext;
pstNode-> pstPrev = pstList;
pstList->pstNext->pstPrev = pstNode;
pstList->pstNext = pstNode;
}
```
#### 4. 插入一個節點(尾)
* 插入連結串列尾部(*即是插入當前節點的前面*)
```c
/**
* @brief 插入連結串列尾部
* @param pstList:連結串列(也是當前節點)
* @param pstNode:節點(需要插入的節點)
* @retval none
* @author lzm
*/
void listTailInsert(LSS_LIST *pstList, LSS_LIST *pstNode)
{
listAdd(pstList-> pstPrev, pstNode); // 把當前節點的前一個節點作為參考即可
}
```
#### 5. 刪除一個節點
* 刪除當前節點
* 先處理需要刪除的節點 **內指向**
* 再處理需要刪除的節點 **外指向**
```c
/**
* @brief 刪除當前節點
* @param pstNode:節點(需要刪除的節點)
* @retval none
* @author lzm
*/
void listDelete(LSS_LIST *pstNode)
{
pstNode->pstNext->pstPrev = pstNode->pstPrev;
pstNode->pstPrev->pstNext = pstNode->pstNext;
pstNode->pstNext = (LSS_LIST *)NULL;
pstNode->pstPrev = (LSS_LIST *)NULL;
}
```
#### 6. 判斷一個連結串列是否為空
* 判斷該連結串列節點是否指向 初始化時的值即可。
```c
/**
* @brief 刪除當前節點
* @param pstNode:節點(需要刪除的節點)
* @retval TRUE:連結串列為空
* @retval FALSE:連結串列不為空
* @author lzm
*/
bool listEmpty(LSS_LIST *pstNode)
{
return (bool)(pstNode->pstNext == pstNode);
}
```
#### 7. 獲取到資訊控制代碼的偏移 *
* 通過 **資訊結構體型別、資訊結構體中的成員名字** 可以獲得該 **名字** 到資訊控制代碼的偏移。
```c
/**
* @brief 獲取到資訊控制代碼的偏移
* @param type:資訊結構體型別
* @param member:成員名字,即是欄位(域)
* @retval 偏移長度(單位:byte)
* @author lzm
*/
#define getOffsetOfMenber(type, member) ((uint32_t)&(((type *)0)->member))
```
#### 8. 獲取節點所在的資訊控制代碼 *
* 即是獲取 節點 所在的資訊結構體地址
```c
/**
* @brief 獲取節點所在的資訊控制代碼
* @param type:資訊結構體型別
* @param member:成員名字,即是欄位(域)
* @retval 返回節點所在的資訊控制代碼
* @author lzm
*/
#define getItemDataHandle(item, type, member) \
((type *)((char *)item - getOffsetOfMenber(type, member))) \
```
#### 9. 遍歷連結串列
```c
/**
* @brief 刪除節點並重新初始化
* @param pstList:需要重新初始化的連結串列節點
* @retval
* @author lzm
*/
#define LIST_FOR_EACH(item, list) \
for ((item) = (list)->pstNext; \
(item) != (list); \
(item) = (item)->pstNext)
```
#### 10. 遍歷整個連結串列並獲得資訊控制代碼(巨集) *
* 本巨集並非為一個完整的語句,僅僅是一個 **for** 語句,做一個連結串列遍歷。
```c
/**
* @brief 遍歷整個連結串列並獲得資訊控制代碼(巨集)
* @param handle:儲存目標節點資訊控制代碼
* @param item:需要遍歷的連結串列(節點)
* @param type:資訊型別(結構體名)
* @param member:該連結串列在 type 中的名字
* @retval 就是也該for語句
* @author lzm
*/
#define LIST_FOR_EACH_HANDEL(handle, list, type, member) \
for (handle = getItemDataHandle((list)->pstNext, type, member); \
&handle->member != (list); \
handle = getItemDataHandle(handle->member.pstNext, type, member))
```
#### 11. 刪除節點並重新初始化
* 先從連結串列中刪除本節點
* 再重新初始化本節點
```c
void osListDel(LSS_LIST *pstPrevNode, LSS_LIST *pstNextNode)
{
pstNextNode->pstPrev = pstPrevNode;
pstPrevNode->pstNext = pstNextNode;
}
/**
* @brief 刪除節點並重新初始化
* @param pstList:需要重新初始化的連結串列節點
* @retval
* @author lzm
*/
void listDelInit(LSS_LIST *pstList)
{
osListDel(pstList->pstPrev, pstList->pstNext);
listInit(pstList);
}
```
### 參考
* 連結
* [我的Gitee](https://gitee.com/lidreaming)
* [非通用連結串列完整C語言原始碼](https://www.cnblogs.com/lizhuming/p/13792662.html)
* LiteOS 內