通过二级指针删除链表节点看链表本质

在大多数情况下,会用到链表,但是一直都没有仔细深究过,今天抽点时间做了个实验。

在看内核源码时,会发现使用二级指针插入或删除节点的情况,比如在字符设备里面的__register_chrdev_region及在notifier_chain_register里面。如下:

for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
        if ((*cp)->major > major ||
            ((*cp)->major == major &&
             (((*cp)->baseminor >= baseminor) ||
              ((*cp)->baseminor + (*cp)->minorct > baseminor))))
            break;

   ...

    cd->next = *cp;
    *cp = cd;

当找到要插入的地方(*cp),其实cp是上个节点的next指针所存放的地址,修改cp其实是修改了上个节点的next指针的指向。将被插入元素的next指针指向*cp指向的位置(即下一个节点),然后将*cp指针改向,改指为cd(该句保证与之前的链表元素连接不间断)。
其实精髓在于修改指针的指向,跟C里面的传地址和传值一样。

 

自己写了一个程序来验证一下:

#include <stdio.h>
#include <stdlib.h>
struct linklist
{
    int num;
    struct linklist *next;
};

struct linklist head = {.num = 0, .next = NULL};

int main()
{
    int i;
    struct linklist *tmp = NULL;
    struct linklist **ttmp = NULL;
    for(i = 1; i < 6; i += 2)
    {
        tmp = (struct linklist *)malloc(sizeof(*tmp));
        tmp->num = i;
        tmp->next = head.next;
        head.next = tmp;
    }

    ttmp = &(head.next);
    while(*ttmp)
    {
         printf("%d, %016x, %016x, %016x\n", (*ttmp)->num, ttmp, *ttmp, (*ttmp)->next);
         ttmp = &((*ttmp)->next);
    }

    printf("============================\n");
    struct linklist addnode = {.num = 2, .next = NULL};
    ttmp = &(head.next);
    while(*ttmp)
    {
        if ((*ttmp)->num < addnode.num)
        {
            break;
        }
        ttmp = &((*ttmp)->next);
    }
    addnode.next = *ttmp;
    *ttmp = &addnode;

    ttmp = &(head.next);
    while(*ttmp)
    {
        printf("%d, %016x, %016x, %016x\n", (*ttmp)->num, ttmp, *ttmp, (*ttmp)->next);
        ttmp = &((*ttmp)->next);
    }

...

return 0;
}

运行结果如下:

这种方法还适用于删除链表节点的场景,代码类似如下:

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节点的步骤,如下:

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

 

posted @ 2017-03-06 16:09  penghan  阅读(862)  评论(0编辑  收藏  举报