1. 切片:对底层数组的一个连续片段的引用,所以切片是一个引用类型(和数组不一样)该数组称为相关数组,通常是匿名的。
  2. 切片提供对该数组中编号的元素序列的访问。
  3. 未初始化切片的值为nil(注意:nil切片空切片[]的区别)。
  4. 与数组一样切片是可索引的并且具有长度。
  5. 切片s的长度可以通过内置的len()函数获取,与数组不同切片的长度可能在执行期间发生变化。
  6. 元素可以通过整数索引0len(s)-1来寻址,切片相当一个长度可变的数组。
  7. 计算容器的函数cap()。可以计算切片最大长度。
  8. 切片的长度永远不会超过它的容量,所以对于s切片来说,0 <= len(s) <= cap(s)
  9. 一旦初始化,切片始终与保存其元素的基础数据相关联。
    • 因此,切片会和其拥有同一基础数据的其他切片共享存储。
    • 相比之下,不同的数组总是拥有不同的存储。
  10. 使用make()函数可以给切片初始化,该函数指定切片类型、长度和可选容量的参数。
  11. 因为切片是引用,所以他们不需要使用额外的内存,并且比数组更高效,因此切片比数组常用。

声明切片

  1. 声明切片格式。不需要指定长度,切片在未初始化之前默认为nil,长度为0
1
var identifer []type
  1. 切片初始化格式。
    • slice1是由数组arr1start索引到end-1索引之间的元素构成的子集。
    • start:end 称为slice表达式。
1
2
3
4
// [start,end)
//   len = end - start
//   cap = end - start
var slice1 []type = arr1[strat:end]
  1. 切片初始化格式。
1
var x = []int{2,3,5,7,11,13} // 这样创建一个长度和容量为6的切片
  1. 使用make()函数来创建一个切片。
1
2
3
var slice1 []type = make([]type, len, cap)
// 简写形式如下
slice1 := make([]type, len, cap)
  1. make函数的len是数组的长度也是slice的初始长度,cap是容量,是可选参数。
1
2
3
// 创建一个有50个int值得数组,并且创建长度为10,容量为50的切片
// 该切片指向数组的前10个元素
v := make([]int, 10 , 50)
  1. 从数组或者切片中生成一个新的切片。
1
2
3
4
5
6
7
// cap = max - low 的结果表示容量
// len = high - low 的结果表示长度
a[low:high:max] // high和max不能大于a.cap的值

// low的默认值0
// high的默认值 len(a)
// max的默认值 cap(a)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
)

func main()  {
    a := [5]int{1,2,3,4,5}

    // len = 3-1 = 2
    // cap = 5-1 = 4
    t := a[1:3:5]       // 可以看出此处变量t引用了变量a的部分数据

    fmt.Println(a, t)   // [1 2 3 4 5] [2 3]

    a[1] += 1

    fmt.Println(a, t)   // [1 3 3 4 5] [3 3]

    fmt.Println(t, len(t), cap(t))  // [3,3] 2 4
}
  1. 切片取值时索引大于长度会导致异常发生,即使容量远远大于长度也没有用。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
    "fmt"
)

func main()  {
    sli := make([]int, 5, 10)
    fmt.Printf("切片sli长度和容量:%d, %d\n", len(sli), cap(sli))
    fmt.Println(sli)

    // len = 10, cap = 10
    newSli := sli[:cap(sli)]    // 可以看出此处变量newSli复用了sli的底层数组,导致修改一个全部都变
    fmt.Println(sli, newSli)    // [0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0]
    newSli[0] += 10
    fmt.Println(sli, newSli)    // [10 0 0 0 0] [10 0 0 0 0 0 0 0 0 0]

    var x = []int{2,3,5,7,11}
    fmt.Printf("切片x长度和容量:%d, %d\n", len(x), cap(x))

    a := [5]int{1,2,3,4,5}
    // len = 2, cap = 4
    t := a[1:3:5]       // 可以看出变量t复用了a的数据,导致修改一个全部都变
    fmt.Println(t, a)   // [2 3] [1 2 3 4 5]
    t[0] += 10
    fmt.Println(t, a)   // [12 3] [1 12 3 4 5]
    fmt.Printf("切片t长度和容量:%d, %d\n", len(t), cap(t))

    // fmt.Println(t[2])  // panic 索引不能超过切片的长度
}

/**
 * 切片sli长度和容量:5, 10
 * [0 0 0 0 0]
 * [0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0]
 * [10 0 0 0 0] [10 0 0 0 0 0 0 0 0 0]
 * 切片x长度和容量:5, 5
 * [2 3] [1 2 3 4 5]
 * [12 3] [1 12 3 4 5]
 * 切片t长度和容量:2, 4
 */
  1. 创建切片汇总。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import "fmt"

func main() {
    // 1. 声明切片,nil切片,也就是没有分配底层关联数组的切片
    var s1 []int    // 默认值为 nil 格式如 type slice struct {nil, 0, 0}
    if s1 == nil {
        fmt.Println("是nil") // 是nil
    } else {
        fmt.Println("不是nil")
    }
    
    // Output:
    // 是nil
    
    // 2. := []切片,也就是初始化了底层关联数组的切片
    s2 := []int{}   // 默认值为 struct {0xxxxxx, 0, 0}
    if s2 != nil {
        fmt.Println("不是nil")   // 不是nil
    } else {
        fmt.Println("是nil")
    }
    
    // Output:
    // 不是nil

    // 3. make()
    var s3 []int = make([]int, 0) // 空切片
    fmt.Printf("s1:%#v s2:%#v s3:%#v\n", s1, s2, s3) // s1:[]int(nil) s2:[]int{} s3:[]int{}
    fmt.Println(s1, s2, s3)	// [] [] []
    
    // 4. 初始化赋值
    var s4 []int = make([]int, 0, 0) // 空切片
    fmt.Printf("s4:%#v\n", s4) // s4:[]int{}
    fmt.Println(s4) // []
    s5 := []int{1, 2, 3}
    fmt.Println(s5) // [1 2 3]
    
    // 5. 从数组切片
    arr := [5]int{1, 2, 3, 4, 5}
    var s6 []int
    // [1,4),该语法的意思是引用数组地址,注意返回的是对应的切片
    // len = 3, cap = 4
    s6 = arr[1:4] // 这种情况会共用数组的作为底层关联数组
    fmt.Println(s6, len(s6), cap(s6)) // [2 3 4] 3 4
}

切片初始化

array、string、slice 使用 [:]

  1. 数组使用[:]返回的是对应的【切片】类型,数组使用 [low:high:max] 时,lowhighmax必须是满足[0, len(array)]范围。
  2. 字符串使用[:]返回的还是【字符串】类型,字符串使用 [low:high] 时,lowhigh必须是满足[0, len(string)]范围。字符串不支持第三个参数max也没任何意义。
  3. 切片使用[:]返回的还是【切片】类型,切片使用 [low:high:max] 时,lowhighmax必须满足[0, cap(slice)]范围。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package main

import (
    "fmt"
    "unsafe"
)

// 切片内存结构
type sliceStruct struct {
    data uintptr
    len int
    cap int
}

// 字符内存结构
type stringStruct1 struct {
    data uintptr
    len int
}

func main() {
    fmt.Println("array -------")
    // 1. 数组使用[low:high:max]情况时
    var a [3]int = [3]int{1, 2, 3}

    // &a:0xc000014138, &a[0]0xc000014138
    fmt.Printf("&a:%p, &a[0]%p\n", &a, &a[0])	

    // len = 2-0 = 2
    // cap = 3-0 = 3
    a1 := a[:2:3]   // 这种形式确实共用了同一个底层关联数组
    u1 := *(**byte)(unsafe.Pointer(&a1))
    u2 := *(*sliceStruct)(unsafe.Pointer(&a1))

    // u1:0xc000014138, u2:main.sliceStruct{data:0xc000014138, len:2, cap:3}
    fmt.Printf("u1:%p, u2:%#v\n", u1, u2)	
    fmt.Printf("a.Type: %T\n", a1)  // a.Type: []int

    // > -----------------------------------------------------------------------

    fmt.Println("string -------")
    // 2. 字符串使用[low:high]情况时,感觉字符串中max参数没用
    var ss string = "hello,world!"  // 12
    // &ss[0]:0x436cf1
    fmt.Printf("&ss[0]:%p\n", *(**byte)(unsafe.Pointer(&ss)))		
    // &ss-struct: main.stringStruct1{data:0x436cf1, len:12}
    fmt.Printf("&ss-struct: %#v\n", *(*stringStruct1)(unsafe.Pointer(&ss)))	

    ss1 := ss[:10] // len = 10
    // &ss1[0]:0x436cf1
    fmt.Printf("&ss1[0]:%p\n", *(**byte)(unsafe.Pointer(&ss1)))					
    // &ss1-struct: main.stringStruct1{data:0x436cf1, len:10}
    fmt.Printf("&ss1-struct: %#v\n", *(*stringStruct1)(unsafe.Pointer(&ss1)))	
    fmt.Printf("ss1.Type: %T\n", ss1)	// ss1.Type: string

    // > -----------------------------------------------------------------------

    // 3. 切片
    fmt.Println("slice -------")
    var sl []int = []int{1,2,3,4,5,6,7,8}   // len=8,cap=8
    // 为什么下面的地址没有替换翻倍扩容?原因使sl被覆盖了
    // 如果在这一行打印sl地址,与下面坑定不同。fmt.Printf("&sl[0]:%p\n", &sl[0])
    sl = append(sl, 9) // 这里翻倍扩容了
    // &sl[0]:0xc00000c340
    fmt.Printf("&sl[0]:%p\n", &sl[0])					
    // &sl[0]:0xc00000c340
    fmt.Printf("&sl[0]:%p\n", *(**byte)(unsafe.Pointer(&sl)))		
    // sl-struct:main.sliceStruct{data:0xc00000c340, len:9, cap:16}
    fmt.Printf("sl-struct:%#v\n", *(*sliceStruct)(unsafe.Pointer(&sl)))	

    sl1 := sl[:]
    // &sl1[0]:0xc00000c340
    fmt.Printf("&sl1[0]:%p\n", &sl1[0])									

    sl2 := sl[:10:16] // 正是由于前面sl翻倍扩容了,这里能取到16
    // &sl1[0]:0xc00000c340
    fmt.Printf("&sl1[0]:%p\n", &sl2[0])									
}

[:]初始化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 全局:
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // len=10
// len=9,cap=10
var slice0 []int = arr[0:9] // [0,9)
// len=9,cap=10
var slice1 []int = arr[:9]  // [0,9)  	
// len=10,cap=10
var slice2 []int = arr[0:]  // [0,10]    
// len=10,cap=10
var slice3 []int = arr[:]   // [0,10]
// len=9,cap=10
// arr[:9]
var slice4 = arr[:len(arr)-1] // 去掉切片的最后一个元素

// 局部:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[0:9]
slice6 := arr[:9]        
slice7 := arr[0:]     
slice8 := arr[:]  
slice9 := arr[:len(arr)-1]  //去掉切片的最后一个元素
操作 含义
s[n] 切片s中索引位置为n的项
s[:] 从切片s的索引位置0len(s)-1处所获得的切片
s[low:] 从切片s的索引位置lowlen(s)-1处所获得的切片
s[:high] 从切片s的索引位置0high处所获得的切片,len == high
s[low:high] 从切片s的索引位置lowhigh处所获得的切片,len == high-low
s[low:high:max] 从切片s的索引位置lowhigh处所获得的切片,len == high-lowcap == max-low
len(s) 切片s的长度,总是 <= cap(s)
cap(s) 切片s的容量,总是 >= len(s)
  1. s[low:high:max]
    • 省略low默认为0
    • 省略high默认为len(s)
    • 省略max默认为cap(s)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package main

import (
    "fmt"
)

var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

// main.init
// main.init.0

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)

    // Output:
    // 全局变量: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]
}

make()创建切片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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)

    // Output:
    // 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]
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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)
    
    // Output:
    // [102 203]
    // [0 1 102 203 4 5]
}
  1. 可直接创建 slice 对象,自动分配底层数组。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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))
    
    // Output:
    // [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
}
  1. 使用 make 动态创建slice,避免了数组必须用常量做长度的麻烦。
  2. 还可用指针直接访问底层数组,退化成普通数组操作。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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]
    
    // Output:
    // [0 1 102 3]
}

二维切片

  1. 至于 [][]T,是指元素类型为 []T,该结构为二维切片。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // make函数只能初始化最外层内存,也就是24 * 16,最里层不能
    // 会创建24 * 16 大小内存,也就是[]int * cap会分配内存,但是最里一层并没有初始化这点需要注意
    s := make([][]int, 8, 16)	
    // [][]int{[]int(nil), []int(nil), []int(nil), []int(nil), []int(nil), []int(nil), []int(nil), []int(nil)}
    fmt.Printf("%#v\n", s)	
    s[0] = make([]int, 2, 4)
    // [][]int{[]int{0, 0}, []int(nil), []int(nil), []int(nil), []int(nil), []int(nil), []int(nil), []int(nil)}
    fmt.Printf("%#v\n", s)	
    // 注意s还是切片结构,因此这里依然占用24字节大小
    fmt.Printf("s占用内存大小%d\n", unsafe.Sizeof(s))	
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
    "fmt"
)

func main() {
    data := [][]int{
        []int{1, 2, 3},
        []int{100, 200},
        []int{11, 22, 33, 44},
    }

    fmt.Println(data)	
    fmt.Printf("%#v\n", data)	
    
    // Output:
    // [[1 2 3] [100 200] [11 22 33 44]]
    // [][]int{[]int{1, 2, 3}, []int{100, 200}, []int{11, 22, 33, 44}}
}
  1. 可直接修改 struct array/slice 成员。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
    "fmt"
)

func main() {
    d := [5]struct {
        x int
    }{}

    // 使用d[:]数组d称为s的底层关联数组 
    s := d[:]

    // 修改d的数据相当于直接修改s的底层关联数组
    d[1].x = 10
    // 修改s的数据也相当于直接修改s的底层关联数组d的数据
    s[2].x = 20

    fmt.Println(d)  // [{0} {10} {20} {0} {0}]
    fmt.Println(s)  // [{0} {10} {20} {0} {0}]

    // 0xc00000c3f0, 0xc00000c3f0, 0xc000004078, 0xc00000c3f0
    fmt.Printf("%p, %p, %p, %p\n", &d, &d[0], &s, &s[0]) 
}

append()追加元素

  1. append(s S, x ...T) S
    • s切片中追加数据TT的类型为s切片的元素类型)S = []T。返回追加后的切片S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
    "fmt"
)

func main() {
    var a = []int{1, 2, 3}  // len 3 cap 3
    fmt.Printf("slice a : %v\n", a)
    var b = []int{4, 5, 6}  // len 3 cap 3
    fmt.Printf("slice b : %v\n", b)
    c := append(a, b...)    // len 6 cap 6
    fmt.Printf("slice c : %v\n", c)
    d := append(c, 7)       // len 7 cap 12
    fmt.Printf("slice d : %v\n", d)
    e := append(d, 8, 9, 10)// len 10 cap 12
    fmt.Printf("slice e : %v, %d, %d\n", e, len(e), cap(e))
    
    // Output:
    // 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], 10, 12
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
     var c []byte = make([]byte, 0, 20)

    // slice = append([]byte("hello "), "world"...)
    c = append(c, "Go语言001"...)

    fmt.Printf("%#v\n", c)

    fmt.Println(string(c))

    // Output:
    // []byte{0x47, 0x6f, 0xe8, 0xaf, 0xad, 0xe8, 0xa8, 0x80, 0x30, 0x30, 0x31}
    // Go语言001
}
  1. append:向slice尾部添加数据,返回新的slice对象。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    s1 := make([]int, 0, 5)
    // %p 这里是 打印s1第一个指针地址,所以这里显示的是第一个8B存储的值
    fmt.Printf("s1关联数组地址:%p\n", s1)

    s2 := append(s1, 2012, 5)
    fmt.Printf("s2关联数组地址:%p\n", s2)

    fmt.Println(s1, s2, len(s1), cap(s1), len(s2), cap(s2)) // [] [2012 5] 0 5 2 5

    // Output:
    // s1关联数组地址:0xc0000c2060
    // s2关联数组地址:0xc0000c2060
    // [] [2012 5] 0 5 2 5

    // 切片结构 slice struct { data pointer, len int, cap int }

    // a1拿到的是结构体中data的值也就是底层关联数组的首地址值
    a1 := *(*uintptr)(unsafe.Pointer(&s2))

    fmt.Printf("a1:type:%T a1存储的值:%#x\n", a1, a1) // a1:type:uintptr a1存储的值:0xc0000c2060

    // s2[0]的值
    fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(nil)) + *(*uintptr)(unsafe.Pointer(&s2)))))

    // s2[1]的值
    fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(nil)) + *(*uintptr)(unsafe.Pointer(&s2)) + unsafe.Sizeof(int(0)))))

    // s2[2]的值 直接访问s2[2]会出现下标越界
    fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(nil)) + *(*uintptr)(unsafe.Pointer(&s2)) + 2 * unsafe.Sizeof(int(0)))))

    // Output:
    // s1关联数组地址:0xc0000c6060
    // s2关联数组地址:0xc0000c6060
    // [] [2012 5] 0 5 2 5
    // a1:type:uintptr a1存储的值:oxc0000c6060
    // 2012
    // 5
    // 0
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
    "fmt"
    "unsafe"
)

