专栏首页aoho求索Go 语言入门系列:切片的应用实践

Go 语言入门系列:切片的应用实践

前文回顾

前面的文章主要介绍了 Go 基于语法中 Go 容器:数组。本文将会具体切片的使用。

Go 中常用的容器

Golang 中以标准库的方式提供了常用的容器实现,基本能够满足我们日常开发的需要。我们来具体学习下 Go 数组的使用。

切片

切片是对数组一个连续片段的引用,它是一个容量可变的序列。我们可以简单将切片理解为动态数组,它的内部结构包括底层数组指针、大小和容量,它通过指针引用底层数组,把对数据的读写操作限定在指定的区域内。

切片的结构体由三部分组成:

  • array 是指向底层存储数据数组的指针;
  • len 指当前切片的长度,即成员数量;
  • cap 指当前切片的容量,它总是大于等于 len

我们可以从原有数组中生成一个切片,那么生成的切片指针即指向原数组,生成的样式如下:

slice := source[begin:end]

source表示生成切皮的原有数组,begin 表示切片的开始位置,end 表示切片的结束位置,不包含 end 索引指向的成员。具体效果如下例子所示:

source := [...]int{1,2,3}
sli := source[0:1]

fmt.Printf("sli value is %v\n", sli)
fmt.Printf("sli len is %v\n", len(sli))
fmt.Printf("sli cap is %v\n", cap(sli))

输出的结果为:

sli value is [1]
sli len is 1
sli cap is 3

在这个切片内,我们仅能访问长度内的值,如果访问的下标超过了切片的长度,编译器将会抛出下标越界的异常。如果此时我们对切片内的成员进行修改,因为切片作为指向原有数组的引用,对切片进行修改就是对原有数组进行修改,如下例子所示:

sli[0] = 4
fmt.Printf("sli value is %v\n", sli)
fmt.Printf("source value is %v\n", source)

结果如下所示:

sli value is [4]
source value is [4 2 3]

上面例子中我们修改了切片中的值,直接导致原数组中的值也发生了变化。

我们也可以通过 make 函数动态创建切片,在创建过程中指定切片的长度和容量,样式如下所示:

make([]T, size, cap)

T 即切片中的成员类型,size 为当前切片具备的长度,cap 为当前切片预分配的长度,即切片的容量。例子如下所示:

sli = make([]int, 2, 4)
fmt.Printf("sli value is %v\n", sli)
fmt.Printf("sli len is %v\n", len(sli))
fmt.Printf("sli cap is %v\n", cap(sli))

输出的结果如下:

sli value is [0 0]
sli len is 2
sli cap is 4

从上述输出可以看到 make 函数创建的新切片中的成员都被初始化为类型的初始值。

切片的其他声明方式

除了上面介绍的切片初始化方式。还可以直接声明新的切片,这就类似于数组的初始化,但是不需要指定其大小,否则就变成了数组,样式如下所示:

var name []T

此时声明的切片并没有分配内存,我们可以在声明切片的同时对其进行初始化,如下例子所示:

ex := []int{1,2,3}
fmt.Printf("ex value is %v\n", ex)
fmt.Printf("ex len is %v\n", len(ex))
fmt.Printf("ex len is %v\n", cap(ex))

结果如下所示:

ex value is [1 2 3]
ex len is 3
ex cap is 3

此时声明的切片大小和容量都为 3。

Golang 中提供了 append 内建函数用于动态向切片添加成员,它将返回新的切片。如果当前切片的容量可以容纳更多的成员,添加的操作将在切片指向的原有数组上进行,这将会覆盖掉原有数组的值;如果当前切片的容量不足以容纳更多的成员,那么切片将会进行扩容,具体的扩容过程为:申请一个新的连续内存空间,空间大小一般是原有容量的 2 倍,然后将原来数组中的数据拷贝到新的数组中,同时将切片中的指针指向新的数组,最后将新的成员添加到新的数组中。我们将通过下面的例子来进行演示:

package main

import "fmt"

