首页 > 其他分享 >判断go对象是否能直接赋值进行深拷贝

判断go对象是否能直接赋值进行深拷贝

时间:2023-01-29 15:46:37浏览次数:66  
标签:name age BarA go path 拷贝 main 赋值

golang中可以使用a := b这种方式将b赋值给a,只有当b能进行深拷贝时ab才不会互相影响,否则就需要进行更为复杂的深拷贝。

下面就是Go赋值操作的一个说明:
Go语言中所有赋值操作都是值传递,如果结构中不含指针,则直接赋值就是深度拷贝;如果结构中含有指针(包括自定义指针,以及切片,map等使用了指针的内置类型),则数据源和拷贝之间对应指针会共同指向同一块内存,这时深度拷贝需要特别处理。目前,有三种方法,一是用gob序列化成字节序列再反序列化生成克隆对象;二是先转换成json字节序列,再解析字节序列生成克隆对象;三是针对具体情况,定制化拷贝。前两种方法虽然比较通用但是因为使用了reflex反射,性能比定制化拷贝要低出2个数量级,所以在性能要求较高的情况下应该尽量避免使用前两者。

现在我需要判断某个对象是否可以直接用赋值进行深拷贝,如果不能直接进行深拷贝时,到底是哪个字段影响了深拷贝,下面就是判断的代码:

package main

import (
	"bytes"
	"fmt"
	"reflect"
)

type (
	PerA struct {
		A int
		B string
		c []byte
	}
	Per struct {
		PerA
		Name string
		Age  int
	}
	BarA struct {
		A string
		b *int
	}
	Bar struct {
		A int64
		BarA
	}
	CatA struct {
		name string
		age  int
	}
	Cat struct {
		name string
		age  int
		CatA
	}
)

func main() {
	var out bytes.Buffer
	ok := CanDeepCopy(Per{}, &out)
	fmt.Println(ok, out.String())

	out.Reset()
	ok = CanDeepCopy(Bar{}, &out)
	fmt.Println(ok, out.String())

	out.Reset()
	ok = CanDeepCopy(Cat{}, &out)
	fmt.Println(ok, out.String())

	bi := 1
	b0 := Bar{A: 1, BarA: BarA{A: "11", b: &bi}}
	b1 := b0
	b1.A, b1.BarA.A, *b1.BarA.b = 2, "22", 2
	fmt.Printf("%#v,%p,%d\n", b0, &b0, *b0.BarA.b)
	fmt.Printf("%#v,%p,%d\n", b1, &b1, *b1.BarA.b)

	c0 := Cat{name: "1", age: 1, CatA: CatA{name: "1", age: 1}}
	c1 := c0
	c1.name, c1.age, c1.CatA.name, c1.CatA.age = "2", 2, "2", 2
	fmt.Printf("%#v,%p\n", c0, &c0)
	fmt.Printf("%#v,%p\n", c1, &c1)
}

func CanDeepCopy(v any, path *bytes.Buffer) bool {
	t := reflect.TypeOf(v)
	if path.Len() == 0 {
		path.WriteString(t.Name()) // 记录首次对象名称
	}
	switch t.Kind() {
	case reflect.Pointer: // 指针可比较,但不能深拷贝
		path.WriteString(" is pointer") // 该字段为指针
		return false
	case reflect.Struct: // 结构体需要判断每一个字段
		path.WriteByte('.')
		for i, pn := 0, path.Len(); i < t.NumField(); i++ {
			tf := t.Field(i)
			path.WriteString(tf.Name) // 记录子字段名称
			// 构造一个该字段类型的对象,注意将指针换成值
			fv := reflect.New(tf.Type).Elem().Interface()
			if !CanDeepCopy(fv, path) {
				return false // 递归判断每个字段,包括匿名字段
			}
			path.Truncate(pn) // 回溯时截断没问题的子字段
		}
	}
	if t.Comparable() {
		return true
	}
	path.WriteString(" incomparable") // 该字段不可比较
	return false
}

运行结果:

