设为首页 加入收藏

TOP

Go语言-Slice详解(一)
2023-09-09 10:25:40 】 浏览:394
Tags:语言 -Slice 详解

Go语言中的slice表示一个具有相同类型元素的可变长序列,语言本身提供了两个操作方法:

  1. 创建:make([]T,len,cap)
  2. 追加: append(slice, T ...)

同时slice支持随机访问。本篇文章主要对slice的具体实现进行总结。

1. 数据结构

go语言的slice有三个主要的属性:

  1. 指针:slice的首地址指针
  2. 长度:slice中元素的个数
  3. 容量:由于slice底层结构本身物理空间可能更大,因此该值记录slice实际空间大小。

因此,在golang官网中的Go Slices: usage and internals对slice的描述如下:

A slice is a descriptor of an array segment. It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment).

slice是一段array,包括了上面的三个部分,他的物理结构如下:

如果我们通过make([]byte,5,5)创建了一个len=5,cap=5的slice,其物理结构如此:

如果我们仅仅想使用原数组的一部分,例如:

s = s[2:4]

则s的物理结构如此:

但实际上,这两者所引用的是同一块连续的空间,如果我们修改其中一个,另一个也会跟着修改。实际上,slice在go语言中的代码表示为:

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

我们是如何知道这件事的呢?请看继续阅读该文章。

操作

go语言为slice提供了两个修改类操作:

  1. 创建
  2. 追加

接下来我们会对这两个操作进行分析。

1. 创建slice

slice的定义(分配空间)有三种方式:

  1. 字面量创建:s := []int{1,2,3}
  2. 内置函数make创建:make([]T, len, cap)
  3. 切取其他数据结构:s := array[1:2]

还有两种声明方式(不分配空间):

  1. var s []int
  2. s := []int{}

接下来我们通过一组示例代码,查看slice的创建流程,以及上面的定义与声明的区别。

  1. 字面量创建方式

    // main.go
    package main
    
    import "fmt"
    
    func main() {
        s1 := []int{1,2,3}
        fmt.Println(s1)
    }
    

    这组代码给出了一个通过字面量方式创建的slice s1,我们通过delve工具对这部分代码进行debug。命令行进入到main.go所在目录,键入如下命令:

    dlv debug
    
    # 为main包的main函数第1行即文件第7行打上断点
    b main.go:7
    
    # 运行到断点处
    c
    
    # 对要运行的部分进行反汇编
    disassemble
    

    我们就可以看到如下代码:

    TEXT main.main(SB) D:/code/Notes/docs/go/list/main.go
            main.go:6       0x948300        493b6610                cmp rsp, qword ptr [r14+0x10]
            main.go:6       0x948304        0f86f5000000            jbe 0x9483ff
            main.go:6       0x94830a        4883ec78                sub rsp, 0x78
            main.go:6       0x94830e        48896c2470              mov qword ptr [rsp+0x70], rbp
            main.go:6       0x948313        488d6c2470              lea rbp, ptr [rsp+0x70]
    =>      main.go:7       0x948318*       488d05a1940000          lea rax, ptr [rip+0x94a1]
            main.go:7       0x94831f        90                      nop
            # 调用runtime的newobject创建一个新的对象
            main.go:7       0x948320        e81b53f6ff              call $runtime.newobject
            # 将调用结果(即新slice的地址)存到栈顶中
            main.go:7       0x948325        4889442428              mov qword ptr [rsp+0x28], rax
            # 把1放入slice中
            main.go:7       0x94832a        48c70001000000          mov qword ptr [rax], 0x1
            # 从栈顶将slice的地址取出放入rcx寄存器中
            main.go:7       0x948331        488b4c2428              mov rcx, qword ptr [rsp+0x28]
            main.go:7       0x948336        8401                    test byte ptr [rcx], al
            # 把2放入slice中
            main.go:7       0x948338        48c7410802000000        mov qword ptr [rcx+0x8], 0x2
            main.go:7       0x948340        488b4c2428              mov rcx, qword ptr [rsp+0x28]
            main.go:7       0x948345        8401                    test byte ptr [rcx], al
            # 把3放入slice中
            main.go:7       0x948347        48c7411003000000        mov qword ptr [rcx+0x10], 0x3
            main.go:7       0x94834f        488b4c2428              mov rcx, qword ptr [rsp+0x28]
            main.go:7       0x948354        8401                    test byte ptr [rcx], al
            main.go:7       0x948356        eb00                    jmp 0x948358
            # 最后设置slice的指针,并将len和cap都设置为3
            main.go:7       0x948358        48894c2440              mov qword ptr [rsp+0x40], rcx
            main.go:7       0x94835d        48c744244803000000      mov qword ptr [rsp+0x48], 0x3
            main.go:7       0x948366        48c744245003000000      mov qword ptr [rsp+0x50], 0x3
    

    由此可见,使用字面量创建slice时,len和cap都会设置为初始化数据的个数。

    可以简单看一下刚才使用的runtime.newobject(),该函数在runtime/malloc.go文件中,代码如下:

    func newobject(typ *_type) unsafe.Pointer {
        return mallocgc(typ.size, typ, true)
    }
    

    本质上还是通过内存管理机制为一个对象申请一块连续空间并返回对应指针。

  2. make函数创建:

    // main.go
    package main
    
    import "fmt"
    
    func main() {
        s := make([]int, 10,20)
        fmt.Println(s)
    }
    

    该例子使用make方式创建了slice s,其len=10,cap=20,同样使用delve进行debug,脚本同上,我们得到的反汇编结果如下:

    TEXT main.main(SB) D:/code/Notes/docs/go/list/main.go
            main.go:6       0xea8300        493b6610                cmp rsp, qword ptr [r14+0x10]
            main.go:6       0xea8304        0f86
首页 上一页 1 2 3 4 5 6 下一页 尾页 1/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇gRPC with JWT 下一篇Go 格式化动词

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目