【问题标题】:When does a pointer to a linked list change the actual list?指向链表的指针什么时候改变实际的链表?
【发布时间】:2019-11-08 01:53:44
【问题描述】:

我有一个单链表 L,并创建一个指向该列表 P 的指针。似乎有时修改 P 会更改实际列表,而其他时候修改 P 对实际列表 L 没有任何作用,只会改变什么P 指向。

假设我创建了一个指向 L 的指针,P = L(在 python 中)。执行 P = P.next 之类的操作使 L 保持不变,但 P.next = P.next.next 会更改 L。类似地,通过修改 P.data 更改存储在列表中的实际数据实际上会更改 L.data。

为什么会这样?我觉得我缺少一些关于指针/引用的基本知识。

class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

    def addNode(self, val):
        root = self
        while root.next is not None:
            root = root.next
        root.next = Node(val)

    def iterateLL(self):
        root = self
        print
        while root is not None:
            print(str(root.val) + " ", end="")
            root = root.next
        print()

if __name__ =="__main__":
    L = Node(1)
    L.addNode(2)
    L.addNode(3)
    L.addNode(4)

    # iterate through list and print:
    L.iterateLL()

    # changing value of pointer does not affect L
    P = L
    P = P.next
    L.iterateLL() # L is unchanged

    # changing "next" value of pointer does affect L
    P = L 
    P.next = P.next.next
    L.iterateLL() # now we've skipped node 2

    # changing data of pointer does affect L
    P = L
    P.val = 10
    L.iterateLL()

上面的代码执行后输出如下(第一行显示原始链表,第二行显示指针P更改后链表没有变化,而第三和第四行显示链表发生了变化)

1 2 3 4

1 2 3 4

1 3 4

10 3 4

这里发生了什么?为什么更改 P 不会影响 L,但更改 P.next 和 P.val 会?如果所有这些操作的行为方式相同,那么更改指针也不会总是更改链接列表(因此 P = P.next 应该通过删除第一个节点来修改 L),或 从不 更改链接列表(因此 P.next = P.next.next 应该保持 L 不变)?

我感觉这与 L.next 是一个指针这一事实有关,就像 P.next 一样。所以修改 P.next 最终会修改 L.next 指向的(?)。但我觉得规则对我来说不是很清楚。

【问题讨论】:

  • python中没有指针
  • 如果我把手指指向你,然后我开始指向 Joe,这不会影响你或 Joe。如果我走到你面前,用你的手指指向 Joe 指向的任何人,那确实会影响你。
  • @SamuelMuldoon Python 中几乎所有的东西都是一个指针,Python 只是很好地隐藏了它们。大多数人甚至没有想到他们正在使用的变量只是指针这一事实。

标签: python pointers linked-list reference


【解决方案1】:

在 Python 中的大多数 情况下,当您对变量(在这种情况下为 P)执行赋值时,P 的值会发生变化,但它最初引用的对象会发生变化不是。这是因为 Python 变量只是对象的引用/指针。这是一个例子:

var1 = "test1"
var2 = "test2"
var3 = var1 # var3 = "test1"
var1 = var2 # var1 = "test2"

print(var1) # "test2"
print(var2) # "test2"
print(var3) # "test1"

那么这里发生了什么?好吧,我们只是改变了这些变量所指向的东西,我们并没有改变底层的对象。

现在,在您的情况下,您可以执行以下操作:

# changing value of pointer does not affect L
P = L
P = P.next
L.iterateLL() # L is unchanged

当您执行P = LP = P.next 时,您只是在更改变量P 所指向的内容。您没有对P 指向的基础对象进行更改。让我们可视化它。

原始配置:

P = L

P = L.next

但是,当你这样做时

P = L
P.next = P.next.next
L.iterateLL() # we've now skipped node two

您正在更改P 指向的对象的属性。您将属性P.next 设置为指向P.next.next。您实际上并未对 P.next 最初指向的基础对象进行更改。通过这样做,P.next 最初指向的对象超出范围并被垃圾收集器清理。

P.next = P.next.next

根据您的代码判断,我假设您在这种情况下的预期行为是从 LinkedList 中删除 L 并最终得到一个类似“2 3 4”的列表。为此,只需执行L = L.next 就足够了。这将导致第一个节点超出范围,垃圾收集器应该清理它。

作为一个简短的警告,我提到在大多数情况下,赋值不会更改变量指向的对象。但是,属性有点不同。它们覆盖了__set__ 魔术方法,该方法允许您使用赋值运算符编辑底层对象。这不是这里的情况。

【讨论】:

  • 好吧,这很有意义。感谢您的周到回复。这也解释了为什么设置 P.val = newVal 也会将 L.val 更改为 newVal。 (在这种情况下,我没有预期的代码用途,我只是想弄清楚这些指针在链表中会发生什么)
【解决方案2】:

你写的,

# changing "next" value of pointer does not affect L
P = L
P = P.next
L.iterateLL() # L is unchanged

但是,您没有更改下一个值。您阅读来自P.nextP.next 位于作业的右侧。为了更改P.nextP.next 必须位于赋值运算符的左侧 (=)

然后你写:

# changing "next.next" value of pointer does affect L
P = L 
P.next = P.next.next
L.iterateLL() # now we've skipped node 2

这次P.next 位于赋值运算符 (=) 的左侧。因此,你实际上改变了它。

write_to = read_from

【讨论】:

  • 我同意我不会更改 P.next,因此我同意该评论具有误导性。但是通过设置 P=P.next,P 现在是 2->3->4,而 L 仍然是 1->2->3->4。因此,如果我改为在评论中写“更改指针的值”,我认为问题仍然存在
  • @mcc:如果你必须从指针的角度考虑,那么就这样考虑。 P = whatever 正在将一个指针分配给另一个指针。这不会影响指针指向的任何内容。 P.next = whatever 还将一个指针分配给另一个指针,这不会影响指针指向的任何内容……但是您分配给的指针是另一个对象的成员,并且该成员现在指向新的地方。
猜你喜欢
  • 1970-01-01
  • 2012-08-11
  • 2019-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多