SingleLinkNode with Pointers-to-Pointers

来源:互联网 发布:Mvc pattern java 编辑:程序博客网 时间:2024/05/16 09:18

最近在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: 20160710日 星期日 113551************************************************************************/#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;}
0 1