C语言通用双向循环链表操作函数集

说明

相比Linux内核链表宿主结构可有多个链表结构的优点,本函数集侧重封装性和易用性,而灵活性和效率有所降低。
     可基于该函数集方便地构造栈或队列集。
     本函数集暂未考虑并发保护。

一  概念

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序通过链表中的指针链接次序实现。链表由一系列存储结点组成,结点可在运行时动态生成。每个结点均由两部分组成,即存储数据元素的数据域和存储相邻结点地址的指针域。当进行插入或删除操作时,链表只需修改相关结点的指针域即可,因此相比线性表顺序结构更加方便省时。

链表可分为单链表(Singly Linked List)、双向链表(Doubly Linked List)和循环链表(Circular List)等。一个单链表结点仅包含一个指向其直接后继结点的指针,因此当需要操作某个结点的直接前驱结点时,必须从单链表表头开始查找。

双向链表和循环链表均为单链表的变体。通常创建双向循环链表以综合利用两者的优点。

1.1 双向链表

双向链表的每个结点除含有数据域外,还有两个指针域,分别指向直接前驱结点和直接后继结点。因此,从双向链表中的任一结点开始,均可方便地访问其前驱结点和后继结点。双向链表的结点结构示意图如下所示:

图1 双向链表的结点结构

其中,Data为结点存储的数据元素,prev指针指向该结点的前驱结点,next指针指向该结点的后继结点。双向链表通常含有一个表头结点,亦称哨兵结点(Sentinel Node),用于简化插入和删除等操作。带头结点的非空双向链表如下图所示:

图2 带头结点的非空双向链表

图中,表头指针dhead指向表头结点Head,该结点的前驱指针为空;结点C为表尾结点,其后继指针为空。除表头结点和表尾结点外,对指向双向链表任一结点的指针p,满足下面的关系:

p = p->prev->next = p->next->prev

即当前结点前驱的后继是自身,其后继的前驱也是自身。

链表有查找、插入和删除三种基本操作。双向链表也不例外。

1) 查找操作

在带表头的双向链表中查找数据域为一特定值的某个结点时,可从表头结点开始向后依次匹配各结点数据域的值,若与特定值相同则返回指向该结点的指针,否则继续往后遍历直至表尾。

2) 插入操作

假设指针p和q指向双向链表中的两个前后相邻结点,将某个新结点(指针为s)插到p和q之间,其过程及C语言描述如下图所示:

图3 在双向链表中插入结点的过程

注意,结点前驱后继指针的操作顺序并非唯一,但必须保证最后才对p->next或q->prev赋值(操作?),否则会“丢失”p的后继结点或q的前驱结点。

可见,若相邻结点指针p、q均已知,则在p和q之间插入新结点s时,只需依次将s的前驱指针指向p,s的后继指针指向q,p的后继指针指向s,q的前驱指针指向s。即:


① s->prev = p;

② s->next = q;

③ p->next = s;

④ q->prev = s;

双向链表中p和q->prev指向同一结点,因此上述步骤等效于图3中q“视角”的第二种插入顺序。为便于记忆,可想象孩子(s)先后去拉爸爸(p)和妈妈(q)的手,爸爸(p)妈妈(q)再先后拉住孩子(s)的手。

3) 删除操作

删除某个结点,其实就是插入某个结点的逆操作。还是对于双向循环链表,要在连续的三个结点s,p,q中删除p结点,只需把s的右链域指针指向q,q的左链域指针指向s,并收回p结点即可。

假设指针p、s和q指向双向链表中的三个前后相邻结点,删除结点s的过程及C语言描述如下图所示:

图4 在双向链表中删除结点的过程

可见,删除时只需将p的后继指针指向q,q的前驱指针指向p,并回收结点s即可。

1.2 循环链表

将单链表尾结点的指针域指向第一个结点或表头结点,即构成单向循环链表,简称循环链表。从循环链表中任一结点单向出发,均可找到链表中其他结点。

借助表头结点可统一空表和非空表的运算,因此循环链表中往往加入表头结点。带头结点的循环链表如下图所示:

图5 带头结点的循环链表(头指针)

循环链表的操作算法与普通单链表基本相同,只是对表尾的判断有所改变。在循环链表chead中,判断表尾结点p的条件是p->next == chead,即当结点的后继指针指向表头结点时,说明已到表尾。

注意,创建循环链表时必须使其尾结点的后继指针指向表头结点,尤其是在尾结点后插入新结点时。

弃用头指针而采用尾指针,可方便地找到循环链表的开始结点和终端结点。如下图所示:

图6 带头结点的循环链表(尾指针)

1.3 双向循环链表

双向链表通常采用带表头结点的循环链表形式,即双向循环链表。双向循环链表在双向链表的基础上,将表头结点的前驱指针指向尾结点,尾结点的后驱指针指向头结点,首尾相连形成一个双向环。双向循环链表可方便地获取当前结点的前驱结点,不必像单向循环链表那样从头开始遍历;而其循环的特性又可方便地从任一结点出发单向遍历整个链表,不必像双向链表那样根据方向而使用不同的指针域。

带头结点的双向循环链表如下图所示:

图7 带头结点的双向循环链表

二  实现

本节将采用C语言实现一个通用双向循环链表的创建及操作函数集。

文中“OMCI_”和“Omci”前缀为代码所在模块名信息,使用接口时可按需修改这些前缀。

2.1 数据结构

定义双向循环链表单元结构示意如下:

图8 双向循环链表单元结构示意图

其中,根结点的pHead字段指向链表头结点,pTail字段指向链表尾结点。头结点的pPrev字段指向尾结点,尾结点的pNext字段指向头结点。若链表为空(仅含头结点),则pHead和pTail字段均指向头结点。

链表结点定义如下:

1 typedef struct T_OMCI_LIST_NODE{
2     struct T_OMCI_LIST_NODE  *pPrev;      /* 指向链表直接前驱结点的指针 */
3     struct T_OMCI_LIST_NODE  *pNext;      /* 指向链表直接后继结点的指针 */
4     VOID                     *pvNodeData; /* 指向链表数据的指针。获取具体数据时需显式转换该指针类型为目标类型 */
5 }T_OMCI_LIST_NODE;

相应地,链表定义如下:

1 typedef struct{
2     T_OMCI_LIST_NODE   *pHead;          /* 指向链表头结点的指针 */
3     T_OMCI_LIST_NODE   *pTail;          /* 指向链表尾结点的指针 */
4     INT32U             dwNodeNum;       /* 链表结点数目 */
5     INT32U             dwNodeDataSize;  /* 链表结点保存的数据字节数 */
6 }T_OMCI_LIST;

为支持不同的数据类型和数据结构(通用性),链表结点数据域定义为VOID *pvNodeData指针。变量dwNodeDataSize指示数据域的数据宽度(字节数)。也可将数据宽度信息存储于头结点数据域内,从而不必定义变量dwNodeDataSize。通过遍历链表并计数可得结点数目,故变量dwNodeNum也并非必要。因此,dwNodeDataSize和dwNodeNum意在简化逻辑,也是“空间换时间”思想的体现。

