专栏首页每周一脱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的使用和底层原理,可以更透彻的理解他们的使用场景,里面有什么坑。这对我们平时编写程序是极有裨益的,后面我会继续深入,有时间再更。

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

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

登录 后参与评论
0 条评论

相关文章

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

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

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

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

    frank.
  • Golang中slice和map的线程安全问题

    多个线程在并行访问同一个对象时,线程安全的代码通过同步机制保证各个线程都可以正常且正确的执行,且都可以获得正确的结果,不会出现数据污染等情况,就表示这个对象是线...

    素履coder
  • golang-101-hacks(17)——数组和切片的相互转换

    在Go中,数组是相同数据类型组成长度固定的连续内存数据结构,slice只是指向底层数组的引用类型。它们是不同的类型,因此不能彼此直接赋值。请看下面的例子:

    羊羽shine
  • JavaScript中数组的splice方法和slice方法详解

    最近在做一些算法题,不能说不知道splice方法和slice方法怎么用,但是总是写出来有点点小问题,干脆就整理一下,再试两个小例子写一篇文章,彻底弄明白。

    伯约同学
  • 一文彻底搞懂 JS 中的基础类型和引用类型

    这两种方法的主要区别在于,在赋值原始值(primitive)时是传递值,而在赋值对象(objects)时是传递引用。

    前端修罗场
  • Golang泛型编程初体验

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

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

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

    李海彬
  • Go:学习笔记兼吐槽(3)

    长度是数组类型的一部分,就是说数组不可以脱离长度而存在。听起来不太明白,我们来看下面的一个示例就明白了,这真的是一个大坑。

    丹枫无迹
  • Golang语言--slice 切片原理

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

    李海彬
  • Go语言切片面试真题8连问

    切片是对数组的抽象,因为数组的长度是不可变的,在某些场景下使用起来就不是很方便,所以Go语言提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切...

    Golang梦工厂
  • 崩溃 golang入坑系列

    早上(11.30)收到邮件,Vultr东京机房网络故障。当时搭建SS时,考虑到了机房故障。所以特意分出了日本和香港两条线路。但千算万算,忘记数据库还在东京机房中...

    随机来个数
  • [Introduction]Go特殊的引用类型:值传递/指针传递/引用传递

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

    Wzy_CC
  • Golang高效实践之泛谈篇

    我博客之前的Golang高效实践系列博客中已经系统的介绍了Golang的一些高效实践建议,例如:《Golang高效实践之interface、reflection...

    用户2937493
  • 【译】golang 可变参数函数终极指南

    Ultimate Guide to Go Variadic Functions 原文地址 https://blog.learngoprogramming.com...

    goodspeed
  • 如何在Go中使用切片容量和长度

    Run it on the Go Playground → https://play.golang.org/p/7PgUqBdZ6Z

    KevinYan
  • golang切片内存应用技巧

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

    KevinYan
  • 面试官:说下Golang Slice的底层实现,泪崩了!

    数组固定长度数组长度是数组类型的一部分,所以[3]int 和[4]int 是两种不同 的数组类型数组需要指定大小,不指定也会根据处初始化对的自动推算出大 小,不...

    码农编程进阶笔记
  • Go语法指南

    spilledyear

扫码关注腾讯云开发者

领取腾讯云代金券