简体   繁体   English

从单链表中删除一个条目

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

So today I was watching The mind behind Linux |所以今天我在看Linux 背后的思想 | Linus Torvalds , Linus posted two pieces of code in the video, both of them are used for removing a certain element in a singly-linked list. Linus Torvalds ,Linus 在视频中发布了两段代码,它们都用于删除单链表中的某个元素。

The first one (which is the normal one):第一个(这是正常的):

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

And the better one:还有一个更好的:

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

So I cannot understand the second piece of code, what happens when *indirect = entry->next;所以我无法理解第二段代码,当*indirect = entry->next;时会发生什么evaluates?评价? I cannot see why it leads to the remove of the certain entry.我不明白为什么它会导致删除某个条目。 Someone explains it please, thanks!请哪位大神解释一下,谢谢!

what happens when *indirect = entry->next; *indirect = entry->next;时会发生什么*indirect = entry->next; evaluates? 评估? I cannot see why it leads to the remove of the certain entry. 我不明白为什么它导致删除某些条目。

I hope you have clear understanding of double pointers 1) . 我希望你对双指针有清楚的认识1)

Assume following: 假设如下:
Node structure is 节点结构是

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

and linked list is having 5 nodes and the entry pointer pointing to second node in the list. 链表有5节点, entry指针指向列表中的第二个节点。 The in-memory view would be something like this: 内存中的视图将是这样的:

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

This statement: 这个说法:

linked_list** indirect = &head;

will make indirect pointer pointing to head . 将使indirect指针指向head

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

The while loop while循环

    while ((*indirect) != entry)

*indirect will give the address of first node because head is pointing to first node and since entry is pointing to second node the loop condition evaluates to true and following code will execute: *indirect将给出第一个节点的地址,因为head指向第一个节点,因为entry指向第二个节点,循环条件的计算结果为true ,后面的代码将执行:

indirect = &(*indirect)->next;

this will make the indirect pointer pointing to the next pointer of first node. 这将使indirect指针指向第一个节点的next指针。 The in-memory view: 内存中的视图:

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

now the while loop condition will be evaluated. 现在将评估while循环条件。 Because the indirect pointer is now pointing to next of first node, the *indirect will give the address of second node and since entry is pointing to second node the loop condition evaluates to false and the loop exits. 因为indirect指针现在指向第一个节点的next一个,所以*indirect将给出第二个节点的地址,并且由于entry指向第二个节点,因此循环条件的计算结果为false并且循环退出。
The following code will execute now: 以下代码现在将执行:

*indirect = entry->next;

The *indirect dereference to next of first node and it is now assigned the next of node which entry pointer is pointing to. *indirect解引用到next首节点,它现在被指定为next该节点的entry指针指向。 The in-memory view: 内存中的视图:

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

Now the next of first node is pointing to third node in the list and that way the second node is removed from the list. 现在,第一个节点的next一个节点指向列表中的第三个节点,这样就可以从列表中删除第二个节点。

Hope this clear all of your doubts. 希望这清楚你所有的疑虑。


EDIT : 编辑

David has suggested, in comment, to add some details around - why are the (..) parenthesis required in &(*indirect)->next ? 大卫在评论中建议添加一些细节 - 为什么(..) &(*indirect)->next所需的(..)括号&(*indirect)->next

The type of indirect is linked_list ** , which means it can hold the address of pointer of type linked_list * . indirect的类型是linked_list ** ,这意味着它可以保存linked_list *类型的指针的地址。 The *indirect will give the pointer of type linked_list * and ->next will give its next pointer. *indirect将给出linked_list *类型的指针,而->next将给出它的next指针。
But we cannot write *indirect->next because the precedence of operator -> is higher than unary * operator. 但是我们不能写*indirect->next因为operator ->的优先级高于unary * operator。 So, *indirect->next will be interpreted as *(indirect->next) which is syntactically wrong because indirect is a pointer to pointer. 因此, *indirect->next将被解释为*(indirect->next) ,这在语法上是错误的,因为indirect是指向指针的指针。 Hence we need () around *indirect . 因此我们需要()围绕*indirect

Also, &(*indirect)->next will be interpreted as &((*indirect)->next) , which is the address of the next pointer. 另外, &(*indirect)->next将被解释为&((*indirect)->next) ,这是next指针的地址。


1) If you don't know how double pointer works, check below: 1)如果您不知道双指针的工作原理,请查看以下内容:

Lets take an example: 让我们举一个例子:

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

