一文搞懂Golang?值傳遞還是引用傳遞
Go 官方的定義
本部分引用 Go 官方 FAQ 的 “When are function parameters passed by value?”,內(nèi)容如下。
如同 C 系列的所有語(yǔ)言一樣,Go 語(yǔ)言中的所有東西都是以值傳遞的。也就是說(shuō),一個(gè)函數(shù)總是得到一個(gè)被傳遞的東西的副本,就像有一個(gè)賦值語(yǔ)句將值賦給參數(shù)一樣。
傳值和傳引用
什么是傳值(值傳遞)
傳值的意思是:函數(shù)傳遞的總是原來(lái)這個(gè)東西的一個(gè)副本,一副拷貝。其指的是在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制一份傳遞到函數(shù)中,這樣在函數(shù)中如果對(duì)參數(shù)進(jìn)行修改,將不會(huì)影響到實(shí)際參數(shù)。 比如我們傳遞一個(gè)int類型的參數(shù),傳遞的其實(shí)是這個(gè)參數(shù)的一個(gè)副本;傳遞一個(gè)指針類型的參數(shù),其實(shí)傳遞的是這個(gè)該指針的一份拷貝,而不是這個(gè)指針指向的值。
對(duì)于int這類基礎(chǔ)類型我們可以很好的理解,它們就是一個(gè)拷貝,但是指針呢?我們覺(jué)得可以通過(guò)它修改原來(lái)的值,怎么會(huì)是一個(gè)拷貝呢?下面我們看個(gè)例子。
test_demo.go
package main import ( "fmt" "testing" ) func modify(ip *int) { fmt.Printf("函數(shù)里接收到的指針的內(nèi)存地址是:%p\n", &ip) *ip = 1 } func TestDemo(t *testing.T) { i := 10 ip := &i fmt.Printf("原始指針的內(nèi)存地址是:%p\n", &ip) modify(ip) fmt.Println("int值被修改了,新值為:", i) }
輸出結(jié)果:
原始指針的內(nèi)存地址是:0xc00000e038
函數(shù)里接收到的指針的內(nèi)存地址是:0xc00000e040
int值被修改了,新值為: 1
什么是傳引用(引用傳遞)
傳引用,也叫做引用傳遞, 指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)的地址直接傳遞到函數(shù)中,那么在函數(shù)中對(duì)參數(shù)所進(jìn)行的修改,將影響到實(shí)際參數(shù)。
在 Go 語(yǔ)言中,官方已經(jīng)明確了沒(méi)有傳引用,也就是沒(méi)有引用傳遞這一情況。
爭(zhēng)議最大的 map 和 slice
這時(shí)候又有小伙伴疑惑了,你看 Go 語(yǔ)言中的 map 和 slice 類型,能直接修改,難道不是同個(gè)內(nèi)存地址,不是引用了?
其實(shí)在 FAQ 中有一句提醒很重要:“map 和 slice 的行為類似于指針,它們是包含指向底層 map 或 slice 數(shù)據(jù)的指針的描述符”。
迷惑map
package main import ( "fmt" "testing" ) func modify(p map[string]int) { fmt.Printf("函數(shù)里接收到map的內(nèi)存地址是:%p\n", &p) p["張三"] = 20 } func TestDemo(t *testing.T) { persons := make(map[string]int) persons["張三"] = 19 mp := &persons fmt.Printf("原始map的內(nèi)存地址是:%p\n", mp) modify(persons) fmt.Println("map值被修改了,新值為:", persons) }
輸出結(jié)果:
原始map的內(nèi)存地址是:0xc000114028
函數(shù)里接收到map的內(nèi)存地址是:0xc000114030
確實(shí)是值傳遞,那修改后的 map 的結(jié)果應(yīng)該是什么。既然是值傳遞,那肯定就是 “這次一定!",對(duì)嗎?
輸出結(jié)果:
map值被修改了,新值為: map[張三:20]
原因:
指針類型可以修改,非指針類型不行,可以大膽的猜測(cè),使用make
函數(shù)創(chuàng)建的map
是不是一個(gè)指針類型呢?看一下源代碼:
// makemap implements a Go map creation make(map[k]v, hint) // If the compiler has determined that the map or the first bucket // can be created on the stack, h and/or bucket may be non-nil. // If h != nil, the map can be created directly in h. // If bucket != nil, bucket can be used as the first bucket. func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap { //省略無(wú)關(guān)代碼 }
通過(guò)查看src/runtime/hashmap.go
源代碼發(fā)現(xiàn),注意其返回的是 *hmap
類型,是一個(gè)指針。也就是 Go 語(yǔ)言通過(guò)對(duì) map
類型的相關(guān)方法進(jìn)行封裝,達(dá)到了用戶需要關(guān)注指針傳遞的作用。
現(xiàn)在看func modify(p map)
這樣的函數(shù),其實(shí)就等于func modify(p *hmap)
,和前面什么是值傳遞里舉的func modify(ip *int)
的例子一樣,可以參考分析。
這類情況我們稱其為 “引用類型” ,但 “引用類型” 不等同于就是傳引用,又或是引用傳遞了,還是有比較明確的區(qū)別的。
chan類型
chan類型本質(zhì)上和map類型是一樣的,這里不做過(guò)多的介紹,參考下源代碼:
func makechan(t *chantype, size int64) *hchan { //省略無(wú)關(guān)代碼 }
chan
也是一個(gè)引用類型,和map
相差無(wú)幾,make
返回的是一個(gè)*hchan
。
和map、chan都不一樣的slice
slice
和map
、chan
都不太一樣的,一樣的是,它也是引用類型,它也可以在函數(shù)中修改對(duì)應(yīng)的內(nèi)容。
package main import ( "fmt" "testing" ) func modify(ages []int) { fmt.Printf("函數(shù)里接收到slice的內(nèi)存地址是%p\n", ages) ages[0] = 1 } func TestDemo(t *testing.T) { ages := []int{6, 6, 6} fmt.Printf("原始slice的內(nèi)存地址是%p\n", ages) modify(ages) fmt.Println(ages) }
從結(jié)果來(lái)看,兩者的內(nèi)存地址一樣,也成功的變更到了變量 ages
的值。這難道不是引用傳遞嗎?
關(guān)注兩個(gè)細(xì)節(jié):
- 沒(méi)有用
&
來(lái)取地址。 - 可以直接用
%p
來(lái)打印。
之所以可以同時(shí)做到上面這兩件事,是因?yàn)闃?biāo)準(zhǔn)庫(kù) fmt
針對(duì)在這一塊做了優(yōu)化:
func (p *pp) fmtPointer(value reflect.Value, verb rune) { var u uintptr switch value.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: u = value.Pointer() default: p.badVerb(verb) return } //省略部分代碼 }
通過(guò)源代碼發(fā)現(xiàn),對(duì)于chan
、map
、slice
等被當(dāng)成指針處理,通過(guò)value.Pointer()
獲取對(duì)應(yīng)的值的指針。
// If v's Kind is Slice, the returned pointer is to the first // element of the slice. If the slice is nil the returned value // is 0. If the slice is empty but non-nil the return value is non-zero. func (v Value) Pointer() uintptr { // TODO: deprecate k := v.kind() switch k { //省略無(wú)關(guān)代碼 case Slice: return (*SliceHeader)(v.ptr).Data } }
很明顯了,當(dāng)是slice
類型的時(shí)候,返回是slice
這個(gè)結(jié)構(gòu)體里,字段Data
第一個(gè)元素的地址。
type SliceHeader struct { Data uintptr Len int Cap int } type slice struct { array unsafe.Pointer len int cap int }
在 Go 語(yǔ)言運(yùn)行時(shí),傳遞的也是相應(yīng) slice
類型的底層數(shù)組的指針,但需要注意,其使用的是指針的副本。嚴(yán)格意義是引用類型,依舊是值傳遞。slice
是一種結(jié)構(gòu)體+元素指針的混合類型,通過(guò)元素array(Data)
的指針,可以達(dá)到修改slice
里存儲(chǔ)元素的目的。
總結(jié)
最終可以確認(rèn)的是Go語(yǔ)言中所有的傳參都是值傳遞(傳值),都是一個(gè)副本,一個(gè)拷貝。
讓最多人犯迷糊的就是 slice
、map
、chan
等類型,都會(huì)認(rèn)為是 “引用傳遞”,從而認(rèn)為 Go 語(yǔ)言的 xxx 就是引用傳遞。正因?yàn)樗鼈冞€引用類型(指針、map、slice、chan等這些),這樣就可以修改原內(nèi)容數(shù)據(jù)。
再記住,Go里只有傳值(值傳遞)。
參考資料
群里又吵起來(lái)了,Go 是傳值還是傳引用?
Go語(yǔ)言參數(shù)傳遞是傳值還是傳引用
到此這篇關(guān)于Golang 值傳遞還是引用傳遞的文章就介紹到這了,更多相關(guān)Golang: 值傳遞還是引用傳遞內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang xorm及time.Time自定義解決json日期格式的問(wèn)題
這篇文章主要介紹了golang xorm及time.Time自定義解決json日期格式的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12go語(yǔ)言心跳超時(shí)的實(shí)現(xiàn)示例
本文主要介紹了go語(yǔ)言心跳超時(shí)的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05Go?WaitGroup及Cond底層實(shí)現(xiàn)原理
這篇文章主要為大家介紹了Go?WaitGroup及Cond底層實(shí)現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08IdeaGo啟動(dòng)報(bào)錯(cuò)Failed to create JVM的問(wèn)題解析
這篇文章主要介紹了IdeaGo啟動(dòng)報(bào)錯(cuò)Failed to create JVM的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11詳解Go?語(yǔ)言如何通過(guò)測(cè)試保證質(zhì)量
這篇文章主要為大家介紹了Go?語(yǔ)言如何通過(guò)測(cè)試保證質(zhì)量詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08goland中使用leetcode插件實(shí)現(xiàn)
本文主要介紹了goland中使用leetcode插件實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04Go語(yǔ)言題解LeetCode1051高度檢查器示例詳解
這篇文章主要為大家介紹了Go語(yǔ)言題解LeetCode1051高度檢查器示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12