当前位置:网站首页>Golang 数组和切片
Golang 数组和切片
2022-08-03 12:47:00 【云满笔记】
这里填写标题
1. Golang 数组和切片
1.1. 数组
数组初始化方式常用的有 3 种,至于其它的用的很少,就不用管了,常用方式如下:
var a[4]intb := [4]int{
2, 4}
c := [...]int{
2, 4}
Go 数组是值类型,赋值和传参会复制整个数组数据,为了避免数据复制,可以使用数组指针:
func test(x *[2]int) {
x[1] += 1
}
func main() {
a := [2]int{
2, 3}
test(&a)
}
最后需要区分指针数组和数组指针的区别:
func main() {
x, y := 1, 2
a := [...]*int{
&x, &y} // 元素为指针的指针数组
p := &a // 存储数组地址的指针
}
Go 的数组,其实我们用的不多,一般大家都用切片,所以对于数组,掌握上述知识就可以了,其它关于数组的知识,需要用的时候,查阅相关资料即可。
1.2. 切片
1.2.1. 概念
切片出现的原因也是因为数组的可操作性不高。切片的长度是不固定的,可以追加数据,可以理解切片是一个动态数组,切片的底层是一个结构体。
type slice struct {
array unsafe.Pointer
len int
cap int
}
切片类型 (slice
) 本身并不是动态数组或数组指针。它内部通过指针引用底层数组,设定相关属性将操作限定在指定范围内。当需要时,会申请更大的内存,将当前数据复制过去,以实现类似动态数组的功能。
1.2.2. 切片创建
可直接创建切片对象,无需预先准备数组。因为是引用类型,须使用 make
函数或显式初始化语句,它会自动完成底层数组内存分配。
普通格式:
var 切片名 [] 数据类型
自动推导类型创建切片:
切片名 := [] 类型{
}
make
函数创建切片:长度是已经初始化的空间,容量是已经开辟的空间,包括已经初始化的空间和空闲的空间。
// 长度是不能大于容量的,容量可以省略不写,不写时候就默认和长度的值一样
切片名称 := make ([] 切片类型,长度 容量)
// 返回切片的容量使用 cap, 返回切片的长度使用 len
fmt.Println(cap(切片名称))
// 演示
s1 := make([]int, 3, 5) // 指定 len、cap, 底层数组初始化为零值
s2 := make([]int, 3) // 省略 cap, 和 len 相等
s3 := []int{
10, 20, 5: 30} // 按初始化元素分配底层数组,并设置 len、cap, 设置索引 5 的数据为 30
1.2.3. 切片初始化
三种创建格式,都是可以通过 append
向切片添加数据的,初始化格式:
// 普通格式创建的切片
切片名 [索引] = 值
// 自动推导类型创建的切片
切片名 := [] 类型{
数据 1, 数据 2, 数据 3}
// make 函数方式创建的切片可以通过 append 和循环初始化
切片名称 = append(切片名称,数据 1, 数据 2...)
// 演示
s1 := make([]int, 4, 6) // 由于 `len = 4`, 所以后面 2 个暂时访问不到,但是容量还是在,数组里面每个变量都是 0。
s2 := []int{
10,20,30,40,50,60}
1.2.4. append 函数
append
函数是向切片的末尾slice(len)
添加数据- 如果添加的内容超出了切片初始定义的容量,切片会自动扩容
- 扩容机制是:上一次的容量 * 2
- 如果超过
1024
字节,每次扩容上一次的1/4
append
每次扩容都是一个新的内存,和原来的无关联,所以如果是通过参数传递的方式,使用append
添加数据,但是不会影响到原切片的数据,原因就是append
每次拓展都是一个新的空间,指向的内存不再是原切片。
1.2.5. copy 函数
- 把切片 2 的数据 (
0
索引到len-1
) 赋值到切片 1 中。 - 注意:如果切片 1 的容量不够,则不赋值剩余的数据。如果切片 1 的数据比切片 2 的多,从切片 2 复制的数据是有多少,复制多少。
- 总结:
copy
只是复制索引相对应的数据,如果长度不够,不会覆盖原来的数据。
格式:
copy(切片 1, 切片 2)
演示:
// 从切片 2 复制到切片 1, 但是切片 2 的数据比切片 1 的多,所以,最终只是复制了一部分,也就是索引相对应的数据
func main() {
slice := []int{
1, 2, 3}
slice2 := []int{
4, 5, 6, 7, 8, 9}
copy(slice, slice2)
fmt.Println(slice) // [4 5 6]
}
// 从切片 1 复制到切片 1, 但是切片 1 的数据比切片 2 的少,所以,最终只是复制了一部分,也就是索引相对应的数据
func main() {
slice := []int{
1, 2, 3}
slice2 := []int{
4, 5, 6, 7, 8, 9}
copy(slice2, slice)
fmt.Println(slice2) // [1 2 3 7 8 9]
}
还可直接从字符串中复制数据到 []byte
:
func main() {
b := make([]byte, 3)
n := copy(b, "abcde")
fmt.Println(n, b)
}
1.2.6. 切片截取
切片截取就是从切片中获取指定的数据。如果初始化切片时,没有指定切片的容量,切片容量是跟随原切片的。
切片截取的操作:
操作 | 含义 |
---|---|
s[n] | 切片 s 中索引位置为 n 的项 |
s[:] | 从切片 s 的索引位置 0 到 len(s)-1 处所获得的切片 |
s[low:] | 从切片 s 的索引位置 low 到 len(s)-1 处所获得的切片 |
s[:high] | 从切片 s 的索引位置 0 到 high 处所获得的切片,len=high |
s[low:high] | 从切片 s 的索引位置 low 到 high 处所获得的切片,len=high-low |
s[low:high:max] | 从切片 s 的索引位置 low 到 high 处所获得的切片,len=high-low, cap=max-low |
len(s) | 切片 s 的长度,总是 <=cap(s) |
cap(s) | 切片 s 的容量,总是 >=len(s) |
/** 第一个值:截取的起始索引 第二个值:截取的终止索引(不包括该值) 第三个值:用来计算切片的容量,可以省略,默认和长度一样 容量 = 第三个值 - 第一个值 长度 = 第二个值 - 第一个值 */
newSlice := slice[0:3:3] // 切片的操作符 `s[i:j:k]`, `j` 和 `k` 是个开区间。
1.2.7. 切片值的修改
切片截取后返回新切片,对新切片的值进行修改,会影响原来的切片。
原因:切片截取后新的切片,不会给新的切片是指向了原来的切片,没有给新的切片开辟新的空间,所以对于新的切片操作会影响到原来的切片。
1.2.8. nil 和空切片
nil
切片的指针指向 nil
, 表示一个不存在的切片:
var slice []int
空切片一般会用来表示一个空的集合。比如数据库查询,一条结果也没有查到,那么就可以返回一个空切片。
silce := make([]int , 0) slice := []int{
}
需要说明的一点:不管是使用 nil
切片还是空切片,对其调用内置函数 append
, len
和 cap
的效果都是一样的。然后切片只能和 nil
判等,不支持切片判等。
1.2.9. 切片扩容
Go 切片扩容策略:如果切片的容量小 1024
个元素,于是扩容的时候就翻倍增加容量。上面那个例子也验证了这一情况,总容量从原来的 4 个翻倍到现在的 8 个。一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25
, 即每次增加原来容量的 1/4
。下面我们看一种情况,当扩容时没有新建一个新的数组的情况,这里容易出问题:
func main() {
array := [4]int{
10, 20, 30, 40}
slice := array[0:2] // 10 20
newSlice := append(slice, 50) // 10 20 50
newSlice[1] += 10 // 10 30 50
// 这里 slice=[10 30], array=[10 30 50 40], 入坑!! !
fmt.Printf("slice = %v\n", slice) // [10 30]
fmt.Printf("array = %v\n", array) // [10 30 50 40]
fmt.Printf("newSlice = %v\n", newSlice) // [10 30 50]
}
slice
、newSlice
和 array
底层共用一个数组,当修改 newSlice[1]
时,因为底层数据被修改,其它也都被修改了,这样非常容易产生莫名的 Bug!
1.2.10. 切片遍历
遍历和数组一样可以使用普通的 for
循环和 range
遍历得到。
// 演示
func main() {
slice := []int{
1, 2, 3, 4, 5}
for i := 0; i < len(slice); i++ {
fmt.Print(slice[i])
}
for _, v := range slice {
fmt.Println(v)
}
}
如果用 range
的方式去遍历一个切片,拿到的 Value 其实是切片里面的值拷贝,每次打印 Value 的地址都不变,所以仅修改 Value 的值,是不会改变 Slice 中的数据,这点切记!! !
1.2.11. 切片作为函数参数
切片可以做为函数的参数,但是在函数中修改切片的值,会影响到原切片。
因为切片的底层是结构体,结构体里有个参数 Pointer, Pointer 会指向切片的内存地址,使用的是浅拷贝方式,所以会影响到原切片值。
func main() {
slice := []int{
1, 2, 3, 4, 5}
SliceDemo10(slice)
}
func SliceDemo10(slice []int) {
for _, v := range slice {
fmt.Println(v)
}
slice = append(slice, 5, 6, 7)
fmt.Println(slice)
}
边栏推荐
- 基于php旅游网站管理系统获取(php毕业设计)
- 【R】用grafify搞定统计绘图、方差分析、干预比较等!
- 图像融合DDcGAN学习笔记
- Unsupervised learning KMeans notes and examples
- An基本工具介绍之选择线条工具(包教会)
- php microtime encapsulates the tool class, calculates the running time of the interface (breakpoint)
- Secure Custom Web Application Login
- Golang 接口 interface
- An introduction to basic tools for selecting line tools (package church)
- 【Verilog】HDLBits题解——验证:阅读模拟
猜你喜欢
【蓝桥杯选拔赛真题48】Scratch跳舞机游戏 少儿编程scratch蓝桥杯选拔赛真题讲解
图像融合SDDGAN文章学习
什么是分布式锁?几种分布式锁分别是怎么实现的?
汉源高科G8032标准ERPS环网交换机千兆4光10电工业以太网交换机环网+WEB管理+SNMP划VLAN
Station B responded that "HR said that core users are all Loser": the interviewer was persuaded to quit at the end of last year and will learn lessons to strengthen management
层次分析法
期货公司开户关注的关键点
便携烙铁开源系统IronOS,支持多款便携DC, QC, PD供电烙铁,支持所有智能烙铁标准功能
An动画基础之元件的影片剪辑动画与传统补间
海外代购系统/代购网站怎么搭建——源码解析
随机推荐
类型转换、常用运算符
【必读要点】Pod控制器Deployment更新、回退详解
An工具介绍之3D工具
安防监控必备的基础知识「建议收藏」
链游NFT元宇宙游戏系统开发技术方案及源码
GameFi 行业下滑但未出局| June Report
博客记录生活
YOLOv5训练数据提示No labels found、with_suffix使用、yolov5训练时出现WARNING: Ignoring corrupted image and/or label
Tinymce plugins [Tinymce扩展插件集合]
海外代购系统/代购网站怎么搭建——源码解析
浅谈低代码平台远程组件加载方案
Image fusion DDcGAN study notes
Yahoo!Answers - data set
shell编程条件语句
An动画优化之补间形状与传统补间的优化
论文理解:“Gradient-enhanced physics-informed neural networks for forwardand inverse PDE problems“
滑动窗口的最大值
【蓝桥杯选拔赛真题48】Scratch跳舞机游戏 少儿编程scratch蓝桥杯选拔赛真题讲解
Classes and Objects (lower middle)
基于php校园医院门诊管理系统获取(php毕业设计)