false Per.PerA.c incomparable # 说明 Per.a.c.cc 字段属于不可比较字段导致不能深拷贝
false Bar.BarA.b is pointer   # 说明 Bar.BarA.b 字段是指针导致不能深拷贝
true Cat.  # 说明 Cat 对象可以直接进行深拷贝

# 由于 Bar 不可以深拷贝
# 可以看到 b1 := b0 之后,两个对象共用 BarA.b 指针指向对象,因此 *b1.BarA.b = 2 之后也影响了b0
main.Bar{A:1, BarA:main.BarA{A:"11", b:(*int)(0xc0000a6148)}},0xc0000a03e0,2
main.Bar{A:2, BarA:main.BarA{A:"22", b:(*int)(0xc0000a6148)}},0xc0000a0400,2

# 由于 Cat 可以深拷贝,因此 c1 := c0 之后这两个对象互不影响,这种对象直接赋值,不用其他方案进行深拷贝
main.Cat{name:"1", age:1, CatA:main.CatA{name:"1", age:1}},0xc0000bc5d0
main.Cat{name:"2", age:2, CatA:main.CatA{name:"2", age:2}},0xc0000bc600

通过研究go赋值逻辑,理解了深拷贝和浅拷贝的逻辑。实际上go的赋值操作只存在值拷贝,由于一些引用类型赋值的是地址导致两个变量共用内存数据才导致需要额外进行深拷贝处理。

引用
https://www.ssgeek.com/post/golang-jie-gou-ti-lei-xing-de-shen-qian-kao-bei/
https://sorcererxw.com/articles/go-comparable-type
https://blog.csdn.net/pengpengzhou/article/details/105839518
https://www.cnblogs.com/gtea/p/16850496.html

标签:name,age,BarA,go,path,拷贝,main,赋值
From: https://www.cnblogs.com/janbar/p/17072751.html

相关文章

  • k8s client-go 01介绍
    关于client-goclient-go是一个golang的client,我们可以通过client-go与K8SapiServer进行交互,对k8s集群中资源对象,包括内置资源(例如:Pod、Deployment、Service等)和CRD进......
  • Go学习笔记
    1.当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个......
  • ReplaceGoogleCDN扩展下载和安装
    ReplaceGoogleCDN扩展下载和安装文档说明:只记录关键地方;2023-01-29在线安装备注:扩展市场的代码版本远落后于源码仓库版本ChromeFirefoxEdge手动安装:Chrome......
  • golang调用钉钉webhook发送消息
    golang使用dingtalk的webhook地址,发送消息通知此处用于记录golang调用dingtalkwebhook地址发送消息通知;一、使用http包自己拼接消息体,使用http包的post请求来......
  • golang使用sqlx操作MySQL
     packagemain//sqlx示例import("errors""fmt"_"github.com/go-sql-driver/mysql""github.com/jmoiron/sqlx")varDB*sqlx.DBtypeUs......
  • golang连接操作mysql
    golang操作mysqlpackagemainimport("database/sql""fmt""time"_"github.com/go-sql-driver/mysql")//定义一个全局db对象vardb*sql.DB......
  • Django处理带T、带Z的时间格式
    问题:   USE_TZ=False后,django orm 查询时间报错:MySQLbackenddoesnotsupporttimezone-awaredatetimeswhenUSE_TZisFalse.解决: 查询时间前,做替换imp......
  • 探究Array.of为浅拷贝
    发现一个很有意思的事/***Array.of是否为浅拷贝*/constarr=[1,{x:1}]constarrOf=Array.of(...arr)console.log('arr',arr)arrOf[0]='zjy'......
  • Good Bye 2022 简要题解
    从这里开始比赛目录过气选手留下了只会套路的眼泪。sad......ProblemA KoxiaandWhiteboards相信大家都会.jpgCode#include<bits/stdc++.h>using......
  • GFast V3.2.1 版本发布,采用 GoFrame 2.3 + Vue3 后台管理系统
    平台简介基于全新GoFrame2.3+Vue3+ElementPlus开发的全栈前后端分离的管理系统前端采用vue-next-admin、Vue、ElementUI。特征高生产率:几分钟即可搭建一个后台管......