写在前面:
本篇除了介绍Slice和Map外,还会介绍数组类型以及初始化的一些方法,单独吧Slice和Map拿出来是因为开发中几乎每个函数都会遇到它们,所以需要重点了解。
0x01 — 数组
数组在Golang中不经常用到,用到数组的地方一般都会用slice,不过slice的基础还是一个数组,所以我们还是要了解数组。
首先是定义方法:
package main
import "testing"
// 数组定义
func TestArray(t *testing.T){
// 定义一个int数组,长度为3,
// 在未初始化时数组默认值是根据定义数组的类型有关系,
// 比如int都是0,字符串都是空字符串"",
// 下面的ta数组中第三位未定义
var ta [3]int
// 赋值
ta[0] = 1
ta[1] = 2
t.Log("数组ta: ", ta)
//ta[4] = 3 超出下标抛出异常:Invalid array index '4' (out of bounds for the 3-element array)
// 定义一个int数组,长度为5,并进行赋值
ta1 := [5]int{234,333,444,555,1231}
t.Log("数组ta1: ", ta1)
// 通过len函数获取数组长度
t.Log("数组ta1的长度: ", len(ta1))
// 通过for循环遍历出数组,range会返回两个值,索引和值
for index, d := range ta1 {
t.Logf("索引: %d 的值 是: %d", index, d)
}
//如果不确定数组长度可以使用...表示
var ta2 = [...]int{11,2,2,3,4,5,232}
// 注意,数组在初始化时长度就固定了,无法修改长度,同时超出
// 索引范围的访问都是失效的
// ta2[19] = 99 //Invalid array index '19' (out of bounds for the 7-element array)
t.Log("数组ta2:", ta2)
t.Log("数组ta2的长度:", len(ta2))
}
输出:
=== RUN TestArray
slice_map_test.go:15: 数组ta: [1 2 0]
slice_map_test.go:21: 数组ta1: [234 333 444 555 1231]
slice_map_test.go:24: 数组ta1的长度: 5
slice_map_test.go:28: 索引: 0 的值 是: 234
slice_map_test.go:28: 索引: 1 的值 是: 333
slice_map_test.go:28: 索引: 2 的值 是: 444
slice_map_test.go:28: 索引: 3 的值 是: 555
slice_map_test.go:28: 索引: 4 的值 是: 1231
slice_map_test.go:33: 数组ta2: [11 2 2 3 4 5 232]
slice_map_test.go:34: 数组ta2的长度: 7
--- PASS: TestArray (0.00s)
PASS
注意事项:
数组的长度是固定的,不可修改
定义多维数组是可通过var ta3 = [2][3]int{},访问也需要通过下标多级访问ta3[1][2]。
0x03 — Slice(切片)
GoLang中切片可以说是数组的抽象,基于数组基础,在之上建立可变长度的索引指针。
切片的声明方式和数组不同的一点就是不需要指定长度或… 定义如下:
var identifier []type
切片的一系列操作:
package main
import (
"math/rand"
"sort"
"testing"
)
// 切片
func TestSlice(t *testing.T) {
//定义切片
var ts []int
// 切片由于未初始化,长度为0,所以无法赋值,此错误编译简短无法抛出,
// 运行时会抛出异常:panic: runtime error: index out of range [0] with length 0
//ts[0] = 1
t.Log("切片ts: ", ts)
// 通过下面两种方式初始化一个切片,
// make方式是比较常用的初始化方式map也会通过make初始化
var ts1 []int= make([]int, 5) // int型数组长度为5,默认值为0
//var ts1 = make([]int, 3) // 类型[]int可以省略,Golang会自动推算出类型
ts2 := []int{4, 5, 9} // 直接定义并赋值
t.Logf("切片ts1:%v, 长度:%d ", ts1, len(ts1))
t.Logf("切片ts2:%v, 长度:%d ", ts2, len(ts2))
// 数组长度通过append方式增加也可以通过切片索引方式取其中的部分
ts2 = append(ts2,100, 200, 300) // 向数组中增加三个元素
t.Logf("(append)切片ts2:%v, 长度:%d ", ts2, len(ts2))
ts2 = ts2[2:4] // 取2,3两个数据组成ts2
t.Logf("([2:4])切片ts2:%v, 长度:%d ", ts2, len(ts2))
// 通过解构方式可以合并两个数组
ts2 = append(ts2, ts1...)
t.Logf("(将ts1合并到ts2)切片ts2:%v, 长度:%d ", ts2, len(ts2))
// Copy slice
ts3 := make([]int, 10)
for i := 0; i < 10; i++ {
ts3[i] = int(rand.Int31n(1000)) // 随机生成1000内的整数
}
t.Logf("切片ts3:%v, 长度:%d ", ts3, len(ts3))
sort.Ints(ts3) // 对ts3进行排序
ts3c := make([]int, 5)
copy(ts3c, ts3[:5]) // 将ts3的前5个数copy到ts3c中
t.Logf("切片ts3c:%v, 长度:%d ", ts3c, len(ts3c))
ts3c1 := make([]int, 5)
copy(ts3c1[1:], ts3[:4]) // 将ts3的前4个数copy到ts3c1中的后4个位置
t.Logf("切片ts3c1:%v, 长度:%d ", ts3c1, len(ts3c1))
// 创建二维切片,子切片里的类型可以省略,最后一个元素的逗号不可省略
ts4 := [][]int{
[]int{1,2,3},
[]int{4,5,6},
{7,8,9},
}
t.Logf("切片ts4:%v, 长度:%d ", ts4, len(ts4))
}
输出:
=== RUN TestSlice
slice_map_test.go:53: 切片ts: []
slice_map_test.go:60: 切片ts1:[0 0 0 0 0], 长度:5
slice_map_test.go:61: 切片ts2:[4 5 9], 长度:3
slice_map_test.go:65: (append)切片ts2:[4 5 9 100 200 300], 长度:6
slice_map_test.go:67: ([2:4])切片ts2:[9 100], 长度:2
slice_map_test.go:70: (将ts1合并到ts2)切片ts2:[9 100 0 0 0 0 0], 长度:7
slice_map_test.go:77: 切片ts3:[81 887 847 59 81 318 425 540 456 300], 长度:10
slice_map_test.go:81: 切片ts3c:[59 81 81 300 318], 长度:5
slice_map_test.go:84: 切片ts3c1:[0 59 81 81 300], 长度:5
slice_map_test.go:92: 切片ts4:[[1 2 3] [4 5 6] [7 8 9]], 长度:3
--- PASS: TestSlice (0.00s)
PASS
切片在初始化时通过make可以指定长度和容量(capacity),容量可选,默认和长度一致。
copy slice时注意接收侧的优先级要高于被拷贝的元素数量
0x04 — Map(映射,字典,hashMap)
Golang中map是一种无序键值对,可以通过key来获取key对应的value,map可以通过循环遍历出每对元素。
package main
import "testing"
// map
func TestMap(t *testing.T) {
//定义Map
var tm map[string]int
// 这里只定义了tm,如果不初始化 map,那么就会创建一个 nil map
// nil map 不能用来存放键值对,直接赋值会报错:assignment to entry in nil map
//tm["ming"] = 12
//var tm = map[string]int{"wang":11, "niu": 23} // 可以在定义时进行初始化赋值
// 初始化map,起始容量为5的map,如果你能知道你的Map有多少个键,定义时指定一个初始大小可以获得一定的性能提升
tm = make(map[string]int, 2)
t.Logf("Map tm:%v, 长度:%d", tm, len(tm))
tm["ming"] = 12
tm["hua"] = 23
tm["ca"] = 44
t.Logf("Map tm:%v, 长度:%d", tm, len(tm))
// 获取map中元素需要设置两个接收遍历,第二个返回值会返回是否存在
ming, ok := tm["ming"]
t.Log("Map item ming:", ming, ok)
// 如果发现key值不存在,则返回value的默认值,同时第二个返回值为false
// 如果不想使用第二个值,可以将第二个值改为_,默认会被抛弃同时不会编译报错
// ming1, _ := tm["ming1"]
ming1, ok1 := tm["ming1"]
t.Log("Map item ming1:", ming1, ok1)
//遍历map
for k := range tm {
t.Logf("Map key:%s, value:%d", k, tm[k])
}
// 删除元素
delete(tm, "ming")
delete(tm, "ming")
t.Log("Map detete tm:", tm)
}
输出:
=== RUN TestMap
slice_map_test.go:16: Map tm:map[], 长度:0
slice_map_test.go:20: Map tm:map[ca:44 hua:23 ming:12], 长度:3
slice_map_test.go:24: Map item ming: 12 true
slice_map_test.go:29: Map item ming1: 0 false
slice_map_test.go:33: Map key:ming, value:12
slice_map_test.go:33: Map key:hua, value:23
slice_map_test.go:33: Map key:ca, value:44
slice_map_test.go:38: Map detete tm: map[ca:44 hua:23]
--- PASS: TestMap (0.00s)
PASS
注意事项:
map一定要注意声明后需要初始化,不然无法接收key
map是无序的,每次遍历的得到顺序可能不同
0x05 — 初始化
在初始化Slice和Map时,以及后续初始化Chan,都需要用到make,make是内置的初始化函数,定义+分配内存,官方定义解释:
Slice: The size specifies the length. The capacity of the slice is
equal to its length. A second integer argument may be provided to
specify a different capacity; it must be no smaller than the
length. For example, make([]int, 0, 10) allocates an underlying array
of size 10 and returns a slice of length 0 and capacity 10 that is
backed by this underlying array.
Map: An empty map is allocated with enough space to hold the
specified number of elements. The size may be omitted, in which case
a small starting size is allocated.
Channel: The channel's buffer is initialized with the specified
buffer capacity. If zero, or the size is omitted, the channel is
unbuffered.
解释:
Slice:大小指定长度。片的容量等于它的长度。可以提供第二整数参数以指定不同的容量;必须不小于长度。
例如,make([]int,0,10)分配一个大小为10的基础数组,并返回由该基础数组支持的长度为0和容量为10的片。
map:为一个空map分配足够的空间来容纳指定数量的元素。可以省略该大小,在这种情况下分配一个小的起始大小。
通道:通道的缓冲区用指定的缓冲区容量初始化。如果为零,或者省略了大小,则不缓冲信道(缓存信道是队列类型)
make方法可以创建并初始,和new方法类似,不同之处在于make返回初始化后的内容,new返回的是指针。
对slice而言,除了指定元素,还可以指定长度和容量,长度表示当前初始化的数量,容量表示最大数量,超出索引将抛出异常。
对map而言,初始化时指定第二个参数,会暗示需要的长度,有助于空间快速分配和访问,但是这个参数不会控制map真正的长度。
0x06 — 总结
Slice和Map是使用频率很高的功能,在使用时注意初始化,同时在开发过程中如果熟悉方法变量,内置函数参数等,可以通过IDE的追溯功能找到源代码注解,看一遍大概理解的更深。比如make函数,可以通过查看源码文档,同时还可以直接点进去查看源码