在Swift里,数组和字典里所能存放的值的类型是明确的。这意味着你不能误把一个错误类型的值添加到数组或字典里,也意味着你可以明白无误地知道从数组或字典里取得的值会是什么类型的。Swift集合是类型明确的,这保证了你的代码会清楚地知道它们所能处理的值的类型,并让你能在代码开发阶段就发现任何输入错误。
注意: Swift的数组在赋值给常量、变量或者传值给函数、方法时,它的行为表现和其它类型并不一样。详情参见 可变集合和 集合类型的赋值和拷贝行为
数组(Array)
数组在一个有序链表里存储了多个类型相同的值。同一个值可以在数组的不同位置出现多次。Swift的数组对它们能存放的值的类型是明确的。这不同于Objective-C的NSArray类和NSMutableArray类,Objective-C的数组能存储任何类型的对象,并且不提供关于这些对象自身的任何信息。在Swift里,任何一个特定的数组所能存储的值,其类型总会被确定下来,或者通过显式的类型说明,或者通过类型推断,并且该类型不必是类类型。例如,如果你创建了一个Int型数组,你就不能把任何非Int型的值插入这个数组。Swift数组是类型安全的,它总是知道它能存储什么值。
数组类型的简写语法
Swift数组的类型是Array<SomeType>,这里的SomeType是某种数组能够存放的类型。你也可以把数组类型简写为SomeType[]。尽管这两种形式功能上是一样的,我们更偏向于使用简写形式,本书里所有数组类型都是使用简写形式。数组字面量
可以用一个数组字面量来初始化一个数组,简单地把一个或多个值放在一起就可以了。数组字面量的写法是一行用逗号隔开的值,并在行的两端用一对方括号包起来:[value 1,value 2,value 3]
下面的例子创建了一个存放String类型值,名为shoppingList的数组:
- var shoppingList: String[] = ["Eggs","Milk"]
- // shoppingList 用两个元素完成初始化
注意:数组shoppingList被声明为变量(用关键字var)而不是常量(用关键字let),是因为在下面的例子中会往这个购物清单里添加更多的元素。在本例中,这个数组字面量只包含了两个String值。这和shoppingList变量定义中的类型一致(一个存放String值的数组),所以可以把这个数组字面量直接赋值给shoppingList进行初始化。
得益于Swift的类型推断,如果使用数组字面量来初始化一个数组,这个数组字面量里的值具有相同的类型,你可以不必明确写出这个数组的类型。上面初始化shoppingList的代码可以简写为:
- var shoppingList = ["Eggs","Milk"]
数组的存取与修改
对数组的存取与修改可以通过数组的方法和属性来进行,或者使用数组的下标语法。要知道数组中元素的数量,可以查看它的只读属性count:
- println("The shopping list contains \(shoppingList.count) items.")
- // 输出“The shopping list contains 2 items.”
- 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("Flour")
- // shoppingList现在包含3个元素了,看起来有人要摊薄饼啊
- shoppingList += "Baking Powder"
- // shoppingList现在包含4个元素了
- shoppingList += ["Chocolate Spread","Cheese","Butter"]
- // shoppingList现在包含7个元素了
- var firstItem = shoppingList[0]
- // firstItem 等于 "Eggs"
你可以使用下标语法来改变给定索引的已存在的值:
- shoppingList[0] = "Six eggs"
- // 这个清单的第一项现在是“Six eggs”了,而不是"Eggs"
- shoppingList[4...6] = ["Bananas","Apples"] // shoppingList现在包含6个元素
注意: 不能使用下标语法添加新元素到数组末尾。如果试图使用超出数组范围的下标来取用或存放一个元素,会产生运行时错误。在使用一个索引值之前,应该把它跟数组的count属性进行比较,以检测它是否有效。除非count是0(意味着这是个空数组),数组的最大有效索引总是count - 1,因为数组的索引是从0开始的。插入一个元素到特定位置,可以调用数组的insert(atIndex:)方法:
- shoppingList.insert("Maple Syrup",atIndex: 0)
- // shoppingList现在包含7个元素
- // 清单的第一个元素现在是"Maple Syrup"
类似地,你可以使用removeAtIndex方法从数组删除一个元素。该方法删掉指定索引上的元素,并返回这个被删掉的元素(如果你不需要返回值,可以忽略它):
- let mapleSyrup = shoppingList.removeAtIndex(0)
- // 索引为0的元素已从数组中删掉了
- // shoppingList现在包含6个元素,不包含"Maple Syrup"
- // 常量mapleSyrup现在等于被删掉的字符串"Maple Syrup"
- firstItem = shoppingList[0]
- // firstItem现在等于"Six eggs"
- let apples = shoppingList.removeLast() // 数组的最后一个元素被删除了 // shoppingList现在包含5个元素,不包含"cheese" // 常量apples现在等于被删掉的字符串"Apples"
数组的迭代访问
你可以通过for-in循环来迭代访问整个数组的值。
- for item in shoppingList {
- println(item)
- }
- // Six eggs
- // Milk
- // Flour
- // Baking Powder
- // Bananas
- 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
数组创建与初始化
可以通过初始化语法创建一个特定类型的空数组,数组中不包含任何初始值:
- var someInts = Int[]()
- println("someInts is of type Int[] with \(someInts.count) items.")
- // 输出 "someInts is of type Int[] with 0 items.
并且,如果上下文中已经提供了类型信息,例如方法的参数、已经定义类型的常量或变量,你就可以使用一个空的数组字面量来创建一个空的数组。空的数组字面量被写为[],就是一对不包括任何内容的方括号。
- someInts.append(3)
- // someInts现在包含了一个整型值 someInts = []
- // someInts现在成为了一个空数组,但其类型仍然是Int[]
- 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的类型被推断为Double[],值为[2.5,2.5,2.5]
字典(Dictionary)
字典是一种存储多个类型相同的值的容器。每个值都和一个唯一的键相对应,这个键在字典里就是其对应值的唯一标识。跟数组不同,字典里的元素并没有特定的顺序。在“字典”中使用键来查询其对应值的方式,跟在“现实世界的字典”中使用单词查询单词定义差不多。Swift的字典对它们能存放的键和值的类型是明确的。这不同于Objective-C的NSDictionary类和NSMutableDictionary类,Objective-C的字典能存储任何类型的对象作为键或值,并且不提供关于这些对象自身的任何信息。在Swift里,任何一个特定的字典键和值,其类型总会被确定下来,或者通过显式的类型说明,或者通过类型推断。
Swift的字典类型是Dictionary<KeyType,ValueType>,其中KeyType是字典中键的类型,ValueType是字典中值的类型。
对键类型KeyType的唯一限制是,它必须是可哈希的,也就是说,它必须能够提供一个方式让自己被唯一表示出来。Swift的所有基础类型(例如String、Int、Double和Bool)默认都是可哈希的,这些类型都能够用作字典中的键。枚举成员中没有绑定值的值(参见 枚举)默认也是可哈希的。
字典字面量
可以用一个字典字面量来初始化一个数组,其语法格式类似于之前看到的数组字面量。字典字面量是写一个具有单个或多个键值对字典的简便方法。键值对是一个键和一个值的组合。在字典字面量中,每对键值对中的键和值使用冒号分开,键值对之间用逗号分开,用一对方括号将这些键值对包起来:
[key 1:value 1,key 2:value 2,key 3:value 3]
下面的例子创建了一个存储国际机场名字的字典,在这个字典中,键是三字母的国际航空运输协会码,值是机场的名字:
- var airports: Dictionary<String,String> = ["TYO": "Tokyo","DUB": "Dublin"]
注意:字典airports被声明为变量(用关键字var)而不是常量(用关键字let),是因为在下面的例子中会往这个字典里添加更多的机场。这里的字典airports使用了一个字典字面量来初始化,该字典字面量含有两个键值对,第一对的键为"TYO"、值为"Tokyo",第二对的键为"DUB"、值为"Dublin"。
在本例中,此数组字面量只包含了两个String: String键值对。这和airports变量定义中的类型一致(键的类型为String,值的类型也为String),所以可以把这个字典字面量直接赋值给airports进行初始化。
就像数组一样,如果使用具有相应类型的键和值的字典字面量来对字典进行初始化,你可以不必明确写出这个字典的类型。上面初始化airports的代码可以简写为:
- var airports = ["TYO": "Tokyo","DUB": "Dublin"]
字典的存取与修改
对字典的存取与修改可以通过字典的方法和属性来进行,或者使用字典的下标语法。就像数组一样,可以通过只读属性count获得Dictionary中元素的数量:
- println("The dictionary of airports contains \(airports.count) items.")
- // 输出"The dictionary of airports contains 2 items."
- airports["LHR"] = "London"
- // 字典airports现在包含3个元素
- airports["LHR"] = "London Heathrow"
- // "LHR"的值被改为"London Heathrow
updateValue(forKey:)函数返回一个值的类型的可选值。例如一个值类型为String的字典,该函数返回值的类型为String?。如果更新前该键的值存在,函数返回值就是该键更新前的值,如果不存在,函数返回值就是nil:
- if let oldValue = airports.updateValue("Dublin International",forKey: "DUB") {
- println("The old value for DUB was \(oldValue).")
- }
- // 输出 "The old value for DUB was Dublin.
- if let airportName = airports["DUB"] {
- println("The name of the airport is \(airportName).")
- } else {
- println("That airport is not in the airports dictionary.")
- }
- // 输出 "The name of the airport is Dublin International."
- if let removedValue = airports.removeValueForKey("DUB") {
- println("The removed airport's name is \(removedValue).")
- } else {
- println("The airports dictionary does not contain a value for DUB.")
- }
- // 输出 "The removed airport's name is Dublin International."
字典的迭代访问
你可以通过for-in循环来迭代访问整个字典的键值对。它会对字典中的每个元素都会返回一个(key,value)元组,你可以把元组中的成员转为变量或常量来使用:
- for (airportCode,airportName) in airports {
- println("\(airportCode): \(airportName)")
- }
- // TYO: Tokyo // LHR: London Heathrow
可以通过字典的keys属性和values属性,获得一个可以迭代访问的集合:
- for airportCode in airports.keys {
- println("Airport code: \(airportCode)")
- }
- // Airport code: TYO
- // Airport code: LHR
- for airportName in airports.values {
- println("Airport name: \(airportName)")
- }
- // Airport name: Tokyo
- // Airport name: London Heathrow
- let airportCodes = Array(airports.keys)
- // airportCodes为["TYO","LHR"]
- let airportNames = Array(airports.values)
- // airportNames为["Tokyo","London Heathrow"]
注意: Swift的Dictionary类型不是一个有序集合,迭代访问字典的keys、values、键值对时候的顺序并无具体规定。
生成一个空字典
像数组一样,你可以使用初始化语法创建一个特定类型的空Dictionary:
- var namesOfIntegers = Dictionary<Int,String>()
- // namesOfIntegers是一个Dictionary<Int,String>类型的空字典
如果上下文中已经提供了类型信息,可以使用空字典字面量[:]来创建一个空字典:
- namesOfIntegers[16] = "sixteen"
- // namesOfIntegers现在含有1个键值对
- namesOfIntegers = [:]
- // namesOfIntegers又成为一个类型为Int,String的空字典
注意: 实际上,Swift的数组和字典类型是通过泛型集合实现的。要获得关于泛型类型和集合的更多资料,请查看 Generics。
集合的可变性
数组和字典都把多个值存放在一个集合里。如果你创建了一个数组或者字典,并且将之指定为变量,那么该集合就是可变的,这意味着在集合被创建后,可以通过增加或删除元素来改变集合的容量大小。相反地,如果你指定一个数组或字典为常量,那么该数组或字典就是不可变的,其容量大小不能被改变。对字典来说,不可变还意味着你不能改变字典里某个键的值。一个不可变的字典一旦被设置值后,它里面的内容就不能再改变。
但是,数组的不可变性跟字典略有不同。尽管你不能进行任何可能会改变数组大小的操作,但是你可以给数组中的某个索引设置一个新的值。这使得Swift的数组在大小固定的情况下能够达到最佳的性能。
Swift数组类型的可变性行为还会影响数组对象是如何被复制与修改的,详细信息可参见 集合类型的复制与拷贝行为。
注意: 在任何不需要改变集合大小的地方,最好将集合定义为不可变的。这么做能让Swift的编译器优化该集合的性能。