前言
3月27号苹果发布了Swift3.1,官方教程也更新到了3.1,查看更新记录发现更新的内容对之前的文章并没有影响,所以继续Swift学习。
目录
- 集合的可变性
- 数组
- 集合
- 执行集合运算符
- 字典
Swift提供了三种主要的集合类型 数组,集合和字典,用来存储一组值。数组是有序的值的集合。集合是唯一值的无序集合。字典是键值对的无序集合。
Swift的数组,集合和字典可以存储的数据类型是确定的。这意味着你不能错误的向集合插入一个错误的类型的值。同时也可以明确从集合访问的值的类型。
注意
Swift的数组,集合和字典被当做泛型集合 来实现的。更多关于泛型类型和集合的内容,详见泛型
集合的可变性
如果创建了一个数组,集合或者字典并把它赋值给一个变量,那么这个你创建的集合就是可变的。这意味你可以通过增加,删除或者修改集合的元素来改变这个集合。如果把数组,集合或者字典赋值给一个常量,那么这个集合就是不可变的,它的大小和内容都不可改变。
注意
实践证明当一个集合无需改变时,那么就创建一个不可变的集合。这样做可以更容易的推导你的代码,Swift的编译器可以最优化你创建的集合的性能。
数组
数组可以将相同类型的值存储为一个有序列表。一个数组可以在不同的位置存在多个同样的值。
注意
Swift的Array
类型可以桥接Foundation的NSArray
类
数组类型的简写语法
Swift数组类型完整语法格式为Array<Element>
,Element
是数组存储值的类型。同样也可以简写为[Element]
。尽管两种形式功能上是相同的,但更推荐简写形式而且简写形式贯穿本文。
创建空数组
使用初始化语法创建某个类型的空数组:
var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."
注意变量someInts
的类型根据构造器的类型被推断为[Int]
。
此外,如果上下文已经提供了类型信息,例如一个函数的参数或者一个已知类型的变量或者常量,可以使用一个空的数组字面量[]
创建一个空数组:
someInts.append(3)
// someInts now contains 1 value of type Int
someInts = []
// someInts is now an empty array, but is still of type [Int]
创建包含默认值的数组
Swift的数组类型同样提供了一个构造器来创建一个固定大小,默认值均相同的数组。向参数repeating
传入一个适当类型的默认值,向参数count
传入数组的大小:
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]
通过组合两个数组来创建一个新的数组
使用加号运算符+
将两个类型兼容的已知数组相加来创建一个新的数组。新数组的类型通过相加的两个数组的类型来推断:
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoubles is of type [Double], and equals [2.5, 2.5, 2.5]
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
使用数组字面量创建数组
可以使用数组字面量创建数组,这种方式可以方便的将一个或多个值写入一个数组集合。一个数组字面量被写成一组值,以逗号分隔,被一对方括号包围:
[value1, value2, value3]
下面创建了一个名为shoppingList
的数组存储String
类型值:
var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items
变量shoppingList
声明为“一个字符值的数组”,写成[String]
。因为这个数组已经明确了类型String
,所以只允许存储String
类型值。这个例子中,数组shoppingList
用写在一个数组字面量中的两个String
值("Eggs"
和 "Milk"
)来初始化。
注意
数组shoppingList
声明为一个变量而不是一个常量,因为接下来的例子会向这个数组添加更多的元素。
例子中,这个数组字面量只包含两个String
值,并无其它。这符合变量shoppingList
声明的类型(一个只能包含String
值的数组),因此允许这个包含两个元素的数组字面量作为初值来初始化变量shoppingList
。
多谢Swift的类型推断,使用一个包含相同类型值的数组字面量来初始化数组时无需写明数组类型。shoppingList
的初始化可以简写为:
var shoppingList = ["Eggs", "Milk"]
因为数组字面量的所有值类型一致,Swift可以推断[String]
是使用变量shoppingList
的正确类型。
访问和修改数组
可以使用数组的的方法,属性或者下标来访问和修改数组。
使用只读属性count
来计算数组元素的数量。
print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."
使用isEmpty
布尔属性作为检查count
属性是否为0
的简便方法:
if shoppingList.isEmpty {
print("The shopping list is empty.")
} else {
print("The shopping list is not empty.")
}
// Prints "The shopping list is not empty."
使用append(_:)
方法在数组末尾增加一个元素:
shoppingList.append("Flour")
// shoppingList now contains 3 items, and someone is making pancakes
同样的,使用加等于运算符+=
拼接一个包含一个或多个兼容的元素的数组:
shoppingList += ["Baking Powder"]
// shoppingList now contains 4 items
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList now contains 7 items
使用下标语法 访问数组中的某个值,方法为在数组名后添加一对方括号,括号内传入你想访问的值的索引:
var firstItem = shoppingList[0]
// firstItem is equal to "Eggs"
注意
数组的第一个元素的索引为0
不是1
。Swift的数组索引总是从零开始的。
使用下标语法修改一个指定索引位置的值:
shoppingList[0] = "Six eggs"
// the first item in the list is now equal to "Six eggs" rather than "Eggs"
使用下标语法时,指定的索引值必须是合法的。例如,尝试写成这样shoppingList[shoppingList.count] = "Salt"
来在数组的末尾拼接一个元素会触发运行时错误。
也可以使用下标语法一次修改一个范围的值,即使替换值的长度与被替换值的长度不同。下面的例子将"Chocolate Spread"
,"Cheese"
和"Butter"
替换为"Bananas"
和"Apples"
:
shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList now contains 6 items
使用insert(_:at:)
方法在指定的索引位置插入一个元素:
shoppingList.insert("Maple Syrup", at: 0)
// shoppingList now contains 7 items
// "Maple Syrup" is now the first item in the list
调用insert(_:at:)
方法在购物清单的开头(0
索引位置)插入一个新的元素"Maple Syrup"
。
同样的,使用remove(at:)
方法删除一个元素。这个方法删除指定索引位置的元素并且返回被删除的元素(如果不需要可以忽略这个返回值):
let mapleSyrup = shoppingList.remove(at: 0)
// the item that was at index 0 has just been removed
// shoppingList now contains 6 items, and no Maple Syrup
// the mapleSyrup constant is now equal to the removed "Maple Syrup" string
注意
尝试访问或修改一个超出数组边界的索引对应的值会触发运行时错误。使用索引前可以将它和数组的count
属性比较来检查它是否合法。数组最大的合法索引是count - 1
因为数组是从0开始索引的,但是当count
等于0
时(意味着数组是空的),数组不存在合法的索引。
当一个元素被删除时,数组中的任何空位都会被关闭,因此索引0
的值再次等于"Six eggs"
:
firstItem = shoppingList[0]
// firstItem is now equal to "Six eggs"
使用removeLast()
而不是remove(at:)
方法删除数组的最后一个元素来避免查询数组的数量。同remove(at:)
一样,removeLast()
也返回删除的元素:
let apples = shoppingList.removeLast()
// the last item in the array has just been removed
// shoppingList now contains 5 items, and no apples
// the apples constant is now equal to the removed "Apples" string
使用for-in
循环遍历数组的所有元素:
for item in shoppingList {
print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas
如果同时需要数组的值和对应的整形索引,使用enumerated()
方法。对于数组中的每个元素,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
集合
集合 将相同类型的唯一值无序的存储在一个集合中。当顺序不重要或需要确保一个元素只出现一次时,可以使用集合而不是数组。
注意
Swift的Set
类型与Foundation的NSSet
类进行了桥接。
集合类型的哈希值
一个可哈希化的类型才能被存储进集合,也就是说这个类型必须提供一个方式来计算它自己的哈希值。一个哈希值是一个Int
类型的值,相等对象的哈希值必须一样,例如如果a == b
,那么必须a.hashValue == b.hashValue
。
默认Swift所有的基本类型(例如String
,Int
,Double
和Bool
)是可哈希化的,可以用作集合的值类型或者字典的键类型。没有关联值的枚举值默认也是可以哈希化的。
注意
可以通过让你的自定义类型遵循Swift标准库的Hashable
协议来让其作为集合的值类型或者字典的键类型。遵循Hashable
协议的类型必须提供一个名为hashValue
的可读的Int
型属性。根据相同或不同程序的不同实现,一个类型的hashValue
属性返回的值不要求是相同的。
因为Hashable
协议遵循Equatable
协议,所以遵循协议的类型必须提供一个等于运算符(==
)的实现。Equatable
协议要求任何==
的实现都是一个相等的关系。因此,==
的实现必须满足以下三个条件,对于a
,b
,c
的所有值:
-
a == a
(自反性) -
a == b
意味着b == a
(对称性) -
a == b && b == a
意味着a == c
(传递性)
集合类型语法
集合类型写成Set<Element>
,Element
是集合允许存储的类型。与数组不同,集合没有一个等价的简写形式。
创建和初始化一个空集合
使用初始化语法创建一个确定类型的空的集合:
var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// Prints "letters is of type Set<Character> with 0 items."
注意
根据构造器的类型,变量letters
被推断为Set<Character>
类型。
此外,如果上下文已经提供了类型信息,例如一个函数的参数或者一个已知类型的变量或者常量,可以使用一个空的数组字面量创建一个空集合:
letters.insert("a")
// letters now contains 1 value of type Character
letters = []
// letters is now an empty set, but is still of type Set<Character>
使用数组字面量创建一个集合
同样可以使用数组字面量创建集合,这种方式可以方便的将一个或多个值写入一个集合。
下面创建了一个名为favoriteGenres
的集合存储String
类型值:
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items
变量favoriteGenres
声明为“一个字符值的集合”,写成Set<String>
。因为这个集合已经明确了类型String
,所以只允许存储String
类型值。这个例子中,数组favoriteGenres
用写在一个数组字面量中的三个String
值("Rock"
,"Classical"
和 "Hip hop"
)来初始化。
注意
集合favoriteGenres
声明为一个变量而不是一个常量,因为接下来的例子会向这个集合添加或删除元素。
单凭一个数组字面量无法推断一个集合的类型,所以类型Set
必须被显示声明。但是由于Swift的类型推断,使用一个包含相同类型值的数组字面量来初始化集合时无需写明集合类型。favoriteGenres
的初始化可以简写为:
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
因为数组字面量的所有值类型一致,Swift可以推断Set<String>
是使用变量favoriteGenres
的正确类型。
访问和修改集合
可以使用集合的方法和属性来访问和修改集合。
使用只读属性count
来计算集合元素的数量。
print("I have \(favoriteGenres.count) favorite music genres.")
// Prints "I have 3 favorite music genres."
使用isEmpty
布尔属性作为检查count
属性是否为0
的简便方法:
if favoriteGenres.isEmpty {
print("As far as music goes, I'm not picky.")
} else {
print("I have particular music preferences.")
}
// Prints "I have particular music preferences."
调用集合的insert(_:)
方法向集合添加一个新的元素:
favoriteGenres.insert("Jazz")
// favoriteGenres now contains 4 items
调用remove(_:)
方法删除一个元素,如果存在的话就删除并返回删除的元素否则返回nil
。调用removeAll()
方法删除所有元素。
if let removedGenre = favoriteGenres.remove("Rock") {
print("\(removedGenre)? I'm over it.")
} else {
print("I never much cared for that.")
}
// Prints "Rock? I'm over it."
使用contains(_:)
方法检查一个集合是否包含一个指定元素。
if favoriteGenres.contains("Funk") {
print("I get up on the good foot.")
} else {
print("It's too funky in here.")
}
// Prints "It's too funky in here."
遍历集合
使用for-in
循环遍历集合的所有元素:
for genre in favoriteGenres {
print("\(genre)")
}
// Jazz
// Hip hop
// Classical
Swift的集合类型没有定义好的顺序。想要以特定的顺序遍历集合,使用sorted()
方法,它像数组使用<
运算符一样返回集合的元素。
for genre in favoriteGenres.sorted() {
print("\(genre)")
}
// Classical
// Hip hop
// Jazz
执行集合运算
可以高效的执行基础的集合运算,例如合并两个集合,求两个集合的交集。
基本的集合运算
下图描述了a
和b
两个集合进行不同的集合运算的结果,如阴影所示。
-
intersection(_:)
创建一个新的集合,取两个集合共有的值 -
symmetricDifference(_:)
创建一个新的集合,取两个集合独有但不共有的值 -
union(_:)
创建一个新的集合,取两个集合所有的值 -
subtracting(_:)
创建一个新的集合,取指定集合不包含的值
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]
集合关系和等同性
下图描述了三个集合a
,b
和c
的关系,重叠区域代表集合的共同元素。a
是b
的超集,因为a
包含b
的所有元素。相反的b
是a
的子集,因为b
的所有元素都被a
包含。a
和c
是不相交的,因为没有共同的元素。
- 使用等于运算符(
==
)判断两个集合是否包含所有的同样值 - 使用
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
字典
字典以无序的方式将相同类型的键和相同类型的值构成的关联对象存储在一个集合中。每一个值都和唯一的键关联,这个键作为这个值在字典的标识。与数组中的元素不同,字典的元素没有明确的顺序。当需要基于标识查找值的时候可以使用字典,这和现实世界中的字典用来查找一个特定单词的定义一样。
注意
Swift的Dictionary
类型与Foundation的NSDictionary
类进行了桥接。
字典类型的简写语法
Swift字典类型完整写法为Dictionary<Key, Value>
,Key
是可以用来作为字典键的值的类型,Value
是字典为那些键所存储的值的类型。
注意
字典的Key
类型必须遵循Hashable
协议,像集合的值类型一样。
同样可以将字典的类型简写为[Key: Value]
。尽管两种形式功能上是相同的,但更推荐简写形式而且简写形式贯穿本文。
创建一个空字典
和数组一样,可以使用初始化语法创建一个明确类型的空字典:
var namesOfIntegers = [Int: String]()
// namesOfIntegers is an empty [Int: String] dictionary
这个例子创建了一个类型为[Int: String]
的空字典来存储整型值的易读的名字。它的键是Int
类型,值是String
类型。
如果上下文已经提供了类型信息,可以使用一个空的字典字面量来创建一个空字典,写为[:]
(一个冒号在一对方括号中间):
namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type [Int: String]
使用字典字面量创建字典
可以使用字典字面量 创建字典,它的语法和之前提到的数组字面量相似。这种方式可以方便的将一个或多个键值对写入一个字典集合。
一个键值对 是由一个键和一个值组成的。在字典字面量中,每个键值对的键和值以冒号分隔。这些键值对被写成一个列表,以逗号分隔,被一对方括号包围:
[key 1: value 1, key 2: value 2, key 3: value 3]
下例创建了一个字典来存储国际机场的名字。这个字典中,键是三个单词的国际空中运输协会码,值是机场名:
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
airports
字典声明为[String: String]
类型,可以解读为“这个字典的键是String
类型,值也是String
类型”。
注意
airports
字典声明为一个变量而不是常量,因为接下来更多的机场会被添加到字典中。
airports
字典以包含两个键值对的字典字面量初始化。第一对包含一个键"YYZ"
和一个值"Toronto Pearson"
。第二对包含一个键"DUB"
和一个值"Dublin"
。
这个字典字面量包含两个String: String
对。这个键值类型符合airports
变量声明的类型(一个键为String
类型,值为String
类型的字典),因此允许这个包含两个元素的字典字面量作为初值来初始化字典airports
。
同数组一样,如果使用一个键和值类型一致的字典字面量来初始化一个字典,那么无需写明字典类型。airports
的初始化可以简写成:
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
因为字面量中的所有键类型相同,值类型也相同,Swift可以推断出[String: String]
是使用字典airports
的正确的类型。
访问和修改字典
使用方法,属性或者下标来访问或者修改字典。
和数组一样,使用只读属性count
来计算字典的元素数量:
print("The airports dictionary contains \(airports.count) items.")
// Prints "The airports dictionary contains 2 items."
使用isEmpty
布尔属性作为检查count
属性是否为0
的简便方法:
if airports.isEmpty {
print("The airports dictionary is empty.")
} else {
print("The airports dictionary is not empty.")
}
// Prints "The airports dictionary is not empty."
使用下标语法向字典添加一个新的元素。使用一个合适类型的新的键作为下标索引,并赋值一个合适类型的新值。
airports["LHR"] = "London"
// the airports dictionary now contains 3 items
同样可以使用下标语法修改一个特别键关联的值:
airports["LHR"] = "London Heathrow"
// the value for "LHR" has been changed to "London Heathrow"
除了下标语法,也可以使用字典的updateValue(_:forKey:)
方法设置或者更新一个特殊键对应的值。像上例的下标一样,如果键不存在,updateValue(_:forKey:)
方法为这个键设置一个新值,反之会更新这个值。与下标不同,当执行完更新后,updateValue(_:forKey:)
方法会返回旧值。这使你可以检查是否执行了更新。
updateValue(_:forKey:)
方法返回字典值类型的可选值。例如,一个字典存储String
值,这个方法返回一个String?
类型或者“可选的String
”型的值。更新之前值存在的话,这个可选型会包含这个旧值,反之返回nil
:
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
// Prints "The old value for DUB was Dublin."
同样可以使用下标语法来访问字典中一个特殊键对应的值。因为可能请求一个不存在值的键,字典的下标会返回这个字典值类型的可选值。如果字典包含所请求的键对应的值,下标返回一个包含这个值的可选值。相反,返回nil
:
if let airportName = airports["DUB"] {
print("The name of the airport is \(airportName).")
} else {
print("That airport is not in the airports dictionary.")
}
// Prints "The name of the airport is Dublin Airport."
可以使用下标语法删除一个键值对,方法为对这个键对应的值赋值为nil
:
airports["APL"] = "Apple International"
// "Apple International" is not the real airport for APL, so delete it
airports["APL"] = nil
// APL has now been removed from the dictionary
同样可以使用removeValue(forKey:)
方法删除一个键值对。这个方法在键值对存在的情况下会删除这个键值对并返回删除的值,反之返回nil
:
if let removedValue = airports.removeValue(forKey: "DUB") {
print("The removed airport's name is \(removedValue).")
} else {
print("The airports dictionary does not contain a value for DUB.")
}
// Prints "The removed airport's name is Dublin Airport."
遍历字典
使用for-in
循环遍历字典的键值对。字典的每个元素会被返回为一个(key, value)
类型的元组,可以将这个元组的成员解包为临时的常量或者变量作为遍历的一部分:
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
// YYZ: Toronto Pearson
// LHR: London Heathrow
同样可以通过访问字典的keys
和values
属性来访问一个字典的键或者值的集合:
for airportCode in airports.keys {
print("Airport code: \(airportCode)")
}
// Airport code: YYZ
// Airport code: LHR
for airportName in airports.values {
print("Airport name: \(airportName)")
}
// Airport name: Toronto Pearson
// Airport name: London Heathrow
如果你需要在字典的键或者值上使用Array
实例拥有的API,使用keys
和values
属性初始化一个新的数组:
let airportCodes = [String](airports.keys)
// airportCodes is ["YYZ", "LHR"]
let airportNames = [String](airports.values)
// airportNames is ["Toronto Pearson", "London Heathrow"]
Swift的Dictionary
类型没有定义好的顺序。想要按指定的顺序遍历字典的键或者值,在字典的keys
或者values
属性上使用sorted()方法。
上一篇:Swift-字符串和字符
下一篇:Swift-控制流