专栏首页每周一脱topic彻底搞懂golang中的数组和切片slice
原创

彻底搞懂golang中的数组和切片slice

切片slice是golang中的一种非常重要和关键的数据类型,被大量地使用。本文总结数组arrays的使用,切片slice的使用以及它的底层是如何实现的。在了解底层之后,以后slice的使用上会更胸有成竹。

一、数组arrays

golang中的切片slice其实是数组arrays的一种抽象,所以要搞懂切片slice,就要先弄明白数组arrays。

数组arrays很好理解,就是一个固定长度、固定元素类型的数组。在go中数组类型包含两层意思:长度和元素类型。因此数组[2]int和数组[3]int,这两个是不同类型。虽然元素类型相同,但是长度不同。访问某个元素,可以使用下标的方式,array[n]会访问数组第n个元素,从0开始。

var a [2]int 
var b [3]int 
b[0] = 123
a = b

因为变量a和b是不同类型,所以上面的赋值语句在编译时就报错了:

./main.go:23:4: cannot use b (type [3]int) as type [2]int in assignment
Compilation finished with exit code 2

数组不需要主动的进行初始化,相对应的0值会在声明后被赋值。在内存中[2]int就是线性排列的2个int值,所以数组访问是O(1)的时间复杂度,速度极快。

var a [2]int    // [0 0] 
var c [3]string    // [3个空字符串] 

还有一点要搞清楚,数组arrays在go中是值,而不是指针。不像c或者java,数组是指向底层数组第1个元素的指针。因此在go中你赋值或者传递数组arrays,都会对整个数组内容进行一份复制。所以为了避免无谓的复制,我们会传递数组的指针,而不是数组。

初始化声明一般有两种。e这种声明不用填长度,编译器会帮你计算这个数。

d := [2]int{11, 22} 
e := [...]int{11, 22} 
fmt.Println(d == e)  // 输出 true

二、切片slices

2.1 使用

由于数组arays的特性,在go代码中适用场景有限,而切片slices会用得非常多。切片slices基于数组,但提供了更高的灵活性。

[]T就是一个切片slices,和声明数组的区别就是没有指定长度。可以使用范围来截取切片,例如s1[x:y],会将s1中x位的元素至y-1位的元素截取。

d := [2]int{11, 22} // 数组类型 
s1 := []int{11, 22, 33} // 切片类型 
fmt.Println(s1[:2])  // [11 22]
fmt.Println(s1[1:2]) // [22]

创建一个切片会经常用到内建函数make,它的函数声明如下。我们可以看到,make可接受3个参数,第1个是切片,第2个是切片的长度,第3个是可选的容量大小。不指定cap容量的话,默认会和长度len相同。

func make([]T, len, cap) []T

2.2 slicing的底层细节

s := make([]int, 5),s底层即为上图的数据结构。ptr是一个指针,指向底层对应的数组。len是切片的长度5,cap是底层数组的容量5。

当我们执行下面语句时 :

s2 := s[1:3]

做slicing的时候,go会新建一个slice值s2,而底层的数据是不动的。s2如上图深蓝色,通过更改指针、长度和容量来进行slicing。这也就是为什么slicing的性能非常高的原因。

一个slice不能越过cap进行操作,这个我们从底层很容易理解,因为就相当于越过底层数组的上界进行非法访问了。

2.3 切片增长

一般我们会使用内部函数append来往切片slice后动态追加元素,当cap不够时,如果reslice后可以放下,那么它会reslice。例如上面的s2,底层的数组足以再追加2个元素。如果不行,那么它会new一个新的底层数组,大小为之前cap的两倍,并将之前的元素复制进去。下面测试了下:

s := make([]int, 5) 
s = append(s, 6, 7) 
fmt.Println(len(s), cap(s)) // 输出7 10 
s = append(s, 8, 9, 10, 11) 
fmt.Println(len(s), cap(s))//输出11 20 

如果要追加一个slice到另一个slice的话,这样:

s5 := make([]int, 5) 
s6 := []int{11, 22, 33, 44, 55, 66} 
s := append(s5, s6...) 
fmt.Println(s, len(s), cap(s))//输出[0 0 0 0 0 11 22 33 44 55 66] 11 12 

2.4 注意内存

在这样的场景下注意:如果我们只用到一个slice的一小部分,那么底层的整个数组也将继续保存在内存当中。当这个底层数组很大,或者这样的场景很多时,可能会造成内存急剧增加,造成崩溃。

所以在这样的场景下,我们可以将需要的分片复制到一个新的slice中去,减少内存的占用。例如一个很大的切片data里,我们需要的数据是data[m:n],那么我们创建一个新的slice变量r,将数据复制到r中返回。

mydata := data[m:n]
r := make([]int, len(mydata))
copy(r, mydata) 
return r 

三、总结

通过了解数组array和切片slice的使用和底层原理,可以更透彻的理解他们的使用场景,里面有什么坑。这对我们平时编写程序是极有裨益的,后面我会继续深入,有时间再更。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Golang泛型编程初体验

    序言 众所周知,Golang中不支持类似C++/Java中的标记式泛型,所以对于常用算法,比如冒泡排序算法,有些同学容易写出逻辑上重复的代码,即整型是第一套代码...

    李海彬
  • Golang泛型编程初体验

    序言 众所周知,Golang中不支持类似C++/Java中的标记式泛型,所以对于常用算法,比如冒泡排序算法,有些同学容易写出逻辑上重复的代码,即整型是第一套代码...

    李海彬
  • golang切片内存应用技巧

    在 Go 语言中切片是使用非常频繁的一种聚合类型,它代表变长的序列,底层引用一个数组对象。一个切片由三个部分构成:指针、长度和容量。指针指向该切片自己第一个元素...

    KevinYan
  • Golang 笔记(三):一种理解 Slice 的模型

    Golang 中 slice 极似其他语言中数组,但又有诸多不同,因此容易使初学者产生一些误解,并在使用时不易察觉地掉进各种坑中。本篇小文,首先从 Go 语言官...

    青藤木鸟
  • Golang slice 增删改查

    slice名为切片,是Go中的可变长数组,是对底层数组的封装和引用。切片指向一个底层数组,并且包含长度和容量信息。未初始化切片的值为 nil。作用于切片的内建函...

    Dabelv
  • [Introduction]Go特殊的引用类型:值传递/指针传递/引用传递

    变量名本身并没有作用,只相当于代号利于程序员编程,引用作为别名本质上还是指向同一个内存地址。指针本质上占用一小段内存空间

    Wzy_CC
  • golang新手容易犯的3个错误

    这是因为代码中只是声明了map的类型,却没有为map创建底层数组,此时的map实际上在内存中还不存在,即nil map,可以运行下面的代码进行验证:

    李海彬
  • 深度解密Go语言之Slice

    Go 语言的 slice 很好用,不过也有一些坑。slice 是 Go 语言一个很重要的数据结构。网上已经有很多文章写过了,似乎没必要再写。但是每个人看问题的视...

    梦醒人间
  • 深度解密Go语言之Slice

    Go 语言的 slice 很好用,不过也有一些坑。slice 是 Go 语言一个很重要的数据结构。网上已经有很多文章写过了,似乎没必要再写。但是每个人看问题的视...

    李海彬
  • Golang 基础语法-高级数据类型

    本文主要介绍 golang 内置数据类型的 array, slice, map。这几种数据类型在日常使用中是非常常见的。

    luxixing
  • Golang学习专栏

    学习Golang语言也快有一年半了,就想谈谈自己的看法,最初学完了golang相关的语法知识,感觉自己已经掌握了golang,就在网上去找比人认为比较优秀的源码...

    Librant
  • Golang语言--slice 切片原理

    golang 中的 slice 非常强大,让数组操作非常方便高效。在开发中不定长度表示的数组全部都是 slice 。但是很多同学对slice 的模糊认识,造成认...

    李海彬
  • Golang 语言中数组和切片的区别是什么?

    在很多编程语言中都有数组,而切片类型却不常见。实际上,Golang 语言中的切片的底层存储也是基于数组。因为数组是固定长度的,而切片比数组更加灵活,所以在 Go...

    frank.
  • 关于Golang切片Slice和append的有趣问题

    slice 的数据结构,一个指向真实 array 地址的指针 ptr ,slice 的长度 len 和容量 cap ,在底层数组容量不足时可以实现自动重分配并生...

    benny
  • 彻底搞懂Django中的数据迁移

    Django是基于Python语言的网站开发框架,书中以案例的方式,介绍了Django框架的基本应用。

    老齐
  • 一日一技:Golang 字符串切片与 Python 列表的不同

    最近在粉丝交流群里面看到不少学 Python 的同学都在学习 Golang,那么今天我们来看一个非常基础的数据结构:Python中的列表和 Golang 中的切...

    青南
  • golang-101-hacks(12)——切片作为函数参数传递

    注:本文是对golang-101-hacks中文翻译。 在Go语言中,函数参数是值传递。使用slice作为函数参数时,函数获取到的是slice的副本:一个指针...

    羊羽shine
  • Golang 语言的内存管理

    使用 len() 获取字符串长度,返回的是字节长度,如果想要获取 unicode 长度,需要使用 utf8 包的方法。

    frank.
  • 一文理清 Go 引用的常见疑惑

    之所以要谈它,一方面是之前的我也有些概念混乱,想梳理下,另一方面是因为很多人对引用都有疑问。我经常会看到与引用有关的问题。

    波罗学

扫码关注云+社区

领取腾讯云代金券