Go 切片Slice的长度len与容量cap

您所在的位置:网站首页 切片函数参数无效 Go 切片Slice的长度len与容量cap

Go 切片Slice的长度len与容量cap

2023-12-21 09:28| 来源: 网络整理| 查看: 265

0x00 简介

Go 语言之旅示例 切片的长度与容量

切片拥有长度和容量。 切片的长度是它所包含的元素个数。 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。 切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。

package main import "fmt" func main() { s := []int{2, 3, 5, 7, 11, 13} printSlice(s) // 截取切片使其长度为 0 s = s[:0] printSlice(s) // 拓展其长度 s = s[:4] printSlice(s) // 舍弃前两个值 s = s[2:] printSlice(s) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } len=6 cap=6 [2 3 5 7 11 13] len=0 cap=6 [] len=4 cap=6 [2 3 5 7] len=2 cap=4 [5 7] 0x01 程序运行

1,第一个输出为[2,3,5,7,11,13],长度为6,容量为6; 在这里插入图片描述 2,左指针和右指针同时指向s[0],所以长度为0,容量为6; 在这里插入图片描述 3,左指针指向s[0],右指针指向s[4],由于切片概念是只包含左边元素不包含右边元素,所以长度为4,但左指针在s[0]处,走过0个元素,所以容量仍然为6; 在这里插入图片描述 4,在经历步骤3切片后的基础上,左指针指向s[2],右指针指向最右边,所以长度为2,由于左指针走过两个元素,离最右边还剩4个元素,所以容量为4。 在这里插入图片描述

本文内容最难理解的是切片的容量,我们可以把容量当做成总长度减去左指针走过的元素值,比如: s[:0] ——> cap = 6 - 0 =6; s[2:] ——> cap = 6 - 2 = 4。

0x02 切片的内幕

一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yci8EQBR-1585976257296)(https://blog.go-zh.org/go-slices-usage-and-internals_slice-struct.png)] 前面使用 make([]byte, 5) 创建的切片变量 s 的结构如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kR7U0t8j-1585976257297)(https://blog.go-zh.org/go-slices-usage-and-internals_slice-1.png)] 长度是切片引用的元素数目。容量是底层数组的元素数目(从切片指针开始)。 关于长度和容量和区域将在下一个例子说明。 我们继续对 s 进行切片,观察切片的数据结构和它引用的底层数组:

s = s[2:4]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZTU83Aa-1585976257299)(https://blog.go-zh.org/go-slices-usage-and-internals_slice-2.png)] 切片操作并不复制切片指向的元素。它创建一个新的切片并复用原来切片的底层数组。 这使得切片操作和数组索引一样高效。因此,通过一个新切片修改元素会影响到原始切片的对应元素。

d := []byte{'r', 'o', 'a', 'd'} e := d[2:] // e == []byte{'a', 'd'} e[1] = 'm' // e == []byte{'a', 'm'} // d == []byte{'r', 'o', 'a', 'm'}

前面创建的切片 s 长度小于它的容量。我们可以增长切片的长度为它的容量:

s = s[:cap(s)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t0aIOp7h-1585976257306)(https://blog.go-zh.org/go-slices-usage-and-internals_slice-3.png)] 切片增长不能超出其容量。增长超出切片容量将会导致运行时异常,就像切片或数组的索引超 出范围引起异常一样。同样,不能使用小于零的索引去访问切片之前的元素。

0x03 切片append package main import "fmt" func main() { s := []int{2, 3, 5, 7, 11, 13} printSlice(s) // 截取切片使其长度为 0 s = s[:0] printSlice(s) // 拓展其长度 s = s[:5] printSlice(s) // 第一次增加 s = append(s, 1) printSlice(s) // 第二次增加 s = append(s, 1) printSlice(s) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d ptr=%p %v\n", len(s), cap(s), s, s) } len=6 cap=6 ptr=0x450000 [2 3 5 7 11 13] len=0 cap=6 ptr=0x450000 [] len=5 cap=6 ptr=0x450000 [2 3 5 7 11] len=6 cap=6 ptr=0x450000 [2 3 5 7 11 1] len=7 cap=12 ptr=0x44e030 [2 3 5 7 11 1 1]

第一次append的时候,长度没有超过容量,所以容量没有变。 第二次append的时候,长度超过了容量,这时容量会扩展到原来的2倍。 同时,增加后的切片地址和原来不同,也就是说: append操作可能会导致原本使用同一个底层数组的两个Slice变量变为使用不同的底层数组。

0x04 切片append package main import "fmt" func main() { s := []int{2, 3, 5, 7, 11, 13} printSlice(s) // 截取切片使其长度为 0 s = s[:0] printSlice(s) // 拓展其长度 s = s[:4] printSlice(s) // append后再切片 s = append(s, 4) s = s[:6] printSlice(s) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } len=6 cap=6 [2 3 5 7 11 13] len=0 cap=6 [] len=4 cap=6 [2 3 5 7] len=6 cap=6 [2 3 5 7 4 13]

这里验证一个问题,在[2 3 5 7]基础上append一个4后,在全容量获取切片,发现最后一个13还在,只是11被4替换了。 这说明append的写入是把slice后边数据逐个覆盖掉。

0x05 切片copy

用于将内容从一个数组切片复制到另一个数组切片。如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行复制。

package main import "fmt" func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} slice3 := []int{5, 4, 3} copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中 printSlice(slice2) copy(slice1, slice3) // 只会复制slice3的3个元素到slice1的前3个位置 printSlice(slice1) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } len=3 cap=3 [1 2 3] len=5 cap=5 [5 4 3 4 5]

要增加切片的容量必须创建一个新的、更大容量的切片,然后将原有切片的内容复制到新的切片。 整个技术是一些支持动态数组语言的常见实现。下面的例子将切片 s 容量翻倍,先创建一个2倍 容量的新切片 t ,复制 s 的元素到 t ,然后将 t 赋值给 s :

t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0 for i := range s { t[i] = s[i] } s = t

循环中复制的操作可以由 copy 内置函数替代。copy 函数将源切片的元素复制到目的切片。 它返回复制元素的数目。

func copy(dst, src []T) int

copy 函数支持不同长度的切片之间的复制(它只复制较短切片的长度个元素)。 此外, copy 函数可以正确处理源和目的切片有重叠的情况。

使用 copy 函数,我们可以简化上面的代码片段:

t := make([]byte, len(s), (cap(s)+1)*2) copy(t, s) s = t

一个常见的操作是将数据追加到切片的尾部。下面的函数将元素追加到切片尾部, 必要的话会增加切片的容量,最后返回更新的切片:

func AppendByte(slice []byte, data ...byte) []byte { m := len(slice) n := m + len(data) if n > cap(slice) { // if necessary, reallocate // allocate double what's needed, for future growth. newSlice := make([]byte, (n+1)*2) copy(newSlice, slice) slice = newSlice } slice = slice[0:n] copy(slice[m:n], data) return slice }

下面是 AppendByte 的一种用法:

p := []byte{2, 3, 5} p = AppendByte(p, 7, 11, 13) // p == []byte{2, 3, 5, 7, 11, 13}

类似 AppendByte 的函数比较实用,因为它提供了切片容量增长的完全控制。 根据程序的特点,可能希望分配较小的活较大的块,或则是超过某个大小再分配。

0x06 总结 切片的长度和容量不同,长度表示左指针至右指针之间的距离,容量表示左指针至底层数组末尾的距离。切片的扩容机制,append的时候,如果长度增加后超过容量,则将容量增加2倍,同时变换了底层数组。切片append机制,是把slice后边数据逐个覆盖写入。切片copy机制,按其中较小的那个数组切片的元素个数进行复制。 0x07 参考

Golang中Slice的append详解 Golang数组与Slice,以及append函数的陷阱 Go指南_切片的长度与容量 Go 切片:用法和本质



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3