一起学GO吧:Go语言中的数组和切片类型

在Go语言中,数组和切片是两种常用的集合类型。它们之间的主要区别在于数组的长度是固定的,而切片的长度是可变的。本文将介绍数组和切片的基本概念、特点以及使用示例。

1. 数组

数组是一种固定长度的数据结构,其长度在声明时就已经确定,之后不能改变。数组的类型由元素类型决定。例如,一个包含整数的数组可以表示为[1]int[2]int

go 复制代码
package main

import "fmt"

func main() {
    var a1 int = 10
    var a2 int = 20
    fmt.Println(a1, a2) // 输出: 10 20
}

2. 切片

切片是一种灵活的数据结构,其长度是可变的。切片的类型只包含元素的类型,而不包含长度。切片可以通过字面量或通过内置函数创建。切片的底层数据结构是一个动态数组。

go 复制代码
package main

import "fmt"

func main() {
    s := []int{1, 2, 3, 4, 5}
    fmt.Println(s) // 输出: [1 2 3 4 5]
}

3. 数组和切片的区别

  • 数组的长度是固定的,而切片的长度是可变的。
  • 数组的元素类型必须相同,而切片的元素类型可以不同。
  • 数组是值类型,而切片是引用类型。这意味着对数组的修改不会影响到原始数组,而对切片的修改会影响到原始数组。

4. 使用示例

下面是一些使用数组和切片的示例代码:

4.1 创建数组

go 复制代码
package main

import "fmt"

func main() {
    var arr1 [2]int = [2]int{1, 2}
    var arr2 [3]int = [3]int{3, 4, 5}
    fmt.Println(arr1, arr2) // 输出: [1 2] [3 4 5]
}

4.2 创建切片

go 复制代码
package main

import "fmt"

func main() {
    var s1 []int = []int{1, 2, 3, 4, 5}
    var s2 []int = make([]int, 3)
    fmt.Println(s1, s2) // 输出: [1 2 3 4 5] [0 0 0]
}

4.3 访问数组和切片的元素

go 复制代码
package main

import "fmt"

func main() {
    var arr1 [2]int = [2]int{1, 2}
    var arr2 [3]int = [3]int{3, 4, 5}
    var s1 []int = []int{1, 2, 3, 4, 5}
    var s2 []int = make([]int, 3)

    fmt.Println(arr1[0], arr1[1]) // 输出: 1 2
    fmt.Println(arr2[0], arr2[1], arr2[2]) // 输出: 3 4 5
    fmt.Println(s1[0], s1[1], s1[2]) // 输出: 1 2 3
    fmt.Println(s2[0], s2[1], s2[2]) // 输出: 0 0 0
}

4.4 修改数组和切片的元素

go 复制代码
package main

import "fmt"

func main() {
    var arr1 [2]int = [2]int{1, 2}
    var arr2 [3]int = [3]int{3, 4, 5}
    var s1 []int = []int{1, 2, 3, 4, 5}
    var s2 []int = make([]int, 3)

    arr1[0] = 10
    arr2[1] = 20
    s1[2] = 30
    s2[0] = 40

    fmt.Println(arr1, arr2) // 输出: [10 2] [3 20 5]
    fmt.Println(s1, s2) // 输出: [1 2 30] [40 0 0]
}

4.5 遍历数组和切片

go 复制代码
package main

import "fmt"

func main() {
    var arr1 [2]int = [2]int{1, 2}
    var arr2 [3]int = [3]int{3, 4, 5}
    var s1 []int = []int{1, 2, 3, 4, 5}
    var s2 []int = make([]int, 3)

    for i := 0; i < len(arr1); i++ {
        fmt.Println(arr1[i]) // 输出: 1 2
    }

    for i := 0; i < len(arr2); i++ {
        fmt.Println(arr2[i]) // 输出: 3 4 5
    }

    for i := 0; i < len(s1); i++ {
        fmt.Println(s1[i]) // 输出: 1 2 3 4 5
    }

    for i := 0; i < len(s2); i++ {
        fmt.Println(s2[i]) // 输出: 40 0 0
    }
}

5. 知识扩展

1. 问题:怎样估算切片容量的增长?

一旦一个切片无法容纳更多的元素,Go 语言就会想办法扩容。但它并不会改变原来的切片,而是会生成一个容量更大的切片,然后将把原有的元素和新元素一并拷贝到新切片中。在一般的情况下,你可以简单地认为新切片的容量(以下简称新容量)将会是原切片容量(以下简称原容量)的 2 倍。

但是,当原切片的长度(以下简称原长度)大于或等于1024时,Go 语言将会以原容量的1.25倍作为新容量的基准(以下新容量基准)。新容量基准会被调整(不断地与1.25相乘),直到结果不小于原长度与要追加的元素数量之和(以下简称新长度)。最终,新容量往往会比新长度大一些,当然,相等也是可能的。

另外,如果我们一次追加的元素过多,以至于使新长度比原容量的 2 倍还要大,那么新容量就会以新长度为基准。注意,与前面那种情况一样,最终的新容量在很多时候都要比新容量基准更大一些。更多细节可参见runtime包中 slice.go 文件里的growslice及相关函数的具体实现。

2. 问题:切片的底层数组什么时候会被替换?

确切地说,一个切片的底层数组永远不会被替换。为什么?虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。它是把新的切片作为了新底层数组的窗口,而没有对原切片及其底层数组做任何改动。

请记住,在无需扩容时,append函数返回的是指向原底层数组的新切片,而在需要扩容时,append函数返回的是指向新底层数组的新切片。所以,严格来讲,"扩容"这个词用在这里虽然形象但并不合适。不过鉴于这种称呼已经用得很广泛了,我们也没必要另找新词了。

顺便说一下,只要新长度不会超过切片的原容量,那么使用append函数对其追加元素的时候就不会引起扩容。这只会使紧邻切片窗口右边的(底层数组中的)元素被新的元素替换掉。

相关推荐
研究司马懿13 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大1 天前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰1 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo