Golang 基础 Part 1: 数组和切片

文章讨论了关于数组和切片在 Golang 中的基础知识(切片的本质、扩容以及值传递问题)、怎样在 Golang 中应用他们以及使用切片需要注意的地方。


1. 切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息

  • 底层数组的指针
  • 切片的长度(len)
  • 切片的容量(cap)

举个例子,现在有一个数组 a := [8]int {0,1,2,3,4,5,6,7},切片 s1 := a[:5],相应示意图如下

Figure 1.切片 s1 和底层数组 a

切片 s2 := a[3:6],相应示意图如下:

Figure 2.切片 s2 和底层数组 a

2. 切片的扩容

其中:

  • T:切片的元素类型
  • size:切片中元素的数量
  • cap:切片的容量

举例

1// make()函数创建切片
2fmt.Println()
3var slices = make([]int, 4, 8)
4//[0 0 0 0]
5fmt.Println(slices)
6// 长度:4, 容量8
7fmt.Printf("长度:%d, 容量%d", len(slices), cap(slices))

需要注意的是,golang 中没办法通过下标来给切片扩容,如果需要扩容,需要用到 append

1slices2 := []int{1,2,3,4}
2slices2 = append(slices2, 5)
3fmt.Println(slices2)
4// 输出结果 [1 2 3 4 5]

同时切片还可以将两个切片进行合并

1// 合并切片
2slices3 := []int{6,7,8}
3slices2 = append(slices2, slices3...)
4fmt.Println(slices2)
5// 输出结果  [1 2 3 4 5 6 7 8]

需要注意的是,切片会有一个扩容操作,当元素存放不下的时候,会将原来的容量扩大两倍。

3. 切片的传递问题

切片本身传递给函数形参时是引用传递,但 append 后,切片长度变化时会被重新分配内存,而原来的切片还是指向原来地址,致使与初始状况传进来的地址不一样,要想对其值有改变操作,需使用指针类型操作。

我们来看一道 leetcode 78:

 1
 2package main
 3
 4import "fmt"
 5
 6func helper(nums []int, res *[][]int, tmp []int, level int) {
 7	if len(tmp) <= len(nums) {
 8		//长度一样的tmp用的是同一个地址,故可能会存在覆盖值得情况,
 9		// 长度不一样时重新开辟空间,将已有得元素复制进去
10		//*res = append(*res, tmp) 如此处,最终长度为1的tmp会被最后3这个元素全部覆盖
11		//以下相当于每次重新申请内存,使其指向的地址不一样,解决了最后地址一样的元素值被覆盖的状态状态
12		var a []int
13		a = append(a, tmp[:] ...)
14		//res = append(res, a) 如果此处不是指针引用传递,在append后,res重新分配内存,与之前传进来的res地址不一样,最终res仍为空值
15		*res = append(*res, a)
16	}
17	//fmt.Println(*res, "--->", tmp)
18	for i := level; i < len(nums); i ++ {
19		tmp = append(tmp, nums[i])
20		helper(nums, res, tmp, i + 1)
21		tmp = tmp[:len(tmp) - 1] //相当于删除tmp末位的最后一个元素
22	}
23}
24
25func subsets(nums []int) [][]int {
26	if len(nums) == 0 {
27		return nil
28	}
29	var res [][]int
30	var tmp []int
31	helper(nums, &res, tmp, 0)
32	return res
33}
34
35
36func main()  {
37	pre := []int{1, 2, 3}
38	fmt.Println(subsets(pre))
39}
40//错误结果:[[] [3] [1 3] [1 2 3] [1 3] [3] [2 3] [3]], 可以看出,长度为1的切片都被3覆盖了,这由于它们的地址不一样
41//正确输出:[[] [1] [1 2] [1 2 3] [1 3] [2] [2 3] [3]], 这是因为每次都为a分配内存,其地址都与之前的不一样,故最终的值没有被覆盖

翻译: