首先我們要有一個理解:go的函數參數傳遞都是值傳遞,為什麼說是傳值呢?因為go的函數傳遞都是複製了一份傳遞到參數中。下面我們看一個例子:
package main
import "fmt"
func main() { a := 1 b := "xx" c := []int{1} d := map[string]string{"xx": "xxx"} e := Tmp{ Name: "xxx", } f := func() string { return "xxx" } g := new(Tmp) fmt.Printf("原來的:a:%p b:%p c:%p d:%p e:%p f:%p g:%p\n", &a, &b, &c, &d, &e, &f, &g) testParamFunc(a, b, c, d, e, f, g)}
func testParamFunc(a int, b string, c []int, d map[string]string, e Tmp, f func() string, g *Tmp) { fmt.Printf("函數內的:a:%p b:%p c:%p d:%p e:%p f:%p g:%p\n", &a, &b, &c, &d, &e, &f, &g)}
type Tmp struct { Name string}執行結果如下:
原來的:a:0xc00000a0b0 b:0xc00003c1f0 c:0xc000004480 d:0xc000006028 e:0xc00003c200 f:0xc000006030 g:0xc000006038函數內的:a:0xc00000a0e0 b:0xc00003c220 c:0xc0000044c0 d:0xc000006048 e:0xc00003c230 f:0xc000006050 g:0xc000006058上面的例子我們看到了不管是什麼類型傳過來,函數都給複製了一份傳遞到函數中,函數的參數地址都變化了,參數地址指向的內容是原來的值。
所謂值傳遞:指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。但是其實go裡面有些類型會影響到實際參數,下面我們對不同的類型來不同的講解。
關於字符串和整形的參數傳遞例子:
package main
import "fmt"
func main() { a := 1 bTmp := 2 b := &bTmp c := "xx" dTmp := "kk" d := &dTmp e := 3 fmt.Printf("a:%d b:%d c:%s d:%s e:%d\n", a, *b, c, *d, e) testFunc(a, b, c, d, &e) fmt.Printf("a:%d b:%d c:%s d:%s e:%d\n", a, *b, c, *d, e)}
func testFunc(a int, b *int, c string, d *string, eParam *int) { a = 11 tmpInt := 22 b = &tmpInt c = "qqq" tmpStr := "ooo" d = &tmpStr *eParam = 33}執行結果如下:
a:1 b:2 c:xx d:kk e:3a:1 b:2 c:xx d:kk e:33字符串和整形傳遞函數裡面是無法修改實參的,但是為什麼e我們看那個值變化了呢?*eParam = 33這行代碼其實不是對e直接賦值,是對eParam的地址進行重新賦值了,本來eParam傳過來就是上面變量e的地址,然後在函數裡面取*eParam就是變量e。
關於struct參數傳遞
package main
import "fmt"
func main() { a := Tmp{ Name: "xxx", } b := &Tmp{ Name: "xxx", } fmt.Printf("a:%v b:%v\n", a, *b) testFunc2(a, b) fmt.Printf("a:%v b:%v\n", a, *b)}
func testFunc2(a Tmp, b *Tmp) { a.Name = "ddd" b.Name = "ggg"
}
type Tmp struct { Name string}執行結果如下:
a:{xxx} b:{xxx}a:{xxx} b:{ggg}struct的參數傳遞,如果是本身傳遞,參數內無法修改實參,但是如果是傳struct地址,通過地址也能取參數的屬性,這樣是可以修改實參,所以我們看到b被修改了。
關於slice,map,chan三種數據結構,我們也先看下例子
package main
import "fmt"
func main() { a := []int{1, 2, 3, 6} b := []int{1, 2, 3, 6} c := map[string]string{"xxx": "vvv"} d := make(chan int, 1) fmt.Printf("a:%v b:%v c:%v d:%d\n", a, b, c, len(d)) testFunc3(a, b, c, d) fmt.Printf("a:%v b:%v c:%v d:%d\n", a, b, c, len(d))}func testFunc3(a, b []int, c map[string]string, d chan int) { a = append(a, 100) b[3] = 100 c["iiii"] = "sss" d <- 1}結果如下:
a:[1 2 3 6] b:[1 2 3 6] c:map[xxx:vvv] d:0a:[1 2 3 6] b:[1 2 3 100] c:map[iiii:sss xxx:vvv] d:1結果是不是沒想到呢?a通過append是沒法直接修改實參,但是b呢通過直接操作某一個屬性是可以修改的。因為a是slice,用append的話,slice會擴容,會copy一個slice,原來的地址(即臨時參數)會指向新的slice,但是這個臨時參數又和原來的傳進來的變量地址不一樣,所以沒法修改實參。但是b呢,能修改是因為makeslice返回的是一個指針,我們通過修改屬性其實修改的是b變量的指向底層數組指針屬性,所以能修改。但是c呢,為什麼直接傳過來就直接修改了呢?這個得看看源碼了,翻了一下makemap看看,makemap返回值是返回一個指針,其實就是map的地址,我們在函數內操作直接就修改了實參。關於d呢,原因和c是一樣的,makechan會返回一個指針,函數內操作其實就是直接對傳過來的變量的屬性進行更改了,所以函數內操作直接修改了實參。
下面我們過一下這個創建的函數的源碼,以上的實例分析希望對大家有幫助,有問題隨時交流。
創建slice
func makeslice(et *_type, len, cap int) unsafe.Pointer { . return mallocgc(mem, et, true)}創建map
func makemap(t *maptype, hint int, h *hmap) *hmap { . return h}創建chan
func makechan(t *chantype, size int) *hchan { . return c}