除此之外,还定义以下状态值,以使链表内部状态透明化:

 1 //链表函数返回状态枚举值
 2 typedef enum{
 3     OMCI_LIST_OK    = (INT8U)0,
 4     OMCI_LIST_ERROR = (INT8U)1
 5 }LIST_STATUS;
 6
 7 //链表结点空闲情况枚举值
 8 typedef enum{
 9     OMCI_LIST_OCCUPIED = (INT8U)0,
10     OMCI_LIST_EMPTY    = (INT8U)1,
11     OMCI_LIST_NULL     = (INT8U)2
12 }LIST_OCCUPATION;
13
14 //BOOL型常量,适用于‘Is‘前缀函数
15 #define  OMCI_LIST_TRUE   (BOOL)1
16 #define  OMCI_LIST_FALSE  (BOOL)0

2.2 宏代码

为确保安全性,链表操作中需要进行大量的指针校验。因此,定义几个校验空指针的宏,以简化代码篇幅:

 1 #define   FUNC_NAME    __FUNCTION__ //(__func__)
 2
 3 /* 指针校验宏 */
 4 //若无返回值则retVal置RETURN_VOID
 5 #define RETURN_VOID
 6 #define CHECK_SINGLE_POINTER(ptr1, retVal) do{ 7     if(NULL == (ptr1))
 8     {  9         printf("[%s(%d)]Null Pointer: "#ptr1"!\n\r", FUNC_NAME, __LINE__); 10         return retVal; 11     } 12 }while(0)
13 #define CHECK_DOUBLE_POINTER(ptr1, ptr2, retVal) do{14     if((NULL == (ptr1)) || (NULL == (ptr2))) 15     { 16         printf("[%s(%d)]Null Pointer: "#ptr1"(%p), "#ptr2"(%p)!\n\r", FUNC_NAME, __LINE__, ptr1, ptr2); 17         return retVal; 18     } 19 }while(0)
20 #define CHECK_TRIPLE_POINTER(ptr1, ptr2, ptr3, retVal) do{21     if((NULL == (ptr1)) || (NULL == (ptr2)) || (NULL == (ptr3))) 22     { 23         printf("[%s(%d)]Null Pointer: "#ptr1"(%p), "#ptr2"(%p), "#ptr3"(%p)!\n\r", FUNC_NAME, __LINE__, ptr1, ptr2, ptr3); 24         return retVal; 25     } 26 }while(0)

若待检查的指针中至少有一个指针为空时,校验宏打印所有待检查的指针值并退出。但其实现使得下面的语句在pList为空时崩溃(打印时试图访问pList->pHead等):

CHECK_TRIPLE_POINTER(pList, pList->pHead, pList->pHead->pNext, OMCI_LIST_ERROR);

因此必须使用下面的分级校验以避免多级指针前级为NULL时访问本级出错:

CHECK_SINGLE_POINTER(pList, OMCI_LIST_ERROR);

CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_ERROR);

CHECK_SINGLE_POINTER(pList->pHead->pNext, OMCI_LIST_ERROR);

若不打印各指针的值,则逻辑或(||)的运算顺序足矣保证CHECK_TRIPLE_POINTER写法的安全性。

注意,这些指针校验宏大量应用于2.3节函数接口中,以保证其安全性。使用者若能在外部杜绝空指针引用,则可添加条件编译开关“剔除”这些校验宏,以提高代码执行效率。

对于链表结点的操作比较固定,因此也用宏定义加以封装:

 1 //创建结点为作为链表头以生成双向循环空链表
 2 #define OMCI_INIT_NODE(pNode) do{  3     (pNode)->pNext = (pNode)->pPrev = (pNode);  4 }while(0)
 5 //"孤立"链表结点,避免通过该结点访问其前驱和后继结点(进而遍历链表)
 6 #define OMCI_ISOL_NODE(pNode) do{  7     (pNode)->pNext = (pNode)->pPrev = NULL;  8 }while(0)
 9 //判断链表是否仅含头结点
10 #define OMCI_LIST_WITH_HEAD(pHeadNode) do{ 11     (((pHeadNode)->pPrev == (pHeadNode)) && ((pHeadNode->pNext == pHeadNode))); 12 }while(0)
13
14 //插入链表结点
15 #define OMCI_INSERT_NODE(prevNode, insertNode) do{ 16     (insertNode)->pNext      = (prevNode)->pNext;  17     (insertNode)->pPrev      = (prevNode);         18     (prevNode)->pNext->pPrev = (insertNode);       19     (prevNode)->pNext        = (insertNode);       20 }while(0)
21 //删除链表结点
22 #define OMCI_REMOVE_NODE(removeNode) do{ 23     (removeNode)->pPrev->pNext = (removeNode)->pNext;  24     (removeNode)->pNext->pPrev = (removeNode)->pPrev;  25 }while(0)
26
27 //获取链表结点及其数据(不做安全性检查)
28 #define GET_NODE_NUM(pList)      ((pList)->dwNodeNum)
29 #define GET_HEAD_NODE(pList)     ((pList)->pHead)
30 #define GET_TAIL_NODE(pList)     ((pList)->pTail)
31 #define GET_PREV_NODE(pNode)     ((pNode)->pPrev)
32 #define GET_NEXT_NODE(pNode)     ((pNode)->pNext)
33 #define GET_NODE_DATA(pNode)     ((pNode)->pvNodeData)
34
35 //双向循环链表遍历校验宏
36 #define LIST_ITER_CHECK(pList, retVal) do{37     CHECK_SINGLE_POINTER((pList), retVal); 38     CHECK_SINGLE_POINTER((pList)->pHead, retVal); 39     CHECK_SINGLE_POINTER((pList)->pHead->pNext, retVal); 40 }while(0)
41 //双向循环链表遍历宏
42 //pList: 链表指针;pLoopNode: 链表结点,用作循环计数器;
43 //pTmpNode: 链表结点,用作删除pLoopNode时临时保存pLoopNode->pNext
44 //某些情况下采用遍历宏代替OmciLocateListNode或OmciTraverseListNode函数可提高执行效率。
45 //如外部数据和结点数据需按共同的规则转换时,采用遍历宏可使外部数据不必重复转换。
46 #define LIST_ITER_LOOP(pList, pLoopNode) 47   for(pLoopNode = (pList)->pHead->pNext; 48       pLoopNode != (pList)->pHead; 49       pLoopNode = pLoopNode->pNext)
50 #define LIST_ITER_LOOP_SAFE(pList, pLoopNode, pTmpNode) 51   for(pLoopNode = (pList)->pHead->pNext, pTmpNode = pLoopNode->pNext; 52       pLoopNode != (pList)->pHead; 53       pLoopNode = pTmpNode, pTmpNode = pLoopNode->pNext)

结点的插入和删除操作可参考1.1节双向链表的图例。GET_HEAD_NODE等宏可高效(但不安全)地获取链表结点及其数据,后续将给出其函数版本。LIST_ITER_LOOP宏旨在给使用者提供一定程度的自由度,某些情况下可提高执行效率。

2.3 函数接口

首先定义一组私有函数,主要是创建、删除和销毁链表结点。这些内部使用的函数已尽可能保证参数安全性,故省去参数校验处理。

为简便起见,下文中“XX指针”均表示指向XX的指针。

创建新的链表结点:

 1 /**********************************************************************
 2 * 函数名称: CreateListNode
 3 * 功能描述: 创建新的链表结点
 4 * 输入参数: T_OMCI_LIST *pList :链表指针
 5 *           VOID *pvNodeData  :待插入的链表结点数据指针
 6 * 输出参数: NA
 7 * 返 回 值: T_OMCI_LIST_NODE*
 8 ***********************************************************************/
 9 static T_OMCI_LIST_NODE *CreateListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
10 {
11     T_OMCI_LIST_NODE *pInsertNode = (T_OMCI_LIST_NODE*)calloc((sizeof(T_OMCI_LIST_NODE)+pList->dwNodeDataSize), 1);
12     if(NULL == pInsertNode)
13     {
14         printf("[%s]pList(%p) failed to alloc for pInsertNode!\n", FUNC_NAME, pList);
15         return NULL;
16     }
17
18     pInsertNode->pvNodeData = (INT8U *)pInsertNode + sizeof(T_OMCI_LIST_NODE);
19     if(NULL != pvNodeData)
20     {   //创建非头结点时
21         memmove(pInsertNode->pvNodeData, pvNodeData, pList->dwNodeDataSize);
22     }
23
24     return pInsertNode;
25 }

删除指定的链表结点:

 1 /**********************************************************************
 2 * 函数名称: RemoveListNode
 3 * 功能描述: 删除指定的链表结点(释放结点内存并置其前驱后继指针为NULL)
 4 * 输入参数: T_OMCI_LIST *pList :链表指针
 5 *           VOID *pvNode       :待删除的链表结点指针
 6 * 输出参数: NA
 7 * 返 回 值: LIST_STATUS
 8 * 注意事项: 本函数未置待删除结点指针为NULL,请避免访问已删除结点
 9 ***********************************************************************/
10 static LIST_STATUS RemoveListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pNode)
11 {
12     OMCI_ISOL_NODE(pNode);
13     free(pNode);  //释放链表结点
14
15     return OMCI_LIST_OK;
16 }

OMCI_ISOL_NODE 宏用于"孤立"待删除的链表结点,避免通过该结点访问其前驱和后继结点(进而遍历链表)。因为RemoveListNode函数无法将结点指针置空(C语言值传递特性),故调用者需注意避免再次使用已删除的结点。若要达到结点指针置空的目的,可调用销毁结点的接口函数:

 1 /**********************************************************************
 2 * 函数名称: DestroyListNode
 3 * 功能描述: 销毁指定的链表结点(释放结点内存并置结点指针为NULL)
 4 * 输入参数: T_OMCI_LIST *pList :链表指针
 5 *           VOID **pNode       :待销毁的链表结点指针的指针
 6 * 输出参数: NA
 7 * 返 回 值: LIST_STATUS
 8 * 注意事项: 当指向待销毁结点的指针存在多份拷贝且散布程序各处时(尤其当
 9 *           调用链未能保证**pNode指向原始结点时),无法彻底销毁该结点
10 ***********************************************************************/
11 static LIST_STATUS DestroyListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE **pNode)
12 {
13     free(*pNode);  //释放链表结点
14     *pNode = NULL;
15
16     return OMCI_LIST_OK;
17 }

DestroyListNode函数会释放指定结点的内存并将结点指针置空。但当代码中存在该结点指针的其他副本时,该函数显然无法将这些指针副本置空。

至于RemoveListNode和DestroyListNode函数孰优孰劣,可参考附注中对“悬垂指针”的讨论。

有时可能需要获知链表的确切占用情况(通常没有必要),如不含任何结点、仅含头结点或者还包含其他有用结点。GetListOccupation函数可满足这一“吹毛求疵”的需求,其他情况应使用下文将要给出的判空函数OmciIsListEmpty。OmciIsListEmpty将不含任何结点和仅含头结点均视为空链表,以隐藏内部细节。

 1 /**********************************************************************
 2 * 函数名称: GetListOccupation
 3 * 功能描述: 获取链表占用情况
 4 * 输入参数: T_OMCI_LIST *pList :链表指针
 5 * 输出参数: NA
 6 * 返 回 值: LIST_OCCUPATION
 7 * 注意事项: 本函数仅用于内部测试。
 8 ***********************************************************************/
 9 static LIST_OCCUPATION GetListOccupation(T_OMCI_LIST *pList)
10 {
11     CHECK_SINGLE_POINTER(pList, OMCI_LIST_NULL);
12     CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_NULL);
13
14     return (0 == pList->dwNodeNum) ? OMCI_LIST_EMPTY : OMCI_LIST_OCCUPIED;
15 }

基于上述私有函数,可进一步构建链表及其结点的基本操作接口。

2.3.1 链表操作

使用链表前,必须对其初始化。初始化时将创建头结点,并确定后续将要链接的结点数据宽度。

 1 /**********************************************************************
 2 * 函数名称: OmciInitList
 3 * 功能描述: 链表初始化,产生空的双向循环链表
 4 * 输入参数: T_OMCI_LIST *pList    :链表指针
 5 *           INT32U dwNodeDataSize :链表结点保存的数据字节数
 6 * 输出参数: NA
 7 * 返 回 值: LIST_STATUS
 8 ***********************************************************************/
 9 LIST_STATUS OmciInitList(T_OMCI_LIST *pList, INT32U dwNodeDataSize)
10 {
11     CHECK_SINGLE_POINTER(pList, OMCI_LIST_ERROR);
12
13     if(0 == dwNodeDataSize)
14     {
15         printf("[%s]pList=%p, dwNodeDataSize=%uBytes, undesired initialization!\n",
16                FUNC_NAME, pList, dwNodeDataSize);
17         return OMCI_LIST_ERROR;
18     }
19     pList->dwNodeDataSize = dwNodeDataSize;  //给予重新修改结点数据大小的机会
20
21     if(NULL != pList->pHead)
22     {
23         printf("[%s]pList(%p) has been initialized!\n", FUNC_NAME, pList);
24         return OMCI_LIST_OK;
25     }
26
27     T_OMCI_LIST_NODE *pHeadNode = CreateListNode(pList, NULL);
28     if(NULL == pHeadNode)
29     {
30         printf("[%s]pList(%p) failed to create pHeadNode!\n", FUNC_NAME, pList);
31         return OMCI_LIST_ERROR;
32     }
33
34     OMCI_INIT_NODE(pHeadNode);
35     pList->pHead = pList->pTail = pHeadNode;
36     pList->dwNodeNum = 0;
37
38     return OMCI_LIST_OK;
39 }

通常不会中途修改dwNodeDataSize。仅当使用者确知数据宽度的变化边界(如确知前N个结点数据为四字节,其后为八字节)时,中途修改dwNodeDataSize才有意义。

调用OmciInitList接口后,将创建一张仅含头结点的空双向循环链表。此后可向链表中插入结点。

暂时不需要当前链表时,可清空链表除头结点外的结点。这样再次使用时无需初始化链表,直接插入结点即可。若确定不再需要当前链表时,可销毁链表的所有结点。OmciClearList和OmciDestroyList函数分别完成链表的清空和销毁。

 1 /**********************************************************************
 2 * 函数名称: OmciClearList
 3 * 功能描述: 清空双向循环链表除头结点外的结点
 4 * 输入参数: T_OMCI_LIST *pList :链表指针
 5 * 输出参数: NA
 6 * 返 回 值: LIST_STATUS
 7 * 注意事项: 清空链表结点后,再次插入结点时不需要初始化链表。
 8 ***********************************************************************/
 9 LIST_STATUS OmciClearList(T_OMCI_LIST *pList)
