golang基础笔记(3):数组、切片
数组
与大多数语言中的数组都类似,Go语言的数组也是从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。但是实际上,Go中的数组并不常用,而常用为切片。
声明
1 | var 数组变量名 [元素数量]T |
初始化
基础写法
1 | var testArray [3]int //数组会初始化为int类型的零值 |
编译器判断长度
1 | var numArray = [...]int{1, 2} |
指定索引值
1 | r := [...]int{99: -1} |
定义了一个含有 100 个元素的数组 r
,最后一个元素被初始化为 -1,其它元素都是用 0 初始化
数组遍历
与大多数语言一样,Go语言遍历的方式主要分为两种方法:for和for range
1 | // 方法1:for循环遍历 |
数组是值类型
Go语言的数组是值类型,并不是引用类型。这导致Go语言与C、Java不同的是,Go的数组赋值和传参会复制整个数组。因此传参或是赋值,得到的新数组是原数组的一个副本,修改新数组并不会导致原数组发生变化。
1 | func changeArray(x [3]int) { |
注意点
[n]*T
表示指针数组,*[n]T
表示数组指针- 在Go语言中,数组的长度也是类型的一部分,也就意味着在函数的形参处,数组长度必须要确定,这导致了输入数组的长度必须是相同的,使得数组有很多局限性
切片
前面我们提到,数组在函数形参初输入数组长度必须是相同,这导致数组有很多局限性
Go语言提供了一种非常灵活的,拥有相同类型元素的可变长度的序列——切片(Slice)。切片的概念类似于“动态数组”,它是基于数组类型做的一层封装,支持自动扩容。
切片是一个引用类型,因此从数组中得到的切片修改元素值时,原数组也会发生变化,修改原数组时,切片也会变化。切片一般用于快速地操作一块数据集合。
它的内部结构包括地址
、长度
、容量
。
切片的定义
长度与容量
正如上文所言,切片拥有长度和容量,这与其他语言中的数组类似。
我们可以通过内置的len()函数求得长度,通过内置的cap()函数求得容量。
声明的基本语法
1 | var 变量名 []类型 |
切片表达式构造切片
切片表达式可以从字符串、数组、指向数组或切片的指针构造子字符串或切片。
它具有两种形式:
- 指定low和high两个索引界限值的简单形式——
low:high
- 除了low和high索引界限值外还指定容量的完整形式——
low:high:max
简单形式
我们通过表达式中的low和high就可以确定数组中的索引范围,为一个左包含,右不包含。得到的切片长度为high-low
,容量等于切片的底层数组的容量
1 | a := [5]int{1, 2, 3, 4, 5} |
在切片表达式中,我们可以省略任何索引:
- 省略low为从0开始
- 省略high为到切片操作数的长度
注意:对于切片再切片,high上线边界为原切片的容量cap(a),而不是长度。
完整形式
对于数组,指向数组的指针,或切片(不支持字符串)支持完整切片表达式
1 | a[low:high:max] |
该表达式会构建一个与a[low:high]
同类型、同长度、同元素,但是容量会被设置为max-low
的切片。
在完整切片表达式中,只有第一个索引值low
可以省略。
1 | a := [5]int{1, 2, 3, 4, 5} |
make()函数构造切片
切片表达式都是基于现有数组来创建的切片,如果我们需要动态的创建一个切片,则我们需要用到内置的make()
函数
1 | make([]T, size, cap) |
其中,T为切片的元素类型,size为元素长度,cap为容量
可以只指定长度,也开也长度容量都指定
1 | s1 := make([]uint32) |
切片的性质
判断切片是否为空
要检查切片是否为空,请使用len(s) == 0
来判断,而不应该使用s == nil
来判断。
没有底层数组的切片值为nil
,一个nil切片长度和容量都为0,但是长度和容量都为0的切片不一定是nil:
1 | var s1 []int //len(s1)=0;cap(s1)=0;s1==nil |
因此,要判断一个切片是否为空,要使用len(s) == 0
来判断,而不应该使用s == nil
来判断
切片不能直接相互比较
切片之间是不能直接相互比较的,我们不能使用==
判断两个切片是否含有相同的元素。
切片唯一合法的比较操作是和nil
比较。若要比较,则只能通过枚举比较的方式进行比较。
对于[]byte
,可以使用标准库提供的bytes.Equal
函数比较。
切片的本质
切片的本质是对底层数组的封装。它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。
例子:数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
,切片s1 := a[:5]
,相应示意图如下:
切片s2 := a[3:6]
,相应示意图如下:
CRUD
添加元素
通过append()
函数可以向切片的底层数组中追加元素,可以添加0个或多个元素,并且切片的底层数组会自动扩容。
每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()
函数调用时,所以我们通常都需要用原变量接收append函数的返回值。
1 | s := make([]uint32, 0, 4) |
如果需要追加其他切片或数组,可以在需添加的切片后面...
1 | s = append(s, s1...) |
切片遍历
切片遍历与数组遍历是相同的,支持:
- 索引遍历
for
for index, value := range s
修改元素
通过下标指定即可修改。
注意:在使用for range时,不能通过直接修改遍历所使用的变量,因为range赋值得到的变量是一份副本,并不是引用。例如:for k,v:= range s
,只能通过修改s[k],而不能直接修改v。
删除元素
截取需要留下部分的,在赋值给原切片即可。
1 | var s = []int{1, 2, 3, 4} |
切片拷贝
引用拷贝
直接使用=
操作符赋值的拷贝,新切片得到的是引用拷贝,原切片与新切片所指向的底层数组为同一个数组
1 | s1 := make([]int, 3) //[0 0 0] |
值拷贝
如果我们需要拷贝一个值相同的切片,则我们需要用到copy()
函数,该函数可以将原数组切片复制到新的数组切片。
1 | copy(newSlice, oldSlice) |
如果两个数组切片不一样大,则会按照较小的一个数组切片的元素个数进行赋值。
1 | s1 := []int{1, 2, 3, 4, 5} |