首页 科技正文

邯郸天气:手把手golang教程【二】——数组与切片

admin 科技 2020-09-22 53 1

本文始发于小我私家民众号:TechFlow,原创不易,求个关注


今天是golang专题的第五篇,这一篇我们将会领会golang中的数组和切片的使用。

数组与切片

golang当中数组和C++中的界说类似,除了变量类型写在后面。

好比我们要声明一个长度为10的int型的数组,会写成这样:

var a [10]int

数组的长度界说了之后不能改变,这点和C++以及Java是一样的。然则在我们一样平常使用的历程当中,除非我们异常确定数组长度不会发生转变,否则我们一样平常不会使用数组,而是使用切片(slice)。

切片有些像是数组的引用,它的巨细可以是动态的,因此加倍天真。以是在我们一样平常的使用当中,比数组应用更广。

切片的声明源于数组,和Python中的list切片类似,我们通过指定左右区间的局限来声明一个切片。这里的局限和Python一样,左闭右开。我们来看个例子:

var a [10]int
var s []int = a[0:4]

这是尺度的声明写法,我们也可以不用var来声明,而是直接行使数组给切片赋值,好比上面的语句可以写成这样:

s := a[:4]

在Python当中,当我们使用切片的时刻,注释器会为我们将切片对应的数据复制一份。以是切片之后和之前的效果是差别的,然则golang当中则差别。切片和数据对应的是统一份数据,切片只是数组的一个引用,若是原数组的数据发生转变,那么会连带着切片中的数据一起转变。

照样适才谁人例子:

var a [10]int
var s []int = a[0:4]
fmt.Println(s)

这样我们输出获得的效果是[0 0 0 0],由于数组初始化默认值为0。而如果我们修改一个a中的元素,我们再来打印s,获得的效果就差别了:

var a [10]int
var s []int = a[0:4]
a[0] = 4
fmt.Println(s)

这样获得的效果就是[4 0 0 0],虽然我们并没有修改s当中的数据,由于s本质是a的引用,以是a中发生转变会连带着s一起转变。

进阶用法

前面说了,由于切片比数组加倍利便,以是我们一样平常使用当中都倾向于使用切片,而不是数组。然则凭据现在的语法,切片都是从数组当中发生的,这岂不是意味着,我们若是想要使用切片,必须先要建立出一个对应的数组来吗

golang的设计者思量到了这个问题,为了利便我们的使用,golang设计了直接界说切片的方式。

这是一个数组的声明,我们牢固了数组的长度,而且用指定的元素对它举行了初始化。

var a = [3]int{0, 1, 2}

若是我们去掉长度的声明,那么它就成了一个切片的声明:

var a = []int{0, 1, 2}

这样是同样可以运行的,在golang的内部下面的语句同样建立了数组,我们获取的a是这个数组的一个切片。然则这个数组对我们是不能见的,golang编译器替我们省略了这个逻辑。

长度和容量

明了了切片和数组之间的关系之后,我们就可以来看它的长度容量这两个观点了。

这个单词的英文分别是length和capability,长度指的是切片自己包罗的元素的个数,而容量则是切片对应的数组从最先到末尾包罗的元素个数。我们可以用len操作来获取切片的长度,用cap操作来获取它的容量。

我们来看一个例子,首先我们建立一个切片,然后写一个函数来打印出一个切片的长度和容量:

package main

import "fmt"

func main() {
 s := []int{1, 2, 3, 4, 5, 6}
 printSlice(s)
 
}