10 {
11     LIST_ITER_CHECK(pList, OMCI_LIST_ERROR);
12
13     T_OMCI_LIST_NODE *pNextNode, *pListNode = pList->pHead->pNext;
14     while(pListNode != pList->pHead)
15     {
16         pNextNode = pListNode->pNext;
17         RemoveListNode(pList, pListNode);
18         pListNode = pNextNode;
19     }
20
21     OMCI_INIT_NODE(pList->pHead);
22     pList->pTail = pList->pHead;
23     pList->dwNodeNum = 0;
24
25     return OMCI_LIST_OK;
26 }
27 /**********************************************************************
28 * 函数名称: OmciDestroyList
29 * 功能描述: 销毁双向循环链表,包括头结点
30 * 输入参数: T_OMCI_LIST *pList :链表指针
31 * 输出参数: NA
32 * 返 回 值: LIST_STATUS
33 * 注意事项: 销毁链表后,再次插入结点时需要初始化链表。
34 ***********************************************************************/
35 LIST_STATUS OmciDestroyList(T_OMCI_LIST *pList)
36 {
37     LIST_ITER_CHECK(pList, OMCI_LIST_ERROR);
38
39     T_OMCI_LIST_NODE *pNextNode, *pListNode = pList->pHead->pNext;
40     while(pListNode != pList->pHead)
41     {
42         pNextNode = pListNode->pNext;
43         DestroyListNode(pList, &pListNode);
44         pListNode = pNextNode;
45     }
46
47     DestroyListNode(pList, &(pList->pHead)); //销毁头结点
48     pList->pTail = NULL;                     //尾结点指针置空
49     pList->dwNodeNum = 0;
50     pList->dwNodeDataSize = 0;
51
52     return OMCI_LIST_OK;
53 }

清空或销毁链表后,OmciIsListEmpty函数的返回值将为逻辑真,表明当前链表为空。

 1 /**********************************************************************
 2 * 函数名称: OmciIsListEmpty
 3 * 功能描述: 判断链表是否为空(仅含头结点或不含任何结点)
 4 * 输入参数: T_OMCI_LIST *pList :链表指针
 5 * 输出参数: NA
 6 * 返 回 值: BOOL
 7 ***********************************************************************/
 8 BOOL OmciIsListEmpty(T_OMCI_LIST *pList)
 9 {
10     CHECK_SINGLE_POINTER(pList, OMCI_LIST_TRUE);
11     CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_TRUE);
12
13     T_OMCI_LIST_NODE *pHeadNode = pList->pHead;
14     if((0 == pList->dwNodeNum) &&
15        (pHeadNode->pPrev == pHeadNode) && //冗余校验以加强安全性
16        (pHeadNode->pNext == pHeadNode))
17     {
18         return OMCI_LIST_TRUE;
19     }
20     else
21     {
22         return OMCI_LIST_FALSE;
23     }
24 }

此处为加强安全性对头结点进行检验,但并非必要。若剔除冗余校验,则OmciIsListEmpty函数的实现会更为简洁高效。

2.3.2 结点操作

链表初始化后,可在链表头结点后逆序或顺序依次插入新的结点。当从头结点向后继方向遍历时,逆序插入的行为类似于栈,而顺序插入的行为类似于队列。

 1 /**********************************************************************
 2 * 函数名称: OmciPrependListNode
 3 * 功能描述: 在链表头结点后逆序增加结点,尾结点恒为头结点
 4 *           在头结点指针pHead所指向结点和pHead->pNext所指向结点
 5 *           之间插入新结点,先插入的结点向右移动。遍历链表时
 6 *           从pHead开始向右依次访问至最先插入的结点,类似于栈。
 7 * 输入参数: T_OMCI_LIST *pList :链表指针
 8 *           VOID *pvNodeData   :待插入的链表结点数据指针
 9 * 输出参数: NA
10 * 返 回 值: LIST_STATUS
11 ***********************************************************************/
12 LIST_STATUS OmciPrependListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
13 {
14     CHECK_DOUBLE_POINTER(pList, pvNodeData, OMCI_LIST_ERROR);
15
16     if(0 == pList->dwNodeDataSize)
17     {
18         printf("[%s]pList=%p, dwNodeDataSize=0Bytes, probably uninitialized or initialized improperly. See ‘OmciInitList‘!\n",
19                FUNC_NAME, pList);
20         return OMCI_LIST_ERROR;
21     }
22     T_OMCI_LIST_NODE *pInsertNode = CreateListNode(pList, pvNodeData);
23     if(NULL == pInsertNode)
24     {
25         printf("[%s]pList(%p) failed to create pInsertNode!\n", FUNC_NAME, pList);
26         return OMCI_LIST_ERROR;
27     }
28
29     OMCI_INSERT_NODE(pList->pHead, pInsertNode); //在链表头结点后增加一个结点
30
31     pList->dwNodeNum++;
32
33     return OMCI_LIST_OK;
34 }
35
36 /**********************************************************************
37 * 函数名称: OmciAppendListNode
38 * 功能描述: 在链表头结点后顺序增加结点,新结点作为尾结点
39 *           在头结点指针pHead所指向结点前(即尾结点后)插入新结点,
40 *           先插入的结点向左移动。遍历链表时从pHead开始向右依次
41 *           访问至最后插入的结点,类似于队列。
42 *           双向循环链表已保证pList->pTail(即pHead->pPrev)非空。
43 * 输入参数: T_OMCI_LIST *pList :链表指针
44 *           VOID *pvNodeData   :待插入的链表结点数据指针
45 * 输出参数: NA
46 * 返 回 值: LIST_STATUS
47 ***********************************************************************/
48 LIST_STATUS OmciAppendListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
49 {
50     CHECK_DOUBLE_POINTER(pList, pvNodeData, OMCI_LIST_ERROR);
51
52     if(0 == pList->dwNodeDataSize)
53     {
54         printf("[%s]pList=%p, dwNodeDataSize=0Bytes, probably uninitialized or initialized improperly. See ‘OmciInitList‘!\n",
55                FUNC_NAME, pList);
56         return OMCI_LIST_ERROR;
57     }
58
59     T_OMCI_LIST_NODE *pInsertNode = CreateListNode(pList, pvNodeData);
60     if(NULL == pInsertNode)
61     {
62         printf("[%s]pList(%p) failed to create pInsertNode!\n", FUNC_NAME, pList);
63         return OMCI_LIST_ERROR;
64     }
65
66     OMCI_INSERT_NODE(pList->pTail, pInsertNode); //在链表尾结点后增加一个结点
67     pList->pTail = pInsertNode;                  //新的尾结点指向当前添加的结点
68
69     pList->dwNodeNum++;
70
71     return OMCI_LIST_OK;
72 }

对dwNodeDataSize 的校验用于指示链表未初始化或未正确初始化的错误。将该校验置于私有函数CreateListNode中可简化Prepend和Append代码。但FUNC_NAME信息将暴露内部函数,从而给使用者造成疑惑,故该校验予以保留。

