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{
	//		{"北京", "上海"},
	//		{"广州", "深圳"},
	//		{"成都", "重庆"},
	//	}
}