什么是数组
数组是由 相同类型元素 的 集合 组成的数据结构,计算机会为数组分配一块连续的内存来保存其中的元素,我们可以利用数组中元素的索引快速访问特定元素,常见的数组大多都是一维的线性数组。
在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。
我们可以把 Golang 数组的特征归纳为以下三点:
- 固定长度:数组不可增长、不可缩减。想要扩展数组,只能创建新数组,将原数组的元素复制到新数组。
- 内存连续:可以在缓存中保留的时间更长,搜索速度更快,是一种非常高效的数据结构,同时还意味着可以通过数值的方式(arr[index])索引数组中的元素。
- 固定类型:固定类型限制了每个数组元素可以存放什么样的数据,以及每个元素可以存放多少字节的数据。
数组是个固定长度的数据类型,其长度和存储元素的数据类型都在声明数组时确定,并且不能更改。
如果需要存储更多的元素,必须先创建一个更长的数组,然后把原来数组里的数据复制到新数组中。
数组占用的内存是连续分配的,比如我们创建一个包含 5 个整数元素的数组:
arr1 := [5]int{10,20,30,40,50}
数组在内存中的结构类似下图:
由于内存连续,CPU 能把正在使用的数据缓存更久的时间。而且在内存连续的情况下非常容易计算索引,也就是说可以快速迭代数组里的所有元素。
原因是数组的类型信息可以提供每次访问一个元素时需要在内存中移动的距离,既然数组的每个元素的类型都相同,又是连续分配,因此就可以以固定的速度索引数组中的任意元素,并且速度非常快!
语法
var 数组名 [长度]数据类型
声明方式
- 存储一组相同数据类型的数据的数据结构
- 数组一旦定义后,长度不可改变 ```go arr1 := [5]int{1, 2, 3, 4, 5} // 定义数组arr1并初始化 var a1 [5]int = [5]int{1,2,3} // 定义数组a1并初始化 var a1 = [5]int{1,2,3} // 定义数组a1并初始化 var a2 [5]int // 定义数组a2 默认值为int类型 0 a2[0]=1
var a3 [5]int = [5]int{1:3,4:8} // 在指定位置赋值 var a4 = […]int{1,2,3} // 使用类型推断,自动推测数组长度
Go 语言中的数组是值类型,因此还可以用 `new` 来创建:```govar arr2 = new([5]int)
new 返回类型的指针,因此 arr1 与 arr2 的区别在于:
arr1的类型为 [5]int-
指明数组长度
指明数组的长度 len,然后在 {} 中填写初始化值,这些值会按下标从小到大的顺序分配。
初始化值的个数不允许超过长度 len。
当初始化值的个数小于 len 时,未被初始化的位置等于数据类型的默认值。 ```go package main import “fmt” func main() {// 定义int类型数组 arr并初始化 arr := [3]int{1, 2, 3} fmt.Println(“————定义int类型数组 arr并初始化—————“) for index, data := range arr {
fmt.Printf("arr[%d] = %d\n", index, data)
}
// 定义int类型 数组arr2 未初始化 var arr2 [5]int fmt.Println(“————定义int类型 数组arr2 未初始化—————“) for index, data := range arr2 {
fmt.Printf("arr[%d] = %d\n", index, data)
}
// 定义字符串类型 数组arr3 未初始化var arr3 [5]stringfmt.Println("--------定义字符串类型 数组arr3 未初始化----------")for index, data := range arr3 {fmt.Printf("arr[%d] = %q\n", index, data)}
}
// 打印结果 ————定义int类型数组 arr并初始化————— arr[0] = 1 arr[1] = 2 arr[2] = 3 ————定义int类型 数组arr2 未初始化————— arr[0] = 0 arr[1] = 0 arr[2] = 0 arr[3] = 0 arr[4] = 0 ————定义字符串类型 数组arr3 未初始化————— arr[0] = “” arr[1] = “” arr[2] = “” arr[3] = “” arr[4] = “”
<a name="CR1Au"></a>### **暗示数组长度**初始化时不确定长度时,也可以不直接指明数组的长度,而是使用** **`**[...]**` 代替。<br />和指明数组长度时相同,此时也可以使用顺序填写和指定索引两种方式来初始化数组。当使用 {a, b, c} 方式传递初始化值时,Go 语言将 通过初始化元素的个数来确定数组的长度。```go// 通过传递初始化值确定数组长度// 传递了 5 个元素,数组长度为 5c := [...]int{1, 2, 3, 4, 5}for index, val := range c {fmt.Printf("下标 = %d, 值 = %d\n", index, val)}/* Output:下标 = 0, 值 = 1下标 = 1, 值 = 2下标 = 2, 值 = 3下标 = 3, 值 = 4下标 = 4, 值 = 5*/
若通过指明数组的索引和对应的值来初始化数组,此时数组的长度就等于 最大索引数 + 1
// 最大索引是 9,所以数组的长度为 10e := [...]int{9: 10}for index, val := range e {fmt.Printf("下标 = %d, 值 = %d\n", index, val)}/* Output:下标 = 0, 值 = 0下标 = 1, 值 = 0下标 = 2, 值 = 0下标 = 3, 值 = 0下标 = 4, 值 = 0下标 = 5, 值 = 0下标 = 6, 值 = 0下标 = 7, 值 = 0下标 = 8, 值 = 0下标 = 9, 值 = 10*/
长度与容量
len: 长度 len() 不是数组独有的,array/map/slice/string都可以使用
cap: 容量 cap() 一般用于切片
数组遍历
两种方法
- for
- range ```go package main
import “fmt”
func main() { var a [10]int var b [5]int
fmt.Printf("len(a) = %d, len(b) = %d\n", len(a), len(b)) // len(a) = 10, len(b) = 5//操作数组元素,从0开始,到len()-1, 不对称元素,这个数字,叫下标//这是下标,可以是变量或常量a[0] = 1i := 1a[i] = 2 // a[1] = 2//赋值,每个元素for i := 0; i < len(a); i++ {a[i] = i + 1}//打印// index当前下标,data 当前元素for i, data := range a {fmt.Printf("a[%d] = %d\n", index, data)}
}
<a name="fOqAN"></a>## 指针数组初始化数组 pointerArray,传入的初始化值为整型 m 与 n 的内存地址(&m 和 &n),那么 pointerArray 就是一个装着 int 类型指针的数组,即指针数组,它的类型为 [2]*int。```gopackage mainimport "fmt"func main() {m := 1n := 2// 初始化 pointerArray,传入 m 与 n 的地址// pointerArray 包含了整型地址,是一个装着指针的数组pointerArray := [2]*int{&m, &n}fmt.Println(pointerArray)/* Output:[0xc000114000 0xc000114008]*/}/**数组指针: *[5]int指针数组: [5]*int*/func pArr() {// 指针数组// 并且为索引1和3都创建了内存空间,其他索引是指针的零值nilarray := [5]*int{1: new(int), 3:new(int)}/**以上需要注意的是,只可以给索引1和3赋值,因为只有它们分配了内存,才可以赋值,如果我们给索引0赋值,运行的时候,会提示无效内存或者是一个nil指针引用。*/*array[1] = 1// 分配内存array[0] = new(int)// 赋值*array[0] = 2fmt.Println(*array[0])}
数组是值类型
和 C/C++ 不同,Go 语言的数组是值类型的。赋值和传参都会复制整个数组,而不是指针。
我们初始化一个数组 arr,并把它赋值给数组 bArr,然后打印两者的值与指针:
赋值
package mainimport "fmt"func main() {arr := [5]int{1, 2, 3, 4, 5}bArr := arrfmt.Printf("数组 arr - 值:%v,指针:%p\n", arr, &arr)fmt.Printf("数组 bArr - 值:%v,指针:%p\n", bArr, &bArr)/* Output:数组 arr - 值:[1 2 3 4 5],指针:0xc0000aa060数组 bArr - 值:[1 2 3 4 5],指针:0xc0000aa090*/}
可以看到,两者的值是相同的,但是内存地址却不同,说明在赋值的过程中复制了整个数组。
数组函数参数 (值传递)
- 数组做函数参数,它是值传递
- 实参数组的每个元素给形参数组拷贝一份
- 形参的数组是实参数组的复制品 ```go package main
import “fmt”
func transmitA(b [5]int) { fmt.Printf(“形参 b - 值:%v,指针:%p\n”, b, &b) }
func main() { a := [5]int{1, 2, 3, 4, 5} fmt.Printf(“数组 a - 值:%v,指针:%p\n”, a, &a)
// 把数组 a 传入函数transmitA(a)
}
/ Output: 数组 a - 值:[1 2 3 4 5],指针:0xc00001e0c0 形参 b - 值:[1 2 3 4 5],指针:0xc00001e150 /
从输出可以看出,两者的值依然相同,内存地址却是不同的。这说明在传参时数组也被复制了。<a name="qb958"></a>### 数组指针<br />数组做函数参数时,在函数间传递变量时,总是以值的方式。<br />如果数组非常大,比如长度100多万,那么这对内存是一个很大的开销。<br />如果有几百万怎么办,有一种办法是传递数组的指针,这样,复制的大小只是一个数组类型的指针大小。- 数组指针: *[5]int- 指针数组: [5]*int<a name="bQujU"></a>#### 数组指针定义声明一个数组 arr,然后将它的地址赋值给 arrayPointer。<br />arrayPointer 就是一个指向数组 arr 的指针,即数组指针,它的类型为 *[5]int。```goarr := [5]int{1, 2, 3, 4, 5}// 把数组 arr 的地址赋值给 arrayPointer// arrayPointer 是指向数组的指针,类型为 *[5]intarrayPointer := &arrfmt.Println(arrayPointer)/* Output:&[1 2 3 4 5]*/
数组指针作函数参数 (引用传递)
package mainimport "fmt"/**数组指针 p 类型为 *[5]int*p代表指针所指向的内存,就是实参a *[5]int*/func modify(p *[5]int) {p[0] = 666 // 等价于 (*p)[0] = 666// fmt.Println("modify *a = ", *p)// 传进来的是地址 打印地址fmt.Printf("modify方法 *p - 值:%v,指针:%p\n", *p, p)}func main() {arr := [5]int{1, 2, 3, 4, 5} //初始化fmt.Printf("main 数组 arr - 值:%v,指针:%p\n", arr, &arr)modify(&arr) //地址传递fmt.Printf("main 数组 arr - 值:%v,指针:%p\n", arr, &arr)}// 打印结果main 数组 arr - 值:[1 2 3 4 5],指针:0xc0000aa060modify方法 *p - 值:[666 2 3 4 5],指针:0xc0000aa060main 数组 arr - 值:[666 2 3 4 5],指针:0xc0000aa060
结果中可以看到方法modify中修改了数组首元素的值,main方法中的arr也跟着变了。
- 值传递
- 理解为存储的数值本身
- 将数据复制一份,赋值给变量,修改变量值不影响原来的数据
- 数组也是值传递
- int,float,bool,string,array
- 引用传递
- 理解为存储的数据内存地址
- 将内存地址赋值一份,赋值给其他变量
- slice,map是引用传递
- 值类型可以用==比较数组作为值类型,使用==比较时,只要对应下标位上的值相等,数组就相等
多维数组
练习代码
package mainimport "fmt"func initArr() {// var 数组名称 [长度]数据类型 = [长度]数据类型{元素...}// 固定数组长度: 数组名称 := [长度]数据类型{...}// 暗示数组长度: 数组名称 := [...]数据类型{...}// 固定长度:数组不可增长、不可缩减,想要扩展,只能创建新数组var arr [3]int = [3]int{1, 2, 3}fmt.Println(arr) // 1 2 3// 简写arrTest := [3]int{1, 2, 3}fmt.Println(arrTest) // [1 2 3]// 定义的同时部分初始化 不够后面补nilvar arr1 [4]string = [4]string{"A", "B"}fmt.Println(arr1) // [A B ]// 补0var arr2 [4]int = [4]int{101, 301}fmt.Println(arr2) // [101 301 0 0]// 定义的同时指定元素初始化// 初始化前面数值是数组下标var arr3 [4]int = [4]int{1: 101, 3: 501}fmt.Println(arr3) // [0 101 0 501]// 1.先定义再逐个初始化var arr4 [5]intarr4[1] = 101arr4[2] = 300fmt.Println(arr4) // [0 101 300 0 0]// 暗示长度 定义的同时完全初始化var arr5 = [...]int{1, 2, 3}fmt.Println(arr5) // [1 2 3]// 定义时同时指定元素var arr6 = [...]int{6: 101}fmt.Println(arr6) // [0 0 0 0 0 0 101]arr7 := [...]int{1, 2, 3}arr7[1] = 666fmt.Println(arr7[0]) // 1fmt.Println(arr7[1]) // 666fmt.Println(arr7[2]) // 3// for 遍历for i := 0; i < len(arr7); i++ {fmt.Println(arr7[i])}// for ... rangfor index, value := range arr7 {fmt.Println(index, value)}// 指针数组: [5]*int// 数组指针: *[5]int 参数传递// 指针数组 元素只能存放元素的地址arrP := [...]*int{new(int), new(int)}fmt.Println(arrP) //[0xc0000ae038 0xc0000ae040]x, y := 101, 201arrP2 := [...]*int{&x, &y}fmt.Println(arrP2) // [0xc0000160d0 0xc0000160d8]fmt.Println(*arrP2[0]) // 101fmt.Println(*arrP2[1]) // 102// 数组指针 指向一个数组的地址arr8 := [2]string{"Haha", "wow"}arrP3 := &arr8 //等介 var arrP3 *[2]string = &arr8fmt.Println(arrP3[0]) //Hahafmt.Println(arrP3[1]) //wow}func init() {// initArr()}// 值传递func changeArr(arr [4]int) {for key, value := range arr {arr[key] = value + 1}fmt.Println(arr)}// 引用传递func changeArr2(arr *[4]int) {for key, value := range arr {arr[key] = value + 1}fmt.Println(arr)}func main() {arr := [...]int{100, 200, 300, 400}fmt.Println("arr:", arr)// changeArr(arr)changeArr2(&arr)fmt.Println("arr:", arr)/**arr: [100 200 300 400][101 201 301 401]arr: [100 200 300 400]arr: [100 200 300 400] 修改后&[101 201 301 401]arr: [101 201 301 401] // 修改后*/}
总结
- 数组的长度是固定的,初始化时需要「明示」或「暗示」数组的长度
- 数组的长度是数组类型的组成部分,[2]int 与 [100]int 是不同类型的数组
- 使用 for … range 遍历数组
- 在 Go 语言中,数组是值类型,赋值和传递参数都会发生数组的复制
- 数组指针是一个指针,它指向了一个数组
- 指针数组是一个数组,它里面装着指针
参考:
https://zhuanlan.zhihu.com/p/93053489
https://www.cnblogs.com/sparkdev/p/10704389.html
