golang slice
Golang 数组和切片的区别
数组(Array):
- Array(数组)的本质:
- 固定长度:数组的长度在定义时就已确定,且不可更改。
- 值类型:数组是值类型,意味着当你将一个数组赋值给另一个变量时,会复制整个数组。
- 内存连续:数组在内存中是连续存储的。
- 类型的一部分:数组的长度是其类型的一部分,例如 [5]int 和 [10]int 是不同的类型。 示例:
|
|
切片(Slice):
Slice 本质上是一个结构体,包含三个字段:
- 指针:指向底层数组的指针
- 长度:当前 slice 中元素的数量
- 容量:slice 可以扩展到的最大长度
Go 语言的 runtime 包中定义了 slice 的结构:
|
|
特征:
- 动态长度:slice 的长度可以在运行时改变。
- 引用类型:slice 是引用类型,传递 slice 时传递的是 slice 结构体的副本,但它们指向相同的底层数组。
- 可以动态增长:通过 append 函数,slice 可以自动增长。
- 共享底层数组:多个 slice 可以共享同一个底层数组。
示例:
|
|
- Array 和 Slice 的关系:
- Slice 通常基于 Array 创建。
- Slice 可以看作是数组的一个"视图"。
- 可以从一个数组创建多个 slice,它们共享底层数组。
一些常见的 slice 相关问题:
- 容量与长度混淆: slice 有长度(len)和容量(cap)两个属性,混淆这两个概念可能导致意外行为。
|
|
- 在函数间传递 slice: 将 slice 传递给函数时,函数接收的是 slice 的引用。修改 slice 的元素会影响原始 slice,但是追加元素可能不会。
|
|
- slice 的扩容: 当 append 操作超过 slice 的容量时,Go 会创建一个新的底层数组,这可能导致意外的行为。
|
|
- 使用 append 返回值: 始终使用 append 的返回值,否则可能丢失数据。
|
|
- 从数组创建 slice: 从数组创建的 slice 与原数组共享底层存储,修改 slice 会影响原数组。
|
|
- nil slice 和空 slice: nil slice 和空 slice 是不同的,虽然它们的长度都是 0。
|
|
- 多维 slice 的坑: 创建多维 slice 时,内部的 slice 可能共享底层数组,导致意外的数据修改。
|
|
- 在循环中追加元素: 在遍历 slice 的同时追加元素可能导致无限循环或跳过元素。
|
|
- 使用 copy 函数: copy 函数只会复制最小的 slice 长度,可能导致部分数据未被复制。
|
|
- slice 作为 map 的键: slice 不能直接作为 map 的键,因为它们不是可比较的类型。
|
|
- 在已有切片的基础上进行切片,不会创建新的底层数组。因为原来的底层数组没有发生变化,内存会一直占用,直到没有变量引用该数组。因此很可能出现这么一种情况,原切片由大量的元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量空间,得不到释放。比较推荐的做法,使用
copy
替代re-slice
。
|
|
上述两个函数的作用是一样的,取 origin 切片的最后 2 个元素。
- 第一个函数直接在原切片基础上进行切片。
- 第二个函数创建了一个新的切片,将 origin 的最后两个元素拷贝到新切片上,然后返回新切片。
Go Slice Tricks Cheat Sheet
Copy
![[Pasted image 20240724175623.png]]
Append
![[Pasted image 20240724175633.png]]
Delete
![[Pasted image 20240724175648.png]]
Delete(GC)
![[Pasted image 20240724175656.png]]
Insert
![[Pasted image 20240724175703.png]]
Filter
![[Pasted image 20240724175709.png]]
Push
![[Pasted image 20240724175722.png]] ![[Pasted image 20240724175738.png]]
Pop
![[Pasted image 20240724175808.png]] ![[Pasted image 20240724175815.png]]