最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go語言參數(shù)傳遞是傳值還是傳引用

 更新時(shí)間:2021年12月20日 09:53:30   作者:飛雪無情的博客  
Go?語言到底是傳值(值傳遞),還是傳引用(引用傳遞)?本文就詳細(xì)介紹一下,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

對于了解一門語言來說,會關(guān)心我們在函數(shù)調(diào)用的時(shí)候,參數(shù)到底是傳的值,還是引用?

其實(shí)對于傳值和傳引用,是一個(gè)比較古老的話題,做研發(fā)的都有這個(gè)概念,但是可能不是非常清楚。對于我們做Go語言開發(fā)的來說,也想知道到底是什么傳遞。

那么我們先來看看什么是值傳遞,什么是引用傳遞。

什么是傳值(值傳遞)

傳值的意思是:函數(shù)傳遞的總是原來這個(gè)東西的一個(gè)副本,一副拷貝。比如我們傳遞一個(gè)int類型的參數(shù),傳遞的其實(shí)是這個(gè)參數(shù)的一個(gè)副本;傳遞一個(gè)指針類型的參數(shù),其實(shí)傳遞的是這個(gè)該指針的一份拷貝,而不是這個(gè)指針指向的值。

對于int這類基礎(chǔ)類型我們可以很好的理解,它們就是一個(gè)拷貝,但是指針呢?我們覺得可以通過它修改原來的值,怎么會是一個(gè)拷貝呢?下面我們看個(gè)例子。

func main() {
 i:=10
 ip:=&i
 fmt.Printf("原始指針的內(nèi)存地址是:%p\n",&ip)
 modify(ip)
 fmt.Println("int值被修改了,新值為:",i)
}

 func modify(ip *int){
  fmt.Printf("函數(shù)里接收到的指針的內(nèi)存地址是:%p\n",&ip)
  *ip=1
 }

我們運(yùn)行,可以看到輸入結(jié)果如下:

原始指針的內(nèi)存地址是:0xc42000c028
函數(shù)里接收到的指針的內(nèi)存地址是:0xc42000c038
int值被修改了,新值為: 1

首先我們要知道,任何存放在內(nèi)存里的東西都有自己的地址,指針也不例外,它雖然指向別的數(shù)據(jù),但是也有存放該指針的內(nèi)存。

所以通過輸出我們可以看到,這是一個(gè)指針的拷貝,因?yàn)榇娣胚@兩個(gè)指針的內(nèi)存地址是不同的,雖然指針的值相同,但是是兩個(gè)不同的指針。

通過上面的圖,可以更好的理解。 首先我們看到,我們聲明了一個(gè)變量i,值為10,它的內(nèi)存存放地址是0xc420018070,通過這個(gè)內(nèi)存地址,我們可以找到變量i,這個(gè)內(nèi)存地址也就是變量i的指針ip。

指針ip也是一個(gè)指針類型的變量,它也需要內(nèi)存存放它,它的內(nèi)存地址是多少呢?是0xc42000c028。 在我們傳遞指針變量ip給modify函數(shù)的時(shí)候,是該指針變量的拷貝,所以新拷貝的指針變量ip,它的內(nèi)存地址已經(jīng)變了,是新的0xc42000c038。

不管是0xc42000c028還是0xc42000c038,我們都可以稱之為指針的指針,他們指向同一個(gè)指針0xc420018070,這個(gè)0xc420018070又指向變量i,這也就是為什么我們可以修改變量i的值。

什么是傳引用(引用傳遞)

Go語言(Golang)是沒有引用傳遞的,這里我不能使用Go舉例子,但是可以通過說明描述。

以上面的例子為例,如果在modify函數(shù)里打印出來的內(nèi)存地址是不變的,也是0xc42000c028,那么就是引用傳遞。

迷惑Map

了解清楚了傳值和傳引用,但是對于Map類型來說,可能覺得還是迷惑,一來我們可以通過方法修改它的內(nèi)容,二來它沒有明顯的指針。

func main() {
 persons:=make(map[string]int)
 persons["張三"]=19

 mp:=&persons

 fmt.Printf("原始map的內(nèi)存地址是:%p\n",mp)
 modify(persons)
 fmt.Println("map值被修改了,新值為:",persons)
}

 func modify(p map[string]int){
  fmt.Printf("函數(shù)里接收到map的內(nèi)存地址是:%p\n",&p)
  p["張三"]=20
 }

運(yùn)行打印輸出:

原始map的內(nèi)存地址是:0xc42000c028
函數(shù)里接收到map的內(nèi)存地址是:0xc42000c038
map值被修改了,新值為: map[張三:20]

兩個(gè)內(nèi)存地址是不一樣的,所以這又是一個(gè)值傳遞(值的拷貝),那么為什么我們可以修改Map的內(nèi)容呢?先不急,我們先看一個(gè)自己實(shí)現(xiàn)的struct。

func main() {
 p:=Person{"張三"}
 fmt.Printf("原始Person的內(nèi)存地址是:%p\n",&p)
 modify(p)
 fmt.Println(p)
}

type Person struct {
 Name string
}

 func modify(p Person) {
  fmt.Printf("函數(shù)里接收到Person的內(nèi)存地址是:%p\n",&p)
  p.Name = "李四"
 }

運(yùn)行打印輸出:

原始Person的內(nèi)存地址是:0xc4200721b0
函數(shù)里接收到Person的內(nèi)存地址是:0xc4200721c0
{張三}

我們發(fā)現(xiàn),我們自己定義的Person類型,在函數(shù)傳參的時(shí)候也是值傳遞,但是它的值(Name字段)并沒有被修改,我們想改成李四,發(fā)現(xiàn)最后的結(jié)果還是張三。

這也就是說,map類型和我們自己定義的struct類型是不一樣的。我們嘗試把modify函數(shù)的接收參數(shù)改為Person的指針。

func main() {
 p:=Person{"張三"}
 modify(&p)
 fmt.Println(p)
}

type Person struct {
 Name string
}

 func modify(p *Person) {
  p.Name = "李四"
 }

在運(yùn)行查看輸出,我們發(fā)現(xiàn),這次被修改了。我們這里省略了內(nèi)存地址的打印,因?yàn)槲覀兩厦鎖nt類型的例子已經(jīng)證明了指針類型的參數(shù)也是值傳遞的。 指針類型可以修改,非指針類型不行,那么我們可以大膽的猜測,我們使用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 {
    //省略無關(guān)代碼
}

通過查看src/runtime/hashmap.go源代碼發(fā)現(xiàn),的確和我們猜測的一樣,make函數(shù)返回的是一個(gè)hmap類型的指針*hmap。也就是說map===*hmap。 現(xiàn)在看func modify(p map)這樣的函數(shù),其實(shí)就等于func modify(p *hmap),和我們前面第一節(jié)什么是值傳遞里舉的func modify(ip *int)的例子一樣,可以參考分析。

所以在這里,Go語言通過make函數(shù),字面量的包裝,為我們省去了指針的操作,讓我們可以更容易的使用map。這里的map可以理解為引用類型,但是記住引用類型不是傳引用。

chan類型

chan類型本質(zhì)上和map類型是一樣的,這里不做過多的介紹,參考下源代碼:
 
func makechan(t *chantype, size int64) *hchan {
    //省略無關(guān)代碼
}

chan也是一個(gè)引用類型,和map相差無幾,make返回的是一個(gè)*hchan。

和map、chan都不一樣的slice

slice和map、chan都不太一樣的,一樣的是,它也是引用類型,它也可以在函數(shù)中修改對應(yīng)的內(nèi)容。

func main() {
 ages:=[]int{6,6,6}
 fmt.Printf("原始slice的內(nèi)存地址是%p\n",ages)
 modify(ages)
 fmt.Println(ages)
}

