Swift 5从零到精通iOS开发训练营
上QQ阅读APP看书,第一时间看更新

3.4 集合类型

在Swift语言中一共提供了3种集合类型:数组(Array)、集合(Set)和字典(Dictionary)。数组类型是一种有序集合,放入其中的数据都有一个编号,且编号从0开始依次递增。通过编号,开发者可以找到Array数组中对应的值。集合是一组无序的数据,其中存入的数据没有编号,开发者可以使用遍历的方法获取其中所有的数据。集合是一种键值映射结构,其中每存入一个值都要对应一个特定的键,且键不能重复,开发者通过键可以直接获取到对应的值。Swift官方开发文档中的一张示例图片可以十分清晰地描述这3种集合类型的异同,如图3-1所示。

图3-1 三种集合类型的异同

本节将介绍这三种集合类型的特点及操作数据的方法。

3.4.1 数组(Array)类型

数组中能够存放的元素并非只是数字,它可以存放任意类型的数据,但是所有数据的类型必须统一。在实际开发中,数组中元素的类型决定了数组的类型,例如,一个存放整型数据的数组被称为整型数组,一个存放字符串型数据的数组被称为字符串型数组。在创建数组实例的时候,必须明确指定其中所存放元素的类型。使用Xcode开发工具创建一个名为CollectType的Playground,在其中编写如下示例代码:

     //Int型数组
     var array1:[Int]
     var array2:Array<Int>

上面两行代码都声明了一个Int类型的数组实例,数组的创建可以使用两种方式,一种是使用数组的构造方法来创建,另一种是使用中括号来快捷创建,示例如下:

     //创建空数组
     array1 = []
     array2 = Array()
     //创建整型数组
     array1 = [1,2,3]
     //通过一组元素创建数组
     array2 = Array(arrayLiteral: 1,2,3)

提示

和String类型类似,空数组的含义并非是变量为nil,而是数组中的元素为空,Swift中只有Optional类型的变量可以为nil。

在Swift语言中,数组采用结构体来实现,对于大量重复元素的数组,开发者可以直接使用快捷方法来创建,示例如下:

     //创建大量相同元素的数组
     //创建有10个String类型元素的数组,并且每个元素都为字符串"Hello"
     var array3 = [String](repeating: "Hello", count: 10)
     //创建有10个Int类型元素的数组,且每个元素都为1
     var array4 = Array(repeating: 1, count: 10)

读者需要注意,数组在声明时必须要明确其类型,但是开发者并不一定需要显式地指定类型,如果数组在声明时也设置了初始值,则编译器会根据赋值类型自动推断出数组的类型。数组数组中对加法运算符也进行了重载,开发者可以使用“+”进行两个数组的相加,相加的结果即将第2个数组中的元素拼接到第1个数组后面。需要注意,相加的数组类型必须相同,示例如下:

     //数组相加 array5 = [1,2,3,4,5,6]
     var array5 = [1,2,3]+[4,5,6]

数组中提供了许多方法供开发者来获取数组实例的相关信息或者对数组进行增、删、改、查的操作。示例如下:

这里需要注意,只有当数组实例为变量时,才可以使用增、删、改等方法,常量数组不能进行与修改相关的操作。

开发者也可以使用for-in遍历来获取数组中的元素,示例如下:

可以直接对数组实例进行遍历,Swift中的for-in结构和Objective-C中的for-in结构还是有一些区别的,Swift中的for-in结构在遍历数组时会按照顺序进行遍历。数组实例中还有一个enumerated()方法,这个方法会返回一个元组集合,将数组的下标和对应元素返回。开发者也可以通过遍历数组的下标来获取数组中的元素,和String类型不同的是,数组中的下标可以是Int类型,而String中的下标是严格的Index类型,这里需要注意,不要混淆。

提示

数组类型中有一个indices属性,这个属性将返回一个范围(Range),此范围就是数组下标的范围。

数组类型中还提供了一个排序函数,如果数组中的元素为整型数据,则可以使用系统提供的sorted(by:)方法来进行排序操作,如果是一些自定义的类型,开发者也可以对sorted(by:)方法传入闭包参数实现新的排序规则,这部分内容会在后面章节中详细介绍。进行数组排序的方法示例代码如下:

     var arraySort = [1,3,5,6,7]
     //从大到小排序
     arraySort = arraySort.sorted(by: >)
     //从小到大排序
     arraySort = arraySort.sorted(by: <)

