以前写过一篇关于 golang 中 slice & array 的介绍,但是我觉得写的太复杂了,很多特性是日常开发中用不到的,今天就来精简一下
本文就挑几个重要的部分来讲
1. Slice 的本质
slice 是一种特殊的数据结构,如下:
1
2
3
4
5type slice struct {
array unsafe.Pointer
len int
cap int
}所以一般声明slice之后,使用
unsafe.Sizeof()
函数打印出来的大小应该是 24 (在64位机器上)1
2slice1 := []int{1, 2, 3, 4, 5}
fmt.Println("size of []int is : ", unsafe.Sizeof(slice1)) // 24
2. Array 不是指针
- Go 语言的数组不同于 C/C++ 语言或者其他语言的数组,C/C++ 语言的数组变量是指向数组第一个元素的指针;
- 而
Go 语言的数组是一个值
,Go 语言中的数组是值类型,一个数组变量就表示着整个数组。 - 意味着 Go 语言的数组在传递的时候,传递的是原数组的拷贝。
- 如果有必要可以传指针
*[4]int
,不过感觉这样用的比较少,毕竟大多是用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
25func modifyArr(arr1 [4]int) {
arr1[0] = 23
fmt.Println("in modify arr func, arr = ", arr1) // in modify arr func, arr = [23 2 3 4]
}
func main() {
arr1 := [4]int{1, 2, 3, 4}
fmt.Println("before modify, arr = ",arr1) // before modify, arr = [1 2 3 4]
modifyArr(arr1)
fmt.Println("after modify, arr = ",arr1) // after modify, arr = [1 2 3 4]
}
```
## 3. “切片”现有切片
- 名字起得好,观众少不了 😂
- 通过“切片”`现有切片或数组 (没错,数组也可以切切切)`来形成切片。 通过指定半开放范围来完成切片,其中两个索引用冒号分隔。 例如,表达式b [1:4]创建包括b的元素1到3的切片(得到的切片的索引将是0到2)。
- 注意通过👆上面方法切出来的 `子切片` 和 `原切片` 共享同一个内存空间哦,下面仔细说说
1. 下面的例子中,`b` 的 `len` 和 `cap` 都是6,`b1` 的 `len` 和 `cap` 分别是 2 和 5
2. 这样就有一个特殊的情况:如果向 `b1` append 一个数据,由于 `cap(b1)=5`,这个时候是不会发生扩容的,因此 `b` 也会收到影响
3. 像上面这样切出来的 `子切片` 和 `原切片`,任意一个发生扩容的话,就不会互相影响了
- 感觉上面 `2` 中说的情况可以出一个面试题 😂
```go
b := []byte{'g', 'o', 'l', 'a', 'n', 'g'} // len = 6, cap = 6
b1 = b[1:4] // sharing the same storage as b, len = 2, cap = 5
copy
- 如果不想
子切片
和原切片
互相作用的话,就直接使用copy
即可1
copy( destSlice, srcSlice []T) int
1 | slice1 := []int{1, 2, 3, 4, 5} |
3. 切片的 nil 值
- 感觉这一块也是知道就行,实际生产过程中涉及不到,面试可能会问(就是那种比较变态的面试官😂)
- slice 有三种状态:零切片、空切片、nil切片。
零切片
- 就是其元素值都是元素类型的零值的切片,如下所示:
1
2s := make([]int, 9, 10)
fmt.Println(s) // 9 10 [0 0 0 0 0 0 0 0 0]
空切片
- 就是数组指针不为nil,且 slice 的长度为0。
- 空切片可以理解就是切片的长度为0,就是说 slice 没有元素。
- 社区大多数解释空切片为引用底层数组为 zerobase 这个特殊的指针。但是从操作上看空切片所有的表现就是切片长度为0,如果容量也为零底层数组就会指向 zerobase ,这样就不会发生内存分配, 如果容量不会零就会指向底层数据,会有内存分配。
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
27var s []int
s1 := make([]int, 0)
s2 := make([]int, 0, 0)
s3 := make([]int, 0, 100)
arr := [10]int{}
s4 := arr[:0]
fmt.Println(*(*reflect.SliceHeader)(unsafe.Pointer(&s))) // {0 0 0}
fmt.Println(s) // []
fmt.Println(s == nil) // true
fmt.Println(*(*reflect.SliceHeader)(unsafe.Pointer(&s1))) // {18438904 0 0}
fmt.Println(s1) // []
fmt.Println(s1 == nil) // false
fmt.Println(*(*reflect.SliceHeader)(unsafe.Pointer(&s2))) // {18438904 0 0}
fmt.Println(s2) // []
fmt.Println(s2 == nil) // false
fmt.Println(*(*reflect.SliceHeader)(unsafe.Pointer(&s3))) // {824634277888 0 100}
fmt.Println(s3) // []
fmt.Println(s3 == nil) // false
fmt.Println(*(*reflect.SliceHeader)(unsafe.Pointer(&s4))) // {824633835680 0 10}
fmt.Println(s4) // []
fmt.Println(s4 == nil) // false - 以上示例中除了 s 其它的 slice 都是空切片,打印出来全部都是 [],s 是nil切片下一小节说。
- 要注意 s1 和 s2 的长度和容量都为0,且引用数组指针都是 18349960, 这点太重要了,因为他们都指向 zerobase 这个特殊的指针,是没有内存分配的。
nil切片
- 就是引用底层数组指针为 nil 的 slice。
- 什么是nil切片,这个名字说明nil切片没有引用任何底层数组,底层数组的地址为nil就是nil切片。
- 上一小节中的 s 就是一个nil切片,它的底层数组指针为0,代表是一个 nil 指针。
总结
- 操作上零切片、空切片和正常的切片都没有任何区别,但是nil切片会多两个特性,一个nil切片等于 nil 值,且进行 json 序列化时其值为 null,nil切片还可以通过赋值为 nil 获得。