数组和切片
教程地址:点击去往视频教程
1. 数组
数组
是一组连续内存空间
存储的具有相同类型
的数据,是一种线性结构
。在Go语言中,
数组
的长度是固定
的。
数组声明:var 数组变量名 [元素数量]Type
- 数组变量名:数组声明及使用时的变量名。
- 元素数量:数组的元素数量,可以是一个表达式,但最终通过编译期计算的结果必须是整型数值,元素数量不能含有到运行时才能确认大小的数值。
- Type:可以是任意基本类型。
func main() {
//默认数组中的值是类型的默认值
var arr [3]int
//通过下标取值
fmt.Println(arr[0])
fmt.Println(arr[1])
fmt.Println(arr[2])
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
2. 数组使用
赋值
初始化方式
func main() { var arr [3]int = [3]int{1, 2, 3} //如果第三个不赋值,就是默认值0 var arr1 [3]int = [3]int{1, 2} //可以使用简短声明 arr2 := [3]int{1, 2, 3} //如果不写数据数量,而使用...,表示数组的长度是根据初始化值的个数来计算 arr3 := [...]int{1, 2, 3} //给索引为2的赋值 ,所以结果是 0,0,3 arr4 := [3]int{2: 3} fmt.Println(arr, arr1, arr2, arr3, arr4) }
1
2
3
4
5
6
7
8
9
10
11
12索引方式
func main() { var arr [3]int arr[0] = 5 arr[1] = 6 arr[2] = 7 fmt.Println(arr) }
1
2
3
4
5
6
7
取值
索引方式
func main() { var arr [3]int = [3]int{1, 2, 3} fmt.Println(arr[0]) fmt.Println(arr[1]) fmt.Println(arr[2]) }
1
2
3
4
5
6for range
func main() { var arr [3]int = [3]int{1, 2, 3} for index, value := range arr { fmt.Printf("索引:%d,值:%d \n", index, value) } }
1
2
3
4
5
6
3. 多维数组
func main() {
// 声明一个二维整型数组,两个维度的长度分别是 4 和 2
var array [4][2]int
fmt.Println(array)
}
1
2
3
4
5
2
3
4
5
用法和一维数组一样。
4. 切片
问题:
func main() {
var array [4]int
change(array)
//array的值修改了吗?
fmt.Println(array)
}
func change(array [4]int) {
array[0] = 10
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
切片(slice)
是对数组的一个连续片段的引用,所以切片是一个引用类型
。
切片的零值为nil
格式:slice [开始位置 : 结束位置]
(左闭右开的区间,不含右索引项)
- slice:表示目标切片对象;
- 开始位置:对应目标切片对象的索引;
- 结束位置:对应目标切片的结束索引。
Go语言中切片的内部结构包含
地址
、大小
和容量
,切片一般用于快速地操作一块数据集合。
func main() {
var a = [3]int{1, 2, 3}
//a[1:2] 生成了一个新的切片
fmt.Println(a, a[1:2])
}
1
2
3
4
5
2
3
4
5
在看下面代码:
func main() {
var array []int = []int{0, 0, 0}
change(array)
//array的值修改了吗? 为什么呢?
fmt.Println(array)
}
func change(array []int) {
array[0] = 10
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
从数组或切片生成新的切片拥有如下特性:
- 取出的元素数量为:结束位置 - 开始位置;
- 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
- 当缺省开始位置时,表示从连续区域开头到结束位置
(a[:2])
; - 当缺省结束位置时,表示从开始位置到整个连续区域末尾
(a[0:])
; - 两者同时缺省时,与切片本身等效
(a[:])
; - 两者同时为 0 时,等效于空切片,一般用于切片复位
(a[0:0])
。
注意
:超界会报运行时错误,比如数组长度为3,则结束位置最大只能为3
切片也可以直接声明和赋值:
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
//声明string切片并初始化
var strList []string = []string{"hello","mszlu"}
1
2
3
4
5
6
2
3
4
5
6
切片也可以用make
关键字来创建:
func main() {
//语法:make([]Type, len, cap)
//len是初始化长度,cap是预分配内存空间,
//降低多次分配空间造成的性能问题。
var s []int = make([]int, 5, 10)
fmt.Println(s)
}
1
2
3
4
5
6
7
2
3
4
5
6
7
make仅用于用于创建slice、map和channel,分配内存空间并且初始化
了解切片后,在看一个思考题:
func main() {
var numbers4 = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
myslice := numbers4[4:6]
//这打印出来长度为2
fmt.Printf("myslice为 %d, 其长度为: %d\n", myslice, len(myslice))
myslice = myslice[:cap(myslice)]
//为什么 myslice 的长度为2,却能访问到第四个元素
fmt.Printf("myslice的第四个元素为: %d", myslice[3])
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
5. 切片复制
Go语言的内置函数 copy()
可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。
格式: copy( destSlice, srcSlice []T) int
srcSlice
为数据来源切片destSlice
为复制的目标(也就是将srcSlice
复制到destSlice
)目标切片必须分配过空间且足够承载复制的元素个数
,并且来源和目标的类型必须一致
,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个位置
1
2
3
4
2
3
4
copy属于深拷贝(浅拷贝是只copy地址,深拷贝是值copy)
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1)
//修改slice2 并不会对slice1造成影响
slice2[0] = 10
fmt.Println(slice1, slice2)
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
6. 切片扩容
可以使用append
对切片新增元素:
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 = append(slice1, 10)
//结果 1,2,3,4,5和1,2,3,4,5,10
//这里注意,append后会生成一个新切片(新的地址)
//如果容量够 不会新生成一个切片
fmt.Println(slice1,slice2)
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
前面我们知道,切片有长度
和容量
,容量代表实际其占用的内存空间,当我们在给切片添加元素时,内存占用会变多,这时候就会发生频繁的内存空间分配
,这是比较耗费性能的,所以go做了一些优化。
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice1 = append(slice1, 10)
//打印 长度:6,容量:10
fmt.Printf("长度:%d,容量:%d", len(slice1), cap(slice1))
}
1
2
3
4
5
6
2
3
4
5
6
大家发现,容量为什么是10
,而不是6
呢?
为了防止频繁发生内存分配,在append增加元素时,如果容量不足,在扩容时,会做一定的策略来优化:
- 容量小于256时,两倍扩容
- 容量大于等于256时,按照
newcap += (newcap + 3*256) >> 2
这个公式扩容,越大扩容越小,直到1.25倍- 实际的容量,在上述的基础上,还会进行内存对齐
这里引申出一个概念叫内存对齐
:
- 因为CPU访问的规则,未对齐的内存,会造成CPU多次访问,耗费性能
7. 切片转数组
func main() {
//go1.17版本的新特性
slice1 := make([]int, 2, 8)
arr := *(*[2]int)(slice1)
fmt.Println(arr)
}
1
2
3
4
5
6
2
3
4
5
6
func main() {
//go1.20新特性
slice1 := make([]int, 2, 8)
arr := [2]int(slice1)
fmt.Println(arr)
}
1
2
3
4
5
6
2
3
4
5
6