Z.S.K.'s Records

golang学习(数组及slice常用操作)

这篇主要学习下golang中的数组及slice(切片)及常规操作, 主要是slice, 但是并不会涉及slice的底层原理, 这个范围比较大, 有必要单独拎出来.

数组(array)

定义

数组是内置(build-in)类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度, 数组的大小是类型的一部分。因此[5]int和[25]int是不同的类型

数组的大小必须是常量表达式, 也就是说长度需要在编译阶段能够确认.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 数组有如下几种声明方式:
func ArrayDemo() {
aRray := [...]int{1, 3, 5, 7} //如果不填写个数则会由编译器自动算出
bRray := [2]int{1, 2} //如果指定数据个数,则元素个数不能超过该值
cRray := [3]int{1:10, 2:20} //这里通过索引直接指定值
dRray := [3]int{1, 2} //在初始化时没有指定初值的元素将会赋值为其元素类型的默认值
eRray := [2]int{1, 2, 3} //报错, 元素长度大于2
// var fRray []int 初始值为[], 注意,这个是slice

fmt.Println(aRray, bRray, cRray, dRray)
}
// 输出
[1 3 5 7] [1 2] [0 10 20] [1 2 0]

简单来说就是, 在定义数组时需要为数组指定长度, 或者使用[…], 如果未指定的即为slice

访问

1
2
3
aRray := [...]int{1, 3, 5, 7}
aRray[0] // 1
aRray[5] // out of range

比较

数组的大小是类型的一部分。因此[5]int和[25]int是不同的类型,只有在当数据的长度及类型完全一样时,该数据组才能比较

1
2
3
4
5
6
7
8
9
aRray := [...]int{1, 3, 5, 7}
bRray := [...]int{1, 2, 3, 4}
cRray := [4]int{1, 3, 5, 7}
dRray := [4]string{"1", "3", "5", "7"}
fmt.Println(aRray == bRray) // false
fmt.Println(aRray == cRray) //true
fmt.Println(aRray == dRray) //invalid operation: eRray == fRray (mismatched types [4]int and [4]string)
fmt.Println(aRray == bRray) //invalid operation: aRray == bRray (mismatched types [4]int and [2]int)
fmt.Printf("%p, %p", &aRray, &cRray) // 虽然两个数组相等, 但是数组的地址是不一样的

只有在当数组的长度及类型及元素值完全一样时,该数组才相等, 注意,相等并不相同.

遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
aRray := [...]int{1, 3, 5, 7}
// for
l := len(array)
for i := 0; i < l; i++ {
fmt.Println(array[i])
}

// for range
for k, v := range aRray {
fmt.Printf("k=%d, v=%d\n", k, v)
}

// 这里需要注意的是, 如果把数组做为参数传递给函数, 如果不使用slice, 则在传递时需要明确数组的长度及类型处理.
// 当然一般都不会直接传递数组

slice

slice表示一个拥有相同类型元素的可变长度的序列,slice是一种轻量的数据结构, 可以用来访问数组的部分或者全部元素,而这个元素称为slice的底层数组, 这里先只学习slice的常用操作,slice 底层结构原理会单独记录.

现在可以简单这样理解: slice是底层数组的一层view.

**slice是由三部分组成: 第1个参数是指向底层数组的指针(ptr),这个指针指向真正存储数据的块, 第2个参数是长度(len), 第三个参数是容量(cap)

定义

1
2
3
4
5
// 通过字面量创建
var aSlice = []int{1, 2, 3} //[1 2 3]
bSlice := []string{"a", "b"} // ["a" "b"]
// 通过make函数创建, 第一个参数是类型, 第2个参数是长度, 第三个长度是容量
cSlice := make([]int, 2, 10) // [0 0]

访问

1
2
3
4
// 下标是从0开始 ,按下标访问
aSlice = []int{1, 2, 3}
aSlice[0] // 1
aSlice[10] // 运行时错误: panic: runtime error: index out of range [2] with length

遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
// slice的遍历方法写数组是一样的

aSlice := []int{1, 2, 3}

l := len(aSlice)
for i := 0; i < l; i++ {
fmt.Println(aSlice[i])
}

// for range
for k, v := range aSlice {
fmt.Printf("k=%d, v=%d\n", k, v)
}

方法

1
2
3
4
eSlice := [10]int{1, 2, 3, 4, 5} // [1 2 3 4 5 0 0 0 0 0]
hSlice := eSlice[:]
len(hSlice) // 10
cap(hSlice) // 10
Reslice
1
2
3
4
5
6
7
8
9
// 定义一个数组
eSlice := [10]int{1, 2, 3, 4, 5} // [1 2 3 4 5 0 0 0 0 0]
// slice
fSlice := eSlice[2:4] // [3 4]
// 越界访问
gSlice := fSlice[3] // index out of range [3] with length 2
// reslice
gSlice := fSlice[1:5] // [4 5 0 0]
// gSlice := fSlice[1:10] // slice bounds out of range [:10] with capacity 8

