目錄
前言
- 20201014
- 在閱讀 RTOS LiteOS 核心原始碼時發現該核心使用的連結串列是通用連結串列,而 FreeRTOS 核心使用的是非通用連結串列,所以,有必要記錄一下關於連結串列實現的筆記。
- 以下內容為個人筆記,涉及一些非官方詞彙,敬請諒解,謝謝。
概念
-
正常表達
- 連結串列:
- 連結串列為 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;
-
連結串列
- 多個節點組成連結串列
- 多個節點組成連結串列
-
資訊訪問
- 操作通用連結串列的最核心、最重要部分是通過偏移獲得資訊控制程式碼(襪子頭)
- 如下圖 C 中的長度就是節點與資訊控制程式碼的偏移長度,只需知道 節點地址、資訊型別(結構體型別)及成員名字(即是當前節點在結構體中的成員名字)即可獲得資訊控制程式碼
- 如下圖 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. 初始化連結串列
- 上一個指向自己
- 下一個指向自己
/**
* @brief 連結串列初始化
* @param pstList:需要初始化的連結串列(節點)指標
* @retval none
* @author lzm
*/
void listInit(listItem_t *pstList)
{
pstList->pstNext = pstList;
pstList->pstPrev = pstList;
}
2. 獲取第一個節點
- 指向當前節點的下一個節點
- 第一個即是下一個
/**
* @brief 獲取第一個節點
* @param pstObject:當前節點指標
* @retval none
* @author lzm
*/
#define listGetFirst(pstObject) ((pstObject)->pstNext)
3. 插入一個節點(頭)
- 插入當前節點後面
- 先處理需要插入的節點 外指向
- 再處理需要插入的節點 內指向
/**
* @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. 插入一個節點(尾)
- 插入連結串列尾部(即是插入當前節點的前面)
/**
* @brief 插入連結串列尾部
* @param pstList:連結串列(也是當前節點)
* @param pstNode:節點(需要插入的節點)
* @retval none
* @author lzm
*/
void listTailInsert(LSS_LIST *pstList, LSS_LIST *pstNode)
{
listAdd(pstList->pstPrev, pstNode); // 把當前節點的前一個節點作為參考即可
}
5. 刪除一個節點
- 刪除當前節點
- 先處理需要刪除的節點 內指向
- 再處理需要刪除的節點 外指向
/**
* @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. 判斷一個連結串列是否為空
- 判斷該連結串列節點是否指向 初始化時的值即可。
/**
* @brief 刪除當前節點
* @param pstNode:節點(需要刪除的節點)
* @retval TRUE:連結串列為空
* @retval FALSE:連結串列不為空
* @author lzm
*/
bool listEmpty(LSS_LIST *pstNode)
{
return (bool)(pstNode->pstNext == pstNode);
}
7. 獲取到資訊控制程式碼的偏移 *
- 通過 資訊結構體型別、資訊結構體中的成員名字 可以獲得該 名字 到資訊控制程式碼的偏移。
/**
* @brief 獲取到資訊控制程式碼的偏移
* @param type:資訊結構體型別
* @param member:成員名字,即是欄位(域)
* @retval 偏移長度(單位:byte)
* @author lzm
*/
#define getOffsetOfMenber(type, member) ((uint32_t)&(((type *)0)->member))
8. 獲取節點所在的資訊控制程式碼 *
- 即是獲取 節點 所在的資訊結構體地址
/**
* @brief 獲取節點所在的資訊控制程式碼
* @param type:資訊結構體型別
* @param member:成員名字,即是欄位(域)
* @retval 返回節點所在的資訊控制程式碼
* @author lzm
*/
#define getItemDataHandle(item, type, member) \
((type *)((char *)item - getOffsetOfMenber(type, member))) \
9. 遍歷連結串列
/**
* @brief 刪除節點並重新初始化
* @param pstList:需要重新初始化的連結串列節點
* @retval
* @author lzm
*/
#define LIST_FOR_EACH(item, list) \
for ((item) = (list)->pstNext; \
(item) != (list); \
(item) = (item)->pstNext)
10. 遍歷整個連結串列並獲得資訊控制程式碼(巨集) *
- 本巨集並非為一個完整的語句,僅僅是一個 for 語句,做一個連結串列遍歷。
/**
* @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. 刪除節點並重新初始化
- 先從連結串列中刪除本節點
- 再重新初始化本節點
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);
}
參考
- 連結
- LiteOS 核心原始碼