《系统程序员成长计划》学习交流(3)-前向插入和后向插入释疑

【问题描述】

前一阵子,看通用双向链表实现,一直有个问题,如下:

先看代码:

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;
}


执行的结果是,结点被插入到链表的末尾。并将新插入的结点调整为光标指向的结点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值