有时需要在链表中任意位置插入结点,此时可使用OmciInsertListNode接口。

 1 /**********************************************************************
 2 * 函数名称: OmciInsertListNode
 3 * 功能描述: 在链表中任意位置插入结点
 4 * 输入参数: T_OMCI_LIST *pList          :链表指针
 5 *           T_OMCI_LIST_NODE *pPrevNode :待插入结点的前驱结点指针
 6 *           VOID *pvNodeData            :待插入结点的数据域指针
 7 * 输出参数: NA
 8 * 返 回 值: LIST_STATUS
 9 * 注意事项: 若pPrevNode恒为头结点或尾结点,请使用OmciPrependListNode
10 *           或OmciAppendListNode函数
11 ***********************************************************************/
12 LIST_STATUS OmciInsertListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pPrevNode, VOID *pvNodeData)
13 {
14     CHECK_TRIPLE_POINTER(pList, pPrevNode, pvNodeData, OMCI_LIST_ERROR);
15
16     if(0 == pList->dwNodeDataSize)
17     {
18         printf("[%s]pList=%p, dwNodeDataSize=0Bytes, probably uninitialized or initialized improperly. See ‘OmciInitList‘!\n",
19                FUNC_NAME, pList);
20         return OMCI_LIST_ERROR;
21     }
22
23     T_OMCI_LIST_NODE *pInsertNode = CreateListNode(pList, pvNodeData);
24     if(NULL == pInsertNode)
25     {
26         printf("[%s]pList(%p) failed to create pInsertNode!\n", FUNC_NAME, pList);
27         return OMCI_LIST_ERROR;
28     }
29
30     OMCI_INSERT_NODE(pPrevNode, pInsertNode);
31     if(pPrevNode == pList->pTail)
32         pList->pTail = pInsertNode;
33
34     pList->dwNodeNum++;
35
36     return OMCI_LIST_OK;
37 }

当pPrevNode恒为头结点时,OmciInsertListNode接口等效于OmciPrependListNode;当pPrevNode恒为尾结点时,OmciInsertListNode接口等效于OmciAppendListNode。这两种情况建议使用Prepend或Append接口(毕竟减少一个参数)。

插入若干结点后,就可删除或销毁链表中除头结点外的任一结点。

 1 /**********************************************************************
 2 * 函数名称: OmciRemoveListNode
 3 * 功能描述: 删除双向循环链表中除头结点外的某一结点
 4 * 输入参数: T_OMCI_LIST *pList      :链表指针
 5 *           T_OMCI_LIST_NODE *pNode :待删除的链表结点指针
 6 * 输出参数: NA
 7 * 返 回 值: LIST_STATUS
 8 ***********************************************************************/
 9 LIST_STATUS OmciRemoveListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pNode)
10 {
11     CHECK_DOUBLE_POINTER(pList, pNode, OMCI_LIST_ERROR);
12     CHECK_DOUBLE_POINTER(pNode->pPrev, pNode->pNext, OMCI_LIST_ERROR);
13
14     if(0 == pList->dwNodeNum)
15     {
16         printf("[%s]pList(%p) has no node to be Removed!\n", FUNC_NAME, pList);
17         return OMCI_LIST_ERROR;
18     }
19
20     OMCI_REMOVE_NODE(pNode);
21     if(pNode->pNext == pList->pHead)
22     {
23         pList->pTail = pNode->pPrev; //删除尾结点
24     }
25
26     RemoveListNode(pList, pNode);
27     pList->dwNodeNum--;
28
29     return OMCI_LIST_OK;
30 }
31 /**********************************************************************
32 * 函数名称: OmciDestroyListNode
33 * 功能描述: 销毁双向循环链表中除头结点外的某一结点
34 * 输入参数: T_OMCI_LIST *pList       :链表指针
35 *           T_OMCI_LIST_NODE **pNode :待销毁的链表结点二级指针
36 * 输出参数: NA
37 * 返 回 值: LIST_STATUS
38 ***********************************************************************/
39 LIST_STATUS OmciDestroyListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE **pNode)
40 {
41     CHECK_DOUBLE_POINTER(pList, pNode, OMCI_LIST_ERROR);
42     CHECK_SINGLE_POINTER(*pNode, OMCI_LIST_ERROR);
43
44     if(0 == pList->dwNodeNum)
45     {
46         printf("[%s]pList(%p) has no node to be Removed!\n", FUNC_NAME, pList);
47         return OMCI_LIST_ERROR;
48     }
49
50     OMCI_REMOVE_NODE(*pNode);
51     if((*pNode)->pNext == pList->pHead)
52     {
53         pList->pTail = (*pNode)->pPrev; //删除尾结点
54     }
55
56     DestroyListNode(pList, pNode);
57     pList->dwNodeNum--;
58
59     return OMCI_LIST_OK;
60 }

然而,要删除或销毁链表结点,必须先定位到该结点。

在链表中“定位”某个结点有两种手段:一是通过结点编号查找,如OmciGetListNodeByIndex;二是通过某种给定条件匹配,如OmciLocateListNode(查找首个满足给定条件的结点)。

 1 /**********************************************************************
 2 * 函数名称: OmciGetListNodeByIndex
 3 * 功能描述: 获取链表中指定序号的结点(按头结点后继方向排序)
 4 * 输入参数: T_OMCI_LIST* pList :链表指针
 5 *           INT32U dwNodeIndex :结点序号(从1开始)
 6 * 输出参数: NA
 7 * 返 回 值: T_OMCI_LIST_NODE* 链表结点指针(空表返回NULL)
 8 ***********************************************************************/
 9 T_OMCI_LIST_NODE* OmciGetListNodeByIndex(T_OMCI_LIST *pList, INT32U dwNodeIndex)
10 {
11     CHECK_SINGLE_POINTER(pList, NULL);
12
13     if(0 == dwNodeIndex)
14         return pList->pHead;  //也可返回NULL
15     if(dwNodeIndex >= pList->dwNodeNum)
16         return pList->pTail;
17
18     INT32U dwNodeIdx = 1;
19     T_OMCI_LIST_NODE *pListNode = pList->pHead;
20     for(; dwNodeIdx <= dwNodeIndex; dwNodeIdx++)
21         pListNode = pListNode->pNext;
22
23     return pListNode;
24 }
25 /**********************************************************************
26 * 函数名称: OmciLocateListNode
27 * 功能描述: 查找链表中首个与pData满足函数fpCompareNode判定关系的结点
28 * 输入参数: T_OMCI_LIST* pList            :链表指针
29 *           VOID* pvData                  :待比较数据指针
30 *           CompareNodeFunc fpCompareNode :比较回调函数指针
31 * 输出参数: NA
32 * 返 回 值: T_OMCI_LIST_NODE* 链表结点指针(未找到时返回NULL)
33 ***********************************************************************/
34 /* 比较回调函数原型,用来自定义链表节点比较 */
35 typedef INT8U (*CompareNodeFunc)(VOID *pvNodeData, VOID *pvData, INT32U dwNodeDataSize);
36 T_OMCI_LIST_NODE* OmciLocateListNode(T_OMCI_LIST *pList, VOID *pvData, CompareNodeFunc fpCompareNode)
37 {
38     CHECK_TRIPLE_POINTER(pList, pvData, fpCompareNode, NULL);
39     CHECK_SINGLE_POINTER(pList->pHead, NULL);
40     CHECK_SINGLE_POINTER(pList->pHead->pNext, NULL);
41
42     T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
43     while(pListNode != pList->pHead)
44     {
45         if(0 == fpCompareNode(pListNode->pvNodeData, pvData, pList->dwNodeDataSize))
46             return pListNode;
47
48         pListNode = pListNode->pNext;
49     }
50
51     return NULL;
52 }

