4.9 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-06-07 14:30 |
Slice 面试题 🍕
概述
本文件涵盖 Go Slice 的 4 道高频面试题,涉及底层结构、扩容机制、截取共享和函数传参等核心考点。Slice 是 Go 中最常用的数据结构之一,也是面试中的"必考题"。
关联笔记
- hzh/GolangStar/Go语言基础/Go语言数组与切片 — Slice 基础用法
- hzh/GolangStar/Go语言原理/slice原理 — Slice 底层扩容策略详解
- hzh/GolangStar/Go面试题库/内存管理面试题 — Slice 引用大数组导致的内存泄漏
正文
Q1:slice 的底层结构是怎样的? 🟢简单
[!question] ❓ 思考一下 如果让你设计一个能动态增长的数组,你会在结构体中放哪些字段?
参考答案
slice 是对数组的封装,描述一个数组的片段。其底层是一个包含三个字段的结构体:
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 指向底层数组的指针
len int // 长度(当前元素个数)
cap int // 容量(底层数组从起始位置可容纳的元素数)
}
[!note] 📝 核心考点 len ≠ cap 是理解 slice 的关键。len 是你"能看到"的元素数量,cap 是底层数组的实际容量。当 len == cap 时,append 会触发扩容。
Q2:slice 是怎么扩容的? 🟡中等
[!question] ❓ 思考一下 如果一个 slice 的 cap 是 10,你 append 第 11 个元素时会发生什么?扩容后的 cap 是多少?
参考答案
Go 1.17 及以前
| 条件 | 扩容规则 |
|---|---|
| 期望容量 > 当前容量的 2 倍 | 使用期望容量 |
| len < 1024 | 容量翻倍(×2) |
| len >= 1024 | 容量增加 25%(×1.25),直到 >= 期望容量 |
Go 1.18 及以后(新规则)
| 条件 | 扩容公式 |
|---|---|
| oldcap < 256 | newcap = oldcap × 2 |
| oldcap >= 256 | newcap = oldcap + (oldcap + 3×256) / 4 |
[!warning] ⚠️ 高频陷阱 Go 1.18 的扩容规则更保守,减少了过度分配内存的情况。面试中如果不确定版本,可以先说旧规则再补充新规则的变化。
[!info] 🔗 延伸阅读
- hzh/GolangStar/Go语言原理/slice原理 — 详细的扩容策略源码分析
Q3:切片截取后修改会影响原切片吗? 🟡中等
[!question] ❓ 思考一下
s2 := s1[2:6:7],然后对 s2 做 append,什么时候会影响 s1,什么时候不会?
参考答案
关键判断标准:新切片是否触发了扩容。
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := slice[2:5] // len=3, cap=8, 底层指向 slice[2]
s2 := s1[2:6:7] // len=4, cap=5, 底层指向 slice[4]
s2 = append(s2, 100) // 容量够,直接追加 → 影响底层数组
s2 = append(s2, 200) // 容量不够,触发扩容 → 创建新数组,不影响 s1
s1[2] = 20 // 只影响 s1 可见范围
fmt.Println(s1) // [2 3 20]
fmt.Println(s2) // [4 5 6 7 100 200]
fmt.Println(slice) // [0 1 2 3 20 5 6 7 100 9]
解析思路:
- 第一次 append(100):s2 cap=5,追加后 len=5,未超 cap,直接写入底层数组 → s1 能看到变化
- 第二次 append(200):s2 cap 已满,触发扩容创建新底层数组 → s1 看不到变化
- 修改 s1[2]:通过原始底层数组修改,但 s2 已有独立数组 → s2 不受影响
[!tip] 💡 面试技巧 回答这道题时,先画出三张图展示每次操作后的底层状态变化。面试官最看重的不是背答案,而是你能否清晰地描述数据流向。
Q4:slice 作为函数参数会改变原 slice 吗? 🟡中等
[!question] ❓ 思考一下 在函数里
append(s, 100)后返回新的 slice,外层的 s 会变吗?为什么?
参考答案
分两种情况:
| 操作 | 外层 slice 结构变吗? | 底层数据变吗? |
|---|---|---|
修改元素值 s[i] = x |
否(传的是副本) | 是(共享底层数组) |
| append 后赋值给 s | 否 | 否(s 只是副本) |
传递 *[]int 指针 |
是 | 是 |
// 方法1:返回新 slice(推荐)
func myAppend(s []int) []int {
return append(s, 100)
}
s = myAppend(s) // 需要重新赋值
// 方法2:传递指针
func myAppendPtr(s *[]int) {
*s = append(*s, 100)
}
myAppendPtr(&s) // 直接修改
[!note] 📝 核心考点 slice 本身是结构体(三个字段),传入函数时被复制了一份。函数内修改底层数组的数据会影响外部,但修改 slice 结构本身(如让它的 array 指向别处)不会影响外部。