## 1. 切片
切片是由數組建立的一種方便、靈活且功能強大的包裝(Wrapper)。切片本身不擁有任何數據。它們只是對現有數組的引用
語法:
使用語法`a[start:end]`創建一個從`a`數組索引`start`開始到`end - 1`結束的切片
### 1.1 創建切片的各種方式
~~~
package main
import "fmt"
func main() {
//1.聲明切片
var s1 []int
if s1 == nil {
fmt.Println("是空")
} else {
fmt.Println("不是空")
}
// 2.:=
s2 := []int{}
// 3.make()
var s3 []int = make([]int, 0)
fmt.Println(s1, s2, s3)
// 4.初始化賦值
var s4 []int = make([]int, 0, 0)
fmt.Println(s4)
s5 := []int{1, 2, 3}
fmt.Println(s5)
// 5.從數組切片
arr := [5]int{1, 2, 3, 4, 5}
var s6 []int
// 前包后不包
s6 = arr[1:4]
fmt.Println(s6)
}
~~~
### 1.2 切片初始化
~~~
全局:
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[start:end]
var slice1 []int = arr[:end]
var slice2 []int = arr[start:]
var slice3 []int = arr[:]
var slice4 = arr[:len(arr)-1] //去掉切片的最后一個元素
局部:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]
slice7 := arr[start:]
slice8 := arr[:]
slice9 := arr[:len(arr)-1] //去掉切片的最后一個元素
~~~

代碼:
~~~
package main
import (
"fmt"
)
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[2:8]
var slice1 []int = arr[0:6] //可以簡寫為 var slice []int = arr[:end]
var slice2 []int = arr[5:10] //可以簡寫為 var slice[]int = arr[start:]
var slice3 []int = arr[0:len(arr)] //var slice []int = arr[:]
var slice4 = arr[:len(arr)-1] //去掉切片的最后一個元素
func main() {
fmt.Printf("全局變量:arr %v\n", arr)
fmt.Printf("全局變量:slice0 %v\n", slice0)
fmt.Printf("全局變量:slice1 %v\n", slice1)
fmt.Printf("全局變量:slice2 %v\n", slice2)
fmt.Printf("全局變量:slice3 %v\n", slice3)
fmt.Printf("全局變量:slice4 %v\n", slice4)
fmt.Printf("-----------------------------------\n")
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[2:8]
slice6 := arr[0:6] //可以簡寫為 slice := arr[:end]
slice7 := arr[5:10] //可以簡寫為 slice := arr[start:]
slice8 := arr[0:len(arr)] //slice := arr[:]
slice9 := arr[:len(arr)-1] //去掉切片的最后一個元素
fmt.Printf("局部變量: arr2 %v\n", arr2)
fmt.Printf("局部變量: slice5 %v\n", slice5)
fmt.Printf("局部變量: slice6 %v\n", slice6)
fmt.Printf("局部變量: slice7 %v\n", slice7)
fmt.Printf("局部變量: slice8 %v\n", slice8)
fmt.Printf("局部變量: slice9 %v\n", slice9)
}
~~~
輸出結果:
~~~
全局變量:arr [0 1 2 3 4 5 6 7 8 9]
全局變量:slice0 [2 3 4 5 6 7]
全局變量:slice1 [0 1 2 3 4 5]
全局變量:slice2 [5 6 7 8 9]
全局變量:slice3 [0 1 2 3 4 5 6 7 8 9]
全局變量:slice4 [0 1 2 3 4 5 6 7 8]
-----------------------------------
局部變量: arr2 [9 8 7 6 5 4 3 2 1 0]
局部變量: slice5 [2 3 4 5 6 7]
局部變量: slice6 [0 1 2 3 4 5]
局部變量: slice7 [5 6 7 8 9]
局部變量: slice8 [0 1 2 3 4 5 6 7 8 9]
局部變量: slice9 [0 1 2 3 4 5 6 7 8]
~~~
### 1.3. 通過make來創建切片
~~~
var slice []type = make([]type, len)
slice := make([]type, len)
slice := make([]type, len, cap)
~~~