可见,OmciLocateListNode接口本质上就是“遍历+匹配”。要进行单纯而强大的遍历操作,可使用OmciTraverseListNode接口。

 1 /**********************************************************************
 2 * 函数名称: OmciTraverseListNode
 3 * 功能描述: 链表结点遍历函数,遍历操作由fpTravNode指定
 4 * 输入参数: T_OMCI_LIST* pList      :链表指针
 5 *           VOID* pvTravInfo        :遍历操作回调函数所需信息
 6 *                                    也可为空,取决于回调函数具体实现
 7 *           TravNodeFunc fpTravNode :遍历操作回调函数指针
 8 * 输出参数: NA
 9 * 返 回 值: LIST_STATUS
10 * 注意事项: 本函数可间接实现Print等操作,但不建议代替后者。
11 *           fpTravNode返回非0(OMCI_LIST_OK)值时中止遍历
12 ***********************************************************************/
13 typedef LIST_STATUS (*TravNodeFunc)(VOID *pvNode, VOID *pvTravInfo, INT32U dwNodeDataSize);
14 LIST_STATUS OmciTraverseListNode(T_OMCI_LIST *pList, VOID *pvTravInfo, TravNodeFunc fpTravNode)
15 {
16     CHECK_DOUBLE_POINTER(pList, fpTravNode, OMCI_LIST_ERROR);
17     CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_ERROR);
18     CHECK_SINGLE_POINTER(pList->pHead->pNext, OMCI_LIST_ERROR);
19
20     T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
21     while(pListNode != pList->pHead)
22     {
23         T_OMCI_LIST_NODE *pTmpNode = pListNode->pNext; //fpTravNode内可能会销毁结点pListNode
24         if(OMCI_LIST_OK != fpTravNode(pListNode, pvTravInfo, pList->dwNodeDataSize))
25             break;
26
27         pListNode = pTmpNode;
28     }
29
30     return OMCI_LIST_OK;
31 }

因为OmciAppendListNode和OmciPrependListNode已暗含“正序”和“逆序”的意思,故仅提供OmciTraverseListNode函数,而无需再增加逆序遍历的接口(除非需要同时双序遍历)。

常常需要打印输出链表结点的数据域内容,而OmciTraverseListNode接口稍显笨重。此时可使用专门的打印接口OmciPrintListNode。

 1 /**********************************************************************
 2 * 函数名称: OmciPrintListNode
 3 * 功能描述: 打印输出链表结点的数据域内容
 4 * 输入参数: T_OMCI_LIST* pList        :链表指针
 5 *           PrintListFunc fpPrintList :打印回调函数指针
 6 * 输出参数: NA
 7 * 返 回 值: LIST_STATUS
 8 ***********************************************************************/
 9 /* 打印回调函数原型,用来自定义链表内容打印 */
10 typedef VOID (*PrintListFunc)(VOID *pNodeData, INT32U dwNodeNum);
11 LIST_STATUS OmciPrintListNode(T_OMCI_LIST *pList, PrintListFunc fpPrintList)
12 {
13     CHECK_DOUBLE_POINTER(pList, fpPrintList, OMCI_LIST_ERROR);
14     CHECK_SINGLE_POINTER(pList->pHead, OMCI_LIST_ERROR);
15     CHECK_SINGLE_POINTER(pList->pHead->pNext, OMCI_LIST_ERROR);
16
17     T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
18     while(pListNode != pList->pHead)
19     {
20         //具体打印格式交给回调函数灵活处理(可直接打印也可拷贝至本地处理后打印)
21         fpPrintList(pListNode->pvNodeData, pList->dwNodeNum);
22         pListNode = pListNode->pNext;
23     }
24     printf("\n");
25
26     return OMCI_LIST_OK;
27 }

对于CompareNodeFunc 和PrintListFunc,以下给出两个范例:

 1 /**********************************************************************
 2 * 函数名称: CompareNodeGenric
 3 * 功能描述: 通用链表结点内存比较
 4 * 输入参数: VOID *pvNodeData      :链表结点数据指针
 5 *           VOID *pvData          :待比较外部数据指针
 6 *           INT32U dwNodeDataSize :链表结点数据大小
 7 * 输出参数: NA
 8 * 返 回 值: 0:Equal; !0:Unequal
 9 * 注意事项: 比较长度为结点数据字节数,即默认与外部数据大小一致
10 ***********************************************************************/
11 INT8U CompareNodeGenric(VOID *pvNodeData, VOID *pvData, INT32U dwNodeDataSize)
12 {
13     CHECK_DOUBLE_POINTER(pvNodeData, pvData, 1);
14     return memcmp(pvNodeData, pvData, dwNodeDataSize);
15 }
16 /**********************************************************************
17 * 函数名称: PrintListWord
18 * 功能描述: 打印链表结点,结点数据域为两字节整数
19 * 输入参数: VOID *pvNodeData   :链表节点数据指针
20 *           INT32U dwNodeNum  :链表节点数目
21 * 输出参数: NA
22 * 返 回 值: VOID
23 * 注意事项: 仅作示例,未考虑字节序等问题。
24 ***********************************************************************/
25 VOID PrintListWord(VOID *pvNodeData, INT32U dwNodeNum)
26 {
27     CHECK_SINGLE_POINTER(pvNodeData, RETURN_VOID);
28     printf("%d ", *((INT16U *)pvNodeData));
29 }

最后,给出获取链表结点及其数据的安全接口:

 1 /**********************************************************************
 2 * 函数名称: OmciGetListNodeNum
 3 * 功能描述: 获取链表结点数目
 4 * 输入参数: T_OMCI_LIST *pList :链表指针
 5 * 输出参数: NA
 6 * 返 回 值: INT32U 链表结点数目
 7 ***********************************************************************/
 8 INT32U OmciGetListNodeNum(T_OMCI_LIST *pList)
 9 {
10     CHECK_SINGLE_POINTER(pList, 0);
11     return (pList->dwNodeNum);
12 }
13
14 /**********************************************************************
15 * 函数名称: OmciGetListHead/OmciGetListTail
16 * 功能描述: 获取链表头结点/尾结点指针
17 * 输入参数: T_OMCI_LIST *pList :链表指针
18 ***********************************************************************/
19 T_OMCI_LIST_NODE* OmciGetListHead(T_OMCI_LIST *pList)
20 {
21     CHECK_SINGLE_POINTER(pList, NULL);
22     return (pList->pHead);
23 }
24 T_OMCI_LIST_NODE* OmciGetListTail(T_OMCI_LIST *pList)
25 {
26     CHECK_SINGLE_POINTER(pList, NULL);
27     return (pList->pTail);
28 }
29
30 /**********************************************************************
31 * 函数名称: OmciGetPrevNode/OmciGetNextNode
32 * 功能描述: 获取链表指定结点的前驱结点/后继结点指针
33 * 输入参数: T_OMCI_LIST_NODE *pNode :指定结点的指针
34 ***********************************************************************/
35 T_OMCI_LIST_NODE* OmciGetPrevNode(T_OMCI_LIST_NODE *pNode)
36 {
37     CHECK_SINGLE_POINTER(pNode, NULL);
38     return (pNode->pPrev);
39 }
40 T_OMCI_LIST_NODE* OmciGetNextNode(T_OMCI_LIST_NODE *pNode)
41 {
42     CHECK_SINGLE_POINTER(pNode, NULL);
43     return (pNode->pNext);
44 }
45
46 /**********************************************************************
47 * 函数名称: OmciGetNodeData
48 * 功能描述: 获取链表指定结点的数据域
49 * 输入参数: T_OMCI_LIST_NODE *pNode :指定结点的指针
50 ***********************************************************************/
51 VOID* OmciGetNodeData(T_OMCI_LIST_NODE *pNode)
52 {
53     CHECK_DOUBLE_POINTER(pNode, pNode->pvNodeData, NULL);
54     return (pNode->pvNodeData);
55 }

