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