Go语言中的数组是一种固定长度的线性数据结构,所有元素必须是相同类型,且数组的长度在定义后不可更改。Go语言数组的详细讲解,包含定义、初始化、操作、特性及代码示例。


一、数组的定义

数组的定义格式为:

var 数组名 [长度]元素类型
  • 数组的长度:必须是常量表达式(如整数常量或const定义的常量),且长度是数组类型的一部分。
  • 元素类型:可以是任何基本类型(如intstring)或自定义类型(如结构体)。

示例:定义数组

// 定义一个长度为5的int类型数组
var numbers [5]int

// 定义一个长度为3的字符串数组
var names [3]string

二、数组的初始化

Go语言提供了多种初始化数组的方式:

1. 显式初始化

// 初始化数组元素
var numbers = [5]int{1, 2, 3, 4, 5}
fmt.Println(numbers) // 输出: [1 2 3 4 5]

2. 隐式初始化

未指定的元素会自动初始化为对应类型的零值(如int的零值是0string的零值是空字符串)。

var numbers = [5]int{1, 2} // 剩余元素初始化为0
fmt.Println(numbers)        // 输出: [1 2 0 0 0]

3. 自动推断长度

使用...让编译器根据初始化元素的数量自动推断数组长度:

var numbers = [...]int{1, 2, 3, 4, 5}
fmt.Println(len(numbers)) // 输出: 5

4. 指定索引初始化

可以通过索引直接初始化特定位置的元素:

var sparseArray = [...]int{1: 10, 3: 30}
fmt.Println(sparseArray) // 输出: [0 10 0 30]

5. 多维数组初始化

// 定义一个2x3的二维数组
var matrix [2][3]int = [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}
fmt.Println(matrix) // 输出: [[1 2 3] [4 5 6]]

三、数组的操作

1. 访问数组元素

通过下标访问数组元素,下标从0开始,最大值为len(array)-1。越界访问会触发运行时错误(panic)。

numbers := [5]int{10, 20, 30, 40, 50}
fmt.Println(numbers[0]) // 输出: 10
fmt.Println(numbers[4]) // 输出: 50

2. 修改数组元素

numbers := [5]int{10, 20, 30, 40, 50}
numbers[2] = 100        // 修改索引为2的元素
fmt.Println(numbers)    // 输出: [10 20 100 40 50]

3. 遍历数组

  • 普通for循环

    numbers := [5]int{1, 2, 3, 4, 5}
    for i := 0; i < len(numbers); i++ {
        fmt.Println("Index:", i, "Value:", numbers[i])
    }
    
  • range关键字

    numbers := [5]int{1, 2, 3, 4, 5}
    for index, value := range numbers {
        fmt.Println("Index:", index, "Value:", value)
    }
    

四、数组的特性

1. 数组是值类型

  • 赋值:数组赋值会复制整个数组。
  • 传参:将数组作为函数参数传递时,会复制整个数组(而非引用)。

示例:数组赋值与传参

func modifyArray(arr [3]int) {
    arr[0] = 100
    fmt.Println("Inside function:", arr)
}

func main() {
    arr := [3]int{1, 2, 3}
    modifyArray(arr) // 传入的是数组的副本
    fmt.Println("Outside function:", arr) // 输出: [1 2 3]
}

2. 数组的比较

Go语言允许直接使用==!=比较两个数组是否相等,前提是它们的类型和长度相同。

arr1 := [3]int{1, 2, 3}
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{1, 2, 4}

fmt.Println(arr1 == arr2) // 输出: true
fmt.Println(arr1 == arr3) // 输出: false

在 Go 语言中,如果两个数组的 类型或长度不同,无法直接使用 ==!= 进行比较(因为这会触发编译错误)。此时需要通过其他方式间接比较,具体取决于你的需求。


1. 手动比较元素(适用于类型或长度不同的数组)

如果两个数组的元素类型相同但长度不同,或者类型不同(例如 [3]int[5]int),可以通过以下步骤比较:

步骤:
  1. 检查类型和长度是否匹配:如果不匹配,直接返回 false
  2. 逐元素比较:如果类型和长度匹配,逐个比较元素;如果不匹配,可以选择忽略长度差异或截断处理。
