1.数组
数组可以存放多个同样的数据类型数据,数组也是一种数据类型,在GO中,数组是值类型
数组的定义
语法:var 数组名 [数组大小]数据类型
示例:var array [5]int
赋值:array[0] = 1 a[1] = 30 //[]中括号中输入的是数组中元素的下标,从0开始,0就是第一个元素
上图中,前面三种都是顺序赋值,第四种在初始化时指定给某一个元素赋值,
如果不写长度 var numArr05 = []int,则会被定义成是一个切片
数组的内存地址就是数组中第一个元素的地址,数组元素的内存地址是连续的,取数组元素时,实际上是在取到首元素地址后,根据数据类型增加相应的内存地址即可,比如int64类型的数组,则每次增加8个字节即可取到下一个元素,int32类型的数组则每次增加4个字节取下一个元素
可以仔细观察下图中最后一行输出每个地址的最后两位 a0=>a8=>b0
因为数组是值类型,默认情况下数组在调用函数传参的时候,是进行的值拷贝,函数的内部栈会对数据拷贝一份放到内部,在函数内改变数组的值,不会修改外部数组的值,如果想要进行改变,则可以进行指针传递
2.切片(slice)
为什么需要切片?
因为数组在创建时长度必须确定,但是某些情况下,我们并不能确定长度是多少
切片可以简单理解为动态数组,但是跟数组有一定区别
1.切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制
2.切片的使用和数组类似,遍历切片和访问切片的元素和求切片长度都和数组是一样的
3.切片的长度是可以变化的,因此切片是一个可以动态变化的数组
4.切片定义的基本语法
第一种:由数组中提取部分元素来创建切片
var 切片名 [] 类型:如 var a [] int(跟数组的区别是[](中括号)中不需要写长度)
cap()是一个内置函数,该函数返回的是参数的容量,切片的容量和实际长度是不一样的。
输出结果
第二种:使用make函数创建切片
var 切片名 []类型 = make([]类型,长度,[容量])
说明:容量为可选参数,不传则默认长度为切片的容量
第三种:定义一个切片,直接就指定具体数组,使用原理类似make的方式
输出结果:
没有指定容量,则容量默认是数组长度
切片的扩容机制
切片的容量会根据长度自动扩充,当长度超过容量的时候,
如果需要的容量超过原切片容量的两倍时,会使用需要的容量作为新容量。
如果切片的容量小于1024会直接翻倍,如果超过1024会按照每次25%来增加,直到容量超过长度
切片实际上就是一个结构体,该结构体包含指向数组的指针,长度,容量3个字段
每次扩容都是创建了一个新的数组,扩容后修改切片的元素值,如果切片原本是引用一个数组,则不会影响原数组的值
为了避免因为切片是否发生扩容的问题导致bug,最好的处理办法还是在必要时使用 copy
来复制数据,保证得到一个新的切片,以避免后续操作带来预料之外的副作用。
切片的动态追加
append()内置函数
append()在追加后,会创建一个新的数组,使用扩容机制得到新的容量并把切片原本的元素拷贝到新的数组中进行返回,原数组会被GC(垃圾回收)自动回收
所以在追加后需要接收这个新的数组
slice3 :=append(slice3,400,500,600)
也可以直接追加一个切片(...为固定写法)
slice3 :=append(slice3,slice3...)
切片的拷贝操作
copy()内置函数
copy(slice2,slice1) //把slice1的值拷贝到slice2
copy函数要求参数均为切片类型,不能是数组,拷贝后,两个切片互不影响,均拥有自己的底层数组内存空间
如果第一个参数的长度小于第二个,则会按照第一个参数的长度拷贝相应数量的值
切片跟数组不同是引用类型,所以在函数调用的时候,函数内部修改元素值,外部的切片也会同步进行更改。
踩坑提示:要修改切片里面的元素时,用for i := 0; i < len(slice); i++ {}循环,不要用range,因为range得到的item是拷贝一份的临时变量,不是直接取的切片中的地址,下图中可以看到,i循环得到的地址才是实际上切片的地址
切片的扩展使用
因为string底层是一个byte数组,因此切片可以用来截取字符串