Golang基础学习: array和slice对比和使用

前言

在golang中,常见的序列型数据类型有array和slice这两种,但array因为其固定长度的限制,在实际使用中用得不多,slice则更为常用。下面简单介绍和对比一下这两种相似却又有很多差异的数据类型。

Array:

概念:

在golang中,数组由相同类型的元素组成的具有固定长度的一种序列型复合数据类型。

声明和使用:
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
package main

import "fmt"

/*
数组: array在go里是固定长度、每个元素类型必须相同的,因此使用较少,slice使用反而较多
*/

func main() {
var a [3]int // 3个整数的数组
fmt.Println(a) // [0 0 0]
fmt.Println(a[0])
for i, v := range a {
fmt.Printf("%d %d\n", i, v) // 打印索引和元素
}

for _, v2 := range a {
fmt.Printf("%d\n", v2) // 打印元素
}

// 默认情况下,数组创建后元素都是零值,可以使用数组字面量来初始化一个数组的元素
var q [3]int = [3]int{1, 2, 3}
fmt.Println(q) // [1 2 3]

var q2 [3]int = [3]int{1, 2}
fmt.Println(q2) // [1 2 0]

var q3 = [3]int{1, 2, 3}
fmt.Println(q3) // 缩写用这个方式比较简洁

/*
数组长度是数组类型的一部分,所以q4和q5是不同的类型.
数组的长度必须是常量表达式,因此数组长度在声明时必须确定
*/
q4 := [...]int{1, 2, 3, 4}
q5 := [...]int{1, 2, 3, 4, 5}
fmt.Printf("Type: %T\n", q4) // Type: [4]int
fmt.Printf("Type: %T\n", q5) // Type: [5]int
//q4 = q5 // 编译错误,赋值失败,不同类型不能赋值
}
总结

在golang中array的长度是固定的,声明时必须以数值形式明确指定或者以’…’形式间接指定元素长度,且不同的长度的array类型不同不能转换,因此使用场景有限。

Slice

概念
在golang中,数组由相同类型的元素组成的可变长度的一种序列型复合数据类型。在理解上来说,slice和array唯一的区别是长度可变
声明和使用
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package main

import (
"bytes"
"fmt"
)

/*
slice数据类型在声明时如果不指定len/cap,则slice的cap/len值默认以声明时的数组的长度为准。
len: 长度。slice包含的元素的个数,可变
cap: 容量。slice当前可接纳的元素的个数,根据了len值动态变化
*/

func main() {
// 声明方式1
var b []int // 声明一个空slice,默认len和cap的值都为0。声明长度为0的array: var b [0]int,这两者声明方式非常相似
fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(b), cap(b), b) // Slice a , length: 0 cap: 0 content: []

// 声明方式2
a := []int{1, 2, 3, 4, 5}
fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(a), cap(a), a) // Slice a , length: 5 cap: 5 content: [1 2 3 4 5]
a = append(a, 6) // 追加元素
fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(a), cap(a), a) // Slice a , length: 6 cap: 10 content: [1 2 3 4 5 6]

// 声明方式3
c := make([]int, 5, 10) // make(type, len[, cap])
fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(c), cap(c), c) // Slice a , length: 5 cap: 10 content: [0 0 0 0 0]

// copy方法
d := []int{1, 2}
e := []int{3, 4, 5, 6}
copy(e, d) // copy(to,from) ,第一个参数是目标slice,第二个参数是源slice
fmt.Println(d, e) // [1 2] [1 2 5 6]

f := []int{3, 4, 5, 6}
copy(d, f) // copy(to,from) ,第一个参数是目标slice,第二个参数是源slice
fmt.Println(d, f) // [3 4] [3 4 5 6]

// slice的比较
g := []string{"a", "b"}
h := []string{"a", "b", "c"}
fmt.Println(g != nil) // true
//fmt.Println(g == h) // 这里会编译错误,slice没有==比较方法

// 只能自定义方法来比较两个slice,例如下方自定义的equal方法:
fmt.Println(equal(g, h))

// 检查slice是否为空
fmt.Println(len(g) != 0)
fmt.Println(g != nil) // 不推荐用这个方法,因为即使g != nil,g也有可能是空值


/*
------------------------------------Slice增加/删除/反转元素/转换格式等常用操作------------------------------------

*/

// 尾部操作
j := []int{1, 2, 3, 4, 5}
fmt.Println(append(j, 6)) // [1 2 3 4 5 6] 尾部插入
top := j[len(j)-1]
fmt.Println(top) // 5 获取尾部(栈顶)元素
j = j[:len(j)-1] // 弹出尾部元素
fmt.Println(j) // [1 2 3 4]

// slice中间移除/插入操作,没有自带函数,需要自己写方法
j = sliceRemove(j, 2)
fmt.Println(j) // [1 2 4]
j = sliceInsert(j, 2, 3)
fmt.Println(j) // [1 2 3 4]

// 反转元素
j = sliceReverse(j)
fmt.Println(j) // [4 3 2 1]

// 转换为字符串格式
fmt.Println(intsToString([]int{1, 2, 3})) // [1 & 2 & 3]
}

func sliceRemove(slice []int, i int) []int {
// 移除slice的指定索引位置的元素
copy(slice[i:], slice[i+1:])
return slice[:len(slice)-1]
}

func sliceInsert(slice []int, i int, n int) []int {
// 将元素插入到slice的指定索引位置
sub := []int{n}
for _, e := range slice[i:] {
sub = append(sub, e)
}
copy(slice[i:], sub)
slice = append(slice, sub[len(sub)-1])
return slice
}

func sliceReverse(slice []int) []int {
// 反转slice
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
slice[i], slice[j] = slice[j], slice[i]
}
return slice
}

func equal(x, y []string) bool {
if len(x) != len(y) {
return false
}
flag := true
for i, e := range x {
if e != y[i] {
flag = false
break
}
}
return flag
}

func intsToString(values []int) string {
var buf bytes.Buffer
buf.WriteByte('[')
for i, v := range values {
if i > 0 {
buf.WriteString(" & ")
}
//fmt.Println(v)
fmt.Fprintf(&buf, "%d", v) //输出重定向
}
buf.WriteByte(']')
return buf.String()
}
Slice说明

1.声明一个空slice,默认len和cap的值都为0。声明长度为0的array的方式: var b [0]int,这两者声明方式非常相似
2.slice在新增元素的时候,如果len超过cap,会动态扩容cap,根据网上查到的扩容函数的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func growslice(et *_type, old slice, cap int) slice {

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
...
}

可概括扩容策略如下:
当cap小于len时启动扩容,若cap < 1024,则申请新的内存cap翻倍,若大于2014,则cap提升1/4。(这与python的list扩容策略很相似)

3.使用make方式声明可以指定cap以直接申请一块连续的内存空间,避免频繁地改变cap容量而带来额外的开销。

4.copy函数复制slice时,假设源slice长度为x,目标slice长度为y,则覆盖策略为:
如果 x<y,则用源slice的所有元素逐个覆盖目标slice的前x个元素;
如果 x>y,则用源slice前y个元素逐个覆盖目标slice的所有元素。

赏一瓶快乐回宅水吧~
-------------本文结束感谢您的阅读-------------