代碼:
~~~
package main
import (
"fmt"
)
var slice0 []int = make([]int, 10)
var slice1 = make([]int, 10)
var slice2 = make([]int, 10, 10)
func main() {
fmt.Printf("make全局slice0 :%v\n", slice0)
fmt.Printf("make全局slice1 :%v\n", slice1)
fmt.Printf("make全局slice2 :%v\n", slice2)
fmt.Println("--------------------------------------")
slice3 := make([]int, 10)
slice4 := make([]int, 10)
slice5 := make([]int, 10, 10)
fmt.Printf("make局部slice3 :%v\n", slice3)
fmt.Printf("make局部slice4 :%v\n", slice4)
fmt.Printf("make局部slice5 :%v\n", slice5)
}
~~~
輸出結果:
~~~
make全局slice0 :[0 0 0 0 0 0 0 0 0 0]
make全局slice1 :[0 0 0 0 0 0 0 0 0 0]
make全局slice2 :[0 0 0 0 0 0 0 0 0 0]
--------------------------------------
make局部slice3 :[0 0 0 0 0 0 0 0 0 0]
make局部slice4 :[0 0 0 0 0 0 0 0 0 0]
make局部slice5 :[0 0 0 0 0 0 0 0 0 0]
~~~
切片的數據結構
~~~
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
~~~
讀寫操作實際目標是底層數組,只需注意索引號的差別。
~~~
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 5}
s := data[2:4]
s[0] += 100
s[1] += 200
fmt.Println(s)
fmt.Println(data)
}
~~~
輸出:
~~~
[102 203]
[0 1 102 203 4 5]
~~~
可直接創建 slice 對象,自動分配底層數組。
~~~
package main
import "fmt"
func main() {
s1 := []int{0, 1, 2, 3, 8: 100} // 通過初始化表達式構造,可使用索引號。
fmt.Println(s1, len(s1), cap(s1))
s2 := make([]int, 6, 8) // 使用 make 創建,指定 len 和 cap 值。
fmt.Println(s2, len(s2), cap(s2))
s3 := make([]int, 6) // 省略 cap,相當于 cap = len。
fmt.Println(s3, len(s3), cap(s3))
}
~~~
輸出結果:
~~~
[0 1 2 3 0 0 0 0 100] 9 9
[0 0 0 0 0 0] 6 8
[0 0 0 0 0 0] 6 6
~~~
使用 make 動態創建slice,避免了數組必須用常量做長度的麻煩。還可用指針直接訪問底層數組,退化成普通數組操作。
~~~
package main
import "fmt"
func main() {
s := []int{0, 1, 2, 3}
p := &s[2] // *int, 獲取底層數組元素指針。
*p += 100
fmt.Println(s)
}
~~~
輸出結果:
~~~
[0 1 102 3]
~~~
至于 \[\]\[\]T,是指元素類型為 \[\]T 。
~~~
package main
import (
"fmt"
)
func main() {
data := [][]int{
[]int{1, 2, 3},
[]int{100, 200},
[]int{11, 22, 33, 44},
}
fmt.Println(data)
}
~~~
輸出結果:
~~~
[[1 2 3] [100 200] [11 22 33 44]]
~~~
可直接修改 struct array/slice 成員。
~~~
package main
import (
"fmt"
)
func main() {
d := [5]struct {
x int
}{}
s := d[:]
d[1].x = 10
s[2].x = 20
fmt.Println(d)
fmt.Printf("%p, %p\n", &d, &d[0])
}
~~~
輸出結果:
~~~
[{0} {10} {20} {0} {0}]
0xc4200160f0, 0xc4200160f0
~~~
### 1.4. 用append內置函數操作切片(切片追加)
~~~
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3}
fmt.Printf("slice a : %v\n", a)
var b = []int{4, 5, 6}
fmt.Printf("slice b : %v\n", b)
c := append(a, b...)
fmt.Printf("slice c : %v\n", c)
d := append(c, 7)
fmt.Printf("slice d : %v\n", d)
e := append(d, 8, 9, 10)
fmt.Printf("slice e : %v\n", e)
}
~~~
輸出結果:
~~~
slice a : [1 2 3]
slice b : [4 5 6]
slice c : [1 2 3 4 5 6]
slice d : [1 2 3 4 5 6 7]
slice e : [1 2 3 4 5 6 7 8 9 10]
~~~
append :向 slice 尾部添加數據,返回新的 slice 對象。
~~~
package main
import (
"fmt"
)
func main() {
s1 := make([]int, 0, 5)
fmt.Printf("%p\n", &s1)
s2 := append(s1, 1)
fmt.Printf("%p\n", &s2)
fmt.Println(s1, s2)
}
~~~
輸出結果:
~~~
0xc42000a060
0xc42000a080
[] [1]
~~~
### 1.5. 超出原 slice.cap 限制,就會重新分配底層數組,即便原數組并未填滿。
~~~
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 10: 0}
s := data[:2:3]
s = append(s, 100, 200) // 一次 append 兩個值,超出 s.cap 限制。
fmt.Println(s, data) // 重新分配底層數組,與原數組無關。
fmt.Println(&s[0], &data[0]) // 比對底層數組起始指針。
}
~~~
輸出結果:
~~~
[0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
0xc4200160f0 0xc420070060
~~~
從輸出結果可以看出,append 后的 s 重新分配了底層數組,并復制數據。如果只追加一個值,則不會超過 s.cap 限制,也就不會重新分配。 通常以 2 倍容量重新分配底層數組。在大批量添加數據時,建議一次性分配足夠大的空間,以減少內存分配和數據復制開銷。或初始化足夠長的 len 屬性,改用索引號進行操作。及時釋放不再使用的 slice 對象,避免持有過期數組,造成 GC 無法回收。
### 1.6. slice中cap重新分配規律:
~~~
package main
import (
"fmt"
)
func main() {
s := make([]int, 0, 1)
c := cap(s)
for i := 0; i < 50; i++ {
s = append(s, i)
if n := cap(s); n > c {
fmt.Printf("cap: %d -> %d\n", c, n)
c = n
}
}
}
~~~
輸出結果:
~~~
cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64
~~~
### 1.7. 切片拷貝
~~~
package main
import (
"fmt"
)
func main() {
s1 := []int{1, 2, 3, 4, 5}
fmt.Printf("slice s1 : %v\n", s1)
s2 := make([]int, 10)
fmt.Printf("slice s2 : %v\n", s2)
copy(s2, s1)
fmt.Printf("copied slice s1 : %v\n", s1)
fmt.Printf("copied slice s2 : %v\n", s2)
s3 := []int{1, 2, 3}
fmt.Printf("slice s3 : %v\n", s3)
s3 = append(s3, s2...)
fmt.Printf("appended slice s3 : %v\n", s3)
s3 = append(s3, 4, 5, 6)
fmt.Printf("last slice s3 : %v\n", s3)
}
~~~
輸出結果:
~~~
slice s1 : [1 2 3 4 5]
slice s2 : [0 0 0 0 0 0 0 0 0 0]
copied slice s1 : [1 2 3 4 5]
copied slice s2 : [1 2 3 4 5 0 0 0 0 0]
slice s3 : [1 2 3]
appended slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0]
last slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6]
~~~
copy :函數 copy 在兩個 slice 間復制數據,復制長度以 len 小的為準。兩個 slice 可指向同一底層數組,允許元素區間重疊。
~~~
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("array data : ", data)
s1 := data[8:]
s2 := data[:5]
fmt.Printf("slice s1 : %v\n", s1)
fmt.Printf("slice s2 : %v\n", s2)
copy(s2, s1)
fmt.Printf("copied slice s1 : %v\n", s1)
fmt.Printf("copied slice s2 : %v\n", s2)
fmt.Println("last array data : ", data)
}
~~~
輸出結果:
~~~
array data : [0 1 2 3 4 5 6 7 8 9]
slice s1 : [8 9]
slice s2 : [0 1 2 3 4]
copied slice s1 : [8 9]
copied slice s2 : [8 9 2 3 4]
last array data : [8 9 2 3 4 5 6 7 8 9]
~~~
應及時將所需數據 copy 到較小的 slice,以便釋放超大號底層數組內存。
### 1.8. slice遍歷:
~~~
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
slice := data[:]
for index, value := range slice {
fmt.Printf("inde : %v , value : %v\n", index, value)
}
}
~~~
輸出結果:
~~~
inde : 0 , value : 0
inde : 1 , value : 1
inde : 2 , value : 2
inde : 3 , value : 3
inde : 4 , value : 4
inde : 5 , value : 5
inde : 6 , value : 6
inde : 7 , value : 7
inde : 8 , value : 8
inde : 9 , value : 9
~~~
### 1.9. 切片resize(調整大小)
~~~
package main
import (
"fmt"
)
func main() {
var a = []int{1, 3, 4, 5}
fmt.Printf("slice a : %v , len(a) : %v\n", a, len(a))
b := a[1:2]
fmt.Printf("slice b : %v , len(b) : %v\n", b, len(b))
c := b[0:3]
fmt.Printf("slice c : %v , len(c) : %v\n", c, len(c))
}
~~~
輸出結果:
~~~
slice a : [1 3 4 5] , len(a) : 4
slice b : [3] , len(b) : 1
slice c : [3 4 5] , len(c) : 3
~~~
### 1.10. 字符串和切片(string and slice)
string底層就是一個byte的數組,因此,也可以進行切片操作。
~~~
package main
import (
"fmt"
)
func main() {
str := "hello world"
s1 := str[0:5]
fmt.Println(s1)
s2 := str[6:]
fmt.Println(s2)
}
~~~
輸出結果:
~~~
hello
world
~~~
string本身是不可變的,因此要改變string中字符。需要如下操作: 英文字符串:
~~~
package main
import (
"fmt"
)
func main() {
str := "Hello world"
s := []byte(str) //中文字符需要用[]rune(str)
s[6] = 'G'
s = s[:8]
s = append(s, '!')
str = string(s)
fmt.Println(str)
}
~~~
輸出結果:
~~~
Hello Go!
~~~
### 1.11. 含有中文字符串:
~~~
package main
import (
"fmt"
)
func main() {
str := "你好,世界!hello world!"
s := []rune(str)
s[3] = '夠'
s[4] = '浪'
s[12] = 'g'
s = s[:14]
str = string(s)
fmt.Println(str)
}
~~~
輸出結果:
~~~
你好,夠浪!hello go
~~~
golang slice data\[:6:8\] 兩個冒號的理解
常規slice , data\[6:8\],從第6位到第8位(返回6, 7),長度len為2, 最大可擴充長度cap為4(6-9)
另一種寫法: data\[:6:8\] 每個數字前都有個冒號, slice內容為data從0到第6位,長度len為6,最大擴充項cap設置為8
a\[x:y:z\] 切片內容 \[x:y\] 切片長度: y-x 切片容量:z-x
~~~
package main
import (
"fmt"
)
func main() {
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
d1 := slice[6:8]
fmt.Println(d1, len(d1), cap(d1))
d2 := slice[:6:8]
fmt.Println(d2, len(d2), cap(d2))
}
~~~
數組or切片轉字符串:
~~~
strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)
~~~
*****
【知識點】
* 切片是數組的一個引用,因此切片是引用類型。但自身是結構體,值拷貝傳遞,對切片所做的任何修改都會反映在底層數組中
* 切片的長度可以改變,因此切片是一個可變的數組,切片擴容時容量會變為原來的兩倍
* 切片遍歷方式和數組一樣,可以用len()求長度。表示可用元素數量,讀寫操作不能超過該限制
* cap可以求出slice最大擴張容量,不能超出數組限制。0 <= len(slice) <= len(array),其中array是slice引用的數組
* 當多個切片共用相同的底層數組時,每個切片所做的更改將反映在數組中
* 切片類型的零值為`nil`。一個`nil`切片的長度和容量為 0。可以使用append函數將值追加到`nil`切片
* 切片持有對底層數組的引用。只要切片在內存中,數組就不能被垃圾回收
* 如果 slice == nil,那么 len、cap 結果都等于 0
- 概述
- go語言基礎特性
- Go語言聲明
- Go項目構建及編譯
- go command
- 程序設計原則
- Go基礎
- 變量
- 常量
- iota
- 基本類型
- byte和rune類型
- 類型定義和類型別名
- 數組
- string
- 高效字符串連接
- string底層原理
- 運算符
- new
- make
- 指針
- 下劃線 & import
- 語法糖
- 簡短變量申明
- 流程控制
- ifelse
- switch
- select
- select實現原理
- select常見案例
- for
- range
- range實現原理
- 常見案例
- range陷阱
- Goto&Break&Continue
- Go函數
- 函數
- 可變參數函數
- 高階函數
- init函數和main函數
- 匿名函數
- 閉包
- 常用內置函數
- defer
- defer常見案例
- defer規則
- defer與函數返回值
- defer實現原理
- defer陷阱
- 數據結構
- slice
- slice內存布局
- slice&array
- slice底層實現
- slice陷阱
- map
- Map實現原理
- 集合
- List
- Set
- 線程安全數據結構
- sync.Map
- Concurrent Map
- 面向對象編程
- struct
- 匿名結構體&匿名字段
- 嵌套結構體
- 結構體的“繼承”
- struct tag
- 行為方法
- 方法與函數
- type Method Value & Method Expressions
- interface
- 類型斷言
- 多態
- 錯誤機制
- error
- 自定義錯誤
- panic&recover
- reflect
- reflect包
- 應用示例
- DeepEqual
- 反射-fillObjectField
- 反射-copyObject
- IO
- 讀取文件
- 寫文件
- bufio
- ioutil
- Go網絡編程
- tcp
- tcp粘包
- udp
- HTTP
- http服務
- httprouter
- webSocket
- go并發編程
- Goroutine
- thread vs goroutine
- Goroutine任務取消
- 通過channel廣播實現
- Context
- Goroutine調度機制
- goroutine調度器1.0
- GMP模型調度器
- 調度器竊取策略
- 調度器的生命周期
- 調度過程全解析
- channel
- 無緩沖的通道
- 緩沖信道
- 單向信道
- chan實現原理
- 共享內存并發機制
- mutex互斥鎖
- mutex
- mutex原理
- mutex模式
- RWLock
- 使用信道處理競態條件
- WaitGroup
- 工作池
- 并發任務
- once運行一次
- 僅需任意任務完成
- 所有任務完成
- 對象池
- 定時器Timer
- Timer
- Timer實現原理
- 周期性定時器Ticker
- Ticker對外接口
- ticker使用場景
- ticker實現原理
- ticker使用陷阱
- 包和依賴管理
- package
- 依賴管理
- 測試
- 單元測試
- 表格測試法
- Banchmark
- BDD
- 常用架構模式
- Pipe-filter pattern
- Micro Kernel
- JSON
- json-內置解析器
- easyjson
- 性能分析
- gc
- 工具類
- fmt
- Time
- builtin
- unsafe
- sync.pool
- atomic
- flag
- runtime
- strconv
- template