在leetcode上做到 203. 移除链表元素的题目,该题目的难度为简单,但是在做的时候,由于对Python中的可更改对象和不可更改对象概念理解不是很深刻,导致看不懂答案。该题的一种解法如下:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
head_v = ListNode(0)
head_v.next = head # 设置一个虚拟头结点,方便操作
node = head_v # 创建一个head_v对象的引用,后面对该引用进行操作,达到不改变head_v结点指针的同时更改head_v链表里的内容
while node.next:
if node.next.val == val:
node.next = node.next.next
else:
node = node.next
return head_v.next
关于该问题的解法,本文不多做阐述,感兴趣的可以去leetcode看相关解答,我们这里关注点在于,为何对node链表进行遍历和修改,head_v链表的内容会跟着改变,但head_v指向的地址没有改变。
在python中不存在所谓的传值调用,一切传递的都是对象的引用,也可以认为是传址。Python中自带的数据格式对应的对象分为可更改对象和不可更改对象,不可更改对象包括int、字符串(string)、float、(数值型number)、元组(tuple);可更改对象主要指list和dict。对于这二者的区别,本文不多阐述,感兴趣自行百度,这里我们思考自定义的对象的可更改性。
在上面的代码中head_v是一个ListNode对象,head_v的成员变量next也是一个ListNode对象,另一个成员变量val是一个int类型。我们创建head_v对象的一个引用node,当我们改变node的成员变量时,head_v会跟着改变吗?按照该题解答的逻辑,head_v应该是会跟着变的。我们来验证一下:
#!/usr/bin/python
class node:
def __init__(self, val=0, list_=[], next=None):
self.val = val
self.list_ = list_
self.next = next
node1 = node(val=1, list_=[1,1])
node2 = node(val=2, list_=[2,2])
node3 = node(val=3, list_=[3,3])
node1.next = node2
node2.next = node3
node1_cp = node1 # 创建node1的引用
print(node1)
print(node1_cp) # 引用的地址与node1相同
print(node1.next) # 该地址和node2的地址相同
print(node1_cp.next) # 引用的对象类型成员变量地址与node1的对应成员变量地址相同
print(node2)
print(node3)
# 3个node各自地址互不相同
#修改node1的成员变量,node1_cp的也会相应改变,自定义成员变量都是可更改的
node1.val = -1 # 即使是int类型的成员变量也是可更改的
node1.list_.append(1)
print(node1_cp.val)
print(node1_cp.list_)
# 修改node1本身
node1=node2 # 该操作相当于将node1的地址修改为了node2的地址
print(node1) # 此时node1地址已经修改为node2地址
print(node1_cp) # 但node1_cp的地址没有随之改变
print(node1_cp.next) # 仍是node2的地址
# 由此可见对象引用的成员变量是可更改的,但是对象引用本身是不可更改的,类比于list对象:
a = [1,2,3]
b = a
c= [4,5,6]
b.append(4)
print(a,b) # [1, 2, 3, 4] [1, 2, 3, 4]
b = c
print(a,b) # [1, 2, 3, 4] [4, 5, 6]
# list中的每个元素相当于对象里的成员变量, list对象本身地址改变时,其引用的地址不会随之改变
# 所以,node1的地址改变了,不影响node1_cp仍指向头结点。且对新node1的修改,也即对node2的修改,也即对node1_cp.next的修改,因为三者都是指向node2对象的地址。
所以该题中,对head_v的引用node本身的地址的修改(node=node.next)不会影响head_v本身地址的改变,node地址变成了node.next的地址,但node.next地址与head_v中的head_v.next的地址仍然是相同的,所以对node.next中的成员变量修改,仍然会对应到head_v.next中,除非node.next本身被赋值成其他地址。
总结:
1. 一个自定义对象是可更改的,自定义对象引用的成员变量被修改时,该自定义对象的成员变量也会被修改。
2. 当自定义对象引用本身地址被修改时,其引用的对象会维持原来地址。