大家好,今天我们聊聊 Go 语言中的数组。
Go 语言中有 3 种内建的数据结构可以让用户管理集合数据:数组(Array)、切片(Slice)、映射(Map)。其中数组又是切片和映射的基础数据结构。
概要
本部分内容包括:
数组的内部实现
数组的声明和初始化
使用数组
多维数组
在函数间传递数组
内部实现
数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的元素。Go 语言中,数组的长度是固定的。存储的元素可以是内置数据类型,也可以是用户自定义数据类型。
数组存储相同类型的数据,占用连续的内存空间。由于内存连续,CPU 能把正在使用的数据缓存更久的时间。而且内存连续很容易计算索引,可以快速迭代数组中的所有元素。由于相同数据类型,有了首地址后,可以根据下标计算元素的存储地址,根据下标随机访问的效率很高,时间复杂度为 O(1)。相同数据类型,连续存储,就可以以固定速度索引数组中的任意数据,速度非常快。
声明和初始化
声明数组需要指定类型和数组长度
以上我们声明了一个数组 weeks,但是我们还没有对它进行显示初始化。在 Go 语言中声明变量时,总会使用对应类型的零值来对变量进行初始化。数组声明时,默认数组内每个元素都会初始化为对应类型的零值。这时候数组 weeks 里面的值是 5 个 0,这和 java 不一样,java 里 weeks 是 null。
声明后,可以对数组进行一次显示初始化,如下:
var weeks [7]int
weeks = [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]string
var students2 = [5]string{"xiaoming","xiaohua","xiaoli","xiaoxiao","xiaoxi"}
// 把students2的值赋值给students1
students1 = students2
fmt.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]*int
pArr = [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]float32
triangle = [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.8
fmt.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 语言实战
评论