数据结构

slice是一个动态数组,切片中的元素存放在一块内存地址连续的区域,使用索引可以快速检索到指定位置的元素,以下的数据结构也称为Slice Header

1
2
3
4
5
6
7
8
9
//package:Go SDK 1.22.0/src/runtime/slice.go
type slice struct {
// 指向起点的地址
array unsafe.Pointer
// 切片长度
len int
// 切片容量
cap int
}
  • array:指向了内存空间地址的起始位置,slice数据存放在连续的内存空间中,根据索引 index,在起点的基础上进行地址偏移,从而定位到目标元素。
  • len:slice的长度,指的是逻辑意义上,slice 中实际存放了多少个元素。
  • cap:切片的容量,指的是物理意义上为slice分配了足够用于存放多少个元素的空间,cap永远大于等于len。

slice是引用传递还是值传递

引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

  • 示例1:
1
2
3
4
5
6
7
8
9
10
func Test_slice(t *testing.T){
s := []int{1, 2, 3}
changeSlice(s)
// [1,2,3] -> [-1, 2, 3]
}


func changeSlice(s []int){
s[0] = -1
}

在go语言中,只有值传递。在该例中,slice作为参数时,似乎变成了其他语言中引用传递的效果。

原因在于:方法间传递切片时,会对 slice header 实例本身进行一次值拷贝,然后将 slice header 的副本传递到局部方法中。这个 slice header 副本中的 array 和原 slice 指向同一片内存空间,所以效果为引用传递(但只会修改原有的数组长度的元素)。

  • 示例2:
1
2
3
4
5
6
7
8
9
10
11
12
func Test_slice(t *testing.T){
s := []int{1,2,3}
changeSlice(s)
fmt.Println(s[3])
//数组越界异常,s中的切片长度未发生变化
}

func changeSlice(s []int){
s = append(s, 4)
fmt.Println(s[3])
//此时正常打印s[3]=4
}

综上,slice作为参数在函数方法中时,由于传入了array的地址指针,对应的切片的长度内的元素被修改时,原数组会被修改,若发生扩容则不会影响原数组。

初始化

  • 声明但不初始化
1
var s []int 

s初始化时是一个空指针 nil,并没有完成实际的内存分配操作。

  • 基于 make 进行初始化

    • 指定len
    1
    s := make([]int,8)

    将切片的长度 len 和 容量 cap 同时设置为 8,且初始元素均为0。

    • 指定len和cap
    1
    s := make([]int,8, 16)

    在切片中设置8个元素,初始元素均为0,需要保证 cap >= len。切访问[len, cap)的元素时会越界。

  • 初始化连带赋值

    1
    s := []int{2,3,4}

slice 长度 len 和容量 cap 均设置为 3,同时完成对这 3 个元素赋值。

二维Slice

声明m行n列

1
2
3
4
5
6
7
// 创建一个切片的切片,每个元素表示一行
array := make([][]int, m)

// 遍历每一行,为每一行创建一个长度为n的切片
for i := range array {
array[i] = make([]int, n)
}

参考

https://mp.weixin.qq.com/s/uNajVcWr4mZpof1eNemfmQ