数组的内部实现和基础功能
Go语言有三种数据结构可以让用户管理集合数据:数组、切片和映射。因为数组是切片和映射的基础数据结构。理解数组的工作原理,有助于理解切片和映射提供的优雅和强大的功能。
内部实现
在Go语言中,数组是一个长度固定的数据类型,用于存储一段具有相同类型的元素的连续块。类型可以是内置类型,也可以是某种结构类型。既然数组的每个元素类型相同,又是连续分配,就可以以固定速度索引数组中的任意数据,速度非常快。
声明和初始化
声明数组需要指定内部存储的数据类型,以及需要存储的元素的数量,也就是数组的长度,如:
//声明一个数组,并设置为零值 //声明一个包含5个元素的整型数组 var array [5]int |
一旦声明,数组里存储的数据类型和数组长度就不能改变了。如果需要存储更多的元素就要先创建一个更长的数组,再把原来数组里的值复制到新数组里。
在Go语言中声明变量时,总会使用对应类型的零值来对变量进行初始化。当数组初始化时,数组内每个元素都初始化为对应类型的零值。如上一步声明的数组,元素类型是int,那么每个元素都初始化为0,也就是整型的零值。
一种快速创建数组并初始化的方式是使用数组字面量。数组字面量在允许声明元素数量的同时指定每个元素的值,如:
//使用数组字面量声明数组 //声明一个包含5个元素的整型数组并指定元素值 array := [5]int{10, 20, 30, 40, 50} |
如果使用…替代数组的长度,则会根据初始化时数组元素的数量来确定改数组的长度,如:
//自动计算声明数组的长度 //声明一个整型数组,用具体值来初始化每个元素 array := [...]int{10, 20, 30, 40, 50} |
如果知道数组的长度而且给每个值都指定具体指,如:
//声明数组并指定特定元素的值 //声明一个有5个元素的数组,并用具体指初始化索引1和2的元素,其余值保持零值 array := [5]int{1:10, 2:20} |
使用数组
要访问数组里的某个元素,使用[]运算符,如:
//访问数组元素 //声明一个包含5个元素的整型数组,并用每个元素初始化 array := [5]int{10, 20, 30, 40, 50} //修改索引为2的元素的值 array[2] = 35 //那么array的值就是{0: 10, 1: 20, 2: 35, 3: 40, 4: 50} |
也可以声明一个所有元素都是指针的数组,使用*运算数就可以访问元素指针所指向的值。如:
//访问指针数组的元素 //声明5个元素都是指向整数的数组,用整型指针初始化索引为0和1的数组元素 array := [5]*int{0: new(int), 1: new(int)} //为索引为0和1的元素赋值 *array[0] = 10 *array[0] = 20 |
数组是一个值,可以用在赋值操作中。变量名代表整个数组,因此,同样类型的数组可以赋值给另一个数组。如:
//把同样类型的一个数组赋给另一个数组 //声明第一个包含5个字符串元素的数组 var array1 [5]string //声明第二个包含5个字符串元素的数组, 并初始化 array2 := [5]string{"red", "green", "blue", "yellow", "pink"} //把array2的值复制到array1 array1 = array2 //复制之后,两个数组的值完全一样 |
数组变量的类型包括数组长度和每个元素的类型。只有两部分相同的数组,才是类型相同的数组,才能互相赋值,如:
//编译器会阻止类型不同的数组互相赋值 //声明第一个包含4个元素的字符串数组 var array1 [4]string //声明第二个包含5个元素的字符串数组,并初始化 array2 := [5]string{"red", "green", "blue", "yellow", "pink"} //将array2赋值给array1 array1 = array2 //Compiler Error: cannot use array2 (type [5]string) as type [4]string in assignment |
复制数组指针,只会复制指针的值,而不会复制指针所指向的值,如:
//把一个指针数组赋值给另一个 //声明第一个包含3个元素的指向字符串的指针数组 var array1 [3]*string //声明第二个包含3个元素的指向字符串的指针数组,并使用字符串指针初始化这个数组 array2 := [3]*string{new(string), new(string), new(string)} //给array2每个元素赋值 *array2[0] = "red" *array2[1] = "blude" *array2[2] = "green" //将array2复制给array1 array1 = array2 //复制之后,两个数组指向同一组字符串 |
多维数组
数组本身只有一个维度,不过可以组合多个数组创建多维数组。多维数组很容易管理具有父子关系的数据或者与坐标系相关联的数据。声明方式如下:
1 2 3 4 5 6 7 8 9 |
//声明二维数组 //声明一个二维整型数组,两个维度分别存储4个元素和2个元素 var array [4][2]int //使用数组字面量来声明并初始化一个二维整型数组 array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41} //值为{{10, 11}, {20, 21}, {30, 31}, {40,41}} //声明并初始化外层数组中索引为1和3的元素 array := [4][2]int{1: {20, 21}, 3: {40, 41}} //值为{{0, 0}, {20, 21}, {0, 0}, {40, 41}} //声明并初始化外层数组和内层数组的单个元素 array := [4][2]int{1: {0: 20}, 3: {1: 41}} //值为{{0, 0}, {20, 0}, {0, 0}, {0, 41}} |
只要类型一致,就可以将多维数组互相赋值。多维数组的类型包括每一维度的长度以及最终存储在元素中的数据类型
//同样类型的多维数组赋值 //声明两个不同的二维整型数组 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 //将array2的值复制给array1 array1 = array2 |
在函数间传递数组
在函数间传递数组是一个开销很大的操作,在函数之间传递变量时,总是以值的方式传递的。如果这个变量是一个数组,意味着整个数组,不管有多长,都会完整复制,并传递给函数。
假设我们创建一个包含100万个int类型元素的数组。在64位架构上,这将需要800万字节,即8MB的内存。如果将其传递给函数,则每次调用函数,必须在栈上分配8MB的内存。之后将整个数组的值(8M的内存)被复制到刚分配的内存里。不过我们还有一种更好且更有效的方法来来处理这个操作。可以只传入指向数组的指针,这样值需要复制8个字节的数据而不是8MB的内存数据到栈上。如:
//使用指针在函数间传递大数组 //分配一个需要8MB的数组 var array = [1e6]int //将数组的地址传递给函数foo foo(&array) //函数foo接受一个指向100万个整型值的数组的指针 func foo(array *[1e6]int) { } |
这个操作会更有效地利用内存,性能也更好。不过因为现在传递的是指针,所以如果改变指针指向的值,会改变共享的内存。我们可以使用切片更好地处理这类共享问题。
特别声明:以上文章内容仅代表作者本人观点,不代表变化吧观点或立场。如有关于作品内容、版权或其它问题请于作品发表后的30日内与变化吧联系。
- 赞助本站
- 微信扫一扫
-
- 加入Q群
- QQ扫一扫
-
评论