2022-4-27 实习Day25

1、bugFix4-26测试问题全部检验测试解决完毕。–3小时 100%
2、进行交易时,杭州银行、总行目标账户数量太少,手动添加。–3小时 100%

Golang编程学习(part 20)

1、切片的基本介绍

① 切片的英文是slice
② 切片是数组的一个引用, 因此切片是引用类型, 在进行传递时, 遵守引用传递的机制
③ 切片的使用和数组类似, 遍历切片、访问切片的元素和求切片长度 len(slice) 都一样
④ 切片 的长度是可以变化的, 因此切片是一个可以动态变化的数组
⑤ 切片定义的基本语法:var 切片名 []类型 var a []int
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import "fmt"
func main() {
array := [...]int{1, 22, 33, 66, 99}

// 声明/定义一个切片
// slice就是切片名
// array[1:3]表示slice引用到array这个数组
// 引用array数组的起始下标为1, 最后下标为3(但是不包含3)
slice := array[1:3]

fmt.Println("array =", array)
fmt.Printf("slice 的元素是=%v,类型是%T\n", slice, slice)
fmt.Println("slice 的元素个数 =", len(slice))
fmt.Println("slice 的容量 =", cap(slice))

}

array = [1 22 33 66 99]
slice 的元素是=[22 33],类型是[]int
slice 的元素个数 = 2
slice 的容量 = 4

2、切片在内存中的形式(重要!!!)

1
2
3
4
5
6
7
8
slice := array[1:3] --> [   0x23545     |    2     |    4    ]
ptr len cap
|
\|/
|
数组: array --> [ 1 | 22 | 33 | 66 | 99 ]
0x23545

对上图的分析总结

① slice的确是一个引用类型
② slice从底层来说, 其实就是一个数据结构(struct 结构体)
1
2
3
4
5
6
7
type slice struct{
ptr *[2]int
len int
cap int
}

slice可以理解为一个对象, 有三个属性, ptr存的是第一个元素的地址, len存长度, cap存切片的最大容量

3、切片的使用

① 第一种方式:定义一个切片,然后让切片去引用一个已经创建好的数组,比如前面的案例就是这样的
1
2
3
4
5
6
slice := array[1:3]

fmt.Println("array =", array)
fmt.Printf("slice 的元素是=%v,类型是%T\n", slice, slice)
fmt.Println("slice 的元素个数 =", len(slice))
fmt.Println("slice 的容量 =", cap(slice))
② 第二种方式:通过make来创建切片
基本语法:var 切片名 []type = make([]type, len, cap)
参数说明:type:就是数据类型 len:实际大小 cap:指定切片容量【可选,如果写了要cap>len】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package main

import "fmt"

func main() {
var slice []float64 = make([]float64, 5, 10)
slice[1] = 10
slice[3] = 20
// 对于切片, 必须make使用
fmt.Println(slice)
fmt.Println("slice的size=", len(slice))
fmt.Println("slice的cap=", cap(slice))
fmt.Printf("slice的地址=%p\n\n", &slice[0])

slice = append(slice, 100)
fmt.Println(slice)
fmt.Println("slice的size=", len(slice))
fmt.Println("slice的cap=", cap(slice))
fmt.Printf("slice的地址=%p\n\n", &slice[0])

slice = append(slice, 200)
slice = append(slice, 300)
slice = append(slice, 400)
fmt.Println(slice)
fmt.Println("slice的size=", len(slice))
fmt.Println("slice的cap=", cap(slice))
fmt.Printf("slice的地址=%p\n\n", &slice[0])

slice = append(slice, 500)
fmt.Println(slice)
fmt.Println("slice的size=", len(slice))
fmt.Println("slice的cap=", cap(slice))
fmt.Printf("slice的地址=%p\n\n", &slice[0])

slice = append(slice, 600)
fmt.Println(slice)
fmt.Println("slice的size=", len(slice))
fmt.Println("slice的cap=", cap(slice))
fmt.Printf("slice的地址=%p\n\n", &slice[0])
}

[0 10 0 20 0]
slice的size= 5
slice的cap= 10
slice的地址=0xc0000b00f0

[0 10 0 20 0 100]
slice的size= 6
slice的cap= 10
slice的地址=0xc0000b00f0

[0 10 0 20 0 100 200 300 400]
slice的size= 9
slice的cap= 10
slice的地址=0xc0000b00f0

[0 10 0 20 0 100 200 300 400 500]
slice的size= 10
slice的cap= 10
slice的地址=0xc0000b00f0

[0 10 0 20 0 100 200 300 400 500 600]
slice的size= 11
slice的cap= 20
slice的地址=0xc0000d8000
1
2
3
4
5
6
7
8
9
10
11
对上述代码详解
1、slice可以理解为一个结构体,里面有三个变量【结构体是值类型,赋值给其他变量会拷贝一份】
ptr len cap
slice --> [ 0x2145 | 5 | 10 ]
|
|
\|/
[ 0 | 10 | 0 | 20 |...]
2、slice结构体中的ptr指向了一个数组, 通过slice中的属性就可以控制这种数组

