go中浅拷贝是值传递的自然结果,赋值仅复制句柄(如slice头、map指针),不复制底层数据;深拷贝需手动实现、json/gob序列化或第三方库,各有适用场景与限制。

浅拷贝就是赋值,不是“错”,而是设计使然
Go 没有 deepcopy 关键字,也没有运行时自动深拷贝机制。所谓“浅拷贝”,本质是 Go 值传递模型的自然结果:你传的是什么,就复制什么。对 []int、map[string]int、*T 这类类型,变量本身只是轻量句柄(header 或指针),赋值只复制句柄,不碰底层数据——这恰恰是性能和内存可控性的来源。
常见错误现象:修改副本后原数据意外变更,比如 b := a 后改 b[0] = 10,a[0] 也变了;或函数内修改传入的 map,调用方看到副作用。
- 这不是 bug,是预期行为;关键在“你是否意识到自己传的是句柄”
- struct 若全由值类型字段组成(无指针/切片/map/chan),
u2 := u1就已是完整隔离的副本 - 一旦含
[]string或*Config字段,=就只拷贝顶层,内部仍共享
copy() 适合 slice,但仅限一层值拷贝
copy() 是最常用、最可控的“手动深拷贝”手段,但它只解决 slice 元素级的值复制,不递归处理元素内部的引用。
使用场景明确:你需要一个独立底层数组的新 slice,且元素类型是可直接赋值的(如 int、string、纯值 struct)。
- 正确写法:
dst := make([]T, len(src)); copy(dst, src) - 错误用法:
copy(dst, src)前没初始化dst,或dst长度不足 → panic 或截断 - 陷阱:若
src是[]*User,copy()只复制指针值,dst[i]和src[i]仍指向同一User实例
json.Marshal/Unmarshal 是最常用的“伪深拷贝”方案
对嵌套结构体、map、slice 组合,json.Marshal + json.Unmarshal 是最快落地的深拷贝方式,但它有硬性限制,不是万能解药。
- 忽略未导出字段(首字母小写)→ 拷贝后字段为零值
- 丢弃
func、chan、unsafe.Pointer,遇到直接 panic -
time.Time变成字符串,interface{}中的非 json 可序列化类型会失败 - nil map/slice 反序列化后变成空实例(
map[string]int{}而非nil),可能掩盖业务逻辑中对 nil 的判断
示例:var dst T; json.Unmarshal(json.Marshal(src), &dst) —— 简单但得接受这些妥协。
gob 更接近“内存级深拷贝”,但兼容性更差
encoding/gob 支持未导出字段、保留类型信息,比 json 更贴近原始内存语义,但代价是生态封闭、调试困难、且同样拒绝 func/chan。
- 必须提前注册自定义类型(
gob.Register(T{})),否则反序列化失败 - 生成的二进制不可读,无法人工 inspect 或跨语言交互
- 对含循环引用的结构会 panic,需额外检测或用第三方库(如
github.com/mohae/deepcopy) - 性能通常略优于 json,但差距不大,别指望它解决高吞吐场景
真正需要深拷贝时,优先问自己:是不是本该用不可变设计?比如返回新 struct 而非复用旧实例,或用函数式更新模式(WithXXX() 方法)替代原地修改。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











