文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

咱们来重新认识一下Golang的切片

2024-11-30 15:09

关注

今天废话不用多说,咱们来直接进入正题

切片究竟是什么?

在聊切片之前,我们先来看一下golang中的数组,大家都知道golang其实是c语言写的,那么在数组这一块golang和c语言的含义一样么?当然是不一样的。

golang数组

c语言数组

因为golang中数组是纯粹的值拷贝,所以在golang中,更地道的方式是使用「切片」, 「切片之于数组就像是文件描述符之于文件」数组更多是“退居幕后”,承担的是底层存储空间的角色;而切片则走向“前台”,为底层的存储(数组)打开了一个访问的“窗口”。

切片和数组的关系

其实通过golang源码也可以看出来,其实切片就是数组的指针。

//$GOROOT/src/runtime/slice.go
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

如何声明一个切片?

方式一

s := make([]byte, 5)

我们看到通过上述语句创建的切片,编译器会自动为切片建立一个「底层数组」,如果没有在make中指定cap参数,那么cap = len,即编译器建立的数组长度为len。

方式二(数组切片化)

u := [10]byte{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
s := u[3:7]

数组切片化

当然可以基于一个数组建立多个切片

u := [10]byte{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
s1 := u[1:5]
s2 := u[6:9]
s3 := u[3:7]

基于一个数组建立多个切片

也可以基于已有切片再次创建切片,也叫reslicing

u := [10]byte{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
s1 := u[1:5]
s2 := s1[2:4]

reslicing

动态扩容

在讲动态扩容之前,我们先来看一些例子。

// chapter3/sources/slice_append.go
var s []int  // s被赋予零值nil
s = append(s, 11)
fmt.Println(len(s), cap(s)) //1 1
s = append(s, 12)
fmt.Println(len(s), cap(s)) //2 2
s = append(s, 13)
fmt.Println(len(s), cap(s)) //3 4
s = append(s, 14)
fmt.Println(len(s), cap(s)) //4 4
s = append(s, 15)
fmt.Println(len(s), cap(s)) //5 8

我们看到切片s的len值是线性增长的,但cap值却呈现出不规则的变化。通过下图我们更容易看清楚多次append操作究竟是如何让切片进行动态扩容的。

动态扩容

我们看到append会根据切片的需要,在「当前底层数组容量无法满足」的情况下,「动态分配新的数组」,新数组长度会按一定算法扩展(参见$GOROOT/src/runtime/slice.go中的growslice函数)。新数组建立后,append会把「旧数组中的数据复制到新数组中」,之后新数组便成为切片的底层数组,旧数组后续会被「垃圾回收」掉。

这样的append操作有时会给Gopher带来一些困惑,比如通过语法u[low: high]形式进行数组切片化而创建的切片,一旦切片cap触碰到数组的上界,再对切片进行append操作,切片就会和原数组解除绑定。

小结练习

根据自己对切片的理解,先看看自己能不能想到每一步结果都会输出啥。

// chapter3/sources/slice_unbind_orig_array.go

func main() {
    u := []int{11, 12, 13, 14, 15}
    fmt.Println("array:", u) // [11, 12, 13, 14, 15]
    s := u[1:3]
    fmt.Printf("slice(len=%d, cap=%d): %v\n", len(s), cap(s), s) // [12, 13]
    s = append(s, 24)
    fmt.Println("after append 24, array:", u)
    fmt.Printf("after append 24, slice(len=%d, cap=%d): %v\n", len(s), cap(s), s)
    s = append(s, 25)
    fmt.Println("after append 25, array:", u)
    fmt.Printf("after append 25, slice(len=%d, cap=%d): %v\n", len(s), cap(s), s)
    s = append(s, 26)
    fmt.Println("after append 26, array:", u)
    fmt.Printf("after append 26, slice(len=%d, cap=%d): %v\n", len(s), cap(s), s)

    s[0] = 22
    fmt.Println("after reassign 1st elem of slice, array:", u)
    fmt.Printf("after reassign 1st elem of slice, slice(len=%d, cap=%d): %v\n", len(s), cap(s), s)
}

答案揭晓

$go run slice_unbind_orig_array.go
array: [11 12 13 14 15]
slice(len=2, cap=4): [12 13]
after append 24, array: [11 12 13 24 15]
after append 24, slice(len=3, cap=4): [12 13 24]
after append 25, array: [11 12 13 24 25]
after append 25, slice(len=4, cap=4): [12 13 24 25]
after append 26, array: [11 12 13 24 25]
after append 26, slice(len=5, cap=8): [12 13 24 25 26]
after reassign 1st elem of slice, array: [11 12 13 24 25]
after reassign 1st elem of slice, slice(len=5, cap=8): [22 13 24 25 26]

我们看到在添加元素25之后,切片的元素已经触碰到底层数组u的边界;此后再添加元素26,append发现底层数组已经无法满足添加新元素的要求,于是新创建了一个底层数组(数组长度为cap(s)的2倍,即8),并将原切片的元素复制到新数组中。在这之后,即便再修改切片中的元素值,原数组u的元素也没有发生任何改变,因为此时切片s与数组u已经解除了绑定关系,s已经不再是数组u的描述符了。

来源:程序员小饭内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