Go语言中的切片操作记录

一、前言

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

二、内容

2.1 回顾数组

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

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

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

下面举一些例子:

go 复制代码
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)
    }
}

运行结果如下:

bash 复制代码
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是切片元素的类型。切片没有指定的长度,因此可以根据需要动态增加或减少元素数量。

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

比如 :

go 复制代码
letters := []string{"a", "b", "c", "d"}

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

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

举个例子:

go 复制代码
// 创建一个切片并初始化
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

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

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

2.3 "切片"

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

举个例子:

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

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

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

举一个例子:

go 复制代码
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和切片的长度。

示例代码:

go 复制代码
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):表示底层数组中从切片指针指向的元素开始到数组末尾的元素数量。

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

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

还是举一个例子:

go 复制代码
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语言中数组和切片的操作。数组具有固定长度和元素类型的特点,而切片更加灵活,可以动态调整长度。切片的创建使用半开区间方式,共享底层数组的存储空间,因此对切片的修改会影响原始数据。总的来说,切片为数据操作提供了高效的工具。

相关推荐
刻意思考4 个月前
那篇被网暴的文章
后端·程序员·掘金·日新计划
WAsbry5 个月前
HarmonyOS 开发:我想先告诉你这些(一)
android·程序员·掘金·日新计划
乐知乐之5 个月前
信号量(semaphore):解决并发问题的有力工具
后端·掘金·日新计划
程序员皮卡秋7 个月前
一起来学阿里巴巴Java开发手册(二)
java·后端·掘金·日新计划
程序员皮卡秋7 个月前
一起来学阿里巴巴Java开发手册(一)
java·后端·掘金·日新计划
祯民7 个月前
聊聊焦虑和内耗:这事我有资格做吗?
面试·掘金·日新计划·创业
波小艺8 个月前
为了测试重构接口,我开发了接口测试比对工具
程序员·测试·掘金·日新计划
Xiao镔8 个月前
一次触发线程池拒绝策略问题的排查
java·面试·掘金·日新计划
工程师酷里9 个月前
99年师弟,揭露华为工作的残酷真相
求职·掘金·日新计划
程序员皮卡秋9 个月前
一起来学Mybatis Plus(四) & Service CRUD接口
后端·mybatis·掘金·日新计划