Go语言数组详解
摘要:本文参考菜鸟教程Go数组章节,从零讲解数组定义、初始化、遍历、传参特性,重点拆解入门头号误区:数组长度属于类型、数组是值类型、数组与切片的本质差异,配套完整可运行代码
一、Go数组核心定义
数组是长度固定、元素类型统一、内存连续的数据集合,用于存放一组相同类型的数据。
✅ 长度固定,数组创建完成之后,不能扩容、不能缩小;
✅长度是数组类型的一部分[5]int和[10]int是两种完全不同的数据类型,类型不匹配,无法赋值、传参、比较;
✅ 数组属于值类型。数组赋值、传入函数时会完整拷贝一份数据,修改副本不会影响原始数组。
拓展小知识:内存连续意味着数组支持通过索引快速随机访问,查询速度极快。
二、数组声明与五种初始化方式
2.1 基础语法
var数组名[数组长度]数据类型示例:定义长度为5的int数组
vararr[5]int2.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 两种遍历方式
- 普通for循环,依靠下标遍历
适用场景:需要手动控制遍历起点、终点、步长
arr:=[3]int{100,200,300}fori:=0;i<len(arr);i++{fmt.Printf("下标:%d,值:%d\n",i,arr[i])}- 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除外) |
| 开发场景 | 极少使用,仅元素固定场景 | 绝大多数业务场景首选 |
八、零基础高频易错点汇总
[5]int和[10]int属于不同类型,不能赋值、传参、比较;- 数组是值类型,赋值、传参都会产生拷贝,修改副本不会影响原数组;
- 大数组直接传递性能低下,优先使用数组指针或者转为切片;
- 数组长度固定,无法动态扩容,业务开发优先选择切片;
- 数组索引越界不会编译报错,运行直接触发panic,程序崩溃;
...自动推导长度语法仅适用于数组,切片、多维数组内层不能使用;- 数组初始化元素数量不能超过数组定义长度;
- 数组未赋值的元素,自动填充对应类型零值;
- range遍历数组,循环变量val是拷贝值,修改val无法更改原数组;
- 数组仅支持 == 和 !=,不能使用大于小于进行比较。
九、文末总结
- Go数组三大核心特点:定长、值类型、长度参与类型构成,一定要抛开其他语言数组的固有思维;
- 数组直接传参会发生完整拷贝,如果需要修改原始数据,推荐转为切片;
- 数组只适合元素数量固定不变的场景,真实项目99%场景使用切片;
- 数组是理解切片的基础,弄懂数组,后续学习切片会轻松很多。
参考资料:菜鸟教程 Go语言数组 https://www.runoob.com/go/go-arrays.html
