先来看看Golang中的数组
其实在循环那一节用到过数组,我快速介绍一下。
- 数组中是固定长度的连续空间(内存区域)
- 数组中所有元素的类型是一样的
var a1 [10]int
//初始化数组
var b1 = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
多维数组
//声明二维数组,只要 任意加中括号,可以声明更多维,相应占用空间指数上指
var arr [3][3]int
//赋值
arr = [3][3]int{
{1, 2, 3},
{2, 3, 4},
{3, 4, 5},
}
何谓切片?
类比c
语言,一个int
型数组int a[10]
,a
的类型是int*
,也就是整型指针,而c
语言中可以使用malloc()
动态的分配一段内存区域,c++
中可以用new()
函数。例如:
int* a = (int *)malloc(10);
int* b = new int(4);
此时,a
和b
的类型也是int*
,a
和b
此时分配内存的方式类似于go
语言中的切片。
Go
的数组和切片都是从c
语言中延续过来的设计。
有何不同?
var sliceTmp []int
可以看到和c
不同的是,go
可以声明一个空切片(默认值为nil
),然后再增加值的过程中动态的改变切片值大小。
怎么动态增加?
增加的方式只有一种,使用append
函数追加。
sliceTmp = append(sliceTmp, 4)
sliceTmp = append(sliceTmp, 5)
每个切片有长度len
和容量cap
两个概念,长度是我们最熟知的,和数组长度相同,可以直接用来遍历。
for _,v := range slice1{
fmt.Println(v)
}
用切糕来对比
每个切片,在声明或扩建时会分配一段连续的空间,称为容量cap
,是不可见的;真正在使用的只有一部分连续的空间,称为长度len
,是可见的。
每次append
时,如果发现cap
已经不足以给len
使用,就会重新分配原cap
两倍的容量,把原切片里已有内容全部迁移过去。
新分配的空间也是连续的,不过不一定直接在原切片内存地址处扩容,也有可能是新的内存地址。
切片的长度与容量,len cap append copy
slice1 := []int{1, 2, 3}
普通切片的声明方式,长度和容量是一致的。
len=3 cap=3 slice=[1 2 3]
当然,控制权在我们手上,我们可以自己控制长度和容量,
slice1 = make([]int, 3, 5) // 3 是长度 5 是容量
输出
len=3 cap=5 slice=[0 0 0]
尝试使用一般的方式扩容
slice1[len(slice1)] = 4
//报错 panic: runtime error:
//index out of range [3] with length 3
这种方式是会报错的,虽然容量是 5 ,但是数组长度是3,这里是以长度为准,而不是容量,append
内部如果超过容量相当于创建了一个新数组,每个新数组都是定长的,只不过外部是切片。
尝试扩容
slice1 = append(slice1, 4)
输出,可以发现len
扩容了!
len=4 cap=5 slice=[0 0 0 4]
让我们连续扩容,让容量超过5
slice1 = append(slice1, 5)
slice1 = append(slice1, 6) // 到这里长度超过了容量,容量自动翻倍为 5*2
输出
len=6 cap=10 slice=[0 0 0 4 5 6]
上面的过程,我 用自己的代码模拟一遍
// 上面容量自动翻倍的过程可以看作和下面一致
slice1 = make([]int, 3, 5) // 3 是长度 5 是容量
slice1 = append(slice1, 4)
slice1 = append(slice1, 5)
// 长度不变,容量自动翻倍为 5*2
slice2 := make([]int, len(slice1),
(cap(slice1))*2)
// 拷贝 slice1 的内容到 slice2
// 注意是后面的拷贝给前面
copy(slice2, slice1)
slice2 = append(slice2, 6)
所以,你理解容量,长度的概念了吗?