Go基础之切片Slice的概念

Slice的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}

fmt.Println("arr[2:6]", arr[2:6])
fmt.Println("arr[:6]", arr[:6])
fmt.Println("arr[2:]", arr[2:])
fmt.Println("arr[:]", arr[:])
}

执行结果:

1
2
3
4
arr[2:6] [c d e f]
arr[:6] [a b c d e f]
arr[2:] [c d e f g h]
arr[:] [a b c d e f g h]

注意: 左开右闭区间; Slice是对数组的View(视图)

Slice是引用类型

由于Slice是对底层数组的视图, 当Slice中的元素被修改后, 同样的, 底层数组中的元素也将被修改

看以下demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}

s := arr[2:6]
fmt.Println("原始s变量的值", s)
fmt.Println("原始数组的值", arr)
s[0] = "xx" // 改变视图中的第一个元素
fmt.Println("修改后变量的值", s)
fmt.Println("修改后数组的值", arr)

}

执行结果:

1
2
3
4
原始s变量的值 [c d e f]
原始数组的值 [a b c d e f g h]
修改后变量的值 [xx d e f]
修改后数组的值 [a b xx d e f g h]

Slice & Array

切片和数组的写法很相似, 这里把他们放到一起区分以下

1
2
3
4
5
6
7
func funcName(arr [5]int) // 接收一个长度为5的int类型的数组(值传递)

func funcName(arr *[5]int) // 接收一个长度5的int类型的数组的指针(引用传递)
// &arr 用&传递参数

func funcName(sli []int) // 接收一个Slice切片(引用传递)
// arr[:] 用[:]切片语法传递参数

Reslice

Slice可以在Slice的基础上再次Slice, 创建Slice的视图

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

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}

s := arr[2:6]
fmt.Println("数组arr的值", arr)
fmt.Println("s切片的值", s)

s2 := s[:3]
s3 := s[2:]
fmt.Println("s2, 基于s切片的view", s2)
fmt.Println("s3, 基于s切片的view", s3)
}

执行结果:

1
2
3
4
数组arr的值 [a b c d e f g h]
s切片的值 [c d e f]
s2, 基于s切片的view [c d e]
s3, 基于s切片的view [e f]

上面的Demo中, 即使有多个Slice, 但其本质, 还是view了同一个Array

Slice的扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}

s := arr[2:6]
fmt.Println("数组arr的值", arr)
fmt.Println("s切片是对arr的view, 值为", s)

ss := s[3:5]
fmt.Println("ss切片是对s切片的view, 值为", ss)
}

执行结果:

1
2
3
数组arr的值 [a b c d e f g h]
s切片是对arr的view, 值为 [c d e f]
ss切片是对s切片的view, 值为 [f g]

上面的Demo中, 出现了一个很有意思的问题, 就是ss切片, 根据上面介绍的理论, ss切片是对s切片的view, 也就是Reslice, 但是s切片中只有4个值, 元素索引为0-3, 而ss对其进行reslice后, 却要看3-5的索引, 如果是普通Array的话, 肯定会报索引越界的异常, 但是上面Demo我们看出, 代码并没有抛出任何异常, 不仅还取到了值, 还取到了s切片中不存在的值

1
2
3
4
5
6
7
8
9
"a", "b", "c", "d", "e", "f", "g", "h"  <== Array
0 1 2 3 4 5 6 7 <== Array Index


"c", "d", "e", "f" <== Slice arr[2:6]
0 1 2 3 4 5 <== Sline Index

"f", "g" <== Reslice sli[3:5]
0 1 2 <== Reslice Index

Go语言Slice的扩展特性: 在Slice后, Array后面的元素依然可以被Slice感知, 但是Slice后面这些元素不能直接被取出, 直接取值会报索引越界的错误. 但是当Slice被Reslice时, Slice后面隐藏的值是可以在Reslice中重见天日的

Slice的实现

假设: - - - -为Slice对Array的视图

1
2
3
4
5
Array:  `+ + + + - - - - ~ ~ ~ ~`
↑ | |
ptr | |
|-len-| |
|-----cap-----|

Slice内部有三个元素:

  • ptr: 指向了Slice开头的元素. 依据上图, 也就是指向了第一个-
  • len: 保存了Slice的长度, 使用 sli[x] 这种方式取值, 只能取到sli[len-1]. 依据上图, len保存了-的长度, 4
  • cap: cap保存了从ptr开始到Array数组结束的长度. 依据上图, 也就是说cap保存了从第一个-开始, 到最后一个~为止的长度, 8

所以Go知道, Slice在扩展的时候, 只要不超过cap的长度, 就是可以取到的

注意: Slice可以向后扩展, 但是不能向前扩展, 图示中+部分的值在Slice中永远不会被取出来, 除非重新使用Slice对Array进行view操作`

sli[x]取值不可以超越len(sli); 向后扩展, 不可以超越底层Array cap(sli)

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

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}
fmt.Println("Array数组的cap长度为: ", cap(arr)) // 结果为: 8

s := arr[2:6]
fmt.Println("数组arr的值", arr)
fmt.Println("s切片是对arr的view, 值为", s)
fmt.Println("s切片的len长度和cap长度分别为: ", len(s), cap(s))

ss := s[3:5]
fmt.Println("ss切片是对s切片的view, 值为", ss)
fmt.Println("ss切片的len长度和cap长度分别为: ", len(ss), cap(ss))

}

执行结果:

1
2
3
4
5
6
Array数组的cap长度为:  8
数组arr的值 [a b c d e f g h]
s切片是对arr的view, 值为 [c d e f]
s切片的len长度和cap长度分别为: 4 6
ss切片是对s切片的view, 值为 [f g]
ss切片的len长度和cap长度分别为: 2 3