gSlice := fSlice[1:5]为何会输出[4 5 0 0 ]呢, gSlice的值为[3 4 ], 压根就没有[1: 5],那它为何没有提示越界访问呢?这个其实就是对slice是底层数组的一层view的说明

slice是支持向后扩展的, eSlice其实就是fSlice跟gSlice的底层数组, 只要不超过底层数组的cap,就可以反应在slice上,这也是fSlice[1:5]不报错的原因

fSlice[1:10]是由于gSlice本身有了2个元素, 整个只有8个元素,因此越界.

append

向slice追加元素的时候需要注意存在修改底层数组及扩容的问题.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 修改底层数组	
eSlice := [10]int{1, 2, 3, 4, 5}
hSlice := eSlice[3:5]
fmt.Println(eSlice, hSlice, len(hSlice), cap(hSlice)) //[1 2 3 4 5 0 0 0 0 0] [4 5] 2 7
fmt.Printf("%p\n", &hSlice) //0xc0000c0020

iSlice := append(hSlice, 100) // iSlice修改了底层数组
fmt.Println(eSlice, iSlice) //[1 2 3 4 5 100 0 0 0 0] [4 5 100]
fmt.Printf("%p\n", &iSlice) //0xc0000cc000

// 从这里可以看到eSlice的第5个元素被修改成了100, 上面都没有直接操作eSlice,为何值会变呢?
// 原因就在于hSlice与iSlice都是以eSlice为底层数组, 在对hSlice做追加操作时没有超过eSlice的cap大小, 因 此修改都会反应在eSlice上.

// 扩容
hSlice = append(hSlice, 11, 12, 13, 14, 15, 16, 17, 18)
fmt.Println(eSlice, hSlice) //[1 2 3 4 5 100 0 0 0 0] [4 5 11 12 13 14 15 16 17 18]
fmt.Printf("%p\n", &hSlice) //0xc0000c0020
// 如果追加的元素超过底层数组的cap大小,就会创建一个更大的底层数组,因此在追加那么多元素之后会发现eSlice没 有变化, 因为此是hSlice的底层数组已不再是eSlice, 对hSlice的修改不会影响eSlice.

// 从slice头追加
// 由于在夈追加会引起后面的元素向后移动,因此效率较差.
hSlice = append([]int{0}, hSlice...)
delete/insert/replace
1
2
3
4
5
6
7
8
9
// 删除hSlice第二个元素5
hSlice := []int{0, 4, 5, 11, 12, 13, 14, 15, 16, 17, 18}
hSlice = append(hSlice[:2], hSlice[2+1:]...)

// 在hSlice的第3个元素后插入10
hSlice = append(hSlice[:3], append([]int{10}, hSlice[3:]...)...)

// 把hSlice的第3个元素替换为10
hSlice = append(hSlice[:3], append([]int{10}, hSlice[4:]...)...)
copy

内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 常规的赋值
hSlice := []int{4, 5, 11, 12, 13, 14, 15, 16, 17, 18}
jSlice := hSlice
hSlice[5] = 1000
fmt.Println(hSlice, jSlice) // [4 5 11 12 13 1000 15 16 17 18] [4 5 11 12 13 1000 15 16 17 18]
fmt.Printf("%p, %p\n", &hSlice, &jSlice) //0xc0000ba020, 0xc00000c0c0

// 可以看到, jSlice是hSlice的一个copy, 两者虽然地址是不一样, 修改其中一个的元素,都会影响另一个
//这也是因为slice的数据结构的第一个参数是个数据块指针, jSlice与hSlice共享这个数据块指针, 因此会相互影响

jSlice = append(jSlice, 20000)
fmt.Println(hSlice, jSlice) // [4 5 11 12 13 1000 15 16 17 18] [4 5 11 12 13 1000 15 16 17 18 20000]
fmt.Println(hSlice[10], jSlice[10]) // panic: runtime error: index out of range [10] with length 10
// 虽然jSlice与hSlice共享这个数据块指针, 但两者的len及cap都是各自的, 在jSlice追加了一个元素之后, len(jSlice)及cap(jSlice)发生了变化, 但这个变化并不会影响hSlice, 因此hSlice不存在hSlice[10]

// copy() 返回实际发生复制的元素个数, destSlice必须分配过空间, 以小的slice长度为准进行copy
// copy( destSlice, srcSlice []T) int
hSlice := []int{4, 5, 11, 12, 13, 1000, 15, 16, 17, 18, 2000}
jSlice := make([]int, 1)
n := copy(jSlice, hSlice) // n = 1, jslice = [4]
kSlice := make([]int, len(hSlice))
n := copy(jSlice, hSlice) // n = 11, jslice = [4 5 11 12 13 1000 15 16 17 18 20000]
lSlice := make([]int, 15)
n := copy(jSlice, hSlice) // n = 11, jslice = [4 5 11 12 13 1000 15 16 17 18 20000 0 0 0 0 0]

上面所有的例子都跟slice的底层数据结构有很大的关系, 后续会对slice底层原理写一个详文.

参考文章:

转载请注明出处https://izsk.me

Z.S.K. wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?