【go语言基础】slice

1. slice是什么

slice是一个大小可以动态扩展的类数组的结构。它的底层通过指针引用着一个大小确定的数据,这个数组作数据存储。

当大小够用的时候引用的数组大小保持不变。当引用的数组大小不够用时,slice会申请新大小合适的数组空间,然后将数据复制到新的数组,最后slice的引用新的数组的地址。

slice的原型结构

go 复制代码
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
  • 长度(len) :切片当前包含的元素个数。
  • 容量(cap) :从切片的起始位置到底层数组的末尾的元素个数。

在开发中几乎都是使用slice,很少使用数组。

2. slice在解决什么问题

  • 数组在编译阶段就需要给定大小,而有些数据只有在运行时才可以知道大小,会导致:
    • 所以在编程的时候如果数组大小不够,需要重新申请内存,然后复制,消耗性能。
    • 编译时申请太大的数组可能会浪费内存。
  • 不知道数组实际存放了多少个数据,如果想知道,需要对数组多二封装,增加工作量。
  • 数组内存共享困难,只能靠复制,如果不复制就丢失数组的特性。因为go的数组内存共享只有slice,所以这里拿C语言举例:
arduino 复制代码
#include <stdio.h>

void printArray(int* arr, int len) {
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
}

int main() {
    int data[3] = {1, 2, 3};
    printArray(++data, 2); // 只能传指针和长度
    return 0;
}

这个代码实现了内存共享,但丢失数组的大小,需要另外一个参数做说明长度的补充,所以比较繁琐。

go 复制代码
package main

import "fmt"

func main() {
    arr := []int{1, 2, 3, 4, 5}
    s1 := arr[1:4] // s1 = [2 3 4]
    s2 := arr[2:]  // s2 = [3 4 5]

    fmt.Println("s1:", s1)
    fmt.Println("s2:", s2)

    s1[0] = 99 // 修改 s1 同时影响 arr 和 s2
    fmt.Println("arr:", arr)
}

内存得到了共享,没有丢失长度,而且还有容量

2.1.1. 问题总结

问题 C语言数组(或 Go数组) Go切片
不确定大小 编译时固定 运行时动态增长
扩容麻烦 需手动申请 + 复制 append自动处理
长度管理 需额外变量维护 len(slice)内建支持
共享困难 只能手动传指针、复制数据 多个切片可共享底层数组

3. slice怎么解决

  • 容量可以动态扩容
  • 存放数据的个数可知
  • 可以与数组或slice共享内存,不切不丢失长度和容量

4. slice的特性

特性 描述说明
动态扩容 底层数组不足时,自动分配新数组并复制旧数据
共享内存 多个切片可共享底层数组(类似 C 中的指针),节省内存且支持原位修改
内建长度与容量 内建 lencap,不需要手动传入长度
轻量结构 仅 3 个字段,传参/赋值成本低
值类型 + 引用语义 本质是值类型,但包含指向共享底层数组的指针,因此具有引用语义
简化编程 比 C 或 Go 原生数组更灵活,适合日常开发场景

4.1. 轻量

在go里将一个数组传入函数参数,则会复制一份数组。在c语言下默认是引用,但丢失了数组的容量。slice是一个简单的数据结构(值类型),作为参数也会复制一份slice,但slice引用的数组不会被分支,所以成本低。在x86_64中,slice真用的内存大小为24字节。

虽然官方文档说slice是引用类型,但是它更符合值类型的特征

4.2. 容量可以动态调整

当大小够用的时候引用的数组大小保持不变。当引用的数组大小不够用时,slice会申请新大小合适的数组空间,然后将数据复制到新的数组,最后slice的引用新的数组的地址。

4.3. 保存数据的个数和容量可以随时获取

4.3.1. 1. append 配合使用,扩容自动处理

go 复制代码
s := []int{1, 2}
s = append(s, 3, 4)
  • 若容量足够,append 就在原数组上操作;
  • 若容量不足,会创建新数组,复制数据,然后返回新的切片;
  • 返回的是新的切片(重要!)

4.4. 2. 切片间共享的数据是可变的,但结构是独立的

go 复制代码
a := []int{1, 2, 3}
b := a[:2]   // b 与 a 共享数据
b[0] = 100   // 修改 b 会影响 a

切片结构体(Data, Len, Cap)是复制的,但底层数组是共享的。


4.5. 3. slice 的 cap 和 len 是分开的

go 复制代码
a := make([]int, 2, 5)
fmt.Println(len(a), cap(a)) // 2 5
  • len 控制能访问的元素个数
  • cap 控制 append 时可以继续使用多少容量

这在构建 buffer 时非常有用。


4.6. 4. 支持 nil 切片和空切片

go 复制代码
var a []int        // nil 切片,a == nil
b := make([]int, 0) // 空切片,b != nil
  • len == 0
  • nil 切片常用于表示"没有值"
  • 空切片常用于表示"值是空的集合"

4.7. 5. 切片是安全的内存视图

即使多个切片共享内存,但你无法通过切片访问超出 len 范围的数据(否则 panic),这提高了安全性。


4.8. 6. 切片可以截取切片

你可以对一个切片继续切片(称为"re-slice"):

go 复制代码
s := []int{1, 2, 3, 4}
s2 := s[1:3]      // [2 3]
s3 := s2[1:]      // [3]

4.9. 7. slice 与 copy 函数配合做深复制

go 复制代码
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)

适用于避免共享底层数据的场景。


4.10. 8. 可以与 range 安全配合迭代

css 复制代码
for i, v := range slice {
    // i 是索引,v 是值拷贝
}

这是切片的标准使用模式之一。


4.11. 🔚 总结:slice 的附加特性(补充点)

特性名称 描述
append 自动扩容 不够就重新分配
re-slice 支持 切片还能继续切
copy 支持 可用于深拷贝
range 安全迭代 配合 range 可读性高
nil/空区分 nil 表示无,空表示空集合
cap 控制预分配 可优化性能,减少扩容次数
安全边界限制 超过 len 会 panic,防止越界
可以表示动态数组 在 Go 中承担动态数组的角色
相关推荐
研究司马懿1 天前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大2 天前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰2 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘2 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤2 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto4 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto6 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室7 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题7 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo