Golang 数组
数组,是具有相同唯一类型的一组已编号、且长度固定的数据项序列
- 这种类型可以是任意的原始类型( 数组,是一种复合( 值 )类型 ),如:整型、字符串或自定义类型
- 相对于一个个的声明变量 num0、num1 ... num9,数组形式 num[0],num[1] ... num[9] 更加方便,且易于扩展
- 它可以通过索引来读取( 或修改 )数组元素,索引从 0 开始,依次 + 1
(1)声明与赋值
// 声明:var variable_name [SIZE] variable_type var arr [5]int fmt.Println(arr) // [0 0 0 0 0] // 赋值:数组类型的完整定义为 [n]TYPE,所以,数组赋值给其它数组的时候,n 和 TYPE 必须相同 arr[2] = 3 // [0 0 3 0 0] //arr[4] = 5 // [0 0 3 0 5] fmt.Println(arr)
(2)数组的初始化
「 数组长度确定 」数组从声明时就确定,使用时,可以修改数组成员,但,数组大小不可变化
// (1)全部初始化 // 方式1:声明,并赋值 var arr1 [5]int = [5]int{ 1, 2, 3, 4, 5, } fmt.Println(arr1) // [1 2 3 4 5] // 方式2:自动类型推导 arr2 := [3]float64{1.23, 4.56} fmt.Println(arr2) // [1.23 4.56 0] // (2)部分初始化 arr3 := [3]float64{0: 1.23, 2: 4.56} fmt.Println(arr3) // [1.23 0 4.56]「 数组长度不确定 」可以使用 ... 代替数组的长度,编译器会根据元素的个数自行推断数组的长度
// 全部初始化 arr4 := [...]int{1, 2, 3, 4, 5, 6} fmt.Println(arr4) // [1 2 3 4 5 6] // 部分初始化 arr5 := [...]int{0: 1, 3: 3, 5: 5} fmt.Println(arr5) // [1 0 0 3 0 5]
(3)数组元素的访问与遍历
// 定义一个int数组
arr6 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 访问
fmt.Println(arr6) // [1 2 3 4 5 6 7 8 9 10]
fmt.Println(arr6[2]) // 3
// 修改值
arr6[2] = 100
fmt.Println(arr6) // [1 2 100 4 5 6 7 8 9 10]
// for循环遍历
for i := 0; i < len(arr6); i++ {
fmt.Println(arr6[i])
}
// for range 遍历:索引,值
for _, j := range arr6 {
fmt.Println(j)
}
规则:Golang 中的传值方式,是值传递。
这意味着,给变量赋值、给函数传参时,都是直接拷贝一个副本,将副本赋值给对方
「 拷贝 」数组,是一个复合( 值 )类型,也遵循此规则
// 拷贝: var arrCopy [5]int = arr // [0 0 3 0 5] fmt.Println(arrCopy) // [0 0 3 0 5] // 拷贝后的数组及数组元素,其指针,与原数组并不相同 fmt.Println(&arr[4]) // 0x1400001c1a0 fmt.Println(&arrCopy[4]) // 0x1400001c230 // 因此,修改拷贝后的数组,并不会影响原数组 arrCopy[0] = 100 fmt.Println(arrCopy) // [100 0 3 0 5] fmt.Println(arr) // [0 0 3 0 5]「 指针数组 [n]*T 」如果想获取、保持数组元素的指针,可以定义一个指针数组
所谓指针数组,可以这样理解:它是一个数组,用来保存指针类型的数组元素
// 创建一个 int 类型的指针数组 arrPointer := [...]*int{ 0: new(int), // 指针的默认初始化值为 nil // new(TYPE) 函数,会为一个 TYPE 类型的数据结构,划分内存,并做默认初始化操作,然后,返回这个数据对象的指针 3: new(int), } fmt.Println(arrPointer) // [0x14000126020 <nil> <nil> 0x14000126028] // 对指针元素进行赋值 *arrPointer[0] = 100 fmt.Println(*arrPointer[0]) // 100 fmt.Println(arrPointer) // [0x14000126020 <nil> <nil> 0x14000126028] // 定义一个新的指针数组元素 arrPointer[2] = new(int) fmt.Println(arrPointer) // [0x1400001a0a8 <nil> 0x1400001a0c8 0x1400001a0c0]
(4)传递数组参数( 推荐:指针方式 )给函数
Golang 中的传值方式,是值传递,这意味着给变量赋值、给函数传参时,都是直接拷贝一个副本,将副本赋值给对方
这样的拷贝方式,意味着:
- 如果数据结构体积庞大,则要完整拷贝一个数据结构副本时,效率会很低
- 函数内部修改数据结构时,只能在函数内部生效,函数一退出就失效了,因为,它修改的是副本对象
也就是说:如果函数的参数是数组类型,那么,调用函数时,传递给函数的数组,也一样是这个数组拷贝后的一个副本
[ 问题 ] Then,当声明代码中的 big_arr 后,就有 100W 个元素,假设这个 int 占用 8 字节,整个数组就占用 800W 字节,大约有 8M 数据。当调用 foo 的时候,Go 会直接复制这 8M 数据形成另一个数组副本,并将这个副本交给 foo 进行处理。在 foo 中处理的数组,实际上是这个副本,foo() 不会对原始的 big_arr 产生任何影响。
// 创建一个 100W 个元素的数组,将其传递给函数 foo() var big_arr [1e6]int func foo(a [1e6]int) { ... } foo(bigarr) // 调用foo[ 解决方案 ] So,可以将数组的指针传递给函数,这样,指针传递给函数时,复制给函数参数的是这个指针,总共才 8 个字节( 每个指针占用 1 个机器字长,64 位机器上是 64bit 共占用 8 字节 ),复制的数据量非常少。
而且,因为复制的是指针,foo() 修改这个数组时,会直接影响原始数组。
// 代码示例 func demo(arrPointer *[5]int) { fmt.Println(arrPointer) // 数组指针,&[1 2 3 4 5] fmt.Println(*arrPointer) // 数组,[1 2 3 4 5] (*arrPointer)[0] = 100 // [100 2 3 4 5] ; 通过指针传参修改后,原数组也将修改 fmt.Println(*arrPointer) } func main() { arr7 := [5]int{1, 2, 3, 4, 5} fmt.Println(arr7) // [1 2 3 4 5] demo(&arr7) // 调用函数,通过指针传递,这样,在函数内修改,就是对原数组进行修改 fmt.Println(arr7) // [100 2 3 4 5] }
多维数组
Go 语言是支持多维数组的,以二维数组( 可以简单理解为:数组中嵌套数组 )为例
package main import "fmt" func main() { // 二维数组的定义 a := [3][2]string{ // 行 列,索引从 0 开始 {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } fmt.Println(a) // [[北京 上海] [广州 深圳] [成都 重庆]] fmt.Println(a[2][1]) // 支持索引取值:重庆 // 二维数组的遍历 for _, v1 := range a { for _, v2 := range v1 { fmt.Printf("%s\t", v2) // 北京 上海 // 广州 深圳 // 成都 重庆 } fmt.Println() } // 另,多维数组只有第一层可以使用 ... 来让编译器推导数组长度,如: // 支持的写法 a := [...][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } // 不支持多维数组的内层使用... // b := [3][...]string{ // {"北京", "上海"}, // {"广州", "深圳"}, // {"成都", "重庆"}, // } }
