go切片传参是值传递,复制结构体(ptr、len、cap),故修改元素通过共享底层数组生效,而append或重赋值仅改变副本字段,原变量不变;需返回新切片或传*[]t才能影响调用方。

Go 里切片传给函数,不是“传引用”,也不是“传指针”,而是传一个包含指针、长度、容量的三元结构体副本 —— 修改元素可能影响原切片,但重赋值或 append 不会。
为什么改 s[0] = 100 生效,但 s = append(s, 1) 不生效?
因为切片变量本身是 struct{array unsafe.Pointer; len int; cap int}。传参时复制这个结构体,所以:
-
s[0] = 100→ 通过副本里的array指针写到底层数组,原切片和副本共享同一块内存,效果可见 -
s = append(s, 1)→ 如果触发扩容,append返回新底层数组的切片,只改了副本的array和len字段,原变量没变 -
s = s[:2]或s = make([]int, 5)同理:只改副本,不影响调用方
func f(s []int) 和 func f(s *[]int) 的关键区别
前者传结构体副本,后者传指向该结构体的指针 —— 这决定了你能否让调用方的切片变量本身被替换:
- 用值接收者(
[]int):能安全修改底层数组元素,但不能改变调用方切片的长度/容量/底层数组地址 - 用指针接收者(
*[]int):可对*s赋值,比如*s = append(*s, x),此时调用方变量会更新 - 常见误写:
s = append(s, x)在指针接收者方法里毫无意义 —— 它只改了局部变量s,不是*s
什么时候必须用 *[]T?
只有当你需要在函数内「替换整个切片头」并让调用方感知变化时才需要,典型场景:
- 就地扩容后替换原切片(如实现自定义
Grow方法) - 从函数内清空切片并复用底层数组:
*s = (*s)[:0] - 动态构建切片并返回给调用方,且不希望调用方再做赋值(比如封装一个
ParseArgs方法直接更新输入参数) - 注意:
append本身不保证复用底层数组;若需强控制,应配合cap判断或预分配
零长度切片、nil 切片传参要注意什么?
它们都合法,但行为不同,容易混淆:
-
var s []int是nil切片:len(s) == 0、cap(s) == 0、s == nil,append(s, 1)会新建底层数组 -
s := make([]int, 0)是非nil空切片:len(s) == 0、cap(s) > 0,append(s, 1)可能复用底层数组 - 传入函数后,两者都能正常读写(只要不越界),但
json.Marshal对nil输出null,对空切片输出[] - 判空别只用
s == nil,稳妥做法是len(s) == 0
最常被忽略的是:你以为传了个切片进去就能靠 append 改它,结果发现调用方啥也没变 —— 那是因为你没解引用指针,或者根本没传指针。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











