前言:下面的内容都是边看【飞雪无情】大佬的博客,自己边整理的,其中部分内容有过删改,推荐大家去看原作者的博客进行学习,本博客内容仅作为自己的学习笔记。在此之前,我跟着b站韩茹老师刷完了Go语言入门教程。
学习链接:https://www.flysnow.org/archives/
参考书籍:《Go语言实战》《Go语言学习笔记》
四、Go 数组
数组,是用来存储集合数据的,这种场景非常多,我们编码的过程中,都少不了要读取或者存储数据(补充:比如使用数据库或者文件,或者访问网络,总需要一种方法来处理接收和发送的数据)。当然除了数组之外,我们还有切片、Map映射等数据结构可以帮我们存储数据,但是数组是它们的基础。
1. 内部实现
要想更清晰的了解数组,我们得了解它的内部实现。数组是长度固定的数据类型,必须存储一段相同类型的元素,而且这些元素是连续的。我们这里强调固定长度,可以说这是和切片最明显的区别。
数组存储的类型可以是内置类型,比如整型或者字符串,也可以是自定义的数据结构。因为是连续的,所以索引比较好计算,所以我们可以很快的索引数组中的任何数据。
这里的索引,一直都是0,1,2,3这样的,因为其元素类型相同,我们也可以使用反射,获取类型占用大小,进行移位,获取相应的元素,这个到反射的时候,我们再讲。
2. 声明和初始化
数组的声明和初始化,和其他类型差不多。声明的原则是:
- 指明存储数据的类型。
- 存储元素的数量,也就是数组长度。
var array [5]int // (补充:array中默认五个0)
以上我们声明了一个数组array
,但是我们还没有对他进行初始化,这时候数组array
里面的值,是对应元素类型的零值,也就是说,现在这个数组是5个0,这和我们java不一样,java里是null。
数组一旦声明后,其元素类型和大小都不能变了,如果还需要存储更多的元素怎么办?那么只能通过创建一个新的数组,然后把原来数组的数据复制过去。
刚刚声明的数组已经被默认的元素类型零值初始化了,如果我们再次进行初始化怎么做呢,可以采用如下办法:
var array [5]int
array = [5]int{1, 2, 3, 4, 5}
这两步比较繁琐,Go为我们提供了:=
操作符,可以让我们在创建数组的时候直接初始化。
array := [5]int{1, 2, 3, 4, 5}
这种简短变量声明的方式不仅适用于数组,还适用于任何数据类型,这也是Go语言中常用的方式(补充:简短变量声明不能用于全局变量/常量)。
有时候我们更懒,连数组的长度都不想指定,不过没有关系,使用...
代替就好了,Go会自动推导出数组的长度。
array := [...]int{1, 2, 3, 4, 5}
假如我们只想给索引为1和3的数组初始化相应的值,其他都为0怎么做呢,直接的办法有:
array := [5]int{0, 1, 0, 4, 0}
还有一种更好的办法,上面讲默认初始化为零值,那么我们就可以利用这个特性,只初始化索引1和3的值:
array := [5]int{1:1, 3:4}
3. 使用数组
数组的访问非常简单,通过索引即可,操作符为[]
,因为内存是连续的,所以索引访问的效率非常高。
array := [5]int{1:1, 3:4}
fmt.Printf("%d", array[1])
修改数组中的一个元素也很简单:
array := [5]int{1:1, 3:4}
fmt.Printf("%d\n",array[1])
array[1] = 3
fmt.Printf("%d\n",array[1])
如果我们要循环打印数组中的所有值,一个传统的就是常用的for循环:
func main() {
array := [5]int{1: 1, 3: 4}
for i := 0; i < 5; i++ {
fmt.Printf("索引:%d,值:%d\n", i, array[i])
}
}
不过大部分时候,我们都是使用for range循环:
func main() {
array := [5]int{1: 1, 3: 4}
for i, v := range array {
fmt.Printf("索引:%d,值:%d\n", i, v)
}
}
这两段示例代码,输出的结果是一样的。
同样类型的数组是可以相互赋值的,不同类型的不行,会编译错误。那么什么是同样类型的数组呢?Go语言规定,必须是长度一样,并且每个元素的类型也一样的数组,才是同样类型的数组。
array := [5]int{1: 1, 3: 4}
var array1 [5]int = array //success
var array2 [4]int = array1 //error
指针数组和数组本身差不多,只不过元素类型是指针。
(补充:
)
array := [5]*int{1: new(int), 3: new(int)} // (补充:注意,这是指针数组,从后往前读)
这样就创建了一个指针数组,并且为索引1和3都创建了内存空间,其他索引是指针的零值nil
,这时候我们要修改指针变量的值也很简单,如下即可:
array := [5]*int{1: new(int), 3:new(int)}
*array[1] = 1 // (补充:只能给索引1和3赋值)
以上需要注意的是,只可以给索引1和3赋值,因为只有它们分配了内存,才可以赋值,如果我们给索引0赋值,运行的时候,会提示无效内存或者是一个nil指针引用。
panic: runtime error: invalid memory address or nil pointer dereference
要解决这个问题,我们要先给索引0分配内存,然后再进行赋值修改。
array := [5]*int{1: new(int), 3:new(int)}
array[0] =new(int) // (补充:给索引0分配内存)
*array[0] = 2
fmt.Println(*array[0])
4. 函数间传递数组
在函数间传递变量时,总是以值的方式,如果变量是个数组,那么就会整个复制,并传递给函数,如果数组非常大,比如长度100多万,那么这对内存是一个很大的开销。
func main() {
array := [5]int{1: 2, 3: 4}
modify(array)
fmt.Println(array)
}
func modify(a [5]int){
a[1] =3
fmt.Println(a)
}
通过上面的例子,可以看到,数组是复制的,原来的数组没有修改。我们这里是5个长度的数组还好,如果有几百万怎么办,有一种办法是传递数组的指针,这样,复制的大小只是一个数组类型的指针大小。
func main() {
array := [5]int{1: 2, 3: 4}
modify(&array)
fmt.Println(array)
}
func modify(a *[5]int){
a[1] =3
fmt.Println(*a)
}
这是传递数组的指针的例子,会发现数组被修改了。所以这种情况虽然节省了复制的内存,但是要谨慎使用,因为一不小心,就会修改原数组,导致不必要的问题。
这里注意,数组指针和指针数组是两个概念,数组指针是
*[5]int
,指针数组是[5]*int
,注意*
的位置。(补充:指针数组还是个数组,只是里面存储的是指针;而数组的指针是一个指向数组的指针,要获取数组变量的地址)
针对函数间传递数组的问题,比如复制问题,比如大小僵化问题,都有更好的解决办法,这个就是切片,它更灵活,下一篇将详细介绍。
(补充:
*5. 多维数组
数组本身只有一个维度,不过可以组合多个数组创建多维数组。多维数组很容易管理具有父子关系的数据或者与坐标系相关联的数据。
直接看示例:
声明二维数组:
// 声明一个二维整型数组,两个维度分别存储4个元素和2个元素
var array [4][2]int
// 使用数组字面量来声明并初始化
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化外层数组中索引为1和3的元素
array := [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1: {0: 20}, 3: {1: 41}}
访问二维数组的元素:
var array1 [2][2]int
array1[0][0] = 10
array1[0][1] = 20
array1[1][0] = 30
array1[1][1] = 40
使用索引为多维数组赋值:
// 将array1的索引为1的维度复制到一个同类型的新数组里
var array3 [2]int = array1[1]
// 将外层数组的索引为1、内层数组的索引为0的整型值复制到新的整型变量里
var value int = array1[1][0]
在定义多维数组时,仅第一维度允许使用
“...”
内置类型 len 和 cap 都返回第一维度长度
如果元素类型支持 “==”、“!=” 等操作符,那么数组也支持此操作.
func main() {
var a, b [2]int
fmt.Println(a == b)
c := [2]int{1, 2}
d := [2]int{0, 1}
fmt.Println(c != d)
var e, f [2]map[string]int
fmt.Println(e == f) // 无效操作:[2]map[string]int cannot be compared
}
)