脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 简单聊一聊Go语言中的数组和切片

简单聊一聊Go语言中的数组和切片

2021-08-16 00:41架构精进之路 Golang

数组和切片由于语法十分相似,在使用中容易混淆,要认真区分,下面这篇文章主要给大家介绍了关于Go语言中数组和切片的相关资料,需要的朋友可以参考下

1. 数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在 Go 语言中很少直接使用数组。和数组对应的类型是 Slice(切片),它是可以增长和收缩的动态序列,slice 功能也更灵活。

数组的每个元素可以通过索引下标来访问,索引下标的范围是从 0 开始到数组长度减 1 的位置。内置的 len 函数将返回数组中元素的个数。

  1. var a [3]int // array of 3 integers
  2. fmt.Println(a[0]) // print the first element
  3. fmt.Println(a[len(a)-1]) // print the last element, a[2]

默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是 0。

  1. var q [3]int = [3]int{1, 2, 3}
  2. var r [3]int = [3]int{1, 2}
  3. fmt.Println(r[2]) // "0"

如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算。因此,上面 q 数组的定义可以简化为:

  1. q := [...]int{1, 2, 3}
  2. fmt.Printf("%T\n", q) // "[3]int"

数组的长度是数组类型的一个组成部分,因此[3]int 和[4]int 是两种不同的数组类型。

数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。

  1. q := [3]int{1, 2, 3}
  2. q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int

如果一个数组的元素类型是可以相互比较的,那么数组类型也是可以相互比较的,这时候我们可以直接通过==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。不相等比较运算符!=遵循同样的规则。

  1. a := [2]int{1, 2}
  2. b := [...]int{1, 2}
  3. c := [2]int{1, 3}
  4. fmt.Println(a == b, a == c, b == c) // "true false false"
  5. d := [3]int{1, 2}
  6. fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int

2. 切片(Slice)

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个 slice 类型一般写作[]T,其中 T 代表 slice 中元素的类型;slice 的语法和数组很像,只是没有固定长度而已。

一个 slice 是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且 slice 的底层确实引用一个数组对象。

一个 slice 由三个部分构成:指针、长度和容量。

  • 指针指向第一个 slice 元素对应的底层数组元素的地址,要注意的是 slice 的第一个元素并不一定就是数组的第一个元素。
  • 长度对应 slice 中元素的数目;
  • 长度不能超过容量,容量一般是从 slice 的开始位置到底层数据的结尾位置。内置的 len 和 cap 函数分别返回 slice 的长度和容量。

表示一年中每个月份名字的字符串数组,还有重叠引用了该数组的两个 slice。数组这样定义:

  1. months := [...]string{1: "January", /* ... */, 12: "December"}

因此一月份是 months[1],十二月份是 months[12]。

通常,数组的第一个元素从索引 0 开始,但是月份一般是从 1 开始的,因此我们声明数组时直接跳过第 0 个元素,第 0 个元素会被自动初始化为空字符串。

slice 的切片操作 s[i:j],其中 0 ≤ i≤ j≤ cap(s),用于创建一个新的 slice,引用 s 的从第 i 个元素开始到第 j-1 个元素的子序列。新的 slice 将只有 j-i 个元素。如果 i 位置的索引被省略的话将使用 0 代替,如果 j 位置的索引被省略的话将使用 len(s)代替。因此,months[1:13]切片操作将引用全部有效的月份,和 months[1:]操作等价;months[:]切片操作则是引用整个数组。让我们分别定义表示第二季度和北方夏天月份的 slice,它们有重叠部分:

简单聊一聊Go语言中的数组和切片

  1. Q2 := months[4:7]
  2. summer := months[6:9]
  3. fmt.Println(Q2) // ["April" "May" "June"]
  4. fmt.Println(summer) // ["June" "July" "August"]

两个 slice 都包含了六月份。

append 函数

append 函数用于向 slice 追加元素:

  1. var runes []rune
  2. for _, r := range "Hello, 世界" {
  3. runes = append(runes, r)
  4. }
  5. fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"

