簡(jiǎn)介
這是一個(gè)Swift語(yǔ)言教程,基于最新的iOS 9,Xcode 7.3和Swift 2.2,會(huì)為你介紹Swift編程非常基礎(chǔ)的內(nèi)容。從電腦如何工作的全程基本原理到語(yǔ)言結(jié)構(gòu),你會(huì)足夠了解這門(mén)語(yǔ)言,來(lái)處理數(shù)據(jù)和管理代碼的行為。
快速鏈接
- 一個(gè)下午讓你掌握Swift基礎(chǔ) ( 1/9 ) 編程本質(zhì) & Playground基礎(chǔ)
- 一個(gè)下午讓你掌握Swift基礎(chǔ) ( 2/9 ) 變量 & 常量
- 一個(gè)下午讓你掌握Swift基礎(chǔ) ( 3/9 ) 數(shù)字類型 & 操作
- 一個(gè)下午讓你掌握Swift基礎(chǔ) ( 4/9 ) 字符串
- 一個(gè)下午讓你掌握Swift基礎(chǔ) ( 5/9 ) 做判斷
- 一個(gè)下午讓你掌握Swift基礎(chǔ) ( 6/9 ) 重復(fù)步驟
- 一個(gè)下午讓你掌握Swift基礎(chǔ) ( 7/9 ) 函數(shù)
- 一個(gè)下午讓你掌握Swift基礎(chǔ) ( 8/9 ) 閉包
- 一個(gè)下午讓你掌握Swift基礎(chǔ) ( 9/9 ) 可選值
- Swift 初學(xué)者 ( 11/12 ) 字典
- Swift 初學(xué)者 ( 12/12 ) 集合
第二部分:集合類型
到目前為止,你看到的數(shù)據(jù)主要都是單個(gè)形式。盡管元祖可以有多塊數(shù)據(jù),但你必須指定大小;帶有三個(gè)字符串的元祖和帶有兩個(gè)字符串的元祖是完全不同的類型,他們之間的轉(zhuǎn)換不是件小事。
在這個(gè)部分,你會(huì)學(xué)習(xí)有關(guān) Swift 中集合類型(collection types)的內(nèi)容。集合是靈活的“容器”,可以讓你一起存儲(chǔ)任意多個(gè)值。
Swift 中有三個(gè)集合類型:數(shù)組,字典和 sets。你會(huì)在下面三篇文章用這個(gè)順序來(lái)學(xué)習(xí)他們:
- 教程 10,數(shù)組
- 教程 11,字典
- 教程 12,sets
集合類型們有相似的外觀,但非常不同的用法。當(dāng)你讀完三篇文章后,把差別記在腦海里,你要開(kāi)始養(yǎng)成隨時(shí)知道應(yīng)該用哪種類型的那種感覺(jué)。
作為探索集合類型之間區(qū)別的一部分,我們也會(huì)考慮性能:集合執(zhí)行特定操作有多快,比如向集合中添加元素或在其中搜索。
討論性能的常用方式是用大O符號(hào)(big-O notation)。如果你還不熟悉它,讀一讀簡(jiǎn)短的介紹吧。
介紹大O符號(hào)
大O符號(hào)是描述運(yùn)行時(shí)間(running time)的一種方式,也就是一個(gè)操作用了多長(zhǎng)時(shí)間來(lái)完成。想法是操作用的確切時(shí)間并不重要;重要的是在規(guī)模上的相對(duì)差異。
想想你有一列名字,是某種隨機(jī)的順序,現(xiàn)在你需要找到列表上的第一個(gè)名字。這列只有一個(gè)名字或一百萬(wàn)個(gè)名字都不重要—找到最開(kāi)始的那個(gè)名字總是花同樣多的時(shí)間。這是一次恒量時(shí)間(constant time)操作的例子,或者是大O符號(hào)中的 O(1)。
現(xiàn)在假設(shè)你需要找到列表中的一個(gè)特定名字。你需要掃描整個(gè)列表,看每一個(gè)單獨(dú)的名字,直到你找到一個(gè)匹配或抵達(dá)終點(diǎn)。這次也是,我們不關(guān)心這次用的確切時(shí)間,只關(guān)心相比其他操作的相對(duì)時(shí)間。
要計(jì)算運(yùn)行時(shí)間,要考慮工作的單位。你需要看每個(gè)名字,所以把這認(rèn)為是每個(gè)名字的工作的一個(gè)“單位”。如果你有 100 個(gè)名字,那就是 100 個(gè)工作單位。如果你把名字的個(gè)數(shù)翻倍到 200;那怎么改變工作的量呢?他也讓工作的量翻倍了。相似的,如果你讓名字的數(shù)量變成四倍,那也讓工作的量變成四倍。
這是線性時(shí)間(linear time)操作的例子,也就是大O符號(hào)中的 O(N)。輸入的大小是變量 N,表示操作花的時(shí)間的量也是 N。這里有一個(gè)直接的、線性的關(guān)系,在輸入尺寸(列表里名字的數(shù)量)和搜索一個(gè)名字會(huì)用的時(shí)間之間。
你可以看到為什么恒量時(shí)間操作在 O(1) 里有數(shù)字 1。不論怎么樣,他們只是一個(gè)單獨(dú)的工作單位!
你可以通過(guò)搜索網(wǎng)絡(luò)來(lái)讀更多大O符號(hào)相關(guān)的內(nèi)容。這個(gè)教程里你只需要恒量時(shí)間和線性時(shí)間,但還有其他的超出這個(gè)范圍的復(fù)雜時(shí)間(time complexities)。
大O符號(hào)在處理集合類型的時(shí)候尤其重要,因?yàn)榧峡梢源鎯?chǔ)很大量的數(shù)據(jù),并且你需要知道在添加、刪除和編輯值得時(shí)候的運(yùn)行時(shí)間。
例如,如果集合類型 A 搜索有恒量時(shí)間,集合類型 B 搜索有線性時(shí)間,你選擇使用哪個(gè)會(huì)基于你計(jì)劃搜索的量級(jí)。
數(shù)組
數(shù)組(Arrays)是你會(huì)在 Swift 里遇到的最通用的集合類型。數(shù)組是類型化的,就像常規(guī)的變量和常量,存儲(chǔ)多個(gè)值比如一個(gè)簡(jiǎn)單的列表。
在你創(chuàng)建第一個(gè)數(shù)組前,花一點(diǎn)時(shí)間考慮一下細(xì)節(jié),數(shù)組是什么以及為什么你想要用它。
什么是數(shù)組?
數(shù)組是同類型的值的有序集合。數(shù)組里的元素是零索引(zero-indexed),表示第一個(gè)元素的索引是 0,第二個(gè)元素的索引是 1,以此類推。知道這一點(diǎn),你可以算出最后一個(gè)元素的索引是數(shù)組中的值的個(gè)數(shù)減 1。
這個(gè)數(shù)組里有五個(gè)元素,索引值是 0-4。
還有,所有值都是 String 類型。你不能向一個(gè)保存字符串的數(shù)組添加非字符串類型。注意相同的值可以出現(xiàn)多次。
什么時(shí)候數(shù)組有用?
當(dāng)你想把一些東西用一個(gè)特定順序保存的時(shí)候,數(shù)組很有用。你想要順序可能是因?yàn)橐诸愓碓兀蛘咝枰ㄟ^(guò)索引取出元素,而不是遍歷一整個(gè)數(shù)組。
例如,如果你在存儲(chǔ)高分?jǐn)?shù)據(jù),順序不影響。你會(huì)想要最高分出現(xiàn)在列表的第一個(gè)(也就是在 0 索引),第二高的分?jǐn)?shù)次之,以此類推。
可修改 vs 不可修改數(shù)組
就像你之前已經(jīng)讀到的類型,比如 String 或 Int,當(dāng)你創(chuàng)建數(shù)組的時(shí)候,必須聲明它為常量或變量。
如果數(shù)組創(chuàng)建之后不需要改變,你應(yīng)該用 let 聲明它為常量,讓它不可修改。如果你需要添加、移除或更新數(shù)組里的值,你應(yīng)該聲明它為變量來(lái)創(chuàng)建一個(gè)可修改的數(shù)組。
創(chuàng)建數(shù)組
在這個(gè)部分,你會(huì)練習(xí)聲明數(shù)組和給他們賦值初始值。
顯示聲明
你可以顯示聲明一個(gè)數(shù)組,或利用 Swift 的類型推斷特色。這是一個(gè)顯示聲明的例子:
let numbers: Array<Int>
尖括號(hào)里的類型定義了數(shù)組可以存儲(chǔ)的值類型,編譯器在你向數(shù)組添加元素的時(shí)候會(huì)這么強(qiáng)迫你。例如,如果你想添加一個(gè)字符串,編譯器會(huì)返回一個(gè)錯(cuò)誤,你的代碼就無(wú)法編譯了。這個(gè)強(qiáng)大的強(qiáng)迫機(jī)制叫做 generics,你會(huì)在后面學(xué)習(xí)更多。
在這個(gè)例子里,你把 numbers 聲明為一個(gè)數(shù)組,只能存儲(chǔ) Int 值。你把這個(gè)數(shù)組定義為常量,所以它的值不能改變。
推斷聲明
Swift 也能從類型的初始化中推斷數(shù)組的類型:
let inferredNumbers = Array<Int>()
這里 inferredNumbers 也是一個(gè)整數(shù)的數(shù)組。如果你跟著做了,把這行代碼打到了 playground 里,你會(huì)注意到結(jié)果區(qū)域的 []。這是 Swift 對(duì)于空數(shù)組的表示。
另外,定義數(shù)組簡(jiǎn)短點(diǎn)的方式是用方括號(hào)圍繞類型,像這樣:
let alsoInferredNumbers = [Int]()
這是對(duì)于數(shù)組的最常用的聲明形式,你會(huì)在本教程中貫穿使用。
數(shù)組字面值
當(dāng)你聲明數(shù)組的時(shí)候,你可能想給它初始值。你可以用數(shù)組字面值(array literals),是一個(gè)提供數(shù)組值得簡(jiǎn)潔的方式。一個(gè)數(shù)組字面值是一列值,用逗號(hào)間隔,被方括號(hào)包圍:
let evenNumbers = [2, 4, 6, 8]
因?yàn)槁暶髦话麛?shù),Swift 推斷 evenNumbers 的類型為 Int 值的數(shù)組,也就是 [Int]。
創(chuàng)建一個(gè)數(shù)組,把值全部設(shè)置為一個(gè)默認(rèn)值也是可能的:
let allZeros = [Int](count: 5, repeatedValue: 0)
// > [0, 0, 0, 0, 0]
因?yàn)?[Int]—包含方括號(hào)—只是一個(gè)類型,你在這個(gè)例子里調(diào)用了它的初始化。兩個(gè)參數(shù)制定了個(gè)數(shù),和將要被重復(fù)的值。
你到目前為止創(chuàng)建的數(shù)組都是不可修改的,因?yàn)槟惆阉麄冑x值為常量。對(duì)于不會(huì)改變的數(shù)組這是一個(gè)好的習(xí)慣。例如,考慮這個(gè)數(shù)組:
let vowels = ["A", "E", "I", "O", "U"]
vowels (元音)是字符串的數(shù)組,并且它的值不會(huì)被改變。但這很好呀,因?yàn)樵舻牧斜聿粫?huì)經(jīng)常變!
訪問(wèn)元素
能夠創(chuàng)建數(shù)組并沒(méi)有什么用,除非你知道怎么從數(shù)組里獲取值。在這個(gè)部分,你會(huì)學(xué)習(xí)到訪問(wèn)數(shù)組里元素的幾種不同方式。
使用 properties 和方法
假設(shè)你在創(chuàng)建一個(gè)紙牌游戲,希望用一個(gè)數(shù)組存儲(chǔ)選手的名字。選手加入或離開(kāi)游戲的時(shí)候要改變這個(gè)列表,所以要聲明一個(gè)可被修改的數(shù)組:
var players = ["Alice", "Bob", "Cindy", "Dan"]
這個(gè)例子里,players 是一個(gè)可變數(shù)組,因?yàn)槟惆阉峙錇樽兞俊T谟螒蜷_(kāi)始前,需要確保有足夠的選手。可以用 isEmpty property 來(lái)檢測(cè)是不是有至少一名選手:
print(players.isEmpty)
// > false
數(shù)組不是空的,但要開(kāi)始游戲的話至少要兩個(gè)選手。可以用 count property 來(lái)獲取選手的數(shù)量:
if players.count < 2 {
print("我們需要至少兩名選手!")
} else {
print("開(kāi)始吧!")
}
// > 開(kāi)始吧!
是時(shí)候開(kāi)始游戲了!你決定游戲的順序是根據(jù)數(shù)組里的名字排列順序。那怎么得到第一個(gè)選手的名字呢?
數(shù)組提供了 first property 來(lái)獲取數(shù)組的第一個(gè)對(duì)象:
var currentPlayer = players.first
打印 currentPlayer 的值顯示了有趣的東西:
print(currentPlayer)
// > Optional("Alice")
frist property 實(shí)際上返回了一個(gè)可選值,因?yàn)槿绻麛?shù)組是空的話,first 會(huì)返回 nil。
相似的,數(shù)組有一個(gè) last property 返回?cái)?shù)組的最后一個(gè)值,如果數(shù)組是空的話就返回 nil:
print(players.last)
// > Optional("Dan")
從數(shù)組獲取值的另一種方式是通過(guò)調(diào)用 minElement()。這個(gè)方法返回?cái)?shù)組里有最低的值的元素——不是最低的索引!如果數(shù)組包含的是字符串,就返回按字母表順序最低的字符串,在這個(gè)例子里是"Alice":
currentPlayer = players.minElement()
print(currentPlayer)
// > Optional("Alice")
很明顯,frist 和 minElement() 不會(huì)總返回同樣的值。例如:
print([2, 3, 1].first)
// > Optional(2)
print([2, 3, 1].minElement())
// > Optional(1)
你可能已經(jīng)猜到了,數(shù)組也有一個(gè) maxElement() 方法。
注意:first 和 last properties 和 minElement() 和 maxElement() 方法不只是數(shù)組有。每個(gè)集合類型都有這些 properties 和方法,在那些許許多多的屬性以外。你會(huì)在讀到協(xié)議(protocols)的時(shí)候?qū)W習(xí)更多有關(guān)這個(gè)行為的內(nèi)容。
現(xiàn)在你已經(jīng)搞明白怎么得到第一名選手了,你要介紹一下這名選手是誰(shuí):
if let currentPlayer = currentPlayer {
print("\(currentPlayer) 將要開(kāi)始")}
// > Alice 將要開(kāi)始
你用了 if let 來(lái)拆包從 first 得到的可選值;否則,語(yǔ)句會(huì)打印 Optional("Alice") 將要開(kāi)始,這應(yīng)該不是你想要的吧。
這些 properties 和方法在你想得到第一個(gè),最后一個(gè),最小的或最大的元素的時(shí)候會(huì)很有幫助。但如果你想要的元素不能通過(guò)這些 properties 或方法中的任何一個(gè)觀察到呢?
使用下標(biāo)
訪問(wèn)數(shù)組里的元素最方便的方式是通過(guò)使用下標(biāo)(subscript)語(yǔ)法。語(yǔ)法允許你通過(guò)使用包括索引的方括號(hào)來(lái)直接訪問(wèn)任意值:
var firstPlayer = players[0]
print("第一名選手是 \(firstPlayer)")
// > 第一名選手是 "Alice"
因?yàn)閿?shù)組是零索引,所以用 0 索引來(lái)獲取第一個(gè)對(duì)象。你可以使用一個(gè)更大的索引來(lái)獲得數(shù)組里的下一個(gè)元素,但如果你嘗試訪問(wèn)超過(guò)了數(shù)組尺寸的索引,你會(huì)得到一個(gè)運(yùn)行時(shí)錯(cuò)誤。
var player = players[4]
// > fatal error: Array index out of range(致命錯(cuò)誤:數(shù)組索引超出范圍)
為什么會(huì)得到這個(gè)錯(cuò)誤呢?因?yàn)?players 只包含了四個(gè)字符串。索引 4 表示第五個(gè)元素,但這個(gè)數(shù)組并沒(méi)有第五個(gè)元素呀。
當(dāng)你使用下標(biāo)的時(shí)候,不需要擔(dān)心可選值,因?yàn)閲L試訪問(wèn)一個(gè)不存在的索引不會(huì)返回 nil;直接就導(dǎo)致運(yùn)行時(shí)錯(cuò)誤了。
使用范圍
你可以結(jié)合范圍(ranges)來(lái)使用下標(biāo)語(yǔ)法,從數(shù)組中獲取不止一個(gè)單獨(dú)的值。例如:如果你想得到接下來(lái)要開(kāi)始的兩名選手,可以這樣做:
let upcomingPlayers = players[1...2]
print(upcomingPlayers)
// > ["Bob", "Cindy"]
如你所見(jiàn),upcomingPlayers 常量實(shí)際上是和原始數(shù)組相同類型的數(shù)組。
你用的范圍是 1...2,表示每個(gè)數(shù)組的第二和第三個(gè)項(xiàng)目。只要起始值小于結(jié)束值,你可以使用任何索引,它們都在數(shù)組的范圍內(nèi)。
檢查一個(gè)元素
你可以檢查數(shù)組里是否存在至少一個(gè)特定的元素,使用 contains(_:),如果找到了就返回 true,沒(méi)有就 false。
可以用這個(gè)方法寫(xiě)一個(gè)函數(shù),檢查給定的選手在不在游戲里:
func isPlayerEliminated(playerName: String) -> Bool {
if players.contains(playerName) {
return false
} else {
return true
}}
現(xiàn)在可以在任意你需要檢查選手是否被淘汰的時(shí)候使用這個(gè)函數(shù):
print(isPlayerEliminated("Bob"))
// > false
你甚至可以在一個(gè)特定范圍里測(cè)試一個(gè)元素是否存在:
players[1...3].contains("Bob")
// > true
現(xiàn)在你能從數(shù)組里取出數(shù)據(jù)了,是時(shí)候看看可變數(shù)組以及如何修改它們的值了。
修改元素
你可以對(duì)可變數(shù)組做所有類型的改變:添加或移除元素,更新已存在的值,和移動(dòng)元素到一個(gè)不同的順序。在這個(gè)部分,你會(huì)看到如何處理數(shù)組,讓它配合你的游戲正在進(jìn)行的事情。
附加元素
如果新的選手想要加入游戲,他們需要注冊(cè)然后添加他們的名字到數(shù)組里。Eli 是要加入已經(jīng)存在的四名選手的第一個(gè)選手。你可以使用 append(_:) 來(lái)把 Eli 加到數(shù)組的結(jié)尾:
players.append("Eli")
如果你想附加除了字符串以外的東西,編譯器會(huì)顯示一個(gè)錯(cuò)誤。記住,數(shù)組只能包含相同類型的值。還有,append(_:) 只對(duì)可變數(shù)組奏效。
下一個(gè)加入游戲的選手是 Gina。你可以用另一種方式把她附加到游戲里,使用 += 操作符:
players += ["Gina"]
這個(gè)表達(dá)式右側(cè)是只有一個(gè)單獨(dú)元素的數(shù)組,字符串"Gina"。通過(guò)使用 +=,你在附加這個(gè)數(shù)組的元素到 players 上。現(xiàn)在這個(gè)數(shù)組看起來(lái)像這樣:
print(players)
// > ["Alice", "Bob", "Cindy", "Dan", "Eli", "Gina"]
這里,你添加一個(gè)單獨(dú)的元素到數(shù)組上,但你可以看到附加多個(gè)項(xiàng)目會(huì)多么簡(jiǎn)單,只要在 Gina 的名字后面添加更多名字就可以了。
插入元素
這個(gè)紙牌游戲的不成文規(guī)矩是選手的名字要按字母表順序排列。如你所見(jiàn),這個(gè)列表遺失了一個(gè)以字母 F 開(kāi)始的選手。幸運(yùn)的是,F(xiàn)rank 剛剛抵達(dá)。你想把他添加到列表的 Eli 和 Gina 中間。要做這件事,使用 insert(_:atIndex:):
players.insert("Frank", atIndex: 5)
atIndex 元素定義了你想添加元素的位置。記住數(shù)組是零索引的,所以索引 5 實(shí)際上是在 Eli 和 Gina 中間。
移除元素
游戲進(jìn)行中,其他選手逮到了 Cindy 和 Gina 作弊。應(yīng)該把他們從游戲中移除!你知道 Gina 在選手列表的最后一個(gè),所以你可以輕松地使用 removeLast() 來(lái)移除她:
var removedPlayer = players.removeLast()
print("\(removedPlayer) 被移除了")
// > Gina 被移除了
這個(gè)方法做了兩件事:它移除了最后一個(gè)元素,然后返回了它,以防你需要打印它或存儲(chǔ)到其他什么地方去——比如一個(gè)作弊者數(shù)組!
要從游戲里移除 Cindy,你需要知道她的名字被存儲(chǔ)的確切索引。看著選手列表,你發(fā)現(xiàn)她在列表的第三個(gè),所以她的索引是 2。
removedPlayer = players.removeAtIndex(2)
print("\(removedPlayer) 被移除了")
// > Cindy 被移除了
你會(huì)怎么獲得一個(gè)元素的索引呢?有一個(gè)方法就是做這個(gè)的!indexOf(_:) 返回元素的第一個(gè)索引,因?yàn)閿?shù)組可能包含同樣的值的多個(gè)拷貝。如果方法沒(méi)有找到元素,就返回 nil。
迷你練習(xí)
使用 indexOf(_:) 來(lái)確定元素"Dan"在 players 里的位置。
更新元素
Frank 已經(jīng)決定每個(gè)人從現(xiàn)在開(kāi)始都要叫他 Franklin。你可以從數(shù)組里移除"Frank"值,然后添加"Franklin",但那對(duì)于一個(gè)簡(jiǎn)單的任務(wù)來(lái)說(shuō)太多工作量了。作為替代,你應(yīng)該用下標(biāo)語(yǔ)法來(lái)更新名字:
print(players)
// > ["Alice", "Bob", "Dan", "Eli", "Frank"]
players[4] = "Franklin"
print(players)
// > ["Alice", "Bob", "Dan", "Eli", "Franklin"]
你要小心不要用一個(gè)超出數(shù)組邊界的索引,不然你的 app 就會(huì)崩潰。
隨著游戲繼續(xù),一些選手被淘汰了,新人加入替換他們。幸運(yùn)的是,你也可以結(jié)合范圍使用下標(biāo)來(lái)用一行代碼更新多個(gè)值:
players[0...1] = ["Donna", "Craig", "Brian", "Anna"]
print(players)
// > ["Donna", "Craig", "Brian", "Anna", "Dan", "Eli", "Franklin"]
這段代碼替換了前兩個(gè)選手,Alice 和 Bob,替換為新的 players 數(shù)組里的四名選手。如你所見(jiàn),范圍的尺寸不需要等于你在添加的數(shù)組持有的值的尺寸。
移動(dòng)元素
看看這一團(tuán)糟!players 數(shù)組包含名字的首字母從 A 到 F,但他們的順序不對(duì),這違背了游戲的規(guī)則。
你可以嘗試解決這個(gè)狀況,通過(guò)手動(dòng)一個(gè)一個(gè)移動(dòng)值到正確的位置上,像這樣:
let playerAnna = players.removeAtIndex(3)
players.insert(playerAnna, atIndex: 0)
print(players)
// > ["Anna", "Donna", "Craig", "Brian", "Dan", "Eli", "Franklin"]
如果你想移動(dòng)一個(gè)單獨(dú)的元素,這會(huì)奏效,但如果你想排序整個(gè)數(shù)組,應(yīng)該使用sort():
players = players.sort()
print(players)
// > ["Anna", "Brian", "Craig", "Dan", "Donna", "Eli", "Franklin"]
sort() 做的完全就是你預(yù)想的那樣:返回一個(gè)數(shù)組排序好的拷貝。如果您想對(duì)數(shù)組本身(in place)進(jìn)行排序,而不是返回一個(gè)已排序的副本,應(yīng)該用 sortInPlace()。
遍歷數(shù)組
現(xiàn)在時(shí)間越來(lái)越晚了,選手決定今晚暫停,明天繼續(xù);在此期間,你要把他們的分?jǐn)?shù)保存到一個(gè)單獨(dú)的數(shù)組里。你會(huì)在關(guān)于字典的教程里看到更好的方式來(lái)做這個(gè),但現(xiàn)在你要繼續(xù)用數(shù)組:
let scores = [2, 2, 8, 6, 1, 2]
選手離開(kāi)之前,你想打印仍然在游戲里的名字。你可以使用 for-in 循環(huán)來(lái)做,你在第6篇教程里已經(jīng)讀到。
for playerName in players {
print(playerName)
}
// > Anna
// > Brian
// > Craig
// > Donna
// > Eli
// > Franklin
這段代碼經(jīng)過(guò)了 players 的所有元素,從 0 索引知道 players.count - 1,打印了它們的值。在第一個(gè)循環(huán)里,playerName 等于數(shù)組的第一個(gè)元素;第二個(gè)循環(huán)里,等于數(shù)組的第二個(gè)元素;以此類推直到循環(huán)已經(jīng)打印了數(shù)組里的所有名字。
如果你也需要每個(gè)元素的索引,你可以遍歷數(shù)組的 enumerate() 方法的返回值,返回一個(gè)元祖包含數(shù)組里每個(gè)元素的索引和值。
for (index, playerName) in players.enumerate() {
print("\(index + 1). \(playerName)")
}
// > 1. Anna
// > 2. Brian
// > 3. Craig
// > 4. Donna
// > 5. Eli
// > 6. Franklin
現(xiàn)在你可以這個(gè)剛剛學(xué)到的技術(shù)來(lái)寫(xiě)一個(gè)函數(shù),帶有一個(gè)整數(shù)的數(shù)組作為它的輸入,返回它的元素的和:
func sumOfAllItems(intArray: [Int]) -> Int {
var sum = 0
for number in intArray {
sum += number
}
return sum
}
你可以用這個(gè)函數(shù)來(lái)計(jì)算選手的總分:
print(sumOfAllItems(scores))
// > 21
迷你練習(xí)
寫(xiě)一個(gè) for-in 循環(huán),打印選手的名字和分?jǐn)?shù)。
順序操作
你剛剛看到的 for-in 循環(huán)都有一個(gè)共同點(diǎn):他們遍歷了數(shù)組的所有元素,然后對(duì)每個(gè)項(xiàng)目實(shí)施了特定的行為。
Reduce
reduce(_:combine:) 帶有一個(gè)初始值作為第一個(gè)參數(shù),然后按順序累計(jì)數(shù)組里的每個(gè)值,使用 combine 操作。一個(gè)例子勝過(guò)千言萬(wàn)語(yǔ):
let sum = scores.reduce(0, combine: +)
print(sum)// > 21
這和寫(xiě) let sum = 0 + scores[0] + scores[1] + ... +scores[5] 是相同的。它做了 sumOfAllValues(_:) 做的完全一樣的事,但只有一行代碼。
combine 參數(shù)是很高端的,它支持不止一個(gè)單獨(dú)的算術(shù)操作。它是一個(gè)閉包(closure),你會(huì)在后面的教程里學(xué)習(xí)更多有關(guān)內(nèi)容,“函數(shù)式編程。”
Filter
filter(:) 返回一個(gè)新數(shù)組,通過(guò)從它被調(diào)用的數(shù)組中篩選出元素。它唯一的參數(shù)是一個(gè)閉包,返回一個(gè)布爾型,它會(huì)為數(shù)組里的每個(gè)元素執(zhí)行這個(gè)閉包一次。如果閉包返回 true,filter(:) 會(huì)添加元素到返回的數(shù)組中;否則它會(huì)忽略這個(gè)元素。
例如,在你的紙牌游戲里,你想打印這天的高分,你把這定義為大于 5 分的分?jǐn)?shù):
print(scores.filter({ $0 > 5 }))
// > [8, 6]
你可以使用簡(jiǎn)寫(xiě)的 $0 指向閉包里的第一個(gè)參數(shù),這樣你就能指向 filter(_:) 當(dāng)前在操作的元素。
Map
map(_:) 也帶有唯一的閉包參數(shù)。就像它名字表示的,它將一個(gè)數(shù)組中的每個(gè)值映射(map)到一個(gè)新值,使用閉包參數(shù)。
再一次,來(lái)個(gè)例子會(huì)更易懂。假設(shè)你感覺(jué)很慷慨,你想要把每個(gè)選手擁有的值翻倍。
print(scores)
// > [2, 2, 8, 6, 1, 2]
let newScores = scores.map({ $0 * 2 })
print(newScores)
只要一行代碼,你已經(jīng)創(chuàng)建了一個(gè)新數(shù)組,分?jǐn)?shù)都被乘以 2。
注意:你會(huì)在后面的教程學(xué)習(xí)更多有關(guān)順序操作,“函數(shù)式編程。”
數(shù)組操作的運(yùn)行時(shí)間
數(shù)組在內(nèi)容里存儲(chǔ)為連續(xù)不斷的塊。意味著如果你在一個(gè)數(shù)組里有十個(gè)元素,十個(gè)值都被一個(gè)接一個(gè)存儲(chǔ)。記在心中,這是多個(gè)數(shù)組操作消耗的表現(xiàn):
訪問(wèn)元素:獲取一個(gè)元素的消耗是 O(1)。因?yàn)樗兄刀际切蛄谢模?em>隨機(jī)訪問(wèn)和獲取位于特定索引的值很簡(jiǎn)單;編譯器要知道的就是數(shù)組從哪里開(kāi)始,以及你想要獲取哪個(gè)索引。
插入元素:添加一個(gè)元素的復(fù)雜性基于你添加新元素的位置在哪:
- 如果你在數(shù)組的開(kāi)頭添加,Swift 可以用 O(1) 完成。
- 如果你添加到數(shù)組的中間,從這個(gè)索引開(kāi)始的所有值都需要被平移。這樣做會(huì)需要 N/2 操作,因此運(yùn)行時(shí)間是 O(N)。
- 如果你添加到數(shù)組的末尾并且有空間的話,它會(huì)使用 O(1)。如果沒(méi)有空間,Swift 需要從其他什么地方找到空間,在添加之前復(fù)制整個(gè)數(shù)組過(guò)去,會(huì)占用 O(N)。但平均情況是 O(1),因?yàn)閿?shù)組大部分情況下都不是滿的。
刪除元素:刪除一個(gè)元素留下了被移除的元素本來(lái)所在的空當(dāng)。就像之前提到的,數(shù)組里的所有元素需要被序列化這樣空當(dāng)才能關(guān)閉,通過(guò)向前平移元素。
復(fù)雜性和插入元素相似:如果你在移除開(kāi)頭或結(jié)尾的元素,這是一個(gè) O(1) 操作。如果你在從中間移除,復(fù)雜性是 O(N)。
搜索一個(gè)元素:如果你在搜索的元素是數(shù)組的第一個(gè)元素,搜索會(huì)在一次操作后結(jié)束。如果這個(gè)元素不存在,你需要執(zhí)行 N 次操作直到你意識(shí)到元素沒(méi)有被找到。平均來(lái)看,搜索一個(gè)元素會(huì)用 N/2 操作,因?yàn)樗阉饔幸粋€(gè) O(N) 的復(fù)雜性。
當(dāng)你在閱讀接下來(lái)的關(guān)于數(shù)組和集合的教程的時(shí)候,你會(huì)看到他們的表現(xiàn)特征和數(shù)組有什么不同。這會(huì)給你一點(diǎn)靈感,對(duì)于特定情況應(yīng)該用哪個(gè)集合類型。
關(guān)鍵點(diǎn)
- 數(shù)組(Arrays)是相同類型的值的有序集合。
- 使用下標(biāo)(subscripting)來(lái)訪問(wèn)和更新元素。
- 數(shù)組是一個(gè)值類型,所以被分配到一個(gè)新的變量或作為參數(shù)傳遞給函數(shù)的時(shí)候是被拷貝的。
- 千萬(wàn)不要訪問(wèn)一個(gè)越界索引。
接下來(lái)去哪兒?
數(shù)組在編程里很常見(jiàn),當(dāng)你需要操作許多特定類型的元素的時(shí)候你會(huì)看到他們被大量使用。
在接下來(lái)的兩篇教程里,你會(huì)學(xué)習(xí)字典(dictionaries)和集合(sets),另外兩個(gè) Swift 內(nèi)置的集合類型。當(dāng)你繼續(xù)的時(shí)候,仍然把數(shù)組放在腦海里,這樣就可以比較和發(fā)現(xiàn)差異;這會(huì)讓你看到區(qū)別,幫助你判斷何時(shí)使用哪個(gè)集合類型。
挑戰(zhàn)
挑戰(zhàn) A:你就是編譯器
以下哪個(gè)是有效語(yǔ)句?
1. let array1 = [Int]()
2. let array2 = []
3. let array3: [String] = []
對(duì)于接下來(lái)的5條語(yǔ)句,array4 被如此聲明:
let array4 = [1, 2, 3]
4. print(array4[0])
5. print(array4[5])
6. array4[1...2]
7. array4[0] = 4
8. array4.append(4)
對(duì)于最后的5條語(yǔ)句,array5 被如此聲明:
var array5 = [1, 2, 3]
9. array5[0] = array5[1]
10. array5[0..1] = [4, 5]
11. array5[0] = "Six"
12. array5 += 6
13. for item in array5 { print(item) }
挑戰(zhàn) B:移除一個(gè)元素
寫(xiě)一個(gè)函數(shù),移除一個(gè)整數(shù)數(shù)組中給定的整數(shù)的第一個(gè)存在。這是函數(shù)的結(jié)構(gòu):
func removeOnce(itemToRemove: Int, fromArray: [Int]) -> [Int]
挑戰(zhàn) C:挑選
寫(xiě)一個(gè)函數(shù)從整數(shù)數(shù)組中移除給定的整數(shù)的所有存在。這是函數(shù)的結(jié)構(gòu):
func remove(itemToRemove: Int, fromArray: [Int]) -> [Int]
挑戰(zhàn) D:反轉(zhuǎn)數(shù)組
數(shù)組有一個(gè) reverse() 方法,反轉(zhuǎn)數(shù)組里項(xiàng)目的順序。寫(xiě)一個(gè)函數(shù)反轉(zhuǎn)數(shù)組,但不使用 reverse()。這是函數(shù)的結(jié)構(gòu):
func reverse(array: [Int]) -> [Int]
挑戰(zhàn) E:隨機(jī)化數(shù)組
下面的函數(shù)返回一個(gè)介于 0 和給定參數(shù)之間的隨機(jī)數(shù)字:
import Foundation
func randomFromZeroTo(number: Int) -> Int {
return Int(arc4random_uniform(UInt32(number)))
}
使用它寫(xiě)一個(gè)函數(shù),把數(shù)組里的元素打亂成為隨機(jī)順序。這是函數(shù)的結(jié)構(gòu):
func randomArray(array: [Int]) -> [Int]
挑戰(zhàn)源代碼
https://yunpan.cn/cMZGvcJpQDZqD (提取碼:f737)
介紹
歡迎來(lái)到Swift世界!Swift是一門(mén)蘋(píng)果在2014年夏天發(fā)布的編程語(yǔ)言。從那之后,Swift發(fā)布了一個(gè)主要的版本跳躍,成為了開(kāi)始在蘋(píng)果平臺(tái):iOS,OS X,watchOS和tvOS開(kāi)發(fā)的最簡(jiǎn)單的方式。
誰(shuí)適合這篇教程
這篇教程適合懂一點(diǎn)編程、并且希望學(xué)習(xí)Swift的人。也許你已經(jīng)為網(wǎng)站寫(xiě)過(guò)一些JavaScript代碼,或者用Python寫(xiě)過(guò)一些簡(jiǎn)短的程序。這篇教程就是為你準(zhǔn)備的!你會(huì)學(xué)習(xí)到編程的基本概念,同時(shí)也會(huì)成為Swift語(yǔ)言小能手。
如果你是赤裸裸的編程新手,這篇教程也是為你準(zhǔn)備的!教程里貫穿有簡(jiǎn)短的鍛煉和挑戰(zhàn)來(lái)給你一些編程練習(xí),同時(shí)測(cè)試你的知識(shí)。
需要準(zhǔn)備什么
要看這篇教程,你需要準(zhǔn)備如下的東西:
- 一臺(tái)運(yùn)行OS X El Captian(10.11)的Mac,帶有最新發(fā)布的更新并且安裝了安全補(bǔ)丁。這樣你才能夠安裝需要的開(kāi)發(fā)工具:最新版本的Xcode。
- Xcode 7.3 或更新的版本。Xcode是用Swift寫(xiě)代碼的主要開(kāi)發(fā)工具。最小也需要Xcode 7.3版本,因?yàn)槟莻€(gè)版本包含Swift 2.2。你可以免費(fèi)從Mac App Store下載Xcode的最新版本,這里:http://apple.co/1FLn51R。
如果你還沒(méi)有安裝Xcode最新版本,在繼續(xù)看下面的教程前要確定安裝。
如何使用這篇教程
每篇教程都會(huì)介紹觸手可及的話題理論,伴隨大量Swift代碼來(lái)示范在學(xué)習(xí)的實(shí)際的應(yīng)用程序。
教程里的所有代碼都是平臺(tái)中立的;這意味著不是為iOS、OS X或任何其它平臺(tái)而特定。代碼在playgrounds里運(yùn)行,你在本篇中已經(jīng)學(xué)習(xí)了。
在剩下的教程里,你可以把代碼在自己的playground里輸入進(jìn)去。這樣你就可以和代碼“玩耍”(play around),做一些改變立即就能看見(jiàn)代碼運(yùn)行的結(jié)果。
剩下的教程里會(huì)貫穿實(shí)際小練習(xí),都是簡(jiǎn)短的練習(xí),關(guān)于觸手可及的主題。每篇的末尾也有挑戰(zhàn),會(huì)有編程問(wèn)題也會(huì)有長(zhǎng)一點(diǎn)的代碼練習(xí)來(lái)測(cè)試你的知識(shí)。做完就能掌握大部分的Swift基礎(chǔ)知識(shí)。