下列方法可以获取数组中的最大值与最小值:

     var arraySort = [1,3,5,6,7]
     //获取数组中的最大值
     arraySort.max()
     //获取数组中的最小值
     arraySort.min()

3.4.2 集合(Set)类型

集合类型不关注元素的顺序,但是其中的元素不可以重复,读者也可以将其理解为一个无序的集合。与数组一样,集合在声明时必须指定其类型,或者对其赋初值,使得编译器可以自行推断出集合的类型。声明与创建集合的示例代码如下:

     //创建set
     var set1:Set<Int> = [1,2,3,4]
     var set2 = Set(arrayLiteral: 1,2,3,4)

由于集合并不关注其中元素的顺序,因此通过下标的方式来取值对集合来说不太有意义,但是集合类型依然支持通过下标来获取其中的元素,示例如下:

     //获取集合首个元素(顺序不定)
     set1[set1.startIndex]
     //进行下标的移动
     //获取某个下标后一个元素
     set1[set1.index(after: set1.startIndex)]
     //获取某个下标后几个元素
     set1[set1.index(set1.startIndex, offsetBy: 3)]

需要注意,集合的下标操作为不可逆的操作,只能向后移动,不能向前移动。

下面这个方法可以获取集合实例中的一些信息:

集合同样支持进行增、删、改、查操作,示例如下:

     //向集合中插入一个元素
     set1.insert(5)
     //移除集合中的某个元素
     set1.remove(1)
     //移除集合中的第一个元素
     set1.removeFirst()
     //移除集合中某个位置的元素
     set1.remove(at: set1.firstIndex(of: 3)!)
     //移除集合中所有的元素
     set1.removeAll()

在使用remove(at:)方法删除集合某个位置的元素时,需要传入一个集合元素的下标值,通过集合实例的firstIndex(of:)方法可以获取具体某个元素的下标值。需要注意,这个方法将会返回一个Optional类型的可选值,因为要寻找的元素可能不存在,在使用时,开发者需要对其进行拆包操作。

集合与数组除了有序和无序的区别外,集合还有一个独有的特点:可以进行数学运算,例如交集运算、并集运算、补集运算等。Swift官方开发文档中的一张图片示意了集合进行数学运算时的场景,如图3-2所示。

图3-2 集合进行数学运算示意图

从图3-2中可以看出,集合支持4类数学运算,分别为intersection(交集)运算、symmetricDifference(交集的补集)运算、union(并集)运算和subtracting(补集)运算。交集运算的结果为两个集合的交集,交集的补集运算的结果为a集合与b集合的并集除去a集合与b集合的交集,并集运算的结果为两个集合的并集,补集运算的结果为a集合除去a集合与b集合的交集。上述4种运算的示例代码如下:

     var set3:Set<Int> = [1,2,3,4]
     var set4:Set<Int> = [1,2,5,6]
     //返回交集 {1,2}
     var setInter = set3.intersection(set4)
     //返回交集的补集{3,4,5,6}
     var setEx = set3.symmetricDifference(set4)
     //返回并集{1,2,3,4,5,6}
     var setUni = set3.union(set4)
     //返回第二个集合的补集{3,4}
     var setSub = set3.subtracting(set4)

使用比较运算符“==”可以比较两个集合是否相等,当两个集合中的所有元素都相等时,两个集合才相等。集合中还提供了一些方法用于判断集合间的关系,示例代码如下:

     var set5:Set = [1,2]
     var set6:Set = [2,3]
     var set7:Set = [1,2,3]
     var set8:Set = [1,2,3]
     //判断是否是某个集合的子集,set5是set7的子集,返回ture
     set5.isSubset(of: set7)
     //判断是否是某个集合的超集,set7是set5的超集,返回ture
     set7.isSuperset(of: set5)
     //判断是否是某个集合的真子集,set5是set7的真子集,返回ture
     set5.isStrictSubset(of: set7)
     //判断是否是某个集合的真超集,set7不是set8的真超集,返回false
     set7.isStrictSuperset(of: set8)

与数组类似,集合也可以通过for-in遍历的方式来获取所有集合中的数据,可以通过3种方法来进行遍历:遍历元素、遍历集合的枚举与遍历集合的下标。集合枚举会返回一个元组,元组中将集合下标和其对应的值一同返回,示例代码如下:

集合虽然不强调元素顺序,但是在遍历时,开发者可以对其进行排序后再遍历,示例如下:

     //从大到小排序再遍历集合
     for item  in set7.sorted(by: >) {
        print(item)
     }