示例代码:
func compareArrays(arr1, arr2 interface{}) bool {
    // 使用反射获取数组类型和值
    v1 := reflect.ValueOf(arr1)
    v2 := reflect.ValueOf(arr2)

    // 检查是否为数组类型
    if v1.Kind() != reflect.Array || v2.Kind() != reflect.Array {
        return false
    }

    // 检查元素类型是否相同
    if v1.Type().Elem() != v2.Type().Elem() {
        return false
    }

    // 获取较短的数组长度
    minLen := v1.Len()
    if v2.Len() < minLen {
        minLen = v2.Len()
    }

    // 逐元素比较
    for i := 0; i < minLen; i++ {
        if !reflect.DeepEqual(v1.Index(i).Interface(), v2.Index(i).Interface()) {
            return false
        }
    }

    // 如果长度不同,返回 false
    return v1.Len() == v2.Len()
}
使用示例:
arr1 := [3]int{1, 2, 3}
arr2 := [3]int{1, 2, 3}
arr3 := [5]int{1, 2, 3, 4, 5}

fmt.Println(compareArrays(arr1, arr2)) // true
fmt.Println(compareArrays(arr1, arr3)) // false (类型相同但长度不同)
fmt.Println(compareArrays(arr1, [3]string{"a", "b", "c"})) // false (类型不同)

2. 使用 reflect.DeepEqual(适用于任意类型数组)

如果数组的类型或长度不同,但你希望比较它们的元素值(忽略类型或长度差异),可以使用 reflect.DeepEqual,但需要额外处理。

示例代码:
import "reflect"

func compareArraysDeepEqual(arr1, arr2 interface{}) bool {
    // 检查是否为数组类型
    v1 := reflect.ValueOf(arr1)
    v2 := reflect.ValueOf(arr2)
    if v1.Kind() != reflect.Array || v2.Kind() != reflect.Array {
        return false
    }

    // 比较元素类型是否相同
    if v1.Type().Elem() != v2.Type().Elem() {
        return false
    }

    // 使用 DeepEqual 比较整个数组
    return reflect.DeepEqual(arr1, arr2)
}

使用示例:

arr1 := [3]int{1, 2, 3}
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{1, 2, 4}

fmt.Println(compareArraysDeepEqual(arr1, arr2)) // true
fmt.Println(compareArraysDeepEqual(arr1, arr3)) // false

3. 转换为切片后比较(适用于动态长度场景)

如果数组的长度可能不同,但你希望比较它们的元素值(忽略长度差异),可以将数组转换为切片后再比较。

示例代码:
func compareArraySlices(arr1, arr2 interface{}) bool {
    v1 := reflect.ValueOf(arr1)
    v2 := reflect.ValueOf(arr2)

    // 将数组转换为切片
    slice1 := reflect.MakeSlice(reflect.SliceOf(v1.Type().Elem()), v1.Len(), v1.Len())
    reflect.Copy(slice1, v1)
    slice2 := reflect.MakeSlice(reflect.SliceOf(v2.Type().Elem()), v2.Len(), v2.Len())
    reflect.Copy(slice2, v2)

    // 比较切片
    return reflect.DeepEqual(slice1.Interface(), slice2.Interface())
}
使用示例:
arr1 := [3]int{1, 2, 3}
arr2 := [5]int{1, 2, 3, 4, 5}

fmt.Println(compareArraySlices(arr1, arr2)) // false (元素值不完全匹配)

4. 自定义逻辑处理(根据业务需求)

如果数组的类型或长度不同,但你需要自定义比较逻辑(例如忽略长度差异、部分匹配等),可以编写特定的比较函数。

示例:忽略长度差异,比较前 N 个元素
func compareFirstNElements(arr1, arr2 interface{}, n int) bool {
    v1 := reflect.ValueOf(arr1)
    v2 := reflect.ValueOf(arr2)

    // 检查是否为数组类型
    if v1.Kind() != reflect.Array || v2.Kind() != reflect.Array {
        return false
    }

    // 检查元素类型是否相同
    if v1.Type().Elem() != v2.Type().Elem() {
        return false
    }

    // 获取最小长度
    minLen := v1.Len()
    if v2.Len() < minLen {
        minLen = v2.Len()
    }
    if n > minLen {
        n = minLen
    }

    // 比较前 n 个元素
    for i := 0; i < n; i++ {
        if !reflect.DeepEqual(v1.Index(i).Interface(), v2.Index(i).Interface()) {
            return false
        }
    }

    return true
}
使用示例:
arr1 := [5]int{1, 2, 3, 4, 5}
arr2 := [3]int{1, 2, 3}

