1. 程式人生 > >連結串列-雙向通用連結串列

連結串列-雙向通用連結串列

[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 內