
在 go 语言中,make([]int, 0)、[]int{} 和 var s []int 均可创建逻辑上等价的空切片,三者均不触发底层内存分配;区别在于零值语义、json 序列化行为及代码意图表达。
在 Go 中,初始化一个长度为 0、容量可动态增长的切片(slice)有三种常见写法,它们在绝大多数运行时行为上完全一致,但语义和使用场景略有差异:
✅ 三种写法对比
| 写法 | 示例 | 类型 | 长度/容量 | 是否分配底层数组 | JSON 序列化结果 | 典型用途 |
|---|---|---|---|---|---|---|
| nil 切片 | var s []int | []int(未初始化) | len=0, cap=0 | ❌ 不分配 | "null" | 表达“尚未初始化”或需延迟分配的意图 |
| 空字面量 | s := []int{} | []int(已初始化) | len=0, cap=0 | ❌ 不分配 | "[]" | 明确表示“存在且为空”的集合 |
| make 初始化 | s := make([]int, 0) | []int(已初始化) | len=0, cap=0 | ❌ 不分配(Go 1.16+ 优化后) | "[]" | 强调后续将追加元素,且可能预设容量(如 make([]int, 0, 16)) |
? 关键事实:自 Go 1.16 起,make([]T, 0) 已被优化为零分配操作(与 []T{} 等价),不再调用底层 runtime.makeslice 分配内存。三者在性能上无差异。
? 推荐实践
优先使用 []int{}:简洁、直观、符合 Go 的惯用风格,明确传达“这是一个空但有效的切片”,适用于大多数初始化场景。
使用 var s []int 当需强调“未初始化”语义:例如函数返回值、结构体字段默认值,或配合 if s == nil 显式判空逻辑(尽管 len(s) == 0 对 nil 和空切片都成立)。
-
使用 make([]int, 0, N) 当有性能敏感的预扩容需求:例如已知将追加约 N 个元素,可避免多次底层数组复制:
s := make([]string, 0, 100) // 预分配容量 100,后续 100 次 append 不 realloc for i := 0; i < 100; i++ { s = append(s, fmt.Sprintf("item-%d", i)) }
⚠️ 注意事项
-
nil 切片与空切片在 json.Marshal() 中行为不同:
var a []int // nil b := []int{} // empty literal c := make([]int, 0) // same as b fmt.Println(json.Marshal(a)) // [null] fmt.Println(json.Marshal(b)) // [[]] fmt.Println(json.Marshal(c)) // [[]]若 API 协议要求空数组必须序列化为 [](而非 null),请避免使用 var s []T。
-
所有三种方式均可安全调用 append():
var s []int s = append(s, 42) // ✅ 合法:自动分配底层数组
综上,没有绝对“正确”的写法,只有更贴合上下文意图的选择。日常开发中推荐 []int{} —— 简洁、明确、无歧义;需要语义区分或预扩容时再选用其他形式。