fmt.Println(compareFirstNElements(arr1, arr2, 3)) // true

总结
方法适用场景优点缺点
手动比较元素类型或长度不同的数组灵活,可自定义逻辑需要额外代码
reflect.DeepEqual类型相同的数组简洁,直接比较所有元素无法处理类型不同的数组
转换为切片后比较动态长度场景支持动态比较性能开销较大
自定义逻辑(如忽略长度差异)业务需求特殊(如部分匹配)完全控制比较逻辑实现复杂

注意事项
  1. 类型安全:如果数组的元素类型不同(如 [3]int[3]string),比较时需确保类型一致,否则会触发运行时错误。
  2. 性能:反射(reflect 包)的性能开销较高,不适合频繁调用。
  3. 切片 vs 数组:如果需要动态长度的数据结构,建议使用切片([]T),但切片本身不能直接比较(需转换为数组或使用 reflect.DeepEqual)。

3. 获取数组的长度和容量

  • len(array):获取数组的长度(元素个数)。
  • cap(array):对于数组来说,cap(array)始终等于len(array),因为数组是固定长度的。
arr := [5]int{1, 2, 3, 4, 5}
fmt.Println("Length:", len(arr)) // 输出: 5
fmt.Println("Capacity:", cap(arr)) // 输出: 5

五、指针数组与数组指针

1. 指针数组

指针数组是一个数组,其元素是指针类型

var ptrs [3]*int
x, y, z := 1, 2, 3
ptrs[0] = &x
ptrs[1] = &y
ptrs[2] = &z

for _, p := range ptrs {
    fmt.Println(*p)
}

2. 数组指针

数组指针是指向数组的指针。

arr := [3]int{10, 20, 30}
var p *[3]int = &arr
fmt.Println(*p) // 输出: [10 20 30]

六、数组作为函数参数

由于数组是值类型,传递给函数时会复制整个数组。如果数组较大,频繁复制可能会影响性能。此时可以使用数组指针切片(slice)优化。

示例:传递数组指针

func modifyArray(arr *[3]int) {
    arr[0] = 100
}

func main() {
    arr := [3]int{1, 2, 3}
    modifyArray(&arr) // 传递数组指针
    fmt.Println(arr)  // 输出: [100 2 3]
}

七、多维数组

多维数组的定义和操作类似于一维数组,但需要通过多个下标访问元素。

示例:二维数组

// 定义一个3x3的二维数组
var matrix [3][3]int

// 填充数组
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        matrix[i][j] = i * j
    }
}

// 打印数组
for _, row := range matrix {
    fmt.Println(row)
}

八、数组与切片的区别

  • 数组:长度固定,类型中包含长度(如[5]int)。
  • 切片:长度可动态变化,基于数组实现,类型不包含长度(如[]int)。

示例:切片与数组的关系

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:3] // 创建切片,引用数组的第1到第2个元素
fmt.Println(slice) // 输出: [2 3]

slice[0] = 100
fmt.Println(arr) // 输出: [1 100 3 4 5](修改切片会影响底层数组)

九、总结

  • 数组的优缺点
    • 优点:内存连续,访问速度快;适合固定长度的数据集合。
    • 缺点:长度固定,灵活性差。
  • 适用场景
    • 数据量小且长度固定的场景(如配置项、固定大小的缓冲区)。
    • 需要直接操作底层内存的场景(如嵌入式开发)。

如果需要动态长度的集合,建议使用切片(slice)代替数组。

Go语言切片(Slice)的详细优缺点分析,以及与数组(Array) 的全面对比表格,深入理解两者的区别和适用场景。


✅Go语言切片(Slice)的优缺点

一、切片的定义

切片是对数组的动态抽象,它本身不存储数据,而是指向一个底层数组的引用结构,包含三个部分:

  • 指针(ptr):指向底层数组的起始位置。
  • 长度(len):当前切片中元素的个数。
  • 容量(cap):从起始位置到底层数组末尾的元素个数。
s := []int{1, 2, 3} // 创建一个长度为3、容量为3的切片

