请选择 进入手机版 | 继续访问电脑版
从零开始 从零开始 资讯 查看内容

Go说话中重要的数据结构之一 Slice,你把握了吗?

2020-2-28 13:09| 发布者: 侬去斯| 查看: 147| 评论: 2

摘要: 本文主要介绍Go语言中切片(slice)及它的基本使用。引子因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。 例如:func arraySum(x int) int{ sum := 0 for _, v := range x{ sum = su ...

本文首要先容Go说话中切片(slice)及它的根基利用。

引子


由于数组的长度是牢固的而且数组长度属于范例的一部分,所以数组有很多的范围性。 例如:
func arraySum(x [3]int) int{
sum := 0
for _, v := range x{
sum = sum + v
}
return sum
}

这个求和函数只能接管[3]int范例,其他的都不支持。 再比如,
a := [3]int{1, 2, 3}

数组a中已经有三个元素了,我们不能再继续往数组a中增加新元素了。

切片


切片(Slice)是一个具有不异范例元素的可变长度的序列。它是基于数组范例做的一层封装。它很是灵活,支持自动扩容。

切片是一个援用范例,它的内部结构包括地址、长度和容量。切片一般用于快速地操纵一块数据调集。

切片的界说


声明切片范例的根基语法以下:
var name []T

其中,
  • name:暗示变量名
  • T:暗示切片中的元素范例

举个例子:

Go说话中重要的数据结构之一 Slice,你把握了吗?_资讯_2020-2-28 13:09发布_从零开始_147

切片的长度和容量


切片具有自己的长度和容量,我们可以经过利用内置的len()函数求长度,利用内置的cap()函数求切片的容量。

基于数组界说切片

由于切片的底层就是一个数组,所以我们可以基于数组界说切片。
func main() {
\t// 基于数组界说切片
\ta := [5]int{55, 56, 57, 58, 59}
\tb := a[1:4] //基于数组a建立切片,包括元素a[1],a[2],a[3]
\tfmt.Println(b) //[56 57 58]
\tfmt.Printf("type of b:%T\\n", b) //type of b:[]int
}

还支持以下方式:
c := a[1:] //[56 57 58 59]
d := a[:4] //[55 56 57]
e := a[:] //[55 56 57 58 59]

切片再切片


除了基于数组获得切片,我们还可以经过切片来获得切片。

Go说话中重要的数据结构之一 Slice,你把握了吗?_资讯_2020-2-28 13:09发布_从零开始_147

输出:
a:[北京 上海 广州 深圳 成都 重庆] type:[6]string len:6 cap:6
b:[上海 广州] type:[]string len:2 cap:5
c:[广州 深圳 成都 重庆] type:[]string len:4 cap:4

留意: 对切片停止再切片时,索引不能跨越原数组的长度,否则会出现索引越界的毛病。

利用make()函数机关切片


我们上面都是基于数组来建立的切片,假如需要静态的建立一个切片,我们就需要利用内置的make()函数,格式以下:
make([]T, size, cap)

其中:
  • T:切片的元素范例
  • size:切片中元素的数目
  • cap:切片的容量

举个例子:
func main() {
\ta := make([]int, 2, 10)
\tfmt.Println(a) //[0 0]
\tfmt.Println(len(a)) //2
\tfmt.Println(cap(a)) //10
}

上面代码中a的内部存储空间已经分派了10个,但现实上只用了2个。 容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量。

切片的本质


切片的本质就是对底层数组的封装,它包括了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],响应表示图以下。

Go说话中重要的数据结构之一 Slice,你把握了吗?_资讯_2020-2-28 13:09发布_从零开始_147

切片s2 := a[3:6],响应表示图以下:

Go说话中重要的数据结构之一 Slice,你把握了吗?_资讯_2020-2-28 13:09发布_从零开始_147

切片不能间接比力


切片之间是不能比力的,我们不能利用==操纵符来判定两个切片能否含有全数相称元素。 切片唯一正当的比力操纵是和nil比力。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。可是我们不能说一个长度和容量都是0的切片一定是nil,例以下面的示例:
var s1 []int //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

所以要判定一个切片能否是空的,如果用len(s) == 0来判定,不应当利用s == nil来判定。

切片的赋值拷贝


下面的代码中演示了拷贝前后两个变量同享底层数组,对一个切片的点窜会影响另一个切片的内容,这点需要出格留意。
func main() {
\ts1 := make([]int, 3) //[0 0 0]
\ts2 := s1 //将s1间接赋值给s2,s1和s2共用一个底层数组
\ts2[0] = 100
\tfmt.Println(s1) //[100 0 0]
\tfmt.Println(s2) //[100 0 0]
}

切片遍历


切片的遍历方式和数组是分歧的,支持索引遍历和for range遍历。

Go说话中重要的数据结构之一 Slice,你把握了吗?_资讯_2020-2-28 13:09发布_从零开始_147

append()方式为切片增加元素


Go说话的内建函数append()可以为切片静态增加元素。 每个切片会指向一个底层数组,这个数组能包容一定数目的元素。当底层数组不能包容新增的元素时,切片就会自动依照一定的战略停止“扩容”,此时该切片指向的底层数组就会更换。“扩容”操纵常常发生在append()函数挪用时。 举个例子:

Go说话中重要的数据结构之一 Slice,你把握了吗?_资讯_2020-2-28 13:09发布_从零开始_147

输出:
[0] len:1 cap:1 ptr:0xc0000a8000
[0 1] len:2 cap:2 ptr:0xc0000a8040
[0 1 2] len:3 cap:4 ptr:0xc0000b2020
[0 1 2 3] len:4 cap:4 ptr:0xc0000b2020
[0 1 2 3 4] len:5 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000b8000

从上面的成果可以看出:
  1. append()函数将元素追加到切片的最初并返回该切片。
  2. 切片numSlice的容量依照1,2,4,8,16这样的法则自动停止扩容,每次扩容后都是扩容前的2倍。

append()函数还支持一次性追加多个元素。 例如:
var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]

