Y说
最近生活风平浪静。工作时间还是比较忙,业余时间写文章的频率不高,本来想把关于slice的都写完,但后来发现太多了,于是打算拆成三篇来写。
前段时间花了大量的时间在弄小程序,本来都发布成功了,但后来某一次小程序升级说涉及交友服务,需要改类目,而交友类目需要电信增值服务的证,这个证比较难搞,所以可能后面打算终止这个小程序了,过段时间可能会把代码开源吧。
几种初始化
某天,小Y在写代码时,发现自己的一个Golang里对slice的声明及初始化有很多种不同的写法。
大概是这几种:
// slice var s []int // cap可以省略,但如果传值,必须大于等于len s := make([]int, len, cap) s := make([]int, len) s := []int{} s := []int{1, 2, 3} // 这里顺便说下array,以下为array var a [len]int a := [len]int{} a := [2]int{0, 1}
小Y比较奇怪,这几种写法有什么区别呢?于是网上查询了一些资料,总结了一下。
只声明不初始化
首先是第一种写法,只声明不初始化。这种写法在其它语言中也很常见,比如Java中的:
A a;
在Golang中,slice其实本质上是一个指针。所以只声明不初始化时,它的值是nil
。基于这个规则,以下代码会有这个表现:
var s []int // 输出 true fmt.Println(s == nil) // 输出 [] fmt.Println(s) marshal, err := json.Marshal(s) // 输出 null <nil> fmt.Println(string(marshal), err)
这里需要注意的是json序列化一个nil
变量后,会输出字符串null
。反序列化时,也会反序列化为nil
。
这个有时候可能会有坑,比如给前端返回json的时候,本来约定了数组对象哪怕没值,也应该返回一个空列表[]
,这样前端好处理。但因为后端是这么写的,而后面的逻辑也因为去初始化,就会返回一个null
给前端。所以小Y个人不是很推荐用这种方式。
用make初始化
用make初始化其实是比较推荐的一种方式。需要传入len
和cap
或者只传入len
。slice底层是一个带有长度和当前位置指针的可扩容数组。其中cap代表整个数组当前的长度,扩容后会增加。而len代表的是有值的长度。
在make的时候输入len,会用默认值初始化,可以看以下代码:
s := make([]int, 2, 5) // [0 0] fmt.Println(s) // 2 fmt.Println(len(s)) // 5 fmt.Println(cap(s))
什么情况下需要输出cap
呢?很明显,当你明确知道你的slice的长度时,推荐用这个,因为不用频繁扩容。关于扩容的东西后面会单写一篇文章。
->
为什么这种情况不用array呢?因为array是值对象,每次传递都要copy,会有内存浪费,所以不推荐。
<-
这里也需要注意的是如果传了len
参数不为0,那后续使用append
方法,就是从len后面开始加值了。所以传了len一般是后续配合直接指定index赋值来使用的。
s := make([]int, 2, 5) s = append(s, 1) // [0 0 1] fmt.Println(s)
字面量声明
字面量声明就是这种写法了,go会默认根据你声明的元素来设置好len和cap。
s := []int{} // [] fmt.Println(s) // 0 fmt.Println(len(s)) // 0 fmt.Println(cap(s)) s = []int{1, 2, 3} // [1 2 3] fmt.Println(s) // 3 fmt.Println(len(s)) // 3 fmt.Println(cap(s))
这里第一行的写法,Goland编辑器也会给出warning提示:Empty slice declaration using a literal。它会推荐你改成上面第一种只声明,不初始化的写法。这两种写法都有其各自的优缺点,一个是空数组,一个是nil。知乎上有同学因为这个推荐遇到了前面提到的json序列化为null的问题,所以觉得这是一个Goland的bug,原文标题叫《Go 语言 IDE GoLand 的 BUG》。
小Y去google了一下,四年前真的有人去给Goland提了这个bug,但Goland的人并不认为它是一个bug。小Y大概看了一下,这个问题至今其实并没有解决,负责解决这个问题的人只是建议把这个警告关掉。链接在这:youtrack.jetbrains.com/issue/GO-53…
总结
总共有三种声明及初始化方法,不同的声明方式适用于不同的场景。小Y个人用第二种比较多,其实哪怕是空数组,声明一下也比nil不会多用太多的内存,但能防止json反序列化等异常,所以其实个人很少使用第一种方式。而第三种方式,也是测试的时候可能用得多一点。