
go 语言中,结构体按值传递,但其内部的切片字段修改会影响原结构体——这是因为切片本身是包含指针、长度和容量的三元描述符,其底层数据(底层数组)在复制时被共享,而非深拷贝。
go 语言中,结构体按值传递,但其内部的切片字段修改会影响原结构体——这是因为切片本身是包含指针、长度和容量的三元描述符,其底层数据(底层数组)在复制时被共享,而非深拷贝。
在 Go 中,所有参数都是按值传递的,结构体也不例外。当你将结构体 a 传入函数 addOne(s SomeType) 时,Go 会完整复制 a 的所有字段——包括 decimal float32 和 myslice []int。但关键在于:切片([]int)本身是一个轻量级的值类型,它不直接存储数据,而是由三个字段组成:指向底层数组的指针(ptr)、当前长度(len)和容量(cap)。
因此,当 s.myslice[0]++ 执行时,实际操作的是 s.myslice.ptr 所指向的同一块内存地址上的元素——这个地址在 a.myslice 和 s.myslice 的副本中完全相同。这就是为什么你看到 a.myslice[0] 从 1 变成了 2,而 a.decimal 却未改变(float32 是纯值类型,复制后修改仅作用于副本)。
✅ 正确理解:
- ❌ 切片不是“按引用传递”;
- ✅ 切片是按值传递的引用型描述符(value-passed reference descriptor);
- ✅ 复制切片 = 复制
ptr+len+cap,但ptr指向的底层数组不变。
下面是一个直观对比示例:
func demonstrateSliceCopy() {
original := []int{1, 2, 3}
copy := original // 按值复制切片头(ptr, len, cap)
copy[0] = 999
fmt.Println(original) // [999 2 3] ← 底层数组被修改
fmt.Println(copy) // [999 2 3]
copy = append(copy, 4) // 可能触发底层数组扩容
fmt.Println(original) // [999 2 3] ← 不再受影响(ptr 已不同)
fmt.Println(copy) // [999 2 3 4]
}⚠️ 注意事项:
- 若函数内对切片执行
append且超出容量,Go 会分配新底层数组,此时修改不再影响原切片; - 若需真正“按值”隔离整个结构体(含切片内容),必须手动深拷贝:对切片字段重新
make并copy; - 对于只读场景,可接受此行为以提升性能;若需写保护,建议设计为不可变接口或显式克隆。
总结:这不是 Go 的 bug 或特例,而是切片设计的核心特性——兼顾表达力与效率。理解 slice = header + backing array 这一模型,是写出健壮 Go 代码的关键基础。

