为了提高内存使用效率,新分配的数组一般略大于保存 x 和 y 所需要的最低大小。通过在每次扩展数组时直接将长度翻倍从而避免了多次内存分配,也确保了添加单个元素操作的平均时间是一个常数时间。这个程序演示了效果:

  1. func main() {
  2. var x, y []int
  3. for i := 0; i < 10; i++ {
  4. y = appendInt(x, i)
  5. fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y)
  6. x = y
  7. }
  8. }
  9.  
  10. //每一次容量的变化都会导致重新分配内存和copy操作:
  11. 0 cap=1 [0]
  12. 1 cap=2 [0 1]
  13. 2 cap=4 [0 1 2]
  14. 3 cap=4 [0 1 2 3]
  15. 4 cap=8 [0 1 2 3 4]
  16. 5 cap=8 [0 1 2 3 4 5]
  17. 6 cap=8 [0 1 2 3 4 5 6]
  18. 7 cap=8 [0 1 2 3 4 5 6 7]
  19. 8 cap=16 [0 1 2 3 4 5 6 7 8]
  20. 9 cap=16 [0 1 2 3 4 5 6 7 8 9]

让我们仔细查看 i=3 次的迭代。当时 x 包含了[0 1 2]三个元素,但是容量是 4,因此可以简单将新的元素添加到末尾,不需要新的内存分配。然后新的 y 的长度和容量都是 4,并且和 x 引用着相同的底层数组,如图 4.2 所示。

简单聊一聊Go语言中的数组和切片

在下一次迭代时 i=4,现在没有新的空余的空间了,因此 appendInt 函数分配一个容量为 8 的底层数组,将 x 的 4 个元素[0 1 2 3]复制到新空间的开头,然后添加新的元素 i,新元素的值是 4。新的 y 的长度是 5,容量是 8;后面有 3 个空闲的位置,三次迭代都不需要分配新的空间。当前迭代中,y 和 x 是对应不同底层数组的 view。这次操作如图 4.3 所示。

简单聊一聊Go语言中的数组和切片

内置的 append 函数可能使用比 appendInt 更复杂的内存扩展策略。

因此,通常我们并不知道 append 调用是否导致了内存的重新分配,因此我们也不能确认新的 slice 和原始的 slice 是否引用的是相同的底层数组空间。

同样,我们不能确认在原先的 slice 上的操作是否会影响到新的 slice。

总结

到此这篇关于Go语言中数组和切片的文章就介绍到这了,更多相关Go语言数组和切片内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://mp.weixin.qq.com/s/oWlZ5CHHG6wcFbAIDgi_RA

延伸 · 阅读

精彩推荐
  • GolangGo语言基础单元测试与性能测试示例详解

    Go语言基础单元测试与性能测试示例详解

    这篇文章主要为大家介绍了Go语言基础单元测试与性能测试示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助祝大家多多进步...

    枫少文7812021-12-05
  • Golanggo语言获取系统盘符的方法

    go语言获取系统盘符的方法

    这篇文章主要介绍了go语言获取系统盘符的方法,涉及Go语言调用winapi获取系统硬件信息的技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    无尽海3862020-04-24
  • GolangGo语言实现自动填写古诗词实例代码

    Go语言实现自动填写古诗词实例代码

    这篇文章主要给大家介绍了关于Go语言实现自动填写古诗词的相关资料,这是最近在项目中遇到的一个需求,文中通过示例代码介绍的非常详细,需要的朋...

    FengY5862020-05-14
  • GolangGolang实现四种负载均衡的算法(随机,轮询等)

    Golang实现四种负载均衡的算法(随机,轮询等)

    本文介绍了示例介绍了Golang 负载均衡的四种实现,主要包括了随机,轮询,加权轮询负载,一致性hash,感兴趣的小伙伴们可以参考一下...

    Gundy_8442021-08-09
  • GolangGO语言字符串处理Strings包的函数使用示例讲解

    GO语言字符串处理Strings包的函数使用示例讲解

    这篇文章主要为大家介绍了GO语言字符串处理Strings包的函数使用示例讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加...

    Jeff的技术栈6882022-04-14
  • Golang深入浅析Go中三个点(...)用法

    深入浅析Go中三个点(...)用法

    这篇文章主要介绍了深入浅析Go中三个点(...)用法,需要的朋友可以参考下...

    踏雪无痕SS6472021-11-17
  • GolangGo语言range关键字循环时的坑

    Go语言range关键字循环时的坑

    今天小编就为大家分享一篇关于Go语言range关键字循环时的坑,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来...

    benben_20154202020-05-23
  • GolangGolang 语言极简类型转换库cast的使用详解

    Golang 语言极简类型转换库cast的使用详解

    本文我们通过 cast.ToString() 函数的使用,简单介绍了cast 的使用方法,除此之外,它还支持很多其他类型,在这没有多多介绍,对Golang 类型转换库 cast相关知...

    Golang语言开发栈6112021-12-02