golang中的空slice案例

golang中允許對值為 nil 的 slice 添加元素

package main 
func main() {
 var s []int
 s = append(s, 1)
}

運行成功~

補充:golang slice 詳解

一、數組切片的使用

func main() {
	//1.基於數組創建數組切片
	var array [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	var slice = array[1:7] //array[startIndex:endIndex] 不包含endIndex
	//2.直接創建數組切片
	slice2 := make([]int, 5, 10)
	//3.直接創建並初始化數組切片
	slice3 := []int{1, 2, 3, 4, 5, 6}
	//4.基於數組切片創建數組切片
	slice5 := slice3[:4]
	//5.遍歷數組切片
	for i, v := range slice3 {
		fmt.Println(i, v)
	}
	//6.len()和cap()
	var len = len(slice2) //數組切片的長度
	var cap = cap(slice)  //數組切片的容量
	fmt.Println("len(slice2) =", len)
	fmt.Println("cap(slice) =", cap)
	//7.append() 會生成新的數組切片
	slice4 := append(slice2, 6, 7, 8)
	slice4 = append(slice4, slice3...)
	fmt.Println(slice4)
	//8.copy() 如果進行操作的兩個數組切片元素個數不一致,將會按照個數較小的數組切片進行復制
	copy(slice2, slice3) //將slice3的前五個元素復制給slice2
	fmt.Println(slice2, slice3)
}

二、數組切片數據結構分析

數組切片slice的數據結構如下,一個指向真實array地址的指針ptr,slice的長度len和容量cap

// slice 數據結構
type slice struct {
	array unsafe.Pointer 
	len   int            
	cap   int            
}

當傳參時,函數接收到的參數是數組切片的一個復制,雖然兩個是不同的變量,但是它們都有一個指向同一個地址空間的array指針,當修改一個數組切片時,另外一個也會改變,所以數組切片看起來是引用傳遞,其實是值傳遞。

三、append()方法解析

3.1 數組切片不擴容的情況

運行以下代碼思考一個問題:s1和s2是指向同一個底層數組嗎?

func main() {
	array := [20]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := array[:5]
	s2 := append(s1, 10)
	fmt.Println("s1 =", s1)
	fmt.Println("s2 =", s2)
	s2[0] = 0
	fmt.Println("s1 =", s1)
	fmt.Println("s2 =", s2)
}

輸出結果:

s1 = [1 2 3 4 5]

s2 = [1 2 3 4 5 10]

s1 = [0 2 3 4 5]

s2 = [0 2 3 4 5 10]

由第一行和第二行結果看來,似乎這是指向兩個不同的數組;但是當修改瞭s2,發現s1也跟著改變瞭,這又表明二者是指向同一個數組。到底真相是怎樣的呢?

運行以下代碼:

import (
	"fmt"
	"unsafe"
)
type Slice struct {
	ptr unsafe.Pointer // Array pointer
	len int            // slice length
	cap int            // slice capacity
}
func main() {
	array := [20]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := array[:5]
	s2 := append(s1, 10)
	s2[0] = 0
	// 把slice轉換成自定義的 Slice struct
	slice1 := (*Slice)(unsafe.Pointer(&s1))
	fmt.Printf("ptr:%v len:%v cap:%v \n", slice1.ptr, slice1.len, slice1.cap)
	slice2 := (*Slice)(unsafe.Pointer(&s2))
	fmt.Printf("ptr:%v len:%v cap:%v \n", slice2.ptr, slice2.len, slice2.cap)
}

輸出結果:

ptr:0xc04205e0a0 len:5 cap:20

ptr:0xc04205e0a0 len:6 cap:20

由結果可知:ptr指針存儲的是數組中的首地址的值,並且這兩個值相同,所以s1和s2確實是指向同一個底層數組。

但是,這兩個數組切片的元素不同,這個可以根據首地址和數組切片長度len來確定不同的數組切片應該包含哪些元素,因為s1和s2雖然指向同一個底層數組,但是二者的len不同。通過這個demo,也驗證瞭數組切片傳參方式也是值傳遞。

3.2 數組切片擴容的情況:

運行以下代碼,思考與不擴容情況的不同之處,以及為什麼

func main() {
	s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	s2 := append(s1, 10)
	fmt.Println("s1 =", s1)
	fmt.Println("s2 =", s2)
	s2[0] = 0
	fmt.Println("s1 =", s1)
	fmt.Println("s2 =", s2)
}

輸出結果:

s1 = [1 2 3 4 5 6 7 8 9]

s2 = [1 2 3 4 5 6 7 8 9 10]

s1 = [1 2 3 4 5 6 7 8 9]

s2 = [0 2 3 4 5 6 7 8 9 10]

根據結果我們發現,修改s2後,s1並未改變,這說明當append()後,s1和s2並未指向同一個底層數組,這又是為什麼呢?

同樣,我們接著運行以下代碼:

import (
	"fmt"
	"unsafe"
)
type Slice struct {
	ptr unsafe.Pointer // Array pointer
	len int            // slice length
	cap int            // slice capacity
}
func main() {
	s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	s2 := append(s1, 10)
	fmt.Println("s1 =", s1)
	fmt.Println("s2 =", s2)
	s2[0] = 0
	fmt.Println("s1 =", s1)
	fmt.Println("s2 =", s2)
	// 把slice轉換成自定義的 Slice struct
	slice1 := (*Slice)(unsafe.Pointer(&s1))
	fmt.Printf("ptr:%v len:%v cap:%v \n", slice1.ptr, slice1.len, slice1.cap)
	slice2 := (*Slice)(unsafe.Pointer(&s2))
	fmt.Printf("ptr:%v len:%v cap:%v \n", slice2.ptr, slice2.len, slice2.cap)
}

輸出結果:

s1 = [1 2 3 4 5 6 7 8 9]

s2 = [1 2 3 4 5 6 7 8 9 10]

s1 = [1 2 3 4 5 6 7 8 9]

s2 = [0 2 3 4 5 6 7 8 9 10]

ptr:0xc04207a000 len:9 cap:9

ptr:0xc04207c000 len:10 cap:18

由結果可知:append()後,s1和s2確實指向瞭不同的底層數組,並且二者的數組容量cap也不相同瞭。

過程是這樣的:當append()時,發現數組容量不夠用,於是開辟瞭新的數組空間,cap變為原來的兩倍,s2指向瞭這個新的數組,所以當修改s2時,s1不受影響

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。

推薦閱讀: