Files
cs-note/hzh/GolangStar/Go面试题库/Slice面试题.md
T

144 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [go, golang, interview, slice-questions]
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 是对数组的封装,描述一个数组的片段。其底层是一个包含三个字段的结构体:
```go
// 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,什么时候不会?
## 参考答案
**关键判断标准**:新切片是否触发了扩容。
```go
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]
```
**解析思路:**
1. 第一次 append(100):s2 cap=5,追加后 len=5,未超 cap,直接写入底层数组 → s1 能看到变化
2. 第二次 append(200):s2 cap 已满,触发扩容创建新底层数组 → s1 看不到变化
3. 修改 s1[2]:通过原始底层数组修改,但 s2 已有独立数组 → s2 不受影响
> [!tip] 💡 面试技巧
> 回答这道题时,先画出三张图展示每次操作后的底层状态变化。面试官最看重的不是背答案,而是你能否清晰地描述数据流向。
---
### Q4:slice 作为函数参数会改变原 slice 吗? 🟡中等
> [!question] ❓ 思考一下
> 在函数里 `append(s, 100)` 后返回新的 slice,外层的 s 会变吗?为什么?
## 参考答案
**分两种情况:**
| 操作 | 外层 slice 结构变吗? | 底层数据变吗? |
|------|---------------------|--------------|
| 修改元素值 `s[i] = x` | 否(传的是副本) | **是**(共享底层数组) |
| append 后赋值给 s | 否 | 否(s 只是副本) |
| 传递 `*[]int` 指针 | **是** | **是** |
```go
// 方法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 指向别处)不会影响外部。
## 关联笔记
- [[hzh/GolangStar/Go语言基础/Go语言数组与切片]]
- [[hzh/GolangStar/Go语言原理/slice原理]]
- [[hzh/GolangStar/Go面试题库/内存管理面试题]]