func main()  {


 arr1 := [...]int{1,2,3,4}
 arr2 := [...]int{1,2,3,4}

 sli1 := arr1[0:2] // 长度为 2,容量为 4
 sli2 := arr2[2:4] // 长度为 2,容量为 2

 fmt.Printf("sli1 pointer is %p, len is %v, cap is %v, value is %v\n", &sli1, len(sli1), cap(sli1), sli1)
 fmt.Printf("sli2 pointer is %p, len is %v, cap is %v, value is %v\n", &sli2, len(sli2), cap(sli2), sli2)

 newSli1 := append(sli1, 5)
 fmt.Printf("newSli1 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli1, len(newSli1), cap(newSli1), newSli1)
 fmt.Printf("source arr1 become %v\n", arr1)

 newSli2 := append(sli2, 5)
 fmt.Printf("newSli2 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli2, len(newSli2), cap(newSli2), newSli2)
 fmt.Printf("source arr2 become %v\n", arr2)

}

上述例子的结果为:

sli1 pointer is 0xc00000c040, len is 2, cap is 4, value is [1 2]
sli2 pointer is 0xc00000c060, len is 2, cap is 2, value is [3 4]
newSli1 pointer is 0xc00007c020, len is 3, cap is 4, value is [1 2 5]
source arr1 become [1 2 5 4]
newSli2 pointer is 0xc00007c060, len is 3, cap is 4, value is [3 4 5]
source arr2 become [1 2 3 4]

通过上面的例子,我们可以发现,容量足够的 sli1 直接将 append 添加的新成员覆盖到原有数组 arr1,而容量不够的 sli2 进行了扩容操作,申请了新的底层数组,不在原数组的基础上进行操作。在实际使用的过程中要千万记住这两种区别。

如果原有数组可以添加新的成员,即切片指向的数组后还有空间,但切片的容量已经饱和,此时进行 append 操作,同样会进行扩容,申请新的内存空间,如下例子所示:

arr3 := [...]int{1,2,3,4}
sli3 := arr3[0:2:2] // 长度为 2,容量为 2

fmt.Printf("sli3 pointer is %p, len is %v, cap is %v, value is %v\n", &sli3, len(sli3), cap(sli3), sli3)

newSli3 := append(sli3,5)
fmt.Printf("newSli3 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli3, len(newSli3), cap(newSli3), newSli3)
fmt.Printf("source arr3 become %v\n", arr3)

对应的输出结果为:

sli3 pointer is 0xc00008e080, len is 2, cap is 2, value is [1 2]
newSli3 pointer is 0xc00006e0a0, len is 3, cap is 4, value is [1 2 5]
source arr3 become [1 2 3 4]

在上述代码中,我们指定了创建切片的第三个参数 capcap 必须大于 end,生成切片的容量将为 cap - begin,限定了 sli3 的容量为 2。当进行 append 操作时,即使原有数组存在足够的空间,newSli3 还是指向新的数组空间。

为了方便切片的数据快速复制到另一个切片中,Golang 提供了内建的 copy 函数,它的使用样式如下:

copy(destSli, srcSli []T)

它的返回结果为实际发生复制的元素个数。如果要保证来源切片的数据都拷贝到目标切片,需要保证目标切片的长度大于等于来源切片的长度,否则将按照目标切面的长度大小进行拷贝。

小结

本文主要介绍了切片的基本使用,切片本质就是一个结构体,他里面包含三部分:address + len + cap,因此作为一个引用空间,该空间和元素空间完全是两个空间,所以切片的首地址和头号元素的首地址完全不同。

总得来说 Go 语言中数组和切片有如下的区别:

  • 切片是指针类型,数组是值类型
  • 数组的长度是固定的,而切片不是(切片是动态的数组)
  • 切片比数组多一个属性:容量(cap)
  • 切片的底层是数组

切片是 Go 中提供了一种灵活,功能强悍的内置类型("动态数组")。与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

本文分享自微信公众号 - aoho求索(aohoBlog),作者:cangwu

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-07-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Go 语言入门系列:指针的基本应用

    前面文章介绍了 Go 语言原生数据类型。Golang 中具备丰富的数据类型,基本类型有整型、浮点数、布尔型、字符串型等,除此之外,还有切片、结构体、指针、通道、...

    aoho求索
  • Go 语言入门系列:数组的使用

    前面的文章主要介绍了 Go 基于语法中的分支和循环控制结构。其中涉及到的 If 、Switch 和 for 等关键字,是我们日常编码所常用的。当我们在程序中操作...

    aoho求索
  • Go语言入门系列(四)之map的使用

    map是一种映射,可以将键(key)映射到值(value),格式为:map[keyType]valueType。

    二十二画程序员
  • Go语言入门系列(七)之如何使用Go的方法?

    如果你用过面向对象的语言,比如Java,那你肯定对类、对象、成员变量、方法等很熟悉。

    二十二画程序员
  • Go 语言入门系列:Go 语言中的常量别名与控制结构

    前面一篇文章主要介绍了 Go 语言中的指针基本概念与相关用法。Go 语言限制了指针类型的偏移和运算能力,使得指针类型具备了指针高效访问的特性,但又不会发生指针偏...

    aoho求索
  • 【Go语言入门系列】Go语言工作目录介绍及命令工具的使用

    在【保姆级教程】手把手教你进行Go语言环境安装及相关VSCode配置一文中已经配置过工作空间GOPATH的环境变量了,并在工作空间中新建了三个目录src、pkg...

    二十二画程序员
  • 【实践】GO语言框架REDIGO使用REDIS数据库入门

    基于GO的REDIOS调用框架有开源库redigo。本文主要讲解redigo的框架和调用样例。

    辉哥
  • Github开源免费编程书籍

    时见疏星
  • Golang 入门系列(五)GO语言中的面向对象

    其实GO并不是一个纯面向对象编程语言。它没有提供类(class)这个关键字,只提供了结构体(struct)类型。

    架构师精进
  • Go 语言 Web 编程系列(一)—— 快速入门:创建第一个 Web 应用

    首先,我们基于 HTTP 编程中介绍的 net/http 包来实现一个简单的 HTTP 服务器作为 Web 服务器:

    学院君
  • Go语言入门系列(五)之指针和结构体的使用

    我们写的代码都存储在外存(C盘、D盘)中,比如我存在了D:\Work\Program\go目录下。如果你想要运行你的代码,必须先把你的代码加载进内存中,然后交给...

    二十二画程序员
  • 500 份源码合集——GitHub 热点速览 v.21.02

    GitHub 项目名,如同变量命名,一个好的项目名能让你一眼就知道它是什么。500-AI-Machine-learning-Deep-learning-Comp...

    HelloGitHub
  • Go语言学习路线 - 8.高级篇:从五个问题来畅想Go工程师的未来发展

    有人常说,编程语言对软件工程师来说并不重要,更重要的是软件工程思想、架构设计能力等更高层面的内容。

    junedayday
  • 浅谈编程语言的本质

    最近博客和公众号文章差不多有二个多月没有更新了,相比之前每月一篇文章的更新频率,最近确实懒散了许多,一来年底收尾之际手上的工作繁重抽不出时间,二来自己最近的空闲...

    phoenix.xiao
  • go语言教程零基础入门到精通

    课程详细目录: ├─L001-Go语言-mp4 │ 01 Go开发1期 day1 开课介绍01.mp4 │ 02 Go开发1期 day1 ...

    码农编程进阶笔记
  • C语言入门系列之8.指针的概念与应用

    指针是C语言中的一个重要的概念,也是C语言的一个重要特色。 正确而灵活地运用它,可以有效地表示复杂的数据结构;能动态分配内存;能方便地使用字符串;有效而方便地...

    cutercorley
  • 很多小伙伴问我推荐什么书籍和网课,这次把私藏很久的资料都贡献了(上)

    平时有不少读者朋友问,有没有学习书籍网上课程推荐?今天结合自己学习经历与身边几个朋友的经历总结了一份程序员相关的书籍和网课。

    C语言与CPP编程
  • 这么多的编程语言为何选择Go

    在你阅读以下内容时,我不得不告诉你一个事实,编程语言Go正在成为一颗冉冉升起的新星,为什么这样说,出身于Google,它是名门出身,它的作者可以说称得上是神级一...

    陌无崖
  • Golang 入门系列(二)学习Go语言需要注意的坑

    上一章节我们已经了解了 Go 环境的配置,不了解的,请查看前面的文章 https://www.cnblogs.com/zhangweizhong/p/94599...

    架构师精进

扫码关注云+社区

领取腾讯云代金券