
1.3 数组和切片基础技巧
1.3.1 数组和切片基础
在Go语言中数组的长度不可改变,在特定场景中这样的集合就不太适用。Go语言中提供了一种灵活、功能强悍的内置类型Slices(切片)(动态数组),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。切片中有两个概念:一是len长度;二是cap容量。长度的值等于已经被赋过值的最大下标+1,可通过内置函数len()获得。容量是指切片目前可容纳的元素个数,可通过内置函数cap()获得。切片是引用类型,因此在当传递切片时将引用同一指针,修改切片的值将会影响使用该切片的其他对象。
切片(slice)是对数组的一个连续“片段”的引用,所以切片是一个引用类型(因此更类似于C/C++中的数组类型,或者Python中的list类型)。
这个“片段”可以是整个数组,也可以是由起始和终止索引标识的一些项的子集。
提示:终止索引标识的项不包括在切片内。
切片的内部结构包含地址、大小和容量。切片一般用于快速操作一个数据集合。
如图1-2所示,切片的结构体由3部分构成:pointer是指向一个数组的指针;len代表当前切片的长度;cap是当前切片的容量。cap总是大于或等于len。

● 图1-2
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。从连续内存区域生成切片是常见的操作,格式如下。

语法说明如下。
● slice:表示目标切片对象。
● 开始位置:对应目标切片对象的索引。
● 结束位置:对应目标切片的结束索引。
切片的创建有以下4种方式。
● make([ ] Type,length,capacity)
● make([ ] Type,length)
● [ ] Type{}
● [ ] Type {value1,value2,…,valueN }
1.3.2 迭代处理数组
Go语言可以通过关键字range,配合关键字for来迭代数组或切片里的每一个元素。当迭代切片时,关键字range会返回两个值,第1个值是当前迭代到的索引位置,第2个值是该位置对应元素值的1份副本,示例如下。

输出结果如下。

因为迭代返回的变量是一个在迭代过程中根据切片依次赋值的新变量,所以value的地址总是相同的。如果想要获取每个元素的地址,需要使用切片变量和索引值(例如上面代码中的&array[k])。
当然,range关键字不仅仅可以用来遍历切片,它还可以用来遍历数组、字符串、map或者通道等。完整示例如下。
代码路径:chapter1/arraySlice/iteration1.go。


1.3.3 从数组中删除元素
Go语言并没有直接提供用于删除数组或切片元素的语法或接口,而是利用切片本身的特性来删除或者追加元素。即以被删除元素为分界点,将前后两个部分的内存重新连接起来,通过append()函数实现对单个元素以及元素片段的删除。从数组删除元素的示例如下。

1.3.4 将数组转换为字符串
1)将数组里的一个元素直接转化为字符串,示例如下。

2)将数组里面的数据全转换为字符串,示例如下。

1.3.5 检查某个值是否在数组中
如果要检查某个值是否在数组或切片中,则需要根据相应的类型进行逐个对比,示例如下。
代码路径:chapter1/arraySlice/array1.go。

1.3.6 查找一个元素在数组中的位置
如果要查找一个元素在数组中的位置,方法是:首先通过reflect包的ValueOf()函数获取数组的值,然后使用for循环遍历数组对值进行比较(比较输入的值与数组中的值的大小),如果相等,则返回位置的索引值。
用Go语言实现泛型数组查找成员位置的示例代码如下。
代码路径:chapter1/arraySlice/array2.go。


1.3.7 查找数组中最大值或最小值元素
在Go语言中,如果要查找数组中最大值或最小值元素,可以通过for循环逐个比较元素的大小。在for循环中,如果发现有更大的数,则进行交换。例如,求出一个数组的最大值,并得到对应的下标,代码如下。
代码路径:chapter1/arraySlice/array4.go。


代码输出结果如下。

1.3.8 随机打乱数组
把一个数组随机打乱的实质就是“洗牌问题”,“洗牌问题”不仅追求速度,还要求“洗得足够开”。常见的应用场景有三国杀游戏、斗地主游戏等。
接下来通过Fisher-Yates随机置乱算法来进行讲解。Fisher-Yates随机置乱算法也称高纳德置乱算法,其核心思想是从1~n之间随机出一个数和最后一个数(n)交换,然后从1~n-1之间随机出一个数和倒数第二个数(n-1)交换。这个算法生成的随机排列是等概率的,所以每个排列都是等可能的,其生成随机整数的示例如下。
代码路径:chapter1/arraySlice/array5.go。


需要注意的是,无论是算法本身的执行过程,还是生成随机数的过程,使用Fisher-Yates洗牌算法必须谨慎,否则就可能出现一些偏差。例如随机数生成带来的误差,会造成洗牌的结果整体上不满足均匀分布的特点。
1.3.9 删除数组中重复的元素
在开发实战中,往往会遇到数组中有重复元素的情况,需要删除数组中重复的元素。例如给定一个数组,需要删除重复出现的元素,使得每个元素只出现一次,并返回移除后数组的新长度。不要使用额外的数组空间,必须通过直接修改输入数组的方式,并在使用空间复杂度为O(1)的条件下完成,示例代码如下。
代码路径:chapter1/arraySlice/array6.go。


代码输出结果如下。
