按值傳遞還是指針傳遞?
變量賦值有兩種方式:按值傳遞、按"指針"傳遞(指針也常稱為"引用")。不同的編程語言賦值的方式不一樣,例如Python是按"指針"傳遞的,Go是按值傳遞的。
注意,"指針"加了引號,因為它不是真正的按指針拷貝,見下文分析。
參數傳值其實也是變量賦值的過程,只不過參數是函數的本地變量而已。
按值傳遞的意思是每次賦值都拷貝內存中完整的數據結構對象,這時在內存中會保存兩份內容完全相同,但地址不同的數據對象。
按"指針"傳遞的意思是每次賦值都只拷貝內存中數據結構對象的地址,這個地址占用一個機器字長(一個機器字長,在32位cpu上為32bit共4字節,64位則64bit共8字節),當然有些數據結構除了指針還包括其它屬性,這時可能會占用數個機器字長。總之,按"指針"傳遞時,由於只拷貝一份能表示數據對象的屬性(比如地址),拷貝的內容非常少,速度非常快。但必須注意,拷貝"指針"后,內存中只有一份數據對象,但將有兩份完全相同的指向內存中數據對象的"指針",無論是通過哪個"指針"去修改數據對象,都會影響另一個。
對於那些不支持操作指針的語言,通常會將按"指針"傳遞稱為"淺拷貝(shallow copy)",然后額外提供一個函數或工具實現按指傳遞,這稱為"深拷貝(deep copy)"。
例如:
a=10
b=a
首先會在內存中划分一個格子用來創建數據對象10,然后將這個數據對象的地址保存到變量a中。
如果是按值拷貝的語言,則會在內存中拷貝一份數據對象10的副本,再將這個副本數據對象的地址保存到b中。
顯然,a和b保存的地址是不一樣的,內存中也有兩份內容完全相同的數據對象10。所以,修改a的值時不會影響b的值,修改b的值時不會影響a。
如果是按"指針"拷貝的語言,則會直接拷貝a中的地址並保存到b中。
因為a和b的地址都一樣,所以,修改a的值會影響b,修改b的值會影響a。
也許你已經發現了,按"指針"傳遞時,雖然a、b保存的地址相同,但如果a=11
,a將指向新的數據對象,而b仍然指向10,即b=10
,修改a並沒有影響b。這是因為數值是不可變的,無法在原始的內存地址處修改,也就是無法將10替換成11,所以只要想修改這種不可變的對象就一定會創建新數據對象。對此,有兩方面需要說明。
一方面,有些數據對象是可以在原始內存地址處直接進行替換修改的(例如python中的列表)。假設,某編程語言對數值也是可原處修改的,那么a=11
將會在內存中將10替換成11,而不會新創建另一個數據對象11。
另一方面,上面的"按指針傳遞"並非是真正的按指針傳遞,而是按引用傳遞,或者說是按地址傳遞。這就是前文"按指針傳遞"中的"指針"都加上了引號的原因。
真正的指針是額外保存的,是占用空間的,和變量不同(變量保存了地址,在棧空間中),它是保存在堆內存中的。對於支持指針操作的語言(如C、C++、Go等),需要使用語法獨立生成數據對象的指針,這類語言一般都能直接在原處修改數據對象。例如:
a=10
b=&a
其中b=&a
表示生成a所指向(因為a保存了地址)數據對象的一個額外的指針,這個指針中保存了數據對象的地址,然后將這個指針賦值給b,這時b保存的是指針的地址,而不是數據對象的地址。
這時,修改a,或者修改b都會影響另一方,因為支持指針操作的語言一般都支持原處修改:
a=11
print(*b) /* 輸出11 */
其中*b
表示解除指針的引用,也就是取得數據對象的內容。
再回到按"指針"傳遞的拷貝方式,雖然它不是真正的拷貝指針,而是拷貝地址,但對於那些支持原處修改的數據對象,它們達到的效果和真實的指針傳遞是一樣的。例如,數組、python的列表。
# 以下為python代碼:
L1=[1,2,3,4]
L2=L1
L2[0]=11
print(L1) # 輸出:[11,2,3,4]
可變對象的原處修改
支持指針操作的語言,通過指針修改數據時,是直接在原始地址塊上修改為新數據的。例如:
func main() {
a := 10
println(a)
println(&a)
println("---------------")
*(&a) = 20
println(a)
println(&a)
}
結果:
10
0xc042085f48
---------------
20
0xc042085f48
但是python中的可變對象(比如列表),雖然俗稱"原處修改",但並非真的原處修改,而是在堆內存中新創建一個數據對象,並將它作為可變對象的一部分,所以可變對象整體的地址沒有改變,但內部元素的地址已經改變了,也就是舊的元素對象被回收。
>>> L=[222,333,444,555]
>>> id(L),id(L[1])
(44652184, 43798256)
>>> L[1]=3333
>>> id(L),id(L[1])
(44652184, 43798240)