链表在程序设计中是最为基本的数据结构,也是相对最容易出错环节。在C++中,我们能够使用标准的STL链表,以达到软件开发的快速性,重用性和强壮性。那么,我们在C语言中如何达到这样的效果呢?嗯,…,好像ANSC C库暂时还没有吧。现在不妨让我们自己来尝试做一个可重用的双向链表吧J。
首先,让我们先浏览一下下面简单演示代码,然后再逐个仔细分析。
#ifndef _WYQ_LIST_H #define _WYQ_LIST_H
#if defined( __cplusplus ) || defined( c_plusplus ) extern "C" { #endif
typedef struct __WYQSListHead { struct __WYQSListHead *pPrev; struct __WYQSListHead *pNext; } WYQSListHead;
#define WYQList_Declare( listName ) / WYQSListHead listName = { &listName, &listName }
#define WYQList_Init( pListHead ) / do { / (pListHead)->pPrev = (pListHead); / (pListHead)->pNext = (pListHead); / } while ( 0 )
#define WYQList_Entry( ptr, type, member ) / ((type *) ((unsigned long) (ptr) - (unsigned long) (&((type *) 0)->member)))
#define WYQList_IsEmpty( pList ) ((pList) == (pList)->pNext)
#define WYQList_InsertBefore( pHead, pNew ) / WYQList_II_InsertBefore( pHead, pNew )
#define WYQList_InsertAfter( pHead, pNew ) / WYQList_II_InsertBefore( (pHead)->pNext, pNew )
#define WYQList_Append( pList, pNew ) / WYQList_InsertBefore( pList, pNew )
#define WYQList_Add( pList, pNew ) / WYQList_Append( pList, pNew )
#define WYQList_Remove( pHead ) / do { / WYQSListHead *__pHead = (pHead); / __pHead->pPrev->pNext = __pHead->pNext; / __pHead->pNext->pPrev = __pHead->pPrev; / __pHead->pPrev = __pHead; / __pHead->pNext = __pHead; / } while ( 0 )
#define WYQList_Splice( pList1, pList2 ) / do { / WYQSListHead *__pList1 = (pList1); / WYQSListHead *__pList2 = (pList2); / WYQSListHead *__pTail2 = __pList2->pPrev; / __pTail2->pNext = __pList1; / __pList2->pPrev = __pList1->pPrev; / __pList1->pPrev->pNext = __pList2; / __pList1->pPrev = __pTail2; / /* Remove list2's header */ / __pList2->pPrev->pNext = __pList2->pNext; / __pList2->pNext->pPrev = __pList2->pPrev; / __pList2->pPrev = __pList2; / __pList2->pNext = __pList2; / } while ( 0 )
#define WYQList_ForEach( pList, pIter ) / for ( pIter = (pList)->pNext; pIter != (pList); pIter = pIter->pNext )
#define WYQList_Clear( pList, destor ) / do { / WYQSListHead *__pList = (pList); / while ( !WYQList_IsEmpty( __pList ) ) / { / WYQSListHead *__pIter = __pList->pNext; / WYQList_Remove( __pIter ); / destor( __pIter ); / } / } while ( 0 )
#define WYQList_Reverse( pList ) / do { / WYQSListHead *__pList = (pList); / WYQSListHead *__pIter = (pList); / do / { / WYQSListHead *__pTemp = __pIter->pPrev; / __pIter->pPrev = __pIter->pNext; / __pIter->pNext = __pTemp; / __pIter = __pIter->pPrev; / } while ( __pIter != __pList ); / } while ( 0 )
#define WYQList_Visit( pList, visit ) / do { / WYQSListHead *__pIter; / WYQList_ForEach( pList, __pIter ) / { / visit( __pIter ); / } / } while ( 0 )
#define WYQList_II_InsertBefore( pHead, pNew ) / do { / WYQSListHead *__pHead = (pHead); / WYQSListHead *__pNew = (pNew); / __pNew->pPrev = __pHead->pPrev; / __pNew->pNext = __pHead; / __pHead->pPrev->pNext = __pNew; / __pHead->pPrev = __pNew; / } while ( 0 )
#if defined( __cplusplus ) || defined( c_plusplus ) } #endif
#endif |
它定义了一个双向链表头所包含的数据成员,一个前向指针pPrev和一个后向指针pNext,如下所示:
typedef struct __WYQSListHead { struct __WYQSListHead *pPrev; struct __WYQSListHead *pNext; } WYQSListHead; |
我们可以使用WYQList_Declare宏来定义一个链表,也可以使用WYQList_Init来初始化这个链表头。比如:
static WYQList_Declare( list ); 或者 WYQList_Init( &list ); |
它们两者的不同之处是,前者在定义时初始化链表,而后者是在变量分配后在初始化它。对于不同的软件场景,我们可以合适的选择它们。
它是一个很有意思的宏,主要是将WYQSListHead的指针转化为对应数据结构的指针。请看下面的例子,它是在对应链表中查询flags满足要求的第一个链表点。
typedef struct __WYQSTestListHead { WYQSListHead head; uint32_t flags; … } WYQSTestListHead; static WYQList_Declare( list ); WYQEResult Find( WYQSListHead *pList, uint32_t flags, WYQSTestListHead **ppHead ) { WYQSListHead *pIter; WYQList_ForEach( pList, pIter ) { WYQSTestListHead *pHead = WYQList_Entry( pIter, WYQSTestListHead, head ); if ( (pHead->flags & flags) == flags ) { *ppHead = pHead; return WYQ_OK; } } *ppHead = NULL; return WYQ_EUNFND; } |
它是用来确定链表是否为空。
它将新链表节点(pNew)插入到当前节点(pNode)之前。
它将新链表节点(pNew)插入到当前节点(pNode)之后。
它将新链表节点(pNew),追加到链表之后。不同的名字仅仅是为了个人的喜好不同而准备。
将当前节点(pNode)从链表中删除。代码中的最后两个赋值语句是多余的,加上得目的仅仅是为了程序的强壮性。
将list2中的所有节点追加到list1中,除list2的链表头之外。这个宏对两个链表的合并操作很有用。
清空链表中所有的节点,destor一般是在删除节点后,清理删除节点,比如内存的释放等操作。
将list的次序整个颠倒。比如说1,2,3,…N变成N,N-1,N-2,…,1。
遍历这个链表,对每个节点调用visit操作,它一般而言只适合读或修改操作,在visit中不能从事对链表的添加或者是删除工作,否则操作未知。此外,visit可以是其他的宏,也可以是一个函数,这个根据编成者个人喜好而定J。
演示程序的宏中,我们定义了很多临时的变量,看上去好像是多此一举,但是它确保证了程序的正确性。因为宏的pNode可能以pOtherNode->pNext->pPrev(它实际上等于pOtherNode)传入的,如果将它宏展开后,对应的添加或者删除操作可能是有错误的。如果编译器支持inline,那么它应该是一个更好的选择。
#include <stdio.h>
#include "WYQTypes.h" #include "WYQAssert.h" #include "WYQMem.h" #include "WYQList.h" #include "WYQ.h"
#define MAX_NUM 10
#define GEN_LVL() ((int32_t) ((rand() / (double) RAND_MAX) * MAX_NUM + 0.5 ))
#define WYQSNode_Entry( p ) WYQList_Entry( p, WYQSNode, head ) #define WYQSNode_Destor( p ) WYQFree( WYQSNode_Entry( p ) ) #define WYQSNode_Visit( p ) / do { / WYQSNode *__pNode = WYQSNode_Entry( p ); / printf( "(%d : %d)/n", __pNode->no, / __pNode->level ); / } while ( 0 )
typedef struct __WYQSNode { WYQSListHead head; int32_t no; int32_t level; } WYQSNode;
static WYQList_Declare( head1 ); static WYQList_Declare( head2 );
static void help( void ) { printf( "0:/t/t quit/n" ); printf( "1:/t/t visit list1/n" ); printf( "2:/t/t visit list2/n" ); printf( "3:/t/t reverse list1/n" ); printf( "4:/t/t reverse list2/n" ); printf( "5:/t/t splice list1 to list2/n" ); printf( "6:/t/t splice list2 to list1/n" ); }
static WYQEResult init( void ) { int32_t i;
for ( i = 0; i < MAX_NUM; ++ i ) { WYQSNode *pNode = (WYQSNode *) WYQMalloc( sizeof( WYQSNode ) ); WYQAssert( pNode != NULL ); pNode->no = i; pNode->level = GEN_LVL(); WYQList_Add( &head1, &pNode->head ); }
for ( i = 0; i < MAX_NUM; ++ i ) { WYQSNode *pNode = (WYQSNode *) WYQMalloc( sizeof( WYQSNode ) ); WYQAssert( pNode != NULL ); pNode->no = i; pNode->level = GEN_LVL(); WYQList_Add( &head2, &pNode->head ); }
return WYQ_OK; }
static WYQEResult visit( int32_t who ) { if ( who == 1 ) WYQList_Visit( &head1, WYQSNode_Visit ); else if ( who == 2 ) WYQList_Visit( &head2, WYQSNode_Visit ); else return WYQ_EINV;
return WYQ_OK; }
static WYQEResult reverse( int32_t who ) { if ( who == 1 ) WYQList_Reverse( &head1 ); else if ( who == 2 ) WYQList_Reverse( &head2 ); else return WYQ_EINV;
return WYQ_OK; }
static WYQEResult splice( int32_t from, int32_t to ) { if ( from == 1 ) { if ( to == 2 ) WYQList_Splice( &head2, &head1 ); else return WYQ_EINV; } else if ( from == 2 ) { if ( to == 1 ) WYQList_Splice( &head1, &head2 ); else return WYQ_EINV;
} else return WYQ_EINV;
return WYQ_OK; }
static WYQEResult term( void ) { WYQList_Clear( &head1, WYQSNode_Destor ); WYQList_Clear( &head2, WYQSNode_Destor );
return WYQ_OK; }
int main( void ) { int32_t flag; int32_t cmd;
WYQFunCheck( WYQ_Init() );
WYQFunCheck( init() );
help();
for ( flag = 1; flag != 0; ) {
scanf( "%d", &cmd );
switch ( cmd ) { case 0: flag = 0; break;
case 1: WYQFunCheck( visit( 1 ) ); break;
case 2: WYQFunCheck( visit( 2 ) ); break;
case 3: WYQFunCheck( reverse( 1 ) ); break;
case 4: WYQFunCheck( reverse( 2 ) ); break;
case 5: WYQFunCheck( splice( 1, 2 ) ); break;
case 6: WYQFunCheck( splice( 2, 1 ) ); break;
default: help(); break; } }
WYQFunCheck( term() );
WYQFunCheck( WYQ_Term() );
return 0; } |
本文演示了一个简单通用的双向链表的实现,主要目的是给读者一些自己在链表使用过程中的心得体会,希望能为程序质量的提升,并给读者一些有意的启迪。
谢谢!