在实际需求中,我们会把很多同一类型的元素放在一起,这就是集合。Go语言中,数组(Array)、切片(Slice)、映射(Map)这些都是集合类型,用于存放同一类元素。
Array数组
数组存放的是固定长度、相同类型的数据,而且这些存放的数据是连续的。所存放的数据类型没有限制,可以是整型、字符串甚至自定义。
数组声明
数组的声明和基础类型是一样的。类型名前加[]中括号,并设置好长度,就可以通过推测数组的类型。注意:[5]string 和 [4]string是不同的类型,因为长度也是数组类型的一部分。
array := [5]string{"a","b","c","d","e"}
array := [...]string{"a","b","c","d","e"}
array := [5]string{1:"b", 3:"d"}
声明了一个字符串数组,长度是5,所以其类型定义为[5]string,其中大括号中的元素用于初始化数组。另外,数组中的数据都是连续存放的,可以通过下标的方式访问,小标从0开始。
在定义数组的时候,数组的长度可以省略, 这时候Go语言会根据大括号{}中元素的个数推导出长度。省略数组长度的声明只适用于所有元素都被初始化的数组,如果只针对特定元素初始化的情况,就不适用了。没有初始化的索引,器默认值都是数组类型的了零值,也就是string类型的零值“”空字符串。
数组循环
使用传统for循环遍历数组,这种方式很繁琐,一般不使用,大部分情况下,使用for range这种Go语言的新型循环。对于数组,range表达式返回两个结果:
for i, v := range array {
fmt.Println("索引: %d, 值: %d", i, v)
}
- 第一个是数组的索引
- 第二个是数组的值
把返回的结果分别赋值给变量i和v,相比传统for循环,for range更简洁,如果返回的值用不到,可以使用占位符_ 丢弃。
注意:v 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值。因此要修改原集合中的值,需要使用索引的方式。
Slice切片
切片和数组类似,可以理解为动态数组。切片是基于数组实现的,它的底层就是一个数组。对数组任意分割,就可以得到一个切片。切片是一个具备三个字段的数据结构,分别是指向数组的指针data,长度len和容量cap。
基于数组生成切片
// 基于数组生成切片,包含索引start,不包含索引end
slice := array[start:end]
// 小技巧:切片表达式 中start和end索引都是可以省略的,如果省略start,那么start的值默认是0,如果省略end,那么end的默认值为数组的长度。
array[:4] <==> array[0:4]
array[1:] <==> array[1:5]
array[:] <==> array[0:5]
切片和数组一样,也可以通过索引定位元素。尽管切片底层用的也是数组,但是经过切片后,切片的索引范围改变了。
切片修改
array:=[5]string{"a","b","c","d","e"}
slice := array[2:5]
slice[1] = "f"
fmt.Println(array)
输出结果为:
[a b c f e]
数组对应的值已经被修改为f,所以也证明了基于数组的切盼,使用的底层数组还是原来的数组,一旦修改切片元素的值,那么底层数组对应的值也会被改变。
切片声明
除了可以从一个数组得到一个切片,还可以声明切片,比较简单的是使用make函数。
slice := make([]string, 4,8)
slice1 := []string{"a","b","c","d","e"}
上述示例代码声明了一个元素类型为string的切片,长度是4,容量是8,Go语言在内存上会分了一块容量为8的内存空间(容量是8),但是只有4个内存空间才有元素(长度是4),其他的内存空间处于空闲状态,当通过**append()**函数往切片中追加元素时,会追加到空闲的内存上,当切片的长度要超过容量的时候,会进行扩容。 需要注意的是:切片的容量不能比长度小。切片长度就是切片内元素的个数,容量是切片的空间。
切片不仅可以通过make函数声明,也可以通过字面量的方式声明和初始化,如上所示。可以看到,切片和数组的字面量初始化方式,差别就是中括号[]里的长度,有数字的是数组,没有数字的是切片。另外,通过字面量初始化的切片,长度和容量相同。
Append
我们可以通过内置的 append函数对一个切片追加元素,返回新的切片。append函数可以有三种操作,需要根据自己的实际需求进行选择,append会自动处理切片容量不足需要扩容的问题。
==小技巧:==在创建新切片的时候,最好要让切片的长度和容量一致,这样在追加操作的时候会生成新的底层数组,从而和原数组分离,就不会因为共用底层数组导致修改内容的时候影响多个切片。
// 追加一个元素
slice2 := append(slice1, "f")
// 追加多个元素
slice2 := append(slice1, "f", "g")
// 追加另一个切片
slice2 := append(slice1, slcie...)
切片元素循环
切片的循环和数组一样, 常用的也是for range 方式。在Go语言开发中,切片是使用最多的,尤其是作为函数的参数时,相比数组,通常会优先选择切片,因为它高效,内存占用小。
Map映射
在Go语言中,map是一个无序的K-V键值对集合,结构为map[K]V。其中K对应Key,V对应Value。map中所有的Key必须具有相同的类型,Value也一样,但是Key和Value的类型可以不同。另外,Key的类型必须支持**==比较运算符**,这样才可以判断是否存在,并保证Key的唯一。
Map声明初始化
创建一个map可以通过内置的 make 函数。如下所示:Key的类型为string, Value的类型为int。
testMap := make(map[string]int)
testMap1 := map[string]int{"zhangsi" : 10}
还可以通过字面量的方式创建map。如上所示。在创建map的同时添加键值对,如果不想初始化,使用空大括号{}即可,要注意的是,大括号一定不能省略。
Map获取和删除
map的操作和切片、数组差不多,都是通过[]操作符,只不过数组切片的[]是索引,而map[]中是Key。Go语言的map可以获取不存在的K-V键值对,如果Key不存在,返回的Value是该类型的零值。很多时候,我们需要先判断map中Key是否存在。
map的[]操作符可以返回两个值:
- 第一个值是对应的Value
- 第二个值标记该Key是否存在,如果存在,它的值是true
testMap := make(map[string]int)
testMap["zhangsan"] = 1
if v, ok := testMap["zhangsan"]; ok {
delete(testMap, "zhangsan")
}
delete函数由两个参数:第一个参数是map,第二个参数是要删除的键值对的Key。
遍历Map
map是一个键值对结合,它同样可以被遍历,在Go语言中,map的遍历依然使用for range循环。 对于map, for range 返回两个值:
- 第一个是map的Key
- 第二个是map的Value
for range map 时,可以使用一个值返回。使用一个值返回的时候,这个返回值默认是map的Key。
需要注意的是:map的遍历是无序的,即每次遍历,键值对的顺序可能会不一样。如果想按照顺序遍历,可以先获取所有的Key,并对Key排序,让后根据排好序的Key获取对应的Value。
// 同时获得键和值
for k,v := range testMap {}
// 只遍历值,将Key使用占位符_ 改为匿名变量形式
for _, v := ranege testMap{}
// 只遍历键,无须将值改为匿名变量形式,直接忽略即可
for k := range testMap{}
Map的大小
和数组切片不一样的是,map是没有容量的,它只有长度,也就是map的大小(键值对的个数),要想获得map的大小,使用内置的 len 函数即可。
String和[]byte
字符串 string 也是一个不可变的字节序列,所以可以直接转化为字节切片[]byte。string不止可以直接转为[]byte,还可以使用[]操作符获取指定索引的字节值。
s := "hello奔跑的蜗牛君"
bs := []byte(s)
fmt.Println(bs)
// 因为字符串是字节序列,每一个索引对应的是一个字节,而在UTF8编码下,一个汉字对应三个字节
fmt.Println(s[0], s[1], s[15])
如果想把一个汉字当成一个长度计算,可以使用utf8.RuneCountInString函数。 在使用for range 对字符串进行循环时,也恰好是按照unicode字符进行循环的。
fmt.Println(utf8.RuneCountInString(s))
// i是索引,v是unicode字符对应的unicode码点,说明for range 循环处理字符串的时候,自动隐式解码unicode字符串
for i, v := range s {
fmt.Println(i, v)
}