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("%pn", &a[2])
fmt.Printf("%pn", &b[0])
}
打印出来的地址完全一致, 印证了之前的猜想, 果然是一个数组. 同时修改a
数组, 也会影响到b
数组.
那可不可以对b
数组越界访问, 访问a
数组的值呢? 不行, GO 会对数组进行越界检查.
查看文档后发现, GO 切片的内部实现是这样的包含了三个字段. 其中各字段含义如下:
- 数组首地址指针: 指向底层数组的首地址(这个是真正的数组)
- 数组长度: 数组当前已经使用的长度
- 数组容量: 数组已分配内存的总长度, 比数组长度多出的部分, 是占用内存还没有使用的.
如此看来, 对其进行切割, 并不会整个复制, 对于大切片的操作就显得很友好了, 毕竟共享底层数组, 只需要创建很少的数据就可以了. 只是要注意数组的同步修改问题.
这么看来, 貌似也可以解释为什么叫切片了. **切片就是将底层的数组切出一部分来, 而不会创建新的数组. **
切片是有容量的, 那上面的切片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
函数复制一个全新的数组出来, 与原数组分离就可以了.
- python selenium2示例 - 生成 HTMLTestRunner 测试报告
- python selenium2示例 - email发送
- pyhton-----break语句
- python unittest使用基本过程
- 基于unittest集成你的selenium2测试
- Selenium Webdriver Desired Capabilities
- 在Selenium Webdriver中使用XPath Contains、Sibling函数定位
- Python多线程Selenium跨浏览器测试
- Python Selenium设计模式-POM
- 基于Excel参数化你的Selenium2测试
- 创建你的第一个webdriver python代码
- Python Selenium Webdriver安装手册
- 工具篇 - HTTP协议报文结构及示例03
- 工具篇 - JMeter目录及关键配置分析02
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 基于Tensorflow读取MNIST数据集时网络超时的解决方式
- python中的错误如何查看
- python实现斗地主分牌洗牌
- Python Mock模块原理及使用方法详解
- python实现猜数游戏(保存游戏记录)
- PHP实现简单计算器小程序
- PHP单例模式模拟Java Bean实现方法示例
- php实现每日签到功能
- laravel5实现微信第三方登录功能
- Laravel框架定时任务2种实现方式示例
- tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this T
- Python 基于jwt实现认证机制流程解析
- Keras – GPU ID 和显存占用设定步骤
- PHP中如何使用Redis接管文件存储Session详解
- 看我一波,Android获取进程名函数,代码优化到极致的操作!