SingleLinkNode with Pointers-to-Pointers

最近在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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值