• go深浅拷贝


    在刚使用 Go 时,菜刀曾将 Python 深拷贝手法[:]用于 Go 中 ,结果造成了 bug。相信不少转语言的 Gopher 也在切片拷贝上栽过跟头。

    切片是 Go 中最基础的数据结构,之前我们谈过切片传递切换转换切片扩容等内容。

    本文,我们将探讨切片拷贝,就切片的三种拷贝方式进行图解分析,希望帮助读者巩固一下基础知识。

    深浅拷贝

    所谓深浅拷贝,其实都是进行复制,主要区别在于复制出来的新对象和原来的对象,它们的数据发生改变时,是否会相互影响。

    简单而言,B 复制 A,如果 A 的数据发生变化,B 也跟着变化,这是浅拷贝。反之, 如果 B 不发生变化,则为深拷贝。

    深浅拷贝差异的根本原因在于,复制出来的对象与原对象是否会指向同一个地址。

    以下是 Python 中 list 与 Go 中 slice  深浅拷贝的表现差异

    // Python 版
    if __name__ == '__main__':
        a = [1, 2, 3]
        b = a
        c = a[:]
        a[0] = 100
        print(a, b, c) // [100, 2, 3] [100, 2, 3] [1, 2, 3]

    // Golang 版
    func main() {
     a := []int{1, 2, 3}
     b := a
     c := a[:]
     a[0] = 100
     fmt.Println(a, b, c) // [100 2 3] [100 2 3] [100 2 3]
    }

    发现没有?在 Go 中 [:] 操作并不是深拷贝。

    = 拷贝

    通过=操作符拷贝切片,这是浅拷贝。

    func main() {
     a := []int{1, 2, 3}
     b := a
     fmt.Println(unsafe.Pointer(&a))  // 0xc00000c030
     fmt.Println(a, &a[0])            // [100 2 3] 0xc00001a078
     fmt.Println(unsafe.Pointer(&b))  // 0xc00000c048
     fmt.Println(b, &b[0])            // [100 2 3] 0xc00001a078
    }

    图解

    图片

    [:] 拷贝

    通过[:]方式复制切片,同样是浅拷贝。

    func main() {
     a := []int{1, 2, 3}
     b := a[:]
     fmt.Println(unsafe.Pointer(&a)) // 0xc0000a4018
     fmt.Println(a, &a[0])           // [1 2 3] 0xc0000b4000
     fmt.Println(unsafe.Pointer(&b)) // 0xc0000a4030
     fmt.Println(b, &b[0])           // [1 2 3] 0xc0000b4000
    }

    图解

    图片

    我们有时会使用[start: end]进行拷贝。例如,b:=a[1:],那它的拷贝情况如何

    图片

    copy() 拷贝

    上述两种方式都是浅拷贝,如果要切片深拷贝,需要用到copy()内置函数。

    copy()函数签名如下

    func copy(dst, src []Type) int

    其返回值代表切片中被拷贝的元素个数

    func main() {
     a := []int{1, 2, 3}
     b := make([]int, len(a), len(a))
     copy(b, a)
     fmt.Println(unsafe.Pointer(&a))  // 0xc00000c030
     fmt.Println(a, &a[0])            // [1 2 3] 0xc00001a078
     fmt.Println(unsafe.Pointer(&b))  // 0xc00000c048
     fmt.Println(b, &b[0])            // [1 2 3] 0xc00001a090
    }

    图解

    图片

    copy 的元素数量与原始切片和目标切片的大小、容量有关系

    func main() {
     a := []int{1, 2, 3}
     b := []int{-1, -2, -3, -4}
     copy(b, a)
     fmt.Println(unsafe.Pointer(&a))  // 0xc0000a4018
     fmt.Println(a, &a[0])            // [1 2 3] 0xc0000b4000
     fmt.Println(unsafe.Pointer(&b))  // 0xc0000a4030
     fmt.Println(b, &b[0])            // [1 2 3 -4] 0xc0000aa060
    }

    图解

    图片

    总结

    切片是 Go 语言中最基本的数据结构,它的扩容与拷贝细节,在理解不当时,是很容易写出程序 bug 的。

    本文分别就切片的三种拷贝方式,=[:]copy()进行了探讨。其中,=[:]是浅拷贝,copy()拷贝是深拷贝。

    这样的图解方式,你喜欢吗

  • 相关阅读:
    C#执行异步操作的几种方式
    spring boot swagger ui使用 nginx 部署后无法使用问题
    PHP支付宝接口RSA验证
    认证加密算法php hash_hmac和java hmacSha1的问题
    如何实现从 Redis 中订阅消息转发到 WebSocket 客户端
    ffmpeg安装
    nginx静态资源反向代理
    Redis基础知识 之——发布/订阅
    linux下安装git
    php实现无限级分类查询(递归、非递归)
  • 原文地址:https://www.cnblogs.com/cheyunhua/p/16460512.html
Copyright © 2020-2023  润新知