func modify(ages []int){
 fmt.Printf("函數(shù)里接收到slice的內(nèi)存地址是%p\n",ages)
 ages[0]=1
}

運(yùn)行打印結(jié)果,發(fā)現(xiàn)的確是被修改了,而且我們這里打印slice的內(nèi)存地址是可以直接通過%p打印的,不用使用&取地址符轉(zhuǎn)換。

這就可以證明make的slice也是一個(gè)指針了嗎?不一定,也可能fmt.Printf把slice特殊處理了。

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
 }
 //省略部分代碼
}

通過源代碼發(fā)現(xiàn),對于chan、map、slice等被當(dāng)成指針處理,通過value.Pointer()獲取對應(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 {
 //省略無關(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
}

所以我們通過%p打印的slice變量ages的地址其實(shí)就是內(nèi)部存儲數(shù)組元素的地址,slice是一種結(jié)構(gòu)體+元素指針的混合類型,通過元素array(Data)的指針,可以達(dá)到修改slice里存儲元素的目的。

所以修改類型的內(nèi)容的辦法有很多種,類型本身作為指針可以,類型里有指針類型的字段也可以。

單純的從slice這個(gè)結(jié)構(gòu)體看,我們可以通過modify修改存儲元素的內(nèi)容,但是永遠(yuǎn)修改不了len和cap,因?yàn)樗麄冎皇且粋€(gè)拷貝,如果要修改,那就要傳遞*slice作為參數(shù)才可以。

func main() {
 i:=19
 p:=Person{name:"張三",age:&i}
 fmt.Println(p)
 modify(p)
 fmt.Println(p)
}

type Person struct {
 name string
 age  *int
}

func (p Person) String() string{
 return "姓名為:" + p.name + ",年齡為:"+ strconv.Itoa(*p.age)
}

func modify(p Person){
 p.name = "李四"
 *p.age = 20
}

運(yùn)行打印輸出結(jié)果為:

姓名為:張三,年齡為:19
姓名為:張三,年齡為:20

通過這個(gè)Person和slice對比,就更好理解了,Person的name字段就類似于slice的len和cap字段,age字段類似于array字段。在傳參為非指針類型的情況下,只能修改age字段,name字段無法修改。要修改name字段,就要把傳參改為指針,比如:

modify(&p)
func modify(p *Person){
 p.name = "李四"
 *p.age = 20
}

這樣name和age字段雙雙都被修改了。

所以slice類型也是引用類型。

小結(jié)

最終我們可以確認(rèn)的是Go語言中所有的傳參都是值傳遞(傳值),都是一個(gè)副本,一個(gè)拷貝。因?yàn)榭截惖膬?nèi)容有時(shí)候是非引用類型(int、string、struct等這些),這樣就在函數(shù)中就無法修改原內(nèi)容數(shù)據(jù);有的是引用類型(指針、map、slice、chan等這些),這樣就可以修改原內(nèi)容數(shù)據(jù)。

是否可以修改原內(nèi)容數(shù)據(jù),和傳值、傳引用沒有必然的關(guān)系。在C++中,傳引用肯定是可以修改原內(nèi)容數(shù)據(jù)的,在Go語言里,雖然只有傳值,但是我們也可以修改原內(nèi)容數(shù)據(jù),因?yàn)閰?shù)是引用類型。

這里也要記住,引用類型和傳引用是兩個(gè)概念。

再記住,Go里只有傳值(值傳遞)。

