繁体   English   中英

从单链表中删除一个条目

[英]delete an entry from a singly-linked list

所以今天我在看Linux 背后的思想 | Linus Torvalds ,Linus 在视频中发布了两段代码,它们都用于删除单链表中的某个元素。

第一个(这是正常的):

void remove_list_entry(linked_list* entry) {
    linked_list* prev = NULL;
    linked_list* walk = head;
    while (walk != entry) {
        prev = walk;
        walk = walk->next;
    }
    if (!prev) {
        head = entry->next;
    } else {
        prev->next = entry->next;
    }
}

还有一个更好的:

void remove_list_entry(linked_list* entry) {
    // The "indirect" pointer points to the
    // *address* of the thing we'll update
    linked_list** indirect = &head;

    // Walk the list, looking for the thing that
    // points to the entry we want to remove
    while ((*indirect) != entry)
        indirect = &(*indirect)->next;

    // .. and just remove it
    *indirect = entry->next;
}

所以我无法理解第二段代码,当*indirect = entry->next;时会发生什么评价? 我不明白为什么它会导致删除某个条目。 请哪位大神解释一下,谢谢!

*indirect = entry->next;时会发生什么*indirect = entry->next; 评估? 我不明白为什么它导致删除某些条目。

我希望你对双指针有清楚的认识1)

假设如下:
节点结构是

typedef struct Node {
    int data;
    struct Node *next;
} linked_list;

链表有5节点, entry指针指向列表中的第二个节点。 内存中的视图将是这样的:

                          entry -+
   head                          |
      +---+     +-------+     +-------+     +-------+     +-------+     +--------+
      |   |---->| 1 |   |---->| 2 |   |---->| 3 |   |---->| 4 |   |---->| 5 |NULL|
      +---+     +-------+     +-------+     +-------+     +-------+     +--------+

这个说法:

linked_list** indirect = &head;

将使indirect指针指向head

                         entry -+
  head                          |
     +---+     +-------+     +-------+     +-------+     +-------+     +--------+
     |   |---->| 1 |   |---->| 2 |   |---->| 3 |   |---->| 4 |   |---->| 5 |NULL|
     +---+     +-------+     +-------+     +-------+     +-------+     +--------+
       ^
       |
     +---+
     |   |
     +---+
   indirect

while循环

    while ((*indirect) != entry)

*indirect将给出第一个节点的地址,因为head指向第一个节点,因为entry指向第二个节点,循环条件的计算结果为true ,后面的代码将执行:

indirect = &(*indirect)->next;

这将使indirect指针指向第一个节点的next指针。 内存中的视图:

                          entry -+
   head                          |
      +---+     +-------+     +-------+     +-------+     +-------+     +--------+
      |   |---->| 1 |   |---->| 2 |   |---->| 3 |   |---->| 4 |   |---->| 5 |NULL|
      +---+     +-------+     +-------+     +-------+     +-------+     +--------+
                      ^
                      |
                    +---+
                    |   |
                    +---+
                  indirect

现在将评估while循环条件。 因为indirect指针现在指向第一个节点的next一个,所以*indirect将给出第二个节点的地址,并且由于entry指向第二个节点,因此循环条件的计算结果为false并且循环退出。
以下代码现在将执行:

*indirect = entry->next;

*indirect解引用到next首节点,它现在被指定为next该节点的entry指针指向。 内存中的视图:

                          entry -+
   head                          |
      +---+     +-------+     +-------+     +-------+     +-------+     +--------+
      |   |---->| 1 |   |--   | 2 |   |---->| 3 |   |---->| 4 |   |---->| 5 |NULL|
      +---+     +-------+  \  +-------+     +-------+     +-------+     +--------+
                  *indirect \              /
                             +------------+

现在,第一个节点的next一个节点指向列表中的第三个节点,这样就可以从列表中删除第二个节点。

希望这清楚你所有的疑虑。


编辑

大卫在评论中建议添加一些细节 - 为什么(..) &(*indirect)->next所需的(..)括号&(*indirect)->next

indirect的类型是linked_list ** ,这意味着它可以保存linked_list *类型的指针的地址。 *indirect将给出linked_list *类型的指针,而->next将给出它的next指针。
但是我们不能写*indirect->next因为operator ->的优先级高于unary * operator。 因此, *indirect->next将被解释为*(indirect->next) ,这在语法上是错误的,因为indirect是指向指针的指针。 因此我们需要()围绕*indirect

另外, &(*indirect)->next将被解释为&((*indirect)->next) ,这是next指针的地址。


1)如果您不知道双指针的工作原理,请查看以下内容:

让我们举一个例子:

#include <stdio.h>

int main() {
        int a=1, b=2;
        int *p = &a;
        int **pp = &p;

        printf ("1. p : %p\n", (void*)p);
        printf ("1. pp : %p\n", (void*)pp);
        printf ("1. *p : %d\n", *p);
        printf ("1. *pp : %d\n", **pp);

        *pp = &b;  // this will change the address to which pointer p pointing to
        printf ("2. p : %p\n", (void*)p);
        printf ("2. pp : %p\n", (void*)pp);
        printf ("2. *p : %d\n", *p);
        printf ("2. *pp : %d\n", **pp);

        return 0;
}

在上面的代码中,在此声明中 - *pp = &b; ,你可以看到,如果不直接访问指针p ,我们可以使用双指针pp改变它所指向的地址,这指向指针p ,因为取消引用双指针pp将给出指针p

它的输出:

1. p : 0x7ffeedf75a38
1. pp : 0x7ffeedf75a28
1. *p : 1
1. *pp : 1
2. p : 0x7ffeedf75a34   <=========== changed 
2. pp : 0x7ffeedf75a28
2. *p : 2
2. *pp : 2

内存中的视图将是这样的:

//Below in the picture
//100 represents 0x7ffeedf75a38 address
//200 represents 0x7ffeedf75a34 address
//300 represents 0x7ffeedf75a28 address

int *p = &a
      p         a
      +---+     +---+
      |100|---->| 1 |
      +---+     +---+

        int **pp = &p;

      pp        p         a
      +---+     +---+     +---+
      |300|---->|100|---->| 1 |
      +---+     +---+     +---+


*pp = &b;

      pp        p         b
      +---+     +---+     +---+
      |300|---->|200|---->| 2 |
      +---+     +---+     +---+
                ^^^^^     ^^^^^

该条目并未真正“删除”,它已不在列表中。 如果这是你的链:

A --> B --> C --> D --> E --> ■

而你想删除C,你真的只是链接它。 它仍然存在于内存中,但不再可以从您的数据结构中访问。

            C 
A --> B --------> D --> E --> ■

最后一行将B的next指针设置为D而不是C.

如第一个示例所示,第二个示例不是循环遍历列表中的条目,而是遍历指向列表中条目的指针 这允许第二个例子用您所询问的语句得出简单的结论,在英语中是“设置用于指向我想要从列表中删除的条目的指针,以便它现在指向该条目指着“。 换句话说,它使这是指向您删除点过去 ,你要移除项条目的指针。

第一个示例必须有一种特殊的方法来处理要删除的条目的唯一大小写,作为列表中的第一个条目。 因为第二个例子循环遍历指针(以&head开头),所以它没有特殊情况。

* indirect = entry-> next; 那只是将它移动到下一个节点你需要删除一个条目所以你必须指向..在入口节点之前的下一个入口节点所以你的循环应该在入口之前停止while((* indirect) - > next!= ()间接=&(*间接) - >下一步

(*间接) - >下一个=输入 - >下一个

我希望能帮助你

如果你重写indirect = &(*indirect)->next; 这将更容易理解。 As 间接 = &((*indirect)->next);

while 循环将给我们一个 next 指针的地址,该地址属于某个节点,其中 next 指针指向条目。所以最后一条语句实际上是在更改这个 next 指针的值,使其不指向条目了。 并且在入口为头的特殊情况下,将跳过while循环,最后一行改变头指针的值,使其指向入口的下一个节点

这个例子既是一种特别是操作链表结构的好方法,也是一个非常好的方法来展示指针的力量。

当你从单链表中删除一个元素时,你必须让前一个节点指向下一个节点,绕过你要删除的节点。 例如,如果您要删除节点E ,那么无论它用于指向E的列表指针是什么,您都必须使其指向E.next指向的任何内容。

现在,问题在于“用于指向E的任何列表指针”有两种可能性。 很多时候,是某个前一个节点的next指针指向E 但是,如果E恰好是列表中的第一个节点,这意味着列表中没有前一个节点,并且它是指向E的顶级列表指针——在 Linus 的示例中,这就是变量head

所以在 Linus 的第一个“正常”示例中,有一个if语句。 如果有前一个节点,代码设置prev->next指向下一个节点。 但是如果没有前一个节点,这意味着它正在删除列表头部的节点,因此它将head设置为指向下一个节点。

尽管这不是世界末日,但它是两个独立的分配和一个if条件来处理我们在英语中所认为的“无论是用于指向E的列表指针”。 优秀程序员的关键标志之一是能够准确无误地嗅出这种不必要的冗余并用更干净的东西取而代之。

在这种情况下,关键的见解是我们可能想要更新的两件事,即headprev->next ,都是指向列表节点或linked_list *的指针。 指针最擅长的事情之一就是指向我们关心的事情,即使根据情况,这件事可能是几件不同的事情中的一件。

由于我们关心的是指向linked_list的指针,指向我们关心的东西的指针将是指向linked_listlinked_list **的指针。

这正是 Linus 的“更好”示例中的indirect变量。 从字面上看,它是一个指向“用于指向E的任何列表指针”的指针(或者,在实际代码中,不是E ,而是被删除的传入entry )。 起初, indirect指针指向head ,但后来,在我们开始遍历列表以找到要删除的节点后,它指向指向我们的节点(前一个节点)的next指针'重新看。 所以,在任何情况下, *indirect (也就是由indirect指向的指针)就是我们要更新的指针。 这正是神奇的线

*indirect = entry->next;

在“更好”的例子中。

需要注意的另一件事(尽管这可能使代码一开始更加神秘)是indirect变量也代替了第一个示例中使用的walk变量。 也就是说,第一个示例在任何地方都使用walk ,“更好”的示例使用*indirect 但这是有道理的:我们需要遍历列表中的所有节点,寻找entry 所以我们需要一个指针来越过这些节点——这就是第一个例子中walk变量所做的。 但是,当我们找到要删除的条目时,指向该条目的指针将是“用于指向E的任何列表指针”——它将是要更新的指针。 在第一个示例中,我们不能将walk设置为prev->next这只会更新本地walk变量,而不是head或列表中的next指针之一。 但是通过使用indirect指向(间接)遍历列表的指针, *indirect (即indirect指向的指针)总是指向我们正在查看的节点的原始指针(而不是坐在其中的副本) walk ),这意味着我们可以通过说*indirect = entry->next来有效地更新它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM