数组与切片基础
数组
数组是 Go 的构建块,通常隐藏在 Slice 之下。 数组大小是类型的一部分,限制了表达能力。
var buffer [256]bytebuffer 变量持有 256 字节数据。访问越界会崩溃。
len(buffer) 返回固定值 256。
数组主要用途是作为 Slice 的存储。
切片头
Slice 描述数组的一段连续区域,Slice 不是数组,而是数组片段的描述。
var slice []byte = buffer[100:150]
// or
var slice = buffer[100:150]
// or
slice := buffer[100:150]Slice 变量内部结构(sliceHeader):
type sliceHeader struct {
Length int
ZerothElement *byte
}Slice 可以再次切片(reslice):
slice2 := slice[5:10]slice2 的头指向同一个底层数组 buffer,但偏移量和长度不同。
Nil 切片
nil Slice 的 Header 零值:
sliceHeader{
Length: 0,
Capacity: 0,
ZerothElement: nil,
}nil Slice 等同于零长度 Slice,但指针为 nil。可以对其调用 append(会自动分配)。
函数传递与修改
传递 Slice 给函数
Slice 是值类型,包含指针和长度。传递 Slice 给函数时,复制的是 Slice Header。 由于 Header 包含指向底层数组的指针,函数内修改元素会影响原数组。
func AddOneToEachElement(slice []byte) {
for i := range slice {
slice[i]++
}
}但函数内修改 Slice Header(如长度)不会影响原变量,因为 Header 是复制的。 若需修改 Header,需返回新 Slice:
func SubtractOneFromLength(slice []byte) []byte {
slice = slice[0 : len(slice)-1]
return slice
}Slice 指针与方法接收者
若需在函数内修改 Slice Header,可传递 Slice 指针:
func PtrSubtractOneFromLength(slicePtr *[]byte) {
slice := *slicePtr
*slicePtr = slice[0 : len(slice)-1]
}惯用法:修改 Slice 的方法使用指针接收者。
type path []byte
func (p *path) TruncateAtFinalSlash() {
i := bytes.LastIndex(*p, []byte("/"))
if i >= 0 {
*p = (*p)[0:i]
}
}若方法仅修改元素内容(如 ToUpper),值接收者即可。
切片管理与扩容
容量
Slice Header 实际包含三个字段:
type sliceHeader struct {
Length int
Capacity int
ZerothElement *byte
}Capacity:底层数组从 Slice 起始位置开始的实际空间大小。Length:Slice 当前长度。
增长超过 Capacity 会导致 panic。
cap(slice) 返回容量。
创建切片 (Make)
make 用于分配新数组并创建 Slice Header。
slice := make([]int, 10, 15) // len=10, cap=15若省略 capacity,则默认为 length:
gophers := make([]Gopher, 10) // len=10, cap=10复制切片 (Copy)
copy 函数在两个 Slice 间复制数据,处理重叠,复制数量取两者长度最小值。
newSlice := make([]int, len(slice), 2*cap(slice))
copy(newSlice, slice)Append 的机制
手动实现 Extend
实现一个 Extend 函数,容量不足时扩容(分配新数组并复制):
func Extend(slice []int, element int) []int {
n := len(slice)
if n == cap(slice) {
// 容量已满,扩容为原来的 2 倍 + 1
newSlice := make([]int, len(slice), 2*len(slice)+1)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0 : n+1]
slice[n] = element
return slice
}必须返回新 Slice,因为扩容后指向了新数组。
内置函数 append
Go 内置 append 函数支持任意类型 Slice,处理扩容逻辑。
必须保存返回值:slice = append(slice, elem)。
用法示例:
slice = append(slice, 4) // 添加元素
slice = append(slice, slice2...) // 添加另一 Slice
slice3 := append([]int(nil), slice...) // 复制 Slice字符串
字符串是只读的字节 Slice。 索引访问字节,切片获取子串。
slash := "/usr/ken"[0] // byte '/'
usr := "/usr/ken"[0:4] // string "/usr"字符串与 []byte 转换需要内存复制(因为字符串不可变,而 []byte 可变)。
字符串切片操作非常高效,只需创建新的 String Header,共享底层数组。