3append()多次使用后一旦超过了cap容量大小,数组就会扩容(会发生数组中值的拷贝)
对上面代码小结
1)通过make方式创建切片可以指定切片的大小和容量
2) 如果没有给切片的各个元素赋值, 那么就会使用默认值 int, float =>0 string=>”” bool=>false
3)通过make方式创建的切片对应的数组是由make底层维护, 对外不可见, 即只能通过slice去访问各个元素
③ 第三种方式:定义一个切片, 直接就指定具体数组, 使用原理类似 make 方式
1
2
3
4
5
6
7
8
9
10
11
// 直接就指定具体数组, 使用原理类似 make 的方式
var slice = []string{"tom", "jack", "mary"}

fmt.Println("slice=", slice)
fmt.Println("slice size=", len(slice))
fmt.Println("slice cap=", cap(slice))

slice= [tom jack mary]
slice size= 3
slice cap= 3

④ 方式1和2的区别(面试)
方式1是直接引用数组, 这个数组是事先存在的, 程序员是可见的
方式2是通过make来创建切片, make也会创建一个数组, 是由切片在底层进行维护, 程序员是看不见的。

4、切片的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1、使用常规的for循环遍历切片
var arr = [...]int{10, 20, 30, 40, 50}
slice := arr[1:4] //20 30 40
for i:=0; i<len(slice); i++{
fmt.Printf("slice[%v]=%v", i, slice[i])
}

slice[0]=20
slice[1]=30
slice[2]=40

// 2、使用for-range方式遍历切片
for i,v := range slice {
fmt.Printf("i=%v v=%v \n", i, v)
}

i=0 v=20
i=1 v=30
i=2 v=40

5、切片使用的注意事项和细节讨论

① 切片初始化时 var slice = arr[startIndex : endIndex]
说明:从arr数组下标为startIndex, 取到下标为endIndex的元素(不含 arr[endIndex])
② 切片初始化时,仍然不能越界。范围在 [0 - len(arr)] 之间, 但是可以动态增长
var slice = arr[0 : end] 可以简写 var slice = arr[:end]
var slice = arr[start : len(arr)] 可以简写 var slice = arr[start : ]
var slice = arr[0 : len(arr)] 可以简写 var slice = arr[ : ]
③ cap是一个内置函数, 用于统计切片的容量, 即最大可以存放多少个元素
④ 切片定义完后, 还不能使用, 因为本身是一个空的, 需要让其引用到一个数组, 或者make一个空间供切片来使用
⑤ 切片可以继续切片
1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = [...]int{10, 20, 30, 40, 50}
slice := arr[1:4]
slice2 := slice[1 : 2]
fmt.Printf("%p\n", &slice[0])
fmt.Println(cap(slice))
fmt.Printf("%p\n", &slice2[0])
fmt.Println(cap(slice2))

0xc00000a3f8
4
0xc00000a400
3

⑥ 使用append内置函数, 可以对切片进行动态追加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

func main() {
var slice = []int{100, 200, 300}
fmt.Println(len(slice), cap(slice))
fmt.Printf("切片地址=%p 切片第一个元素地址=%p \n", &slice, &slice[0])

slice = append(slice, 400, 500, 600)
fmt.Printf("切片地址=%p 切片第一个元素地址=%p \n", &slice, &slice[0])
/*
append 内置函数将元素附加到切片的末尾。如果它有足够的容量,则重新划分目标以容纳新元素。
如果没有,将分配一个新的底层数组。 Append 返回更新后的切片。因此有必要将
append 的结果存储在保存切片本身的变量中:
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
作为一种特殊情况,将字符串附加到字节切片是合法的,如下所示:
slice = append([]byte("hello"), "world"...)
*/
slice = append(slice, slice...)
fmt.Printf("切片地址=%p 切片第一个元素地址=%p \n", &slice, &slice[0])
}

3 3
切片地址=0xc000004078 切片第一个元素地址=0xc00000c150
切片地址=0xc000004078 切片第一个元素地址=0xc00000a3f0
切片地址=0xc000004078 切片第一个元素地址=0xc000070120

⑦ 切片 append 操作的底层原理分析
1)切片 append操作的本质就是对数组扩容(只有在切片容量不够用时才扩容)
2)go底层会创建一下新的数组 newArr(扩容后大小)
3)将slice原来包含的元素拷贝到新的数组newArr,slice重新引用到newArr
4)注意newArr是在底层来维护的,程序员不可见
⑧ 切片的拷贝操作:切片使用copy内置函数完成拷贝,举例:
1
2
3
4
5
6
7
8
9
10
11
var slice = []int{1, 2, 3, 4, 5, 6}
var slice1 = make([]int, 10)
copy(slice1, slice)
fmt.Println("slice=", slice)
fmt.Println("slice1=", slice1)

slice= [1 2 3 4 5 6]
slice1= [1 2 3 4 5 6 0 0 0 0]

// copy(des,src) 参数的数据类型是切片
// 按照上面代码来看, slice和slice1的数据空间是独立的, 相互不影响, 也就是说 slice[0]=99, slice1[0]仍然是1
⑨ 分析下面代码
1
2
3
4
5
var a []int = []int{1, 2, 3, 4, 5}
var slice = make([]int, 1)
fmt.Println(slice) //[0]
copy(slice, a)
fmt.Println(slice) //[1]