删除链接列表中的第一个节点

时间:2019-08-21 20:03:41

标签: c

我正在尝试从C上的简单链接列表中删除节点,当我删除除第一个节点以外的其他任何节点时,它都可以正常工作,但是当我尝试删除第一个节点时,整个列表都弄乱了,不同的解决方案,结果也一样,我不知道该怎么办

我的尝试之一是:

void deleteClient (client **p, int n){
    client *t = *p;

    if (t){
        while (t && t->id != n)
                t = t->next;
            if (t){
                client * ax = t;
                t = t->next;
                free(ax);
            }
    }
}

另一个是这个

void deleteClient (client **p, int n){
    client *t = *p;

    if (t)
        if (t->id == n){
            client * ax = *p;
            *p = (*p)->next;
            free(ax);
            return;
        }
        else{
            while (t->next && t->next->id != n)
                t = t->next;
            if (t->next){
                client * ax = t->next;
                t->next = t->next->next;
                free(ax);
            }
        }
}

但是在这两个版本的代码中,它只会从第二个节点开始删除,而如果我尝试删除第一个节点,则会弄乱整个列表。

2 个答案:

答案 0 :(得分:2)

您可以通过简单地使用指向节点的指针到指针来保存当前节点和指向下一个节点的指针来消除对多种情况的测试(节点是否为第一个,如果不是第一个,等等。)

/** delete node with value n from list (for loop) */
void deleteClient (client **p, int n)
{
    client **ppn = p;           /* pointer to pointer to node*/
    client *pn = *p;            /* pointer to node */

    for (; pn; ppn = &pn->next, pn = pn->next) {
        if (pn->id == n) {
            *ppn = pn->next;    /* set address to next */
            free (pn);
            break;
        }
    }
}

此方法在Linus on Understanding Pointers

中有详细介绍

答案 1 :(得分:0)

在处理您的问题时,我遇到的第一个问题是:如果您定义了一个函数接口,该接口通过引用接收到client 的指针,为什么不您不是从这个事实中获利并用它来修改接收到的指针吗? (我对此感到惊讶,因为您在函数中所做的第一件事就是取消引用,并使用普通的指针,并且您不再触摸收到的原始指针了)如果您将指针传递给第一个节点,您将永远无法访问指针变量,并且您将无法更改其值,因此,您将永远无法取消链接第一个元素,因此,您需要访问指向第一个节点的指针(以便能够更改它)。擅长通过引用传递指针,但是很糟糕,因为您不知道为什么。

(pointer to 1st el.)
    +-----+          +----------------+       +----------------+
--->| *p >---------->| client | next >------->| client | next >------.
    +-----+          +----------------+       +----------------+     |
     ^                                                               V          
     |                                                              NULL
  +--|--+
  |  p  | (reference to the pointer that points to the first element)
  +-----+

移动指针引用时,您会遇到以下情况:

    +-----+          +----------------+       +----------------+
--->| *p >---------->| client | nxt >-------->| client | nxt >-------.
    +-----+          +----------------+       +----------------+     |
                               ^                                     V
       ,-----------------------'                                    NULL
    +--|--+
    |  p  | (see how the reference points to the pointer, not to client node)
    +-----+

在这种情况下,使用&p指向的引用,我们需要使指向下一个client节点的nxt指针而不是该节点的指针指向的值本身。如这里:

              ,---------------------------.
              |                           |
    +-----+   |      +----------------+   |   +----------------+
--->| *p >----'      | client | nxt >-----+-->| client | nxt >-------.
    +-----+          +----------------+       +----------------+     |
     ^                ^                                            =====
     |                |                                             ===
  +--|--+           +-|--+                                           =
  |  p  |           | q  | (q points to the client node, instead)
  +-----+           +----+

在此图中,q是一个节点指针,我们用来链接到要断开的client节点,以便free()断开链接。因此,您的第一种方法可以变成这样:

void deleteClient (client **p, int n)
{
    /* first advance the reference to the n-esim pointer,
     * (zero meaning the first node) We decrement n after
     * checking, and test goes on while *p is also non null
     */
    while (*p && (*p)->id != n)
        p = &(*p)->next; /* move the reference to a reference
                          * to the pointer in the next node. */

    client *q = *p;      /* temporary link to the node to be 
                          * freed */

    if (q) {             /* if found */
        *p = q->next;    /* make *p point to the next node. */
        free(q);         /* free the client node */
    }
}

调用此例程的方式应为:

client *list;

/* ... */
deleteClient(&list, 3); /* delete node with id == 3 */

语句p = &(*p)->next;需要一些解释:

  1. *pclient所引用的指针指向的p节点的地址。
  2. (*p)->nextnext所指向的节点的p指针。
  3. &(*p)->next是该指针的地址。

因此,我们使p指向由引用指针next指向的client节点的*p指针的地址。

注意

当您删除第一个节点时,您的代码将整个列表弄乱了的原因是,您使指针(指向第一个节点的初始指针)指向第二个节点,但是该指针位于函数的本地,并且您永远不会修改通过引用传递的指针(您一旦进入函数即立即修改所创建的副本,就永远不会在其上面进行修改),它会继续指向(现在为free() d)节点,因此这使情况变得一团糟(不仅您有一个指向无效地址的指针,而且泄漏了其余节点---因为指向节点的next字段可以被free()更改由于管理了返回的内存块---):)

最后,您有一个完整的示例here,可以从github签出。