一起学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函数对其追加元素的时候就不会引起扩容。这只会使紧邻切片窗口右边的(底层数组中的)元素被新的元素替换掉。

相关推荐
童先生7 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
幼儿园老大*8 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
架构师那点事儿13 小时前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
于顾而言1 天前
【笔记】Go Coding In Go Way
后端·go
qq_172805591 天前
GIN 反向代理功能
后端·golang·go
follycat1 天前
2024强网杯Proxy
网络·学习·网络安全·go
OT.Ter2 天前
【力扣打卡系列】单调栈
算法·leetcode·职场和发展·go·单调栈
探索云原生2 天前
GPU 环境搭建指南:如何在裸机、Docker、K8s 等环境中使用 GPU
ai·云原生·kubernetes·go·gpu
OT.Ter2 天前
【力扣打卡系列】移动零(双指针)
算法·leetcode·职场和发展·go
码财小子2 天前
k8s 集群中 Golang pprof 工具的使用
后端·kubernetes·go