设为首页 加入收藏

TOP

判断go对象是否能直接赋值进行深拷贝(一)
2023-07-23 13:33:31 】 浏览:230
Tags:判断 能直接

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

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

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

package main

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

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  float64
		CatA
	}

	ccc struct {
		A int
		B Cat
	}
)

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

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

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

	out.Reset()
	ok = CanDeepCopy(ccc{A: 1}, &out)
	fmt.Println(ok, out.String(), PointerLess(ccc{A: 1}))

	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 {
	tv := reflect.TypeOf(v)
	if path.Len() == 0 {
		path.WriteString(tv.Name()) // 记录首次对象名称
	}
	switch t := tv.Kind(); t {
	case reflect.Struct: // 结构体需要判断每一个字段
		path.WriteByte('.')
		for i, pn := 0, path.Len(); i < tv.NumField(); i++ {
			tf := tv.Field(i)
			path.WriteString(tf.Name) // 记录子字段名称
			// 构造一个该字段类型的对象,注意将指针换成值
			fv := reflect.New(tf.Type).Elem().Interface()
			if !CanDeepCopy(fv, path) {
				return false // 递归判断每个字段,包括匿名字段
			}
			path.Truncate(pn) // 回溯时截断没问题的子字段
		}
		return true
	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
		reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.Array,
		reflect.String /* string类型是特例,也是引用类型,但不可修改导致复制也没关系 */:
		return true // 上述类型可以进行深拷贝
	default:
		path.WriteString(": ")
		path.WriteString(tv.String())
		path.WriteString(" is ")
		path.WriteString(t.String())
		path.WriteString(" no deep copy")
		return false // 其他类型不能深拷贝
	}
}

func PointerLess(x any) bool {
	// 来自大佬的解惑: https://studygolang.com/topics/16111
	// 经过测试Cat对象为false,根据解释是因为有string类型,但是字符串类型不可修改,所以是特例
	// 因此最好还是按照上面的方案递归判断
	var ptr uintptr
	ptr = *(*uintptr)(unsafe.Pointer(uintptr(*(*unsafe.Pointer)(unsafe.Pointer(&x))) + unsafe.Size
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Go基础系列 01-Golang简介 下一篇Go chan解析

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目