大家好,今天我们聊聊 Go 语言中的数组。
Go 语言中有 3 种内建的数据结构可以让用户管理集合数据:数组(Array)、切片(Slice)、映射(Map)。其中数组又是切片和映射的基础数据结构。
概要
本部分内容包括:
- 数组的内部实现 
- 数组的声明和初始化 
- 使用数组 
- 多维数组 
- 在函数间传递数组 
内部实现
数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的元素。Go 语言中,数组的长度是固定的。存储的元素可以是内置数据类型,也可以是用户自定义数据类型。
数组存储相同类型的数据,占用连续的内存空间。由于内存连续,CPU 能把正在使用的数据缓存更久的时间。而且内存连续很容易计算索引,可以快速迭代数组中的所有元素。由于相同数据类型,有了首地址后,可以根据下标计算元素的存储地址,根据下标随机访问的效率很高,时间复杂度为 O(1)。相同数据类型,连续存储,就可以以固定速度索引数组中的任意数据,速度非常快。
声明和初始化
声明数组需要指定类型和数组长度
以上我们声明了一个数组 weeks,但是我们还没有对它进行显示初始化。在 Go 语言中声明变量时,总会使用对应类型的零值来对变量进行初始化。数组声明时,默认数组内每个元素都会初始化为对应类型的零值。这时候数组 weeks 里面的值是 5 个 0,这和 java 不一样,java 里 weeks 是 null。
声明后,可以对数组进行一次显示初始化,如下:
 var weeks [7]intweeks = [7]int{0, 1, 2, 3, 4, 5, 6}
   复制代码
 我们也可以在数组声明时,使用数组字面量初始化数组,如下:
 var weeks = [7]int{0, 1, 2, 3, 4, 5, 6}
   复制代码
 一旦声明,数组的类型和长度就不能改变了。如果要存储更多的数据就要重新创建一个更长的数组,再把原来的复制到新数组。
 var weeks = [7]int8{0,1,2,3,4,5,6,7} //编译报错
   复制代码
 在函数内部,也可以使用:=短变量声明操作符
 weeks := [7]int{0, 1, 2, 3, 4, 5, 6}
   复制代码
 我们也可以使用...让 Go 语言自动计算声明数组的长度
 weeks := [...]int{0, 1, 2, 3, 4, 5, 6}
   复制代码
 如果只想初始化特定元素的值,可以
 weeks := [7]int{1:1, 6:6}// 与weeks := [7]int{0,1,0,0,0,0,6}一样效果weeks := [...]int{1:1, 6:6} // 推断数组长度为7
   复制代码
 也可以初始化部分
 weeks := [7]int{1,2,3}// 与weeks := [7]int{1,2,3,0,0,0,0}一样效果
   复制代码
 使用数组
由于数组存储相同类型的元素,占用连续的内存空间。因此数组可以根据下标随机访问,效率很高,操作符为[]。
 weeks := [7]int{0, 1, 2, 3, 4, 5, 6}
   复制代码
 上面声明了一个 weeks 数组,可以通过下标访问数组元素
修改数组元素的值也很简单
Go 语言中,数组是一个值。意味着数组可以用在赋值操作中
 var students1 [5]stringvar students2 = [5]string{"xiaoming","xiaohua","xiaoli","xiaoxiao","xiaoxi"}// 把students2的值赋值给students1students1 = students2fmt.Printf("students1:%v %p \n",students1,&students1)fmt.Printf("students2:%v %p \n",students2,&students2)
   复制代码
 只有类型和长度都相同的数组间才可以执行赋值操作。以上,Printf 是打印出格式化的字符串,%p 表示指针,十六进制表示,前缀 0x,输出结果如下:
 students1:[xiaoming xiaohua xiaoli xiaoxiao xiaoxi] 0xc0000c2000 students2:[xiaoming xiaohua xiaoli xiaoxiao xiaoxi] 0xc0000c2050
   复制代码
 内存地址不一样,赋值操作相当于复制了一份数据
我们也可以声明所有元素都是指针的数组
 var pArr [5]*intpArr = [5]*int{1:new(int),3:new(int)}fmt.Printf("pArr:%v \n",pArr)
   复制代码
 
以上输出结果为:
 pArr:[<nil> 0xc000120000 <nil> 0xc000120008 <nil>]
   复制代码
 复制指针数组,只会复制指针的值,而不会复制指针指向的值。
通过切片操作会产生一个新数组
 var colors2 = [3]string{"red","yellow","blue"}var colors3 = colors2[1:2]fmt.Printf("colors3:%v \n",colors3)
   复制代码
 可以通过内建函数 len 获取数组的长度
 clen := len(colors2)fmt.Printf("colors2 len:%v \n",clen)
   复制代码
 多维数组
多维数组用于管理具有父子关系的数据或者坐标相关联的数据。
 // 声明并初始化一个直角三角形var triangle [3][2]float32triangle = [3][2]float32{{0,3.0},{4.0,0},{0,0}}fmt.Printf("triangle:%v \n",triangle)
// 声明并初始化一个等腰直角三角形triangle2 := [3][2]float32{{0,1.0},{-1.0,0},{1.0,0}}fmt.Printf("triangle2:%v \n",triangle2)
// 声明并初始化一个三角形的一条边triangle3 := [3][2]float32{1:{1.5,1.5},2:{-1.5,-1.5}}fmt.Printf("triangle3:%v \n",triangle3)
//访问与修改a := triangle2[0]fmt.Printf("a:%v \n",a)
ay := triangle2[0][1]fmt.Printf("ay:%v \n",ay)
triangle2[0] = [2]float32{0,0.9}fmt.Printf("triangle2:%v \n",triangle2)
triangle2[0][1] = 0.8fmt.Printf("triangle2:%v \n",triangle2)
   复制代码
 
在函数间传递数组
Go 语言在函数间传递变量时,总是以值的方式传递。变量是数组也不例外,会复制数组并传递给函数。函数间传递数组是个开销很大的操作。
 // 声明一个需要8MB的数组var array [1e6]int
// 将数组传递给foo,效率低下foo(array)func foo(array [1e6]int) {     fmt.Println("foo")}
   复制代码
 更有效的做法,将数组指针传递给 foo,但是这样会共享内存。
 foo2(&array)func foo2(array *[1e6]int) {   fmt.Println("foo2")}
   复制代码
 
总结
- 数组用一组连续的内存空间存储相同类型的元素,数组的长度是固定的。数组按下标随机访问的效率很高,时间复杂度为 O(1)。虽说数组结构简单,但是它是切片和映射的基础数据结构。 
- 声明数组需要指定类型和数组长度。数组声明时,默认数组内每个元素都会初始化为对应类型的零值,可以用数组字面量对数组进行自定义初始化,初始化后数组长度不能改变。 
- 可以通过数组下标访问和修改数组中的元素。数组是值,相同类型相同长度的数组间可以进行赋值,赋值会执行复制操作,产生相同的 2 份数据。 
- Go 语言在函数间传递变量时,总是以值的方式传递。直接传递数组开销比较大,有效的做法是传递数组指针给函数,但需要注意共享内存。 
参考
- Go 语言实战 
评论