Go学习笔记(9)Go容器类型——数组&切片&map

x33g5p2x  于2022-03-06 转载在 其他  
字(4.0k)|赞(0)|评价(0)|浏览(562)

写在前面

前面的文章介绍了Go的一些基本类型,本文开始涉及Go的一些容器类型,它们都是可以包含多个元素的数据结构,如数组、切片、map

数组

数组是具有相同类型且长度固定的一组元素集合,定义的格式:var 数组名 [数组长度]数组元素类型,下面声明一个长度为5的int型数组arr

var arr [5]int
  • 数组元素可以是任意的原始类型,例如整型、字符串等,一个数组内所有元素的类型必须是相同的
  • 数组的长度必须是一个常量表达式,且是一个非负整数,特别注意数组的长度也是数组类型的一部分,因此不同长度的数组是不同的类型。数组的长度在声明时就要给出。获取数组长度用内置函数len(arr)
  • 当声明数组时所有的元素都会被初始化为默认的类型零值,我们也可以在声明数组的同时进行初始化
var arr = [5]int{1, 2, 3, 4, 5}
a := [3]int{1, 2, 3}

还可以在初始化时仅初始化指定元素

b := [3]int{1 : 2, 2 : 3}  //初始化索引为1的值为2,索引为2的值为3
  • 如果声明数组时不想直接写长度,可以用...代替,编译器会自动生成满足最低长度要求的数组
c := [...]int{4 : 1}        //因为我们指定了索引为4的值为1,因此数值至少包含5个元素,这里将生成长度为5的数组
  • 通过for循环来遍历操作数组
var arr [5]int
for i := 0; i < len(arr); i++ {
	arr[i] = i * 2
}
for i, v := range arr {
	fmt.Println("index: ", i, "value: ", v)
}
  • 数组是一种值类型(不像C/C++那样是指向首元素的指针),可以通过new来创建数组,不过它返回的是指向该数组的指针
d := new([4]int)
fmt.Println(d)            //&[0,0,0,0]
fmt.Println(*d)           //[0,0,0,0]

注意区分指向数组的指针和指针数组

e := [5]int{1, 2, 3, 4, 5}
var p *[5]int = &e              //p为指向数组e的指针 &[1,2,3,4,5]
x, y := 1, 2
z := [2]*int{&x,&y}            //z是一个指针数组
  • 多维数组
h := [2][3]int{
	{1, 1, 1},
	{2, 2, 2}
}
  • 数组可以使用==!=来比较
f := [2]int{1, 2}
g := [2]int{1, 2}
fmt.Println(f == g)                 //true

切片

切片(slice)是对某个数组的一段连续片段的引用(该数组我们称为相关数组,该片段可以是整个数组,也可以是数组中的某一段),因此切片是一个引用类型。切片底层实现是数组,它与数组的关系如下图:

    多个slice可以指向同一个底层相关数组,此时其中一个值改变会影响全部的值

  • 切片是一个可变长的数组,它的长度可以动态修改,可以用len()来获取切片的长度。切片还有另外一个属性容量,表示切片可以达到的最大长度,可以用cap()来获取。切片的容量等于切片的长度+相关数组中在切片之后剩下的的长度。所以切片的长度小于等于切片的容量。

  • 切片的容量是预先分配的,如果在运行的过程中,切片的长度超过了原来分配的容量,则会重新分配一个空间更大的数组,然后将值都拷贝过去

  • 切片的声明格式:var 切片名 []元素类型,这里不需要指定长度,如果是数组则必须指定长度或者用...替代

  • 切片可以通过底层相关数组切取,也可以用make直接创建,还可以通过已有的切片来生成

  • 通过数组生成

arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s1 := arr[2:5]           //切取数组arr索引从2~4这一段(即[3,4,5]),注意索引值不包括5
s2 := arr[:]               //切片为整个arr数组
s3 := arr[5:]             //切取从索引5开始到末尾,即[6,7,8,9,10]
  • 通过make创建
    格式:make([]type, len, cap),其中cap可以省略,省略则默认和len相同
s4 := make([]int, 3, 20)
s5 := make([]int, 3)
  • 通过切片生成,这种方式有时也被称之未称未切片重组reslice
sa := []int{1, 2, 3, 4, 5}
sb := sa[1:4]                    //[2,3,4],len:3,cap:4
sc := sb[1:4]                    //[3,4,5],len:3,cap:3

    需要注意的是,新的切片的索引不能超过原切片的容量,否则会引发编译错误,而不是重新分配数组

  • 在切片上追加元素(append
        如果要在切片上添加新的元素,可以使用append函数,用法为append(被追加元素的slice, 追加的元素...)
s1 := make([]int, 3, 6)
s1 = append(s1, 1, 2, 3)     //[0,0,0,1,2,3],还是返回原来的slice (没有超过原来cap)
s1 = append(s1, 1, 2, 3)     //[0,0,0,1,2,3,1,2,3],返回的是一个新的slice(已经超过了cap,重新分配底层数组)

    注意,如果追加后的长度未超过原slice的容量,则返回原始的slice,如果超过了,则重新分配空间更大的数组并拷贝原始数据

  • 拷贝slice
        可以使用copy函数将一个切片拷贝到另外一个切片中,用法copy(目标slice, 被拷贝的slice)
sc1 := []int{1, 2, 3, 4, 5}
sc2 := []int{6, 7, 8}
copy(sc1, sc2)
fmt.Println(sc1)                 //[6 7 8 4 5]
copy(sc2,sc1)
fmt.Println(sc2)                //[6,7,8]
copy(sc2[0:2], sc1[3:5])       //指定具体位置
fmt.Println(sc2)               //[4 5 8]

Map

Map是Go里面的键值对集合,由key-value对组成,给定key,可以快速定位到对应的value。也被称为字典、哈希表等

  • map中的key可以是任意能够用==或者!=操作符比较的类型,比如stringintfloat等,不能是函数、map、切片;value可以是任意类型
  • map的声明方式var map变量名 map[key类型]vlaue类型
var m map[int]string               //声明
m = map[int]string{1:"a", 2:"b"}  //初始化

    特别注意,map必须要初始化才能使用,即如果用上面这种方式,必须要有初始化的语句map1 = map[keyType]ValueType{},否则将报错。当然如果觉得太繁杂,可以使用下面的make语句来替代,更加简洁(主要是因为使用前需要先分配好内存空间给map,使用初始化语句或者make语句才能实现空间的分配)

  • map也属于引用类型,声明的时候不需要知道长度,可以动态增加,可以使用make来创建
var m1 map[int]string = make(map[int]string)
m2 := make(map[int]string)           //简要写法
m1[1] = "ok"                         //插入 (1 : ok)的key-value
m2[1] = "good"

    make创建还可以指定容量,make(map[key类型]value类型,cap)cap为容量,可以省略。超出容量会自动扩容,但为了性能还是尽量提供一个大概的初始值。

  • 获取指定key的value a:=m[1] (获取key为1的value)
  • 插入一个k-v对(或对指定key进行修改)m[1]="ok"
  • 判断键值对是否存在
        上面获取指定key的值map[key]还有另外一种用法:value,isPresent = map[key],即返回两个值,第二个值isPresent是布尔类型,如果该key存在,该值为true,且value为该key对应的值;如果key不存在,则isPresentfalse,且value为空值。
if value, ok := map1[key1];ok{
	fmt.Println(value)
}
  • 删除指定key的键值对delete(map1, key1)
  • 使用for-range对map进行遍历
map1 := make(map[int]string)
map1[1] = "a"
map1[2] = "b"
map1[3] = "c"
for key, value := range map1 {
	fmt.Println(key,value)
}

    如果指向获取key或者value,可以这么使用

for key := range map1 {
	fmt.Println(key)
}
for _, vlaue := range map1 {
	fmt.Println(value)
}

    需要注意的是,for中获得的key和value值都是副本,直接对这两个值进行修改并不会对原来的map有影响。需要用map[key]才能真正改变map中的值

  • map类型的切片:注意需要使用两次make,第一次分配切片,第二次分配切片中每个map元素
sm := make([]map[int]string, 5)
for i := range sm {
	sm[i] = make(map[int]string,1)
	sm[i][1]="ok"
}
  • 嵌套map的使用:即map中value也是map类型,这种情况同样需要注意每一个嵌套的map也需要进行make之后才能使用(初始化分配空间)
var m2 map[int]map[int]string
m2 = make(map[int]map[int]string)
m2[1] = make(map[int]string)     //value中嵌套的map也需要进行初始化
m2[1][1] = "ok1"

相关文章