Golang slice的几种用法

简介: 最近生活风平浪静。工作时间还是比较忙,业余时间写文章的频率不高,本来想把关于slice的都写完,但后来发现太多了,于是打算拆成三篇来写。前段时间花了大量的时间在弄小程序,本来都发布成功了,但后来某一次小程序升级说涉及交友服务,需要改类目,而交友类目需要电信增值服务的证,这个证比较难搞,所以可能后面打算终止这个小程序了,过段时间可能会把代码开源吧。

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初始化其实是比较推荐的一种方式。需要传入lencap或者只传入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反序列化等异常,所以其实个人很少使用第一种方式。而第三种方式,也是测试的时候可能用得多一点。

目录
相关文章
|
5月前
|
Go 索引
Go 语言中同一 slice 上的切片其底层数组是否是同一个
Go 语言中同一 slice 上的切片其底层数组是否是同一个
50 0
|
5月前
|
存储 Go
Golang底层原理剖析之slice类型与扩容机制
Golang底层原理剖析之slice类型与扩容机制
69 0
|
5月前
|
存储 安全 Java
Go 基础数据结构的底层原理(slice,channel,map)
Go 基础数据结构的底层原理(slice,channel,map)
92 0
|
2天前
|
存储 缓存 测试技术
golang slice相关常见的性能优化手段
【10月更文挑战第23天】本文介绍了 Go 语言中切片使用的四个优化技巧:预分配容量、减少中间切片的创建、利用切片的复用特性和合理使用 `copy` 函数。通过这些方法,可以有效提高程序性能,减少不必要的内存分配和数据复制操作。每个技巧都附有详细的原理说明和代码示例,帮助开发者更好地理解和应用。
|
1月前
|
Go
Golang语言之切片(slice)快速入门篇
这篇文章是关于Go语言中切片(slice)的快速入门教程,详细介绍了切片的概念、定义方式、遍历、扩容机制、使用注意事项以及相关练习题。
28 5
|
2月前
|
人工智能 Go
go slice 扩容实现
go slice 扩容实现
43 3
|
2月前
|
人工智能 编译器 Go
go slice 基本用法
go slice 基本用法
45 1
|
2月前
|
存储 算法 Go
|
2月前
|
存储 人工智能 Go
golang 反射基本原理及用法
golang 反射基本原理及用法
25 0
|
4月前
|
Java Go
Go 中 slice 的内存管理机制
Go 中 slice 的内存管理机制