上一篇文章中和大家介绍了go语言的原生数据类型,接下来继续和大家介绍go语言的数据类型,包括:数组、切片、映射、列表,希望对大家有所帮助
在go语言中,数组的概念和c语言中数组的概念是完全一致的。数组从声明时就确定了大小,使用时可以修改数组元素,但是不能改变数组的大小。
数组声明格式:var 变量名 [元素数量] T,T为数据类型
// 声明int数组
var intArray [1]int
// 声明string数组
var stringArray [3]string
(1)系统默认初始化
在声明数组之后,如果不给指定的元素赋值的话,系统会给对应的元素初始化一个默认值,正如参数默认值那样,如:
// 声明int数组
var intArray [1]int
// 声明string数组
var stringArray [3]string
stringArray[1] = "string1"
stringArray[2] = "string2"
println(intArray[0], stringArray[0], stringArray[1], stringArray[2])
/*
结果:
0 string1 string2
*/
从以上打印结果可以得知,int数组没有赋值的情况下,元素默认值为0,string数组没有赋值的情况下,元素默认值为空字符串,因此可以得知数组会对元素进行初始化设置。
(2)逐个初始化
如:
// 声明string数组
var stringArray [3]string
// 逐个赋值
stringArray[0] = "string0"
stringArray[1] = "string1"
stringArray[2] = "string2"
(3)使用初始化列表
如:
// 使用初始化列表声明并初始化int数组
var intArray = [3]int{0, 1, 2}
注意:需保证大括号里面的元素数量与数组的大小一致,否则将会出现缺省默认值的情况
如:
// 使用初始化列表方式声明并初始化int数组
var intArray = [3]int{1, 2}
println(intArray[2])
/*
结果:
0
*/
(4)使用简易初始化列表
简易初始化列表的方式省略了元素数量的指定,数组的具体大小交由编译器在编译时确定,具体如下:
// 使用初始化列表声明并初始化int数组
var intArray2 = [...]int{1, 2, 3}
// 注意,[]内是有...的,如果没有的话初始化的将会是一个切片
var slice = []int{1, 2, 3}
// 使用初始化列表声明并初始化int数组
var intArray = []int{10, 20, 30}
for k, v := range intArray {
println(k, v)
}
/*
结果:
0 10
1 20
2 30
*/
go语言切片内部结构包括内存地址、大小、容量,一般用于快速操作一块数据集合。
声明格式:var name []T
示例如下:
// 声明整型切片
var a []int
// 初始化一个空切片
a = []int{}
// 打印第一个元素会报索引越界,error: index out of range
// println(a[0])
// 重新赋值
a = []int{1, 2, 3}
// 正常打印 1
println(a[0])
使用make()函数可以动态创建一个切片,格式如下:
make([]T, size, cap)
T: 切片类型
size:指定切片元素个数
cap:预设容量,用于提前分配空间,降低多次分配空间造成的性能损耗
示例:
// 使用make构造一个分配三个元素,初始容量为10的切片
var a = make([]int, 3, 10)
// 正常打印第一个元素,说明make构造切片会默认初始化默认值
print(a[0])
注意:使用make函数生成切片时一定会发生内存分配操作,但给定开始、结束为止的切片只是将新的切片地址指向已经分配好的内存区域,并不会发生内存分配操作。
除了使用make函数构造切片以外,还可以从连续内存区域生成切片,可以是数组,或是切片本身,
格式如下:
slice [开始位置:结束位置]
slice表示目标对象,开始位置和结束位置对应目标的指定索引,索引从0开始
// 使用make构造一个分配三个元素,初始容量为10的切片
var a = make([]int, 3, 10)
// 从切片a中生成新的切片b
b := a[0:2]
注意事项:
(1)取出元素数量 = 结束位置 - 开始位置
(2)取出元素不包括结束位置对应索引的元素
(3)当缺省开始位置时(如[:2]),表示从开头到结束位置
(4)当缺省结束位置时(如[2:]),表示从开始位置到末尾
(5)同时缺省时(如:[:]),表示从开头都末尾
(6)两者同时为0时(如:[0:0]),表示空切片,一般用于切片复位
(7)根据开始及结束位置生成切片时,如果越界的话会报运行错误。
(1)为切片添加元素,使用appand函数,示例如下:
// 声明一个整型切片
var slice []int
// 为切片slice添加元素1
slice = append(slice, 1)
// 为切片slice添加元素2、3
slice = append(slice, 2, 3)
(2)删除切片元素,依然使用appand函数,因为切片没有提供删除元素的方法,只能截取后重新拼接,示例如下:
// 声明一个整型切片
var slice = []int{1, 2, 3}
// 指定删除索引
index := 1
// 获取索引前后元素
a := slice[:1]
b := slice[index+1:]
// 重新拼接切片
result := append(a, b...)
(3)复制切片元素,使用copy函数,示例如下:
// 声明一个整型切片
var slice = []int{1, 2, 3}
// 指定删除索引
index := 1
// 获取索引前后元素
a := slice[:1]
b := slice[index+1:]
// 重新拼接切片
result := append(a, b...)
切片复制时需要注意的事项:
(1)目标切片类型必须和源切片类型保持一致
(2)目标切片必须分配过空间
(3)目标切片需拥有足够承载复制的元素个数(强调:是元素个数,而不是切片容量)
(4)如果目标切片元素个数大于源切片元素个数,复制时将以此替换对应索引的值,超出源切片索引的元素数据将保持原样
go语言提供的映射关系容器是map,使用散列表(hash)实现。
声明格式:map[keyType]valueType
示例如下:
// 声明map
var a map[string]string
// 尝试赋值,会报运行时错误:panic: assignment to entry in nil map,因为还未分配内存
// a["key"] = "value"
// 初始化
a = map[string]string{"key1": "value1", "key2": "value2"}
// 添加新的键值对
a["key3"] = "value3"
// 使用make函数构造
var b = make(map[string]string)
// 添加键值对
b["key"] = "value"
3.2、设置及访问映射关系方式
// 使用make函数构造
var b = make(map[string]string)
// 添加键值对
b["key"] = "value"
b["key1"] = "value1"
// 访问"key"对应的值
println(b["key"])
_ = b["key"]
// 通过遍历方式访问
for k, v := range b {
println(k, v)
}
注意:遍历映射表(map)时,输出元素的顺序与填充顺序无关,不能期望遍历时返回某种期望的结果。
示例如下:
// 使用make函数构造
var b = make(map[string]string)
// 添加键值对
b["key"] = "value"
b["key1"] = "value1"
// 删除"key"键值对
delete(b, "key")
// 通过遍历打印键值对,输出结果为:key1 value1,可以看到"key"键值对成功被删除
for k, v := range b {
println(k, v)
}
注意:go语言中没有为map提供删除所有元素的方法,所以如果需要删除所有元素的话,直接make一个新的map吧。
go语言中的map在并发情况下,只读是线程安全的,但是同时读写的话线程是不安全的。如果需要并发读写的话,一般的做法是对map进行加锁处理,但是这样性能并不高。go1.9版本中提供了一种效率较高且并发安全的sync.Map, sync.Map不是以语言原生形态提供,而是在sync包下的特殊结构。
sync.Map有以下特点:
(1)无须初始化,直接声明便可使用
(2)不能使用map的方式(如:map[key])进行取值和设置等操作,取而代之的是使用sync.Map的方法进行调用。Store表示存储,Load表示获取,Delete表示删除
(3)遍历使用Range方法配合一个回调函数进行操作,回调函数
具体示例如下:
// 声明sync.Map
var syncMap sync.Map
// 添加元素
syncMap.Store("key", "value")
syncMap.Store("key1", 1)
syncMap.Store(1, "value of int key")
// 获取值,load 方法会返回key对应的值以及取值结果
_, result := syncMap.Load("unexistsKey")
// 如果读取一个不存在的key的话,result会返回false,值为nil
println(result)
// 读取存在的key
value, result := syncMap.Load("key")
intValue, intResult := syncMap.Load("key1")
// 打印结果
println(value.(string), result)
println(intValue.(int), intResult)
// 下面这句打印代码会报运行时错误:panic: interface conversion: interface {} is int, not string
// println(intValue.(string), intResult)
/*
结果:
value true
1 true
*/
// 删除1和key1的键值对
syncMap.Delete(1)
syncMap.Delete("key1")
// 通过range遍历map
syncMap.Range(func(k, v interface{}) bool {
println(k.(string), v.(string))
return true
})
/*
结果:
key value
*/
go语言使用sync.Map注意事项:
(1)sync.Map在sync包下,所以使用是需 import "sync"
(2)sync.Map没有限制键值的类型,所以同一个sync.Map里面是允许存在不同的键值对类型的,因为sync.Map的键值对最终是以interface类型存在的
(3)通过load 方法获取值时,会返回key对应的值以及取值结果(bool类型),因此在使用load方法取值时,可以根据取值结果判断是否存在对应的值
(4)load方法返回的值是interface类型,没有值时返回nil,因此在使用返回值时需进行显示转换,转换方式为interface.(T),另外进行interface.(T)进行转换时需保证T类型与Store存储时的类型一致,否则将报运行时错误:panic: interface conversion: interface {} is int, not string,具体内容可以查看上例。
(5)使用Range方法进行遍历时,需提供一个函数,该函数需包含两个参数且类型为interface{}, 并且返回值为bool类型,该返回值用于决定是否继续迭代,true继续,false终止。
(6)sync.Map没有提供获取数量的方法,替代方法是遍历进行计算。
(7)sync.Map为了保证并发安全有一些性能损失,因此在非并发情况下,使用map并使用sync.Map会有更好的性能。
列表是一种非连续存储的容器,由多个节点组成,节点通过一些变量记录彼此之间的关系。在go语言中,list和sync.Map 一样不是以原生形态出现的, 而是使用container/list 包来实现,内部实现原理为双链表,能够高效的进行任意位置的元素插入和删除操作。
// 声明一个list,声明的类型是List
var a list.List
// 通过new方法初始化,初始化出来的类型是*List
var b = list.New()
4.2、往列表插入元素
// 添加元素, 不会限制元素类型,会返回*list.Element
// 从末尾追加
element1 := a.PushBack("aaa")
// 从开头插入
a.PushFront(1)
// 在指定元素之后插入
a.InsertAfter(2, element1)
// 在指定元素之前插入
a.InsertBefore(3, element1)
// 通过其他列表添加,需传入*List类型
b.PushBackList(&a)
b.PushFrontList(&a)
通过for循环进行遍历,Front()获取list第一个元素,Next()获取list下一个元素
for i := a.Front(); i != nil; i = i.Next() {
// 遍历结果为interface,需显式转换
println(i.Value.(int))
}
(1)不支持通过值进行元素删除,只能通过*list.Element进行删除,示例如下:
// 不支持通过值进行元素删除,如下面这行语句会编译错误:cannot use "aaa" (type string) as type *list.Element in argument to a.Remove
// a.Remove("aaa")
// 只能通过*list.Element进行删除
a.Remove(element1)
(2)如何通过值删除go语言list指定元素?
我目前能想到的是采用遍历匹配的方式进行查找,如有更好的方式希望大家能指出,具体示例如下:
// 删除值为1的list元素
for i := a.Front(); i != nil; i = i.Next() {
// 遍历结果为interface,需显式转换
value := (i.Value.(int))
if value == 1 {
a.Remove(i)
}
}
注意:以上这种方法只能删除第一个匹配到的元素
那么原因是什么呢?应该怎么删除所有匹配的元素呢?下面我们对代码稍作修改,看看删除所有元素会发生什么
// 删除list所有元素
for i := a.Front(); i != nil; i = i.Next() {
a.Remove(i)
}
/*
删除前结果:1 3 2
删除后结果:3 2
*/
从结果可以看出以上代码只删除了list的第一个元素, 原因就出在for 循环的 i.Next()上,进入第1次循环,获取到了list的第一个元素并执行了remove操作,然后此时执行 i.Next()将会等到nil,所以i != nil 不成立,此时循环便终止了,不会继续对后面的元素执行删除操作。
所以上例通过值删除go语言list指定元素只删除了第一个匹配的元素的原因和删除全部元素只删除第一个元素的原因是一样的。原因已经找到了,解决起来也很简单,只需增加个变量提前保存 i.Next()就可以了,具体示例如下:
// 删除list所有元素
var n *list.Element
for i := a.Front(); i != nil; i = n {
n = i.Next()
a.Remove(i)
}
go语言使用list注意事项:
(1)list不是以原生形态出现的, 使用时需引入container/list 包
(2)list.List声明的类型是List
(3)list.New()初始化出来的类型是*List
(4)往list添加元素时, list不会限制元素类型,会返回*list.Element
(5)通过for循环遍历list返回的是*list.Element类型,element.Value返回的是interface类型,使用是需进行显式转换,并且需要注意转换的类型,否则会发生运行时错误。