最近在reddit上看到了一篇文章,觉得不错,在这里大概记录下自己的理解。原文链接。
有这篇文章的原因是Linus在slashdot上回答问题,回答某个问题时,做了如下回答:
At the opposite end of the spectrum, I actually wish more people understood the really core low-level kind of coding. Not big, complex stuff like the lockless name lookup, but simply good use of pointers-to-pointers etc. For example, I’ve seen too many people who delete a singly-linked list entry by keeping track of the “prev” entry, and then to delete the entry, doing something like
if (prev)
prev->next = entry->next;
else
list_head = entry->next;
and whenever I see code like that, I just go “This person doesn’t understand pointers”. And it’s sadly quite common.
People who understand pointers just use a “pointer to the entry pointer”, and initialize that with the address of the list_head. And then as they traverse the list, they can remove the entry without using any conditionals, by just doing a “*pp = entry->next”.
大概意思是很多人在删除单链表中某个元素时,会用一个变量记录当前链表节点的上一个节点,当要删除当前节点时,用上个节点的next域指向当前节点的下一个节点。就像引用中的代码那样。说实话我以前一直是这么做的,而且觉得只能这么做啊,还能怎么样?但是Linus说,这么做的人是没有理解指针的,汗啊。。。Linus说理解指针的人应该用一个指向指针的指针去跟踪每个节点。指针这个东西,说很难说明白,下面我用代码来解释下我的理解。
typedef struct node
{
struct node * next;
....
} node;
typedef bool (* remove_fn)(node const * v);
// Remove all nodes from the supplied list for which the
// supplied remove function returns true.
// Returns the new head of the list.
node * remove_if(node * head, remove_fn rm)
{
for (node * prev = NULL, * curr = head; curr != NULL; )
{
node * const next = curr->next;
if (rm(curr))
{
if (prev)
prev->next = next;
else
head = next;
free(curr);
}
else
prev = curr;
curr = next;
}
return head;
}
这段代码很好理解,通常我们处理单链表节点的删除就是这么做的。
Linus描述的方法:
void remove_if(node ** head, remove_fn rm)
{
for (node** curr = head; *curr;)
{
node * entry = *curr;
if (rm(entry))
{
*curr = entry->next;
free(entry);
}
else
curr = &entry->next;
}
}
同上一段代码有何改进呢?我们看到:不需要prev指针了,也不需要再去判断是否为链表头了,但是,curr变成了一个指向指针的指针。这正是这段程序的精妙之处。(注意,我所highlight的那三行代码)
让我们来人肉跑一下这个代码,对于——
删除节点是表头的情况,输入参数中传入head的二级指针,在for循环里将其初始化curr,然后entry就是*head(*curr),我们马上删除它,那么第8行就等效于*head = (*head)->next,就是删除表头的实现。
删除节点不是表头的情况,对于上面的代码,我们可以看到——
1)(第12行)如果不删除当前结点 —— curr保存的是当前结点next指针的地址。
2)(第5行) entry 保存了 *curr —— 这意味着在下一次循环:entry就是prev->next指针所指向的内存。
3)(第8行)删除结点:*curr = entry->next; —— 于是:prev->next 指向了 entry -> next;
是不是很巧妙?我们可以只用一个二级指针来操作链表,对所有节点都一样。
自己验证的单链表创建与删除实例:
/*************************************************************************
> File Name: SingleListNode.cpp
> Author:
> Mail:
> Created Time: 2016年07月10日 星期日 11时35分51秒
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstdlib>
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
void AddToHead(ListNode** pHead , int value)
{
ListNode* pNew = new ListNode();
if(pNew == NULL)
return;
pNew->m_nValue = value;
pNew->m_pNext = NULL;
if(*pHead == NULL)
*pHead = pNew;
else
{
pNew->m_pNext = *pHead;
*pHead = pNew;
}
}
void AddToTail(ListNode** pHead , int value)
{
ListNode* pNew = new ListNode();
if(pNew == NULL)
return;
pNew->m_nValue = value;
pNew->m_pNext = NULL;
if(*pHead == NULL)
*pHead = pNew;
else
{
ListNode* pNode = *pHead;
while(pNode->m_pNext != NULL)
pNode = pNode->m_pNext;
pNode->m_pNext = pNew;
}
}
void Print(ListNode** pHead)
{
for(ListNode* pNode = *pHead ; pNode ; )
{
printf("%d " , pNode->m_nValue);
pNode = pNode->m_pNext;
}
}
void Remove_if_P2P(ListNode** pHead , int value)
{
for(ListNode** pNode = pHead ; *pNode ;)
{
ListNode* tmp = *pNode;
if(tmp->m_nValue == value)
{
*pNode = tmp->m_pNext;
delete tmp;
}
else
{
pNode = &tmp->m_pNext;
}
}
}
void Remove_if(ListNode* pHead , int value)
{
for(ListNode* prev = NULL , *tmp = pHead; tmp ; )
{
ListNode* next = tmp->m_pNext;
if(tmp->m_nValue == value)
{
if(prev)
prev->m_pNext = next;
else
pHead = next;
delete tmp;
}
else
{
prev = tmp;
}
tmp = next;
}
}
int main()
{
struct ListNode *pfirst=NULL;
struct ListNode *psecond= NULL;
for(int i =0; i <10 ; i++)
{
AddToHead(&pfirst , i);
AddToTail(&psecond, i);
}
Print(&pfirst);
printf("\n--------------------------------\n");
Print(&psecond);
printf("\n--------------------------------\n");
Remove_if_P2P(&pfirst,5);
Print(&pfirst);
printf("\n--------------------------------\n");
Remove_if(psecond,6);
Print(&psecond);
return 0;
}