func printSlice(s []int) {
 fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

当我们运行之后获得的效果是这样的:

这个和我的预期应该是一致的,我们建立出了6个元素的切片,自然它的容量和长度应该都是6,但接下来的操作可能就会有点出入了。

我们对这个切片再举行切片,继续输出切片之后的容量和长度:

s = s[:2]
printSlice(s)

运行之后会获得下面这个效果:

我们发现它的长度变成了2,然则容量照样6,这个也不是稀奇难明了。由于虽然当前的切片长度变小了,然则它对应的数组并没有任何转变,以是它的容量应该照样6。

我们继续,我们继续切片:

s := []int{1, 2, 3, 4, 5, 6}
s = s[:2]
s = s[:4]
printSlice(s)

获得这样的效果:

事情最先有点不一样了,对照令人关注的点有两个。一个是s在之前切片竣事之后的效果长度是2,然则我们居然可以对它切片到下标4的位置。这说明我们在执行切片的时刻,执行的工具并不是切片自己,而是切片背后对应的数组。这一点异常重要,若是不能明了这点,那么切片的许多操作看起来都市以为匪夷所思难以明了。

第二个点是切片的容量依然没有发生转变,这样不会发生转变,那么我们再换一种切片的方式试试,看看会不会有什么差别。

s = s[2:]
printSlice(s)

这一次获得的效果就差别了,它是这样的:

这一次发生转变了,切片的容量变成了4,也就是说变小了,这是为什么呢?

缘故原由很简朴,由于数组的头指针的位置移动了。数组原本的长度是6,往右移动了两位,剩下的长度自然就是4了。然则剩下的问题是,为什么数组的头指针会移动呢?

由于数组的头指针和切片的位置是挂钩的,我们前面的切片操作虽然会改变切片中的元素和它的长度,然则都没有改变切片指针的位置。而这一次我们举行的切片是[2:],当我们执行这个操作的时刻,本质上是指针的位置向右移动到了2。

这也是为什么切片的容量界说是它对应的数组从最先到末尾元素的个数,而不是对应的数组元素的个数。由于指针向右移动会改变容量的巨细,然则数组自己的长度是没有转变的

我们来看个例子就明了了:

var a = [6]int{1, 2, 3, 4, 5, 6}
 s := a[:]
 //printSlice(s)
 s = s[:2]
 printSlice(s)
 s = s[2:]
 printSlice(s)
 //s[0] = 4
 fmt.Println(a)

我们这一次使用显性的切片,我们对s举行一系列切片之后,它的容量变成了4,然则a当中的元素个数照样6,并没有转变。以是不能简朴将容量明了成数组的长度,而是切片位置到数组末尾的长度。由于切片操作会改变切片指针的位置,从而改变容量,然则数组的巨细是没有转变的。

make操作

一样平常在我们使用切片的时刻,我们都是把它当做动态数组用的,也就是Python中的list。以是我们一方面不希望体贴切片背后数组,另一方面希望能够有一个区分度较大的组织方式,和建立数组做一个鲜明的区分。

以是基于以上思量,golang当中为我们提供了一个make方式,可以用来建立切片。由于make还可以用来建立其他的类型,好比map,以是我们在使用make的时刻,需要传入我们想要建立的变量类型。这里我们想要建立的是切片,以是我们要传入切片的类型,也就是[]int,或者是[]float等等。之后,我们需要传入切片的长度和容量。

好比:

s := make([]int, 0, 5)

我们就获得了一个长度为0,容量是5的切片。我们也可以只传入一个参数,若是只传入一个参数的话,示意切片的长度和容量相等。

像是这样:

s := make([]int, 5)

我们若是打印这个s的话,会获得[0 0 0 0 0],也就是说golang会为我们给切片填充零值。

append方式

前面说了和数组比起来切片的使用加倍天真,意味着切片的长度是可变的,我们可以通过使用append方式向切片当中追加元素。

golang中的append方式和Python已经其他语言差别,golang中的append方式需要传入两个参数,一个是切片自己,另一个是需要添加的元素,最后会返回一个切片。

以是我们应该写成这样:

s := make([]int, 4)
s = append(s, 4)

这么做的目的也很简朴,由于切片的长度是动态的,也就意味着切片对应的数组的长度也是可变的,至少是可能增大的。若是当前的数组容量不足以存储切片的时刻,golang会分配一个更大的数组,这时刻会返回一个指向新数组的切片。也就是说由于切片底层实现机制的关系,导致了append方式不能做成inplace的,以是必须要举行返回。我猜,这也是由于性能的思量。

二维切片

最后我们来看看二维切片在golang当中应该怎么实现,只能要能明了二维,拓展到多维也是一样。

golang缔造二维切片的方式和C++建立二维的vector有些类似,我们一最先先直接界说一个二维的切片,然后用循环往里面填充。我们界说二维切片的方式和一维的切片类似,只是多了一个方括号而已,之后我们用循环往其中填充若干个一维切片:

mat := make([][]int, 10)
for i := 0; i < 10; i++ {
  mat[i] = make([]int, 10)
}

末端

到这里,golang中关于数组和切片的常见的用法就先容完了。不仅如此,关于切片底层的实现原理,我们也有了一点浅陋的明了。刚最先接触切片这个观点的时刻可能会以为有点怪,总以为似乎和我们之前学习的语言对不上号,关于容量的观点也不太容易明了,这个是异常正常的,本质上来说,这一切看起来不太正常或者是不太舒适的地方,背后都有创作者的思索,以及为了性能的权衡。以是,若是你以为想不通的话,可以多往这个方面思索,也许会有不一样的收获。

今天的文章就到这里,原创不易,扫码关注我,获取更多精彩文章。

,

sunbet

sunbet是Sunbet www.sunbet.xyz指定的Sunbet官网,Sunbet提供Sunbet(Sunbet)、Sunbet、申博代理合作等业务。

版权声明

本文仅代表作者观点,
不代表本站dafa888的立场。
本文系作者授权发表,未经许可,不得转载。

评论

精彩评论
  • 2020-09-22 00:00:27

    Allbet Gmaing欢迎进入欧博Allbet官网(www.aLLbetgame.us),欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。能看到这篇好幸运