# GoLang 数组 > 有过Python、JavaScript编程经验的人都知道它们的数组是动态的, 可以随需求自动增大数组长度, Go里面的数组长度却是**固定**的,无法扩大或者缩小, 但Go中也有类似的动态"数组",称为slice,后面会讲到 ## 概述 数组是由相同类型元素的集合组成的数据结构,计算机会为数组分配一块连续的内存来保存其中的元素,我们可以利用数组中元素的索引快速访问特定元素,常见的数组大多都是一维的线性数组,而多维数组在数值和图形计算领域却有比较常见的应用 ![3D-array](image/2019-02-20-3D-array.jpg) ## 数组的特点 当在Go中声明一个数组之后,会在内存中开辟一段**固定长度**的、**连续的空间**存放数组中的各个元素,这些元素的数据**类型完全相同**,可以是内置的简单数据类型(int、string等),也可以是自定义的struct类型。 - **固定长度**:这意味着数组不可增长、不可缩减。想要扩展数组,只能创建新数组,将原数组的元素复制到新数组 - **连续空间**:这意味可以在缓存中保留的时间更长,搜索速度更快,是一种非常高效的数据结构,同时还意味着可以通过数值index的方式访问数组中的某个元素 - **数据类型**:意味着限制了每个block中可以存放什么样的数据,以及每个block可以存放多少字节的数据 例如,使用下面的语句声明一个长度为4的int类型的数组,那么这个数组最多只能存放4个元素,且所有元素都只能是int类型。同时,还为这个数组做了初始化 ```go studentArray := [6]int{20, 50, 60, 70, 80, 90} ``` 这个数组的结构如下图所示: ![arrays-in-golang](image/Untitled-Diagram33.jpg) 最下面一排是这个数组值的存放位置, 也就是index ## 数组的定义 ![img](image/Untitled-Diagram34.jpg) Go 语言数组声明需要指定元素类型及元素个数,语法格式如下 ```go var 数组变量名 [元素数量]T ``` 比如:`var a [5]int`, 数组的长度必须是常量,并且长度是数组类型的一部分。**一旦定义,长度不能变**。 `[5]int`和`[10]int`是不同的类型。 ```go var a [3]int var b [4]int a = b //不可以这样做,因为此时a和b是不同的类型 ``` 数组可以通过索引进行访问,索引是从`0`开始,最后一个元素索引是:`len-1`,访问越界(索引在合法范围之外),则触发访问越界`panic`。 ## 数组的初始化 ![img](image/Untitled-Diagram35.jpg) 数组的初始化也有很多方式。 ### 方法一 初始化数组时可以使用初始化列表来设置数组元素的值。 ```go func main() { var testArray [3]int //数组会初始化为int类型的零值 var numArray = [3]int{1, 2} //使用指定的初始值完成初始化 var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化 fmt.Println(testArray) //[0 0 0] fmt.Println(numArray) //[1 2 0] fmt.Println(cityArray) //[北京 上海 深圳] } ``` ### 方法二 按照上面的方法每次都要确保提供的初始值和数组长度一致,一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度,例如: ```go func main() { var testArray [3]int var numArray = [...]int{1, 2} var cityArray = [...]string{"北京", "上海", "深圳"} fmt.Println(testArray) //[0 0 0] fmt.Println(numArray) //[1 2] fmt.Printf("type of numArray:%T\n", numArray) //type of numArray:[2]int fmt.Println(cityArray) //[北京 上海 深圳] fmt.Printf("type of cityArray:%T\n", cityArray) //type of cityArray:[3]string } ``` ### 方法三 我们还可以使用指定索引值的方式来初始化数组,例如: ```go func main() { a := [...]int{1: 1, 3: 5} fmt.Println(a) // [0 1 0 5] fmt.Printf("type of a:%T\n", a) //type of a:[4]int } ``` ## 数组的遍历 遍历数组有以下两种方法: ```go func main() { var a = [...]string{"北京", "上海", "深圳"} // 方法1:for循环遍历 for i := 0; i < len(a); i++ { fmt.Println(a[i]) } // 方法2:for range遍历 for index, value := range a { fmt.Println(index, value) } } ``` ## 多维数组 Go语言是支持多维数组的,我们这里以二维数组为例(数组中又嵌套数组)。 ### 二维数组的定义 ```go func main() { a := [3][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]] fmt.Println(a[2][1]) //支持索引取值:重庆 } ``` ### 二维数组的遍历 ::: {tabbed} INPUT ```go func main() { a := [3][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } for _, v1 := range a { for _, v2 := range v1 { fmt.Printf("%s\t", v2) } fmt.Println() } } ``` ::: ::: {tabbed} OUTPUT ```bash 北京 上海 广州 深圳 成都 重庆 ``` ::: ```{NOTE} 多维数组**只有第一层**可以使用`...`来让编译器推导数组长度。 ``` ```go //支持的写法 a := [...][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } //不支持多维数组的内层使用... b := [3][...]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } ``` ## 数组的属性 **cap和slice** - cap 代表capacity容量 - len代表length长度 - len代表目前数组里的几个元素,cap代表给数组分配的内存空间可以容纳多少个元素 - 由于数组初始化之后长度不会改变,不需要预留额外的内存空间,`len(arr) == cap(arr)` ## 指针数组 可以声明一个指针类型的数组,这样数组中就可以存放指针。注意,指针的默认初始化值为`nil` 例如,创建int类型指针的数组: ``` a := [4]*int{0: new(int), 3: new(int)} fmt.Println(a) // 如果指针地址为空, 是会报空指针错误的, 比如 // *a[1] = 3 会报 panic: runtime error: invalid memory address or nil pointer dereference *a[0] = 10 *a[3] = 20 fmt.Println(a) fmt.Println(*a[0], *a[3]) // 为1赋值 a[1] = new(int) *a[1] = 30 fmt.Println(a, *a[1]) ``` ![image](image/array_ptr1.png) ## 数组传参 ```{note} 与C语言中的数组显著不同的是,Go语言中的数组在赋值和函数调用时的形参都是值复制。 ``` - 数组的长度和类型都是数组类型的一部分属性,函数传递数组类型时这两部分必须吻合 - go语言没有引用传参,都是按值传参,即传递数组实际上是传的数组的拷贝,当数组的长度很大时,仅传参开销就非常大 - 如果想修改函数外部的数组,就把数组的指针传进来 ```go func modifyArray(x [3]int) { x[0] = 100 } func modifyArray2(x [3][2]int) { x[2][0] = 100 } func modifyArray3(arr *[3]int) *[3]int { arr[2] = 1 return arr func main() { a := [3]int{10, 20, 30} modifyArray(a) //在modify中修改的是a的副本x fmt.Println(a) //[10 20 30] b := [3][2]int{ {1, 1}, {1, 1}, {1, 1}, } modifyArray2(b) //在modify中修改的是b的副本x fmt.Println(b) //[[1 1] [1 1] [1 1]] var aa [3]int = [3]int{3, 4, 5} modifyArray3(&aa) fmt.Println(aa) } ``` ```{IMPORTANT} 1. 数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。 2. `[n]*T`表示指针数组,`*[n]T`表示数组指针 。 ``` ## 练习题 1. 求数组`[1, 3, 5, 7, 8]`所有元素的和 2. 找出数组中和为指定值的两个元素的下标,比如从数组`[1, 3, 5, 7, 8]`中找出和为8的两个元素的下标分别为`(0,3)`和`(1,2)`。 ### 解题1 ```go func q1Demo(x [5]int) { ret := 0 for _, v := range x { ret += v } fmt.Println(ret) } ``` ### 解题2 ```go func q2Demo(arr [5]int, ret int) { Loop1: for index, value := range arr { for index1, value1 := range arr[index+1:] { if value+value1 == ret { fmt.Println(index, index1+1+index) continue Loop1 // break } else { continue } } } } ```