到此這篇關(guān)于Go語言參數(shù)傳遞是傳值還是傳引用的文章就介紹到這了,更多相關(guān)Go語言參數(shù)傳遞內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go中init()執(zhí)行順序詳解

    Go中init()執(zhí)行順序詳解

    go允許定義多個(gè)init(),多個(gè)init()會涉及到執(zhí)行先后的問題,本文將詳細(xì)講解Go中init()執(zhí)行順序,感興趣的朋友一起看看吧
    2022-09-09
  • Go語言讀取YAML 配置文件的兩種方式分享

    Go語言讀取YAML 配置文件的兩種方式分享

    在日常開發(fā)中,YAML 格式的文件基本上被默認(rèn)為是配置文件,其內(nèi)容因?yàn)榭s進(jìn)帶來的層級感看起來非常直觀和整潔。本文分享了讀取YAML 配置文件的兩種方式,需要的可以參考一下
    2022-12-12
  • 使用Go語言連接和操作數(shù)據(jù)庫的基本步驟

    使用Go語言連接和操作數(shù)據(jù)庫的基本步驟

    在Go語言中,連接和操作數(shù)據(jù)庫通常使用database/sql包,它提供了一個(gè)數(shù)據(jù)庫抽象層,支持多種數(shù)據(jù)庫引擎,如MySQL、PostgreSQL、SQLite等,下面我將以MySQL為例,詳細(xì)講解如何使用Go語言連接和操作數(shù)據(jù)庫,需要的朋友可以參考下
    2024-06-06
  • Go channel如何批量讀取數(shù)據(jù)

    Go channel如何批量讀取數(shù)據(jù)

    本文將展示一個(gè)從 Go channel 中批量讀取數(shù)據(jù),并批量發(fā)送到 Kafka 和批量寫入網(wǎng)絡(luò)數(shù)據(jù)的示例,文中的示例代碼講解詳細(xì),有需要的可以參考下
    2024-10-10
  • golang string、int、int64 float 互相轉(zhuǎn)換方式

    golang string、int、int64 float 互相轉(zhuǎn)換方式

    這篇文章主要介紹了golang string、int、int64 float 互相轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Golang 在gin框架中如何使用JWT鑒權(quán)

    Golang 在gin框架中如何使用JWT鑒權(quán)

    JWT,全稱 JSON Web Token,是一種開放標(biāo)準(zhǔn)(RFC 7519),用于安全地在雙方之間傳遞信息,這篇文章主要介紹了golang 在Gin框架中使用JWT鑒權(quán),需要的朋友可以參考下
    2024-07-07
  • 詳解為什么說Golang中的字符串類型不能修改

    詳解為什么說Golang中的字符串類型不能修改

    在接觸Go這么語言,可能你經(jīng)常會聽到這樣一句話。對于字符串不能修改,可能你很納悶,日常開發(fā)中我們對字符串進(jìn)行修改也是很正常的,為什么又說Go中的字符串不能進(jìn)行修改呢?本文就來通過實(shí)際案例給大家演示一下
    2023-03-03
  • go高并發(fā)時(shí)append方法偶現(xiàn)錯(cuò)誤解決分析

    go高并發(fā)時(shí)append方法偶現(xiàn)錯(cuò)誤解決分析

    這篇文章主要為大家介紹了go高并發(fā)時(shí)append方法偶現(xiàn)錯(cuò)誤解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • Beego AutoRouter工作原理解析

    Beego AutoRouter工作原理解析

    這篇文章主要為大家介紹了Beego AutoRouter工作原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 部署Go語言項(xiàng)目的 N 種方法(小結(jié))

    部署Go語言項(xiàng)目的 N 種方法(小結(jié))

    這篇文章主要介紹了部署Go語言項(xiàng)目的 N 種方法(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11

最新評論

老河口市| 大英县| 溧水县| 通州市| 托里县| 西城区| 武平县| 蚌埠市| 东兰县| 孝义市| 广东省| 河北省| 伊春市| 镇康县| 嘉定区| 普格县| 都兰县| 玛曲县| 屯门区| 滦平县| 临江市| 公主岭市| 怀集县| 临西县| 互助| 石楼县| 会泽县| 平原县| 陆丰市| 怀集县| 吉木乃县| 睢宁县| 恩施市| 老河口市| 和顺县| 深水埗区| 兖州市| 禹州市| 沛县| 永福县| 怀宁县|