1. 引子
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode()
p = dummy
while list1 != None and list2 != None:
if list1.val < list2.val:
p.next = list1
list1 = list1.next
p = p.next
elif list2.val <= list1.val:
p.next = list2
list2 = list2.next
p = p.next
if list1 != None:
p.next = list1
if list2 != None:
p.next = list2
return dummy.next
今天在刷一道leetcode,很简单用一个双指针和dummy node 的trick就可以很快的解决。但是做完之后突然细思恐极,这个list1,和list2在函数里被我各种 .next 操作会不会变了。赶紧查一下python的函数是怎么传值和传引用的
2. 理解概念
值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
其实区别就是值传递相当于考试的时候,场外的学霸看到了你的题,然后打印一份一样的,帮你做完再把答案发给你。
引用传递相当于学霸知道了你在考场坐在哪,然后直接替考。
3. python中使用
Python程序中存储的所有数据都是对象,每一个对象有一个身份,一个类型和一个值。
看变量的实际作用,执行a = 8 这行代码时,就会创建一个值为8,类型是int的对象。
变量名是对这个"一个值为8的int对象"的引用。
- 可以通过id() 来取得对象的身份
这个内置函数,它的参数是a这个变量名,这个函数返回的值是这个变量a引用的那个"一个值为8的int对象"的内存地址。
a = 8
print(id(a), id(8))
# 140711661084640 140711661084640
- 可以通过type()来取得a引用对象的数据类型
type(a)
# int
- 当变量出现在表达式中,它会被它引用的对象的值替代。
- Python函数传递参数到底是传值还是引用?
传值、引用这个是c/c++中的概念,Python中一切都是对象,实参向形参传递的是对象的引用值。就像Python赋值的意思。
如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值’来传递对象。
举个例子
- 传值
def test(c):
print('test before + :', id(c))
c += 2
print('test after + :', id(c))
return c
if __name__ == '__main__':
a = 2
print('main before invoke test:', id(a))
a_return = test(a)
print('main after invoke test: ',id(a))
print('return id:', id(a_return))
# main before invoke test: 140711661084448
# test before + : 140711661084448
# test after + : 140711661084512
# main after invoke test: 140711661084448
# return id: 140711661084512
- 传引用
在下面这个例子里没有修改c, 而只是修改了 c[0]其实是c.get(0),而这时候对c[0] 的修改就会直接再内存上修改
def test(c):
print('test before + :', id(c),id(c[0]))
c[0] += 10
print('test after + :', id(c),id(c[0]))
return c
if __name__ == '__main__':
a = [0,0,0]
print('main before invoke test:', id(a),id(a[0]))
a_return = test(a)
print('main after invoke test: ',id(a),id(a[0]))
print('return id:', id(a_return),id(a_return[0]))
# main before invoke test: 1911175809216 140711661084384
# test before + : 1911175809216 140711661084384
# test after + : 1911175809216 140711661084704
# main after invoke test: 1911175809216 140711661084704
# return id: 1911175809216 140711661084704
如果我们接着直接修改 c 试一下, 发现和传值是一样的,上文c+=2和这里c = c+[1]中左侧的c都是另一个id的引用了,相当于又开辟了一块新的内存,所以有人总说c = c+[1]很慢
def test(c):
print('test before + :', id(c),id(c[0]))
c[0] += 10
c = c+[1] #注意
print('test after + :', id(c),id(c[0]))
return c
if __name__ == '__main__':
a = [0,0,0]
print('main before invoke test:', id(a),id(a[0]))
a_return = test(a)
print('main after invoke test: ',id(a),id(a[0]))
print('return id:', id(a_return),id(a_return[0]))
# main before invoke test: 1911209370432 140711661084384
# test before + : 1911209370432 140711661084384
# test after + : 1911209103488 140711661084704 #注意
# main after invoke test: 1911209370432 140711661084704
# return id: 1911209103488 140711661084704 #注意
当然了我们一般不用c = c+[1],我们一般会用从c+=[1],或者c.append(1), 这样会发生什么呢,发现相当于在原来的c上修改并没有用新的c,所以有人说这样要快一点
def test(c):
print('test before + :', id(c),id(c[0]))
c[0] += 10
c+=[1]
print('test after + :', id(c),id(c[0]))
return c
if __name__ == '__main__':
a = [0,0,0]
print('main before invoke test:', id(a),id(a[0]))
a_return = test(a)
print('main after invoke test: ',id(a),id(a[0]))
print('return id:', id(a_return),id(a_return[0]))
# main before invoke test: 1911209010432 140711661084384
# test before + : 1911209010432 140711661084384
# test after + : 1911209010432 140711661084704
# main after invoke test: 1911209010432 140711661084704
# return id: 1911209010432 140711661084704
至于引子里的例子,list1和list2都相当于传值,类似于复制一份,然后处理复印件,(传引用的第二段代码,或者传值的代码)。然而如果对list1.val进行修改,当跳出函数时,这个修改仍然存在,相当于传引用(传引用的第一段代码)。
本文借鉴了
https://www.cnblogs.com/loleina/p/5276918.html
https://zhuanlan.zhihu.com/p/32828289