zoukankan      html  css  js  c++  java
  • 通过二级指针删除链表节点看链表本质

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

    在看内核源码时,会发现使用二级指针插入或删除节点的情况,比如在字符设备里面的__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
    ", (*ttmp)->num, ttmp, *ttmp, (*ttmp)->next);
             ttmp = &((*ttmp)->next);
        }
    
        printf("============================
    ");
        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
    ", (*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;
    }
  • 相关阅读:
    angularjs的$on、$emit、$broadcast
    angularjs中的路由介绍详解 ui-route(转)
    ionic入门教程-ionic路由详解(state、route、resolve)(转)
    Cocos Creator 加载使用protobuf第三方库,因为加载顺序报错
    Cocos Creator 计时器错误 cc.Scheduler: Illegal target which doesn't have uuid or instanceId.
    Cocos Creator 构造函数传参警告 Can not instantiate CCClass 'Test' with arguments.
    Cocos Creator 对象池NodePool
    Cocos Creator 坐标系 (convertToWorldSpaceAR、convertToNodeSpaceAR)
    Cocos Creator 常驻节点addPersistRootNode
    Cocos Creator 配合Tiled地图的使用
  • 原文地址:https://www.cnblogs.com/chaozhu/p/6510415.html
Copyright © 2011-2022 走看看