前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 语言入门系列:切片的应用实践

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

作者头像
aoho求索
发布2021-07-16 15:49:48
3150
发布2021-07-16 15:49:48
举报
文章被收录于专栏:aoho求索aoho求索

前文回顾

前面的文章主要介绍了 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 中提供了一种灵活,功能强悍的内置类型("动态数组")。与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 aoho求索 微信公众号,前往查看

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

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前文回顾
  • 前面的文章主要介绍了 Go 基于语法中 Go 容器:数组。本文将会具体切片的使用。
  • Go 中常用的容器
  • 切片
  • 切片的其他声明方式
  • 小结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档