切片的扩容战略


可以经过检察$GOROOT/src/runtime/slice.go源码,其中扩容相关代码以下:

Go说话中重要的数据结构之一 Slice,你把握了吗?_资讯_2020-2-28 13:09发布_从零开始_147

从上面的代码可以看出以下内容:
  • 首先判定,假如新申请容量(cap)大于2倍的旧容量(old.cap),终极容量(newcap)就是新申请的容量(cap)。
  • 否则判定,假如旧切片的长度小于1024,则终极容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
  • 否则判定,假如旧切片长度大于即是1024,则终极容量(newcap)从旧容量(old.cap)起头循环增加本来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到终极容量(newcap)大于即是新申请的容量(cap),即(newcap >= cap)
  • 假如终极容量(cap)计较值溢出,则终极容量(cap)就是新申请容量(cap)。

需要留意的是,切片扩容还会按照切片中元素的范例分歧而做分歧的处置,比如int和string范例的处置方式就纷歧样。

利用copy()函数复制切片


首先我们来看一个题目:

Go说话中重要的数据结构之一 Slice,你把握了吗?_资讯_2020-2-28 13:09发布_从零开始_147

由于切片是援用范例,所以a和b实在都指向了同一块内存地址。点窜b的同时a的值也会发生变化。

Go说话内建的copy()函数可以敏捷地将一个切片的数据复制到别的一个切片空间中,copy()函数的利用格式以下:
copy(destSlice, srcSlice []T)

其中:
  • srcSlice: 数据来历切片
  • destSlice: 方针切片

举个例子:

Go说话中重要的数据结构之一 Slice,你把握了吗?_资讯_2020-2-28 13:09发布_从零开始_147

从切片中删除元素


Go说话中并没有删除切片元素的公用方式,我们可以利用切片自己的特征来删除元素。 代码以下:
func main() {
\t// 从切片中删除元素
\ta := []int{30, 31, 32, 33, 34, 35, 36, 37}
\t// 要删除索引为2的元素
\ta = append(a[:2], a[3:]...)
\tfmt.Println(a) //[30 31 33 34 35 36 37]
}

总结一下就是:要从切片a中删除索引为index的元素,操纵方式是a = append(a[:index], a[index+1:]...)

练习题


1.请写出下面代码的输出成果。
func main() {
\tvar a = make([]string, 5, 10)
\tfor i := 0; i < 10; i++ {
\t\ta = append(a, fmt.Sprintf("%v", i))
\t}
\tfmt.Println(a)
}

2.请利用内置的sort包对数组var a = [...]int{3, 7, 8, 9, 1}停止排序。

这些你会了吗?

原文链接:https://www.liwenzhou.com/posts/Go/06_slice/

本文作者:李文周,原创授权公布

鲜花

握手

雷人

路过

鸡蛋
发表评论

最新评论

引用 123457010 2020-2-28 13:10
转发了
引用 同感丶 2020-2-28 13:10
转发了

查看全部评论(2)

相关分类

点击排行
  • 联系我们
  • 邮箱:admin@c0ks.com(请把#改成@)
  • 电话:18530790808
  • QQ客服 1031180668
  • 工作时间:周一至周五(早上9点至下午5点)
  • 微信二维码

  • 扫描访问手机版

Archiver|手机版|小黑屋|从零开始

GMT+8, 2020-6-5 05:11 , Processed in 0.149550 second(s), 22 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

  • QQ: 1031180668

    客服电话

    18530790808

    电子邮件

    admin@c0ks.com

    在线时间:8:00-16:00