前言
Go 语言给用户提供了三种数据结构用于管理集合数据: 数组, 切片 (Slice) 和映射(Map). 这三种数据结构是语言核心的一部分, 在标准库里被广泛使用. 学会这些数据结构, 编写 go 程序会变得快速, 有趣且十分灵活. 掌握数组是理解切片和映射的基础, 我们就从数组开始学习.
什么是数组
Go 语言中, 数组是一个长度固定的数据类型, 用于存储一段相同数据类型的元素, 这些元素在内存中是连续存储的. 数组存储的类型可以是内置类型, 如整型, 字符串等, 也可以是自定义的数据结构. 强调数组固定, 有别于切片, 它是可以增长和收缩的动态序列. 数组的每个元素可以通过索引下标来访问, 索引下标的范围是从[0 , len(array)-1].
声明与初始化
数组声明有两个要点:
指定数组存储的数据的类型;
元素个数, 即数组的长度;
- var array0 [5]int // 声明一个包含 5 个元素的整型数组, 但我们并未初始化
- fmt.Println(array0) // 输出:[0 0 0 0 0]
前面我们已经讲过, Go 语言中声明变量时, 总会使用对应类型的零值来对变量进行初始化. 数组也不例外. 当数组初始化时, 数组内每个元素都初始化为对应类型的零值. 从输出结果可以看到, 整型数组里的每个元素都初始化为 0, 也就是整型的零值.
- var array0 [5]int
- array0 = [5]int{
- 1,2,3,4,5
- } // 手动初始化
- fmt.Println(array0) // 输出:[1 2 3 4 5]
最基本的声明并初始化:
- // 声明并初始化
- var array0 = [5]int{
- 1,2,3,4,5
- }
- fmt.Println(array0)
使用 Go 提供的:= 操作符:
array0 := [5]int{1,2,3,4,5}
Go 提供了一种机制, 免去了我们指定数组长度的烦恼, 使用..., 根据初始化时数组元素的数量来确定该数组的长度.
- array := [...]int{
- 1,2,3,4,5
- }
- fmt.Println(len(array)) // 内置的 len()函数返回数组中元素的个数.
假如我只想给索引为 1,3 的元素指定初始化的值怎么办? 还是有办法的:
array := [...]int{0,2,0,4,0}
更简便的方法:
array := [5]int{1:2,3:4}
学会使用数组
上面提到过得, 因为数组的内存分布是连续的, 所以在数组访问任一的效率是很高, 这也是数组的优势. 可以使用 [] 运算符访问数组的当个元素.
- array := [5]int{
- 1,2,3,4,5
- }
- fmt.Println(array[3]) // 访问单个元素
- array[3] = 30 // 修改当个元素的值
- fmt.Println(array[3])
使用 for,for range 循环遍历数组:
- array := [5]int{1,2,3,4,5}
- // for
- for i:=0;i<len(array);i++ {
- fmt.Printf("索引 %d 的值: %d\n",i,array[i])
- }
- // for range
- for i,v := range array{
- fmt.Printf("索引 %d 的值: %d\n",i,v)
- }
输出的结果是一样的:
索引 0 的值: 1
索引 1 的值: 2
索引 2 的值: 3
索引 3 的值: 4
索引 4 的值: 5
数组变量的类型包括数组长度和每个元素的类型. Go 语言规定只有这两部分都相同的数组, 才是类型相同的数组, 才能互相赋值, 不然会编译出错.
- var array1 [5]int
- array2 := [5]int{
- 1,2,3,4,5
- }
- array1 = array2
- fmt.Println(array1) // 输出:[1 2 3 4 5]
- var array3 [4]int = array2
- // 编译出错: cannot use array2 (type [5]int) as type [4]int in assignment
数组指针和指针数组
我们可以声明一个指针变量, 指向一个数组:
- arr := [6]int{
- 5:9
- }
- // 数组指针
- var ptr *[6]int = &arr
- // 简写
- ptr := &arr
需要注意的是, 指针变量 ptr 的类型是 *[6]int, 也就是说它只能指向包含 6 个元素的整型数组, 否则编译报错. 指针数组和数组差不多, 只不过元素类型是指针:
- // 指针数组
- x,y := 1,2
- var arrPtr = [5]*int{
- 1:&x,3:&y
- } // 没有手动初始化的元素, 已经自动初始化指针类型对应的零值 nil
- fmt.Println(*arrPtr[1]) // 输出: 1
- *arrPtr[1] = 10
- fmt.Println(x,*arrPtr[1]) // 输出: 10 10
*arrPtr[1] = 10, 同时也修改了变量 x 的值, 因为 x 和 arrPtr[1]指向同一内存地址. 提一点, 相同类型的指针数组也可以相互赋值. 总结一句话: 注意 * 与谁结合, 如 p *[5]int,* 与数组结合说明是数组指针; 如 p [5]*int,* 与 int 结合, 说明这个数组都是 int 类型的指针, 是指针数组.
函数间传递数组
函数之间传递变量时, 总是以值的方式传递的. 如果变量是一个数组, 意味着整个数组, 不管有多大, 都会完整赋值一份, 并传递给函数. 复制出来的数组只是原数组的一份副本, 在函数中修改传递进来数组是不会改变原数组的值得.
- // 传递数组的副本
- func modify(a [5]int) {
- a[1] = 1
- fmt.Println(a)
- }
- func main(){
- arr := [5]int{4:9}
- fmt.Println(arr)
- modify(arr)
- fmt.Println(arr)
- }
输出:
- [0 0 0 0 9]
- [0 1 0 0 9]
- [0 0 0 0 9]
原数组元素的值没有被修改.
大家可以想一个问题, 如果一个数组的数据量很大, 如果还采用值传递的话, 这无疑是一个开销很大的操作, 对内存和性能都是不友好的. 还好, 我们还有一个更好的办法: 传递指向数组的指针, 这样只需要复制一个数组类型的指针大小就可以.
- // 传递数组的指针
- func modifyPtr(a *[5]int){
- a[1] = 2
- fmt.Println(*a)
- }
- func main(){
- arr := [5]int{4:9}
- fmt.Println(arr)
- modifyPtr(&arr)
- fmt.Println(arr)
- }
输出:
- [0 0 0 0 9]
- [0 2 0 0 9]
- [0 2 0 0 9]
有没有发现! 原数组已经改变了, 因为现在传递的是数组指针, 所以如果改变指针指向的值, 原数组在内存的值也会被修改. 这种操作虽然更有效地利用内存(免去了大量的内存复制), 性能也更好, 但如果没有处理好指针, 也会带来不必要的问题, 所以, 使用的时候需要谨慎小心.
来源: https://juejin.im/post/5c0fb85cf265da615064506b