zoukankan      html  css  js  c++  java
  • GO 切片实力踩坑

    概述

    GO 语言的切片这两天用了用, 可以支持切割数组的中间部分. 但今天使用中, 出了 bug, 查了半天, 发现是切片的问题, 简单写个 demo 复现当时的情况:

    package main
    
    import "fmt"
    
    func main() {
    	a := []int{1, 2, 3, 4, 5}
    	b := a[2:4]
    	b[0] = 9
    	fmt.Println(a)
    }
    

    你以为输出的是什么? 来, 看结果:

    [1 2 9 4 5]

    懵没懵?? 这是怎么回事呢?

    (我用个语言怎么老踩坑, 笨的一X)

    解惑

    看这段 GO 代码的输出, 我们在修改b数组第一个元素值的时候, a数组的第三个元素修改了, 这两个有什么联系吗? 仔细看, b数组在切的时候, 切的不就是a数组第三第四的元素吗? 如此看来, b[0] 不正对应 a[2] 吗?

    大胆假设: **GO 中对数组进行切割, 并不会切一个新的数组出来, 而是仍然使用原数组, 只是修改下数组的首地址和长度. **

    验证:

    package main
    
    import "fmt"
    
    func main() {
    	a := []int{1, 2, 3, 4, 5}
    	b := a[2:4]
    	b[0] = 9
    	fmt.Printf("%p
    ", &a[2])
    	fmt.Printf("%p
    ", &b[0])
    }
    

    打印出来的地址完全一致, 印证了之前的猜想, 果然是一个数组. 同时修改a数组, 也会影响到b数组.

    那可不可以对b数组越界访问, 访问a数组的值呢? 不行, GO 会对数组进行越界检查.

    查看文档后发现, GO 切片的内部实现是这样的包含了三个字段. 其中各字段含义如下:

    1. 数组首地址指针: 指向底层数组的首地址(这个是真正的数组)
    2. 数组长度: 数组当前已经使用的长度
    3. 数组容量: 数组已分配内存的总长度, 比数组长度多出的部分, 是占用内存还没有使用的.

    如此看来, 对其进行切割, 并不会整个复制, 对于大切片的操作就显得很友好了, 毕竟共享底层数组, 只需要创建很少的数据就可以了. 只是要注意数组的同步修改问题.

    这么看来, 貌似也可以解释为什么叫切片了. **切片就是将底层的数组切出一部分来, 而不会创建新的数组. **

    切片是有容量的, 那上面的切片b的容量是多少呢? 我看了一下: 长度是2, 容量是3.

    GO 的切片在容量足够的时候, 是不会动态扩容的. (扩容会创建更大容量新的数组并复制原数组数据). 那也就是说, 如果我向b追加数据, 就可以影响到原数组的后面的数据了??

    试一发:

    package main
    
    import "fmt"
    
    func main() {
    	a := []int{1, 2, 3, 4, 5}
    	b := a[2:4]
    	b = append(b, 10)
    	fmt.Println(b)
    	fmt.Println(a)
    }
    

    果然, 容量允许的话, 追加操作使用的仍然是原始数组.

    所以: 切片的容量其实是底层数组的容量

    同时, 有了之前对 GO 的了解, 知道 GO 所有的函数都是以传递副本值的方式进行, 传递切片也一样, 而切片的结构体包含(数组指针, 长度, 容量)三个元素, 底层数组并不属于值本身, 所以切片在函数间传递的复制成本很小, 而且函数对切片的修改也会反应到底层数组上. 同理可得, 如果在函数中对切片执行了扩容操作, 那改动就不会影响原数据, 因为扩容后操作的是新的数组了.

    OK. 切片到这里就结束了, 简单说就是数组上面再套一层. 切片的切片共享底层数组.

    最后说一句, GO 创建数组和切片的方式(数组和切片是不同的数据结构):

    // 方括号为空, 创建的是切片类型
    a := []int{1, 2}
    // 方括号指定长度, 创建的是真正的数组类型
    b := [2]int{}
    

    总结

    至此, 对 GO 的切片有了全新的认识. 在使用切片的时候, 需要特别注意, 切片的截取与原对象共享底层数组, 在数据修改时要特别注意.

    如果需要一个安全的可修改的切片, 可以使用copy函数复制一个全新的数组出来, 与原数组分离就可以了.

  • 相关阅读:
    C语言printf()输出格式大全
    C语言ASCII码、运算符优先级、转义字符
    通过Navicat for MySQL远程连接的时候报错mysql 1130 的解决方法
    mysql kill process解决死锁
    常用的二种修改mysql最大连接数的方法
    show processlist结果筛选
    数据库连接driverClass和jdbcUrl大全
    在实例中引用模式文档
    在Eclipse中导入dtd和xsd文件,使XML自动提示
    Linux下Java安装与配置
  • 原文地址:https://www.cnblogs.com/hujingnb/p/13334289.html
Copyright © 2011-2022 走看看