三  测试

本节将对上文实现的链表操作接口进行测试,测试函数兼作使用示例。

 1 #ifdef TEST_AND_EXAMPLE
 2
 3 static LIST_STATUS TravPrintWord(VOID *pvNode, VOID *pvTravInfo, INT32U dwNodeDataSize)
 4 {
 5     CHECK_SINGLE_POINTER(pvNode, OMCI_LIST_ERROR);
 6     T_OMCI_LIST_NODE *pNode = (T_OMCI_LIST_NODE *)pvNode;
 7     printf("%d ", *((INT16U *)GET_NODE_DATA(pNode)));
 8     return OMCI_LIST_OK;
 9 }
10
11 T_OMCI_LIST gExampleList = {0};
12 VOID ListTestExample(VOID)
13 {   //本函数并非严格意义上的测试函数,主要用作示例,且示例并非最佳用法。
14     INT8U ucTestIndex = 1;
15     INT16U aTestListData[] = {11, 22, 33, 44, 55, 66};
16
17     printf("\n<Test Case %u>: Initialization!\n", ucTestIndex++);
18     OmciInitList(&gExampleList, sizeof(INT16U));
19     printf("gExampleList=%p, pHead=%p, pTail=%p\n", &gExampleList,
20            OmciGetListHead(&gExampleList), OmciGetListTail(&gExampleList));
21
22     printf("\n<Test Case %u>: Append Node to List!\n", ucTestIndex++);
23     OmciAppendListNode(&gExampleList, &aTestListData[0]);
24     OmciAppendListNode(&gExampleList, &aTestListData[1]);
25     OmciAppendListNode(&gExampleList, &aTestListData[2]);
26     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)\n", OmciIsListEmpty(&gExampleList));
27     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
28     OmciPrintListNode(&gExampleList, PrintListWord);
29
30     printf("\n<Test Case %u>: Insert Node to List!\n", ucTestIndex++);
31     T_OMCI_LIST_NODE *pPrevNode = OmciGetListNodeByIndex(&gExampleList, 2);
32     printf("NodeData2=%d\n", *((INT16U *)OmciGetNodeData(pPrevNode)));
33     OmciInsertListNode(&gExampleList, pPrevNode, &aTestListData[4]);
34     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
35     OmciPrintListNode(&gExampleList, PrintListWord);
36
37     printf("\n<Test Case %u>: Remove Node from List!\n", ucTestIndex++);
38     T_OMCI_LIST_NODE *pDeleteNode = OmciLocateListNode(&gExampleList, &aTestListData[1], CompareNodeGenric);
39     OmciRemoveListNode(&gExampleList, pDeleteNode);
40     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
41     OmciPrintListNode(&gExampleList, PrintListWord);
42
43     printf("\n<Test Case %u>: Clear List!\n", ucTestIndex++);
44     OmciClearList(&gExampleList);
45     printf("gExampleList=%p, pHead=%p, pTail=%p\n", &gExampleList,
46            GET_HEAD_NODE(&gExampleList), GET_TAIL_NODE(&gExampleList));
47     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)\n", OmciIsListEmpty(&gExampleList));
48     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
49
50     printf("\n<Test Case %u>: Prepend Node to List!\n", ucTestIndex++);
51     OmciPrependListNode(&gExampleList, &aTestListData[3]);
52     OmciPrependListNode(&gExampleList, &aTestListData[4]);
53     OmciPrependListNode(&gExampleList, &aTestListData[5]);
54     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty)\n", OmciIsListEmpty(&gExampleList));
55     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
56     OmciPrintListNode(&gExampleList, PrintListWord);
57
58     T_OMCI_LIST_NODE *pListNode = NULL;
59     LIST_ITER_LOOP(&gExampleList, pListNode)
60     {
61         printf("%d ", *((INT16U *)GET_NODE_DATA(pListNode)));
62     }
63     printf("\n");
64
65     OmciTraverseListNode(&gExampleList, NULL, TravPrintWord);
66     printf("\n");
67
68     printf("\n<Test Case %u>: Destory List!\n", ucTestIndex++);
69     OmciDestroyList(&gExampleList);
70     printf("gExampleList=%p, pHead=%p, pTail=%p\n", &gExampleList,
71            GET_HEAD_NODE(&gExampleList), GET_TAIL_NODE(&gExampleList));
72     printf("gExampleList NodeNum=%u\n", OmciGetListNodeNum(&gExampleList));
73     printf("OmciIsListEmpty=%u(0-Occupied; 1-Empty; 2-Null)\n", GetListOccupation(&gExampleList));
74     return;
75 }
76
77 #endif

在上述测试代码中,Prepend或Append结点的代码若用OmciInsertListNode实现,如下:

1 OmciInsertListNode(&gExampleList, OmciGetListHead(&gExampleList), &aTestListData[3]);
2 OmciInsertListNode(&gExampleList, OmciGetListHead(&gExampleList), &aTestListData[4]);
3 OmciInsertListNode(&gExampleList, OmciGetListHead(&gExampleList), &aTestListData[5]);
4 //Or
5 OmciInsertListNode(&gExampleList, OmciGetListTail(&gExampleList), &aTestListData[1]);
6 OmciInsertListNode(&gExampleList, OmciGetListTail(&gExampleList), &aTestListData[2]);
7 OmciInsertListNode(&gExampleList, OmciGetListTail(&gExampleList), &aTestListData[3]);

测试结果如下所示:

 1 <Test Case 1>: Initialization!
 2 gExampleList=0x804bc8c, pHead=0x8b39010, pTail=0x8b39010
 3
 4 <Test Case 2>: Append Node to List!
 5 OmciIsListEmpty=0(0-Occupied; 1-Empty)
 6 gExampleList NodeNum=3
 7 11 22 33
 8
 9 <Test Case 3>: Insert Node to List!
