
go 中切片、映射、通道和指针等类型默认按引用传递,直接赋值会共享底层数据;要实现真正的值拷贝,需根据类型选择深拷贝、切片复制或结构体解引用等方法。
go 中切片、映射、通道和指针等类型默认按引用传递,直接赋值会共享底层数据;要实现真正的值拷贝,需根据类型选择深拷贝、切片复制或结构体解引用等方法。
在 Go 语言中,“复制变量”这一操作容易产生误解——因为 Go 没有传统意义上的“深拷贝语法糖”,其赋值行为严格遵循值语义:基础类型(如 int、string、struct)赋值即复制整个值;但复合类型如 []T(切片)、map[K]V、*T(指针)和 chan T 等,其本身是轻量级描述符,赋值仅复制该描述符(如底层数组指针、长度、容量),而非底层数据。因此,如下代码:
a := []string{"A", "B"}
b := a // ⚠️ 不是深拷贝!b 与 a 共享同一底层数组
b[0] = "C"
fmt.Println(a) // 输出:[C B] —— a 被意外修改!
这正是问题中观察到的现象:看似“复制”,实则共享状态。
✅ 正确的值拷贝方式
1. 切片:使用 copy() 或 append([]T(nil), src...)
a := []string{"A", "B"}
b := make([]string, len(a))
copy(b, a) // ✅ 安全拷贝:b 拥有独立底层数组
// 或更简洁(推荐用于小切片):
b := append([]string(nil), a...)
b[0] = "C"
fmt.Println(a) // [A B] —— 未被修改
fmt.Println(b) // [C B]
2. 指针指向的结构体:解引用后赋值
若 PublicVar 是 *MyStruct 类型:
type MyStruct struct {
Name string
Age int
}
var PublicVar = &MyStruct{Name: "Alice", Age: 30}
temp := *PublicVar // ✅ 解引用获得副本
temp.Name = "Bob" // 修改 temp 不影响 PublicVar
⚠️ 注意:若结构体字段含切片/映射/指针,*s 仍为浅拷贝(字段仍共享)。此时需手动深拷贝或使用 encoding/gob、第三方库(如 github.com/jinzhu/copier)。
3. Map:需手动遍历复制
src := map[string]int{"x": 1, "y": 2}
dst := make(map[string]int, len(src))
for k, v := range src {
dst[k] = v // ✅ 基础类型值拷贝
}
4. 通用深拷贝(谨慎使用)
对嵌套复杂结构,可借助序列化:
import "encoding/gob"
func deepCopy[T any](src T) (T, error) {
var dst T
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
if err := enc.Encode(src); err != nil {
return dst, err
}
if err := dec.Decode(&dst); err != nil {
return dst, err
}
return dst, nil
}
✅ 适用场景:结构体字段均为可序列化类型;❌ 缺点:性能开销大,不支持函数、channel、unsafe.Pointer 等。
? 关键总结
- Go 中 “复制” = 复制变量本身的值,而非其指向的数据;
- 切片/映射/指针赋值是零拷贝引用传递,务必显式创建新底层数组或副本;
- 优先使用 copy()、append(...)、make()+range 等标准手法,避免过度依赖反射或序列化;
- 始终通过 fmt.Printf("%p", &slice[0]) 验证底层数组地址是否相同,是调试拷贝行为的有效手段。