type SliceHeader struct {
    Data uintptr
    Len int
    Cap int
}

func main() {
    s1 := make([]int, 0, 5)
    fmt.Println(s1)         // []
    fmt.Printf("%p\n", s1)  // 0xc00001a0c0

    // main.SliceHeader{Data:0xc00001a0c0, Len:0, Cap:5}
    fmt.Printf("%#v\n", *(*SliceHeader)(unsafe.Pointer(&s1)))	

    s2 := append(s1, 1, 2)

    fmt.Printf("%p\n", s2)  // 0xc00001a0c0

    s3 := append(s2, 1, 2, 3, 4)

    fmt.Printf("%p\n", s3)  // 0xc0000c4000
}

slice.cap 限制

  1. 会重新分配底层数组,即便原数组并未填满。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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]) // 比对底层数组起始指针。
    
    // Output:
    // [0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
    // 0xc00000c3f0 0xc00005c060
}
  1. 从输出结果可以看出:
    • append 后的 s 重新分配了底层数组,并复制数据。
    • 如果只追加一个值,则不会超过 s.cap 限制,也就不会重新分配。
    • 通常以 2 倍容量重新分配底层数组。
  2. 在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。
  3. 或初始化足够长的 len 属性,改用索引号进行操作。
  4. 及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。

copy() 使用

  1. copy(to, fm slice) intcopy(to []byte, fm string) int
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
    "fmt"
)

func main() {
    s1 := []int{1, 2, 3, 4, 5}
    fmt.Printf("slice s1 : %v\n", s1)   // slice s1 : [1 2 3 4 5]
    s2 := make([]int, 10)
    fmt.Printf("slice s2 : %v\n", s2)   // slice s2 : [0 0 0 0 0 0 0 0 0 0]
    // copy(to, fm slice) int       fm -> to
    copy(s2, s1)
    fmt.Printf("copied slice s1 : %v\n", s1) // copied slice s1 : [1 2 3 4 5]
    fmt.Printf("copied slice s2 : %v\n", s2) // copied slice s2 : [1 2 3 4 5 0 0 0 0 0]

    s3 := []int{1, 2, 3}
    fmt.Printf("slice s3 : %v\n", s3)   // slice s3 : [1 2 3]
    s3 = append(s3, s2...)
    fmt.Printf("appended slice s3 : %v\n", s3) // appended slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0]
    s3 = append(s3, 4, 5, 6)
    fmt.Printf("last slice s3 : %v\n", s3) // last slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6]
}
  1. 函数copy在两个slice间复制数据,复制长度以 len小的为准。
  2. 两个slice可指向同一底层数组,允许元素区间重叠(两个切片都指向同一底层数组时需要注意覆盖问题)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
    "fmt"
)

