Go语言数组详解

Go语言数组详解

摘要:本文参考菜鸟教程Go数组章节,从零讲解数组定义、初始化、遍历、传参特性,重点拆解入门头号误区:数组长度属于类型、数组是值类型、数组与切片的本质差异,配套完整可运行代码

一、Go数组核心定义

数组是长度固定、元素类型统一、内存连续的数据集合,用于存放一组相同类型的数据。
✅ 长度固定,数组创建完成之后,不能扩容、不能缩小;
长度是数组类型的一部分
[5]int[10]int是两种完全不同的数据类型,类型不匹配,无法赋值、传参、比较;
✅ 数组属于值类型。数组赋值、传入函数时会完整拷贝一份数据,修改副本不会影响原始数组。

拓展小知识:内存连续意味着数组支持通过索引快速随机访问,查询速度极快。

二、数组声明与五种初始化方式

2.1 基础语法

var数组名[数组长度]数据类型

示例:定义长度为5的int数组

vararr[5]int

2.2 五种初始化写法

方式1:仅声明,自动初始化为零值

Go不存在无默认值的数组,声明之后全部填充对应类型零值:int→0,string→"",bool→false,指针→nil。

packagemainimport"fmt"funcmain(){vararr[5]intfmt.Println(arr)// [0 0 0 0 0]}
方式2:完整初始化所有元素
// var标准写法vararr1[5]int=[5]int{1,2,3,4,5}// 短变量声明(开发常用)arr2:=[5]int{1,2,3,4,5}fmt.Println(arr1)fmt.Println(arr2)
方式3:使用...自动推导长度

不想手动填写数组长度,可以使用...,编译器自动统计元素个数确定数组长度。

arr:=[...]int{10,20,30,40}fmt.Println(len(arr))// 4

⚠️易错提醒:...只能用于数组,切片不能使用;多维数组仅最外层支持...,内层数组不允许。

方式4:指定下标局部初始化

只给指定索引赋值,其余位置自动填充零值。适合稀疏数组场景。

arr:=[5]int{1:666,3:888}fmt.Println(arr)// [0 666 0 888 0]
方式5:部分初始化,剩余补零

初始化元素数量少于数组长度,未赋值位置自动填充零值。

arr:=[5]int{1,2}fmt.Println(arr)// [1 2 0 0 0]

❌编译错误提醒:初始化元素总数不能大于数组定义长度,否则直接编译失败。

三、数组访问与遍历

3.1 通过索引访问元素

数组下标从0开始,合法区间:0 ~ len(arr)-1

funcmain(){arr:=[3]int{11,22,33}fmt.Println(arr[0])// 11fmt.Println(arr[2])// 33}

⚠️致命坑:索引越界不会出现编译警告,运行时直接触发panic,程序异常崩溃,编码时一定要注意边界!

3.2 两种遍历方式

  1. 普通for循环,依靠下标遍历
    适用场景:需要手动控制遍历起点、终点、步长
arr:=[3]int{100,200,300}fori:=0;i<len(arr);i++{fmt.Printf("下标:%d,值:%d\n",i,arr[i])}
  1. for range遍历(日常优先推荐)
    语法简洁,适合完整遍历整个数组
arr:=[3]int{100,200,300}// 同时接收下标与数值foridx,val:=rangearr{fmt.Println(idx,val)}// 不需要下标,使用空白标识符 _ 舍弃,避免变量未使用编译报错for_,val:=rangearr{fmt.Println(val)}

💡补充:range遍历数组时,每次循环得到的val是数组元素的拷贝,修改val无法改变原数组内容。

四、数组传参大坑:值类型拷贝

核心结论:数组是值类型,函数传参时会完整拷贝整个数组,修改副本无法影响原数组。

错误演示

packagemainimport"fmt"funcchangeArr(arr[3]int){arr[0]=999}funcmain(){arr:=[3]int{1,2,3}changeArr(arr)fmt.Println(arr)// [1 2 3],原值没有任何变化}

原理:传入函数内的只是原数组的拷贝,函数操作的是全新副本,和外部数组内存相互独立。

额外性能隐患:如果数组长度很大,完整拷贝会造成大量内存开销,严重影响性能!

两种可行解决方案

方案1:传递数组指针

传递数组地址,函数内部操作原始数组内存

funcchangeArr(arr*[3]int){arr[0]=999// Go语法糖,可以不用写(*arr)[0]}funcmain(){arr:=[3]int{1,2,3}changeArr(&arr)fmt.Println(arr)// [999 2 3]}

缺点:数组指针依旧绑定固定长度,通用性很差。

方案2:数组转为切片(项目开发首选)

开发中基本不会使用数组指针,更推荐把数组转为切片传递,摆脱固定长度限制。

funcchangeSlice(s[]int){s[0]=999}funcmain(){arr:=[3]int{1,2,3}changeSlice(arr[:])// arr[:] 将数组转为切片fmt.Println(arr)// [999 2 3]}

五、数组比较规则

Go数组支持==!=比较,但有严格限制:
只有长度相同、元素类型相同的两个数组才允许比较;
长度不一致直接触发编译错误,因为二者不属于同一种类型。

补充:数组不支持 > 、< 、>= 、<=大小比较!

funcmain(){a:=[2]int{1,2}b:=[2]int{1,2}c:=[3]int{1,2,3}fmt.Println(a==b)// true// fmt.Println(a == c) 编译报错,类型不一致}

六、多维数组拓展

实际开发偶尔会用到二维数组,简单示例:

packagemainimport"fmt"funcmain(){// 2行3列二维数组arr:=[2][3]int{{1,2,3},{4,5,6},}fmt.Println(arr[0][1])// 2}

重点:多维数组依然遵守规则,[2][3]int[3][2]int是两种不同类型。

七、数组 vs 切片核心对比

特性数组 [N]T切片 []T
长度固定不变,长度属于类型一部分长度动态,支持append扩容
类型兼容[5]int 和 [10]int 是不同类型同类型切片之间完全兼容
传参机制值传递,完整拷贝,大数据下性能差引用语义,共享底层数组,开销小
比较运算支持==、!=不支持直接使用==比较(nil除外)
开发场景极少使用,仅元素固定场景绝大多数业务场景首选

八、零基础高频易错点汇总

  1. [5]int[10]int属于不同类型,不能赋值、传参、比较;
  2. 数组是值类型,赋值、传参都会产生拷贝,修改副本不会影响原数组;
  3. 大数组直接传递性能低下,优先使用数组指针或者转为切片;
  4. 数组长度固定,无法动态扩容,业务开发优先选择切片;
  5. 数组索引越界不会编译报错,运行直接触发panic,程序崩溃;
  6. ...自动推导长度语法仅适用于数组,切片、多维数组内层不能使用;
  7. 数组初始化元素数量不能超过数组定义长度;
  8. 数组未赋值的元素,自动填充对应类型零值;
  9. range遍历数组,循环变量val是拷贝值,修改val无法更改原数组;
  10. 数组仅支持 == 和 !=,不能使用大于小于进行比较。

九、文末总结

  1. Go数组三大核心特点:定长、值类型、长度参与类型构成,一定要抛开其他语言数组的固有思维;
  2. 数组直接传参会发生完整拷贝,如果需要修改原始数据,推荐转为切片;
  3. 数组只适合元素数量固定不变的场景,真实项目99%场景使用切片;
  4. 数组是理解切片的基础,弄懂数组,后续学习切片会轻松很多。

参考资料:菜鸟教程 Go语言数组 https://www.runoob.com/go/go-arrays.html