In the above code, in this statement - *pp = &b; 在上面的代码中,在此声明中 - *pp = &b; , you can see that without accessing pointer p directly we can change the address it is pointing to using a double pointer pp , which is pointing to pointer p , because dereferencing the double pointer pp will give pointer p . ,你可以看到,如果不直接访问指针p ,我们可以使用双指针pp改变它所指向的地址,这指向指针p ,因为取消引用双指针pp将给出指针p

Its output: 它的输出:

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

In-memory view would be something like this: 内存中的视图将是这样的:

//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 |
      +---+     +---+     +---+
                ^^^^^     ^^^^^

The entry isn't really "deleted", it's just no longer in the list. 该条目并未真正“删除”,它已不在列表中。 If this is your chain: 如果这是你的链:

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

And you want to delete C, you're really just linking over it. 而你想删除C,你真的只是链接它。 It's still there in memory, but no longer accessible from your data structure. 它仍然存在于内存中,但不再可以从您的数据结构中访问。

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

That last line sets the next pointer of B to D instead of C. 最后一行将B的next指针设置为D而不是C.

Instead of looping through the entries in the list, as the first example does, the second example loops through the pointers to the entries in the list. 如第一个示例所示,第二个示例不是循环遍历列表中的条目,而是遍历指向列表中条目的指针 That allows the second example to have the simple conclusion with the statement you've asked about, which in English is "set the pointer that used to point to the entry I want to remove from the list so that it now points to whatever that entry was pointing to". 这允许第二个例子用您所询问的语句得出简单的结论,在英语中是“设置用于指向我想要从列表中删除的条目的指针,以便它现在指向该条目指着“。 In other words, it makes the pointer that was pointing to the entry you're removing point past the entry you're removing. 换句话说,它使这是指向您删除点过去 ,你要移除项条目的指针。

The first example has to have a special way to handle the unique case of the entry you want to remove being the first entry in the list. 第一个示例必须有一种特殊的方法来处理要删除的条目的唯一大小写,作为列表中的第一个条目。 Because the second example loops through the pointers (starting with &head), it doesn't have a special case. 因为第二个例子循环遍历指针(以&head开头),所以它没有特殊情况。

*indirect = entry->next; * indirect = entry-> next; That just move it to the next node You need to remove the entry one So you have to point .. before entry node the next of the entry node So your loop should stop before the entry while ((*indirect)->next != entry) indirect = &(*indirect)->next 那只是将它移动到下一个节点你需要删除一个条目所以你必须指向..在入口节点之前的下一个入口节点所以你的循环应该在入口之前停止while((* indirect) - > next!= ()间接=&(*间接) - >下一步

(*indirect)->Next =entry-> next (*间接) - >下一个=输入 - >下一个

I hope that help you 我希望能帮助你

This will be much easier to understand if you rewrite indirect = &(*indirect)->next;如果你重写indirect = &(*indirect)->next; 这将更容易理解。 As Indirect = &((*indirect)->next); As 间接 = &((*indirect)->next);

The while loop will give us the address of a next pointer belong to some node of which the next pointer is pointing to the entry.So the last statement is actually changing the value of this next pointer so that it doesn't point to the entry anymore. while 循环将给我们一个 next 指针的地址,该地址属于某个节点,其中 next 指针指向条目。所以最后一条语句实际上是在更改这个 next 指针的值,使其不指向条目了。 And in the special case when the entry is head,the while loop will be skipped and the last line change the value of the head pointer and make it point to the next node of the entry并且在入口为头的特殊情况下,将跳过while循环,最后一行改变头指针的值,使其指向入口的下一个节点

This example is both a great way of manipulating linked list structures in particular, but also a really excellent way of demonstrating the power of pointers in general.这个例子既是一种特别是操作链表结构的好方法,也是一个非常好的方法来展示指针的力量。

When you delete an element from a singly-linked list, you have to make the previous node point to the next node, bypassing the node you're deleting.当你从单链表中删除一个元素时,你必须让前一个节点指向下一个节点,绕过你要删除的节点。 For example, if you're deleting node E , then whatever list pointer it is that used to point to E , you have to make it point to whatever E.next points to.例如,如果您要删除节点E ,那么无论它用于指向E的列表指针是什么,您都必须使其指向E.next指向的任何内容。

Now, the problem is that there are two possibilities for "whatever list pointer it is that used to point to E ".现在,问题在于“用于指向E的任何列表指针”有两种可能性。 Much of the time, it's some previous node's next pointer that points to E .很多时候,是某个前一个节点的next指针指向E But if E happens to be the first node in the list, that means there's no previous node in the list, and it's the top-level list pointer that points to E — in Linus's example, that's the variable head .但是,如果E恰好是列表中的第一个节点,这意味着列表中没有前一个节点,并且它是指向E的顶级列表指针——在 Linus 的示例中,这就是变量head

So in Linus's first, "normal" example, there's an if statement.所以在 Linus 的第一个“正常”示例中,有一个if语句。 If there's a previous node, the code sets prev->next to point to the next node.如果有前一个节点,代码设置prev->next指向下一个节点。 But if there's no previous node, that means it's deleting the node at the head of the list, so it sets head to point to the next node.但是如果没有前一个节点,这意味着它正在删除列表头部的节点,因此它将head设置为指向下一个节点。

And although that's not the end of the world, it's two separate assignments and an if condition to take care of what we thought of in English as "whatever list pointer it is that used to point to E ".尽管这不是世界末日,但它是两个独立的分配和一个if条件来处理我们在英语中所认为的“无论是用于指向E的列表指针”。 And one of the crucial hallmarks of a good programmer is an unerring sense for sniffing out needless redundancy like this and replacing it with something cleaner.优秀程序员的关键标志之一是能够准确无误地嗅出这种不必要的冗余并用更干净的东西取而代之。

In this case, the key insight is that the two things we might want to update, namely head or prev->next , are both pointers to a list node, or linked_list * .在这种情况下,关键的见解是我们可能想要更新的两件事,即headprev->next ,都是指向列表节点或linked_list *的指针。 And one of the things that pointers are great at is pointing at a thing we care about, even if that thing might be, depending on circumstances, one of a couple of different things.指针最擅长的事情之一就是指向我们关心的事情,即使根据情况,这件事可能是几件不同的事情中的一件。

Since the thing we care about is a pointer to a linked_list , a pointer to the thing we care about will be a pointer to a pointer to a linked_list , or linked_list ** .由于我们关心的是指向linked_list的指针,指向我们关心的东西的指针将是指向linked_listlinked_list **的指针。

And that's exactly what the variable indirect is in Linus's "better" example.这正是 Linus 的“更好”示例中的indirect变量。 It is, literally, a pointer to "whatever list pointer it is that used to point to E " (or, in the actual code, not E , but the passed-in entry being deleted).从字面上看,它是一个指向“用于指向E的任何列表指针”的指针(或者,在实际代码中,不是E ,而是被删除的传入entry )。 At first, the indirect pointer points to head , but later, after we've begun walking through the list to find the node to delete, it points at the next pointer of the node (the previous node) that points at the one we're looking at.起初, indirect指针指向head ,但后来,在我们开始遍历列表以找到要删除的节点后,它指向指向我们的节点(前一个节点)的next指针'重新看。 So, in any case, *indirect (that is, the pointer pointed to by indirect ) is the pointer we want to update.所以,在任何情况下, *indirect (也就是由indirect指向的指针)就是我们要更新的指针。 And that's precisely what the magic line这正是神奇的线

*indirect = entry->next;

does in the "better" example.在“更好”的例子中。

The other thing to notice (although this probably makes the code even more cryptic at first) is that the indirect variable also takes the place of the walk variable used in the first example.需要注意的另一件事(尽管这可能使代码一开始更加神秘)是indirect变量也代替了第一个示例中使用的walk变量。 That is, everywhere the first example used walk , the "better" example uses *indirect .也就是说,第一个示例在任何地方都使用walk ,“更好”的示例使用*indirect But that makes sense: we need to walk over all the nodes in the list, looking for entry .但这是有道理的:我们需要遍历列表中的所有节点,寻找entry So we need a pointer to step over those nodes — that's what the walk variable did in the first example.所以我们需要一个指针来越过这些节点——这就是第一个例子中walk变量所做的。 But when we find the entry we want to delete, the pointer to that entry will be "whatever list pointer it is that used to point to E " — and it will be the pointer to update.但是,当我们找到要删除的条目时,指向该条目的指针将是“用于指向E的任何列表指针”——它将是要更新的指针。 In the first example, we couldn't set walk to prev->next — that would just update the local walk variable, not head or one of the next pointers in the list.在第一个示例中,我们不能将walk设置为prev->next这只会更新本地walk变量,而不是head或列表中的next指针之一。 But by using the pointer indirect to (indirectly) walk the list, it's always the case that *indirect — that is, the pointer pointed to by indirect — is the original pointer to the node we're looking at (not a copy sitting in walk ), meaning it's something we can usefully update by saying *indirect = entry->next .但是通过使用indirect指向(间接)遍历列表的指针, *indirect (即indirect指向的指针)总是指向我们正在查看的节点的原始指针(而不是坐在其中的副本) walk ),这意味着我们可以通过说*indirect = entry->next来有效地更新它。

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

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