- 数组(Arrays)
- 集合(Sets)
- 字典(Dictionaries)
- 集合的可变性(Mutability of Collections)
Swift 语言提供经典的数组和字典两种集合类型来存储集合数据。数组用来按顺序存储相同类型的数据。字典虽然无序存储相同类型数据值但是需要由独有的标识符引用和寻址(就是键值对)。
Swift 语言里的数组和字典中存储的数据值类型必须明确。 这意味着我们不能把不正确的数据类型插入其中。 同时这也说明我们完全可以对获取出的值类型非常自信。 Swift 对显式类型集合的使用确保了我们的代码对工作所需要的类型非常清楚,也让我们在开发中可以早早地找到任何的类型不匹配错误。
注意:Swift 的数组结构在被声明成常量和变量或者被传入函数与方法中时会相对于其他类型展现出不同的特性。 获取更多信息请参见集合的可变性与集合在赋值和复制中的行为章节。
数组
数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。
Swift 数组特定于它所存储元素的类型。这与 Objective-C 的 NSArray 和 NSMutableArray 不同,这两个类可以存储任意类型的对象,并且不提供所返回对象的任何特别信息。在 Swift 中,数据值在被存储进入某个数组之前类型必须明确,方法是通过显式的类型标注或类型推断,而且不是必须是class
类型。例如: 如果我们创建了一个Int
值类型的数组,我们不能往其中插入任何不是Int
类型的数据。 Swift 中的数组是类型安全的,并且它们中包含的类型必须明确。
数组的简单语法
写 Swift 数组应该遵循像Array<SomeType>
这样的形式,其中SomeType
是这个数组中唯一允许存在的数据类型。 我们也可以使用像[SomeType]
这样的简单语法。 尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。
数组构造语句
我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值。[value 1,value 2,value 3]
。
下面这个例子创建了一个叫做shoppingList
并且存储字符串的数组:
var shoppingList: [String] = ["Eggs","Milk"]
// shoppingList 已经被构造并且拥有两个初始项。
shoppingList
变量被声明为“字符串值类型的数组“,记作[String]
。 因为这个数组被规定只有String
一种数据结构,所以只有String
类型可以在其中被存取。 在这里,shoppinglist
数组由两个String
值("Eggs"
和"Milk"
)构造,并且由字面量定义。
注意:
Shoppinglist
数组被声明为变量(var
关键字创建)而不是常量(let
创建)是因为以后可能会有更多的数据项被插入其中。
在这个例子中,字面量仅仅包含两个String
值。匹配了该数组的变量声明(只能包含String
的数组),所以这个字面量的分配过程就是允许用两个初始项来构造shoppinglist
。
由于 Swift 的类型推断机制,当我们用字面量构造只拥有相同类型值数组的时候,我们不必把数组的类型定义清楚。 shoppinglist
的构造也可以这样写:
var shoppingList = ["Eggs","Milk"]
因为所有字面量中的值都是相同的类型,Swift 可以推断出[String]
是shoppinglist
中变量的正确类型。
访问和修改数组
我们可以通过数组的方法和属性来访问和修改数组,或者下标语法。还可以使用数组的只读属性count
来获取数组中的数据项数量。
println("The shopping list contains \(shoppingList.count) items.")
// 输出"The shopping list contains 2 items."(这个数组有2个项)
使用布尔项isEmpty
来作为检查count
属性的值是否为 0 的捷径。
if shoppingList.isEmpty {
println("The shopping list is empty.")
} else {
println("The shopping list is not empty.")
}
// 打印 "The shopping list is not empty."(shoppinglist不是空的)
也可以使用append
方法在数组后面添加新的数据项:
shoppingList.append("Flour")
// shoppingList 现在有3个数据项,有人在摊煎饼
除此之外,使用加法赋值运算符(+=
)也可以直接在数组后面添加一个或多个拥有相同类型的数据项:
shoppingList += ["Baking Powder"]
// shoppingList 现在有四项了
shoppingList += ["Chocolate Spread","Cheese","Butter"]
// shoppingList 现在有七项了
可以直接使用下标语法来获取数组中的数据项,把我们需要的数据项的索引值放在直接放在数组名称的方括号中:
var firstItem = shoppingList[0]
// 第一项是 "Eggs"
注意第一项在数组中的索引值是0
而不是1
。 Swift 中的数组索引总是从零开始。
我们也可以用下标来改变某个已有索引值对应的数据值:
shoppingList[0] = "Six eggs"
// 其中的第一项现在是 "Six eggs" 而不是 "Eggs"
还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把"Chocolate Spread"
,"Cheese"
,和"Butter"
替换为"Bananas"
和 "Apples"
:
shoppingList[4...6] = ["Bananas","Apples"]
// shoppingList 现在有六项
注意:我们不能使用下标语法在数组尾部添加新项。如果我们试着用这种方法对索引越界的数据进行检索或者设置新值的操作,我们会引发一个运行期错误。我们可以使用索引值和数组的
count
属性进行比较来在使用某个索引之前先检验是否有效。除了当count
等于 0 时(说明这是个空数组),最大索引值一直是count - 1
,因为数组都是零起索引。
调用数组的insert(atIndex:)
方法来在某个具体索引值之前添加数据项:
shoppingList.insert("Maple Syrup",atIndex: 0)
// shoppingList 现在有7项
// "Maple Syrup" 现在是这个列表中的第一项
这次insert
函数调用把值为"Maple Syrup"
的新数据项插入列表的最开始位置,并且使用0
作为索引值。
类似的我们可以使用removeAtIndex
方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它):
let mapleSyrup = shoppingList.removeAtIndex(0)
// 索引值为0的数据项被移除
// shoppingList 现在只有6项,而且不包括Maple Syrup
// mapleSyrup常量的值等于被移除数据项的值 "Maple Syrup"
数据项被移除后数组中的空出项会被自动填补,所以现在索引值为0
的数据项的值再次等于"Six eggs"
:
firstItem = shoppingList[0]
// firstItem 现在等于 "Six eggs"
如果我们只想把数组中的最后一项移除,可以使用removeLast
方法而不是removeAtIndex
方法来避免我们需要获取数组的count
属性。就像后者一样,前者也会返回被移除的数据项:
let apples = shoppingList.removeLast()
// 数组的最后一项被移除了
// shoppingList现在只有5项,不包括cheese
// apples 常量的值现在等于"Apples" 字符串
数组的遍历
我们可以使用for-in
循环来遍历所有数组中的数据项:
for item in shoppingList {
println(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas
如果我们同时需要每个数据项的值和索引值,可以使用全局enumerate
函数来进行数组遍历。enumerate
返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:
for (index,value) in enumerate(shoppingList) {
println("Item \(index + 1): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas
更多关于for-in
循环的介绍请参见for 循环。
创建并且构造一个数组
我们可以使用构造语法来创建一个由特定数据类型构成的空数组:
var someInts = [Int]()
println("someInts is of type [Int] with \(someInts.count) items。")
// 打印 "someInts is of type [Int] with 0 items。"(someInts是0数据项的Int[]数组)
注意someInts
被设置为一个[Int]
构造函数的输出所以它的变量类型被定义为[Int]
。
除此之外,如果代码上下文中提供了类型信息, 例如一个函数参数或者一个已经定义好类型的常量或者变量,我们可以使用空数组语句创建一个空数组,它的写法很简单:[]
(一对空方括号):
someInts.append(3)
// someInts 现在包含一个INT值
someInts = []
// someInts 现在是空数组,但是仍然是[Int]类型的。
Swift 中的Array
类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(count
)和适当类型的初始值(repeatedValue
)传入数组构造函数:
var threeDoubles = [Double](count: 3,repeatedValue:0.0)
// threeDoubles 是一种 [Double]数组,等于 [0.0,0.0,0.0]
因为类型推断的存在,我们使用这种构造方法的时候不需要特别指定数组中存储的数据类型,因为类型可以从默认值推断出来:
var anotherThreeDoubles = Array(count: 3,repeatedValue: 2.5)
// anotherThreeDoubles is inferred as [Double],and equals [2.5,2.5,2.5]
最后,我们可以使用加法操作符(+
)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来:
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles 被推断为 [Double],2.5]
集合
集合用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以把集合当做是数组另一形式。
注意:Swift的
Set
类型被桥接到Fundation
中的NSSet
类关于使用Fundation
和Cocoa
中集合的知识,请看Swift与Cocoa和Objective-C使用
Set类型语法
Swift中的Set
类型被写为Set<SomeType>
,这里的SomeType
表示Set
中允许存储的类型,和数组不同的是,集合没有等价的简化形式。
创建和构造一个Set
你可以通过构造器语法创建一个特定类型的空集合:
var letters = Set<Character>()
println("letters is of type Set<Character> with \(letters.count) items.")
// 打印 "letters is of type Set<Character> with 0 items."
注意这里的letters
变量的类型来自于构造器的类型,其为Set<Character>
。
另外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,你可以通过一个空的数组字面量创建一个空的Set
:
letters.insert("a")
// letters现在含有1个Character类型的值
letters = []
// letters现在是一个空的Set,但是它依然是Set<Character>类型
集合与数组字面量
你可以使用一个数组字面量来构造一个集合,并且可以使用简化形式写一个或者多个值作为集合元素。
下面的例子创建一个称之为favoriteGenres
的集合来存储String
类型的值:
var favoriteGenres: Set<String> = ["Rock","Classical","Hip hop"]
// favoriteGenres被构造成含有三个初始值的集合
这个favoriteGenres
变量被声明为“一个String
值的集合”,写为Set<String>
。由于这个特定的集合含有指定String
类型的值,所以它只允许存储String
类型值。这里的favoriteGenres
变量有三个String
类型的初始值("Rock
","Classical
"和"Hip hop
"),并以数组字面量的方式出现。
注意:
favoriteGenres
被声明为一个变量(拥有var
标示符)而不是一个常量(拥有let
标示符),因为它里面的元素将会在下面的例子中被增加或者移除。
一个Set
类型不能从数组中字面量中独立地被推断出来,因此Set
类型必须显式声明。然而,由于Swift的类型推导功能,如果你想使用一个数组字面量构造一个Set并且该数组字面量中的所有元素类型相同,那么你无须写出Set
的具体类型。favoriteGenres
的构造形式可以采用简化的方式代替:
var favoriteGenres: Set = ["Rock","Hip hop"]
由于数组字面量中的所有元素类型相同,Swift可以推断出Set<String>
作为favoriteGenres
变量的正确类型。
访问和修改一个Set
你可以通过Set
的属性和方法来访问和修改一个Set
.
为了找出一个Set
中元素的数量,可以使用其只读属性count
:
println("I have \(favoriteGenres.count) favorite music genres.")
// 打印 ""I have 3 favorite music genres.""
使用布尔属性isEmpty
作为一个缩写形式去检查count
属性是否为0
:
if favoriteGenres.isEmpty {
println("As far as music goes,I'm not picky.")
} else {
println("I have particular music preferences.")
}
// 打印 "I have particular music preferences."
你可以通过调用Set
的insert(_:)
方法添加一个新的元素:
favoriteGenres.insert("Jazz")
// favoriteGenres 现在包含4个元素
你可以通过调用Set
的remove(_:)
方法去删除一个元素,如果该值是该Set
的一个元素则删除该元素并且返回被删除的元素值,否认如果该Set
不包含该值,则返回nil
。另外,Set
中的所有元素可以通过它的removeAll()
方法删除。
if let removedGenre = favoriteGenres.remove("Rock") {
println("\(removedValue)? I'm over it.")
} else {
println("I never much cared for that.")
}
// 打印 "Rock? I'm over it."
使用contains(_:)
方法去检查Set
中是否包含一个特定的值。
if favoriteGenres.contains("Funk") {
println("I get up on the good foot.")
} else {
println("It's too funky in here.")
}
// 打印 "It's too funky in here."
遍历一个Set
你可以在一个for-in
循环中遍历一个Set
中的所有值。
for genre in favoriteGenres {
println("\(value)")
}
// Classical
// Jazz
// Hip hop
更多关于for-in
循环信息,请看For循环
Swift的Set
类型没有确定的顺序,为了按照特定顺序来遍历一个Set
中值可以使用全局sorted
函数,它将根据提供的序列返回一个排序的集合.
-> for genre in sorted(favoriteGenres) {
println("\(genre)")
}
// prints "Classical"
// prints "Hip hop"
// prints "Jazz
完成集合操作
你可以高效的完成Set
的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。
构造集合
下面的插图描述了两个集合-a
和b
-以及通过阴影部分的区域显示集合各种操作的结果。
- 使用
union(_:)
方法根据两个集合的值创建一个新的集合。 - 使用
subtract(_:)
方法根据不在该集合中的值创建一个新的集合。 - 使用
intersect(_:)
方法根据两个集合中都包含的值创建的一个新的集合。 - 使用
exclusiveOr(_:)
方法根据值在一个集合中但不在两个集合中的值创建一个新的集合。
let oddDigits: Set = [1,3,5,7,9]
let evendigits: Set = [0,2,4,6,8]
let singleDigitPrimeNumbers: Set = [2,7]
sorted(oddDigits.union(evendigits))
// [0,1,2,3,4,5,6,7,8,9]
sorted(oddDigits.intersect(evendigits))
// []
sorted(oddDigits.subtract(singleDigitPrimeNumbers))
// [1,9]
sorted(oddDigits.exclusiveOr(singleDigitPrimeNumbers))
// [1,9]
集合比较
下面的插图描述了三个集合-a
,b
和c
,以及通过悬浮区域表述集合间共享的元素。Set a
是Set b
的父集合,因为a
包含了b
中所有的元素,相反的,Set b
是a
的子集合,因为属于b
的元素也被a
包含。Set b
和Set c
彼此不关联,因为它们之间没有共同的元素。
- 使用“是否等”运算符(
=
)来判断两个集合是否包含相同的值。 - 使用
isSubsetof(_:)
方法来判断一个集合中的值是否也被包含在另外一个集合中。 - 使用
isSupersetof(_:)
方法来判断一个集合中包含的值是另一个集合中所有的值。 - 使用
isstrictSubsetof(_:)
或者isstrictSupersetof(_:)
方法来判断一个集合是否是另外一个集合的子集合或者父集合并且和特定集合不相等。 - 使用
isdisjointWith(_:)
方法来判断两个结合是否不含有相同的值。
let houseAnimals: Set = ["