go语言无内置深拷贝,struct赋值默认浅拷贝:值类型字段完整复制,引用类型字段仅复制地址;深拷贝需手动实现、序列化或第三方库,但各有局限,关键在明确业务语义决定拷贝策略。

Go 语言里没有“深拷贝”这个语言特性,只有值拷贝和指针引用;所谓深拷贝,必须手动实现或借助序列化/反射/第三方库,否则所有赋值、传参、返回都是浅拷贝——但这个“浅”,只对引用类型字段生效。
struct 赋值默认是浅拷贝,但不是所有字段都“共享”
结构体赋值 copy := original 会逐字段复制:
- 值类型字段(
int、string、不含引用字段的struct):内存内容完整复制,互不影响 - 引用类型字段(
[]T、map[K]V、*T、chan T、func):只复制头信息或地址,底层数据仍共享
例如:original.Tags 和 copy.Tags 指向同一底层数组,改 copy.Tags[0] 就等于改 original.Tags[0];original.Info 和 copy.Info 指向同一 Detail 实例,改 copy.Info.Age 也会影响 original。
手动实现深拷贝时,必须逐字段处理引用类型
通用性差但性能好、可控性强,适合字段明确、嵌套不深的结构体。关键点:
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
-
[]T:先make([]T, len(src)),再copy(dst, src);若T本身含引用类型(如[]map[string]int),需递归处理每个元素 -
map[K]V:先make(map[K]V),再for k, v := range src { dst[k] = v };若V是引用类型,也要递归赋值 -
*T:判空后解引用再取地址,如if src != nil { dst = &(*src) };若T含引用字段,需进一步深拷贝其内部 - 未导出字段、
channel、func、unsafe.Pointer无法通过反射安全访问,手动实现时天然绕过这些问题
示例片段:
func (p Person) DeepCopy() Person {
var tagsCopy []string
if p.Tags != nil {
tagsCopy = make([]string, len(p.Tags))
copy(tagsCopy, p.Tags)
}
var infoCopy *Detail
if p.Info != nil {
infoCopy = &Detail{Age: p.Info.Age} // 假设 Detail 只有值字段
}
return Person{ Name: p.Name, Tags: tagsCopy, Info: infoCopy }
}
用 encoding/gob 或 json 做“伪深拷贝”要小心边界
它们本质是序列化 + 反序列化,不是真正的内存级深拷贝原语,但能自动处理嵌套结构:
-
gob支持未导出字段,但不能处理channel、func、unsafe.Pointer;类型需可注册(通常没问题) -
json更常用但限制多:忽略未导出字段、丢掉func/channel、把time.Time变字符串、不保留方法;json.Unmarshal对nilmap/slice 默认创建空实例,可能掩盖逻辑错误 - 两者都有明显性能开销(分配内存、遍历、编码/解码),比手动复制慢一个数量级以上
- 验证是否真正独立?别猜,用
reflect.DeepEqual(original, copy)判等后,改copy再测!reflect.DeepEqual(original, copy)
第三方库如 copier 或 deepcopy 不是银弹
它们用反射自动推导字段行为,省代码但引入隐式依赖和运行时不确定性:
-
github.com/jinzhu/copier默认跳过未导出字段,不支持channel/func,对嵌套map或slice的深拷贝策略不可控 -
github.com/mohae/deepcopy更激进,但遇到循环引用会 panic,且无法定制字段级行为(比如某 map 字段只想浅拷贝) - 所有反射类方案在编译期不报错,运行时才暴露字段不可访问、类型不支持等问题
- 如果结构体字段语义明确(比如某个
*bytes.Buffer确实不该深拷贝),反射库反而会做错事
真正难的从来不是“怎么拷”,而是“哪些字段该深、哪些该浅、哪些根本不能拷”——这得靠你读清楚业务语义,而不是交给库猜。










