go结构体赋值默认为浅拷贝,即=赋值或函数传参时直接复制字段值;若字段不含指针、map、slice等引用类型,则副本完全独立;含引用类型时共享底层数据,需深拷贝方案如gob序列化或maps.clone。

Go语言结构体赋值默认就是浅拷贝
直接用 = 赋值结构体变量,或作为函数参数传入时,Go 默认执行的是浅拷贝——这不是需要“实现”的功能,而是语言底层行为。只要结构体字段不包含指针、map、slice、chan 或 func 类型,拷贝后两个变量完全独立,改一个不影响另一个。
常见误解是以为“没写拷贝函数就不是拷贝”,其实不用写任何函数,user2 := user1 就完成了浅拷贝。
含指针或引用类型字段时,浅拷贝会共享底层数据
一旦结构体里有 *int、map[string]int、[]byte 这类字段,赋值后两个结构体的对应字段仍指向同一块内存。改 user1.Data["key"],user2.Data 也会变。
-
slice拷贝后共用底层数组,修改元素会影响对方(除非重新make或copy) -
map拷贝后仍是同一个哈希表,增删改都同步 -
*T拷贝后两个指针指向同一地址,解引用修改会互相影响
例如:
type Config struct {<br> Name string<br> Tags map[string]bool<br>}<br>c1 := Config{Name: "a", Tags: map[string]bool{"x": true}}<br>c2 := c1 // 浅拷贝<br>c2.Tags["y"] = true // c1.Tags 也会看到这个 key
需要深拷贝时,别手写递归函数,优先用标准方案
手写深拷贝容易漏字段、陷入循环引用、忽略未导出字段。Go 官方不提供通用深拷贝函数,但有更稳妥的选择:
- 用
encoding/gob序列化再反序列化(适合可导出字段 + 无 channel/func) - 用
github.com/joshbetz/deepcopy这类成熟第三方库(支持泛型、字段过滤) - 对简单结构体,手动逐字段复制(比如只复制
map:c2.Tags = maps.Clone(c1.Tags),Go 1.21+ 提供maps.Clone)
注意:json.Marshal/Unmarshal 不可靠——会丢掉零值字段、忽略非导出字段、改变时间精度、无法处理 NaN 等特殊浮点值。
函数返回结构体时也是浅拷贝,但要注意逃逸分析
写类似 func NewUser() User { return User{...} } 的函数,返回值会被拷贝到调用方栈上。如果结构体很大(比如含几 MB 的 []byte),频繁调用可能触发堆分配和 GC 压力。
- 大结构体建议返回指针:
func NewUser() *User,避免不必要的内存复制 - 小结构体(字段总大小 ≤ 几个机器字长)按值返回更高效,CPU 缓存友好
- 用
go build -gcflags="-m"查看编译器是否做了逃逸分析,确认分配位置
别因为“浅拷贝”就下意识加 *,该值传时就值传——Go 的设计哲学是“小结构体廉价,大结构体才需指针”。
真正容易被忽略的是:浅拷贝本身没问题,问题出在开发者误以为它能隔离引用类型。看清字段类型,比纠结“怎么实现浅拷贝”重要得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











