1. 程式人生 > >go——切片

go——切片

切片(slice)可以看作一種對陣列的包裝形式,它包裝的陣列為該切片的底層陣列。
反過來講,切片是針對其底層陣列中某個連續片段的描述,下面的程式碼聲明瞭一個切片型別的變數:
var ips = []string{"192.168.1.1","192.168.1.2","192.168.1.3"}
與陣列不同,切片的型別自變數(如[]string)並不攜帶長度資訊。
切片的長度是可變的,且並不是型別的一部分;只要元素型別相同,兩個切片的型別就是相同的。
此外,一個切片型別的零值總是nil,此零值的長度和容量都為0。

切片本身並非動態陣列或陣列指標。它內部通過指標引用底層陣列,設定相關屬性將資料讀寫操作限定在指定區域內。
其內部結構包含了3個元素:指向底層陣列中某個元素的指標、切片的長度以及切片的容量。

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

 

可基於陣列或陣列指標建立切片,以開始和結束索引位置確定所引用的陣列片段。
不支援反向索引,實際範圍是一個右半開區間。

package main

import "fmt"

func main() {
	x := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println(x[:], len(x[:]), cap(x[:]))
	fmt.Println(x[2:5], len(x[2:5]), cap(x[2:5]))
	fmt.Println(x[2:5:7], len(x[2:5:7]), cap(x[2:5:7]))
	fmt.Println(x[4:], len(x[4:]), cap(x[4:]))
	fmt.Println(x[:4], len(x[:4]), cap(x[:4]))
	fmt.Println(x[:4:6], len(x[:4:6]), cap(x[:4:6]))
}

/*
                                   len  cap
x[:]      [0 1 2 3 4 5 6 7 8 9]    10    10
x[2:5]    [2 3 4]                  3     8
x[2:5:7]  [2 3 4]                  3     5
x[4:]     [4 5 6 7 8 9]            6     6
x[:4]     [0 1 2 3]                4     10
x[:4:6]   [0 1 2 3]                4     6
*/
//[x:y:z]  x:切片的起始位置  y:切片的終止位置  z:容量的終止位置
//cap表示切片所引用陣列片段的真實長度
//len用於限定可讀的寫元素數量

 

和陣列一樣,切片同樣使用索引號訪問元素內容。
起始索引為0,而非對應的底層陣列和真實索引位置。

package main

import "fmt"

func main() {
	x := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s := x[2:5]

	for i := 0; i < len(s); i++ {
		fmt.Println(s[i])
	}
}

/*
2
3
4
*/

 

可直接建立切片物件,無需預先準備陣列。
因為是引用型別,須使用make函式或顯式初始化語句,它會自動完成底層陣列記憶體分配。

package main

import "fmt"

func main() {
	s1 := make([]int, 3, 5) //make(初始化型別,長度,容量)  如果沒有指定容量則與長度一致
	s2 := make([]int, 3)
	s3 := []int{10, 20, 5: 30} //第6個元素為30

	fmt.Println(s1, len(s1), cap(s1)) //[0 0 0] 3 5
	fmt.Println(s2, len(s2), cap(s2)) //[0 0 0] 3 3
	fmt.Println(s3, len(s3), cap(s3)) //[10 20 0 0 0 30] 6 6
}

 

注意下面兩種定義方式。

package main

import "fmt"

func main() {
	var a []int  //僅定義了一個[]int變數,並未執行初始化操作
	b := []int{} //初始化表示式完成了全部建立過程

	fmt.Println(a == nil, b == nil) //true false
}

 

不支援比較操作,就算元素型別支援也不行,僅能判斷是否為nil。
可獲取元素地址,但不能像陣列那樣直接用指標訪問元素內容。

如果元素型別也是切片,那麼就可實現類似交錯陣列功能。

package main

import "fmt"

func main() {
	x := [][]int{
		{1, 2},
		{10, 20, 30},
		{100},
	}
	fmt.Println(x[1]) //取值

	x[2] = append(x[2], 200, 300) //新增元素
	fmt.Println(x)
}

/*
[10 20 30]
[[1 2] [10 20 30] [100 200 300]]
*/

 

新建切片物件依舊指向原底層陣列,也就是說修改對所有關聯切片可見。
需要注意的是,當向新建切片中新增值的時候,並不會向底層陣列中新增

package main

import "fmt"

func main() {
	d := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := d[3:7]  //[3, 4, 5, 6]
	s2 := s1[1:3] //[4, 5]

	for i := range s2 {
		s2[i] += 100 //[104, 105]
	}
	fmt.Println(d)
	fmt.Println(s1)
	fmt.Println(s2)
}

/*
[0 1 2 3 104 105 6 7 8 9]
[3 104 105 6]
[104 105]
*/

 

append向切片尾部新增資料,返回新的切片物件。

package main

import "fmt"

func main() {
	s := make([]int, 0, 5)

	s1 := append(s, 10)
	s2 := append(s1, 20, 30)

	fmt.Println(s, len(s), cap(s))
	fmt.Println(s1, len(s1), cap(s1))
	fmt.Println(s2, len(s2), cap(s2))
}

/*
[] 0 5
[10] 1 5
[10 20 30] 3 5
*/

 

陣列被追加到原底層陣列。如果超出cap限制,則為新切片物件重新分配陣列。

package main

import "fmt"

func main() {
	s := make([]int, 0, 100)
	s1 := s[:2:4]
	s2 := append(s1, 1, 2, 3, 4, 5, 6) //超出是s1 cap限制,分配新底層陣列

	fmt.Println("s1:", &s1[0], s1)
	fmt.Println("s2:", &s2[0], s2)
	fmt.Println("s[:10]:", s[:10])
	fmt.Printf("s1 cap: %d, s2 cap: %d\n", cap(s1), cap(s2))
}

/*
s1: 0xc000088000 [0 0]
s2: 0xc00007e040 [0 0 1 2 3 4 5 6]  //陣列地址不同,確認新分配
s[:10]: [0 0 0 0 0 0 0 0 0 0]       //append並未向原陣列中寫入資料
s1 cap: 4, s2 cap: 8                //新陣列是原cap的2倍,一般是2倍,但也不絕對
*/

 

  在兩個切片資料間複製資料,允許指向同一底層陣列,允許目標區間重疊。
最終所複製長度以較短的切片長度(len)為準。

package main

import "fmt"

func main() {
	s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	s1 := s[5:8]
	n := copy(s[4:], s1)
	fmt.Println(n, s)

	s2 := make([]int, 6)
	n = copy(s2, s)
	fmt.Println(n, s2)
}

/*
3 [0 1 2 3 5 6 7 7 8 9]
6 [0 1 2 3 5 6]
*/