数组是 Go 语言编程中最常用的数据结构。
数组的常规声明方法有:
[32]byte // 长度为 32 的数组,每个元素为一个字节
[2*N] struct {x, y int32} // 复杂类型数组
[1000] * float64 // 指针数组
[3][5]int // 二维数组
[2][2][2]float64 // 等同于[2]([2]([2]float64))
在 Go 语言中, 数组长度在定义后就不可更改,在声明时长度可以为一个常量或者一个常量表达式(常量表达式是指在编译期即可计算结果的表达式)。
数组的长度是该数组类型的一个内置常量,可以用 Go 语言的内置函数 len() 来获取。
arrlength := len(arr)
数组元素的访问:
for i := 0; i<len(array); i++ {
fmt.Println("Element", i, "of array is", array[i])
}
Go 和 Python 一样也有关键字 range, 它的功能就是遍历容器中的元素。数组也是 range 的支持范围之一。
for i, v := range array {
fmt.Println("Array element[", i, "]=", v)
}
range 具有两个返回值,第一个返回值是元素的数组下标,第二个返回值是元素的值。
数组内值类型
值类型的概念有 1 个特点:
- 在赋值和作为参数传递时,将产生一次复制动作。
package main
import "fmt"
func modify(array [5]int){
array[0] = 10 //试图修改数组的第一个元素
fmt.Println("In modify(), array values:", array)
}
# 函数 modify 内操作的那个数组跟 main 中传入的数组是两个不同的实例。
func main(){
array := [5]int{1, 2, 3, 4, 5} // 定义并初始化一个数组
modify(array) //传递给一个函数,并试图在函数体内修改这个数组内容
fmt.Println("In main(), array values:", array)
}
Go 语言和 Python 非常相似的地方就是数组切片操作。
数组的特点是:
- 长度在定义之后无法再次修改
- 每传递一次都会产生一份副本。
而切片作为指向数组的指针,同时拥有:
- 指向原数组的指针
- 元素个数
- 已分配的存储空间
这是一个完整的数据结构。只是在底层仍然使用数组来管理元素。
数组切片基于数组添加了一系列管理功能,可以随时动态扩充存放空间,并且可以被随意传递而不会导致所管理的元素被重复复制。
下面从 3 个方面来考察数组切片:
- 创建数组切片
- 基于数组切片创建数组切片
- 元素遍历
- 动态增减元素
- 内容复制
创建
package main
import "fmt"
func main() {
var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var mySlice []int = myArray[:5]
fmt.Println("Elements of myArray: ")
for _, v := range myArray {
fmt.Print(v, " ")
}
fmt.Println("\n Elements of mySlice: ")
for _, v := range mySlice {
fmt.Print(v, " ")
}
fmt.Println()
}
可以看到,这里切片的操作和 Python 是一模一样的。
mySlice = myArray[:]
mySlice = myArray[:5]
mySlice = myArray[5:]
以上的方式都是基于数组来创建的,下面是通过元素直接创建:
mySlice1 := make([]int, 5) // 创建一个初始元素个数为 5 的数组切片,元素初始值为 0
mySlice2 := make([]int, 5, 10) // 创建一个初始元素个数为 5 的数组切片,元素初始值为 0, 并预留 10 个元素的存储空间
mySlice3 := []int{1, 2, 3, 4, 5}
这中间是会有匿名数组被创建出来的,但不需要操心。
还可以基于数组切片创建数组切片,和基于数组创建切片的不同之处在于,数组定长,数组切片不定长。也是基于这个特点,新的切片可以选择到旧切片的范围外,范围外的元素在新切片中会被填上 0 。
oldSlice := []int{1, 2, 3, 4, 5}
newSlice := oldSlice[:6]
遍历
数组切片可以兼容所有操作数组的方法。
# 数组切片的遍历
for i := 0; i < len(mySlice); i++ {
fmt.Println("mySlice[", i, "] =", mySlice[i])
}
for i, v := range mySlice {
fmt.Println("mySlice[", i, "] = ", v)
}
动态增减
与数组相比,数组切片多了一个存储能力的概念,其元素个数和分配的空间可以是两个不同的值。
package main
import "fmt"
func main() {
mySlice := make([]int, 5, 10)
fmt.Println("len(mySlice):", len(mySlice)) // len 返回的是数组切片中当前所存储元素个数
fmt.Println("cap(mySlice):", cap(mySlice)) // cap 返回的数组切片分配的空间大小
}
增加的方法和像数组中追加元素的方法一样,使用 append() 函数, 它的第二个参数是一个不定参数。
mySlice = append(mySlice, 1, 2, 3)
mySlice2 := []int{8, 9, 10}
mySlice = append(mySlice, mySlice2...)
和 Python 一样,Go 也存在省略号。相当于把 mySlice2 包含的所有元素打散后传入。等同于下面:
mySlice = append(mySlice, 8, 9, 10)
数组切片会自动处理存储空间不足的问题。
内容复制
和 Python 一样,Go 也存在内置函数 copy(), 它执行的是将一个切片复制到另一个切片。当两个切片大小不一,却要执行复制操作时,就会按照较小的那个切片个数进行复制。
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制 slice1 的前 3 个元素到 slice2 中
copy(slice1, slice2) // 只会复制 slice2 的 3 个元素到 slice1 的前 3 个位置