1. 内部实现
在Go语言里,数组是一个长度固定的数据类型,用于存储一段具有相同的类型元素的连续块。存储的类型可以是内置类型,也可以是自定义类型。数组是一种非常有用给的数据结构,因为其占用的内存是连续分配的。由于内存连续,CPU能把正在使用的数据缓存更久的时间。而且内存连续很容易计算索引,可以快速访问数组里的任意数据。
在Go语言中,数组是一种类似于整形,浮点型,字符串的基本数据类型,区别于C++语言的是,Go语言的数组在函数传参时是值传递的,因此要想通过某个函数修改数组的值,就必须通过传入数组的指针来实现,实际上Go语言中的所有函数传参都是值传递。
2. 声明和初始化
数组 的类型名是 [n ]elemetType ,其中 n 是数组长度, elementType 是数组元素类型 。 数组一般在创建时通过字面量初始化, 单独声明一个数组类型变量而不进行初始化是没有意义的。
不同初始化的方式:
// 声明
var arr0 [3] int // 声明一个有两个类型的数组,但元素默认值都是0
arr1 := [...] float64{7.0, 8.1, 9.2} // [...]后面跟字面量初始化列表
// 初始化
arr2 := [3]int {1, 2, 3} // 指定长度和初始化字面量
arr3 := [...]int {1, 2, 3} // 不指定长度,但由后面的初始化列表
arr4 := [3]int {1:1, 2:3} // 指定总长度,并通过索引值进行初始化,没有初始化的元素使用默认值
arr5 := [...]int {2:2, 5:4} // 不指定总长度,通过索引值进行初始化,没有初始化的元素使用默认值
// 数据长度由最后一个索引的元素值确定
fmt.Println("arr0:", arr0)
fmt.Println("arr1:", arr1)
fmt.Println("arr2:", arr2)
fmt.Println("arr3:", arr3)
fmt.Println("arr4:", arr4, "len=", len(arr4))
fmt.Println("arr5:", arr5, "len=", len(arr5))
编译运行结果
arr0: [0 0 0]
arr1: [7 8.1 9.2]
arr2: [1 2 3]
arr3: [1 2 3]
arr4: [0 1 3] len= 3
arr5: [0 0 2 0 0 4] len= 6
Process finished with exit code 0
3. 数组的特点
- 数组创建完长度就固定了,不能再追加元素
- 数组是值类型的,数组赋值或作为函数参数都是值拷贝
- 数组长度是数组类型的组成部分,[10]int和[20]int表示不同的类型
- 可以根据数组创建切片(切片后面文章会介绍)
- 在函数间传参是值传递的
4. 数组元素的访问
arr := [...]int{1, 2, 3}
// 方式一
for i:=0; i<len(arr); i++ {
fmt.Printf("arr[%d] = %d\n", i, arr[i])
}
// 方式二
for k, v := range arr {
fmt.Printf("arr[%d] = %d\n", k, v)
}
5. 指针数组
// 声明包含5个元素的指向整数的数组
// 用整形指针初始化索引为0和1的数组元素
arr := [2]*int{0: new(int), 1: new(int)}
// 为索引0和1的元素赋值
*arr[0] = 1;
*arr[1] = 2;
fmt.Println(arr)
for _, v := range arr {
// 对每个地址解引用,得到其指向的值
fmt.Println(\*v)
}
代码编译运行结果如下:
[0xc00000a0c8 0xc00000a0e0]
1
2
指针数组的内存分布如下图所示:
6. 数组之间的赋值
在Go语言里,数组是一个值。这意味着数组可以用在赋值操作中。变量名代表整个数组(和C++不同),同一类型的数组可以赋值给另一个数组,代码如下:
// 声明第一个包含5个元素的字符串数组
var arr1 [5] string
// 声明第二个包含5个元素的字符串数组并并初始化
arr2 := [...]string{"Red", "Blue", "Green", "Yellow", "Pink"}
// 把arr2复制到arr1
arr1 = arr2
复制之后,两个数组完全一样,如下图所示。
前面已经说过,数组变量的类型包括数组长度和每个元素的类型。只有这两部分都相同的数组,才是类型相同的数组,才能互相赋值,否则会报编译错误。
7. 指针数组的赋值
package main
import "fmt"
func main() {
// 声明第一个包含3个元素的指向字符串的指针数组
var arr1 [3] *string
// 声明第二个包含3个元素的指向字符串的指针数组
arr2 := [...]*string{new(string), new(string), new(string)}
// 为每个元素赋值
*arr2[0] = "Red"
*arr2[1] = "Blue"
*arr2[2] = "Green"
// 把arr2复制到arr1
arr1 = arr2
fmt.Println(arr1)
// 打印每个指针元素指向的值
for k, v := range arr1 {
fmt.Printf("arr[%d] = %s\n", k, *v)
}
}
编译运行结果如下。
arr1: [0xc0000441f0 0xc000044200 0xc000044210]
arr[0] = Red
arr[1] = Blue
arr[2] = Green
Process finished with exit code 0
赋值之后,两个数组指向同一个字符串,如下图所示。
8.多维数组
数组本身只有一个维度,但可以组合多个维度创建多维数组,代码如下所示。
package main
import "fmt"
func main() {
// 声明一个二维整形数组,两个维度分别存储4个元素和2个元素
var arr1 [4][2]int
// 使用数组字面量来声明并初始化一个二维数组
arr2 := [4][2] int{{1, 2}, {3, 4}, {5, 6}, {7, 8}}
// 声明并初始化外层数组中索引为1和3的元素
arr3 := [4][2] int{1: {30, 40}, 3: {50, 60}}
// 声明并初始化外层数组和内层数组的单个元素
arr4 := [4][2] int{1: {0: 10}, 2: {1: 40}}
fmt.Println("arr1:", arr1)
fmt.Println("arr2:", arr2)
fmt.Println("arr3:", arr3)
fmt.Println("arr4:", arr4)
}
编译运行结果如下
arr1: [[0 0] [0 0] [0 0] [0 0]]
arr2: [[1 2] [3 4] [5 6] [7 8]]
arr3: [[0 0] [30 40] [0 0] [50 60]]
arr4: [[0 0] [10 0] [0 40] [0 0]]
Process finished with exit code 0
9. 在函数间传递数组
根据内存和性能来看,在函数间传递数组是一个开销很大的操作。在函数之间传递变量时,总是以值的方式传递的。如果这个变量是一个数组,意味着整个数组,不管有多长,都会完整复制后传递给函数。
如果我们需要创建100万个int类型元素的数组,在64位架构上,需要800万字节,即8MB的内存,如果以值传递的方式将会特别耗内存,因此,对于这种大容量数组的传递,一般都是传入数组的指针,代码如下。
package main
// 函数foo接收一个指向100万个整形值的数组的指针
func foo(arr *[1e6] int) {
// ...
}
func main() {
// 分配一个8MB的数组
var arr [1e6]int
// 将数组的地址传递给函数foo
foo(&arr)
}
现在将数组的地址传入函数,在栈空间分配上只需销毁8字节的内存空间。这个操作会更有效第利用内存,性能也更好。不过要意识到,因为现在传递的是指针,如果改变指针指向的值,会改变共享的内存,如果调用者不期望这种改变,就可能出现问题。
关于go语言数组,就啰嗦这么多了~~
我是lioney,年轻的后端攻城狮一枚,爱钻研,爱技术,爱分享。
个人笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎大家指出,也欢迎大家一起交流后端各种问题!
有疑问加站长微信联系(非本文作者)