一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Golang - Go語言參數傳遞是傳值還是傳引用

Go語言參數傳遞是傳值還是傳引用

2022-01-25 00:40飛雪無情的博客 Golang

Go 語言到底是傳值(值傳遞),還是傳引用(引用傳遞)?本文就詳細介紹一下,具有一定的參考價值,感興趣的小伙伴們可以參考一下

對于了解一門語言來說,會關心我們在函數調用的時候,參數到底是傳的值,還是引用?

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

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

什么是傳值(值傳遞)

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
func main() {
 i:=10
 ip:=&i
 fmt.Printf("原始指針的內存地址是:%p\n",&ip)
 modify(ip)
 fmt.Println("int值被修改了,新值為:",i)
}
 
 func modify(ip *int){
  fmt.Printf("函數里接收到的指針的內存地址是:%p\n",&ip)
  *ip=1
 }

我們運行,可以看到輸入結果如下:

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

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

所以通過輸出我們可以看到,這是一個指針的拷貝,因為存放這兩個指針的內存地址是不同的,雖然指針的值相同,但是是兩個不同的指針。

Go語言參數傳遞是傳值還是傳引用

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

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

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

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

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

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

迷惑Map

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
 persons:=make(map[string]int)
 persons["張三"]=19
 
 mp:=&persons
 
 fmt.Printf("原始map的內存地址是:%p\n",mp)
 modify(persons)
 fmt.Println("map值被修改了,新值為:",persons)
}
 
 func modify(p map[string]int){
  fmt.Printf("函數里接收到map的內存地址是:%p\n",&p)
  p["張三"]=20
 }

運行打印輸出:

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
 p:=Person{"張三"}
 fmt.Printf("原始Person的內存地址是:%p\n",&p)
 modify(p)
 fmt.Println(p)
}
 
type Person struct {
 Name string
}
 
 func modify(p Person) {
  fmt.Printf("函數里接收到Person的內存地址是:%p\n",&p)
  p.Name = "李四"
 }

運行打印輸出:

原始Person的內存地址是:0xc4200721b0
函數里接收到Person的內存地址是:0xc4200721c0
{張三}

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
 p:=Person{"張三"}
 modify(&p)
 fmt.Println(p)
}
 
type Person struct {
 Name string
}
 
 func modify(p *Person) {
  p.Name = "李四"
 }

在運行查看輸出,我們發現,這次被修改了。我們這里省略了內存地址的打印,因為我們上面int類型的例子已經證明了指針類型的參數也是值傳遞的。 指針類型可以修改,非指針類型不行,那么我們可以大膽的猜測,我們使用make函數創建的map是不是一個指針類型呢?看一下源代碼:

?
1
2
3
4
5
6
7
8
// 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 {
    //省略無關代碼
}

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

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

chan類型

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

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

和map、chan都不一樣的slice

slice和map、chan都不太一樣的,一樣的是,它也是引用類型,它也可以在函數中修改對應的內容。

?
1
2
3
4
5
6
7
8
9
10
11
func main() {
 ages:=[]int{6,6,6}
 fmt.Printf("原始slice的內存地址是%p\n",ages)
 modify(ages)
 fmt.Println(ages)
}
 
func modify(ages []int){
 fmt.Printf("函數里接收到slice的內存地址是%p\n",ages)
 ages[0]=1
}

運行打印結果,發現的確是被修改了,而且我們這里打印slice的內存地址是可以直接通過%p打印的,不用使用&取地址符轉換。

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

?
1
2
3
4
5
6
7
8
9
10
11
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
 }
 //省略部分代碼
}

通過源代碼發現,對于chan、map、slice等被當成指針處理,通過value.Pointer()獲取對應的值的指針。

?
1
2
3
4
5
6
7
8
9
10
11
12
// 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 {
 //省略無關代碼
 case Slice:
  return (*SliceHeader)(v.ptr).Data
 }
}

很明顯了,當是slice類型的時候,返回是slice這個結構體里,字段Data第一個元素的地址。

?
1
2
3
4
5
6
7
8
9
10
11
type SliceHeader struct {
 Data uintptr
 Len  int
 Cap  int
}
 
type slice struct {
 array unsafe.Pointer
 len   int
 cap   int
}

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

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

單純的從slice這個結構體看,我們可以通過modify修改存儲元素的內容,但是永遠修改不了len和cap,因為他們只是一個拷貝,如果要修改,那就要傳遞*slice作為參數才可以。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
}

運行打印輸出結果為:

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

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

?
1
2
3
4
5
modify(&p)
func modify(p *Person){
 p.name = "李四"
 *p.age = 20
}

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

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

小結

最終我們可以確認的是Go語言中所有的傳參都是值傳遞(傳值),都是一個副本,一個拷貝。因為拷貝的內容有時候是非引用類型(int、string、struct等這些),這樣就在函數中就無法修改原內容數據;有的是引用類型(指針、map、slice、chan等這些),這樣就可以修改原內容數據。

是否可以修改原內容數據,和傳值、傳引用沒有必然的關系。在C++中,傳引用肯定是可以修改原內容數據的,在Go語言里,雖然只有傳值,但是我們也可以修改原內容數據,因為參數是引用類型。

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

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

到此這篇關于Go語言參數傳遞是傳值還是傳引用的文章就介紹到這了,更多相關Go語言參數傳遞內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.flysnow.org/2018/02/24/golang-function-parameters-passed-by-value.html

延伸 · 閱讀

精彩推薦
  • Golanggo日志系統logrus顯示文件和行號的操作

    go日志系統logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang json.Marshal 特殊html字符被轉義的解決方法

    golang json.Marshal 特殊html字符被轉義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
  • Golanggolang的httpserver優雅重啟方法詳解

    golang的httpserver優雅重啟方法詳解

    這篇文章主要給大家介紹了關于golang的httpserver優雅重啟的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    helight2992020-05-14
  • GolangGolang中Bit數組的實現方式

    Golang中Bit數組的實現方式

    這篇文章主要介紹了Golang中Bit數組的實現方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨尊11682021-06-09
  • Golanggolang如何使用struct的tag屬性的詳細介紹

    golang如何使用struct的tag屬性的詳細介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網11352020-05-21
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • GolangGolang通脈之數據類型詳情

    Golang通脈之數據類型詳情

    這篇文章主要介紹了Golang通脈之數據類型,在編程語言中標識符就是定義的具有某種意義的詞,比如變量名、常量名、函數名等等,Go語言中標識符允許由...

    4272021-11-24
主站蜘蛛池模板: 91尤物在线 | 欧美人伦禁忌.5 | 久久电影午夜 | 国产成人h视频在线播放网站 | 国产色司机在线视频免费观看 | 香蕉 在线播放 | 国产91精品久久久久久 | chinese国产人妖hd | 国产1广场舞丰满老女偷 | bnb998八度免费影院丫意浓 | 久久免费看少妇高潮A片特爽 | 午夜AV亚洲一码二中文字幕青青 | 2012年免费中文视频 | 日韩免费一级毛片 | 亚洲天堂男人网 | 日本高清在线播放一区二区三区 | 亚洲AV综合99一二三四区 | 久久免费国产视频 | 男人的天堂在线观看入口 | 国产成人久久久精品一区二区三区 | 国产福利一区二区三区四区 | 99视频精品国在线视频艾草 | 日本连裤袜xxxxx在线视频 | 欧美久久天天综合香蕉伊 | 扒开放荡老师裙子猛烈的进入 | kk4444了欧美 | 亚洲精品第五页中文字幕 | 91啦丨porny丨蝌蚪 | 欧美综合国产精品日韩一 | 高清女主播一区二区三区 | 人妖巨茎video | 日本一级不卡一二三区免费 | 日韩操比视频 | 欧美疯狂做爰xx | 成人福利在线观看 | 国产精品1页 | 91网红福利精品区一区二 | 99在线视频精品 | 波多野结衣伦理在线观看 | 色女的乖男人 | 果冻传媒mv在线观看入口免费 |