- Swift權威指南
- 李寧編著
- 264字
- 2019-01-02 10:22:13
第4章 此字典非彼字典——數組和字典
Swift語言簡化了集合的使用,在Swift語言中只提供了數組(Array)和字典(Dictionary)兩種集合。分別用于管理單值集合和雙值集合(key-value集合)。盡管在Swift語言中仍然可以使用cocoa library中的相應類型,如NSDictionary、NSArray等。但在實際應用中,應盡量避免使用 cocoa 中的類型,這是因為 Swift 中的內置類型提供了更強大的功能和錯誤校驗機制。本章將會深入介紹這兩種集合的功能和使用方法。
本章要點
□ 創建數組和字典
□ 初始化數組和字典
□ 創建空的數組和字典
□ 兩個數組相加的規則
□ 獲取和設置數組與字典的值
□ 向數組和字典中添加值
□ 枚舉數組和字典中的值
□ 將字典中的keys和values轉換為數組
4.1 數組(Array)
源代碼文件:src/ch04/array/array/main.swift
Swift中的數組比其他語言中數組功能更強大。其他語言中的數組通常是一段連續的內存空間,可以快速地定位到任何一個數組元素,但不能對數組元素進行增加和刪除操作。而Swift中的數組相當于數組和List的結合體,對數組中的元素進行增、刪、改等操作非常容易。
4.1.1 創建和初始化數組
最簡單的創建數組的方式是直接用數組初始值進行創建,這樣創建和初始化數組就會同時完成。數組值用一對中括號([...])表示,數組值之間用逗號(,)分隔。例如,下面的代碼創建了一個String類型的數組,并且為該數組初始化了兩個數組元素,最后輸出該數組中的元素值。
let firstArray = ["abc", "efg"]
println(firstArray) // 輸出[abc, efg]
要注意的是,Swift中的數組如果聲明為常量(let),不僅意味著不能修改數組本身,也不能修改數組中的元素值。這一點和其他語言中的數組常量還是有一定區別的。大多數語言將數組定義為常量,指標是數組本身不可改變,而數組元素還是可以改變的。
let array1 = ["abc", "efg"]
var array2 = ["abc", "efg"]
array1 = ["abc"] // 錯誤,不能修改數組本身
array1[0] = "a" // 錯誤,不能修改數組元素值
array2[1] = "xyz" // 正確,因為array2被聲明為變量
Swift編譯器是可以根據初始化時數組元素的類型推斷數組類型的,例如,前面代碼中的array1中的兩個數組元素的類型都是String,所以array1是String類型數組。
我們還可以為數組指定類型,語法格式是Array<Type>,也可以簡寫為[Type ]。例如,下面的代碼分別聲明了typeArray1和typeArray2兩個數組,其中typeArray1是String類型數組,typeArray2是Int類型數組。而且typeArray1只聲明了一個數組,并未初始化。
var typeArray1:[String];
var typeArray2:Array<Int>= [1,2,3,4]
如果只是聲明了數組,但并未初始化該數組,是不能被引用的。例如,typeArray1 是不能用下面的代碼輸出的。
println(typeArray1)
據官方文檔解釋 Swift 中的數組和 Objective-C 中的數組不同,在 Objective-C 中使用NSArray和NSMutableArray來處理數組,其中前者是只讀數組,不可修改數組值,而后者是可變數組。不管是哪個數組,都可以存儲任何類型的值(NSObject)。而Swift中的數組雖然也分為只讀數組和可變數組,但對于同一個數組中的元素類型,必須都相同。
根據這個規則,如果在聲明數組時指定了類型,毫無疑問,這個規則是適用的,例如,下面的代碼是無法編譯通過的。
// 由于最后一個數組元素類型是String,所以無法成功編譯
var typeArray2:Array<Int>= [1,2,3,"xyz"]
如果在聲明數組時不指定數據類型,好像這個規則并不適用,例如,下面的代碼是可以編譯通過的。
var myArray= [1,2,3,"xyz"] // 可以編譯通過
雖然這行代碼可以成功進行編譯,但myArray的數據類型已經不再是Swift中的數組了。其實這是 Swift 編譯器玩的一個魔法。當檢測到數組中所有元素類型都相同時,就會將變量或常量類型設為 Swift 數組,如果發現元素類型不相同,那么就會將變量或常量類型設為NSArray。從這一點可以看出,Swift除了提供了自己的一套數據類型外,還可以使用CocoaLibrary中相應的類作為數據類型。當然,這一切都是Swift編譯器弄的。
在默認情況下,myArray 的類型是 NSArray。也就是說,myArray 默認是只讀的。如果想將myArray設為可變數組,并且每一個數組元素可以存儲不同類型的值,那么就只用顯示聲明為NSMutableArray了。
var myArray:NSMutableArray= [1,2,3,"xyz"] // 可以編譯通過
這樣的話,就可以使用NSMutableArray的addObject方法以及其他方法修改myArray數組的值了。當然,如果數組中的元素類型都相同,建議還是使用 Swift 中的數組類型。因為數組類型中的元素類型是確定的,所以 Swift 編譯器可以檢查數組元素類型的正確性。而NSArray和NSMutableArray中的數組元素類型是NSObject,Swift編譯器是不會檢查這些數組元素的類型的,所以拋出運行時異常的可能性更高。
4.1.2 創建空數組
在上一節聲明的數組里面都至少有一個數組元素。如果在初始化時不指定任何數組元素,那么Swift編譯器會認為這是一個空的數組(數組元素個數為0的數組)。例如,下面的代碼將創建一個空數組。
var nullArray = []
對于這行代碼,Swift編譯器并不能推導數組的類型,所以不會使用Swift自己的數組類型,而會創建一個NSArray對象,當然里面沒有任何數組元素。
如果要創建Swift內置數組類型的空數組(需要指定數組元素的數據類型),可以使用下面3種形式。
var nullArray1 = [Int]() // 創建一個Int類型的空數組
var nullArray2:[String] = []; // 創建一個String類型的空數組
var nullArray3:Array<Float> = []; // 創建一個Float類型的空數組
4.1.3 創建固定長度的數組
初始化數組時還可以指定數組的初始長度,并且為所有的數組元素初始化同一個值。例如,下面的代碼創建了一個初始化長度為4的Int類型的數組,并將這4個數組元素都初始化為2。
var fourInts = [Int](count:4, repeatedValue:2)
println(fourInts.count)
4.1.4 數組的加法
在 Swift 中指定數組的直接相加,數組相加也就是合并數組(將兩個相加的數組前后連接)。例如,下面的代碼將array1和array2相加得到arraySum,最后輸出arraySum。
var array1 = ["a","d"]
var array2 = ["c","d"]
var arraySum = array1 + array2
println(arraySum) // 輸出[a, b, c, b]
4.1.5 獲取和設置數組元素值
Swift中的數組與其他語言中的數組在獲取和設置數組元素值方面是相同的。不管是獲取元素值,還是設置元素值,都可以使用 arrayName[index]形式。其中 arrayName 表示數組變量或常量名稱,index 表示數組元素索引,該索引值從 0 開始。如果是設置數組元素值,arrayName只能是變量名。
var array1 = [1,2,3,4]
// 獲取并輸出第2個數組元素值
println(array1[1])
// 設置第3個數組元素的值
array1[2] = 20
println(array1)
4.1.6 數組區間賦值
Swift中的數組還支持使用閉區間操作符(...)和半閉半開操作符(..<)進行賦值,使用這樣的賦值方法可以同時對多個數組元素進行賦值,就和初始化數組一樣。賦值的語法規則如下。
arrayName[min...max] = [item1, item2, item3,...,itemn]
arrayName[min..<max] = [item1, item2, item3,...,itemn]
下面的代碼是典型的區間賦值案例。
var products = ["iPhone4", "iPad2", "Nexus 5"]
products[0...2] = ["iPhone5", "iPad4", "Nexus 6"]
// 輸出[iPhone5, iPad4, Nexus 6]
println(products)
既然是區間賦值,那么就可能出現區間的范圍和右側賦值的元素個數不同的情況,或者是max和min超過了數組的上下邊界。如果是后者,那么會直接拋出異常。如果是前者,會分如下幾種情況處理。
□ 用于賦值的元素個數超過了區間的范圍,則追加或插入數組元素。例如,arrayName[0...1] = [item1,item2,item3]。很明顯,item3是多余的。對于這種情況,會直接在arrayName[1]的后面追加一個item3。如果arrayName[1]后面還有元素,則插入。否則是最近。
□ 如果賦值的元素個數小于區間的范圍,則刪除多余的數組元素。例如,arrayName的初始值是[item1, item2,item3],使用arrayName[0...2] = [item1, item2],很明顯,用于賦值的數組元素個數比區間范圍少一個,所以arrayName[2]將被刪除,最后arrayName的值是[item1, item2]
□ 如果區間賦值范圍的min不是從0開始(如arrayName[1..2]),那么索引比min小的數組元素并不會被刪除,而會被忽略(原封不動地放在哪,不去管它)。
下面幾行代碼演示了這幾種區間賦值的情況,大家可以不看下面的答案,看看能不能猜出輸出結果。
var products = ["iPhone4", "iPad2", "Nexus 5"]
products[0...2] = ["iPhone5", "iPad4", "Nexus 6"]
println(products)
products[0...1] = ["iPhone6", "iPad5", "Nexus 7"]
println(products)
products[0..<4] = ["iPhone6", "iPad5", "Nexus 7", "MBP", "iMac"]
println(products)
products[1...4] = ["iPhone7", "iPad5", "Nexus 7", "MBP"]
println(products)
products[0...4] = ["iPhone6", "iPad5"]
println(products)
運行這段代碼,會輸出如下的結果。
[iPhone5, iPad4, Nexus 6]
[iPhone6, iPad5, Nexus 7, Nexus 6]
[iPhone6, iPad5, Nexus 7, MBP, iMac]
[iPhone6, iPhone7, iPad5, Nexus 7, MBP]
[iPhone6, iPad5]
4.1.7 添加和刪除數組元素
除了上一講介紹的通過區間賦值可以添加和刪除數組元素外,還可以通過一些方法添加和刪除數組元素。添加數組元素的方法如下。
□ append:在數組末尾追加數組元素。
□ insert:在指定位置插入數組元素。
刪除數組元素的方法如下。
□ removeLast:刪除最后一個數組元素。
□ removeAtIndex:刪除指定位置的數組元素。
□ removeAll:刪除數組中所有的數組元素。
除了使用方法完成對數組元素的添加和刪除外,還可以使用復合加法賦值(+=)來追加數組元素。如果追加一個數組元素,使用arrayName += item1形式,如果追加多個數組元素,可以使用arrayName += [item1, item2,...itemn]形式。
數組還提供了一些方法來判斷數組中是否有元素,還有多少個元素。例如,下面兩個方法分別用來判斷數組是否為空,以及數組中的元素個數。
□ empty:判斷數組是否為空。
□ count:返回數組中元素的個數。
下面是使用這幾個方法添加和刪除數組元素代碼。
var cities = ["沈陽", "北京"]
// 在數組的最后追加一個數組元素
cities.append("上海")
// 輸出結果:[沈陽, 北京, 上海]
println(cities)
cities.insert("廣州", atIndex:1)
// 輸出結果:[沈陽, 廣州, 北京, 上海]
println(cities)
// 刪除最后一個數組元素
cities.removeLast();
// 輸出結果:[沈陽, 廣州, 北京]
println(cities)
// 刪除第2個數組元素
cities.removeAtIndex(1)
// 輸出結果:[沈陽, 北京]
println(cities)
// 輸出結果:2
println(cities.count)
// 清除所有的數組元素
cities.removeAll(keepCapacity: true)
// 判斷cities數組是否為空(沒有任何數組元素)
if cities.isEmpty
{
println("數組為空")
}
// 追加一個數組元素
cities += "蘭州"
// 追加兩個數組元素
cities += ["西安","云南"]
// 輸出結果:[蘭州, 西安, 云南]
println(cities)
4.1.8 枚舉數組中的所有元素
枚舉數組元素的方法通常可以使用如下兩種方法。
□ 從數組的第 1 個元素循環到數組的最后一個元素,然后利用索引獲取每一個數組元素。
□ 將數組當成一個集合,然后枚舉集合中的所有元素。
如果采用第一種方法,可以指定枚舉的訪問,也就是數組的區間(min...max)。而使用第二種方法,只能枚舉數組中的所有元素。
下面是使用這兩種方法枚舉數組中所有元素的代碼。
let provinces = ["遼寧","廣東","福建"]
var i = 0
// 使用第一種方法輸出當前數組的索引和數組元素值
for i in 0..<provinces.count
{
print("\(i):{" + provinces[i] + "}")
}
println()
// 使用第二種方法輸出數組中所有的元素值
for value in provinces
{
print("<"+value+">")
}
println()
// 使用第二種方法輸出數組中所有的元素值和當前的數組索引
// 這里使用了一個全局的enumerate函數,用于獲取存儲數組索引和數組元素的結構體(struct)
// 這樣就可以同時獲取數組索引和數組元素了。關于struct的詳細內容會在后面的章節中介紹
for (index, value) in enumerate(provinces)
{
println("數組索引:\(index) 數組元素:\(value)")
}
4.2 字典(Dictionary)
源代碼文件:src/ch04/dictionary/dictionary/main.swift
字典和 Java 中的 Map 類似,用于存儲 key-value 類型的數據,不過功能比傳統的 Map更強大。本節將詳細介紹字典的各種使用方法。
4.2.1 創建和初始化字典
在Swift中,字典使用Dictionary結構體表示,所以可以直接使用Dictionary創建一個字典,并且在創建的過程中要指定key和value 的數據類型(如Dictionary<String, String>),一旦指定,在向字典添加key-value數據時類型就必須一致了。所以Dictionary和Array一樣。key和value的數據類型在創建字典之初就已經指定了,以后都不能更改。
在初始化字典時和數字一樣,要使用一對中括號([...])。只是寫法和JSON定義數組的樣式類似。key-value 之間用逗號( , )分隔,key 和 value 之間用冒號分隔(如[key1:value1,key2:value2])。但有一點是不同的,在JSON中,所有的key和value都必須用雙引號括起來,而字典的key和value如果不是String類型,就不需要使用雙引號括起來,該使用什么類型的值,就是用什么類型的值。
如果要利用 Swift 的推導功能,可以省略 Dictionary(但指定類型時要改用方括號,如[String, String]),甚至可以不指定類型,只進行初始化。下面是這 3 種創建和初始化字典的方式。
// 標準的創建和初始化字典的方法
var employee1: Dictionary<String,String> = ["name":"bill", "company":"Microsoft"]
// 省略了Dictionary
var employee2: [String: String] = ["name":"bill", "company":"Microsoft"]
// 直接進行初始化
var employee3 = ["name":"bill", "company":"Microsoft"]
如果不指定key-value的數據類型,并且初始化時每一對key-value值的類型不一致,那么Swift編譯器將會認為該字典變量或常量是NSDictionary類型。
// employee不是Swift中的字典類型,而是NSDictionary類型
var employee = ["name":"bill", "age":50]
如果想使用可修改的字典對象(NSMutableDictionary),可以直接將常量或變量聲明為NSMutableDictionary類型。
var employee:NSMutableDictionary = ["name":"bill", "age":50]
注意
字典與數組一樣,如果使用 let 聲明,不僅意味著不能修改字典本身,連字典中的值也不能修改。
4.2.2 創建空的字典
空字典就是沒有任何數據的字典。創建一個空的字典有如下兩種方法。
// 方法1
var nullDict1 = Dictionary<Int, String>()
// 方法2
var nullDict2 = [Int: String]()
如果向空字典中添加數據,那么再將字典設為[:],就可以再次清空字典。
var nullDict = Dictionary<Int, String>()
nullDict[10] = "iPhone6"
nullDict = [:] //再次清空字典
不過要注意,不能在初始化字典時使用[:],否則 Swift編譯器無法推導key和value的數據類型,這時就會認為nullDict是NSDictionary類型。
4.2.3 添加、修改和刪除字典中的數據
向字典中添加數據可以使用類似于數組的方式,也就是dictionaryName[key] = value。如果key在字典中不存在,那么就會將這個key-value對添加到字典中;如果key存在,就是修改原來的value了。例如,下面的幾行代碼分別向product和person中添加了相應的數據。
var product = ["name":"iPhone", "company":"apple"]
var person = [Int:String]()
product["time"] = "2014-1-1"
person[10] = "Bill"
person[20] = "Mary"
如果想修改字典中的值,除了使用 dictionaryName 的形式外,還可以使用 updateValue方法,該方法返回了修改之前的值,具體代碼如下。
if let oldValue = person.updateValue("John", forKey:10)
{
println("舊的值是\(oldValue)")
}
如果想更方便地獲取修改之前的值,可以考慮使用updateValue方法。
刪除字典中的一個key-value對,可以將value設為nil即可。例如,下面的代碼刪除了person中key的數據。
person[20] = nil
刪除數據還可以使用 removeValueForKey 方法,該方法需要指定要刪除數據的 key,并且返回待刪除數據中的value。下面的代碼將刪除person中key為10的數據,并且返回相應的value。
let oldValue = person.removeValueForKey(10)
println(oldValue)
// 輸出當前person中的key-value個數(應為0)
println(person.count)
4.2.4 獲取字典中的值
如果想根據key獲取value,只需要使用dictionaryName[key]即可。如果使用這種形式賦值,那么當key存在時就是修改value;如果key不存在,就會在字典中追加一條key-value。但使用這些形式獲取value時,如果key存在,則會直接返回與key對應的value;如果key不存在,則會返回nil。
var persons = [10:"Bill", 20:"Mike"]
// 輸出Bill
println(persons[10])
// 輸出nil
println(persons[30])
4.2.5 將value轉換為指定的類型值
有時我們需要獲取與value原來類型不同的類型值。例如,value是String類型,在獲取時,value 正好可以轉換為 Int 類型,所以為了方便,在獲取時需要將String 類型轉換為 Int類型的值。另外,如果字典中的value的數據類型不同時,字典就會變成NSDictionary類型,這時每一個value都會變成NSObject。所以在獲取value時,通常需要將這些value轉換為相應的數據類型。下面的代碼演示了如何在不同類型之間進行轉換。
var myDict1 = ["key1":"20", "key2":"abc"]
// String轉換為Int類型的值,轉換時需要使用可選類型變量(類型后面加?)
var value1:Int? = myDict1["key1"]?.toInt()
var myDict2 = ["key1": "200", "key2": 30]
// 將原本是Int類型的NSObject類型值轉換為Int類型的值,這時不需要使用可選類型變量
var value2:Int = myDict2["key2"] as Int
// 將原本是String類型的NSObject類型值轉換為Int類型的值
var value3:Int? = (myDict2["key1"] as? String)?.toInt()
println(value1)
println(value2)
println(value3)
注意
經過測試,發現String類型的值不能直接使用as關鍵字轉換為Int類型的值,而且也無法編譯通過。除此之外,原類型是String的NSObject類型值盡管使用as關鍵字可以轉換為Int類型,也可以成功編譯。但運行時會拋出異常,這可能是Swift編譯器的一個 Bug。大家在使用時要注意。例如,前面代碼中的 myDict2["key1"]不能使用as關鍵字轉換為Int類型的值,而需要先轉換為String,然后再使用toInt方法轉換為Int類型的值。
4.2.6 枚舉字典中的key和value
在Swift中可以很容易地獲取字典中的所有key和value。我們可以同時獲取key和與其相對應的value,也可以單獨獲取key或value。通常獲取字典中所有的key和value,可以使用for循環。如果同時獲取key和value,可以使用下面的形式。
for (key,value) in dictionaryName
{
// key和value就是當前key-value對中的key和value
... ...
}
如果要分別獲取key和value,可以使用字典的keys和values屬性。
for key in dictionaryName.keys
{
... ...
}
for value in dictionaryName.values
{
... ...
}
當字典類型變成了 NSDictionary,可以先將其類型轉換為 Dictionary,然后再進行使用keys和values屬性獲取相應的key和value。
for key in (dictionaryName as Dictionary).keys
{
... ...
}
下面是一個完整的演示各種情況獲取字典中key和value的代碼。
var products = [20:"iPhone", 30:"Nexus 6", 40:"iPad5"]
for (key,value) in products
{
println("\(key):\(value)")
}
for key in products.keys
{
println(key)
}
for values in products.values
{
println(values)
}
// city為NSDictionary類型
var city = [10:"沈陽", "province":"遼寧"]
for key in (city as Dictionary).keys
{
println(key)
}
4.2.7 將keys和values轉換為數組
有時我們需要將keys和values單獨拿出來使用。在這種情況下,將keys和values分別轉換為數組(Array)是最好的方法。通常將字典中的keys和values分別轉換為數組可以使用下面兩個方法。
var 行星 = ["名稱":"地球", "位置":"太陽系第3行星"]
// 方法1
let keys = Array(行星.keys)
println(keys)
// 方法2
let values = [String](行星.values)
println(values)
4.3 小結
Swift中的數組和字典用處很多,而且使用非常簡單。例如,可以利用字典存儲數據庫中的一條記錄。我們會發現,在后面的很多例子中,會使用到數組和字典,因此,建議讀者仔細研究數組和字典的各種細節,以免在后面的學習中不知所措。