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

相关推荐
一丝晨光14 小时前
数值溢出保护?数值溢出应该是多少?Swift如何让整数计算溢出不抛出异常?类型最大值和最小值?
java·javascript·c++·rust·go·c·swift
陌尘(MoCheeen)2 天前
技术书籍推荐(002)
java·javascript·c++·python·go
白泽来了3 天前
字节大模型应用开发框架 Eino 全解(一)|结合 RAG 知识库案例分析框架生态
开源·go·大模型应用开发
致于数据科学家的小陈4 天前
Go 层级菜单树转 json 处理
python·go·json·菜单树·菜单权限·children
白总Server5 天前
Golang领域Beego框架的中间件开发实战
服务器·网络·websocket·网络协议·udp·go·ssl
ん贤6 天前
GoWeb开发
开发语言·后端·tcp/ip·http·https·go·goweb
纪元A梦6 天前
华为OD机试真题——荒岛求生(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
java·c语言·javascript·c++·python·华为od·go
chxii8 天前
3.2goweb框架GORM
go
42fourtytoo9 天前
从0开始建立Github个人博客(hugo&PaperMod)
运维·服务器·python·go·github
xuhe210 天前
[tldr] GO语言异常处理
go·error