当前位置: 首页 > article >正文

【Go】Go语言中深拷贝和浅拷贝

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Go语言开发零基础到高阶实战
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • Go语言中深拷贝和浅拷贝
    • 一、深拷贝和浅拷贝的基本概念
      • 1.1 深拷贝(Deep Copy)
      • 1.2 浅拷贝(Shallow Copy)
    • 二、深拷贝和浅拷贝的区别
      • 2.1 数据复制
      • 2.2 对象关联
      • 2.3 内存占用
    • 三、Go语言中深拷贝和浅拷贝的使用场景
      • 3.1 深拷贝的使用场景
      • 3.2 浅拷贝的使用场景
    • 四、Go语言中深拷贝和浅拷贝的实现方法
      • 4.1 浅拷贝的实现
        • 示例1:引用类型的浅拷贝
        • 示例2:结构体的浅拷贝
      • 4.2 深拷贝的实现
        • 4.2.1 使用`json.Marshal`和`json.Unmarshal`
        • 4.2.2 递归深拷贝
    • 五、总结

Go语言中深拷贝和浅拷贝

在Go语言中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种常见的对象复制方式,它们的主要区别在于是否真正获取到了被拷贝对象的单独掌控权,从而避免互相影响的问题。本文将结合实际案例,详细阐述Go语言中深拷贝和浅拷贝的概念、区别、使用场景及具体实现方法。

一、深拷贝和浅拷贝的基本概念

1.1 深拷贝(Deep Copy)

深拷贝是指创建一个新对象,并递归地复制原对象中的所有数据(包括子对象),使得新对象与原对象在内存中是完全独立的。修改新对象不会影响原对象,反之亦然。深拷贝保证了对象的完全独立性和数据的完整性。

1.2 浅拷贝(Shallow Copy)

浅拷贝只是复制了对象本身,而没有复制其所引用的子对象。换句话说,浅拷贝仅仅复制了对象的引用(或指针),使得原对象和新对象共享同一块内存空间。因此,修改新对象中的引用类型数据会影响到原对象,反之亦然。

二、深拷贝和浅拷贝的区别

2.1 数据复制

  • 深拷贝:复制所有数据,包括值和子对象。
  • 浅拷贝:只复制对象的引用,不复制子对象。

2.2 对象关联

  • 深拷贝:新对象与原对象完全独立,修改其中一个不会影响另一个。
  • 浅拷贝:新对象与原对象共享部分数据(如子对象),修改其中一个可能会影响另一个。

2.3 内存占用

  • 深拷贝:由于复制了所有数据,因此可能会占用更多内存。
  • 浅拷贝:只复制引用,占用内存较少。

三、Go语言中深拷贝和浅拷贝的使用场景

3.1 深拷贝的使用场景

  • 当需要创建一个独立的对象,并且不希望修改原始对象时,应使用深拷贝。例如,处理敏感数据时,为了避免数据泄露,需要复制一份新的数据进行操作。
  • 在并发编程中,当多个goroutine需要操作同一份数据的副本时,为了防止竞态条件,应使用深拷贝。

3.2 浅拷贝的使用场景

  • 当需要创建一个对象的副本,并且希望修改副本时,可以使用浅拷贝。这样,修改副本的同时也会影响到原对象,这在某些场景下是需要的。
  • 在某些性能敏感的场景下,为了减少内存占用和提高性能,可以使用浅拷贝。

四、Go语言中深拷贝和浅拷贝的实现方法

4.1 浅拷贝的实现

在Go语言中,值类型的变量在赋值时默认进行深拷贝(因为值类型的数据直接存储在变量中),而引用类型的变量在赋值时默认进行浅拷贝(因为引用类型的数据存储在堆上,变量中只存储指向数据的指针)。

示例1:引用类型的浅拷贝
package main

import "fmt"

func main() {
    slice1 := []int{1, 2, 3}
    slice2 := slice1 // 浅拷贝
    slice2[0] = 10
    fmt.Println(slice1) // 输出: [10 2 3]
}

在上面的例子中,slice2slice1的浅拷贝,它们共享同一个底层数组。因此,修改slice2的第一个元素也会影响到slice1

示例2:结构体的浅拷贝
package main

import "fmt"

type Dog struct {
    Name string
    Age  int
}

func main() {
    dog1 := Dog{Name: "dog1", Age: 11}
    dog2 := dog1 // 浅拷贝
    dog2.Name = "dog2"
    fmt.Println(dog1) // 输出: {dog1 11}
    fmt.Println(dog2) // 输出: {dog2 11}
}

虽然在这个例子中dog2看起来像是dog1的深拷贝(因为修改dog2.Name没有影响到dog1.Name),但实际上这是因为Dog结构体中的字段都是值类型,它们在赋值时自然进行了深拷贝。如果Dog结构体中包含引用类型的字段,那么就会表现出浅拷贝的特性。

4.2 深拷贝的实现

Go标准库中没有直接提供深拷贝的函数,但可以通过以下几种方式实现深拷贝:

4.2.1 使用json.Marshaljson.Unmarshal

这是一种简单但效率较低的方法,适用于简单的结构体。通过将对象序列化为JSON字符串,然后再反序列化回一个新对象,从而实现深拷贝。

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    person1 := Person{"Alice", 25}
    var person2 Person

    // 序列化
    data, err := json.Marshal(person1)
    if err != nil {
        panic(err)
    }

    // 反序列化
    err = json.Unmarshal(data, &person2)
    if err != nil {
        panic(err)
    }

    person2.Age = 30
    fmt.Println(person1) // 输出: {Alice 25}
    fmt.Println(person2) // 输出: {Alice 30}
}

这种方法虽然简单,但有几个缺点:

  • 需要对象可以序列化为JSON格式,且序列化和反序列化过程需要遍历整个对象,效率较低。
  • 如果对象中包含循环引用或特殊类型(如函数、channel等),则无法正确序列化。
4.2.2 递归深拷贝

对于复杂的数据结构,可以使用递归深拷贝的方法。这种方法需要手动编写代码来遍历对象中的所有字段,如果是基本类型则直接复制,如果是复杂类型(如结构体、切片、映射等)则递归调用深拷贝函数。

package main

import (
    "fmt"
    "reflect"
)

func DeepCopy(input interface{}) interface{} {
    if input == nil {
        return nil
    }

    switch reflect.TypeOf(input).Kind() {
    case reflect.Bool, reflect.String, reflect.Int, reflect.Int8, reflect.Int16,
        reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
        return input
    case reflect.Struct:
        in := reflect.ValueOf(input)
        out := reflect.New(in.Type()).Elem()
        for i := 0; i < in.NumField(); i++ {
            field := in.Field(i)
            if field.CanInterface() {
                // 递归调用DeepCopy
                out.Field(i).Set(reflect.ValueOf(DeepCopy(field.Interface())))
            }
        }
        return out.Interface()
    // 可以根据需要添加对切片、映射等类型的支持
    default:
        // 其他类型根据需要进行处理
        return input
    }
}

func main() {
    // 示例结构体
    type Object struct {
        Num   int
        Str   string
        Slice []int
        Map   map[string]int
        Person
    }

    // 匿名结构体Person
    type Person struct {
        Name string
        Age  int
    }

    obj1 := &Object{
        Num:   1,
        Str:   "hello",
        Slice: []int{2, 3},
        Map:   map[string]int{"age": 18},
        Person: Person{
            Name: "Lucas",
            Age:  20,
        },
    }

    // 深拷贝
    obj2 := DeepCopy(obj1).(*Object)

    // 修改obj1的Name字段
    obj1.Person.Name = "Nina"

    fmt.Println("obj1:", obj1)
    fmt.Println("obj2:", obj2)
}

// 输出结果:
// obj1: &{1 hello [2 3] map[age:18] {Nina 20}}
// obj2: &{1 hello [2 3] map[age:18] {Lucas 20}}

在这个例子中,我们定义了一个DeepCopy函数,它使用反射来遍历对象中的所有字段,并根据字段类型进行相应的深拷贝处理。对于结构体类型,我们递归调用DeepCopy函数来处理其内部字段。这样,无论对象包含多少层嵌套,都能实现完整的深拷贝。

五、总结

深拷贝和浅拷贝是Go语言中常见的对象复制方式,它们的主要区别在于是否真正获取到了被拷贝对象的单独掌控权。深拷贝会复制对象的所有数据和子对象,使得新对象与原对象完全独立;而浅拷贝只是复制了对象的引用,使得新对象与原对象共享同一块内存空间。在选择使用深拷贝还是浅拷贝时,需要根据具体的场景和需求来决定。

在实际编程中,深拷贝通常比浅拷贝更加复杂和昂贵,因为它需要递归地复制对象的所有部分,包括嵌套的对象和集合。然而,深拷贝提供了更高的数据独立性和安全性,避免了因修改副本而影响原始数据的风险。


http://www.kler.cn/news/316484.html

相关文章:

  • PlantUML的使用以及各种图表示例
  • HOJ网站开启https访问 申请免费SSL证书 部署证书详细操作指南
  • 无法将ggplot图保存为PDF文件怎么办
  • 数组高阶应用(C++版)
  • TypeError: expected string or buffer - Langchain, OpenAI Embeddings
  • 力扣3290.最高乘法得分
  • 【PHP小课堂】PHP中的函数相关处理方法学习
  • 【计算机网络 - 基础问题】每日 3 题(十六)
  • 目标检测:滑块验证
  • 2012年408考研真题-数据结构
  • 领夹麦克风哪个品牌好,无线领夹麦克风品牌排名,麦克风品牌大全
  • Python ORM 框架 SQLModel 快速入门教程
  • 每日一道算法题(二)—快乐数
  • STaR: Bootstrapping Reasoning With Reasoning
  • Android 如何实现搜索功能:本地搜索?数据模型如何设计?数据如何展示和保存?
  • 基于YOLOv8+LSTM的商超扶梯场景下行人安全行为姿态检测识别
  • Python3使用websocket调用http代理IP
  • IP包头分析
  • 【SSM-Day2】创建SpringBoot项目
  • 基于丹摩智算平台-手把手拿下经典目标检测模型 Faster-Rcnn
  • H5响应式的文化传媒娱乐公司HTML网站模板源码
  • Linux入门学习:深刻理解计算机硬件与OS体系
  • saltstack配置管理
  • 深度学习基础案例5--VGG16人脸识别(体验学习的痛苦与乐趣)
  • C++第七节课 运算符重载
  • 航拍房屋检测系统源码分享
  • 对条件语言模型(Conditional Language Model)的目标函数的理解
  • C语言编译四大阶段
  • EasyExcel的基本使用——Java导入Excel数据
  • [C#]winform 使用opencvsharp实现玉米粒计数