)
文档教程【免费下载链接】the-swift-programming-language-in-chinese中文版 Apple 官方《Swift 编程语言》项目地址https://gitcode.com/gh_mirrors/th/the-swift-programming-language-in-chinese点击查看免费下载本篇文章以《Swift 编程语言》The Swift Programming Language简称 TSPL中文版仓库中的「集合类型」章节为主体系统讲解 Swift 的三种主要集合类型——数组Array、集合Set与字典Dictionary从创建、访问、修改到遍历的完整 API 用法再到集合运算、成员关系判定等进阶话题。读完本文你将掌握 Swift 集合类型的全部基础语法与典型实战写法并理解其「类型明确」「泛型实现」「可变性由常量/变量决定」等核心设计原则。三种集合类型概览Swift 提供了三种主要的集合类型用于存储值集合数组Array有序的值集合同一个值可以在不同位置多次出现集合Set无序的唯一值集合同一个值只能出现一次字典Dictionary无序的键值对关联集合每个唯一键对应一个值。Swift 中的数组、集合和字典对于它们可以存储的值和键的类型始终是明确的。这意味着你不会错误地将一个类型不匹配的值插入到集合中同时也可以放心地知道从集合中取出的值的类型。这一「类型明确」的设计让编译器能够在编译期发现大量类型错误也让代码的可读性与可维护性显著提升。注意Swift 的数组、集合和字典类型是作为泛型集合实现的。关于泛型类型和集合的更多信息可进一步阅读本仓库的 泛型章节。在本文对应的源文档 CollectionTypes.md 中章节注释保留了若干 TODO如Collection协议、for i in indices(collection)遍历方式、集合相等性讨论等说明该章节仍在随 Swift 语言演进持续更新本文内容以当前仓库所对应的 Swift 6.x 版本为准。集合的可变性var 与 let 的选择如果你创建一个数组、集合或字典并将其赋值给一个变量var则创建的集合是可变的——创建后可以通过添加、删除或更改元素来变异mutate集合如果将其赋值给常量let则集合是不可变的其大小和内容均无法更改。var mutableArray [1, 2, 3] mutableArray.append(4) // 允许可变集合可以修改 let immutableArray [1, 2, 3] // immutableArray.append(4) // 编译错误常量集合不可修改注意在所有不需要更改的情况下创建不可变集合使用let是一种良好的实践。这样做不仅让你更容易理解代码明确表达「这份数据不会变」的意图还能让 Swift 编译器优化你创建的集合的性能例如省去写入时复制的开销。数组有序的值集合数组将相同类型的值存储在一个有序列表中。相同的值可以在数组中以不同位置多次出现例如[1, 2, 1]是合法的。注意Swift 的Array类型与 Foundation 的NSArray类进行了桥接Bridging在 Foundation 与 Cocoa 环境中可以无缝互转。本仓库的 Swift 5.x 及更早版本文档见 README.md 的「当前状态」说明亦包含相关细节。数组类型的两种写法Swift 数组的类型完整写作ArrayElement其中Element是数组允许存储的值的类型。你也可以使用简写形式[Element]来表示数组的类型。虽然两种形式功能上完全相同但简写形式更受欢迎本指南在提到数组类型时也优先使用这种形式。var a: ArrayInt [1, 2, 3] var b: [Int] [1, 2, 3] // 更常用的简写形式创建空数组在 Swift 中可以用两种方法创建空数组方式一空数组字面量[]。当上下文已经提供了类型信息例如函数参数、已明确类型的变量或常量时使用var someInts: [Int] [] print(someInts is of type [Int] with \(someInts.count) items.) // 打印 someInts is of type [Int] with 0 items.方式二显式构造器语法。在方括号中写入元素类型后面再跟一对圆括号var someInts [Int]() print(someInts is of type [Int] with \(someInts.count) items.) // 打印输出 someInts is of type [Int] with 0 items.两种方法效果相同但空数组字面量更短、通常也更易读。无论使用哪种方式初始化你都可以用空数组字面量[]将一个空数组重新赋值给一个已存在的变量someInts.append(3) // someInts 现在包含 1 个类型为 Int 的值 someInts [] // someInts 现在是一个空数组但它仍是 [Int] 类型的注意重新赋值为[]不会改变数组的静态类型someInts依旧是[Int]。用默认值批量创建数组Array类型还提供了一个构造器用于创建特定大小、所有值都相同的数组。你需要传入两个参数repeating默认值与count该值重复的次数var threeDoubles Array(repeating: 0.0, count: 3) // threeDoubles 的类型是 [Double]并且等于 [0.0, 0.0, 0.0]通过合并两个数组创建新数组你可以使用加法运算符将两个兼容类型的现有数组相加从而创建新数组新数组的类型由两个操作数的类型推断出来var anotherThreeDoubles Array(repeating: 2.5, count: 3) // anotherThreeDoubles 的类型是 [Double]并且等于 [2.5, 2.5, 2.5] var sixDoubles threeDoubles anotherThreeDoubles // sixDoubles 被推断为 [Double] 类型并且等于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]用数组字面量初始化数组数组字面量Array Literal是将一个或多个值写入数组集合的简写方法以值列表的形式书写用逗号分隔并用一对方括号括起来[#value 1#, #value 2#, #value 3#]下面的示例创建了一个名为shoppingList的数组来存储String值var shoppingList: [String] [Eggs, Milk] // shoppingList 已经用两个初始项进行了初始化shoppingList被声明为「字符串值数组」[String]因此只允许存储String类型的值。这里声明为变量var而非常量let是因为后续示例中还要向购物清单添加更多商品。得益于 Swift 的类型推断Type Inference能力如果使用包含相同类型值的数组字面量初始化就无需显式写出数组的类型可以简写为var shoppingList [Eggs, Milk]因为字面量中所有值都是String类型Swift 能自动推断出shoppingList的类型为[String]。你还可以在数组字面量的最后一个值之后包含一个尾随逗号Trailing Comma这在每行写一个值的多行字面量中非常实用var shoppingList [ Eggs, Milk, ]尾随逗号使每一行的结尾都一致后续增删或重排值时只需调整对应的行而不必额外添加或删除逗号。访问与修改数组数组的方法、属性与下标语法共同构成了它的操作接口。查询元素数量与空判断。只读属性count返回数组中元素个数布尔属性isEmpty是count 0的快捷检查print(The shopping list contains \(shoppingList.count) items.) // 打印 The shopping list contains 2 items. if shoppingList.isEmpty { print(The shopping list is empty.) } else { print(The shopping list isnt empty.) } // 打印 The shopping list isnt empty.追加元素。调用append(_:)方法将新元素添加到数组末尾或用加法赋值运算符一次性追加一个或多个兼容类型的元素shoppingList.append(Flour) // shoppingList 现在包含 3 项而有人正在做煎饼 shoppingList [Baking Powder] // shoppingList 现在包含 4 项 shoppingList [Chocolate Spread, Cheese, Butter] // shoppingList 现在包含 7 项下标读取与修改。使用下标语法在数组名后的方括号内传入索引即可检索值var firstItem shoppingList[0] // firstItem 的值为 Eggs注意数组中第一项的索引是0而不是1。Swift 中的数组始终是零索引zero-indexed的。下标同样可以用于更改指定索引处的现有值shoppingList[0] Six eggs // 列表中的第一个项现在是 Six eggs 而不是 Eggs使用下标时指定的索引必须有效。例如shoppingList[shoppingList.count] Salt这种「试图追加到数组末尾」的写法会触发运行时错误——与 Ruby、JavaScript 等语言不同那些语言访问非法索引会用nil之类的占位值自动扩展数组Swift 选择直接报错从而避免静默产生脏数据。下标还可以一次更改一个范围的值即使替换值集的长度与要替换的范围不同数组会自动伸缩shoppingList[4...6] [Bananas, Apples] // shoppingList 现在包含 6 项用 2 个值替换了原先 3 个位置的值插入元素。调用insert(_:at:)方法在指定索引处插入新元素shoppingList.insert(Maple Syrup, at: 0) // shoppingList 现在包含 7 项 // Maple Syrup 现在是列表中的第一项删除元素。使用remove(at:)方法删除指定索引处的元素并返回被删除的元素不需要时可以忽略返回值let mapleSyrup shoppingList.remove(at: 0) // 索引 0 处的项刚刚被移除了 // shoppingList 现在包含 6 项且不包含 Maple Syrup // mapleSyrup 常量现在等于已移除的 Maple Syrup 字符串删除元素后数组中的「空隙」会被自动填补——原先位于索引 0 之后的所有元素会向前移动firstItem shoppingList[0] // firstItem 现在等于 Six eggs如果要从数组末尾删除最后一项更推荐removeLast()方法它免去了查询count的麻烦同样返回被删除的元素let apples shoppingList.removeLast() // 数组中的最后一项刚刚被移除了 // shoppingList 现在包含 5 项且不包含 apples // apples 常量现在等于已移除的 Apples 字符串注意访问或修改超出数组现有边界的索引会触发运行时错误。使用索引前建议将其与count属性比较来确认有效性数组中最大的有效索引是count - 1因为数组从零开始索引当count为0数组为空时不存在任何有效索引。遍历数组使用for-in循环可以遍历数组中全部的值for item in shoppingList { print(item) } // Six eggs // Milk // Flour // Baking Powder // Bananas如果还需要每个元素的整数索引请使用enumerated()方法对每个元素它返回一个由「整数 元素」组成的元组整数从 0 开始、逐项加 1枚举整个数组时这些整数恰好与元素的索引一致。可以在遍历时把元组分解为临时常量或变量for (index, value) in shoppingList.enumerated() { print(Item \(index 1): \(value)) } // Item 1: Six eggs // Item 2: Milk // Item 3: Flour // Item 4: Baking Powder // Item 5: Bananas关于for-in循环的更多细节请参阅本仓库的 控制流章节。集合无序的唯一值集合集合Set将相同类型的不同值存储在没有定义顺序的集合中。当元素顺序不重要、或需要确保每个元素只出现一次时集合是比数组更合适的选择。注意Swift 的Set类型桥接到 Foundation 的NSSet类可在 Foundation 与 Cocoa 环境中互转。集合类型的哈希值要求一个类型必须是**可哈希的Hashable才能存储在集合中——也就是说该类型必须提供一种为自身计算哈希值hash value**的方法。哈希值是一个Int类型的值并且满足一致性要求如果a b那么a的哈希值必须等于b的哈希值。Swift 的所有基本类型如String、Int、Double和Bool默认都是可哈希的可以直接用作集合的值类型或字典的键类型没有关联值的枚举 case 值如 枚举章节 所述默认也是可哈希的。注意自定义类型可以通过遵循 Swift 标准库中的Hashable协议来作为集合的值类型或字典的键类型。关于实现所需的hash(into:)方法以及遵循协议的细节请参阅 协议章节。Set 类型语法Swift 集合的类型写作SetElement其中Element是集合允许存储的类型。与数组不同集合没有对应的简写形式——必须显式写出Set关键字这是初学者最容易写错的地方不能写作[String]就当作集合那仍是数组。创建与初始化空集使用构造器语法可以创建特定类型的空集var letters SetCharacter() print(letters is of type SetCharacter with \(letters.count) items.) // 打印 letters is of type SetCharacter with 0 items.注意letters变量的类型根据构造器参数推断为SetCharacter。如果上下文已经提供了类型信息如函数参数、已明确类型的变量可以使用空数组字面量[]创建/重置一个空集letters.insert(a) // letters 现在包含 1 个类型为 Character 的值 letters [] // letters 现在是一个空集合但仍然是 SetCharacter 类型用数组字面量创建集合集合同样可以用数组字面量来初始化。下面的示例创建了一个名为favoriteGenres的集合来存储String值var favoriteGenres: SetString [Rock, Classical, Hip hop] // favoriteGenres 已经用三个初始元素进行了初始化favoriteGenres的类型为SetString因此它只能存储String值。这里声明为变量是因为后续示例会向集合添加和移除元素。关键区别集合类型无法仅通过数组字面量推断出来——同一个[Rock, Classical, Hip hop]字面量既可以是数组也可以是集合因此必须显式声明Set。但得益于类型推断若显式写出了Set就可以省略元素的类型var favoriteGenres: Set [Rock, Classical, Hip hop]因为字面量中的所有值都是String类型Swift 能推断出SetString是正确类型。访问与修改集合查询数量与空判断。与数组一样集合也提供只读属性count与布尔属性isEmptyprint(I have \(favoriteGenres.count) favorite music genres.) // 打印 I have 3 favorite music genres. if favoriteGenres.isEmpty { print(As far as music goes, Im not picky.) } else { print(I have particular music preferences.) } // 打印 I have particular music preferences.插入元素。调用insert(_:)方法将新元素加入集合若元素已存在则无操作集合保证唯一性favoriteGenres.insert(Jazz) // favoriteGenres 现在包含 4 项移除元素。调用remove(_:)方法移除元素若元素确实是集合成员则移除并返回被移除的值若集合中不包含该元素则返回nil因此返回值是可选类型。removeAll()方法则移除集合中所有元素if let removedGenre favoriteGenres.remove(Rock) { print(\(removedGenre)? Im over it.) } else { print(I never much cared for that.) } // 打印 Rock? Im over it.成员检查。使用contains(_:)方法判断集合是否包含特定元素if favoriteGenres.contains(Funk) { print(I get up on the good foot.) } else { print(Its too funky in here.) } // 打印 Its too funky in here.实用提示与数组的线性查找不同Set基于哈希表实现contains(_:)的平均复杂度为常数级O(1)。当需要频繁判断「某元素是否存在」时将数据放入Set而非数组能获得明显更优的性能这一点在官方文档注释中亦作为 TODO 保留讨论。遍历集合使用for-in循环遍历集合中的值。由于Set没有定义的顺序遍历结果的先后顺序不确定for genre in favoriteGenres { print(\(genre)) } // Classical // Jazz // Hip hop若要按特定顺序遍历可以使用sorted()方法它返回一个按运算符排序的数组for genre in favoriteGenres.sorted() { print(\(genre)) } // Classical // Hip hop // Jazz执行集合操作Swift 的Set支持高效的基础集合运算组合两个集合、求共同值、判断集合间的包含关系等。由于Set基于哈希实现这些操作通常都是高效的常数或线性级操作远优于在数组上手动模拟。基本集合操作下图描绘了两个集合a和b阴影区域表示各种集合操作的结果四种基本操作及其语义如下intersection(_:)创建一个只包含两个集合共有值的新集合symmetricDifference(_:)创建一个包含两个集合中存在、但不同时存在的值的新集合并集减去交集union(_:)创建一个包含两个集合中所有值的新集合subtracting(_:)创建一个不包含指定集合中值的新集合从前者中剔除后者。代码示例为便于阅读结果均调用sorted()排序输出let oddDigits: Set [1, 3, 5, 7, 9] let evenDigits: Set [0, 2, 4, 6, 8] let singleDigitPrimeNumbers: Set [2, 3, 5, 7] oddDigits.union(evenDigits).sorted() // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] oddDigits.intersection(evenDigits).sorted() // [] oddDigits.subtracting(singleDigitPrimeNumbers).sorted() // [1, 9] oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted() // [1, 2, 9]注意这四个方法都返回新集合不会修改原集合因此可以对结果继续链式调用sorted()等方法。集合成员关系与相等下图描绘了三个集合a、b和c重叠区域表示集合间共享的元素集合a是集合b的超集superset因为a包含了b的所有元素反之b是a的子集subsetb与c则是**不相交disjoint**的因为它们没有任何共同元素。判断集合关系的 API 一览使用等于运算符判断两个集合是否包含所有相同的值集合相等与元素顺序无关使用isSubset(of:)方法判断一个集合的所有值是否都包含在指定集合中使用isSuperset(of:)方法判断一个集合是否包含指定集合的所有值使用isStrictSubset(of:)或isStrictSuperset(of:)方法判断一个集合是否是指定集合的严格子集/严格超集即包含关系成立但两集合不相等使用isDisjoint(with:)方法判断两个集合是否没有任何共同值。代码示例let houseAnimals: Set [, ] let farmAnimals: Set [, , , , ] let cityAnimals: Set [, ] houseAnimals.isSubset(of: farmAnimals) // true farmAnimals.isSuperset(of: houseAnimals) // true farmAnimals.isDisjoint(with: cityAnimals) // true字典键值关联集合字典Dictionary存储相同类型的键与相同类型的值之间的关联且没有定义顺序。每个值都与一个唯一键相关联键充当字典中该值的标识符。当需要「根据标识符查找值」时可以使用字典其工作方式类似于用真实字典按词条查找定义。注意Swift 的Dictionary类型与 Foundation 的NSDictionary类相互桥接可在 Foundation 与 Cocoa 环境中互转。字典类型简写语法Swift 字典的完整类型写作DictionaryKey, ValueKey是可以用作字典键的值类型Value是字典为这些键存储的值类型。简写形式为[Key: Value]两种形式功能完全相同但简写形式更受欢迎本指南采用简写。注意字典的Key类型必须遵循Hashable协议这与集合的值类型要求相同见前文「集合类型的哈希值要求」。创建空字典与数组一样可以使用构造器语法创建特定类型的空字典。下面示例创建了[Int: String]类型的空字典用于存储整数的可读名称——键类型为Int值类型为Stringvar namesOfIntegers: [Int: String] [:] // namesOfIntegers 是一个空的 [Int: String] 字典如果上下文已经提供了类型信息可以使用空字典字面量[:]方括号内一个冒号创建一个空字典也可以将其重新赋值给已存在的字典变量namesOfIntegers[16] sixteen // namesOfIntegers 现在包含 1 个键值对 namesOfIntegers [:] // namesOfIntegers 再次成为一个类型为 [Int: String] 的空字典用字典字面量创建字典字典字面量Dictionary Literal语法与数组字面量类似每个键值对中键与值用冒号分隔多个键值对用逗号分隔整体用一对方括号括起来[#key 1#: #value 1#, #key 2#: #value 2#, #key 3#: #value 3#]下面的示例创建了一个存储国际机场名称的字典键是三个字母的国际航空运输协会IATA代码值是机场名称var airports: [String: String] [YYZ: Toronto Pearson, DUB: Dublin]airports的类型是[String: String]即「键为String、值也为String的字典」。声明为变量是因为后续示例会向字典添加更多机场。与数组类似当字面量中所有键类型一致、所有值类型一致时可以省略类型注解由 Swift 推断出[String: String]var airports [YYZ: Toronto Pearson, DUB: Dublin]字典字面量同样支持尾随逗号便于多行书写与后续增删var airports [ YYZ: Toronto Pearson, DUB: Dublin, ]访问与修改字典查询数量与空判断。与数组、集合一致字典提供只读属性count与布尔属性isEmptyprint(The airports dictionary contains \(airports.count) items.) // 打印 The airports dictionary contains 2 items. if airports.isEmpty { print(The airports dictionary is empty.) } else { print(The airports dictionary isnt empty.) } // 打印 The airports dictionary isnt empty.下标添加与更新。使用下标语法以适当类型的新键作为下标索引并赋值即可添加新键值对airports[LHR] London // airports 字典现在包含 3 项对已存在的键再次赋值则会更改与该键关联的值airports[LHR] London Heathrow // LHR 的值已更改为 London HeathrowupdateValue(_:forKey:)方法。作为下标的替代方案该方法在键不存在时为该键设置值、在键已存在时更新值与下标不同它会在更新后返回旧值如果旧值不存在则返回nil。其返回类型是字典值类型的可选值——例如对存储String值的字典返回String?。这让你能够检查是否真的发生了更新if let oldValue airports.updateValue(Dublin Airport, forKey: DUB) { print(The old value for DUB was \(oldValue).) } // 打印 The old value for DUB was Dublin.下标读取与可选绑定。由于可能请求一个不存在的键字典的下标总是返回可选值键存在时返回包含现有值的可选值否则返回nil。配合可选绑定if let是读取字典的标准姿势if let airportName airports[DUB] { print(The name of the airport is \(airportName).) } else { print(That airport isnt in the airports dictionary.) } // 打印 The name of the airport is Dublin Airport.下标删除。为某个键赋值为nil即可删除该键值对airports[APL] Apple International // Apple International 不是 APL 的真实机场所以删除它 airports[APL] nil // APL 已经从字典中删除removeValue(forKey:)方法。也可以用它删除键值对键值对存在时移除并返回被移除的值不存在时返回nilif let removedValue airports.removeValue(forKey: DUB) { print(The removed airports name is \(removedValue).) } else { print(The airports dictionary doesnt contain a value for DUB.) } // 打印 The removed airports name is Dublin Airport.遍历字典使用for-in循环遍历字典的键值对每个元素作为一个(key, value)元组返回可在迭代过程中将元组分解为临时常量或变量for (airportCode, airportName) in airports { print(\(airportCode): \(airportName)) } // LHR: London Heathrow // YYZ: Toronto Pearson还可以通过只读属性keys和values分别获取键集合与值集合两者都是可遍历的集合视图for airportCode in airports.keys { print(Airport code: \(airportCode)) } // Airport code: LHR // Airport code: YYZ for airportName in airports.values { print(Airport name: \(airportName)) } // Airport name: London Heathrow // Airport name: Toronto Pearson如果需要把键或值交给接受Array实例的 API可以直接用keys或values初始化一个新数组let airportCodes String // airportCodes 赋值为 [LHR, YYZ] let airportNames String // airportNames 赋值为 [London Heathrow, Toronto Pearson]与Set相同Dictionary没有定义的顺序。若要按特定顺序迭代可对其keys或values使用sorted()方法for airportCode in airports.keys.sorted() { print(Airport code: \(airportCode)) }深入理解与延伸阅读本文内容源自本仓库的 CollectionTypes.md该文件属于《Swift 编程语言》中文版The Swift Programming LanguageTSPL的「语言指南」部分整个仓库使用 Swift-DocC 构建在仓库根目录运行docc preview即可在浏览器中预览详见 README.md 的「构建」说明。围绕集合类型以下几个话题与本篇内容直接相关建议继续阅读泛型集合的实现原理数组、集合与字典均为泛型集合理解 泛型章节 有助于深入掌握ArrayElement、SetElement、DictionaryKey, Value的底层机制for-in循环的完整语法数组、集合、字典的遍历都依赖它见 控制流章节可哈希类型的自定义让自定义类型成为集合值或字典键需要遵循Hashable协议见 协议章节无关联值的枚举默认可哈希见 枚举章节与 Foundation 的桥接Array↔NSArray、Set↔NSSet、Dictionary↔NSDictionary的桥接机制是 Cocoa/Foundation 编程中经常用到的能力可参考本仓库对应的 Swift 5.x 及更早版本归档文档。综上Swift 的三种集合类型各有明确的适用场景需要有序存储、允许重复时选数组需要快速判重、无序唯一时选集合需要按键关联值时选字典。理解它们的类型明确性、可变性规则与常用 API是编写清晰、高效 Swift 代码的基础。赞分享文档教程【免费下载链接】the-swift-programming-language-in-chinese中文版 Apple 官方《Swift 编程语言》项目地址https://gitcode.com/gh_mirrors/th/the-swift-programming-language-in-chinese点击查看免费下载相关推荐Rockstar语言教程数组与集合操作详解Rockstar语言教程数组与集合操作详解 还在为传统编程语言的数组操作感到枯燥乏味吗想要用摇滚歌词的韵律来编写代码吗Rockstar语言将带你进入一个全编程语言编译器Hermes与主流邮件服务的集成SMTP发送、Gmail配置和最佳实践Hermes与主流邮件服务的集成SMTP发送、Gmail配置和最佳实践 Hermes是一个基于Golang的开源库能够帮助开发者快速生成干净、响应式的HTMRxDB RxCollection 完全指南创建、管理与操作同类型文档集合RxDB RxCollection 完全指南创建、管理与操作同类型文档集合 RxCollection 是 RxDB 中存储同类型文档的集合抽象相当于传统数据数据库NoSQL嵌入式数据库实时数据库上一篇vim_ahk 项目下载及安装教程下一篇fast-copy 项目技术文档创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考