go——数组(二)
1.内部实现
在Go语言里,数组是一个长度固定的数据类型,用于存储一段具有相同的类型的元素的连续块。
数组存储的类型可以是内置类型,如整型或字符串,也可以是某种结构类型。
灰格子代表数组里面的元素,每个元素都紧邻另一个元素。
每个元素都属于相同的类型,并且每个元素可以用一个唯一的索引(也称下标或标号)来访问。
数组是一种非常有用的数据结构,因为其占用的内存是连续分配的。
由于内存连续,CPU能把正在使用的数据缓存更久的时间。
而且内存连续很容易计算索引,可以快速迭代数组里的所有元素。
数组的类型信息可以提供每次访问一个元素时需要在内存中移动的距离。
既然数组的每个元素类型相同,又是连续分配,就可以以固定速度索引数组中的任意数据,速度非常快。
2.声明和初始化
声明数组时需要指定内部存储的数据类型,以及需要存储的元素的数量,这个数量也称为数组的长度。
(1)声明一个数组,并设置为零值
//声明一个包含5个元素的整型数组 var array [5]int
一旦声明,数组里存储的数据类型和长度就都不能改变了。
如果需要存储更多的元素,就需要先创建一个更长的数据,再把原来数组里的值复制到新数组里。
在Go语言中声明变量时,总会使用对应类型的零值来对变量进行初始化。数组也不例外。
当数组初始化时,数组内每个元素都初始化为对应类型的零值。
整型数组里的每个元素都初始化为0,也就是整数的零值。
(2)使用数组字面量声明数组
一种快速创建数组并初始化的方式是使用数组字面量。
数组字面量允许声明数组里元素的数量同时指定为每个元素的值。
//声明一个包含5个元素的整型数组 //用具体值初始化每个元素 array := [5]int{10,20,30,40,50}
(3)让Go自动计算声明数组的长度
如果使用...代替数组的长度,Go语言会根据初始化时数组元素的数量来确定该数组的长度。
//声明一个整型数组 //用具体值初始化每个元素 //容量由初始化值的数量决定 array := [...]int{10,20,30,40,50}
(4)声明数组并指定特定元素的值
如果知道数组的长度,而且准备给每个值都指定具体值,就可以通过索引来进行指定。
//声明一个有5个元素的数组 //用具体值初始化索引为1和2的元素 //其余元素保持零值 array := [5]int{1:20, 2: 30}
3.使用数组
(1)访问数组元素
因为内存布局是连续的,所以数组是效率很高的数据结构。
在访问数组里任意元素的时候,这种高效都是数组的优势。
要访问数组里某个单独元素,使用[]运算符。
//声明一个包含5个元素的整型数组 //用具体值初始为每个元素 array := [5]int{10,20,30,40,50} //修改索引为2的元素的值 array[2] = 35
(2)访问指针数组的元素
声明一个所有元素都是指针的数组。使用*运算符就可以访问元素指针所指向的值
//声明包含5个元素的指向整数的数组 //用整型指针初始化索引为0和1的数组元素 array := [5]*int(0: new(int), 1: new(int)) //为索引为0和1的元素赋值 *array[0] = 10 *array[1] = 20
(3)把同样类型的一个数组赋值给另外一个数组
在Go语言里,数组是一个值。这意味着数组可以用在赋值操作中。
变量名代表整个数组,因此,同样类型的数组可以赋值给另一个数组。
//声明第一个包含5个元素的字符串数组 var array1 [5]string //声明第二个包含5个元素的字符串数组 //用颜色初始化数组 array2 := [5]string{"red", "yellow", "blue", "green", "pink"} //把array2的值赋值给array1 array1 = array2
(4)编译器会阻止类型不同的数组互相赋值
数组变量的类型包括数组长度和每个元素的类型。
只有这两部分都相同的数组,才是类型相同的数组,才能相互赋值。
//声明第一个包含4个元素的字符串数组 var array1 [4]string //声明第二个包含5个元素的字符串数组 //初始化数组 array2 := [5]string{"red", "blue", "yellow", "green", "pink"} //复制 array1 = array2 //cannot use array2 (type [5]string) as type [4]string in assignment
(5)把一个指针数组赋值给另一个
复制指针数组,只会复制指针的值,而不会复制指针所指向的值。
//声明第一个包含3个元素的指向字符串的指针数组 var array1 [3]*string //声明第二个包含3个元素的指向字符串的指针数组 //使用字符串指针初始化这个数组 array2 := [3]*string{new(string), new(string), new(string)} //*array[]表示反取元素的值 //赋值 *array2[0] = "blue" *array2[1] = "pink" *array2[2] = "yellow" //复制 array1 = array2
4.多维数组
数组本身只有一个维度,不过可以组合多个数组创建多维数组。
多维数组很容器管理具有父子关系的数据或者与坐标系相关联的数据。
(1)声明二维数组
//声明一个二维整型数组,两个维度分别存储4个元素和2个元素 var array [4][2]int //使用数组字面量来声明并初始化一个二维整型数组 array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41} //声明并初始化外层索引为1和2的元素 array := [4][2]int{1: {20, 21}, 2: {30, 31}} //声明并初始化外层数组和内层数组的单个元素 array := [4][2]int{1: {0: 20}, 2: {1: 31}}
(2)访问二维数组的元素
为了访问单个元素,需要反复组合使用[]操作符。
//声明一个2X2的二维整型数组 var array [2][2]int //设置每个元素的整型值 array[0][0] = 10 array[0][1] = 20 array[1][0] = 30 array[1][1] = 40
(3)同样类型的多维数组赋值
只要类型一致就可以将多维数组相互赋值。
多维数组的类型包括每一维度的长度以及最终存储在元素中的数据类型。
//声明两个不同的二维整型数组 var array1 [2][2]int var array2 [2][2]int //设置每个元素的整型值 array2[0][0] = 10 array2[0][1] = 20 array2[1][0] = 30 array2[1][1] = 40 //复制 array1 =array2
(4)使用索引为多维数组赋值
//将array1的索引为1的维度复制到一个同类型的新数组里面 var array3 [2]int = array1[1] //将外层数组的索引为1、内层数组的索引为0的整型值赋值到新的整型变量里 var value int = array1[1][0]
5.在函数间传递数组
根据内存和性能来看,在函数间传递数组是一个开销很多的操作。
在函数之间传递变量时,总是以值的变量传递。
如果这个变量是一个数组,意味着整个数组不管有多长,都会完整复制,并传递给函数。
假设现在我们有一个包含100万个int类型元素的数组。
在64位架构上,需要800万字节,也就是8MB内存。
(1)使用值传递在函数间传递大数组
//声明一个需要8MB的数组 var array [1000000]int //将数组传递给foo函数 foo(array) //函数foo接受一个100万个整型的数组 func foo(array [1000000]int) { ... }
每次函数foo被调用时,必须在栈上分配8MB的内存。
之后整个数组的值被复制到刚刚分配的内存里。
虽然Go的垃圾回收机制还不错,但是如果大规模调用,势必会有很多资源消耗。
其实我们只需要传递指针,这样只需要8字节的内存分配给指针就可以了。
(2)使用指针在函数间传递大数组
//声明一个需要8MB的数组 var array [1000000]int //将数组传递给foo函数 foo(&array) //函数foo接受一个100万个整型的数组 func foo(array *[1000000]int) { ... }