10 NodeData2=22
11 gExampleList NodeNum=4
12 11 22 55 33
13
14 <Test Case 4>: Remove Node from List!
15 gExampleList NodeNum=3
16 11 55 33
17
18 <Test Case 5>: Clear List!
19 gExampleList=0x804bc8c, pHead=0x8b39010, pTail=0x8b39010
20 OmciIsListEmpty=1(0-Occupied; 1-Empty)
21 gExampleList NodeNum=0
22
23 <Test Case 6>: Prepend Node to List!
24 OmciIsListEmpty=0(0-Occupied; 1-Empty)
25 gExampleList NodeNum=3
26 66 55 44
27 66 55 44
28 66 55 44
29
30 <Test Case 7>: Destory List!
31 gExampleList=0x804bc8c, pHead=(nil), pTail=(nil)
32 gExampleList NodeNum=0
33 [GetListOccupation(140)]Null Pointer: pList->pHead!
34 OmciIsListEmpty=2(0-Occupied; 1-Empty; 2-Null)

四  附注

     悬垂指针(Dangling pointer)和野指针(Wild pointer)

  • 悬垂指针:所指向的对象被释放或收回,但该指针仍指向原对象的内存地址。
  • 野指针:指针在使用之前未进行必要的初始化(未显式初始化的静态指针不是野指针)。

可见,悬垂指针和野指针均指向不合法的对象,应禁止读写其指向的内存。野指针简单且易于处理,以下主要讨论悬垂指针。

在C语言中,当指针所指向的动态内存被显式地释放(free)后,该指针就成为悬垂指针。若通过悬垂指针访问或修改已释放的动态分配内存,则可能引发难以排查的故障(尤其当原对象内存分配作他用时)。若指针是函数内的自动变量,函数退出时会被自动销毁;否则,最好在释放动态内存后将该指针置空(NULL)。虽然将悬垂指针重新置空的做法可能隐藏诸如double free之类的逻辑问题,但却使得对它的读写错误更容易暴露(尤其是在多线程环境中)。
     在C语言中,可通过下述两种free替代版本来尽可能避免悬垂指针错误:

 1 #define SAFE_FREE((pointer)) do{  2     if(pointer != NULL){  3         free(pointer);  4         pointer = NULL;  5 }while(0);
 6
 7 void SafeFree(void **pointer)
 8 {
 9     if(pointer != NULL)
10     {
11         free(*pointer);
12         *pointer = NULL;
13     }
14 }

然而,当指向动态分配内存的指针存在多个副本且散布程序各处时,该技术不会置空其他指针变量,从而导致释放后指针行为的不一致。因此,编码者应保证每个指针都有其明确的用途和生存期。

注意,因为C语言的值传递特性,现有的free库函数内不可能将入参指针置空。若要达到置空的目的,必须传入二级指针,如SafeFree。但SafeFree必然与其内存分配版本(如SafeAlloc)的入参类型不一致,这会增加使用者出错的机率。而由上面的讨论可知,即使置空当前入参指针,也无法清除其副本。因此,最好由调用者自行决定如何置空。至于作者倾向于free还是SafeFree,可参考《关于Linux系统basename函数缺陷的思考》一文,或者试想下逐级释放的顺序性。
     另一种常见的悬垂指针产生于试图返回栈上分配的局部变量的地址。详见《已释放的栈内存》一文。

C语言通用双向循环链表操作函数集

时间: 2024-08-10 15:11:45

C语言通用双向循环链表操作函数集的相关文章

Linux内核中的通用双向循环链表

开发中接触Linux越来越多,休息放松之余,免不了翻看翻看神秘的Linux的内核.看到双向链表时,觉得挺有意思的,此文记下. 作为众多基础数据结构中的一员,双向循环链表在各种“教科书”中的实现是相当的标准和一致的. 大概就是下面这个样子: 1 typedef struct node_tag{ 2 //T data; 3 struct node_tag *prev; 4 struct node_tag *next; 5 }node; 当你需要某种类型的链表时,把数据成员之类的往节点里塞就是了.比如

双向循环链表操作

双链表: 1 2 3 4 5 6 7 8 9 10 11 12 1 3 5 7 9 11 12 10 8 6 4 2 1.设计节点 typedef int datatyped typeddef struct node { typedata data; struct node * next; struct node *prev; }listnode ,*linklist; 2.初始化空双向循环链表 linklist init_list() { linklist l=malloc(sizeof(li

C语言字符串操作函数集

1)字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, p1) 附加字符串 strncat(p, p1, n) 附加指定长度字符串 strlen(p) 取字符串长度 strcmp(p, p1) 比较字符串 strcasecmp忽略大小写比较字符串 strncmp(p, p1, n) 比较指定长度字符串 strchr(p, c) 在字符串中查找指定字符 strrchr(p, c) 在字符串中反向查找 strstr(p, p

C语言实现双向循环链表

#include <stdio.h> #include <stdlib.h> #include <string.h> struct list_head { struct list_head *next, *prev; }; #define list_entry(ptr, type, member) (type *)( (char *)ptr - ((size_t) &((type *)0)->member)) #define list_for_each(p

双向循环链表(C语言描述)(四)

下面以一个电子英汉词典程序(以下简称电子词典)为例,应用双向循环链表.分离数据结构,可以使逻辑代码独立于数据结构操作代码,程序结构更清晰,代码更简洁:电子词典的增.删.查.改操作分别对应于链表的插入.删除.查找.查找和获取链表元素操作. 在程序初始化时,除了初始化链表,还要将保存在文件中的词库加载到链表中: 1 void dict_init() { 2 list = linkedlist_new(); 3 4 dict_load(); 5 printf("Welcome."); 6 }

C语言双向循环链表api(源自gluster源码)

C语言双向循环链表api(源自gluster源码)基本的操作如增加.删除和遍历等 #include <stdio.h> #include <stdlib.h> #include <string.h> /*定义表头*/ struct list_head { struct list_head *next; struct list_head *prev; }; /*表头初始化*/ #define INIT_LIST_HEAD(head) do { (head)->nex

1.Go-copy函数、sort排序、双向链表、list操作和双向循环链表

1.1.copy函数 通过copy函数可以把一个切片内容复制到另一个切片中 (1)把长切片拷贝到短切片中 package main import "fmt" func main() { s1 := []int {1,2} s2 := []int{3,4,5,6} //copy的是角标,不会增加元切片的长度 copy(s1,s2) fmt.Println(s1) //[3 4] fmt.Println(s2) //[3 4 5 6] } (2)把短切片拷贝到长切片中 package ma

C语言实现双向非循环链表的逆序打印

我在上一篇博客中<C语言实现双向非循环链表>实现了如何构造一个双向非循环链表,并实现了正向打印.我还在之前一篇博客<C语言实现单链表的逆序打印>中实现了单链表的逆序打印.这篇博客我们来实现对双向非循环链表进行逆序打印,实现起来非常的简单.代码已经上传至 https://github.com/chenyufeng1991/ReverseDoubleLinkedList . 核心代码如下: //打印非循环双向链表,这个其实是正向打印 void printList(Node *pNode

C语言实现双向非循环链表的节点插入

我在之前一篇博客中<C语言实现双向非循环链表的逆序打印>讲到了如何逆序输出一个双向非循环链表,让我们对这种链表类型有了理性的认识.今天我们要来实现的是对双向非循环链表进行节点的插入.大家可以和<C语言实现单链表节点的插入>单链表的节点插入对比着学习.代码上传至  https://github.com/chenyufeng1991/InsertDoubleLinkedList . 核心代码如下: Node *InsertList(Node *pNode,int pos,int x){