3.4.3 字典(Dictionary)类型

字典是生活中常用的学习工具,字典在使用时是由一个索引找到一个结果。例如,英汉词典通过英文单词可以找到其对应的汉语解释,成语词典通过成语可以找到其对应的意义解释,等等。这种数据的存储模式被称为键值映射模式,即通过一个确定的键可以找到一个确定的值。类比上面的例子,在英汉词典中,英文单词就是键,汉语释义就是值;在成语词典中,成语就是键,意义解释就是值。在Swift语言中也有这样的一种Dictionary集合,即字典集合类型。

Swift中的任何类型在声明时都必须明确其类型,通过对Array和Set的学习,读者应该知道,对于集合类型,在声明时务必明确其内部元素的类型,字典也不例外,由于字典中的一个元素实际上是由键和值两部分组成的,因此在声明字典时,也需要明确其键和值的类型。有两种方式可以进行字典的声明或创建,示例代码如下:

     //声明字典[param1:param2],这种结构用于表示字典类型,param1为键类型,param2为值类型
     var dic1:[Int:String]
     //这种方式和[:]效果一样,dic2与dic1为相同的类型
     var dic2:Dictionary<Int,String>
     //字典创建与赋值
     dic1 = [1:"1",2:"2",3:"3"]
     dic2 = Dictionary(dictionaryLiteral: (1,"1"),(2,"2"),(3,"3"))
     //在创建字典时,也可以不显式声明字典的类型,可以通过赋初值的方式来使编译器自动推断
     var dic3 = ["1":"one"]
     //创建空字典
     var dic4:[Int:Int] = [:]
     var dic5:Dictionary<Int,Int> = Dictionary()

需要注意,字典通过键来找到特定的值,在字典中值可以重复,但是键必须唯一。这样才能保证一个确定的键能找到一个确定的值,并且如果开发者在字典中创建重复的键,编译器也会报出错误。

字典类型也支持使用isEmpty与count来判断是否为空并获取元素个数,示例代码如下:

     //获取字典中的元素个数
     dic1.count
     //判断字典是否为空
     if dic4.isEmpty{
        print("字典为空")
     }

通过具体键可以获取与修改对应的值,示例如下:

     //通过键操作值
     //获取值
     dic1[2]
     //修改值
     dic1[1]="0"
     //添加一对新的键值
     dic1[4] = "4"

上面代码中的dic1[1]="0"与dic1[4]="4"实际上完成了相同的操作,可以这样理解:在对某个键进行赋值时,如果这个键存在,则会进行值的更新,如果这个键不存在,则会添加一对新的键值。然而在开发中,很多情况下需要对一个存在的键进行更新操作,如果这个键不存在,则不添加新键值对,要实现这种效果,可以使用字典的更新键值方法,示例代码如下:

     //对键值进行更新
     dic1.updateValue("1", forKey: 1)

updateValue(value:forkey:)方法用于更新一个已经存在的键值对,其中第1个参数为新值,第2个参数为要更新的键。这个方法在执行时会返回一个Optional类型的值,如果字典中此键存在,则会更新成功,并将键的旧值包装成Optional值返回,如果此键不存在,则会返回nil。在开发中,常常使用if-let结构来处理,示例如下:

     //使用if let 处理updateValue的返回值
     if let oldValue = dic1.updateValue("One", forKey: 1) {
        print("Old Value is \(oldValue)")
     }

其实在通过键来获取字典中的值时,也会返回一个Optional类型的值,如果键不存在,则此Optional值为nil,因此也可以使用if-let结构来保证程序的安全性,示例如下:

     //通过键获取的数据也将返回Optional类型的值,也可以使用if let
     if let value = dic2[1] {
        print("The Value is \(value)")
     }

下面的方法可以实现对字典中键值对的删除操作:

     //通过键删除某个键值对
     dic1.removeValue(forKey: 1)
     //删除所有键值对
     dic1.removeAll()

在对字典进行遍历操作时,可以遍历字典中所有键组成的集合,也可以遍历字典中所有值组成的集合,通过字典实例的keys属性与values属性分别可以获取字典的所有键与所有值,示例代码如下:

如以上代码所示,也可以直接对字典实例进行遍历,遍历中会返回一个元组类型包装字典的键和值。

在进行字典键或者值的遍历时,也支持对其进行排序遍历,实例如下: