七叶笔记 » golang编程 » Golang之Slice和Map

Golang之Slice和Map

写在前面:

本篇除了介绍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函数,可以通过查看源码文档,同时还可以直接点进去查看源码

make的官方定义

相关文章