Go语言中的切片操作记录

2023年 9月 2日 31.4k 0

一、前言

在Go语言中,数组和切片是常见的数据结构,它们在处理集合数据和数据操作中起着重要的作用。本文将首先回顾数组的特性,然后深入探讨切片的创建、操作、以及底层原理,最后进行总结。

二、内容

2.1 回顾数组

切片类型是在Go语言的数组类型之上构建的抽象,因此要理解切片,我们首先需要了解数组。

我们来回顾一下数组的特点:

  • 长度固定:数组的长度在声明时就确定了,不能动态增加或减少。例如[4]int表示由四个整数组成的数组。
  • 元素类型固定:数组中所有元素的类型必须相同。在[4]int中,所有元素都是整数。
  • 数组变量是值:数组变量表示整个数组,而不是指向第一个元素的指针。这意味着在赋值或传递数组时会复制其内容。
  • 零值初始化:如果不显式初始化数组,它将被初始化为其元素类型的零值。例如,[4]int的零值是一个包含四个零的整数数组。
  • 数组字面值:可以使用数组字面值来初始化数组。您可以指定每个元素的值,也可以让编译器计算元素数量。
  • 下面举一些例子:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        // 声明并初始化一个包含4个整数的数组
        var a [4]int
        a[0] = 1
        a[1] = 2
        a[2] = 3
        a[3] = 4
    
        // 使用数组字面值初始化数组
        b := [4]int{1, 2, 3, 4}
    
        // 让编译器计算数组元素数量
        c := [...]int{1, 2, 3, 4, 5}
    
        // 遍历数组元素
        for i := 0; i < len(b); i++ {
            fmt.Println(b[i])
        }
    
        // 使用range遍历数组元素
        for index, value := range c {
            fmt.Printf("Index: %d, Value: %d\n", index, value)
        }
    }
    

    运行结果如下:

    1
    2
    3
    4
    Index: 0, Value: 1
    Index: 1, Value: 2
    Index: 2, Value: 3
    Index: 3, Value: 4
    Index: 4, Value: 5
    

    2.2 切片

    切片是Go语言中非常重要且常用的数据结构,相对于数组更灵活,因为它们的长度是可变的。

    切片的类型规范是[]T,其中T是切片元素的类型。切片没有指定的长度,因此可以根据需要动态增加或减少元素数量。

    切片的声明方式与数组相似,只是省略了元素数量。

    比如 :

    letters := []string{"a", "b", "c", "d"}
    

    一般来说,我们可以使用内置函数make来创建切片。make函数的签名为func make([]T, len, cap) []T,其中:

    • T表示切片的元素类型
    • len是切片的长度
    • cap是可选的容量。容量表示底层数组的大小,通常与长度相等,但可以提前分配更大的容量以提高性能。

    举个例子:

    // 创建一个切片并初始化
    s := make([]byte, 5)  // 长度为5,容量为5的字节切片
    // s == []byte{0, 0, 0, 0, 0}
    
    // 创建切片并指定容量
    s2 := make([]int, 3, 5)  // 长度为3,容量为5的整数切片
    // s2 == []int{0, 0, 0}
    

    另外,当我们需要获取切片的长度和容量时,可以使用内置函数lencap

    s := []int{1, 2, 3, 4, 5}
    length := len(s)  // 获取切片的长度,length == 5
    capacity := cap(s)  // 获取切片的容量,capacity == 5
    

    可以发现,切片就是一种动态的数组,非常灵活且常用于处理集合数据。它们允许您根据需要添加或删除元素,并提供了便捷的操作方法。

    2.3 "切片"

    首先介绍一下,在Go语言中,切片的零值是nil,这意味着切片变量未分配底层数组。对于nil切片,lencap函数都将返回0。

    举个例子:

    var s []int  // 声明一个nil切片
    fmt.Println(len(s))  // 输出0
    fmt.Println(cap(s))  // 输出0
    

    现在,让我们来关注一下,如何利用 “切片” 的方式来引用某个切片。

    "切片"的创建使用半开区间,指定起始索引和结束索引。起始索引包含在切片内,但结束索引不包含在切片内。

    举一个例子:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
        slice := b[1:4]    // “切片”的方式来创建(引用)一个切片
        fmt.Println(slice) // slice == []byte{'o', 'l', 'a'}
        // 注意,切片共享底层数组的存储空间
        b[2] = 'x'
        fmt.Println(slice) // slice == []byte{'o', 'x', 'a'}
    }
    

    另外,如果不指定切片表达式的起始和结束索引,它们将分别默认为0和切片的长度。

    示例代码:

    b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
    slice1 := b[:2]  // 从开头到索引2之前(不包括2)
    // slice1 == []byte{'g', 'o'}
    
    slice2 := b[2:]  // 从索引2到末尾(包括2)
    // slice2 == []byte{'l', 'a', 'n', 'g'}
    
    slice3 := b[:]   // 完整的切片
    // slice3 == b
    

    切片与其底层数组共享存储空间,这意味着对切片的修改也会影响底层数组。因此,在上述代码中修改b的元素也会影响slice的元素。

    2.4 原理

    现在,我们来浅谈一下切片的原理。

    事实上,切片是对数组的一个描述,其内部结构包括三部分:

    • 指针(Pointer):指向底层数组的起始位置。
    • 长度(Length):表示切片引用的元素数量。
    • 容量(Capacity):表示底层数组中从切片指针指向的元素开始到数组末尾的元素数量。

    也就是说,当我们进行切片操作时,并不会复制切片的数据,而是创建了一个指向原始数组的新切片值。这使得切片操作与操作数组索引一样高效,与直接操作数组索引一样。

    但是,由于切片共享底层数组的存储空间,因此修改刚“切片”的元素后,原始切片的相应元素也会被修改。

    还是举一个例子:

    package main
    
    func main() {
        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'}
    }
    

    三、总结

    本文详细介绍了Go语言中数组和切片的操作。数组具有固定长度和元素类型的特点,而切片更加灵活,可以动态调整长度。切片的创建使用半开区间方式,共享底层数组的存储空间,因此对切片的修改会影响原始数据。总的来说,切片为数据操作提供了高效的工具。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论