# Golang 数组和切片
# 一、Golang 数组简介
Go 语言提供了数组类型的数据结构。
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。
数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。数组的下标取值范围是从 0 开始,到长度减 1。
数组一旦定义后,大小不能更改。
# 二、Golang 数组声明
基本格式:
var variable_name [SIZE] variable_type
指定数组大小
var arr [5]float64 //等价于 var arr = [4]float{} //默认初始化为对应数据类型的“零值” fmt.Println(arr) //[0 0 0 0 0]
指定数组大小并赋初始值
var arr = [5] string{"ruby", "hedon", "rose"} // {} 中元素个数不能大于 [] 中指定的大小 fmt.Println(arr) // [ruby hedon rose ] 后面有 2 个空字符串 var arr = [5] int{'A', 'B', 'C', 'D', 'E'} // byte fmt.Println(arr) // [65 66 67 68 69]
指定数组大小并按索引赋初始值
arr := [5] int{4: 100} //表示 arr[4] 初始化为 100 fmt.Println(arr)// [0 0 0 0 100]
根据元素个数,自动识别数组大小
var arr = [...]int{1,2,3,4,5} fmt.Println(len(arr)) //5
根据最大的索引赋值来确定数组大小
arr := [...]int{0:1, 4:1, 9:1} //会根据最后一个 9:1 来判断数组大小为 10 fmt.Println(len(arr)) //10 fmt.Println(arr) //[1 0 0 0 1 0 0 0 0 1]
多维数组
arr := [3][4]int{ {1,2,3,4}, {5,6,7,8}, {9,10,11,12}, //需要逗号 } fmt.Println(arr[0][1]) //2
# 三、Golang 数组遍历
# 1. for ... i
arr := [5]int{1,2,3,4,5}
for i := 0; i < len(arr); i++{
fmt.Println(arr[i])
}
/*
1
2
3
4
5
*/
# 2. for ... range
arr := [5]int{1,2,3,4,5}
for i, v := range arr {
fmt.Printf("index=%d, value=%\n", i, v)
}
/*
index=0, value=1
index=1, value=2
index=2, value=3
index=3, value=4
index=4, value=5
*/
arr := [5]int{1,2,3,4,5}
for _, v := range arr { //如果不需要用到索引,则可以使用 _ 来忽略索引
//...
}
# 四、Golang 数组是值类型
Go 中的数组是值类型,而不是引用类型。这意味着:
当它们被分配给一个新变量时,将把原始数组的副本分配给新变量。如果对新变量进行了更改,则不会在原始数组中反映。
arr1 := [5]int{1,2,3,4,5} arr2 := arr1 arr2[0] = 10086 fmt.Println(arr1) //[1 2 3 4 5] fmt.Println(arr2) //[10086 2 3 4 5]
函数或方法传参时传一个数组,在函数或方法内部对数组进行修改,是不会影响外面的数组的值的。
func main() { arr := [5]int{1,2,3,4,5} fmt.Println("main 传参前:", arr) test(arr) fmt.Println("main 传参后:", arr) } func test(arr [5]int) { arr[0] = 10086 fmt.Println("test 内部修改后:", arr) }
输出:
main 传参前: [1 2 3 4 5] test 内部修改后: [10086 2 3 4 5] main 传参后: [1 2 3 4 5]
数组的大小是类型的一部分,因此 [5]int 和 [3]int 是不同的类型。因此数组的大小的不能调整的。这非常鸡肋,所以一般不怎么会用数组,一般都是用切片 slice。
# 五、Golang 切片简介
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(动态数组),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
切片是一种方便、灵活且强大的包装器。切片本身没有任何数据。它们只是对现有数组的引用。
切片与数组相比,不需要设定长度,在 [] 中不用设定值,相对来说比较自由。
从概念上面来说 slice 像一个结构体,这个结构体包含了三个元素:
type slice struct {
array unsafe.Pointer
len int
cap int
}
- 指针 ptr:指向数组中 slice 指定的开始位置
- 长度 len:即 slice 的长度
- 最大长度 cap:也就是 slice 开始位置到数组的最后位置的长度
# 六、Golang 切片声明
基本格式:
var slice = []type //此时 slice 为 nil
var slice = []type{} //此时 slice 不为 nil
var slice []type = make([]type, len) //此时 slice 不为 nil
slice := make([]type, len)
slice := make([]type, len, cap)
slice := arr[startIndex:endIndex] //从一个数组中开一个切片,不包括 endIndex
slice := arr[:endIndex] //从头到 endIndex,不包括 endIndex
slice := arr[startIndex:] //从 startIndex 到尾
slice2 := slice1[startIndex:endIndex] //从一个切片中再开一个切片
示例:
package main
import "fmt"
func main() {
arr := [7]int{1,2,3,4,5,6,7}
slice := arr[2:4]
fmt.Println(slice) //[3,4]
//虽然 len(slice)=2,但是 cap(slice)=5,所以 slice[0:4] 还是可以取到值的
slice2 := slice[0:4]
fmt.Println(slice2) //[3,4,5,6]
}
# 七、Golang 切片是数组的引用
slice 本质上是 array 的一个引用,所以对 slice 的修改都会反映到其引用到的 array、以及其他引用该 array 的 slice。
package main
import "fmt"
func main() {
arr := [7]int{1,2,3,4,5,6,7}
fmt.Println("修改前:")
fmt.Println("arr:", arr)
slice1 := arr[2:4]
slice2 := slice1[0:4]
slice1[0] = 10086
fmt.Println("修改后:")
fmt.Println("arr:", arr)
fmt.Println("slice1:", slice1)
fmt.Println("slice2:", slice2)
}
输出:
修改前:
arr: [1 2 3 4 5 6 7]
修改后:
arr: [1 2 10086 4 5 6 7]
slice1: [10086 4]
slice2: [10086 4 5 6]
# 八、Golang 切片常用方法
# 1. len
返回 slice 中元素的数量。
arr := [5]int{1,2,3,4,5}
slice := arr[2:4]
fmt.Println(len(slice)) //2
# 2. cap
切片的容量是从创建切片的索引开始的底层数组中元素的数量。
arr := [5]int{1,2,3,4,5}
slice := arr[2:4]
fmt.Println(cap(slice)) //3
# 3. append
append 向 slice 里面追加一个或者多个元素,然后返回一个和 slice 一样类型的 slice。
append 函数会改变 slice 所引用的数组的内容,从而影响到引用同一数组的其它 slice。
但当 slice 中没有剩余空间,即 cap-len == 0 时,此时将动态分配新的数组空间。返回的 slice 数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的 slice 则不受影响。
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* 添加另外一个切片 */
slice2 := []int {10,11,12,13}
numbers = append(numbers, slice2...)
printSlice(numbers)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
输出:
len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=9 cap=12 slice=[0 1 2 3 4 10 11 12 13]
# 4. copy
copy 从源 slice 的 src 中复制元素到目标 dist,并且返回复制的元素的个数。
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 追加一个切片 */
numbers = append(numbers, []int{1,2,3}...)
printSlice(numbers)
/* 声明一个新切片 */
numbers1 := make([]int, len(numbers), cap(numbers) * 2)
printSlice(numbers1)
/* 将 numbers 拷贝到 numbers1 */
copy(numbers1, numbers)
printSlice(numbers1)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
输出:
len=0 cap=0 slice=[]
len=3 cap=3 slice=[1 2 3]
len=3 cap=6 slice=[0 0 0]
len=3 cap=6 slice=[1 2 3]
numbers1 与numbers 两者不存在联系,numbers 发生变化时,numbers1 是不会随着变化的。
也就是说 copy 方法是不会建立两个切片的联系的。
# 5. sort
sort 包中内含支持各种类型切片的排序函数。
package main
import (
"fmt"
"sort"
)
func main() {
s1 := []int{5,7,2,3,1,67}
fmt.Println("排序前:", s1)
//内置的排序算法
sort.Ints(s1)
fmt.Println("排序后:", s1)
//降序,第二个参数传排序规则
sort.Slice(s1, func(i, j int) bool {
return i > j
})
fmt.Println("反排序:", s1)
}
输出:
排序前: [5 7 2 3 1 67]
排序后: [1 2 3 5 7 67]
反排序: [67 7 5 3 2 1]
# 九、Golang 切片原理
# 1. 扩容机制
append 单个元素,或者 append 少量的多个元素,这里的少量指 double 之后的容量能容纳:
- 当 cap < 1024 的时候,每次 × 2
- 当 cap >= 1024 的时候,每次 × 1.25
当原 slice 容量小于 1024 的时候,新 slice 容量变成原来的 2 倍;原 slice 容量超过 1024,新 slice 容量变成原来的 1.25 倍。 这个结论是不对的~
growslice 方法里面会调用 roundupsize 方法内存对齐,进行内存对齐之后,新 slice 的容量是要 大于等于 老 slice 容量的 2 倍或者 1.25 倍。
package main import ( "fmt" ) func main() { s := make([]int, 0) oldCap := cap(s) for i := 0; i < 2048; i++ { s = append(s, i) newCap := cap(s) if newCap != oldCap { fmt.Printf("before cap = %-4d | after append %-4d cap = %-4d\n", oldCap, i, newCap) oldCap = newCap } } }
输出:
before cap = 0 | after append 0 cap = 1 before cap = 1 | after append 1 cap = 2 before cap = 2 | after append 2 cap = 4 before cap = 4 | after append 4 cap = 8 before cap = 8 | after append 8 cap = 16 before cap = 16 | after append 16 cap = 32 before cap = 32 | after append 32 cap = 64 before cap = 64 | after append 64 cap = 128 before cap = 128 | after append 128 cap = 256 before cap = 256 | after append 256 cap = 512 before cap = 512 | after append 512 cap = 1024 before cap = 1024 | after append 1024 cap = 1280 before cap = 1280 | after append 1280 cap = 1696 //这里开始就不是 1.25倍增长,大于1.25倍。 before cap = 1696 | after append 1696 cap = 2304
若是 append 多个元素且 double 后的容量不能容纳,直接使用预估的容量
var s []int s = append(s, 0, 1, 2) //len=3, cap=3