杭州網(wǎng)站推廣怎樣做長沙網(wǎng)站搭建關(guān)鍵詞排名
做了五年的go開發(fā),卻并沒有什么成長,都停留在了業(yè)務(wù)層面了。一直以為golang中函數(shù)傳參,如果傳的是引用類型,則是以引用傳遞,造成這樣的誤解,實在也不能怪我。我們來看一個例子,眾所周知,slice是個引用類型,我們以slice為例。
package main
?
import "fmt"
?
func main() {strSlice := make([]string, 0,10)strSlice = append(strSlice, "初始值")//打印一下沒有在函數(shù)內(nèi)部修改的初始情況fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])//在函數(shù)內(nèi)部修改初始slice內(nèi)容,再打印change(strSlice)fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])
}
func change(str []string){fmt.Println("函數(shù)傳參地址:",&str[0])str[0]="改掉這個內(nèi)容"
}
猜猜,打印出來的會是什么結(jié)果?
?
?不管你是怎么看,如果只有這么一個案例,就很容易產(chǎn)生誤解。打印的函數(shù)參數(shù)的地址和外部slice的地址是一致,并且在函數(shù)體內(nèi)修改的值的確影響了slice的值,由此現(xiàn)象很容易得出是引用傳遞。
事實果真如此么?我們再來看一個案例。
package main
?
import "fmt"
?
func main() {strSlice := make([]string, 0,10)strSlice = append(strSlice, "初始值")//打印一下沒有在函數(shù)內(nèi)部修改的初始情況fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])//在函數(shù)內(nèi)部修改初始slice內(nèi)容,再打印change(strSlice)fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])
}
func change(str []string){fmt.Println("函數(shù)傳參地址:",&str[0])str=append(str,"新增一個內(nèi)容")
}
?如果是引用傳遞,那么經(jīng)過change函數(shù)追加了值的strSlice應(yīng)該是能打印出追加的值。好了,我們直接看結(jié)果。
結(jié)果,很是出乎我的意料之外啊,竟然不是像我上面猜想的那樣。由此至少可以得出一個結(jié)論,golang中函數(shù)的參數(shù)傳遞不是引用傳遞。那么,也就是說即使參數(shù)是引用類型,也是值傳遞,既然是值傳遞,第一個案例作何理解呢?第二個案例又如何理解呢?
我們看一下slice的底層結(jié)構(gòu)。
//go 1.20.3 path: /src/runtime/slice.go
type slice struct {array unsafe.Pointerlen intcap int
}
?array
?是一個指向底層數(shù)組的指針,這個數(shù)組存儲著切片中的元素。len
?表示切片的長度,即切片中元素的數(shù)量。cap
?表示切片的容量,即切片底層數(shù)組中可用的元素數(shù)量。golang的函數(shù)傳參都是值傳遞,即使傳遞的是引用類型,也是對應(yīng)引用類型的地址拷貝。因此,第一個案例中,實際上是把指向底層數(shù)組的指針的地址拷貝生成一個副本傳到了函數(shù)體中,所以,第一個案例中修改了0xc00006c0a0地址里的內(nèi)容會引發(fā)外面的參數(shù)發(fā)生變化。這個我們可以做個案例測試一下。
package main
?
import "fmt"
?
func main() {strSlice := make([]string, 0,10)strSlice = append(strSlice, "初始值")//打印一下沒有在函數(shù)內(nèi)部修改的初始情況fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])//在函數(shù)內(nèi)部修改初始slice內(nèi)容,再打印change(strSlice)fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0])
}
func change(str []string){fmt.Println("函數(shù)傳參地址:",&str[0])for i:=0;i<10;i++{str = append(str, fmt.Sprint(i))}fmt.Println("擴容之后的地址:",&str[0])str[0]="改掉這個內(nèi)容"
}
我們知道,當(dāng)slice發(fā)生擴容,runtime會開辟一塊新的內(nèi)存地址把內(nèi)容拷貝到新的地址指向的內(nèi)存中,那么,我們可以測試一下,當(dāng)slice發(fā)生擴容,再修改內(nèi)容,就不會影響原來的參數(shù)。
?
?實際結(jié)果,驗證了我們的猜想,擴容之后,開辟新的內(nèi)存地址來存放內(nèi)容,因此,再修改這個參數(shù)也不會影響外部參數(shù)。
可是這個依然沒有解除掉第二個案例——沒有擴容時,函數(shù)內(nèi)append之后外部參數(shù)打印結(jié)果和預(yù)期不符的疑惑。實際上并不矛盾,因為,slice結(jié)構(gòu)中有一個變量len,這個表示slice中元素的數(shù)量,用大白話來理解就是可見的元素,傳參的過程中,不僅拷貝了地址,還拷貝了len和cap,因此,雖然形參中的len發(fā)生了變化,但是并不影響實參的len。畫個內(nèi)存示意圖來理解一下。
package main
?
import "fmt"
?
func main() {strSlice := make([]string, 0,10)strSlice = append(strSlice, "初始值")//打印一下沒有在函數(shù)內(nèi)部修改的初始情況fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0],"len=",len(strSlice))//在函數(shù)內(nèi)部修改初始slice內(nèi)容,再打印change(strSlice)fmt.Println("strSlice:",strSlice,"strSlice地址:",&strSlice[0],"len=",len(strSlice))
}
func change(str []string){fmt.Println("函數(shù)傳參地址:",&str[0])str=append(str,"新增一個內(nèi)容")fmt.Println("形參str長度:",len(str))
}
?
直接看結(jié)果,果然驗證了我們上面的猜想。
至此,函數(shù)值傳遞的探究到此結(jié)束。
?