func main() {
    data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println("array data : ", data)  // array data :  [0 1 2 3 4 5 6 7 8 9]
    s1 := data[8:]
    s2 := data[:5]
    fmt.Printf("slice s1 : %v\n", s1)   // slice s1 : [8 9]
    fmt.Printf("slice s2 : %v\n", s2)   // slice s2 : [0 1 2 3 4]
    // copy(to, fm slice) int
    copy(s2, s1)
    fmt.Printf("copied slice s1 : %v\n", s1)    // copied slice s1 : [8 9]
    fmt.Printf("copied slice s2 : %v\n", s2)    // copied slice s2 : [8 9 2 3 4]
    // 注意这里索引0 索引1的值 在copy函数时发生了变化,因为都是同一个底层数组的原因
    fmt.Println("last array data : ", data)	    // last array data :  [8 9 2 3 4 5 6 7 8 9]
}
  1. 应及时将所需数据copy到较小的slice,以便释放超大号底层数组内存。

遍历切片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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. string底层就是一个的数组,因此,也可以进行切片操作。
  2. string数据结构:type string struct {data uintptr; len int}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
    str := "hello world"
    s1 := str[0:5]  // string
    fmt.Println(s1)
    
    s2 := str[6:]   // string
    fmt.Println(s2)
    
    // Output:
    // hello
    // world
}
  1. string本身是不可变的,因此要改变string中字符。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
    str := "Hello world"
    // []byte(str) 会重新分配内存并拷贝str数据
    s := []byte(str) // 中文字符需要用[]rune(str)

    s[6] = 'G'

    // 可以看出使用[]byte强制转换【并没】用共用一个底层数组
    fmt.Println(str, string(s)) // Hello world Hello Gorld

    s = s[:8]

    s = append(s, '!')

    // string(s) 也会重新分配一块内存,然后拷贝s数据
    // 原因是slice是可变的,而string是不可变的
    str = string(s) // 切片转字符串
    fmt.Println(str)// Hello Go!
}

nil切片和空切片区别

  1. nil切片表示切片没有初始化,也就是没有分配存储地址。
  2. 空切片则是切片已经初始化,并分配了存储地址。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
    "fmt"
)

func main() {
    var sl []string // nil切片  sl并没有分配底层内存地址

    if sl == nil {
        fmt.Println("aaaa")
    }
}
// 看下相关汇编
string.go:7 0x496680    493b6610        cmp rsp, qword ptr [r14+0x10]
string.go:7 0x496684    767d            jbe 0x496703
string.go:7 0x496686    4883ec68        sub rsp, 0x68
string.go:7 0x49668a    48896c2460      mov qword ptr [rsp+0x60], rbp
string.go:7 0x49668f    488d6c2460      lea rbp, ptr [rsp+0x60]
// 这是设置 slice.data = 0 可以看出并没有分配存储内存
string.go:8 0x496694    48c744243000000000  mov qword ptr [rsp+0x30], 0x0
// 这里是 slice.len = slice.cap = 0 设置了长度和容量
string.go:8 0x49669d    440f117c2438        movups xmmword ptr [rsp+0x38], xmm15
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
    "fmt"
)

func main() {
    sl := []string{} // 空切片  sl此时已经初始化过,分配了底层内存地址

    if sl == nil {
        fmt.Println("aaaa")
    }
}
// 看下相关汇编
string.go:7	0x46d240    4883ec30    sub rsp, 0x30
string.go:7	0x46d244    48896c2428  mov qword ptr [rsp+0x28], rbp
string.go:7	0x46d249    488d6c2428  lea rbp, ptr [rsp+0x28]
// []string的底层数组被编译器直接分配在了栈上,因为rsp存储的是栈上的值
string.go:8	0x46d24e    488d0424    lea rax, ptr [rsp] // rax = rsp 存储的值
// [rsp+0x8] = rax  这里是存储的变量sl的地址空间指向slice.data
string.go:8	0x46d252    4889442408  mov qword ptr [rsp+0x8], rax
string.go:8	0x46d257    8400        test byte ptr [rax], al
string.go:8	0x46d259    eb00        jmp 0x46d25b
// [rsp+0x10] = rax  slice.data = rax 可以看出是分配了地址空间
string.go:8	0x46d25b    4889442410	mov qword ptr [rsp+0x10], rax
// 这里是 slice.len = slice.cap = 0 设置了长度和容量
string.go:8	0x46d260    440f117c2418    movups xmmword ptr [rsp+0x18], xmm15 
string.go:10 0x46d266   eb00        jmp 0x46d268
string.go:13 0x46d268   488b6c2428  mov rbp, qword ptr [rsp+0x28]
    .:0     0x46d26d    4883c430    add rsp, 0x30
    .:0     0x46d271    c3          ret

切片重组

  1. 通过改变切片长度得到新切片的过程称为切片重组
    • slice1 = slice1[0:end] 取值范围len = end - 0; cap = cap - 0;
    • 其中end是新的末尾索引(即长度)。
  2. 在一个切片基础上重新划分一个切片时,新的切片会继续引用原有切片的相关数组。
  3. 如果忘记这个行为,在程序内存内分配占用大量的内存的临时切片。
    • 然后在这个临时切片基础上创建只引用一小部分原有数据的新切片时。
    • 会导致难以预期的内存使用结果。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
    "fmt"
)

func get() []byte {
    raw := make([]byte, 10000)

    fmt.Println(len(raw), cap(raw), &raw[0]) // 10000 10000 0xc000090000

    // 此处返回值引用变量raw的底层部分数组数据,导致返回时raw从栈逃逸到堆里
    // return raw[:3:3] // 这种也会逃逸到堆
    // 返回切片底层数组引用了raw导致raw不会被释放,占用很多不必要内存空间
    //  len = 3, cap = 10000
    return raw[:3]  // raw变量逃逸到堆了
}

func main()  {
    data := get()
    fmt.Println(len(data), cap(data), &data[0]) // 3 10000 0xc000090000

    // &raw[0] == $data[0] 表明切片被引用了
}
  1. 为了避免这个陷阱,需要在临时的切片中使用内置函数copy(),复制数据(而不是重新引用划分切片)到新切片。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
    "fmt"
)

func get() []byte {
    raw := make([]byte, 10000)

    fmt.Println(len(raw), cap(raw), &raw[0]) // 10000 10000 0xc000080000

    // len = 3, cap = 3
    res := make([]byte, 3)

    // 利用copy函数复制, raw可被GC释放
    // res从raw复制部分数据 copy(to, fm slice) int
    // 使用copy能有效防止变量逃逸
    copy(res, raw[:3])

    return res
}

func main()  {
    data := get()
    fmt.Println(len(data), cap(data), &data[0]) // 3 3 0xc00000a0c8
}
  1. 需要向切片末尾追加数据时,可以使用内置函数append()
1
func append(s S,x ...T) S // T是S元素类型 S = []T
  1. append函数将0个或多个具有相同类型S的元素追加到切片s后面并且返回新的切片。
  2. 追加的元素必须和原切片的元素同类型,如果s的容量不足以存储新增元素,append会分配新的切片来保证已有切片元素和新增元素的存储。
  3. 因此append函数返回的切片可能已经指向一个不同的相关数组了,即使修改了数据也不会同步。
  4. append()函数总是返回成功,除非系统内存耗尽了。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
    "fmt"
)

func main()  {
    // 切片容量不足 append() 返回的是新的切片 指向不同的关联数组
    s0 := []int{0,0}
    fmt.Println(s0, &s0[0], len(s0), cap(s0))// [0 0] 0xc00000a0b0 2 2

    // 由于s0的容量只有2,此时添加元素导致扩容,从新分配内存,并把之前数据复制过来
    s1 := append(s0,2)
    fmt.Println(s1, &s1[0], len(s1), cap(s1))// [0 0 2] 0xc00000e340 3 4
    s1[0] += 1
    fmt.Println(s0, s1) // [0 0] [1 0 2]

    // 此时添加元素 又会导致扩容 从新分配内存 并把之前数据复制过来
    s2 := append(s1, 3, 5, 7)
    fmt.Println(s2, &s2[0], len(s2), cap(s2))// [1 0 2 3 5 7] 0xc00000c300 6 8

    // 此时刚好达到最大容量,不会扩容 变量s3引用变量s2底层数组,修改其中元素会引起其他修改
    s3 := append(s2, s0...)
    fmt.Println(s3, len(s3), cap(s3))   // [1 0 2 3 5 7 0 0] 8 8
    s2[1] += 1
    fmt.Println(s2, s3) // [1 1 2 3 5 7] [1 1 2 3 5 7 0 0]

    // s3[3:6] len=3 cap=5
    // s3[2:] len=6 cap=6
    // 导致s4从新分配内存存储数据
    s4 := append(s3[3:6], s3[2:]...)
    fmt.Println(s4)     // [3 5 7 2 3 5 7 0 0]
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
    "fmt"
)

func main()  {
    // 如果切片容量足够 append()后不会生成新的切片
    s0 := make([]int, 10, 20)
    fmt.Println(s0, &s0[0], len(s0), cap(s0)) // [0 0 0 0 0 0 0 0 0 0] 0xc00007c000 10 20

    s1 := append(s0,2)
    fmt.Println(s1, &s1[0], len(s1), cap(s1)) // [0 0 0 0 0 0 0 0 0 0 2] 0xc00007c000 11 20

    s2 := append(s1, 3, 5, 7)
    fmt.Println(s2, &s2[0], len(s2), cap(s2)) // [0 0 0 0 0 0 0 0 0 0 2 3 5 7] 0xc00007c000 14 20

    // 此时扩容,从新开辟内存,复制原来数据
    s3 := append(s2, s0...)
    fmt.Println(s3, len(s3), cap(s3)) // [0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0] 24 40

    // 这里s4容量为37 是由于s3[3:6] 这里40-3=37容量
    // 此时容量足够,并没有扩容
    s4 := append(s3[3:6], s3[2:]...)
    fmt.Println(s4, len(s4), cap(s4)) // [0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0] 25 37

    fmt.Println(s0) // [0 0 0 0 0 0 0 0 0 0]
    fmt.Println(s1) // [0 0 0 0 0 0 0 0 0 0 2]
    fmt.Println(s2) // [0 0 0 0 0 0 0 0 0 0 2 3 5 7]
    fmt.Println(s3) // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0]
    fmt.Println(s4) // [0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0]
    s0[1] += 1
    fmt.Println(s0) // [0 1 0 0 0 0 0 0 0 0]
    fmt.Println(s1) // [0 1 0 0 0 0 0 0 0 0 2]
    fmt.Println(s2) // [0 1 0 0 0 0 0 0 0 0 2 3 5 7]
    fmt.Println(s3) // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0]
    fmt.Println(s4) // [0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0]
    s4[0] += 1
    fmt.Println(s3) // [0 0 0 1 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0]
    fmt.Println(s4) // [1 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0]
}

陈旧的切片

  1. 多个切片可以引用同一个底层相关数组。
  2. 某些情况下在一个切片中添加新的数据,在原有数组无法保持更多新的数据时,将导致分配一个新的数组。
  3. 而其他的切片还指向老的数组(和老的数据)。
  4. append()函数操作后,有没有生成新的切片需要看原有的切片的容量是否足够。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
    "fmt"
)

func main()  {
    s1 := []int{1,2,3}
    fmt.Println(len(s1), cap(s1), s1)   // 3 3 [1 2 3]

    // len = 3-1 = 2
    // cap = 3-1 = 2
    s2 := s1[1:]
    fmt.Println(len(s2), cap(s2), s2)   // 2 2 [2 3]

    for i := range s2{
        s2[i] += 20
    }

    // s2的修改会音响到数组数据,s1输出新数据
    fmt.Println(s1) // [1 22 23]
    fmt.Println(s2) // [22 23]

    // append s2容量为2 导致slice s2扩容,会生成新的底层数组
    s2 = append(s2, 4)

    for i := range s2{
        s2[i] += 10
    }

    // s1数据现在是来数据 s2扩容了,复制到新数组,他们底层数组已经不是同一个数组了
    fmt.Println(len(s1), cap(s1), s1)   // 3 3 [1 22 23]
    fmt.Println(len(s2), cap(s2), s2)   // 3 4 [32 33 14]
}