鏈表-雙向通用鏈表


前言

  • 20201014
  • 在閱讀 RTOS LiteOS 內核源碼時發現該內核使用的鏈表是通用鏈表,而 FreeRTOS 內核使用的是非通用鏈表,因此,有必要記錄一下關於鏈表實現的筆記。
  • 如下內容爲我的筆記,涉及一些非官方詞彙,敬請諒解,謝謝。

概念

  • 正常表達html

    • 鏈表:
      • 鏈表爲 C 中一種基礎的數據結構。
      • 當作環形晾衣架便可。
    • 節點:
      • 節點組成鏈表
  • 非通用鏈表自理解概念:節點攜帶信息node

    • 鏈表:圓形的晾衣架
    • 節點:掛鉤
      • 包含上一個
      • 下一個
      • 鉤子等其它須要的信息
    • 襪子:掛在到 鉤子 的東西
      • 包含被鉤子
      • 襪子攜帶的信息
  • 通用鏈表自理解概念:信息攜帶節點git

    • 鏈表:圓形的晾衣架
    • 節點:晾衣架圓形框的一截
      • 僅包含上一個
      • 下一個
    • 襪子:擺到晾衣架圓形框的一截上,使得節點成爲襪子的一個成員指針變量
      • 襪子攜帶的信息
      • 信息中包含節點
  • 通用鏈表與非通用鏈表的區別數據結構

    • 通用鏈表節點內容不多通常只有 上一個下一個
    • 通用鏈表節點被放到信息結構體中,經過偏移找到所在的結構體(便是經過偏移找到襪子頭)
    • 而非通用鏈表是在節點中攜帶信息結構體的指針的(便是節點就攜帶信息)。
    • 別人通俗理解,讀者沒必要理會本小點
      • 通用鏈表是把襪子放到晾衣架的圓形圈上,襪子與圓形圈接觸部分爲襪子接待的節點。(信息攜帶節點
      • 非通用鏈表是。(節點攜帶信息
      • 通用鏈表的 鏈-線 穿插於襪子中(襪子便是信息
      • 非通用鏈表的 鏈-線 連在鉤子,再由鉤子鉤襪子

筆錄草稿

雙向鏈表

  • 雙向鏈表理解圖

節點、鏈表及信息訪問 **

  • 節點
    • 成員僅有是一個和下一個
/*
 *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;
  • 鏈表ui

    • 多個節點組成鏈表
  • 信息訪問3d

    • 操做通用鏈表的最核心、最重要部分是經過偏移得到信息句柄襪子頭
      • 以下圖 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);
}

參考

相關文章
相關標籤/搜索