1. 程式人生 > 其它 >【golang】粗淺理解slice

【golang】粗淺理解slice

技術標籤:Go

一、slice的結構

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

從結構可以看出,slice型別的結構體含有三個元素,第一個是指向底層陣列的指標,第二個是當前slice的長度,第三個是slice的容量。其中cap會以倍數方式擴容。

在《go程式設計語言》一書的第五章5.1最後一段話指出

實參是按值傳遞的,所以函式接收到的是每個實參的副本;修改函式的形參變數並不會影響到呼叫者提供的實參。然而,如提供的實參包含引用型別,比如指標、slice、map、函式或者通道,那麼當函式使用形參變數時,就有可能會間接修改實參變數。

那麼,slice作為形參時,是怎麼影響實參變數的呢?

下面看一段程式碼示例以及執行結果

二、程式碼示例

func TestC(t *testing.T)  {
	studentNos := []int{}
	fmt.Printf("%+v, %[1]p, %d, %d,%p \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 1)
	fmt.Printf("%+v, %[1]p, %d, %d,%p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 2)
	fmt.Printf("%+v, %[1]p, %d, %d,%p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 3)
	fmt.Printf("%+v, %[1]p, %d, %d,%p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 4)
	fmt.Printf("%+v, %[1]p, %d, %d,%p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 5)
	fmt.Printf("%+v, %[1]p, %d, %d,%p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	falsify(studentNos)
	fmt.Printf("%+v, %[1]p, %d, %d,%p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 6)
	fmt.Printf("%+v, %[1]p, %d, %d,%p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
}

func falsify(studentNos []int)  {
	fmt.Printf(".............%+v, %[1]p, %d, %d, %p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos[0]=100
	fmt.Printf(".............%+v, %[1]p, %d, %d, %p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos[4]=300
	fmt.Printf(".............%+v, %[1]p, %d, %d, %p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 400)
	fmt.Printf(".............%+v, %[1]p, %d, %d, %p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 400)
	fmt.Printf(".............%+v, %[1]p, %d, %d, %p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 400)
	fmt.Printf(".............%+v, %[1]p, %d, %d, %p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 400)
	fmt.Printf(".............%+v, %[1]p, %d, %d, %p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos = append(studentNos, 400)
	fmt.Printf(".............%+v, %[1]p, %d, %d, %p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
	studentNos[1]=200
	fmt.Printf(".............%+v, %[1]p, %d, %d, %p  \n", studentNos, cap(studentNos), len(studentNos), &studentNos)
}

執行結果

[], 0x679188, 0, 0,0xc00005e4a0
[1], 0xc000064158, 1, 1,0xc00005e4a0
[1 2], 0xc000064180, 2, 2,0xc00005e4a0
[1 2 3], 0xc0000621a0, 4, 3,0xc00005e4a0
[1 2 3 4], 0xc0000621a0, 4, 4,0xc00005e4a0
[1 2 3 4 5], 0xc00007c0c0, 8, 5,0xc00005e4a0
.............[1 2 3 4 5], 0xc00007c0c0, 8, 5, 0xc00005e580
.............[100 2 3 4 5], 0xc00007c0c0, 8, 5, 0xc00005e580
.............[100 2 3 4 300], 0xc00007c0c0, 8, 5, 0xc00005e580
.............[100 2 3 4 300 400], 0xc00007c0c0, 8, 6, 0xc00005e580
.............[100 2 3 4 300 400 400], 0xc00007c0c0, 8, 7, 0xc00005e580
.............[100 2 3 4 300 400 400 400], 0xc00007c0c0, 8, 8, 0xc00005e580
.............[100 2 3 4 300 400 400 400 400], 0xc000098200, 16, 9, 0xc00005e580
.............[100 2 3 4 300 400 400 400 400 400], 0xc000098200, 16, 10, 0xc00005e580
.............[100 200 3 4 300 400 400 400 400 400], 0xc000098200, 16, 10, 0xc00005e580
[100 2 3 4 300], 0xc00007c0c0, 8, 5,0xc00005e4a0
[100 2 3 4 300 6], 0xc00007c0c0, 8, 6,0xc00005e4a0

結果分析:

從上面的demo我們發現

1、當slice作為函式的引數時,slice會複製一份

2、形參的slice中的指標與呼叫方實參的slice的指標指向同一底層陣列

3、形參中len和cap與實參的len和cap完全剖離

4、當函式對實參的len範圍內的元素進行修改,會影響實參的值。但對len範圍外的元素修改,對實參無影響。

5、注意,當在函式中,slice發生擴容,其指標將指向新的地址,與實參中指標指向不同的底層陣列。此後再對形參的slice中的元素做任何操作,都不會影響實參。

總結:slice作為函式引數使用時需謹慎