簡體   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