二、切片的优缺点
  • 优点

    1、动态长度:可使用 append 动态扩容,无需预先确定大小。

    2、内存高效:多个切片可共享同一底层数组,减少内存复制。

    3、操作灵活:支持切片、截取、拼接等操作(如 s[1:3])。

    4、函数传参高效:传递的是结构体(指针+长度+容量),不会复制整个数据。

    5、与标准库兼容性好:Go标准库中大多数集合操作都基于切片设计。

  • 缺点

    1、共享底层数组:多个切片可能共享同一数组,修改一个可能影响其他切片(需注意“副作用”)。

    2、扩容机制复杂:当容量不足时自动扩容,可能导致内存重新分配和数据复制(性能波动)。

    3、不可比较(除与nil):不能直接用 == 比较两个切片是否相等(除非使用 reflect.DeepEqualslices.Equal)。

    4、内存泄漏风险:长时间持有小切片可能阻止整个大数组被GC回收(如 s = bigArr[100:101]


🔄数组 vs 切片 对比表格

特性数组(Array)切片(Slice)
类型定义[N]T(如 [5]int[]T(如 []int
长度固定,定义时确定动态,可变
是否值类型是(赋值/传参会复制整个数组)否(本质是引用类型,但本身是结构体)
内存布局连续内存块,直接存储数据结构体(ptr, len, cap),指向底层数组
初始化方式[3]int{1,2,3}[...]int{1,2,3}[]int{1,2,3}make([]int, 3)
扩容能力不支持支持(通过 append 自动扩容)
比较操作可直接用 == / !=(类型和长度相同)不能直接比较(除 nil),需用 slices.Equalreflect.DeepEqual
函数传参效率低(复制整个数组)高(只复制 slice header)
共享数据不共享(独立副本)可能共享底层数组(注意副作用)
适用场景- 固定长度的小数据(如坐标 [2]float64
- 需要值语义的场景
- 哈希表的 key(数组可作 key,切片不行)
- 动态数据集合(如列表、队列)
- 函数参数传递
- 大多数日常开发场景
能否作为 map 的 key✅ 可以(如果元素类型可比较)❌ 不可以(切片不可比较)
零值空数组(如 [3]int 的零值是 [0 0 0]nil(未初始化)或空切片 []T{}
性能特点访问快,栈上分配,无指针开销稍有间接访问开销,但更灵活高效

🧪示例对比代码

package main

import (
    "fmt"
    "slices" // Go 1.21+ 提供的切片工具
)

func main() {
    // --- 数组示例 ---
    var arr1 [3]int = [3]int{1, 2, 3}
    arr2 := arr1           // 赋值:复制整个数组
    arr2[0] = 999
    fmt.Println("arr1:", arr1) // [1 2 3]
    fmt.Println("arr2:", arr2) // [999 2 3]

    // 数组可以直接比较
    fmt.Println("arr1 == arr2:", arr1 == arr2) // false

    // --- 切片示例 ---
    slice1 := []int{1, 2, 3}
    slice2 := slice1          // 赋值:共享底层数组
    slice2[0] = 999
    fmt.Println("slice1:", slice1) // [999 2 3]
    fmt.Println("slice2:", slice2) // [999 2 3]

    // 切片不能直接比较
    // fmt.Println(slice1 == slice2) // 编译错误!

    // 使用 slices.Equal 比较
    fmt.Println("slice1 == slice2:", slices.Equal(slice1, slice2)) // true

    // 作为 map key
    m := make(map[[3]int]string) // 数组可以作 key
    // m := make(map[[]int]string) // 编译错误:切片不能作 key
}

✅总结建议

使用建议说明
优先使用切片在绝大多数场景下(如函数参数、动态集合),应使用切片,它是 Go 的“一等公民”。
数组用于特殊场景仅在需要固定长度、值语义、高性能或作为 map key 时使用数组。
避免切片内存泄漏若从大数组截取小切片并长期持有,建议使用 copy 创建独立副本。
比较切片用 slices.EqualGo 1.21+ 推荐使用 golang.org/x/exp/slices 或标准库 slices 包。

💡 一句话总结
数组是“值”,切片是“视图”
数组适合“小而固定”,切片适合“大而灵活”。

若做高性能或嵌入式开发,可以结合两者优势:用数组做底层存储,用切片做接口抽象。