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

这篇具有很好参考价值的文章主要介绍了Go 切片Slice的长度len与容量cap。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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]
1
2
3
4
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]
1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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'}
1
2
3
4
5
6
前面创建的切片 s 长度小于它的容量。我们可以增长切片的长度为它的容量:

s = s[:cap(s)]
1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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]
1
2
3
4
5
第一次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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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]
1
2
3
4
这里验证一个问题,在[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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
len=3 cap=3 [1 2 3]
len=5 cap=5 [5 4 3 4 5]
1
2
要增加切片的容量必须创建一个新的、更大容量的切片,然后将原有切片的内容复制到新的切片。 整个技术是一些支持动态数组语言的常见实现。下面的例子将切片 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
1
2
3
4
5
循环中复制的操作可以由 copy 内置函数替代。copy 函数将源切片的元素复制到目的切片。 它返回复制元素的数目。

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

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

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t
1
2
3
一个常见的操作是将数据追加到切片的尾部。下面的函数将元素追加到切片尾部, 必要的话会增加切片的容量,最后返回更新的切片:

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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
下面是 AppendByte 的一种用法:

p := []byte{2, 3, 5}
p = AppendByte(p, 7, 11, 13)
// p == []byte{2, 3, 5, 7, 11, 13}
1
2
3
类似 AppendByte 的函数比较实用,因为它提供了切片容量增长的完全控制。 根据程序的特点,可能希望分配较小的活较大的块,或则是超过某个大小再分配。

0x06 总结
切片的长度和容量不同,长度表示左指针至右指针之间的距离,容量表示左指针至底层数组末尾的距离。
切片的扩容机制,append的时候,如果长度增加后超过容量,则将容量增加2倍,同时变换了底层数组。
切片append机制,是把slice后边数据逐个覆盖写入。
切片copy机制,按其中较小的那个数组切片的元素个数进行复制。
0x07 参考
Golang中Slice的append详解
Golang数组与Slice,以及append函数的陷阱
Go指南_切片的长度与容量
Go 切片:用法和本质
————————————————
版权声明:本文为CSDN博主「Shower稻草人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013474436/article/details/88770501文章来源地址https://www.toymoban.com/news/detail-492870.html

到了这里,关于Go 切片Slice的长度len与容量cap的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • go语言Array 与 Slice

    有的语言会把数组用作常用的基本的数据结构,比如 JavaScript,而 Golang 中的数组(Array),更倾向定位于一种底层的数据结构,记录的是一段连续的内存空间数据。但是在 Go 语言中平时直接用数组的时候不多,大多数场景下我们都会直接选用更加灵活的切片(Slice) 声明与初始化

    2024年02月07日
    浏览(37)
  • go语言(七)----slice的声明方式

    1、声明方式一 2、声明方式二 3、声明方式三 二、切片容量的追加 1、切片的长度和容量不同,长度表示左指针到右指针之间的距离。容量表示左指针到底层数组末尾的距离。 2、切片的扩容机制,append的时候,如果长度增加后超过容量,则将容量增加2倍。 3、切片的截取

    2024年01月21日
    浏览(41)
  • 2023-05-20:go语言的slice和rust语言的Vec的扩容流程是什么?

    2023-05-20:go语言的slice和rust语言的Vec的扩容流程是什么? 答案2023-05-20: go版本是1.20.4。 扩容流程见源码见runtime/slice.go文件中的 growslice 函数。 growslice 函数的大致过程如下: 1.如果元素类型的大小为零,则返回具有 nil 指针但非零长度的切片。否则,下一步。 2.计算新切片的

    2024年02月05日
    浏览(44)
  • Go语言基础之切片

    切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。 切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合 声明切片类型的基本语法如下: 其中, name:表示变

    2024年02月11日
    浏览(54)
  • go语言中的切片

    切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。 切片是一个引用类型,它的内部结构包含 地址 、 长度 和 容量 。切片一般用于快速地操作一块数据集合。 切片的本质就是对底层数组的封装,它包含了

    2024年02月11日
    浏览(41)
  • Go 语言切片是如何扩容的?

    原文链接: Go 语言切片是如何扩容的? 在 Go 语言中,有一个很常用的数据结构,那就是切片(Slice)。 切片是一个拥有相同类型元素的可变长度的序列,它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。 切片是一种引用类型,它有三个属性: 指针 , 长度 和

    2023年04月09日
    浏览(39)
  • 【go语言】2.2.1 数组和切片

    数组和切片是 Go 语言中常用的数据结构,它们都可以存储多个同类型的元素。 数组是具有固定长度的数据类型,它的长度在定义时就已经确定,不能随意改变。 你可以使用以下方式定义一个数组: 这段代码定义了一个长度为 3 的  int  类型数组  arr 。你也可以在定义时初

    2024年02月15日
    浏览(48)
  • Python中的切片(Slice)操作详解

    大家好,这里是果力成,话不多说,学之! 在python学习开发的过程中,我们难免会遇到从某个对象中抽取部分值的情况,对这种经常取指定索引的范围的操作,如果你使用循环的话是可以解决的,但是相对更好的方法而言,此操作是相当繁琐和费事的。 python中的 切片操作 正是专门用

    2024年02月01日
    浏览(46)
  • Golang学习——string和slice切片

    1个字节可以表示256个数字,2个字节可以表示65536个数字 字符集:字符编号对照表。收录很多数字,给它们一一编号。 字符集的更迭 全球化统一标准——通用字符集 于1990年开始研发并于1994年正式公布,实现了跨语言跨平台的文本转换与处理。 字符集促成了字符与二进制的

    2024年02月15日
    浏览(39)
  • 【Go语言快速上手(三)】数组, 切片与映射

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:Go语言专栏⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习更多Go语言知识   🔝🔝 在了解过GO的控制语句和函数后,按照学习语言的逻辑也理应进入到容器的学习,GO语言的容易比较特殊,它不像C++一样有专门的STL库

    2024年04月29日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包