【问题描述】
前一阵子,看通用双向链表实现,一直有个问题,如下:
先看代码:
static DListNode *dlist_get_node(DList *thiz, size_t index, int fail_return_last) { DListNode *iter = thiz->first; while(iter!=NULL && iter->next!=NULL && index>0) { iter = iter->next; index--; } if(!fail_return_last) { iter = index>0 ? NULL:iter; } return iter; } DListRet dlist_insert(DList *thiz, size_t index, void *data) { DListNode *node = NULL; DListNode *cursor = NULL; if((node = dlist_node_create(data)) == NULL) { return DLIST_RET_ERR_CREATE_NODE; } if(thiz->first == NULL) { thiz->first = node; return DLIST_RET_OK; } cursor = dlist_get_node(thiz, index, 1); if(index < dlist_length(thiz)) { if(thiz->first == cursor) { thiz->first = node; } else { cursor->prev->next = node; node->prev = cursor->prev; } node->next = cursor; cursor->prev = node; } else { cursor->next = node; node->prev = cursor; } return DLIST_RET_OK; } DListRet dlist_prepend(DList *thiz, void *data) { return dlist_insert(thiz, 0, data); } DListRet dlist_append(DList *thiz, void *data) { return dlist_insert(thiz,-1, data); } size_t dlist_length(DList *thiz) { size_t length = 0; DListNode *iter = thiz->first; while(iter != NULL) { iter = iter->next; length++; } return length; }
这个dlist_prepend和dlist_append是如何工作的呢?仔细分析后,终于找到了答案。
【分析】
初看代码,你可能会产生如下疑问:
dlist_append和dlist_prepend函数中,index值都不大于0,因此dlist_insert函数中,光标定位cursor = dlist_get_node(thiz, index, 1);在插入过程中,永远是指向thiz->first,即首结点。而index<dlist_length(thiz)的判断恒为真,dlist_prepend和dlist_append貌似没有区别。
其实问题的关键就在index这个形参的声明上,注意形参声明为size_t index,而不是int index。size_t的定义如下
#define size_t unsigned int
是一个无符号数。
(1) dlist_prepend
该函数执行dlist_insert(thiz,0,data),在光标定位中,cursor = dlist_get_node(thiz,0,1);此时index>0不可能成立,故cursor指向thiz->first。而index<dlist_length(thiz)显然成立,故执行如下代码:
DListRet dlist_insert(DList *thiz, size_t index, void *data) { DListNode *node = NULL; DListNode *cursor = NULL; if((node = dlist_node_create(data)) == NULL) { return DLIST_RET_ERR_CREATE_NODE; } if(thiz->first == NULL) { thiz->first = node; return DLIST_RET_OK; } cursor = dlist_get_node(thiz, index, 1); if(index < dlist_length(thiz)) { if(thiz->first == cursor) { thiz->first = node; } ... node->next = cursor; cursor->prev = node; } ... return DLIST_RET_OK; }
省略部分未执行。
所以dlist_prepend实现的功能是在thiz->first处插入新结点,并将新结点调整为thiz->first。
(2) dlist_append
该函数执行dlist_insert(thiz,-1,data),在了解size_t后,就很容易理清了。其实,这个“-1”不是一个小于0的数,而是一个size_t所能表示的最大的无符号整数。因此,在光标定位中,cursor = dlist_get_node(thiz, -1, 1),下述代码被执行:
static DListNode *dlist_get_node(DList *thiz, size_t index, int fail_return_last) { DListNode *iter = thiz->first; while(iter!=NULL && iter->next!=NULL && index>0) { iter = iter->next; index--; } ... return iter; }
执行的结果是,光标cursor被定位到链表的末尾。而index<dlist_length(thiz)为假。故执行如下代码:
DListRet dlist_insert(DList *thiz, size_t index, void *data) { DListNode *node = NULL; DListNode *cursor = NULL; if((node = dlist_node_create(data)) == NULL) { return DLIST_RET_ERR_CREATE_NODE; } if(thiz->first == NULL) { thiz->first = node; return DLIST_RET_OK; } cursor = dlist_get_node(thiz, index, 1); ... else { cursor->next = node; node->prev = cursor; } return DLIST_RET_OK; }
执行的结果是,结点被插入到链表的末尾。并将新插入的结点调整为光标指向的结点。