Golang中深拷贝与浅拷贝的区别及实现

Dylan
Dylan
发布时间:2026-10-15| 3人浏览过
原创

在Golang开发中,理解深拷贝与浅拷贝是避免数据意外修改的关键。本文梳理了两者的底层机制差异,展示如何使用内置copy函数处理切片,以及利用encoding/gob实现对象深拷贝的最佳实践。

基本概念与本质区别

Golang中的变量复制主要分为两种模式,其核心区别在于是否真正获取了对象实体:

  • 深拷贝(Deep Copy):复制数据本身,创建全新对象并开辟独立内存地址。修改新对象不会影响原对象,释放时也可分别进行。值类型(如Array、Int、String、Struct、Float、Bool)默认均为深拷贝。
  • 浅拷贝(Shallow Copy):仅复制数据地址(指针)。新旧对象指向同一块内存区域,修改一方会影响另一方。引用类型(如Slice、Map)默认均为浅拷贝。

浅拷贝实战:等号赋值与copy函数

对于引用类型,直接赋值或使用copy函数均属于浅拷贝范畴,但两者在行为细节上有所不同。

第1步:理解等号赋值的指针共享

当使用=给一个切片赋值时,只是将底层的指针、长度和容量复制给了新变量。两个变量指向同一个底层数组,因此对任一变量的元素修改都会同步反映在另一个上。

package main
 
import (
    "fmt"
    "reflect"
    "unsafe"
)
 
func main() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := slice1 // 浅拷贝,指向同一底层数组
    fmt.Println(slice1)
    fmt.Println(slice2)
    
    // 同时改变两个数组
    slice1[1] = 100
    fmt.Println(slice1)
    fmt.Println(slice2)
    
    // 验证底层地址一致
    fmt.Printf("切片1指向的底层数组地址: %+v\n", (*reflect.SliceHeader)(unsafe.Pointer(&slice1)))
    fmt.Printf("切片2指向的底层数组地址: %+v\n", (*reflect.SliceHeader)(unsafe.Pointer(&slice2)))
}

第2步:正确使用内置copy函数

虽然copy函数也是浅拷贝,但它执行的是“值复制”动作,即把源切片的元素值逐个复制到目标切片。
注意事项如下:

  1. 限制范围:copy只能用于切片(Slice),不能用于Map或其他类型。
  2. 返回值:返回成功复制的元素数量。
  3. 长度匹配:目标切片必须已初始化且有足够长度,否则无法写入或只复制部分内容。

以下是不同长度设置下的表现:

package main
 
import "fmt"
 
func main() {    
    src := []int{1, 2, 3, 5, 6, 7, 8}
    fmt.Println("src len:", len(src), "src:", src)
    
    // 情况1:目标长度不足,部分复制
    dst := make([]int, 0)
    copy(dst, src) // 实际未复制,因为dst长度为0
    fmt.Println("dst len:", len(dst), "dst:", dst)
    
    // 情况2:目标长度小于源,复制前N个
    dst1 := make([]int, len(src)/2)
    copy(dst1, src)
    fmt.Println("dst1 len:", len(dst1), "dst1:", dst1)
    
    // 情况3:目标长度等于源,完全复制
    dst2 := make([]int, len(src))
    copy(dst2, src)
    fmt.Println("dst2 len:", len(dst2), "dst2:", dst2)
    
    // 情况4:目标长度大于源,多余部分填默认值
    dst3 := make([]int, len(src) + 2)
    copy(dst3, src)
    fmt.Println("dst3 len:", len(dst3), "dst3:", dst3)
}

第3步:警惕多维切片的浅拷贝陷阱

当切片元素本身也是引用类型(如二维切片[][]int)时,一维的copy仅仅复制了内层切片的头指针,并未复制内层数据。这导致外层地址不同,但内层元素仍指向相同内存。

错误示例:修改matB[0][0]会连带影响matA[0][0]。

正确做法是遍历初始化每一维切片后再执行copy:

func rightCopyMatrix() {
    matA := [][]int{
        {0, 1, 1, 0},
        {0, 1, 1, 1},
        {1, 1, 1, 0},
    }
    matB := make([][]int, len(matA))
    for i := range matA {
        matB[i] = make([]int, len(matA[i])) // 关键:先初始化每一维
        copy(matB[i], matA[i])              // 再复制内容
    }
    // 此时matA和matB不仅外层地址不同,内层地址也不同,互不影响
}

深拷贝实现:基于序列化的方法

如果需要完全独立的对象副本(尤其是包含复杂结构体或混合类型时),建议使用序列化方式。Golang标准库encoding/gob提供了便捷的深拷贝方案。

import (
    "bytes"
    "encoding/gob"
)

// 注册要使用的类型,防止gob报错
func init() {
    gob.Register(struct{}{}) // 可根据实际需要注册特定结构体
}

func deepCopy(dst, src interface{}) error {
    var buf bytes.Buffer
    if err := gob.NewEncoder(&buf).Encode(src); err != nil {
        return err
    }
    return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
}

该方法将对象编码为字节流,再解码回新对象,确保新旧对象在内存中完全隔离,适用于任意可序列化的Golang类型。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。