这里写Go语言成长之路(二)-内建容器
1、数组
1.1、一维数组
一维数组的声明方式:
var variable_name [SIZE] variable_type
例如:
var b [5]int
数组初始化:
var b = [5]int{1000, 2, 3, 7, 5}
如果数组长度不确定,可以使用 … 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var b = [...]int{1,2,3,4,5}
如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
可以通过在数组名后接中括号加下标,访问数组元素
1.2、多维数组
数组声明:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
例如:
var b [5][5]int
多维数组初始化:
1.3、数组参数传递
数组作为参数传递,会拷贝数组,属于值传递,函数内部对数组的修改不生效,要想修改数组,可以使用指针传递(但是注意,go语言中的指针和c语言中的不一样,不能运算)
2、Slice-切片
Go 数组的长度不可改变,在许多场景下使用起来十分不方便,因此,Go语言中提供了一种灵活的“动态数组”使用方式,被称为“切片”。
与数组相比,切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
2.1、切片定义
var identifier []type
例如:
var testSliceOne []int
这和数组的声明方式是一样的
切片定义的时候不需要声明长度,因为切片属于动态数组,当然也可以去指定
2.2、切片初始化
testSlice = arr[startIndex:endIndex]
切片将会从arr数组的startIndex开始获取数字,然后到endIndex之前截止
2.3、切片常用操作
如下图所示
使用len操作获取切片长度
使用cap操作获取切片可扩展容量
切片的长度是内值的数量
切片的可扩展容量是切片所指向的目标数组从startIndex之后的数组长度
切片在赋值之前为nil,此时切片的len和cap均为0
如下图所示
使用append操作可以为切片添加一个或多个元素
使用copy操作可以将一个切片的内容拷贝到第二个切片
等等,为什么testSliceTwo为空?不是已经copy了吗?
其实这是copy的一个坑,如果进行切片复制,我们需要需要使用copy(dst, src)函数
但是copy实际复制的元素个数,是从两个切片的大小中取最小值,即min(len(dst),len(src)),如果len(dst)=0则没有办法完成复制。
因此,当我们创建一个需要接收复制的切片的时候,根据需要复制的个数,需要对切片的大小进行设置,如下图所示,使用make命令创建切片,指定长度大小,复制成功,这里的复制只是值的复制,这个时候sliceTwo切片已经不是指向arrayOne数组了
需要注意的是,在go语言中,切片相当于底层数组的一个视图,所以当你修改指向数组的切片的值的时候,原本数组的值也会被改变。
3、Map-集合
3.1、Map创建
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
/* 直接赋值 */
map_variable := map[key_data_type]value_data_type{
"key_data" = value_data,
}
代码示例:
3.2、Map赋值
首先,我们直接赋值
发现报错了,错误信息为panic: assignment to entry in nil map
这是为什么呢?
golang中map是引用类型,应用类型的变量未初始化时默认的zero value是nil。直接向nil map写入键值数据会导致运行时错误。如上图所示,0在声明testMapOne后并未初始化它,所以它的值是nil, 不指向任何内存地址。需要通过make方法分配确定的内存地址。程序修改后即可正常运行
这样就成功赋值了。
3.3、Map获取
可以直接用类似数组取值的方式对Map进行取值
除此之外,这种取值方式还能够获得一个变量,如下图所示,第二个返回变量是一个bool值,代表着map当中是否存在与这个key相对应的value,用来做条件判断很方便
3.4、Map遍历
仍然是使用range关键字,可以让下图中的cityName在testMapOne中的所有key的范围内遍历
然而当我们第二次执行的时候,却发现遍历打印value的顺序不一样了
这是为什么呢?
这里我们参考了 Go range实现原理及性能优化剖析 中的示例
range是Golang提供的一种迭代遍历手段,可操作的类型有数组、切片、Map、channel等。
range在遍历Map的时候,获取迭代器是通过调用了mapiterinit()方法,如下图源码所示
// The loop we generate:
// var hiter map_iteration_struct
// for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) {
// index_temp = *hiter.key
// value_temp = *hiter.val
// index = index_temp
// value = value_temp
// original body
// }
而在mapiterinit方法里,有取随机数的部分。go语言会提前取一个随机数,把桶的遍历顺序随机化。
为什么要这么做呢?
map的本质是散列表,而map的增长扩容会导致重新进行散列,这就可能使map的遍历结果在扩容前后变得不可靠,Go设计者为了让大家不依赖遍历的顺序,每次遍历的起点–即起始bucket的位置不一样,即不让遍历都从bucket0开始,所以即使未扩容时我们遍历出来的map也总是无序的。
3.5、Map删除
调用delete方法进行键的删除
delete(map_name,key_data)
如下图所示