![image.png](https://cdn.nlark.com/yuque/0/2022/png/25362645/1643963634997-bd2c5107-e40b-42d7-83d1-963d9f735f6a.png#clientId=ue84a5b62-8c59-4&from=paste&height=243&id=SsePB&margin=%5Bobject%20Object%5D&name=image.png&originHeight=322&originWidth=865&originalType=binary&ratio=1&size=92381&status=done&style=none&taskId=u8c8e2d82-ea6a-4e7f-9d2f-f30afa23749&width=652)
参考资料:
极客时间《go语言核心36讲》、《go语言中文文档》、《go语言圣经》
数组切片对比
在golang语言中,数组类型的值(以下简称数组)的长度是固定的,而切片类型的值(以下简称切片)是可变长的。切片的底层实现是数组,可以将切片数据类型看作是将底层数组封装之后实现的动态数组。
go数据类型简介
基础类型 | 数字、字符串和布尔型 | 统称为:值类型 |
---|---|---|
复合类型 | 数组、结构体 | |
引用类型 | 指针.、切片、字典、函数、通道、 | |
接口类型 |
和java及python不同,Go 语言里不存在令人困惑的“传值或传引用”问题。如果传递的值是引用类型的,那么就是“传引用”。如果传递的值是值类型的,那么就是“传值”。
数组
数组赋值
顺序初始化值序
指定一个索引和对应值列表的方式初始化
数组大小固定
var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值
因为数组的这些特性,在进行参数传递时,或者需要动态数组时,应使用slice
slice切片
slice 是对数组的封装数据结构(包括元素:指针、长度、容量)
slice的第一个元素并不一定就是底层数组的第一个元素
多个slice之间可以共享底层的数组
slice的切片操作s[i:j],用于创建一个新的slice
slice并不是一个纯粹的引用类型,它实际上是一个类似下面结构体的聚合类型
type IntSlice struct {
ptr *int //底层数据
len, cap int //长度、容量
}
示例:
Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2) // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"]
![image.png](https://cdn.nlark.com/yuque/0/2022/png/25362645/1643956123710-5dc3d9da-d071-4b20-a21f-4e23bc7f5ec7.png#clientId=u2b2bb302-f915-4&from=paste&height=480&id=ucc380440&margin=%5Bobject%20Object%5D&name=image.png&originHeight=640&originWidth=750&originalType=binary&ratio=1&size=85795&status=done&style=none&taskId=u4a268e02-0735-4497-a5fa-433e11f4b36&width=563)<br />注意两个基于同一底层数组的两个切片Q2及Q1的cap值
用make函数创建slice
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]
//cap,可以省略,省略时cap==len
示例代码,创建切片,并返回切点的容量(cap)和长度(lenght)
package main
import "fmt"
func main() {
// 示例1。
s1 := make([]int, 5)
fmt.Printf("The length of s1: %d\n", len(s1))
fmt.Printf("The capacity of s1: %d\n", cap(s1))
fmt.Printf("The value of s1: %d\n", s1)
s2 := make([]int, 5, 8)
fmt.Printf("The length of s2: %d\n", len(s2))
fmt.Printf("The capacity of s2: %d\n", cap(s2))
fmt.Printf("The value of s2: %d\n", s2)
}
动态扩容的过程
实现一个slice动态扩容函数、实际中的扩容策略是更复杂的
/append底层实现
//是一个动态扩容的过程
func appendInt(x []int, y int) []int {
var z []int
zlen := len(x) + 1
if zlen <= cap(x) {
// There is room to grow. Extend the slice.
z = x[:zlen]
} else {
// There is insufficient space. Allocate a new array.
//cap扩容为len的两倍
zcap := zlen
if zcap < 2*len(x) {
zcap = 2 * len(x)
}
z = make([]int, zlen, zcap)
copy(z, x) // a built-in function; see text
}
z[len(x)] = y
return z
func main() {
var x, y []int
for i := 0; i < 10; i++ {
y = appendInt(x, i)
fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y)
x = y
}
}
动态扩容过程:
0 cap=1 [0]
1 cap=2 [0 1]
2 cap=4 [0 1 2]
3 cap=4 [0 1 2 3]
4 cap=8 [0 1 2 3 4]
5 cap=8 [0 1 2 3 4 5]
6 cap=8 [0 1 2 3 4 5 6]
7 cap=8 [0 1 2 3 4 5 6 7]
8 cap=16 [0 1 2 3 4 5 6 7 8]
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
![image.png](https://cdn.nlark.com/yuque/0/2022/png/25362645/1643968159358-073ea407-0215-4b7b-9fa2-0cb79f6c6229.png#clientId=ue84a5b62-8c59-4&from=paste&height=310&id=u6bdbe227&margin=%5Bobject%20Object%5D&name=image.png&originHeight=310&originWidth=838&originalType=binary&ratio=1&size=51623&status=done&style=none&taskId=u0b03b545-266d-4719-b9ce-0e88ac6e127&width=838)
`通过我们的扩容伪代码,会发现,其实这里并不能算是真正的在底层数据上进行了库容,而是创建了一个基于新底层数组的新切片,原来的底层数组和切片是没有改变的,所以如果使用append()函数在切片中插入数据时还需要对原切片重新赋值。
测试代码: