本文始发于个人公众号: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中关于数组和切片的常见的用法就介绍完了。不仅如此,关于切片底层的实现原理,我们也有了一点浅薄的理解。刚开始接触切片这个概念的时候可能会觉得有点怪,总觉得好像和我们之前学习的语言对不上号,关于容量的概念也不太容易理解,这个是非常正常的,本质上来说,这一切看起来不太正常或者是不太舒服的地方,背后都有创作者的思考,以及为了性能的权衡。所以,如果你觉得想不通的话,可以多往这个方面思考,也许会有不一样的收获。

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

手把手golang教程【二】——数组与切片的更多相关文章

  1. Golang教程:数组和切片

    数组 数组是类型相同的元素的集合.例如,整数 5, 8, 9, 79, 76 的集合就构成了一个数组.Go不允许在数组中混合使用不同类型的元素(比如整数和字符串). 声明 var variable_n ...

  2. golang多维数组的切片

    通过for循环来取多维数组的切片 package main import ( "fmt" ) func main() { a := [...]string{"USA&qu ...

  3. golang学习笔记 ---数组与切片

    数组: golang数组包含的每个数据称为数组元素(element),数组包含的元素个数被称为数组长度(length). golang数组的长度在定义后不可更改,并且在声明时可以是一个常量或常量表达式 ...

  4. golang 学习笔记 ---数组/字符串/切片

    数组 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成.数组的长度是数组类型的组成部分.因为数组的长度是数组类型的一个部分,不同长度或不同类型的数据组成的数组都是不同的类 ...

  5. go语言基础知识笔记(二)之数组和切片

    数组和切片知识用的也是比较多,的给我们工作带来很大的便利 (一) 数组 定义:在golang中数组的长度是不可变,数组存放要求是同一种数据类型 //golang中数组定义的四种方法1.先声明,后赋值 ...

  6. Golang 数组、切片、映射

    定义数组 var arr1 [5]int //整型类型 fmt.Println(arr1) //[0 0 0 0 0] //赋值 arr1 = [5]int{1, 2, 3, 4, 5} fmt.Pr ...

  7. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(二)-Hexo参数设置

    前言 前文手把手教从零开始在GitHub上使用Hexo搭建博客教程(一)-附GitHub注册及配置介绍了github注册.git相关设置以及hexo基本操作. 本文主要介绍一下hexo的常用参数设置. ...

  8. golang笔记——数组与切片

    一.切片的定义 我们可以从数组(go语言中很少直接使用数组)或者切片来初始化一个新的切片,也可以直接通过 make 来初始化一个所有元素为默认零值的切片. //1.通过数组来初始化切片 arr := ...

  9. golang 数组、切片、map

    一.数组(类似python的list) 数组的长度一旦定义了就不能动态增长.并且存储的数据类型必须相同. 创建方法: var 数组名 [长度]数据类型 例如: package main import ...

随机推荐

  1. HTML+CSS教程(一)简介及其基本标签的使用方法

    一.前端 HTML(结构):HyPer TEXT Markup LanguageCSS(样式): 样式就是对于结构的一种美化JavaScript(js: 行为/ 提供了用户和界面的交互方式)jQuer ...

  2. python数据分析工具——Pandas、StatsModels、Scikit-Learn

    Pandas Pandas是 Python下最强大的数据分析和探索工具.它包含高级的数据结构和精巧的工具,使得在 Python中处理数据非常快速和简单. Pandas构建在 Numpy之上,它使得以 ...

  3. 解决sublime打开文档,出现中文乱码问题

    sublime text 软件中出现中文乱码,大多是因为编码格式不支持,需要安装一个插件就可以解决中文乱码问题,推荐安装 ConvertToUtf8  安装步骤: 1.按“shift + ctrl + ...

  4. 用scrapy实现模拟登陆

    class Test1sSpider(scrapy.Spider): name = 'test1s' allowed_domains = ['yaozh.com'] start_urls = ['ht ...

  5. 去掉input阴影&隐藏滚动条&抛异常&预加载&curl传json

    1.隐藏滚动条:-webkit-scrollbar{ display:none; } 2.array_walk():数组里的每个元素执行一个自定义函数: array_map():数组里的每个元素执行一 ...

  6. 团队一致性的PHP开发环境之Docker

    docker php环境模型 docker 简介 Docker 是一个开源的应用容器引擎 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现 ...

  7. Pig设计模式概要以及与SQL的设计模式的对比

    2019独角兽企业重金招聘Python工程师标准>>> 1概要模式 概要模式其实就是数据的全貌信息的获取,主要分为3种: 1.1数值概要 #HSQL SELECT MIN(num), ...

  8. Java实现zip文件解压[到指定目录]

    2019独角兽企业重金招聘Python工程师标准>>> package com.ljheee.ziptool.core; import java.io.File; import ja ...

  9. JAVA编程思想 Ch3.5题

    练习5:创建一个class类,包含连两个String字段 :name.says.在main方法中创建两个Dog方法 一个命名为spot 叫声为 Ruff,另一个命民为scruffy,叫声为:Wuff: ...

  10. [bzoj2088]P3505 [POI2010]TEL-Teleportation

    洛谷 bzoj 用了分层图的思想 题意 给一张图,要求你再尽可能的多连边,使得从1到2至少要经过5条边 没啥复杂的公式,讲解都在注释里 #include<cstdio> #include& ...