slice傳遞給函數理解
- slice的底層實現是
type IntSlice struct {
ptr *int
len, cap int
}
這裏的ptr是真實的指向slice的第一個元素地址的指針。
len表示的是slice當前包含的元素的數目。
cap表示slice的容量。
slice實際上是基於底層數組實現的,從它的結構體定義中可以得知。
- 下面講解slice傳入一個函數時的情況,
看下面的一段代碼
func use_array(args []int) {
fmt.Println(cap(args))
args[0]=5 //修改slice之後,arg會變成[1,5,3,4,5,6]
args = append(args, 11) //slice增加一個元素,
//由於cap初始值等於原數組元素的個數-slice第一個元素的索引,
//所以cap等於5.len(args)從4變爲5,依然小於等於cap(args)
//不發生擴增,所以append的改變
//仍然應用在原數組上。
fmt.Println(args) //[5,3,4,5,11]
}
func main() {
var arg = [...]int{1, 2, 3, 4, 5, 6}
var args = arg[1:5] // [2,3,4,5]
use_array(args)
fmt.Println(args) // [5,3,4,5]
fmt.Println(arg) // [1,5,3,4,5,11]
}
把slice傳入函數後,由於slice保存了它的第一個元素在底層數組中的地址,這裏就是數組arg中的2的地址。所以args[0]修改的是原數組arg的arg[1]這個元素,變成了5,。然後根據我上面對與append的註釋,改變應用在原來的數組上,所以現在的arg變成了[1,5,3,4,5,11]. append 將args[4] 置爲11,應用到底層數組上後,args[4]的地址也就是arg[5]的地址,所以arg[5]變成了11。
- List item
爲什麼main函數中的args沒有變化?
因爲args傳入函數的時候,是按照值傳遞的,main函數中的args和use_array函數中的args並不是同一個args,use_array中的args是對main函數的args的一個拷貝,但它底層數組同樣也是arg,以及ptr指針也指向的是arg的第一個元素。按值傳遞,main函數的args並不會改變。
- 對原來的程序改變一個地方,append函數發生變化,如下
func use_array(args []int) {
fmt.Println(cap(args))
args[0]=5 //修改slice之後,arg會變成[1,5,3,4,5,6]
args = append(args, 11, 10) //slice增加2個元素,
//由於cap初始值等於原數組元素的個數-slice第一個元素的索引,
//所以cap等於5.len(args)從4變爲6,大於cap(args)
//底層數組轉移到一個新的數組上,原數組不應用改變後的元素
//新的底層數組cap等於10。原底層數組main函數的arg不發生改變。
fmt.Println(args) //[5 3 4 5 11 10]
}
func main() {
var arg = [...]int{1, 2, 3, 4, 5, 6}
var args = arg[1:5] // [1,2,3,4]
use_array(args)
fmt.Println(args) // [1,2,3,4]
fmt.Println(arg) // [1,5,3,4,5,6]
}
發生的變化,請看我對於append方法的註釋。另外的是main函數中的slice args依然沒有變化。因爲是值傳遞的原因。轉成指針傳遞的話,main中的args也就會變化了!
下面提供一個指針傳遞的版本:
func use_array(args *[]int) {
fmt.Println(cap(*args)) //5
(*args)[0] = 5
*args = append(*args, 11, 10)
fmt.Println(*args) //[5,3,4,5,11,10]
fmt.Println(cap(*args)) //10
}
func main() {
var arg = [...]int{1, 2, 3, 4, 5, 6}
var args = arg[1:5]
fmt.Println(args) //[2,3,4,5]
use_array(&args)
fmt.Println(args) //[5,3,4,5,11,10]
fmt.Println(arg) //[1,5,3,4,5,6]
}