文章目录12 - Go Slice底层原理、扩容机制与常见坑位超详细什么是 SliceSlice 和数组的区别Slice 的底层结构核心重点Slice 的创建方式基于数组使用 make直接初始化Slice 的核心操作 append重点 copy 截取共享底层数组Slice 扩容机制面试高频 规则Go 1.18 示例 扩容本质Slice 常见坑非常重要❗坑 共享底层数组导致数据污染❗坑 函数传参修改问题Slice 最佳实践提前分配容量避免共享数据污染使用 copy 做深拷贝面试高频问题总结 QSlice 是值类型还是引用类型 Qappend 一定会扩容吗 Qslice 扩容后原数据还在吗 Q为什么修改 slice 会影响原数组一句话总结 结语12 - Go Slice底层原理、扩容机制与常见坑位超详细在 Go 语言中slice切片是最常用的数据结构之一。很多人会用但不一定真的理解它。这篇文章带你从本质 → 原理 → 实战 → 踩坑 → 面试全面掌握 slice。什么是 Slice 本质一句话Slice 是对数组的一个“动态视图”引用类型Slice 和数组的区别对比项数组Slice长度固定可变类型值类型引用类型传参值拷贝引用传递灵活性低高Slice 的底层结构核心重点Slice 并不是一个简单的数据结构它底层是一个结构体typeslicestruct{array unsafe.Pointer// 指向底层数组lenint// 当前长度capint// 容量} 图解理解slice ↓ --------------------- | ptr | len | cap | --------------------- ↓ 底层数组 [1 2 3 4 5]Slice 的创建方式基于数组packagemainimportfmtfuncmain(){arr:[5]int{1,2,3,4,5}s:arr[1:4]fmt.Println(s)// 输出[2 3 4]fmt.Println(len(s))// 输出3fmt.Println(cap(s))// 输出4}使用 makepackagemainimportfmtfuncmain(){s:make([]int,3,5)fmt.Println(s)// 输出[0 0 0]fmt.Println(len(s))// 输出3fmt.Println(cap(s))// 输出5s[0]1fmt.Println(s)// 输出[1 0 0]}直接初始化packagemainimportfmtfuncmain(){s:[]int{1,2,3}fmt.Println(s)// 输出[1 2 3]fmt.Println(len(s))// 输出3fmt.Println(cap(s))// 输出3}Slice 的核心操作 append重点packagemainimportfmtfuncmain(){s:[]int{1,2}sappend(s,3,4)fmt.Println(s)// 输出[1 2 3 4]sappend(s,5)fmt.Println(s)// 输出[1 2 3 4 5]fmt.Println(len(s))// 输出5fmt.Println(cap(s))// 输出8// 为什么输出 8 而不是 5// 因为 append 函数会将切片扩容扩容的策略是原来的两倍。sappend(s,6)fmt.Println(len(s))// 输出6fmt.Println(cap(s))// 输出8} copypackagemainimportfmtfuncmain(){src:[]int{1,2,3}dst:make([]int,len(src))copy(dst,src)fmt.Println(dst)// 输出[1 2 3]fmt.Println(src)// 输出[1 2 3]fmt.Println(copy(dst,src))// 输出3fmt.Println(len(dst))// 输出3fmt.Println(cap(dst))// 输出3fmt.Println(len(src))// 输出3fmt.Println(cap(src))// 输出3fmt.Println(dst[0])// 输出1fmt.Println(dst[1])// 输出2fmt.Println(dst[2])// 输出3} 截取共享底层数组packagemainimportfmtfuncmain(){s:[]int{1,2,3,4,5}sub:s[1:3]fmt.Println(len(sub))// 输出2fmt.Println(cap(sub))// 输出4sub[0]100fmt.Println(s)// 输出[1 100 3 4 5]fmt.Println(sub)// 输出[100 3]fmt.Println(len(sub))// 输出2fmt.Println(cap(sub))// 输出4}说明共享底层数组Slice 扩容机制面试高频 规则Go 1.18小于 1024翻倍扩容大于等于 1024每次增长约 1.25 倍 示例packagemainimportfmtfuncmain(){s:make([]int,0)fori:0;i10;i{sappend(s,i)fmt.Println(长度:,len(s),容量:,cap(s))}}输出长度: 1 容量: 1 长度: 2 容量: 2 长度: 3 容量: 4 长度: 4 容量: 4 长度: 5 容量: 8 长度: 6 容量: 8 长度: 7 容量: 8 长度: 8 容量: 8 长度: 9 容量: 16 长度: 10 容量: 16 扩容本质当容量不够时创建新数组拷贝旧数据指针指向新数组Slice 常见坑非常重要❗坑 共享底层数组导致数据污染packagemainimportfmtfuncmain(){s1:[]int{1,2,3}s2:s1[:2]s2[0]100fmt.Println(s1)// 被修改 输出[100 2 3]fmt.Println(s2)// 输出[100 2]}❗坑 函数传参修改问题packagemainimportfmtfuncmodify(s[]int){// 修改切片元素s[0]100}funcmain(){s:[]int{1,2,3}modify(s)fmt.Println(s)// 输出[100 2 3]}修改元素可以影响原数据但packagemainimportfmtfuncappendData(s[]int){// 这里修改的是局部变量s并不会影响外部的ssappend(s,100)}funcmain(){s:[]int{1,2,3}appendData(s)fmt.Println(s)// 输出[1 2 3]} 外部不会变Slice 最佳实践提前分配容量// 创建一个长度为0容量为1000的切片s:make([]int,0,1000) 避免频繁扩容避免共享数据污染// 将切片s1的所有元素追加到空切片s2中s2:append([]int(nil),s1...)使用 copy 做深拷贝// 创建一个切片dst:make([]int,len(src))// 将src切片的内容复制到dst中copy(dst,src)面试高频问题总结 QSlice 是值类型还是引用类型 本质是值类型但内部包含指针 → 表现为引用类型 Qappend 一定会扩容吗 不一定容量够就不会 Qslice 扩容后原数据还在吗 在被 copy 到新数组 Q为什么修改 slice 会影响原数组 因为共享底层数组一句话总结Slice 指针 长度 容量本质是“对数组的引用封装”灵活但容易踩坑 结语Slice 是 Go 中最核心的数据结构之一✔ 用得最多✔ 坑也最多✔ 面试必问