所以今天我在看The mind behind 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;
求值时会发生什么?我不明白为什么它会导致删除某个条目。请有人解释一下,谢谢!
7条答案
按热度按时间a64a0gku1#
*indirect = entry->next;
求值时会发生什么?我不明白为什么它会导致删除某个条目。*我希望你对双指针有清楚的理解。
假设如下:
节点结构为
链表有
5
个节点,entry
指针指向链表中的第二个节点。内存视图如下所示:本声明:
将使
indirect
指针指向head
。while
循环*indirect
将给予第一个节点的地址,因为head
指向第一个节点,而entry
指向第二个节点,所以循环条件的计算结果为true
,并将执行以下代码:这将使
indirect
指针指向第一个节点的next
指针。内存视图:现在
while
循环条件将被评估。因为indirect
指针现在指向第一个节点的next
,*indirect
将给予第二个节点的地址,并且由于entry
指向第二个节点,因此循环条件评估为false
并且循环退出。以下代码将立即执行:
*indirect
解引用到第一个节点的next
,现在它被分配到entry
指针所指向的节点的next
。内存视图:现在第一个节点的
next
指向列表中的第三个节点,这样第二个节点就从列表中删除了。希望这能消除你所有的疑虑。
编辑:
&(*indirect)->next
中需要(..)
括号?*indirect
的类型是linked_list **
,这意味着它可以保存linked_list *
类型的指针的地址。*indirect
将给予linked_list *
类型的指针,->next
将给出其next
指针。但是我们不能写
*indirect->next
,因为运算符->
的优先级高于一元运算符*
。因此,*indirect->next
将被解释为*(indirect->next)
,这在语法上是错误的,因为indirect
是指向指针的指针。因此我们需要()
围绕*indirect
。此外,
&(*indirect)->next
将被解释为&((*indirect)->next)
,这是next
指针的地址。**1)**如果您不知道双指针是如何工作的,请检查以下内容:
让我们举一个例子:
在上面的代码中,在这个语句-
*pp = &b;
中,你可以看到,在不直接访问指针p
的情况下,我们可以使用双指针pp
来改变它所指向的地址,双指针pp
指向指针p
,因为解引用双指针pp
将给予指针p
。其输出:
内存中视图应该是这样的:
nwlqm0z12#
该条目并不是真的被“删除”,它只是不再在列表中。如果这是你的链:
如果你想删除C,你实际上只是链接了它,它仍然在内存中,但不再能从你的数据结构中访问。
最后一行将B的
next
指针设置为D而不是C。nnt7mjpx3#
与第一个例子不同,第二个例子循环遍历列表中的条目,而是遍历指向列表中条目的 * 指针。这使得第二个例子可以用你所问的语句得出简单的结论,这在英语中是“设置用于指向我想从列表中删除的条目的指针,以便它现在指向该条目所指向的任何内容”。换句话说,它使指向要删除的条目的指针指向 * 过去 * 要删除的条目。
第一个例子必须有一个特殊的方法来处理你想要删除的条目是列表中的第一个条目的唯一情况。因为第二个例子循环通过指针(以&head开始),所以它没有特殊情况。
n6lpvg4x4#
(*indirect)-〉Next =entry-〉next
希望对你有帮助
mgdq6dx15#
这个例子是一个很好的操作链表结构的方法,也是一个很好的展示指针功能的方法。
当你从一个单链表中删除一个元素时,你必须让前一个节点指向下一个节点,绕过你要删除的节点。例如,如果你要删除节点
E
,那么无论是什么列表指针指向E
,你都必须让它指向E.next
所指向的。现在,问题是“无论是什么列表指针,它都曾经指向
E
”有两种可能性。大多数情况下,它是一些前一个节点的next
指针指向E
。但是如果E
恰好是列表中的第一个节点,这意味着列表中没有前一个节点,它是指向E
的顶级列表指针-在Linus的示例中,这是变量head
。所以在Linus的第一个“正常”示例中,有一个
if
语句。如果有前一个节点,代码将prev->next
设置为指向下一个节点。但是如果没有前一个节点,这意味着它将删除列表头部的节点,因此它将head
设置为指向下一个节点。虽然这不是世界末日,但它是两个独立的赋值和一个
if
条件,以照顾我们在英语中认为的“无论是什么列表指针,它曾经指向E
”。一个好的程序员的关键标志之一是能够准确无误地嗅出不必要的冗余,并用更干净的东西替换它。在这个例子中,关键的一点是我们想要更新的两个东西,即
head
或prev->next
,都是指向列表节点linked_list *
的指针,而指针最擅长的事情之一就是指向我们关心的东西,即使这个东西根据情况可能是几个不同的东西中的一个。因为我们关心的东西是指向
linked_list
的指针,所以指向我们关心的东西的指针将是指向linked_list
或linked_list **
的指针。这正是Linus的“更好”示例中的变量
indirect
,它是一个指向“任何用于指向E
的列表指针”的指针。(或者,在实际代码中,不是E
,而是要删除的传入的entry
)最初,indirect
指针指向head
,但后来,在我们开始遍历列表以找到要删除的节点之后,它指向节点(前一个节点)的next
指针,该指针指向我们正在查看的节点。因此,在任何情况下,*indirect
(即indirect
指向的指针)都是我们想要更新的指针。这正是魔术线在“更好”的例子中。
另一件需要注意的事情(尽管这可能会使代码在开始时更加晦涩)是
indirect
变量也取代了第一个例子中使用的walk
变量。也就是说,在第一个例子使用walk
的地方,“更好”的例子使用*indirect
。但这是有意义的:我们需要遍历列表中的所有节点,寻找entry
。因此我们需要一个指针来遍历这些节点-这就是第一个例子中walk
变量所做的。但是当我们找到我们想要删除的条目时,指向该条目的指针将是“用于指向E
的任何列表指针”-并且它将是要更新的指针。在第一示例中,我们不能将walk
设置为prev->next
-这只会更新本地walk
变量,而不是head
或列表中的next
指针之一。但是通过使用指针indirect
(间接)遍历列表,情况总是*indirect
-即,由indirect
-指向的指针是指向我们正在查看的节点的 * 原始 * 指针(而不是walk
中的副本),这意味着我们可以通过说*indirect = entry->next
来 * 有效地更新它。6qfn3psc6#
如果你重写一下,这会容易理解得多
作为
while循环会给予我们一个next指针的地址,这个指针属于某个节点,而这个节点的next指针指向这个条目,所以最后一个语句实际上是改变了next指针的值,这样它就不再指向这个条目了,在特殊情况下,当条目是head的时候,while循环将被跳过,并且最后一行改变头指针的值,并使其指向条目的下一个节点。
kfgdxczn7#
一个注解是,这两种解决方案都假设头指针不指向NULL,并且要删除的条目确实存在于列表中。