mirror of
https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese
synced 2025-07-06 21:55:47 +00:00
Revise the traditional Chinese translation in mds in the folder souce-tw & Using Swift with Cocoa and ObjectiveC-tw
This commit is contained in:
@ -0,0 +1,62 @@
|
||||
# 個人信息
|
||||
|
||||
梁傑,男,北京航空航天大學,大三。
|
||||
|
||||
熱愛Python,喜歡前端,GitHub重度腦殘粉,目前正在維護swiftist.org社區。
|
||||
|
||||
# 時間點
|
||||
|
||||
6.3 項目發佈 第一天僅有50Star
|
||||
6.4 開始有人關注 300+Star
|
||||
6.5~6.6 翻譯工作開始步入正軌
|
||||
6.7~6.8 翻譯速度一般
|
||||
6.9~6.11 建立QQ群 翻譯速度加快 完成翻譯 初步校對
|
||||
6.12 發佈
|
||||
|
||||
# 發起原因
|
||||
|
||||
最初其實沒想到會做成這樣,只是想著既然Swift這麼火,我也想學一學,不如用點心去翻譯一下,讓我們廣大群眾也懂得一些知識,也算是為大家做點貢獻。
|
||||
|
||||
萬萬沒想到,最後變成了一個這麼大的開源協作項目。
|
||||
|
||||
# 協作形式
|
||||
|
||||
通過GitHub進行協作,文章使用markdown寫成,用gitbook製作成靜態頁面並托管在GitHub上,可以直接在線閱讀。markdown也可以轉換成Epub、PDF、mobi等多種電子書格式。
|
||||
|
||||
參與翻譯的朋友只需要更新markdown文件內容即可,我會將內容通過gitbook轉換成頁面並更新到GitHub。
|
||||
|
||||
# 如何吸引譯者
|
||||
|
||||
項目發起之後我只是在自己的微博上提了一下,開始時候並沒有什麼人關注,不過經過一些大號轉發之後關注的人越來越多,就開始有人參與進來。
|
||||
|
||||
其實能吸引到這麼多人,主要還是因為蘋果的影響力太大,再加上我發起項目的時間非常早,正是全民Swift的時候,所以吸引了很多人參與。
|
||||
|
||||
# 如何組織開源翻譯
|
||||
|
||||
## 讓新手也能參與
|
||||
|
||||
GitHub在國內的普及程度還是不夠,很多有興趣參與的朋友都不太會用。剛開始我也沒有意識到這個問題,直到有一個朋友主動問我我才明白過來,迅速在項目首頁的說明中添加了詳細的貢獻代碼教程。實踐證明很多朋友都是照著這個教程完成了工作。
|
||||
|
||||
## 傳達信息
|
||||
|
||||
組織開源項目最重要的一點就是保證信息的傳達,其實秘訣很簡單——重複說。
|
||||
|
||||
就拿 Swift 這本書來舉例,我一直在項目說明中更新當前進度,按理說大家點進來都會立刻看到,但是仍然有很多朋友問我現在翻譯了多少、還有沒有未認領章節。之後我就開始主動通知大家,在所有能通知的地方通知,一旦有新變動就馬上通知,慢慢的就沒有人問我了,因為大家都很清楚項目進度。
|
||||
|
||||
重要的信息比如時間節點,一定要多次強調。剛開始的一段時間雖然章節很快被認領,但是完成的人很少。後來我開始在群裡說,週三完成翻譯開始校對,一天說了有十幾遍吧,然後從第二天開始完成的人就越來越多。
|
||||
|
||||
大家參與開源項目時相對來說是比較被動的,如果你希望控制時間的話,一定要多次強調,把這個信息發送到每個人的潛意識裡。
|
||||
|
||||
## 把握發展方向
|
||||
|
||||
很多人會參與進來,但是幾乎沒人會主動考慮這個項目該如何發展,一定要記住這一點。
|
||||
|
||||
如果你覺得很多人參與進來你就可以休息的話,那就大錯特錯了,大家擅長幫忙,但並不擅長主導項目。所以你要時刻提醒自己,下一步的目標是什麼?我們應該怎麼去做?主動提出一個方案然後和大家討論,千萬不要提出一個問題然後等待答案。
|
||||
|
||||
# 一點感想
|
||||
|
||||
還是那句話,萬萬沒想到。
|
||||
|
||||
第一天我還在和朋友說,我真羨慕別人的項目,有200多個Star,結果第二天我自己的項目就有了300多個Star,第三天600多……開始時候其實是抱著「做做試試」的心態來翻譯,但是當Star和譯者多起來之後,翻譯完成就變成了一個責任,你肩負的是所有人的努力,一定不能讓大家失望。
|
||||
|
||||
這大概是我21年來做得最大的一件事。
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
84
source-tw/README.md
Normal file
84
source-tw/README.md
Normal file
@ -0,0 +1,84 @@
|
||||
> Swift 興趣交流群:`305014012`,307017261(已滿)
|
||||
> [Swift 開發者社區](http://swiftist.org)
|
||||
|
||||
<!-- -->
|
||||
> 如果你覺得這個項目不錯,請[點擊Star一下](https://github.com/numbbbbb/the-swift-programming-language-in-chinese),您的支持我們最大的動力。
|
||||
|
||||
# The Swift Programming Language 中文版
|
||||
|
||||
###這一次,讓中國和世界同步
|
||||
|
||||
現在是6月12日凌晨4:38,我用了整整一晚上的時間來進行最後的校對,終於可以在12日拿出一個可以發佈的版本。
|
||||
|
||||
9天時間,1317個 Star,310個 Fork,超過30人參與翻譯和校對工作,項目最高排名GitHub總榜第4。
|
||||
|
||||
設想過很多遍校對完成時的場景,仰天大笑還是淚流滿面?真正到了這一刻才發現,疲倦已經不允許我有任何情緒。
|
||||
|
||||
說實話,剛開始發起項目的時候完全沒想到會發展成今天這樣,我一度計劃自己一個人翻譯完整本書。萬萬沒想到,會有這麼多的人願意加入並貢獻出自己的力量。
|
||||
|
||||
coverxit發給我最後一份文檔的時候說,我要去背單詞了,我問他,週末要考六級?他說是的。
|
||||
|
||||
pp-prog告訴我,這幾天太累了,校對到一半睡著了,醒來又繼續做。2點17分,發給我校對完成的文檔。
|
||||
|
||||
lifedim說他平時12點就會睡,1點47分,發給我校對後的文檔。
|
||||
|
||||
團隊裡每個人都有自己的事情,上班、上學、創業,但是我們只用了9天就完成整本書的翻譯。我不知道大家付出了多少,犧牲了多少,但是我知道,他們的付出必將被這些文字記錄下來,即使再過10年,20年,依然熠熠生輝,永不被人遺忘。
|
||||
|
||||
全體人員名單(排名不分先後):
|
||||
|
||||
- [numbbbbb](https://github.com/numbbbbb)
|
||||
- [stanzhai](https://github.com/stanzhai)
|
||||
- [coverxit](https://github.com/coverxit)
|
||||
- [wh1100717](https://github.com/wh1100717)
|
||||
- [TimothyYe](https://github.com/TimothyYe)
|
||||
- [honghaoz](https://github.com/honghaoz)
|
||||
- [lyuka](https://github.com/lyuka)
|
||||
- [JaySurplus](https://github.com/JaySurplus)
|
||||
- [Hawstein](https://github.com/Hawstein)
|
||||
- [geek5nan](https://github.com/geek5nan)
|
||||
- [yankuangshi](https://github.com/yankuangshi)
|
||||
- [xielingwang](https://github.com/xielingwang)
|
||||
- [yulingtianxia](https://github.com/yulingtianxia)
|
||||
- [twlkyao](https://github.com/twlkyao)
|
||||
- [dabing1022](https://github.com/dabing1022)
|
||||
- [vclwei](https://github.com/vclwei)
|
||||
- [fd5788](https://github.com/fd5788)
|
||||
- [siemenliu](https://github.com/siemenliu)
|
||||
- [youkugems](https://github.com/youkugems)
|
||||
- [haolloyin](https://github.com/haolloyin)
|
||||
- [wxstars](https://github.com/wxstars)
|
||||
- [IceskYsl](https://github.com/IceskYsl)
|
||||
- [sg552](https://github.com/sg552)
|
||||
- [superkam](https://github.com/superkam)
|
||||
- [zac1st1k](https://github.com/zac1st1k)
|
||||
- [bzsy](https://github.com/bzsy)
|
||||
- [pyanfield](https://github.com/pyanfield)
|
||||
- [ericzyh](https://github.com/ericzyh)
|
||||
- [peiyucn](https://github.com/peiyucn)
|
||||
- [sunfiled](https://github.com/sunfiled)
|
||||
- [lzw120](https://github.com/lzw120)
|
||||
- [viztor](https://github.com/viztor)
|
||||
- [wongzigii](https://github.com/wongzigii)
|
||||
- [umcsdon](https://github.com/umcsdon)
|
||||
- [zq54zquan](https://github.com/zq54zquan)
|
||||
- [xiehurricane](https://github.com/xiehurricane)
|
||||
- [Jasonbroker](https://github.com/Jasonbroker)
|
||||
- [tualatrix](https://github.com/tualatrix)
|
||||
- [pp-prog](https://github.com/pp-prog)
|
||||
- [088haizi](https://github.com/088haizi)
|
||||
- [baocaixiong](https://github.com/baocaixiong)
|
||||
- [yeahdongcn](https://github.com/yeahdongcn)
|
||||
- [shinyzhu](https://github.com/shinyzhu)
|
||||
- [lslxdx](https://github.com/lslxdx)
|
||||
- [Evilcome](https://github.com/Evilcome)
|
||||
- [zqp](https://github.com/zqp)
|
||||
- [NicePiao](https://github.com/NicePiao)
|
||||
- [LunaticM](https://github.com/LunaticM)
|
||||
- [menlongsheng](https://github.com/menlongsheng)
|
||||
- [lifedim](https://github.com/lifedim)
|
||||
- [happyming](https://github.com/happyming)
|
||||
- [bruce0505](https://github.com/bruce0505)
|
||||
- [Lin-H](https://github.com/Lin-H)
|
||||
- [takalard](https://github.com/takalard)
|
||||
- [dabing1022](https://github.com/dabing1022)
|
||||
- [marsprince](https://github.com/marsprince)
|
41
source-tw/SUMMARY.md
Normal file
41
source-tw/SUMMARY.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Summary
|
||||
|
||||
* [歡迎使用 Swift](chapter1/chapter1.md)
|
||||
* [關於 Swift](chapter1/01_swift.md)
|
||||
* [Swift 初見](chapter1/02_a_swift_tour.md)
|
||||
* [Swift 教程](chapter2/chapter2.md)
|
||||
* [基礎部分](chapter2/01_The_Basics.md)
|
||||
* [基本運算符](chapter2/02_Basic_Operators.md)
|
||||
* [字符串和字符](chapter2/03_Strings_and_Characters.md)
|
||||
* [集合類型](chapter2/04_Collection_Types.md)
|
||||
* [控制流](chapter2/05_Control_Flow.md)
|
||||
* [函數](chapter2/06_Functions.md)
|
||||
* [閉包](chapter2/07_Closures.md)
|
||||
* [枚舉](chapter2/08_Enumerations.md)
|
||||
* [類和結構體](chapter2/09_Classes_and_Structures.md)
|
||||
* [屬性](chapter2/10_Properties.md)
|
||||
* [方法](chapter2/11_Methods.md)
|
||||
* [下標腳本](chapter2/12_Subscripts.md)
|
||||
* [繼承](chapter2/13_Inheritance.md)
|
||||
* [構造過程](chapter2/14_Initialization.md)
|
||||
* [析構過程](chapter2/15_Deinitialization.md)
|
||||
* [自動引用計數](chapter2/16_Automatic_Reference_Counting.md)
|
||||
* [可選鏈](chapter2/17_Optional_Chaining.md)
|
||||
* [類型檢查](chapter2/18_Type_Casting.md)
|
||||
* [嵌套類型](chapter2/19_Nested_Types.md)
|
||||
* [擴展](chapter2/20_Extensions.md)
|
||||
* [協議](chapter2/21_Protocols.md)
|
||||
* [泛型](chapter2/22_Generics.md)
|
||||
* [高級操作符](chapter2/23_Advanced_Operators.md)
|
||||
* [語言參考](chapter3/chapter3.md)
|
||||
* [關於語言參考](chapter3/01_About_the_Language_Reference.md)
|
||||
* [詞法結構](chapter3/02_Lexical_Structure.md)
|
||||
* [類型](chapter3/03_Types.md)
|
||||
* [表達式](chapter3/04_Expressions.md)
|
||||
* [語句](chapter3/10_Statements.md)
|
||||
* [聲明](chapter3/05_Declarations.md)
|
||||
* [特性](chapter3/06_Attributes.md)
|
||||
* [模式](chapter3/07_Patterns.md)
|
||||
* [泛型參數](chapter3/08_Generic_Parameters_and_Arguments.md)
|
||||
* [語法總結](chapter3/09_Summary_of_the_Grammar.md)
|
||||
|
17
source-tw/chapter1/01_swift.md
Normal file
17
source-tw/chapter1/01_swift.md
Normal file
@ -0,0 +1,17 @@
|
||||
> 翻譯:[numbbbbb](https://github.com/numbbbbb)
|
||||
> 校對:[yeahdongcn](https://github.com/yeahdongcn)
|
||||
|
||||
# 關於 Swift
|
||||
-----------------
|
||||
|
||||
Swift 是一種新的編程語言,用於編寫 iOS 和 OS X 應用。Swift 結合了 C 和 Objective-C 的優點並且不受 C 兼容性的限制。Swift 採用安全的編程模式並添加了很多新特性,這將使編程更簡單,更靈活,也更有趣。Swift 是基於成熟而且倍受喜愛的 Cocoa 和 Cocoa Touch 框架,它的降臨將重新定義軟件開發。
|
||||
|
||||
Swift 的開發從很久之前就開始了。為了給 Swift 打好基礎,蘋果公司改進了編譯器,調試器和框架結構。我們使用自動引用計數(Automatic Reference Counting, ARC)來簡化內存管理。我們在 Foundation 和 Cocoa 的基礎上構建框架棧並將其標準化。Objective-C 本身支持塊、集合語法和模塊,所以框架可以輕鬆支持現代編程語言技術。正是得益於這些基礎工作,我們現在才能發佈這樣一個用於未來蘋果軟件開發的新語言。
|
||||
|
||||
Objective-C 開發者對 Swift 並不會感到陌生。它採用了 Objective-C 的命名參數以及動態對像模型,可以無縫對接到現有的 Cocoa 框架,並且可以兼容 Objective-C 代碼。在此基礎之上,Swift 還有許多新特性並且支持過程式編程和面向對像編程。
|
||||
|
||||
Swift 對於初學者來說也很友好。它是第一個既滿足工業標準又像腳本語言一樣充滿表現力和趣味的編程語言。它支持代碼預覽,這個革命性的特性可以允許程序員在不編譯和運行應用程序的前提下運行 Swift 代碼並實時查看結果。
|
||||
|
||||
Swift 將現代編程語言的精華和蘋果工程師文化的智慧結合了起來。編譯器對性能進行了優化,編程語言對開發進行了優化,兩者互不干擾,魚與熊掌兼得。Swift 既可以用於開發 「hello, world」 這樣的小程序,也可以用於開發一套完整的操作系統。所有的這些特性讓 Swift 對於開發者和蘋果來說都是一項值得的投資。
|
||||
|
||||
Swift 是編寫 iOS 和 OS X 應用的極佳手段,並將伴隨著新的特性和功能持續演進。我們對 Swift 充滿信心,你還在等什麼!
|
726
source-tw/chapter1/02_a_swift_tour.md
Normal file
726
source-tw/chapter1/02_a_swift_tour.md
Normal file
@ -0,0 +1,726 @@
|
||||
> 翻譯:[numbbbbb](https://github.com/numbbbbb)
|
||||
> 校對:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
# Swift 初見
|
||||
|
||||
---
|
||||
|
||||
本頁內容包括:
|
||||
|
||||
- [簡單值(Simple Values)](#simple_values)
|
||||
- [控制流(Control Flow)](#control_flow)
|
||||
- [函數和閉包(Functions and Closures)](#functions_and_closures)
|
||||
- [對像和類(Objects and Classes)](#objects_and_classes)
|
||||
- [枚舉和結構體(Enumerations and Structures)](#enumerations_and_structures)
|
||||
- [協議和擴展(Protocols and Extensions)](#protocols_and_extensions)
|
||||
- [泛型(Generics)](#generics)
|
||||
|
||||
通常來說,編程語言教程中的第一個程序應該在屏幕上打印「Hello, world」。在 Swift 中,可以用一行代碼實現:
|
||||
|
||||
```swift
|
||||
println("Hello, world")
|
||||
```
|
||||
|
||||
如果你寫過 C 或者 Objective-C 代碼,那你應該很熟悉這種形式——在 Swift 中,這行代碼就是一個完整的程序。你不需要為了輸入輸出或者字符串處理導入一個單獨的庫。全局作用域中的代碼會被自動當做程序的入口點,所以你也不需要`main`函數。你同樣不需要在每個語句結尾寫上分號。
|
||||
|
||||
這個教程會通過一系列編程例子來讓你對 Swift 有初步瞭解,如果你有什麼不理解的地方也不用擔心——任何本章介紹的內容都會在後面的章節中詳細講解。
|
||||
|
||||
> 注意:
|
||||
> 為了獲得最好的體驗,在 Xcode 當中使用代碼預覽功能。代碼預覽功能可以讓你編輯代碼並實時看到運行結果。
|
||||
> <a href="https://github.com/numbbbbb/the-swift-programming-language-in-chinese/raw/gh-pages/source/chapter1/GuidedTour.playground.zip">打開Playground</a>
|
||||
|
||||
<a name="simple_values"></a>
|
||||
## 簡單值
|
||||
|
||||
使用`let`來聲明常量,使用`var`來聲明變量。一個常量的值,在編譯的時候,並不需要有明確的值,但是你只能為它賦值一次。也就是說你可以用常量來表示這樣一個值:你只需要決定一次,但是需要使用很多次。
|
||||
|
||||
```swift
|
||||
var myVariable = 42
|
||||
myVariable = 50
|
||||
let myConstant = 42
|
||||
```
|
||||
|
||||
常量或者變量的類型必須和你賦給它們的值一樣。然而,聲明時類型是可選的,聲明的同時賦值的話,編譯器會自動推斷類型。在上面的例子中,編譯器推斷出`myVariable`是一個整數(integer)因為它的初始值是整數。
|
||||
|
||||
如果初始值沒有提供足夠的信息(或者沒有初始值),那你需要在變量後面聲明類型,用冒號分割。
|
||||
|
||||
```swift
|
||||
let implicitInteger = 70
|
||||
let implicitDouble = 70.0
|
||||
let explicitDouble: Double = 70
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 創建一個常量,顯式指定類型為`Float`並指定初始值為4。
|
||||
|
||||
值永遠不會被隱式轉換為其他類型。如果你需要把一個值轉換成其他類型,請顯式轉換。
|
||||
|
||||
```swift
|
||||
let label = "The width is"
|
||||
let width = 94
|
||||
let widthLabel = label + String(width)
|
||||
```
|
||||
> 練習:
|
||||
> 刪除最後一行中的`String`,錯誤提示是什麼?
|
||||
|
||||
有一種更簡單的把值轉換成字符串的方法:把值寫到括號中,並且在括號之前寫一個反斜槓。例如:
|
||||
|
||||
```swift
|
||||
let apples = 3
|
||||
let oranges = 5
|
||||
let appleSummary = "I have \(apples) apples."
|
||||
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 使用`\()`來把一個浮點計算轉換成字符串,並加上某人的名字,和他打個招呼。
|
||||
|
||||
使用方括號`[]`來創建數組和字典,並使用下標或者鍵(key)來訪問元素。
|
||||
|
||||
```swift
|
||||
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
|
||||
shoppingList[1] = "bottle of water"
|
||||
```
|
||||
|
||||
```swift
|
||||
var occupations = [
|
||||
"Malcolm": "Captain",
|
||||
"Kaylee": "Mechanic",
|
||||
]
|
||||
occupations["Jayne"] = "Public Relations"
|
||||
```
|
||||
|
||||
要創建一個空數組或者字典,使用初始化語法。
|
||||
|
||||
```swift
|
||||
let emptyArray = String[]()
|
||||
let emptyDictionary = Dictionary<String, Float>()
|
||||
```
|
||||
|
||||
如果類型信息可以被推斷出來,你可以用`[]`和`[:]`來創建空數組和空字典——就像你聲明變量或者給函數傳參數的時候一樣。
|
||||
|
||||
```swift
|
||||
shoppingList = [] // 去逛街並買點東西
|
||||
```
|
||||
|
||||
<a name="control_flow"></a>
|
||||
## 控制流
|
||||
|
||||
使用`if`和`switch`來進行條件操作,使用`for-in`、`for`、`while`和`do-while`來進行循環。包裹條件和循環變量括號可以省略,但是語句體的大括號是必須的。
|
||||
|
||||
```swift
|
||||
let individualScores = [75, 43, 103, 87, 12]
|
||||
var teamScore = 0
|
||||
for score in individualScores {
|
||||
if score > 50 {
|
||||
teamScore += 3
|
||||
} else {
|
||||
teamScore += 1
|
||||
}
|
||||
}
|
||||
teamScore
|
||||
```
|
||||
|
||||
在`if`語句中,條件必須是一個布爾表達式——這意味著像`if score { ... }`這樣的代碼將報錯,而不會隱形地與 0 做對比。
|
||||
|
||||
你可以一起使用`if`和`let`來處理值缺失的情況。有些變量的值是可選的。一個可選的值可能是一個具體的值或者是`nil`,表示值缺失。在類型後面加一個問號來標記這個變量的值是可選的。
|
||||
|
||||
```swift
|
||||
var optionalString: String? = "Hello"
|
||||
optionalString == nil
|
||||
|
||||
var optionalName: String? = "John Appleseed"
|
||||
var greeting = "Hello!"
|
||||
if let name = optionalName {
|
||||
greeting = "Hello, \(name)"
|
||||
}
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 把`optionalName`改成`nil`,greeting會是什麼?添加一個`else`語句,當`optionalName`是`nil`時給greeting賦一個不同的值。
|
||||
|
||||
如果變量的可選值是`nil`,條件會判斷為`false`,大括號中的代碼會被跳過。如果不是`nil`,會將值賦給`let`後面的常量,這樣代碼塊中就可以使用這個值了。
|
||||
|
||||
`switch`支持任意類型的數據以及各種比較操作——不僅僅是整數以及測試相等。
|
||||
|
||||
```swift
|
||||
let vegetable = "red pepper"
|
||||
switch vegetable {
|
||||
case "celery":
|
||||
let vegetableComment = "Add some raisins and make ants on a log."
|
||||
case "cucumber", "watercress":
|
||||
let vegetableComment = "That would make a good tea sandwich."
|
||||
case let x where x.hasSuffix("pepper"):
|
||||
let vegetableComment = "Is it a spicy \(x)?"
|
||||
default:
|
||||
let vegetableComment = "Everything tastes good in soup."
|
||||
}
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 刪除`default`語句,看看會有什麼錯誤?
|
||||
|
||||
運行`switch`中匹配到的子句之後,程序會退出`switch`語句,並不會繼續向下運行,所以不需要在每個子句結尾寫`break`。
|
||||
|
||||
你可以使用`for-in`來遍歷字典,需要兩個變量來表示每個鍵值對。
|
||||
|
||||
```swift
|
||||
let interestingNumbers = [
|
||||
"Prime": [2, 3, 5, 7, 11, 13],
|
||||
"Fibonacci": [1, 1, 2, 3, 5, 8],
|
||||
"Square": [1, 4, 9, 16, 25],
|
||||
]
|
||||
var largest = 0
|
||||
for (kind, numbers) in interestingNumbers {
|
||||
for number in numbers {
|
||||
if number > largest {
|
||||
largest = number
|
||||
}
|
||||
}
|
||||
}
|
||||
largest
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 添加另一個變量來記錄哪種類型的數字是最大的。
|
||||
|
||||
使用`while`來重複運行一段代碼直到不滿足條件。循環條件可以在開頭也可以在結尾。
|
||||
|
||||
```swift
|
||||
var n = 2
|
||||
while n < 100 {
|
||||
n = n * 2
|
||||
}
|
||||
n
|
||||
|
||||
var m = 2
|
||||
do {
|
||||
m = m * 2
|
||||
} while m < 100
|
||||
m
|
||||
```
|
||||
|
||||
你可以在循環中使用`..`來表示範圍,也可以使用傳統的寫法,兩者是等價的:
|
||||
|
||||
```swift
|
||||
var firstForLoop = 0
|
||||
for i in 0..3 {
|
||||
firstForLoop += i
|
||||
}
|
||||
firstForLoop
|
||||
|
||||
var secondForLoop = 0
|
||||
for var i = 0; i < 3; ++i {
|
||||
secondForLoop += 1
|
||||
}
|
||||
secondForLoop
|
||||
```
|
||||
|
||||
使用`..`創建的範圍不包含上界,如果想包含的話需要使用`...`。
|
||||
|
||||
<a name="functions_and_closures"></a>
|
||||
## 函數和閉包
|
||||
|
||||
使用`func`來聲明一個函數,使用名字和參數來調用函數。使用`->`來指定函數返回值。
|
||||
|
||||
```swift
|
||||
func greet(name: String, day: String) -> String {
|
||||
return "Hello \(name), today is \(day)."
|
||||
}
|
||||
greet("Bob", "Tuesday")
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 刪除`day`參數,添加一個參數來表示今天吃了什麼午飯。
|
||||
|
||||
使用一個元組來返回多個值。
|
||||
|
||||
```swift
|
||||
func getGasPrices() -> (Double, Double, Double) {
|
||||
return (3.59, 3.69, 3.79)
|
||||
}
|
||||
getGasPrices()
|
||||
```
|
||||
|
||||
函數可以帶有可變個數的參數,這些參數在函數內表現為數組的形式:
|
||||
|
||||
```swift
|
||||
func sumOf(numbers: Int...) -> Int {
|
||||
var sum = 0
|
||||
for number in numbers {
|
||||
sum += number
|
||||
}
|
||||
return sum
|
||||
}
|
||||
sumOf()
|
||||
sumOf(42, 597, 12)
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 寫一個計算參數平均值的函數。
|
||||
|
||||
函數可以嵌套。被嵌套的函數可以訪問外側函數的變量,你可以使用嵌套函數來重構一個太長或者太複雜的函數。
|
||||
|
||||
```swift
|
||||
func returnFifteen() -> Int {
|
||||
var y = 10
|
||||
func add() {
|
||||
y += 5
|
||||
}
|
||||
add()
|
||||
return y
|
||||
}
|
||||
returnFifteen()
|
||||
```
|
||||
|
||||
函數是第一等類型,這意味著函數可以作為另一個函數的返回值。
|
||||
|
||||
```swift
|
||||
func makeIncrementer() -> (Int -> Int) {
|
||||
func addOne(number: Int) -> Int {
|
||||
return 1 + number
|
||||
}
|
||||
return addOne
|
||||
}
|
||||
var increment = makeIncrementer()
|
||||
increment(7)
|
||||
```
|
||||
|
||||
函數也可以當做參數傳入另一個函數。
|
||||
|
||||
```swift
|
||||
func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool {
|
||||
for item in list {
|
||||
if condition(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func lessThanTen(number: Int) -> Bool {
|
||||
return number < 10
|
||||
}
|
||||
var numbers = [20, 19, 7, 12]
|
||||
hasAnyMatches(numbers, lessThanTen)
|
||||
```
|
||||
|
||||
函數實際上是一種特殊的閉包,你可以使用`{}`來創建一個匿名閉包。使用`in`將參數和返回值類型聲明與閉包涵數體進行分離。
|
||||
|
||||
```swift
|
||||
numbers.map({
|
||||
(number: Int) -> Int in
|
||||
let result = 3 * number
|
||||
return result
|
||||
})
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 重寫閉包,對所有奇數返回0。
|
||||
|
||||
有很多種創建閉包的方法。如果一個閉包的類型已知,比如作為一個回調函數,你可以忽略參數的類型和返回值。單個語句閉包會把它語句的值當做結果返回。
|
||||
|
||||
```swift
|
||||
numbers.map({ number in 3 * number })
|
||||
```
|
||||
|
||||
你可以通過參數位置而不是參數名字來引用參數——這個方法在非常短的閉包中非常有用。當一個閉包作為最後一個參數傳給一個函數的時候,它可以直接跟在括號後面。
|
||||
|
||||
```swift
|
||||
sort([1, 5, 3, 12, 2]) { $0 > $1 }
|
||||
```
|
||||
|
||||
<a name="objects_and_classes"></a>
|
||||
## 對像和類
|
||||
|
||||
使用`class`和類名來創建一個類。類中屬性的聲明和常量、變量聲明一樣,唯一的區別就是它們的上下文是類。同樣,方法和函數聲明也一樣。
|
||||
|
||||
```swift
|
||||
class Shape {
|
||||
var numberOfSides = 0
|
||||
func simpleDescription() -> String {
|
||||
return "A shape with \(numberOfSides) sides."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 使用`let`添加一個常量屬性,再添加一個接收一個參數的方法。
|
||||
|
||||
要創建一個類的實例,在類名後面加上括號。使用點語法來訪問實例的屬性和方法。
|
||||
|
||||
```swift
|
||||
var shape = Shape()
|
||||
shape.numberOfSides = 7
|
||||
var shapeDescription = shape.simpleDescription()
|
||||
```
|
||||
|
||||
這個版本的`Shape`類缺少了一些重要的東西:一個構造函數來初始化類實例。使用`init`來創建一個構造器。
|
||||
|
||||
```swift
|
||||
class NamedShape {
|
||||
var numberOfSides: Int = 0
|
||||
var name: String
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
func simpleDescription() -> String {
|
||||
return "A shape with \(numberOfSides) sides."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意`self`被用來區別實例變量。當你創建實例的時候,像傳入函數參數一樣給類傳入構造器的參數。每個屬性都需要賦值——無論是通過聲明(就像`numberOfSides`)還是通過構造器(就像`name`)。
|
||||
|
||||
如果你需要在刪除對像之前進行一些清理工作,使用`deinit`創建一個析構函數。
|
||||
|
||||
子類的定義方法是在它們的類名後面加上父類的名字,用冒號分割。創建類的時候並不需要一個標準的根類,所以你可以忽略父類。
|
||||
|
||||
子類如果要重寫父類的方法的話,需要用`override`標記——如果沒有添加`override`就重寫父類方法的話編譯器會報錯。編譯器同樣會檢測`override`標記的方法是否確實在父類中。
|
||||
|
||||
```swift
|
||||
class Square: NamedShape {
|
||||
var sideLength: Double
|
||||
|
||||
init(sideLength: Double, name: String) {
|
||||
self.sideLength = sideLength
|
||||
super.init(name: name)
|
||||
numberOfSides = 4
|
||||
}
|
||||
|
||||
func area() -> Double {
|
||||
return sideLength * sideLength
|
||||
}
|
||||
|
||||
override func simpleDescription() -> String {
|
||||
return "A square with sides of length \(sideLength)."
|
||||
}
|
||||
}
|
||||
let test = Square(sideLength: 5.2, name: "my test square")
|
||||
test.area()
|
||||
test.simpleDescription()
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 創建`NamedShape`的另一個子類`Circle`,構造器接收兩個參數,一個是半徑一個是名稱,實現`area`和`describe`方法。
|
||||
|
||||
屬性可以有 getter 和 setter 。
|
||||
|
||||
```swift
|
||||
class EquilateralTriangle: NamedShape {
|
||||
var sideLength: Double = 0.0
|
||||
|
||||
init(sideLength: Double, name: String) {
|
||||
self.sideLength = sideLength
|
||||
super.init(name: name)
|
||||
numberOfSides = 3
|
||||
}
|
||||
|
||||
var perimeter: Double {
|
||||
get {
|
||||
return 3.0 * sideLength
|
||||
}
|
||||
set {
|
||||
sideLength = newValue / 3.0
|
||||
}
|
||||
}
|
||||
|
||||
override func simpleDescription() -> String {
|
||||
return "An equilateral triagle with sides of length \(sideLength)."
|
||||
}
|
||||
}
|
||||
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
|
||||
triangle.perimeter
|
||||
triangle.perimeter = 9.9
|
||||
triangle.sideLength
|
||||
```
|
||||
|
||||
在`perimeter`的 setter 中,新值的名字是`newValue`。你可以在`set`之後顯式的設置一個名字。
|
||||
|
||||
注意`EquilateralTriangle`類的構造器執行了三步:
|
||||
|
||||
1. 設置子類聲明的屬性值
|
||||
2. 調用父類的構造器
|
||||
3. 改變父類定義的屬性值。其他的工作比如調用方法、getters和setters也可以在這個階段完成。
|
||||
|
||||
如果你不需要計算屬性,但是仍然需要在設置一個新值之前或者之後運行代碼,使用`willSet`和`didSet`。
|
||||
|
||||
比如,下面的類確保三角形的邊長總是和正方形的邊長相同。
|
||||
|
||||
```swift
|
||||
class TriangleAndSquare {
|
||||
var triangle: EquilateralTriangle {
|
||||
willSet {
|
||||
square.sideLength = newValue.sideLength
|
||||
}
|
||||
}
|
||||
var square: Square {
|
||||
willSet {
|
||||
triangle.sideLength = newValue.sideLength
|
||||
}
|
||||
}
|
||||
init(size: Double, name: String) {
|
||||
square = Square(sideLength: size, name: name)
|
||||
triangle = EquilateralTriangle(sideLength: size, name: name)
|
||||
}
|
||||
}
|
||||
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
|
||||
triangleAndSquare.square.sideLength
|
||||
triangleAndSquare.triangle.sideLength
|
||||
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
|
||||
triangleAndSquare.triangle.sideLength
|
||||
```
|
||||
|
||||
類中的方法和一般的函數有一個重要的區別,函數的參數名只在函數內部使用,但是方法的參數名需要在調用的時候顯式說明(除了第一個參數)。默認情況下,方法的參數名和它在方法內部的名字一樣,不過你也可以定義第二個名字,這個名字被用在方法內部。
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count: Int = 0
|
||||
func incrementBy(amount: Int, numberOfTimes times: Int) {
|
||||
count += amount * times
|
||||
}
|
||||
}
|
||||
var counter = Counter()
|
||||
counter.incrementBy(2, numberOfTimes: 7)
|
||||
```
|
||||
|
||||
處理變量的可選值時,你可以在操作(比如方法、屬性和子腳本)之前加`?`。如果`?`之前的值是`nil`,`?`後面的東西都會被忽略,並且整個表達式返回`nil`。否則,`?`之後的東西都會被運行。在這兩種情況下,整個表達式的值也是一個可選值。
|
||||
|
||||
```swift
|
||||
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
|
||||
let sideLength = optionalSquare?.sideLength
|
||||
```
|
||||
|
||||
<a name="enumerations_and_structure"></a>
|
||||
## 枚舉和結構體
|
||||
|
||||
使用`enum`來創建一個枚舉。就像類和其他所有命名類型一樣,枚舉可以包含方法。
|
||||
|
||||
```swift
|
||||
enum Rank: Int {
|
||||
case Ace = 1
|
||||
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
|
||||
case Jack, Queen, King
|
||||
func simpleDescription() -> String {
|
||||
switch self {
|
||||
case .Ace:
|
||||
return "ace"
|
||||
case .Jack:
|
||||
return "jack"
|
||||
case .Queen:
|
||||
return "queen"
|
||||
case .King:
|
||||
return "king"
|
||||
default:
|
||||
return String(self.toRaw())
|
||||
}
|
||||
}
|
||||
}
|
||||
let ace = Rank.Ace
|
||||
let aceRawValue = ace.toRaw()
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 寫一個函數,通過比較它們的原始值來比較兩個`Rank`值。
|
||||
|
||||
在上面的例子中,枚舉原始值的類型是`Int`,所以你只需要設置第一個原始值。剩下的原始值會按照順序賦值。你也可以使用字符串或者浮點數作為枚舉的原始值。
|
||||
|
||||
使用`toRaw`和`fromRaw`函數來在原始值和枚舉值之間進行轉換。
|
||||
|
||||
```swift
|
||||
if let convertedRank = Rank.fromRaw(3) {
|
||||
let threeDescription = convertedRank.simpleDescription()
|
||||
}
|
||||
```
|
||||
|
||||
枚舉的成員值是實際值,並不是原始值的另一種表達方法。實際上,如果原始值沒有意義,你不需要設置。
|
||||
|
||||
```swift
|
||||
enum Suit {
|
||||
case Spades, Hearts, Diamonds, Clubs
|
||||
func simpleDescription() -> String {
|
||||
switch self {
|
||||
case .Spades:
|
||||
return "spades"
|
||||
case .Hearts:
|
||||
return "hearts"
|
||||
case .Diamonds:
|
||||
return "diamonds"
|
||||
case .Clubs:
|
||||
return "clubs"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
let hearts = Suit.Hearts
|
||||
let heartsDescription = hearts.simpleDescription()
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 給`Suit`添加一個`color`方法,對`spades`和`clubs`返回「black」,對`hearts`和`diamonds`返回「red」。
|
||||
|
||||
注意,有兩種方式可以引用`Hearts`成員:給`hearts`常量賦值時,枚舉成員`Suit.Hearts`需要用全名來引用,因為常量沒有顯式指定類型。在`switch`裡,枚舉成員使用縮寫`.Hearts`來引用,因為`self`的值已經知道是一個`suit`。已知變量類型的情況下你可以使用縮寫。
|
||||
|
||||
使用`struct`來創建一個結構體。結構體和類有很多相同的地方,比如方法和構造器。它們之間最大的一個區別就是
|
||||
結構體是傳值,類是傳引用。
|
||||
|
||||
```swift
|
||||
struct Card {
|
||||
var rank: Rank
|
||||
var suit: Suit
|
||||
func simpleDescription() -> String {
|
||||
return "The \(rank.simpleDescription()) of \
|
||||
(suit.simpleDescription())"
|
||||
}
|
||||
}
|
||||
let threeOfSpades = Card(rank: .Three, suit: .Spades)
|
||||
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 給`Card`添加一個方法,創建一副完整的撲克牌並把每張牌的 rank 和 suit 對應起來。
|
||||
|
||||
一個枚舉成員的實例可以有實例值。相同枚舉成員的實例可以有不同的值。創建實例的時候傳入值即可。實例值和原始值是不同的:枚舉成員的原始值對於所有實例都是相同的,而且你是在定義枚舉的時候設置原始值。
|
||||
|
||||
例如,考慮從服務器獲取日出和日落的時間。服務器會返回正常結果或者錯誤信息。
|
||||
|
||||
```swift
|
||||
enum ServerResponse {
|
||||
case Result(String, String)
|
||||
case Error(String)
|
||||
}
|
||||
|
||||
let success = ServerResponse.Result("6:00 am", "8:09 pm")
|
||||
let failure = ServerResponse.Error("Out of cheese.")
|
||||
|
||||
switch success {
|
||||
case let .Result(sunrise, sunset):
|
||||
let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
|
||||
case let .Error(error):
|
||||
let serverResponse = "Failure... \(error)"
|
||||
}
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 給`ServerResponse`和`switch`添加第三種情況。
|
||||
|
||||
注意如何從`ServerResponse`中提取日昇和日落時間。
|
||||
|
||||
<a name="protocols_and_extensions"></a>
|
||||
## 協議和擴展
|
||||
|
||||
使用`protocol`來聲明一個協議。
|
||||
|
||||
```swift
|
||||
protocol ExampleProtocol {
|
||||
var simpleDescription: String { get }
|
||||
mutating func adjust()
|
||||
}
|
||||
```
|
||||
|
||||
類、枚舉和結構體都可以實現協議。
|
||||
|
||||
```swift
|
||||
class SimpleClass: ExampleProtocol {
|
||||
var simpleDescription: String = "A very simple class."
|
||||
var anotherProperty: Int = 69105
|
||||
func adjust() {
|
||||
simpleDescription += " Now 100% adjusted."
|
||||
}
|
||||
}
|
||||
var a = SimpleClass()
|
||||
a.adjust()
|
||||
let aDescription = a.simpleDescription
|
||||
|
||||
struct SimpleStructure: ExampleProtocol {
|
||||
var simpleDescription: String = "A simple structure"
|
||||
mutating func adjust() {
|
||||
simpleDescription += " (adjusted)"
|
||||
}
|
||||
}
|
||||
var b = SimpleStructure()
|
||||
b.adjust()
|
||||
let bDescription = b.simpleDescription
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 寫一個實現這個協議的枚舉。
|
||||
|
||||
注意聲明`SimpleStructure`時候`mutating`關鍵字用來標記一個會修改結構體的方法。`SimpleClass`的聲明不需要標記任何方法因為類中的方法經常會修改類。
|
||||
|
||||
使用`extension`來為現有的類型添加功能,比如新的方法和參數。你可以使用擴展來改造定義在別處,甚至是從外部庫或者框架引入的一個類型,使得這個類型遵循某個協議。
|
||||
|
||||
```swift
|
||||
extension Int: ExampleProtocol {
|
||||
var simpleDescription: String {
|
||||
return "The number \(self)"
|
||||
}
|
||||
mutating func adjust() {
|
||||
self += 42
|
||||
}
|
||||
}
|
||||
7.simpleDescription
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 給`Double`類型寫一個擴展,添加`absoluteValue`功能。
|
||||
|
||||
你可以像使用其他命名類型一樣使用協議名——例如,創建一個有不同類型但是都實現一個協議的對象集合。當你處理類型是協議的值時,協議外定義的方法不可用。
|
||||
|
||||
```swift
|
||||
let protocolValue: ExampleProtocol = a
|
||||
protocolValue.simpleDescription
|
||||
// protocolValue.anotherProperty // Uncomment to see the error
|
||||
```
|
||||
|
||||
即使`protocolValue`變量運行時的類型是`simpleClass`,編譯器會把它的類型當做`ExampleProtocol`。這表示你不能調用類在它實現的協議之外實現的方法或者屬性。
|
||||
|
||||
<a name="generics"></a>
|
||||
## 泛型
|
||||
|
||||
在尖括號裡寫一個名字來創建一個泛型函數或者類型。
|
||||
|
||||
```swift
|
||||
func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] {
|
||||
var result = ItemType[]()
|
||||
for i in 0..times {
|
||||
result += item
|
||||
}
|
||||
return result
|
||||
}
|
||||
repeat("knock", 4)
|
||||
```
|
||||
|
||||
你也可以創建泛型類、枚舉和結構體。
|
||||
|
||||
```swift
|
||||
// Reimplement the Swift standard library's optional type
|
||||
enum OptionalValue<T> {
|
||||
case None
|
||||
case Some(T)
|
||||
}
|
||||
var possibleInteger: OptionalValue<Int> = .None
|
||||
possibleInteger = .Some(100)
|
||||
```
|
||||
|
||||
在類型名後面使用`where`來指定對類型的需求,比如,限定類型實現某一個協議,限定兩個類型是相同的,或者限定某個類必須有一個特定的父類
|
||||
|
||||
```swift
|
||||
func anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.Element> (lhs: T, rhs: U) -> Bool {
|
||||
for lhsItem in lhs {
|
||||
for rhsItem in rhs {
|
||||
if lhsItem == rhsItem {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
anyCommonElements([1, 2, 3], [3])
|
||||
```
|
||||
|
||||
> 練習:
|
||||
> 修改`anyCommonElements`函數來創建一個函數,返回一個數組,內容是兩個序列的共有元素。
|
||||
|
||||
簡單起見,你可以忽略`where`,只在冒號後面寫協議或者類名。` <T: Equatable>`和`<T where T: Equatable>`是等價的。
|
BIN
source-tw/chapter1/GuidedTour.playground.zip
Normal file
BIN
source-tw/chapter1/GuidedTour.playground.zip
Normal file
Binary file not shown.
4
source-tw/chapter1/chapter1.md
Normal file
4
source-tw/chapter1/chapter1.md
Normal file
@ -0,0 +1,4 @@
|
||||
# 歡迎使用 Swift
|
||||
|
||||
在本章中您將瞭解 Swift 的特性和開發歷史,並對 Swift 有一個初步的瞭解。
|
||||
|
696
source-tw/chapter2/01_The_Basics.md
Normal file
696
source-tw/chapter2/01_The_Basics.md
Normal file
@ -0,0 +1,696 @@
|
||||
> 翻譯:[numbbbbb](https://github.com/numbbbbb), [lyuka](https://github.com/lyuka), [JaySurplus](https://github.com/JaySurplus)
|
||||
> 校對:[lslxdx](https://github.com/lslxdx)
|
||||
|
||||
# 基礎部分
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [常量和變量](#constants_and_variables)
|
||||
- [註釋](#comments)
|
||||
- [分號](#semicolons)
|
||||
- [整數](#integers)
|
||||
- [浮點數](#floating-point_numbers)
|
||||
- [類型安全和類型推斷](#type_safety_and_type_inference)
|
||||
- [數值型字面量](#numeric_literals)
|
||||
- [數值型類型轉換](#numeric_type_conversion)
|
||||
- [類型別名](#type_aliases)
|
||||
- [布爾值](#booleans)
|
||||
- [元組](#tuples)
|
||||
- [可選](#optionals)
|
||||
- [斷言](#assertions)
|
||||
|
||||
Swift 是 iOS 和 OS X 應用開發的一門新語言。然而,如果你有 C 或者 Objective-C 開發經驗的話,你會發現 Swift 的很多內容都是你熟悉的。
|
||||
|
||||
Swift 的類型是在 C 和 Objective-C 的基礎上提出的,`Int`是整型;`Double`和`Float`是浮點型;`Bool`是布爾型;`String`是字符串。Swift 還有兩個有用的集合類型,`Array`和`Dictionary`,請參考[集合類型](04_Collection_Types.html)。
|
||||
|
||||
就像 C 語言一樣,Swift 使用變量來進行存儲並通過變量名來關聯值。在 Swift 中,值不可變的變量有著廣泛的應用,它們就是常量,而且比 C 語言的常量更強大。在 Swift 中,如果你要處理的值不需要改變,那使用常量可以讓你的代碼更加安全並且更好地表達你的意圖。
|
||||
|
||||
除了我們熟悉的類型,Swift 還增加了 Objective-C 中沒有的類型比如元組(Tuple)。元組可以讓你創建或者傳遞一組數據,比如作為函數的返回值時,你可以用一個元組可以返回多個值。
|
||||
|
||||
Swift 還增加了可選(Optional)類型,用於處理值缺失的情況。可選表示「那兒有一個值,並且它等於 x 」或者「那兒沒有值」。可選有點像在 Objective-C 中使用`nil`,但是它可以用在任何類型上,不僅僅是類。可選類型比 Objective-C 中的`nil`指針更加安全也更具表現力,它是 Swift 許多強大特性的重要組成部分。
|
||||
|
||||
Swift 是一個類型安全的語言,可選就是一個很好的例子。Swift 可以讓你清楚地知道值的類型。如果你的代碼期望得到一個`String`,類型安全會阻止你不小心傳入一個`Int`。你可以在開發階段盡早發現並修正錯誤。
|
||||
|
||||
<a name="constants_and_variables"></a>
|
||||
## 常量和變量
|
||||
|
||||
常量和變量把一個名字(比如`maximumNumberOfLoginAttempts`或者`welcomeMessage`)和一個指定類型的值(比如數字`10`或者字符串`"Hello"`)關聯起來。常量的值一旦設定就不能改變,而變量的值可以隨意更改。
|
||||
|
||||
### 聲明常量和變量
|
||||
|
||||
常量和變量必須在使用前聲明,用`let`來聲明常量,用`var`來聲明變量。下面的例子展示了如何用常量和變量來記錄用戶嘗試登錄的次數:
|
||||
|
||||
```swift
|
||||
let maximumNumberOfLoginAttempts = 10
|
||||
var currentLoginAttempt = 0
|
||||
```
|
||||
|
||||
這兩行代碼可以被理解為:
|
||||
|
||||
「聲明一個名字是`maximumNumberOfLoginAttempts`的新常量,並給它一個值`10`。然後,聲明一個名字是`currentLoginAttempt`的變量並將它的值初始化為`0`.」
|
||||
|
||||
在這個例子中,允許的最大嘗試登錄次數被聲明為一個常量,因為這個值不會改變。當前嘗試登錄次數被聲明為一個變量,因為每次嘗試登錄失敗的時候都需要增加這個值。
|
||||
|
||||
你可以在一行中聲明多個常量或者多個變量,用逗號隔開:
|
||||
|
||||
```swift
|
||||
var x = 0.0, y = 0.0, z = 0.0
|
||||
```
|
||||
|
||||
>注意:
|
||||
如果你的代碼中有不需要改變的值,請使用`let`關鍵字將它聲明為常量。只將需要改變的值聲明為變量。
|
||||
|
||||
### 類型標注
|
||||
|
||||
當你聲明常量或者變量的時候可以加上_類型標注(type annotation)_,說明常量或者變量中要存儲的值的類型。如果要添加類型標注,需要在常量或者變量名後面加上一個冒號和空格,然後加上類型名稱。
|
||||
|
||||
這個例子給`welcomeMessage`變量添加了類型標注,表示這個變量可以存儲`String`類型的值:
|
||||
|
||||
```swift
|
||||
var welcomeMessage: String
|
||||
```
|
||||
|
||||
聲明中的冒號代表著「是...類型」,所以這行代碼可以被理解為:
|
||||
|
||||
「聲明一個類型為`String`,名字為`welcomeMessage`的變量。」
|
||||
|
||||
「類型為`String`」的意思是「可以存儲任意`String`類型的值。」
|
||||
|
||||
`welcomeMessage`變量現在可以被設置成任意字符串:
|
||||
|
||||
```swift
|
||||
welcomeMessage = "Hello"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
一般來說你很少需要寫類型標注。如果你在聲明常量或者變量的時候賦了一個初始值,Swift可以推斷出這個常量或者變量的類型,請參考[類型安全和類型推斷](#type_safety_and_type_inference)。在上面的例子中,沒有給`welcomeMessage`賦初始值,所以變量`welcomeMessage`的類型是通過一個類型標注指定的,而不是通過初始值推斷的。
|
||||
|
||||
### 常量和變量的命名
|
||||
|
||||
你可以用任何你喜歡的字符作為常量和變量名,包括 Unicode 字符:
|
||||
|
||||
```swift
|
||||
let π = 3.14159
|
||||
let 你好 = "你好世界"
|
||||
let □氟□= "dogcow"
|
||||
```
|
||||
|
||||
常量與變量名不能包含數學符號,箭頭,保留的(或者非法的)Unicode 碼位,連線與製表符。也不能以數字開頭,但是可以在常量與變量名的其他地方包含數字。
|
||||
|
||||
一旦你將常量或者變量聲明為確定的類型,你就不能使用相同的名字再次進行聲明,或者改變其存儲的值的類型。同時,你也不能將常量與變量進行互轉。
|
||||
|
||||
> 注意:
|
||||
如果你需要使用與Swift保留關鍵字相同的名稱作為常量或者變量名,你可以使用反引號(`)將關鍵字包圍的方式將其作為名字使用。無論如何,你應當避免使用關鍵字作為常量或變量名,除非你別無選擇。
|
||||
|
||||
你可以更改現有的變量值為其他同類型的值,在下面的例子中,`friendlyWelcome`的值從`"Hello!"`改為了`"Bonjour!"`:
|
||||
|
||||
```swift
|
||||
var friendlyWelcome = "Hello!"
|
||||
friendlyWelcome = "Bonjour!"
|
||||
// friendlyWelcome 現在是 "Bonjour!"
|
||||
```
|
||||
|
||||
與變量不同,常量的值一旦被確定就不能更改了。嘗試這樣做會導致編譯時報錯:
|
||||
|
||||
```swift
|
||||
let languageName = "Swift"
|
||||
languageName = "Swift++"
|
||||
// 這會報編譯時錯誤 - languageName 不可改變
|
||||
```
|
||||
|
||||
### 輸出常量和變量
|
||||
|
||||
你可以用`println`函數來輸出當前常量或變量的值:
|
||||
|
||||
```swift
|
||||
println(friendlyWelcome)
|
||||
// 輸出 "Bonjour!"
|
||||
```
|
||||
|
||||
`println`是一個用來輸出的全局函數,輸出的內容會在最後換行。如果你用 Xcode,`println`將會輸出內容到「console」面板上。(另一種函數叫`print`,唯一區別是在輸出內容最後不會換行。)
|
||||
|
||||
`println`函數輸出傳入的`String`值:
|
||||
|
||||
```swift
|
||||
println("This is a string")
|
||||
// 輸出 "This is a string"
|
||||
```
|
||||
|
||||
與 Cocoa 裡的`NSLog`函數類似的是,`println`函數可以輸出更複雜的信息。這些信息可以包含當前常量和變量的值。
|
||||
|
||||
Swift 用_字符串插值(string interpolation)_的方式把常量名或者變量名當做佔位符加入到長字符串中,Swift 會用當前常量或變量的值替換這些佔位符。將常量或變量名放入圓括號中,並在開括號前使用反斜槓將其轉義:
|
||||
|
||||
```swift
|
||||
println("The current value of friendlyWelcome is \(friendlyWelcome)")
|
||||
// 輸出 "The current value of friendlyWelcome is Bonjour!
|
||||
```
|
||||
|
||||
> 注意:
|
||||
字符串插值所有可用的選項,請參考[字符串插值](03_Strings_and_Characters.html#string_interpolation)。
|
||||
|
||||
<a name="comments"></a>
|
||||
## 註釋
|
||||
請將你的代碼中的非執行文本註釋成提示或者筆記以方便你將來閱讀。Swift 的編譯器將會在編譯代碼時自動忽略掉註釋部分。
|
||||
|
||||
Swift 中的註釋與C 語言的註釋非常相似。單行註釋以雙正斜槓(`//`)作為起始標記:
|
||||
|
||||
```swift
|
||||
// 這是一個註釋
|
||||
```
|
||||
|
||||
你也可以進行多行註釋,其起始標記為單個正斜槓後跟隨一個星號(`/*`),終止標記為一個星號後跟隨單個正斜槓(`*/`):
|
||||
|
||||
```swift
|
||||
/* 這是一個,
|
||||
多行註釋 */
|
||||
```
|
||||
|
||||
與 C 語言多行註釋不同,Swift 的多行註釋可以嵌套在其它的多行註釋之中。你可以先生成一個多行註釋塊,然後在這個註釋塊之中再嵌套成第二個多行註釋。終止註釋時先插入第二個註釋塊的終止標記,然後再插入第一個註釋塊的終止標記:
|
||||
|
||||
```swift
|
||||
/* 這是第一個多行註釋的開頭
|
||||
/* 這是第二個被嵌套的多行註釋 */
|
||||
這是第一個多行註釋的結尾 */
|
||||
```
|
||||
|
||||
通過運用嵌套多行註釋,你可以快速方便的註釋掉一大段代碼,即使這段代碼之中已經含有了多行註釋塊。
|
||||
|
||||
<a name="semicolons"></a>
|
||||
## 分號
|
||||
與其他大部分編程語言不同,Swift 並不強制要求你在每條語句的結尾處使用分號(`;`),當然,你也可以按照你自己的習慣添加分號。有一種情況下必須要用分號,即你打算在同一行內寫多條獨立的語句:
|
||||
|
||||
```swift
|
||||
let cat = "□□ println(cat)
|
||||
// 輸出 "□□
|
||||
```
|
||||
|
||||
<a name="integers"></a>
|
||||
## 整數
|
||||
|
||||
整數就是沒有小數部分的數字,比如`42`和`-23`。整數可以是`有符號`(正、負、零)或者`無符號`(正、零)。
|
||||
|
||||
Swift 提供了8,16,32和64位的有符號和無符號整數類型。這些整數類型和 C 語言的命名方式很像,比如8位無符號整數類型是`UInt8`,32位有符號整數類型是`Int32`。就像 Swift 的其他類型一樣,整數類型採用大寫命名法。
|
||||
|
||||
### 整數範圍
|
||||
|
||||
你可以訪問不同整數類型的`min`和`max`屬性來獲取對應類型的最大值和最小值:
|
||||
|
||||
```swift
|
||||
let minValue = UInt8.min // minValue 為 0,是 UInt8 類型的最小值
|
||||
let maxValue = UInt8.max // maxValue 為 255,是 UInt8 類型的最大值
|
||||
```
|
||||
|
||||
### Int
|
||||
|
||||
一般來說,你不需要專門指定整數的長度。Swift 提供了一個特殊的整數類型`Int`,長度與當前平台的原生字長相同:
|
||||
|
||||
* 在32位平台上,`Int`和`Int32`長度相同。
|
||||
* 在64位平台上,`Int`和`Int64`長度相同。
|
||||
|
||||
除非你需要特定長度的整數,一般來說使用`Int`就夠了。這可以提高代碼一致性和可復用性。即使是在32位平台上,`Int`可以存儲的整數範圍也可以達到`-2147483648`~`2147483647`,大多數時候這已經足夠大了。
|
||||
|
||||
### UInt
|
||||
|
||||
Swift 也提供了一個特殊的無符號類型`UInt`,長度與當前平台的原生字長相同:
|
||||
|
||||
* 在32位平台上,`UInt`和`UInt32`長度相同。
|
||||
* 在64位平台上,`UInt`和`UInt64`長度相同。
|
||||
|
||||
> 注意:
|
||||
盡量不要使用`UInt`,除非你真的需要存儲一個和當前平台原生字長相同的無符號整數。除了這種情況,最好使用`Int`,即使你要存儲的值已知是非負的。統一使用`Int`可以提高代碼的可復用性,避免不同類型數字之間的轉換,並且匹配數字的類型推斷,請參考[類型安全和類型推斷](#type_safety_and_type_inference)。
|
||||
|
||||
<a name="floating-point_numbers"></a>
|
||||
## 浮點數
|
||||
|
||||
浮點數是有小數部分的數字,比如`3.14159`,`0.1`和`-273.15`。
|
||||
|
||||
浮點類型比整數類型表示的範圍更大,可以存儲比`Int`類型更大或者更小的數字。Swift 提供了兩種有符號浮點數類型:
|
||||
|
||||
* `Double`表示64位浮點數。當你需要存儲很大或者很高精度的浮點數時請使用此類型。
|
||||
* `Float`表示32位浮點數。精度要求不高的話可以使用此類型。
|
||||
|
||||
> 注意:
|
||||
`Double`精確度很高,至少有15位數字,而`Float`最少只有6位數字。選擇哪個類型取決於你的代碼需要處理的值的範圍。
|
||||
|
||||
<a name="type_safety_and_type_inference"></a>
|
||||
## 類型安全和類型推斷
|
||||
|
||||
Swift 是一個_類型安全(type safe)_的語言。類型安全的語言可以讓你清楚地知道代碼要處理的值的類型。如果你的代碼需要一個`String`,你絕對不可能不小心傳進去一個`Int`。
|
||||
|
||||
由於 Swift 是類型安全的,所以它會在編譯你的代碼時進行_類型檢查(type checks)_,並把不匹配的類型標記為錯誤。這可以讓你在開發的時候盡早發現並修復錯誤。
|
||||
|
||||
當你要處理不同類型的值時,類型檢查可以幫你避免錯誤。然而,這並不是說你每次聲明常量和變量的時候都需要顯式指定類型。如果你沒有顯式指定類型,Swift 會使用_類型推斷(type inference)_來選擇合適的類型。有了類型推斷,編譯器可以在編譯代碼的時候自動推斷出表達式的類型。原理很簡單,只要檢查你賦的值即可。
|
||||
|
||||
因為有類型推斷,和 C 或者 Objective-C 比起來 Swift 很少需要聲明類型。常量和變量雖然需要明確類型,但是大部分工作並不需要你自己來完成。
|
||||
|
||||
當你聲明常量或者變量並賦初值的時候類型推斷非常有用。當你在聲明常量或者變量的時候賦給它們一個_字面量(literal value 或 literal)_即可觸發類型推斷。(字面量就是會直接出現在你代碼中的值,比如`42`和`3.14159`。)
|
||||
|
||||
例如,如果你給一個新常量賦值`42`並且沒有標明類型,Swift 可以推斷出常量類型是`Int`,因為你給它賦的初始值看起來像一個整數:
|
||||
|
||||
```swift
|
||||
let meaningOfLife = 42
|
||||
// meaningOfLife 會被推測為 Int 類型
|
||||
```
|
||||
|
||||
同理,如果你沒有給浮點字面量標明類型,Swift 會推斷你想要的是`Double`:
|
||||
|
||||
```swift
|
||||
let pi = 3.14159
|
||||
// pi 會被推測為 Double 類型
|
||||
```
|
||||
|
||||
當推斷浮點數的類型時,Swift 總是會選擇`Double`而不是`Float`。
|
||||
|
||||
如果表達式中同時出現了整數和浮點數,會被推斷為`Double`類型:
|
||||
|
||||
```swift
|
||||
let anotherPi = 3 + 0.14159
|
||||
// anotherPi 會被推測為 Double 類型
|
||||
```
|
||||
|
||||
原始值`3`沒有顯式聲明類型,而表達式中出現了一個浮點字面量,所以表達式會被推斷為`Double`類型。
|
||||
|
||||
<a name="numeric_literals"></a>
|
||||
## 數值型字面量
|
||||
|
||||
整數字面量可以被寫作:
|
||||
|
||||
* 一個十進制數,沒有前綴
|
||||
* 一個二進制數,前綴是`0b`
|
||||
* 一個八進制數,前綴是`0o`
|
||||
* 一個十六進制數,前綴是`0x`
|
||||
|
||||
下面的所有整數字面量的十進制值都是`17`:
|
||||
|
||||
```swift
|
||||
let decimalInteger = 17
|
||||
let binaryInteger = 0b10001 // 二進制的17
|
||||
let octalInteger = 0o21 // 八進制的17
|
||||
let hexadecimalInteger = 0x11 // 十六進制的17
|
||||
```
|
||||
|
||||
浮點字面量可以是十進制(沒有前綴)或者是十六進制(前綴是`0x`)。小數點兩邊必須有至少一個十進制數字(或者是十六進制的數字)。浮點字面量還有一個可選的_指數(exponent)_,在十進制浮點數中通過大寫或者小寫的`e`來指定,在十六進制浮點數中通過大寫或者小寫的`p`來指定。
|
||||
|
||||
如果一個十進制數的指數為`exp`,那這個數相當於基數和10^exp的乘積:
|
||||
* `1.25e2` 表示 1.25 × 10^2,等於 `125.0`。
|
||||
* `1.25e-2` 表示 1.25 × 10^-2,等於 `0.0125`。
|
||||
|
||||
如果一個十六進制數的指數為`exp`,那這個數相當於基數和2^exp的乘積:
|
||||
* `0xFp2` 表示 15 × 2^2,等於 `60.0`。
|
||||
* `0xFp-2` 表示 15 × 2^-2,等於 `3.75`。
|
||||
|
||||
下面的這些浮點字面量都等於十進制的`12.1875`:
|
||||
|
||||
```swift
|
||||
let decimalDouble = 12.1875
|
||||
let exponentDouble = 1.21875e1
|
||||
let hexadecimalDouble = 0xC.3p0
|
||||
```
|
||||
|
||||
數值類字面量可以包括額外的格式來增強可讀性。整數和浮點數都可以添加額外的零並且包含下劃線,並不會影響字面量:
|
||||
|
||||
```swift
|
||||
let paddedDouble = 000123.456
|
||||
let oneMillion = 1_000_000
|
||||
let justOverOneMillion = 1_000_000.000_000_1
|
||||
```
|
||||
|
||||
<a name="numeric_type_conversion"></a>
|
||||
## 數值型類型轉換
|
||||
|
||||
通常來講,即使代碼中的整數常量和變量已知非負,也請使用`Int`類型。總是使用默認的整數類型可以保證你的整數常量和變量可以直接被復用並且可以匹配整數類字面量的類型推斷。
|
||||
只有在必要的時候才使用其他整數類型,比如要處理外部的長度明確的數據或者為了優化性能、內存佔用等等。使用顯式指定長度的類型可以及時發現值溢出並且可以暗示正在處理特殊數據。
|
||||
|
||||
### 整數轉換
|
||||
|
||||
不同整數類型的變量和常量可以存儲不同範圍的數字。`Int8`類型的常量或者變量可以存儲的數字範圍是`-128`~`127`,而`UInt8`類型的常量或者變量能存儲的數字範圍是`0`~`255`。如果數字超出了常量或者變量可存儲的範圍,編譯的時候會報錯:
|
||||
|
||||
```swift
|
||||
let cannotBeNegative: UInt8 = -1
|
||||
// UInt8 類型不能存儲負數,所以會報錯
|
||||
let tooBig: Int8 = Int8.max + 1
|
||||
// Int8 類型不能存儲超過最大值的數,所以會報錯
|
||||
```
|
||||
|
||||
由於每種整數類型都可以存儲不同範圍的值,所以你必須根據不同情況選擇性使用數值型類型轉換。這種選擇性使用的方式,可以預防隱式轉換的錯誤並讓你的代碼中的類型轉換意圖變得清晰。
|
||||
|
||||
要將一種數字類型轉換成另一種,你要用當前值來初始化一個期望類型的新數字,這個數字的類型就是你的目標類型。在下面的例子中,常量`twoThousand`是`UInt16`類型,然而常量`one`是`UInt8`類型。它們不能直接相加,因為它們類型不同。所以要調用`UInt16(one)`來創建一個新的`UInt16`數字並用`one`的值來初始化,然後使用這個新數字來計算:
|
||||
|
||||
```swift
|
||||
let twoThousand: UInt16 = 2_000
|
||||
let one: UInt8 = 1
|
||||
let twoThousandAndOne = twoThousand + UInt16(one)
|
||||
```
|
||||
|
||||
現在兩個數字的類型都是`UInt16`,可以進行相加。目標常量`twoThousandAndOne`的類型被推斷為`UInt16`,因為它是兩個`UInt16`值的和。
|
||||
|
||||
`SomeType(ofInitialValue)`是調用 Swift 構造器並傳入一個初始值的默認方法。在語言內部,`UInt16`有一個構造器,可以接受一個`UInt8`類型的值,所以這個構造器可以用現有的`UInt8`來創建一個新的`UInt16`。注意,你並不能傳入任意類型的值,只能傳入`UInt16`內部有對應構造器的值。不過你可以擴展現有的類型來讓它可以接收其他類型的值(包括自定義類型),請參考[擴展](20_Extensions.html)。
|
||||
|
||||
### 整數和浮點數轉換
|
||||
|
||||
整數和浮點數的轉換必須顯式指定類型:
|
||||
|
||||
```swift
|
||||
let three = 3
|
||||
let pointOneFourOneFiveNine = 0.14159
|
||||
let pi = Double(three) + pointOneFourOneFiveNine
|
||||
// pi 等於 3.14159,所以被推測為 Double 類型
|
||||
```
|
||||
|
||||
這個例子中,常量`three`的值被用來創建一個`Double`類型的值,所以加號兩邊的數類型須相同。如果不進行轉換,兩者無法相加。
|
||||
|
||||
浮點數到整數的反向轉換同樣行,整數類型可以用`Double`或者`Float`類型來初始化:
|
||||
|
||||
```swift
|
||||
let integerPi = Int(pi)
|
||||
// integerPi 等於 3,所以被推測為 Int 類型
|
||||
```
|
||||
|
||||
當用這種方式來初始化一個新的整數值時,浮點值會被截斷。也就是說`4.75`會變成`4`,`-3.9`會變成`-3`。
|
||||
|
||||
> 注意:
|
||||
結合數字類常量和變量不同於結合數字類字面量。字面量`3`可以直接和字面量`0.14159`相加,因為數字字面量本身沒有明確的類型。它們的類型只在編譯器需要求值的時候被推測。
|
||||
|
||||
<a name="type_aliases"></a>
|
||||
## 類型別名
|
||||
|
||||
_類型別名(type aliases)_就是給現有類型定義另一個名字。你可以使用`typealias`關鍵字來定義類型別名。
|
||||
|
||||
當你想要給現有類型起一個更有意義的名字時,類型別名非常有用。假設你正在處理特定長度的外部資源的數據:
|
||||
|
||||
```swift
|
||||
typealias AudioSample = UInt16
|
||||
```
|
||||
|
||||
定義了一個類型別名之後,你可以在任何使用原始名的地方使用別名:
|
||||
|
||||
```swift
|
||||
var maxAmplitudeFound = AudioSample.min
|
||||
// maxAmplitudeFound 現在是 0
|
||||
```
|
||||
|
||||
本例中,`AudioSample`被定義為`UInt16`的一個別名。因為它是別名,`AudioSample.min`實際上是`UInt16.min`,所以會給`maxAmplitudeFound`賦一個初值`0`。
|
||||
|
||||
<a name="booleans"></a>
|
||||
## 布爾值
|
||||
|
||||
Swift 有一個基本的_布爾(Boolean)_類型,叫做`Bool`。布爾值指_邏輯上的(logical)_,因為它們只能是真或者假。Swift 有兩個布爾常量,`true`和`false`:
|
||||
|
||||
```swift
|
||||
let orangesAreOrange = true
|
||||
let turnipsAreDelicious = false
|
||||
```
|
||||
|
||||
`orangesAreOrange`和`turnipsAreDelicious`的類型會被推斷為`Bool`,因為它們的初值是布爾字面量。就像之前提到的`Int`和`Double`一樣,如果你創建變量的時候給它們賦值`true`或者`false`,那你不需要將常量或者變量聲明為`Bool`類型。初始化常量或者變量的時候如果所賦的值類型已知,就可以觸發類型推斷,這讓 Swift 代碼更加簡潔並且可讀性更高。
|
||||
|
||||
當你編寫條件語句比如`if`語句的時候,布爾值非常有用:
|
||||
|
||||
```swift
|
||||
if turnipsAreDelicious {
|
||||
println("Mmm, tasty turnips!")
|
||||
} else {
|
||||
println("Eww, turnips are horrible.")
|
||||
}
|
||||
// 輸出 "Eww, turnips are horrible."
|
||||
```
|
||||
|
||||
條件語句,例如`if`,請參考[控制流](05_Control_Flow.html)。
|
||||
|
||||
如果你在需要使用`Bool`類型的地方使用了非布爾值,Swift 的類型安全機制會報錯。下面的例子會報告一個編譯時錯誤:
|
||||
|
||||
```swift
|
||||
let i = 1
|
||||
if i {
|
||||
// 這個例子不會通過編譯,會報錯
|
||||
}
|
||||
```
|
||||
|
||||
然而,下面的例子是合法的:
|
||||
|
||||
```swift
|
||||
let i = 1
|
||||
if i == 1 {
|
||||
// 這個例子會編譯成功
|
||||
}
|
||||
```
|
||||
|
||||
`i == 1`的比較結果是`Bool`類型,所以第二個例子可以通過類型檢查。類似`i == 1`這樣的比較,請參考[基本操作符](05_Control_Flow.html)。
|
||||
|
||||
和 Swift 中的其他類型安全的例子一樣,這個方法可以避免錯誤並保證這塊代碼的意圖總是清晰的。
|
||||
|
||||
<a name="tuples"></a>
|
||||
## 元組
|
||||
|
||||
_元組(tuples)_把多個值組合成一個復合值。元組內的值可以使任意類型,並不要求是相同類型。
|
||||
|
||||
下面這個例子中,`(404, "Not Found")`是一個描述 _HTTP 狀態碼(HTTP status code)_的元組。HTTP 狀態碼是當你請求網頁的時候 web 服務器返回的一個特殊值。如果你請求的網頁不存在就會返回一個`404 Not Found`狀態碼。
|
||||
|
||||
```swift
|
||||
let http404Error = (404, "Not Found")
|
||||
// http404Error 的類型是 (Int, String),值是 (404, "Not Found")
|
||||
```
|
||||
|
||||
`(404, "Not Found")`元組把一個`Int`值和一個`String`值組合起來表示 HTTP 狀態碼的兩個部分:一個數字和一個人類可讀的描述。這個元組可以被描述為「一個類型為`(Int, String)`的元組」。
|
||||
|
||||
你可以把任意順序的類型組合成一個元組,這個元組可以包含所有類型。只要你想,你可以創建一個類型為`(Int, Int, Int)`或者`(String, Bool)`或者其他任何你想要的組合的元組。
|
||||
|
||||
你可以將一個元組的內容_分解(decompose)_成單獨的常量和變量,然後你就可以正常使用它們了:
|
||||
|
||||
```swift
|
||||
let (statusCode, statusMessage) = http404Error
|
||||
println("The status code is \(statusCode)")
|
||||
// 輸出 "The status code is 404"
|
||||
println("The status message is \(statusMessage)")
|
||||
// 輸出 "The status message is Not Found"
|
||||
```
|
||||
|
||||
如果你只需要一部分元組值,分解的時候可以把要忽略的部分用下劃線(`_`)標記:
|
||||
|
||||
```swift
|
||||
let (justTheStatusCode, _) = http404Error
|
||||
println("The status code is \(justTheStatusCode)")
|
||||
// 輸出 "The status code is 404"
|
||||
```
|
||||
|
||||
此外,你還可以通過下標來訪問元組中的單個元素,下標從零開始:
|
||||
|
||||
```swift
|
||||
println("The status code is \(http404Error.0)")
|
||||
// 輸出 "The status code is 404"
|
||||
println("The status message is \(http404Error.1)")
|
||||
// 輸出 "The status message is Not Found"
|
||||
```
|
||||
|
||||
你可以在定義元組的時候給單個元素命名:
|
||||
|
||||
```swift
|
||||
let http200Status = (statusCode: 200, description: "OK")
|
||||
```
|
||||
|
||||
給元組中的元素命名後,你可以通過名字來獲取這些元素的值:
|
||||
|
||||
```swift
|
||||
println("The status code is \(http200Status.statusCode)")
|
||||
// 輸出 "The status code is 200"
|
||||
println("The status message is \(http200Status.description)")
|
||||
// 輸出 "The status message is OK"
|
||||
```
|
||||
|
||||
作為函數返回值時,元組非常有用。一個用來獲取網頁的函數可能會返回一個`(Int, String)`元組來描述是否獲取成功。和只能返回一個類型的值比較起來,一個包含兩個不同類型值的元組可以讓函數的返回信息更有用。請參考[函數參數與返回值](06_Functions.html#Function_Parameters_and_Return_Values)。
|
||||
|
||||
> 注意:
|
||||
元組在臨時組織值的時候很有用,但是並不適合創建複雜的數據結構。如果你的數據結構並不是臨時使用,請使用類或者結構體而不是元組。請參考[類和結構體](09_Classes_and_Structures.html)。
|
||||
|
||||
<a name="optionals"></a>
|
||||
## 可選類型
|
||||
|
||||
使用_可選類型(optionals)_來處理值可能缺失的情況。可選類型表示:
|
||||
|
||||
* _有_值,等於 x
|
||||
|
||||
或者
|
||||
|
||||
* _沒有_值
|
||||
|
||||
> 注意:
|
||||
C 和 Objective-C 中並沒有可選類型這個概念。最接近的是 Objective-C 中的一個特性,一個方法要不返回一個對像要不返回`nil`,`nil`表示「缺少一個合法的對象」。然而,這只對對像起作用——對於結構體,基本的 C 類型或者枚舉類型不起作用。對於這些類型,Objective-C 方法一般會返回一個特殊值(比如`NSNotFound`)來暗示值缺失。這種方法假設方法的調用者知道並記得對特殊值進行判斷。然而,Swift 的可選類型可以讓你暗示_任意類型_的值缺失,並不需要一個特殊值。
|
||||
|
||||
來看一個例子。Swift 的`String`類型有一個叫做`toInt`的方法,作用是將一個`String`值轉換成一個`Int`值。然而,並不是所有的字符串都可以轉換成一個整數。字符串`"123"`可以被轉換成數字`123`,但是字符串`"hello, world"`不行。
|
||||
|
||||
下面的例子使用`toInt`方法來嘗試將一個`String`轉換成`Int`:
|
||||
|
||||
```swift
|
||||
let possibleNumber = "123"
|
||||
let convertedNumber = possibleNumber.toInt()
|
||||
// convertedNumber 被推測為類型 "Int?", 或者類型 "optional Int"
|
||||
```
|
||||
|
||||
因為`toInt`方法可能會失敗,所以它返回一個_可選類型(optional)_`Int`,而不是一個`Int`。一個可選的`Int`被寫作`Int?`而不是`Int`。問號暗示包含的值是可選類型,也就是說可能包含`Int`值也可能不包含值。(不能包含其他任何值比如`Bool`值或者`String`值。只能是`Int`或者什麼都沒有。)
|
||||
|
||||
### if 語句以及強制解析
|
||||
|
||||
你可以使用`if`語句來判斷一個可選是否包含值。如果可選類型有值,結果是`true`;如果沒有值,結果是`false`。
|
||||
|
||||
當你確定可選類型_確實_包含值之後,你可以在可選的名字後面加一個感歎號(`!`)來獲取值。這個驚歎號表示「我知道這個可選有值,請使用它。」這被稱為可選值的_強制解析(forced unwrapping)_:
|
||||
|
||||
```swift
|
||||
if convertedNumber {
|
||||
println("\(possibleNumber) has an integer value of \(convertedNumber!)")
|
||||
} else {
|
||||
println("\(possibleNumber) could not be converted to an integer")
|
||||
}
|
||||
// 輸出 "123 has an integer value of 123"
|
||||
```
|
||||
|
||||
更多關於`if`語句的內容,請參考[控制流](05_Control_Flow.html)。
|
||||
|
||||
> 注意:
|
||||
使用`!`來獲取一個不存在的可選值會導致運行時錯誤。使用`!`來強制解析值之前,一定要確定可選包含一個非`nil`的值。
|
||||
|
||||
<a name="optional_binding"></a>
|
||||
### 可選綁定
|
||||
|
||||
使用_可選綁定(optional binding)_來判斷可選類型是否包含值,如果包含就把值賦給一個臨時常量或者變量。可選綁定可以用在`if`和`while`語句中來對可選類型的值進行判斷並把值賦給一個常量或者變量。`if`和`while`語句,請參考[控制流](05_Control_Flow.html)。
|
||||
|
||||
像下面這樣在`if`語句中寫一個可選綁定:
|
||||
|
||||
```swift
|
||||
if let constantName = someOptional {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
你可以像上面這樣使用可選綁定來重寫`possibleNumber`這個例子:
|
||||
|
||||
```swift
|
||||
if let actualNumber = possibleNumber.toInt() {
|
||||
println("\(possibleNumber) has an integer value of \(actualNumber)")
|
||||
} else {
|
||||
println("\(possibleNumber) could not be converted to an integer")
|
||||
}
|
||||
// 輸出 "123 has an integer value of 123"
|
||||
```
|
||||
|
||||
這段代碼可以被理解為:
|
||||
|
||||
「如果`possibleNumber.toInt`返回的可選`Int`包含一個值,創建一個叫做`actualNumber`的新常量並將可選包含的值賦給它。」
|
||||
|
||||
如果轉換成功,`actualNumber`常量可以在`if`語句的第一個分支中使用。它已經被可選類型_包含的_值初始化過,所以不需要再使用`!`後綴來獲取它的值。在這個例子中,`actualNumber`只被用來輸出轉換結果。
|
||||
|
||||
你可以在可選綁定中使用常量和變量。如果你想在`if`語句的第一個分支中操作`actualNumber`的值,你可以改成`if var actualNumber`,這樣可選類型包含的值就會被賦給一個變量而非常量。
|
||||
|
||||
### nil
|
||||
|
||||
你可以給可選變量賦值為`nil`來表示它沒有值:
|
||||
|
||||
```swift
|
||||
var serverResponseCode: Int? = 404
|
||||
// serverResponseCode 包含一個可選的 Int 值 404
|
||||
serverResponseCode = nil
|
||||
// serverResponseCode 現在不包含值
|
||||
```
|
||||
|
||||
> 注意:
|
||||
`nil`不能用於非可選的常量和變量。如果你的代碼中有常量或者變量需要處理值缺失的情況,請把它們聲明成對應的可選類型。
|
||||
|
||||
如果你聲明一個可選常量或者變量但是沒有賦值,它們會自動被設置為`nil`:
|
||||
|
||||
```swift
|
||||
var surveyAnswer: String?
|
||||
// surveyAnswer 被自動設置為 nil
|
||||
```
|
||||
|
||||
> 注意:
|
||||
Swift 的`nil`和 Objective-C 中的`nil`並不一樣。在 Objective-C 中,`nil`是一個指向不存在對象的指針。在 Swift 中,`nil`不是指針——它是一個確定的值,用來表示值缺失。_任何_類型的可選狀態都可以被設置為`nil`,不只是對像類型。
|
||||
|
||||
### 隱式解析可選類型
|
||||
|
||||
如上所述,可選類型暗示了常量或者變量可以「沒有值」。可選可以通過`if`語句來判斷是否有值,如果有值的話可以通過可選綁定來解析值。
|
||||
|
||||
有時候在程序架構中,第一次被賦值之後,可以確定一個可選類型_總會_有值。在這種情況下,每次都要判斷和解析可選值是非常低效的,因為可以確定它總會有值。
|
||||
|
||||
這種類型的可選狀態被定義為_隱式解析可選類型(implicitly unwrapped optionals)_。把想要用作可選的類型的後面的問號(`String?`)改成感歎號(`String!`)來聲明一個隱式解析可選類型。
|
||||
|
||||
當可選類型被第一次賦值之後就可以確定之後一直有值的時候,隱式解析可選類型非常有用。隱式解析可選類型主要被用在 Swift 中類的構造過程中,請參考[類實例之間的循環強引用](16_Automatic_Reference_Counting.html#strong_reference_cycles_between_class_instances)。
|
||||
|
||||
一個隱式解析可選類型其實就是一個普通的可選類型,但是可以被當做非可選類型來使用,並不需要每次都使用解析來獲取可選值。下面的例子展示了可選類型`String`和隱式解析可選類型`String`之間的區別:
|
||||
|
||||
```swift
|
||||
let possibleString: String? = "An optional string."
|
||||
println(possibleString!) // 需要驚歎號來獲取值
|
||||
// 輸出 "An optional string."
|
||||
```
|
||||
|
||||
```swift
|
||||
let assumedString: String! = "An implicitly unwrapped optional string."
|
||||
println(assumedString) // 不需要感歎號
|
||||
// 輸出 "An implicitly unwrapped optional string."
|
||||
```
|
||||
|
||||
你可以把隱式解析可選類型當做一個可以自動解析的可選類型。你要做的只是聲明的時候把感歎號放到類型的結尾,而不是每次取值的可選名字的結尾。
|
||||
|
||||
> 注意:
|
||||
如果你在隱式解析可選類型沒有值的時候嘗試取值,會觸發運行時錯誤。和你在沒有值的普通可選類型後面加一個驚歎號一樣。
|
||||
|
||||
你仍然可以把隱式解析可選類型當做普通可選類型來判斷它是否包含值:
|
||||
|
||||
```swift
|
||||
if assumedString {
|
||||
println(assumedString)
|
||||
}
|
||||
// 輸出 "An implicitly unwrapped optional string."
|
||||
```
|
||||
|
||||
你也可以在可選綁定中使用隱式解析可選類型來檢查並解析它的值:
|
||||
|
||||
```swift
|
||||
if let definiteString = assumedString {
|
||||
println(definiteString)
|
||||
}
|
||||
// 輸出 "An implicitly unwrapped optional string."
|
||||
```
|
||||
|
||||
> 注意:
|
||||
如果一個變量之後可能變成`nil`的話請不要使用隱式解析可選類型。如果你需要在變量的生命週期中判斷是否是`nil`的話,請使用普通可選類型。
|
||||
|
||||
<a name="assertions"></a>
|
||||
## 斷言
|
||||
|
||||
可選類型可以讓你判斷值是否存在,你可以在代碼中優雅地處理值缺失的情況。然而,在某些情況下,如果值缺失或者值並不滿足特定的條件,你的代碼可能並不需要繼續執行。這時,你可以在你的代碼中觸發一個_斷言(assertion)_來結束代碼運行並通過調試來找到值缺失的原因。
|
||||
|
||||
### 使用斷言進行調試
|
||||
|
||||
斷言會在運行時判斷一個邏輯條件是否為`true`。從字面意思來說,斷言「斷言」一個條件是否為真。你可以使用斷言來保證在運行其他代碼之前,某些重要的條件已經被滿足。如果條件判斷為`true`,代碼運行會繼續進行;如果條件判斷為`false`,代碼運行停止,你的應用被終止。
|
||||
|
||||
如果你的代碼在調試環境下觸發了一個斷言,比如你在 Xcode 中構建並運行一個應用,你可以清楚地看到不合法的狀態發生在哪裡並檢查斷言被觸發時你的應用的狀態。此外,斷言允許你附加一條調試信息。
|
||||
|
||||
你可以使用全局`assert`函數來寫一個斷言。向`assert`函數傳入一個結果為`true`或者`false`的表達式以及一條信息,當表達式為`false`的時候這條信息會被顯示:
|
||||
|
||||
```swift
|
||||
let age = -3
|
||||
assert(age >= 0, "A person's age cannot be less than zero")
|
||||
// 因為 age < 0,所以斷言會觸發
|
||||
```
|
||||
|
||||
在這個例子中,只有`age >= 0`為`true`的時候代碼運行才會繼續,也就是說,當`age`的值非負的時候。如果`age`的值是負數,就像代碼中那樣,`age >= 0`為`false`,斷言被觸發,結束應用。
|
||||
|
||||
斷言信息不能使用字符串插值。斷言信息可以省略,就像這樣:
|
||||
|
||||
```swift
|
||||
assert(age >= 0)
|
||||
```
|
||||
|
||||
### 何時使用斷言
|
||||
|
||||
當條件可能為假時使用斷言,但是最終一定要_保證_條件為真,這樣你的代碼才能繼續運行。斷言的適用情景:
|
||||
|
||||
* 整數類型的下標索引被傳入一個自定義下標腳本實現,但是下標索引值可能太小或者太大。
|
||||
* 需要給函數傳入一個值,但是非法的值可能導致函數不能正常執行。
|
||||
* 一個可選值現在是`nil`,但是後面的代碼運行需要一個非`nil`值。
|
||||
|
||||
請參考[下標腳本](12_Subscripts.html)和[函數](06_Functions.html)。
|
||||
|
||||
> 注意:
|
||||
斷言可能導致你的應用終止運行,所以你應當仔細設計你的代碼來讓非法條件不會出現。然而,在你的應用發佈之前,有時候非法條件可能出現,這時使用斷言可以快速發現問題。
|
||||
|
457
source-tw/chapter2/02_Basic_Operators.md
Normal file
457
source-tw/chapter2/02_Basic_Operators.md
Normal file
@ -0,0 +1,457 @@
|
||||
> 翻譯:[xielingwang](https://github.com/xielingwang)
|
||||
> 校對:[Evilcome](https://github.com/Evilcome)
|
||||
|
||||
# 基本運算符
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [術語](#terminology)
|
||||
- [賦值運算符](#assignment_operator)
|
||||
- [數值運算符](#arithmetic_operators)
|
||||
- [組合賦值運算符(Compound Assignment Operators)](#compound_assignment_operators)
|
||||
- [比較運算符](#comparison_operators)
|
||||
- [三元條件運算符(Ternary Conditional Operator)](#ternary_conditional_operator)
|
||||
- [區間運算符](#range_operators)
|
||||
- [邏輯運算符](#logical_operators)
|
||||
|
||||
運算符是檢查、改變、合併值的特殊符號或短語。例如,加號`+`將兩個數相加(如`let i = 1 + 2`)。複雜些的運算例如邏輯與運算符`&&`(如`if enteredDoorCode && passedRetinaScan`),或讓 i 值加1的便捷自增運算符`++i`等。
|
||||
|
||||
Swift 支持大部分標準 C 語言的運算符,且改進許多特性來減少常規編碼錯誤。如:賦值符(`=`)不返回值,以防止把想要判斷相等運算符(`==`)的地方寫成賦值符導致的錯誤。數值運算符(`+`,`-`,`*`,`/`,`%`等)會檢測並不允許值溢出,以此來避免保存變量時由於變量大於或小於其類型所能承載的範圍時導致的異常結果。當然允許你使用 Swift 的溢出運算符來實現溢出。詳情參見[溢出運算符](23_Advanced_Operators.html#overflow_operators)。
|
||||
|
||||
區別於 C 語言,在 Swift 中你可以對浮點數進行取余運算(`%`),Swift 還提供了 C 語言沒有的表達兩數之間的值的區間運算符,(`a..b`和`a...b`),這方便我們表達一個區間內的數值。
|
||||
|
||||
本章節只描述了 Swift 中的基本運算符,[高級運算符](23_Advanced_Operators.html)包含了高級運算符,及如何自定義運算符,及如何進行自定義類型的運算符重載。
|
||||
|
||||
<a name="terminology"></a>
|
||||
## 術語
|
||||
|
||||
運算符有一元、二元和三元運算符。
|
||||
|
||||
- 一元運算符對單一操作對像操作(如`-a`)。一元運算符分前置符和後置運算符,前置運算符需緊排操作對像之前(如`!b`),後置運算符需緊跟操作對像之後(如`i++`)。
|
||||
- 二元運算符操作兩個操作對像(如`2 + 3`),是中置的,因為它們出現在兩個操作對像之間。
|
||||
- 三元運算符操作三個操作對象,和 C 語言一樣,Swift 只有一個三元運算符,就是三元條件運算符(`a ? b : c`)。
|
||||
|
||||
受運算符影響的值叫操作數,在表達式`1 + 2`中,加號`+`是二元運算符,它的兩個操作數是值`1`和`2`。
|
||||
|
||||
<a name="assignment_operator"></a>
|
||||
## 賦值運算符
|
||||
|
||||
賦值運算(`a = b`),表示用`b`的值來初始化或更新`a`的值:
|
||||
|
||||
```swift
|
||||
let b = 10
|
||||
var a = 5
|
||||
a = b
|
||||
// a 現在等於 10
|
||||
```
|
||||
|
||||
如果賦值的右邊是一個多元組,它的元素可以馬上被分解多個變量或變量:
|
||||
|
||||
```swiflt
|
||||
let (x, y) = (1, 2)
|
||||
// 現在 x 等於 1, y 等於 2
|
||||
```
|
||||
|
||||
與 C 語言和 Objective-C 不同,Swift 的賦值操作並不返回任何值。所以以下代碼是錯誤的:
|
||||
|
||||
```swift
|
||||
if x = y {
|
||||
// 此句錯誤, 因為 x = y 並不返回任何值
|
||||
}
|
||||
```
|
||||
|
||||
這個特性使你無法把(`==`)錯寫成(`=`),由於`if x = y`是錯誤代碼,Swift 從底層幫你避免了這些錯誤代碼。
|
||||
|
||||
<a name="arithmetic_operators"></a>
|
||||
## 數值運算
|
||||
|
||||
Swift 中所有數值類型都支持了基本的四則運算:
|
||||
|
||||
- 加法(`+`)
|
||||
- 減法(`-`)
|
||||
- 乘法(`*`)
|
||||
- 除法(`/`)
|
||||
|
||||
```swift
|
||||
1 + 2 // 等於 3
|
||||
5 - 3 // 等於 2
|
||||
2 * 3 // 等於 6
|
||||
10.0 / 2.5 // 等於 4.0
|
||||
```
|
||||
|
||||
與 C 語言和 Objective-C 不同的是,Swift 默認不允許在數值運算中出現溢出情況。但你可以使用 Swift 的溢出運算符來達到你有目的的溢出(如`a &+ b`)。詳情參見[溢出運算符](23_Advanced_Operators.html#overflow_operators)。
|
||||
|
||||
加法運算符也可用於`String`的拼接:
|
||||
|
||||
```swift
|
||||
"hello, " + "world" // 等於 "hello, world"
|
||||
```
|
||||
|
||||
兩個`Character`值或一個`String`和一個`Character`值,相加會生成一個新的`String`值:
|
||||
|
||||
```swift
|
||||
let dog: Character = "d"
|
||||
let cow: Character = "c"
|
||||
let dogCow = dog + cow
|
||||
// 譯者注: 原來的引號內是很可愛的小狗和小牛, 但win os下不支持表情字符, 所以改成了普通字符
|
||||
// dogCow 現在是 "dc"
|
||||
```
|
||||
|
||||
詳情參見[字符,字符串的拼接](03_Strings_and_Characters.html#concatenating_strings_and_characters)。
|
||||
|
||||
### 求余運算
|
||||
|
||||
求余運算(`a % b`)是計算`b`的多少倍剛剛好可以容入`a`,返回多出來的那部分(餘數)。
|
||||
|
||||
>注意:
|
||||
求余運算(`%`)在其他語言也叫取模運算。然而嚴格說來,我們看該運算符對負數的操作結果,"求余"比"取模"更合適些。
|
||||
|
||||
我們來談談取余是怎麼回事,計算`9 % 4`,你先計算出`4`的多少倍會剛好可以容入`9`中:
|
||||
|
||||

|
||||
|
||||
2倍,非常好,那餘數是1(用橙色標出)
|
||||
|
||||
在 Swift 中這麼來表達:
|
||||
|
||||
```swift
|
||||
9 % 4 // 等於 1
|
||||
```
|
||||
|
||||
為了得到`a % b`的結果,`%`計算了以下等式,並輸出`餘數`作為結果:
|
||||
|
||||
*a = (b × 倍數) + 餘數*
|
||||
|
||||
當`倍數`取最大值的時候,就會剛好可以容入`a`中。
|
||||
|
||||
把`9`和`4`代入等式中,我們得`1`:
|
||||
|
||||
```swift
|
||||
9 = (4 × 2) + 1
|
||||
```
|
||||
|
||||
同樣的方法,我來們計算 `-9 % 4`:
|
||||
|
||||
```swift
|
||||
-9 % 4 // 等於 -1
|
||||
```
|
||||
|
||||
把`-9`和`4`代入等式,`-2`是取到的最大整數:
|
||||
|
||||
```swift
|
||||
-9 = (4 × -2) + -1
|
||||
```
|
||||
|
||||
餘數是`-1`。
|
||||
|
||||
在對負數`b`求余時,`b`的符號會被忽略。這意味著 `a % b` 和 `a % -b`的結果是相同的。
|
||||
|
||||
### 浮點數求余計算
|
||||
|
||||
不同於 C 語言和 Objective-C,Swift 中是可以對浮點數進行求余的。
|
||||
|
||||
```swift
|
||||
8 % 2.5 // 等於 0.5
|
||||
```
|
||||
|
||||
這個例子中,`8`除於`2.5`等於`3`余`0.5`,所以結果是一個`Double`值`0.5`。
|
||||
|
||||

|
||||
|
||||
### 自增和自增運算
|
||||
|
||||
和 C 語言一樣,Swift 也提供了方便對變量本身加1或減1的自增(`++`)和自減(`--`)的運算符。其操作對象可以是整形和浮點型。
|
||||
□
|
||||
```swift
|
||||
var i = 0
|
||||
++i // 現在 i = 1
|
||||
```
|
||||
|
||||
每調用一次`++i`,`i`的值就會加1。實際上,`++i`是`i = i + 1`的簡寫,而`--i`是`i = i - 1`的簡寫。
|
||||
|
||||
`++`和`--`既是前置又是後置運算。`++i`,`i++`,`--i`和`i--`都是有效的寫法。
|
||||
|
||||
我們需要注意的是這些運算符修改了`i`後有一個返回值。如果你只想修改`i`的值,那你就可以忽略這個返回值。但如果你想使用返回值,你就需要留意前置和後置操作的返回值是不同的。
|
||||
|
||||
- 當`++`前置的時候,先自增再返回。
|
||||
|
||||
- 當`++`後置的時候,先返回再自增。
|
||||
|
||||
例如:
|
||||
|
||||
```swift
|
||||
var a = 0
|
||||
let b = ++a // a 和 b 現在都是 1
|
||||
let c = a++ // a 現在 2, 但 c 是 a 自增前的值 1
|
||||
```
|
||||
|
||||
上述例子,`let b = ++a`先把`a`加1了再返回`a`的值。所以`a`和`b`都是新值`1`。
|
||||
|
||||
而`let c = a++`,是先返回了`a`的值,然後`a`才加1。所以`c`得到了`a`的舊值1,而`a`加1後變成2。
|
||||
|
||||
除非你需要使用`i++`的特性,不然推薦你使用`++i`和`--i`,因為先修改後返回這樣的行為更符合我們的邏輯。
|
||||
|
||||
|
||||
### 一元負號
|
||||
|
||||
數值的正負號可以使用前綴`-`(即一元負號)來切換:
|
||||
|
||||
```swift
|
||||
let three = 3
|
||||
let minusThree = -three // minusThree 等於 -3
|
||||
let plusThree = -minusThree // plusThree 等於 3, 或 "負負3"
|
||||
```
|
||||
|
||||
一元負號(`-`)寫在操作數之前,中間沒有空格。
|
||||
|
||||
### 一元正號
|
||||
|
||||
一元正號(`+`)不做任何改變地返回操作數的值。
|
||||
|
||||
```swift
|
||||
let minusSix = -6
|
||||
let alsoMinusSix = +minusSix // alsoMinusSix 等於 -6
|
||||
```
|
||||
雖然一元`+`做無用功,但當你在使用一元負號來表達負數時,你可以使用一元正號來表達正數,如此你的代碼會具有對稱美。
|
||||
|
||||
|
||||
<a name="compound_assignment_operators"></a>
|
||||
## 復合賦值(Compound Assignment Operators)
|
||||
|
||||
如同強大的 C 語言,Swift 也提供把其他運算符和賦值運算(`=`)組合的復合賦值運算符,加賦運算(`+=`)是其中一個例子:
|
||||
|
||||
```swift
|
||||
var a = 1
|
||||
a += 2 // a 現在是 3
|
||||
```
|
||||
|
||||
表達式`a += 2`是`a = a + 2`的簡寫,一個加賦運算就把加法和賦值兩件事完成了。
|
||||
|
||||
>注意:
|
||||
復合賦值運算沒有返回值,`let b = a += 2`這類代碼是錯誤。這不同於上面提到的自增和自減運算符。
|
||||
|
||||
在[表達式](../chapter3/04_Expressions.html)章節裡有復合運算符的完整列表。
|
||||
□
|
||||
<a name="comparison_operators"></a>
|
||||
## 比較運算
|
||||
|
||||
所有標準 C 語言中的比較運算都可以在 Swift 中使用。
|
||||
|
||||
- 等於(`a == b`)
|
||||
- 不等於(`a != b`)
|
||||
- 大於(`a > b`)
|
||||
- 小於(`a < b`)
|
||||
- 大於等於(`a >= b`)
|
||||
- 小於等於(`a <= b`)
|
||||
|
||||
> 注意:
|
||||
Swift 也提供恆等`===`和不恆等`!==`這兩個比較符來判斷兩個對象是否引用同一個對像實例。更多細節在[類與結構](09_Classes_and_Structures.html)。
|
||||
|
||||
每個比較運算都返回了一個標識表達式是否成立的布爾值:
|
||||
|
||||
```swift
|
||||
1 == 1 // true, 因為 1 等於 1
|
||||
2 != 1 // true, 因為 2 不等於 1
|
||||
2 > 1 // true, 因為 2 大於 1
|
||||
1 < 2 // true, 因為 1 小於2
|
||||
1 >= 1 // true, 因為 1 大於等於 1
|
||||
2 <= 1 // false, 因為 2 並不小於等於 1
|
||||
```
|
||||
|
||||
比較運算多用於條件語句,如`if`條件:
|
||||
|
||||
```swift
|
||||
let name = "world"
|
||||
if name == "world" {
|
||||
println("hello, world")
|
||||
} else {
|
||||
println("I'm sorry \(name), but I don't recognize you")
|
||||
}
|
||||
// 輸出 "hello, world", 因為 `name` 就是等於 "world"
|
||||
```
|
||||
|
||||
關於`if`語句,請看[控制流](05_Control_Flow.html)。
|
||||
|
||||
<a name="ternary_conditional_operator"></a>
|
||||
## 三元條件運算(Ternary Conditional Operator)
|
||||
|
||||
三元條件運算的特殊在於它是有三個操作數的運算符,它的原型是 `問題 ? 答案1 : 答案2`。它簡潔地表達根據`問題`成立與否作出二選一的操作。如果`問題`成立,返回`答案1`的結果; 如果不成立,返回`答案2`的結果。
|
||||
|
||||
使用三元條件運算簡化了以下代碼:
|
||||
|
||||
```swift
|
||||
if question: {
|
||||
answer1
|
||||
} else {
|
||||
answer2
|
||||
}
|
||||
```
|
||||
|
||||
這裡有個計算表格行高的例子。如果有表頭,那行高應比內容高度要高出50像素; 如果沒有表頭,只需高出20像素。
|
||||
|
||||
```swift
|
||||
let contentHeight = 40
|
||||
let hasHeader = true
|
||||
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
|
||||
// rowHeight 現在是 90
|
||||
```
|
||||
|
||||
這樣寫會比下面的代碼簡潔:
|
||||
|
||||
```swift
|
||||
let contentHeight = 40
|
||||
let hasHeader = true
|
||||
var rowHeight = contentHeight
|
||||
if hasHeader {
|
||||
rowHeight = rowHeight + 50
|
||||
} else {
|
||||
rowHeight = rowHeight + 20
|
||||
}
|
||||
// rowHeight 現在是 90
|
||||
```
|
||||
|
||||
第一段代碼例子使用了三元條件運算,所以一行代碼就能讓我們得到正確答案。這比第二段代碼簡潔得多,無需將`rowHeight`定義成變量,因為它的值無需在`if`語句中改變。
|
||||
|
||||
三元條件運算提供有效率且便捷的方式來表達二選一的選擇。需要注意的事,過度使用三元條件運算就會由簡潔的代碼變成難懂的代碼。我們應避免在一個組合語句使用多個三元條件運算符。
|
||||
|
||||
<a name="range_operators"></a>
|
||||
## 區間運算符
|
||||
|
||||
Swift 提供了兩個方便表達一個區間的值的運算符。
|
||||
|
||||
### 閉區間運算符
|
||||
閉區間運算符(`a...b`)定義一個包含從`a`到`b`(包括`a`和`b`)的所有值的區間。
|
||||
□
|
||||
閉區間運算符在迭代一個區間的所有值時是非常有用的,如在`for-in`循環中:
|
||||
|
||||
```swift
|
||||
for index in 1...5 {
|
||||
println("\(index) * 5 = \(index * 5)")
|
||||
}
|
||||
// 1 * 5 = 5
|
||||
// 2 * 5 = 10
|
||||
// 3 * 5 = 15
|
||||
// 4 * 5 = 20
|
||||
// 5 * 5 = 25
|
||||
```
|
||||
|
||||
關於`for-in`,請看[控制流](05_Control_Flow.html)。
|
||||
|
||||
### 半閉區間
|
||||
|
||||
半閉區間(`a..b`)定義一個從`a`到`b`但不包括`b`的區間。
|
||||
之所以稱為半閉區間,是因為該區間包含第一個值而不包括最後的值。
|
||||
|
||||
半閉區間的實用性在於當你使用一個0始的列表(如數組)時,非常方便地從0數到列表的長度。
|
||||
|
||||
```swift
|
||||
let names = ["Anna", "Alex", "Brian", "Jack"]
|
||||
let count = names.count
|
||||
for i in 0..count {
|
||||
println("第 \(i + 1) 個人叫 \(names[i])")
|
||||
}
|
||||
// 第 1 個人叫 Anna
|
||||
// 第 2 個人叫 Alex
|
||||
// 第 3 個人叫 Brian
|
||||
// 第 4 個人叫 Jack
|
||||
```
|
||||
|
||||
數組有4個元素,但`0..count`只數到3(最後一個元素的下標),因為它是半閉區間。關於數組,請查閱[數組](04_Collection_Types.html#arrays)。
|
||||
|
||||
<a name="logical_operators"></a>
|
||||
## 邏輯運算
|
||||
|
||||
邏輯運算的操作對象是邏輯布爾值。Swift 支持基於 C 語言的三個標準邏輯運算。
|
||||
|
||||
- 邏輯非(`!a`)
|
||||
- 邏輯與(`a && b`)
|
||||
- 邏輯或(`a || b`)
|
||||
|
||||
### 邏輯非
|
||||
|
||||
邏輯非運算(`!a`)對一個布爾值取反,使得`true`變`false`,`false`變`true`。
|
||||
|
||||
它是一個前置運算符,需出現在操作數之前,且不加空格。讀作`非 a`,然後我們看以下例子:
|
||||
|
||||
```swift
|
||||
let allowedEntry = false
|
||||
if !allowedEntry {
|
||||
println("ACCESS DENIED")
|
||||
}
|
||||
// 輸出 "ACCESS DENIED"
|
||||
```
|
||||
|
||||
`if !allowedEntry`語句可以讀作 "如果 非 alowed entry。",接下一行代碼只有在如果 "非 allow entry" 為`true`,即`allowEntry`為`false`時被執行。
|
||||
|
||||
在示例代碼中,小心地選擇布爾常量或變量有助於代碼的可讀性,並且避免使用雙重邏輯非運算,或混亂的邏輯語句。
|
||||
|
||||
### 邏輯與
|
||||
邏輯與(`a && b`)表達了只有`a`和`b`的值都為`true`時,整個表達式的值才會是`true`。
|
||||
|
||||
只要任意一個值為`false`,整個表達式的值就為`false`。事實上,如果第一個值為`false`,那麼是不去計算第二個值的,因為它已經不可能影響整個表達式的結果了。這被稱做 "短路計算(short-circuit evaluation)"。
|
||||
|
||||
以下例子,只有兩個`Bool`值都為`true`值的時候才允許進入:
|
||||
|
||||
```swift
|
||||
let enteredDoorCode = true
|
||||
let passedRetinaScan = false
|
||||
if enteredDoorCode && passedRetinaScan {
|
||||
println("Welcome!")
|
||||
} else {
|
||||
println("ACCESS DENIED")
|
||||
}
|
||||
// 輸出 "ACCESS DENIED"
|
||||
```
|
||||
|
||||
### 邏輯或
|
||||
邏輯或(`a || b`)是一個由兩個連續的`|`組成的中置運算符。它表示了兩個邏輯表達式的其中一個為`true`,整個表達式就為`true`。
|
||||
|
||||
同邏輯與運算類似,邏輯或也是"短路計算"的,當左端的表達式為`true`時,將不計算右邊的表達式了,因為它不可能改變整個表達式的值了。
|
||||
|
||||
以下示例代碼中,第一個布爾值(`hasDoorKey`)為`false`,但第二個值(`knowsOverridePassword`)為`true`,所以整個表達是`true`,於是允許進入:
|
||||
|
||||
```swift
|
||||
let hasDoorKey = false
|
||||
let knowsOverridePassword = true
|
||||
if hasDoorKey || knowsOverridePassword {
|
||||
println("Welcome!")
|
||||
} else {
|
||||
println("ACCESS DENIED")
|
||||
}
|
||||
// 輸出 "Welcome!"
|
||||
```
|
||||
|
||||
### 組合邏輯
|
||||
|
||||
我們可以組合多個邏輯運算來表達一個復合邏輯:
|
||||
|
||||
```swift
|
||||
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
|
||||
println("Welcome!")
|
||||
} else {
|
||||
println("ACCESS DENIED")
|
||||
}
|
||||
// 輸出 "Welcome!"
|
||||
```
|
||||
|
||||
這個例子使用了含多個`&&`和`||`的復合邏輯。但無論怎樣,`&&`和`||`始終只能操作兩個值。所以這實際是三個簡單邏輯連續操作的結果。我們來解讀一下:
|
||||
|
||||
如果我們輸入了正確的密碼並通過了視網膜掃瞄; 或者我們有一把有效的鑰匙; 又或者我們知道緊急情況下重置的密碼,我們就能把門打開進入。
|
||||
|
||||
前兩種情況,我們都不滿足,所以前兩個簡單邏輯的結果是`false`,但是我們是知道緊急情況下重置的密碼的,所以整個複雜表達式的值還是`true`。
|
||||
|
||||
### 使用括號來明確優先級
|
||||
|
||||
為了一個複雜表達式更容易讀懂,在合適的地方使用括號來明確優先級是很有效的,雖然它並非必要的。在上個關於門的權限的例子中,我們給第一個部分加個括號,使用它看起來邏輯更明確:
|
||||
|
||||
```swift
|
||||
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
|
||||
println("Welcome!")
|
||||
} else {
|
||||
println("ACCESS DENIED")
|
||||
}
|
||||
// 輸出 "Welcome!"
|
||||
```
|
||||
|
||||
這括號使得前兩個值被看成整個邏輯表達中獨立的一個部分。雖然有括號和沒括號的輸出結果是一樣的,但對於讀代碼的人來說有括號的代碼更清晰。可讀性比簡潔性更重要,請在可以讓你代碼變清晰地地方加個括號吧!
|
409
source-tw/chapter2/03_Strings_and_Characters.md
Normal file
409
source-tw/chapter2/03_Strings_and_Characters.md
Normal file
@ -0,0 +1,409 @@
|
||||
> 翻譯:[wh1100717](https://github.com/wh1100717)
|
||||
> 校對:[Hawstein](https://github.com/Hawstein)
|
||||
|
||||
# 字符串和字符(Strings and Characters)
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [字符串字面量](#string_literals)
|
||||
- [初始化空字符串](#initializing_an_empty_string)
|
||||
- [字符串可變性](#string_mutability)
|
||||
- [字符串是值類型](#strings_are_value_types)
|
||||
- [使用字符](#working_with_characters)
|
||||
- [計算字符數量](#counting_characters)
|
||||
- [連接字符串和字符](#concatenating_strings_and_characters)
|
||||
- [字符串插值](#string_interpolation)
|
||||
- [比較字符串](#comparing_strings)
|
||||
- [字符串大小寫](#uppercase_and_lowercase_strings)
|
||||
- [Unicode](#unicode)
|
||||
|
||||
`String`是例如「hello, world」,「海賊王」 這樣的有序的`Character`(字符)類型的值的集合,通過`String`類型來表示。
|
||||
|
||||
Swift 的`String`和`Character`類型提供了一個快速的,兼容 Unicode 的方式來處理代碼中的文本信息。
|
||||
創建和操作字符串的語法與 C 語言中字符串操作相似,輕量並且易讀。
|
||||
字符串連接操作只需要簡單地通過`+`號將兩個字符串相連即可。
|
||||
與 Swift 中其他值一樣,能否更改字符串的值,取決於其被定義為常量還是變量。
|
||||
|
||||
儘管語法簡易,但`String`類型是一種快速、現代化的字符串實現。
|
||||
每一個字符串都是由獨立編碼的 Unicode 字符組成,並提供了以不同 Unicode 表示(representations)來訪問這些字符的支持。
|
||||
|
||||
Swift 可以在常量、變量、字面量和表達式中進行字符串插值操作,可以輕鬆創建用於展示、存儲和打印的自定義字符串。
|
||||
|
||||
> 注意:
|
||||
Swift 的`String`類型與 Foundation `NSString`類進行了無縫橋接。如果您利用 Cocoa 或 Cocoa Touch 中的 Foundation 框架進行工作。所有`NSString` API 都可以調用您創建的任意`String`類型的值。除此之外,還可以使用本章介紹的`String`特性。您也可以在任意要求傳入`NSString`實例作為參數的 API 中使用`String`類型的值作為替代。
|
||||
>更多關於在 Foundation 和 Cocoa 中使用`String`的信息請查看 [Using Swift with Cocoa and Objective-C](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。
|
||||
|
||||
<a name="string_literals"></a>
|
||||
## 字符串字面量(String Literals)
|
||||
|
||||
您可以在您的代碼中包含一段預定義的字符串值作為字符串字面量。
|
||||
字符串字面量是由雙引號 ("") 包裹著的具有固定順序的文本字符集。
|
||||
|
||||
字符串字面量可以用於為常量和變量提供初始值。
|
||||
|
||||
```swift
|
||||
let someString = "Some string literal value"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
`someString`變量通過字符串字面量進行初始化,Swift 因此推斷該變量為`String`類型。
|
||||
|
||||
字符串字面量可以包含以下特殊字符:
|
||||
|
||||
* 轉義字符`\0`(空字符)、`\\`(反斜線)、`\t`(水平製表符)、`\n`(換行符)、`\r`(回車符)、`\"`(雙引號)、`\'`(單引號)。
|
||||
* 單字節 Unicode 標量,寫成`\xnn`,其中`nn`為兩位十六進制數。
|
||||
* 雙字節 Unicode 標量,寫成`\unnnn`,其中`nnnn`為四位十六進制數。
|
||||
* 四字節 Unicode 標量,寫成`\Unnnnnnnn`,其中`nnnnnnnn`為八位十六進制數。
|
||||
|
||||
下面的代碼為各種特殊字符的使用示例。
|
||||
`wiseWords`常量包含了兩個轉移特殊字符 (雙括號);
|
||||
`dollarSign`、`blackHeart`和`sparklingHeart`常量演示了三種不同格式的 Unicode 標量:
|
||||
|
||||
```swift
|
||||
let wiseWords = "\"我是要成為海賊王的男人\" - 路飛"
|
||||
// "我是要成為海賊王的男人" - 路飛
|
||||
let dollarSign = "\x24" // $, Unicode 標量 U+0024
|
||||
let blackHeart = "\u2665" // □, Unicode 標量 U+2665
|
||||
let sparklingHeart = "\U0001F496" // □欠Unicode 標量 U+1F496
|
||||
```
|
||||
|
||||
<a name="initializing_an_empty_string"></a>
|
||||
## 初始化空字符串 (Initializing an Empty String)
|
||||
|
||||
為了構造一個很長的字符串,可以創建一個空字符串作為初始值。
|
||||
可以將空的字符串字面量賦值給變量,也可以初始化一個新的`String`實例:
|
||||
|
||||
```swift
|
||||
var emptyString = "" // 空字符串字面量
|
||||
var anotherEmptyString = String() // 初始化 String 實例
|
||||
// 兩個字符串均為空並等價。
|
||||
```
|
||||
|
||||
您可以通過檢查其`Boolean`類型的`isEmpty`屬性來判斷該字符串是否為空:
|
||||
|
||||
```swift
|
||||
if emptyString.isEmpty {
|
||||
println("什麼都沒有")
|
||||
}
|
||||
// 打印輸出:"什麼都沒有"
|
||||
```
|
||||
|
||||
<a name="string_mutability"></a>
|
||||
## 字符串可變性 (String Mutability)
|
||||
|
||||
您可以通過將一個特定字符串分配給一個變量來對其進行修改,或者分配給一個常量來保證其不會被修改:
|
||||
|
||||
```swift
|
||||
var variableString = "Horse"
|
||||
variableString += " and carriage"
|
||||
// variableString 現在為 "Horse and carriage"
|
||||
let constantString = "Highlander"
|
||||
constantString += " and another Highlander"
|
||||
// 這會報告一個編譯錯誤 (compile-time error) - 常量不可以被修改。
|
||||
```
|
||||
|
||||
> 注意:
|
||||
在 Objective-C 和 Cocoa 中,您通過選擇兩個不同的類(`NSString`和`NSMutableString`)來指定該字符串是否可以被修改,Swift 中的字符串是否可以修改僅通過定義的是變量還是常量來決定,實現了多種類型可變性操作的統一。
|
||||
|
||||
<a name="strings_are_value_types"></a>
|
||||
## 字符串是值類型(Strings Are Value Types)
|
||||
|
||||
Swift 的`String`類型是值類型。
|
||||
如果您創建了一個新的字符串,那麼當其進行常量、變量賦值操作或在函數/方法中傳遞時,會進行值拷貝。
|
||||
任何情況下,都會對已有字符串值創建新副本,並對該新副本進行傳遞或賦值操作。
|
||||
值類型在 [結構體和枚舉是值類型](09_Classes_and_Structures.html#structures_and_enumerations_are_value_types) 中進行了說明。
|
||||
|
||||
> 注意:
|
||||
與 Cocoa 中的`NSString`不同,當您在 Cocoa 中創建了一個`NSString`實例,並將其傳遞給一個函數/方法,或者賦值給一個變量,您傳遞或賦值的是該`NSString`實例的一個引用,除非您特別要求進行值拷貝,否則字符串不會生成新的副本來進行賦值操作。
|
||||
|
||||
Swift 默認字符串拷貝的方式保證了在函數/方法中傳遞的是字符串的值。
|
||||
很明顯無論該值來自於哪裡,都是您獨自擁有的。
|
||||
您可以放心您傳遞的字符串本身不會被更改。
|
||||
|
||||
在實際編譯時,Swift 編譯器會優化字符串的使用,使實際的複製只發生在絕對必要的情況下,這意味著您將字符串作為值類型的同時可以獲得極高的性能。
|
||||
|
||||
<a name="working_with_characters"></a>
|
||||
## 使用字符(Working with Characters)
|
||||
|
||||
Swift 的`String`類型表示特定序列的`Character`(字符) 類型值的集合。
|
||||
每一個字符值代表一個 Unicode 字符。
|
||||
您可利用`for-in`循環來遍歷字符串中的每一個字符:
|
||||
|
||||
```swift
|
||||
for character in "Dog!□梠{
|
||||
println(character)
|
||||
}
|
||||
// D
|
||||
// o
|
||||
// g
|
||||
// !
|
||||
// □捊```
|
||||
|
||||
for-in 循環在 [For Loops](05_Control_Flow.html#for_loops) 中進行了詳細描述。
|
||||
|
||||
另外,通過標明一個`Character`類型註解並通過字符字面量進行賦值,可以建立一個獨立的字符常量或變量:
|
||||
|
||||
```swift
|
||||
let yenSign: Character = "¥"
|
||||
```
|
||||
|
||||
<a name="counting_characters"></a>
|
||||
## 計算字符數量 (Counting Characters)
|
||||
|
||||
通過調用全局`countElements`函數,並將字符串作為參數進行傳遞,可以獲取該字符串的字符數量。
|
||||
|
||||
```swift
|
||||
let unusualMenagerie = "Koala □謠Snail □□Penguin □笠Dromedary □□
|
||||
println("unusualMenagerie has \(countElements(unusualMenagerie)) characters")
|
||||
// 打印輸出:"unusualMenagerie has 40 characters"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
不同的 Unicode 字符以及相同 Unicode 字符的不同表示方式可能需要不同數量的內存空間來存儲。所以 Swift 中的字符在一個字符串中並不一定佔用相同的內存空間。因此字符串的長度不得不通過迭代字符串中每一個字符的長度來進行計算。如果您正在處理一個長字符串,需要注意`countElements`函數必須遍歷字符串中的字符以精準計算字符串的長度。
|
||||
> 另外需要注意的是通過`countElements`返回的字符數量並不總是與包含相同字符的`NSString`的`length`屬性相同。`NSString`的`length`屬性是基於利用 UTF-16 表示的十六位代碼單元數字,而不是基於 Unicode 字符。為了解決這個問題,`NSString`的`length`屬性在被 Swift 的`String`訪問時會成為`utf16count`。
|
||||
|
||||
<a name="concatenating_strings_and_characters"></a>
|
||||
## 連接字符串和字符 (Concatenating Strings and Characters)
|
||||
|
||||
字符串和字符的值可以通過加法運算符(`+`)相加在一起並創建一個新的字符串值:
|
||||
|
||||
```swift
|
||||
let string1 = "hello"
|
||||
let string2 = " there"
|
||||
let character1: Character = "!"
|
||||
let character2: Character = "?"
|
||||
|
||||
let stringPlusCharacter = string1 + character1 // 等於 "hello!"
|
||||
let stringPlusString = string1 + string2 // 等於 "hello there"
|
||||
let characterPlusString = character1 + string1 // 等於 "!hello"
|
||||
let characterPlusCharacter = character1 + character2 // 等於 "!?"
|
||||
```
|
||||
|
||||
您也可以通過加法賦值運算符 (`+=`) 將一個字符串或者字符添加到一個已經存在字符串變量上:
|
||||
|
||||
```swift
|
||||
var instruction = "look over"
|
||||
instruction += string2
|
||||
// instruction 現在等於 "look over there"
|
||||
|
||||
var welcome = "good morning"
|
||||
welcome += character1
|
||||
// welcome 現在等於 "good morning!"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
您不能將一個字符串或者字符添加到一個已經存在的字符變量上,因為字符變量只能包含一個字符。
|
||||
|
||||
<a name="string_interpolation"></a>
|
||||
## 字符串插值 (String Interpolation)
|
||||
|
||||
字符串插值是一種構建新字符串的方式,可以在其中包含常量、變量、字面量和表達式。
|
||||
您插入的字符串字面量的每一項都被包裹在以反斜線為前綴的圓括號中:
|
||||
|
||||
```swift
|
||||
let multiplier = 3
|
||||
let message = "\(multiplier) 乘以 2.5 是 \(Double(multiplier) * 2.5)"
|
||||
// message 是 "3 乘以 2.5 是 7.5"
|
||||
```
|
||||
|
||||
在上面的例子中,`multiplier`作為`\(multiplier)`被插入到一個字符串字面量中。
|
||||
當創建字符串執行插值計算時此佔位符會被替換為`multiplier`實際的值。
|
||||
|
||||
`multiplier`的值也作為字符串中後面表達式的一部分。
|
||||
該表達式計算`Double(multiplier) * 2.5`的值並將結果 (7.5) 插入到字符串中。
|
||||
在這個例子中,表達式寫為`\(Double(multiplier) * 2.5)`並包含在字符串字面量中。
|
||||
|
||||
> 注意:
|
||||
插值字符串中寫在括號中的表達式不能包含非轉義雙引號 (`"`) 和反斜槓 (`\`),並且不能包含回車或換行符。
|
||||
|
||||
<a name="comparing_strings"></a>
|
||||
## 比較字符串 (Comparing Strings)
|
||||
|
||||
Swift 提供了三種方式來比較字符串的值:字符串相等、前綴相等和後綴相等。
|
||||
|
||||
<a name="string_equality"></a>
|
||||
### 字符串相等 (String Equality)
|
||||
|
||||
如果兩個字符串以同一順序包含完全相同的字符,則認為兩者字符串相等:
|
||||
|
||||
```swift
|
||||
let quotation = "我們是一樣一樣滴."
|
||||
let sameQuotation = "我們是一樣一樣滴."
|
||||
if quotation == sameQuotation {
|
||||
println("這兩個字符串被認為是相同的")
|
||||
}
|
||||
// 打印輸出:"這兩個字符串被認為是相同的"
|
||||
```
|
||||
|
||||
<a name="prefix_and_suffix_equality"></a>
|
||||
### 前綴/後綴相等 (Prefix and Suffix Equality)
|
||||
|
||||
通過調用字符串的`hasPrefix`/`hasSuffix`方法來檢查字符串是否擁有特定前綴/後綴。
|
||||
兩個方法均需要以字符串作為參數傳入並傳出`Boolean`值。
|
||||
兩個方法均執行基本字符串和前綴/後綴字符串之間逐個字符的比較操作。
|
||||
|
||||
下面的例子以一個字符串數組表示莎士比亞話劇《羅密歐與朱麗葉》中前兩場的場景位置:
|
||||
|
||||
```swift
|
||||
let romeoAndJuliet = [
|
||||
"Act 1 Scene 1: Verona, A public place",
|
||||
"Act 1 Scene 2: Capulet's mansion",
|
||||
"Act 1 Scene 3: A room in Capulet's mansion",
|
||||
"Act 1 Scene 4: A street outside Capulet's mansion",
|
||||
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
|
||||
"Act 2 Scene 1: Outside Capulet's mansion",
|
||||
"Act 2 Scene 2: Capulet's orchard",
|
||||
"Act 2 Scene 3: Outside Friar Lawrence's cell",
|
||||
"Act 2 Scene 4: A street in Verona",
|
||||
"Act 2 Scene 5: Capulet's mansion",
|
||||
"Act 2 Scene 6: Friar Lawrence's cell"
|
||||
]
|
||||
```
|
||||
|
||||
您可以利用`hasPrefix`方法來計算話劇中第一幕的場景數:
|
||||
|
||||
```swift
|
||||
var act1SceneCount = 0
|
||||
for scene in romeoAndJuliet {
|
||||
if scene.hasPrefix("Act 1 ") {
|
||||
++act1SceneCount
|
||||
}
|
||||
}
|
||||
println("There are \(act1SceneCount) scenes in Act 1")
|
||||
// 打印輸出:"There are 5 scenes in Act 1"
|
||||
```
|
||||
|
||||
相似地,您可以用`hasSuffix`方法來計算發生在不同地方的場景數:
|
||||
|
||||
```swift
|
||||
var mansionCount = 0
|
||||
var cellCount = 0
|
||||
for scene in romeoAndJuliet {
|
||||
if scene.hasSuffix("Capulet's mansion") {
|
||||
++mansionCount
|
||||
} else if scene.hasSuffix("Friar Lawrence's cell") {
|
||||
++cellCount
|
||||
}
|
||||
}
|
||||
println("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
|
||||
// 打印輸出:"6 mansion scenes; 2 cell scenes」
|
||||
```
|
||||
|
||||
<a name="uppercase_and_lowercase_strings"></a>
|
||||
### 大寫和小寫字符串(Uppercase and Lowercase Strings)
|
||||
|
||||
您可以通過字符串的`uppercaseString`和`lowercaseString`屬性來訪問大寫/小寫版本的字符串。
|
||||
|
||||
```swift
|
||||
let normal = "Could you help me, please?"
|
||||
let shouty = normal.uppercaseString
|
||||
// shouty 值為 "COULD YOU HELP ME, PLEASE?"
|
||||
let whispered = normal.lowercaseString
|
||||
// whispered 值為 "could you help me, please?"
|
||||
```
|
||||
|
||||
<a name="unicode"></a>
|
||||
## Unicode
|
||||
|
||||
Unicode 是一個國際標準,用於文本的編碼和表示。
|
||||
它使您可以用標準格式表示來自任意語言幾乎所有的字符,並能夠對文本文件或網頁這樣的外部資源中的字符進行讀寫操作。
|
||||
|
||||
Swift 的字符串和字符類型是完全兼容 Unicode 標準的,它支持如下所述的一系列不同的 Unicode 編碼。
|
||||
|
||||
<a name="unicode_terminology"></a>
|
||||
### Unicode 術語(Unicode Terminology)
|
||||
|
||||
Unicode 中每一個字符都可以被解釋為一個或多個 unicode 標量。
|
||||
字符的 unicode 標量是一個唯一的21位數字(和名稱),例如`U+0061`表示小寫的拉丁字母A ("a"),`U+1F425`表示小雞表情 ("□墩
|
||||
|
||||
當 Unicode 字符串被寫進文本文件或其他存儲結構當中,這些 unicode 標量將會按照 Unicode 定義的集中格式之一進行編碼。其包括`UTF-8`(以8位代碼單元進行編碼) 和`UTF-16`(以16位代碼單元進行編碼)。
|
||||
|
||||
<a name="unicode_representations_of_strings"></a>
|
||||
### 字符串的 Unicode 表示(Unicode Representations of Strings)
|
||||
|
||||
Swift 提供了幾種不同的方式來訪問字符串的 Unicode 表示。
|
||||
|
||||
您可以利用`for-in`來對字符串進行遍歷,從而以 Unicode 字符的方式訪問每一個字符值。
|
||||
該過程在 [使用字符](#working_with_characters) 中進行了描述。
|
||||
|
||||
另外,能夠以其他三種 Unicode 兼容的方式訪問字符串的值:
|
||||
|
||||
* UTF-8 代碼單元集合 (利用字符串的`utf8`屬性進行訪問)
|
||||
* UTF-16 代碼單元集合 (利用字符串的`utf16`屬性進行訪問)
|
||||
* 21位的 Unicode 標量值集合 (利用字符串的`unicodeScalars`屬性進行訪問)
|
||||
|
||||
下面由`D``o``g``!`和`□栨`DOG FACE`,Unicode 標量為`U+1F436`)組成的字符串中的每一個字符代表著一種不同的表示:
|
||||
|
||||
```swift
|
||||
let dogString = "Dog!□皂
|
||||
```
|
||||
|
||||
<a name="UTF-8"></a>
|
||||
### UTF-8
|
||||
|
||||
您可以通過遍歷字符串的`utf8`屬性來訪問它的`UTF-8`表示。
|
||||
其為`UTF8View`類型的屬性,`UTF8View`是無符號8位 (`UInt8`) 值的集合,每一個`UInt8`值都是一個字符的 UTF-8 表示:
|
||||
|
||||
```swift
|
||||
for codeUnit in dogString.utf8 {
|
||||
print("\(codeUnit) ")
|
||||
}
|
||||
print("\n")
|
||||
// 68 111 103 33 240 159 144 182
|
||||
```
|
||||
|
||||
上面的例子中,前四個10進制代碼單元值 (68, 111, 103, 33) 代表了字符`D` `o` `g`和`!`,它們的 UTF-8 表示與 ASCII 表示相同。
|
||||
後四個代碼單元值 (240, 159, 144, 182) 是`DOG FACE`的4字節 UTF-8 表示。
|
||||
|
||||
<a name="UTF-16"></a>
|
||||
### UTF-16
|
||||
|
||||
您可以通過遍歷字符串的`utf16`屬性來訪問它的`UTF-16`表示。
|
||||
其為`UTF16View`類型的屬性,`UTF16View`是無符號16位 (`UInt16`) 值的集合,每一個`UInt16`都是一個字符的 UTF-16 表示:
|
||||
|
||||
```swift
|
||||
for codeUnit in dogString.utf16 {
|
||||
print("\(codeUnit) ")
|
||||
}
|
||||
print("\n")
|
||||
// 68 111 103 33 55357 56374
|
||||
```
|
||||
|
||||
同樣,前四個代碼單元值 (68, 111, 103, 33) 代表了字符`D` `o` `g`和`!`,它們的 UTF-16 代碼單元和 UTF-8 完全相同。
|
||||
|
||||
第五和第六個代碼單元值 (55357 和 56374) 是`DOG FACE`字符的UTF-16 表示。
|
||||
第一個值為`U+D83D`(十進制值為 55357),第二個值為`U+DC36`(十進制值為 56374)。
|
||||
|
||||
<a name="unicode_scalars"></a>
|
||||
### Unicode 標量 (Unicode Scalars)
|
||||
|
||||
您可以通過遍歷字符串的`unicodeScalars`屬性來訪問它的 Unicode 標量表示。
|
||||
其為`UnicodeScalarView`類型的屬性, `UnicodeScalarView`是`UnicodeScalar`的集合。
|
||||
`UnicodeScalar`是21位的 Unicode 代碼點。
|
||||
|
||||
每一個`UnicodeScalar`擁有一個值屬性,可以返回對應的21位數值,用`UInt32`來表示。
|
||||
|
||||
```swift
|
||||
for scalar in dogString.unicodeScalars {
|
||||
print("\(scalar.value) ")
|
||||
}
|
||||
print("\n")
|
||||
// 68 111 103 33 128054
|
||||
```
|
||||
|
||||
同樣,前四個代碼單元值 (68, 111, 103, 33) 代表了字符`D` `o` `g`和`!`。
|
||||
第五位數值,128054,是一個十六進制1F436的十進製表示。
|
||||
其等同於`DOG FACE`的Unicode 標量 U+1F436。
|
||||
|
||||
作為查詢字符值屬性的一種替代方法,每個`UnicodeScalar`值也可以用來構建一個新的字符串值,比如在字符串插值中使用:
|
||||
|
||||
```swift
|
||||
for scalar in dogString.unicodeScalars {
|
||||
println("\(scalar) ")
|
||||
}
|
||||
// D
|
||||
// o
|
||||
// g
|
||||
// !
|
||||
// □捊```
|
428
source-tw/chapter2/04_Collection_Types.md
Normal file
428
source-tw/chapter2/04_Collection_Types.md
Normal file
@ -0,0 +1,428 @@
|
||||
> 翻譯:[zqp](https://github.com/zqp)
|
||||
> 校對:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
# 集合類型 (Collection Types)
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [數組(Arrays)](#arrays)
|
||||
- [字典(Dictionaries)](#dictionaries)
|
||||
- [集合的可變性(Mutability of Collections)](#mutability_of_collections)
|
||||
|
||||
Swift 語言提供經典的數組和字典兩種集合類型來存儲集合數據。數組用來按順序存儲相同類型的數據。字典雖然無序存儲相同類型數據值但是需要由獨有的標識符引用和尋址(就是鍵值對)。
|
||||
|
||||
Swift 語言裡的數組和字典中存儲的數據值類型必須明確。 這意味著我們不能把不正確的數據類型插入其中。 同時這也說明我們完全可以對獲取出的值類型非常自信。 Swift 對顯式類型集合的使用確保了我們的代碼對工作所需要的類型非常清楚,也讓我們在開發中可以早早地找到任何的類型不匹配錯誤。
|
||||
|
||||
> 注意:
|
||||
Swift 的數組結構在被聲明成常量和變量或者被傳入函數與方法中時會相對於其他類型展現出不同的特性。 獲取更多信息請參見[集合的可變性](#mutability_of_collections)與[集合在賦值和複製中的行為](09_Classes_and_Structures.html#assignment_and_copy_behavior_for_collection_types)章節。
|
||||
|
||||
<a name="arrays"></a>
|
||||
## 數組
|
||||
|
||||
數組使用有序列表存儲同一類型的多個值。相同的值可以多次出現在一個數組的不同位置中。
|
||||
|
||||
Swift 數組特定於它所存儲元素的類型。這與 Objective-C 的 NSArray 和 NSMutableArray 不同,這兩個類可以存儲任意類型的對象,並且不提供所返回對象的任何特別信息。在 Swift 中,數據值在被存儲進入某個數組之前類型必須明確,方法是通過顯式的類型標注或類型推斷,而且不是必須是`class`類型。例如: 如果我們創建了一個`Int`值類型的數組,我們不能往其中插入任何不是`Int`類型的數據。 Swift 中的數組是類型安全的,並且它們中包含的類型必須明確。
|
||||
|
||||
<a name="array_type_shorthand_syntax"></a>
|
||||
### 數組的簡單語法
|
||||
|
||||
寫 Swift 數組應該遵循像`Array<SomeType>`這樣的形式,其中`SomeType`是這個數組中唯一允許存在的數據類型。 我們也可以使用像`SomeType[]`這樣的簡單語法。 儘管兩種形式在功能上是一樣的,但是推薦較短的那種,而且在本文中都會使用這種形式來使用數組。
|
||||
|
||||
<a name="array_literals"></a>
|
||||
### 數組構造語句
|
||||
|
||||
我們可以使用字面量來進行數組構造,這是一種用一個或者多個數值構造數組的簡單方法。字面量是一系列由逗號分割並由方括號包含的數值。
|
||||
`[value 1, value 2, value 3]`。
|
||||
|
||||
下面這個例子創建了一個叫做`shoppingList`並且存儲字符串的數組:
|
||||
|
||||
```swift
|
||||
var shoppingList: String[] = ["Eggs", "Milk"]
|
||||
// shoppingList 已經被構造並且擁有兩個初始項。
|
||||
```
|
||||
|
||||
`shoppingList`變量被聲明為「字符串值類型的數組「,記作`String[]`。 因為這個數組被規定只有`String`一種數據結構,所以只有`String`類型可以在其中被存取。 在這裡,`shoppinglist`數組由兩個`String`值(`"Eggs"` 和`"Milk"`)構造,並且由字面量定義。
|
||||
|
||||
> 注意:
|
||||
> `Shoppinglist`數組被聲明為變量(`var`關鍵字創建)而不是常量(`let`創建)是因為以後可能會有更多的數據項被插入其中。
|
||||
|
||||
在這個例子中,字面量僅僅包含兩個`String`值。匹配了該數組的變量聲明(只能包含`String`的數組),所以這個字面量的分配過程就是允許用兩個初始項來構造`shoppinglist`。
|
||||
|
||||
由於 Swift 的類型推斷機制,當我們用字面量構造只擁有相同類型值數組的時候,我們不必把數組的類型定義清楚。 `shoppinglist`的構造也可以這樣寫:
|
||||
|
||||
```swift
|
||||
var shoppingList = ["Eggs", "Milk"]
|
||||
```
|
||||
|
||||
因為所有字面量中的值都是相同的類型,Swift 可以推斷出`String[]`是`shoppinglist`中變量的正確類型。
|
||||
|
||||
<a name="accessing_and_modifying_an_array"></a>
|
||||
### 訪問和修改數組
|
||||
|
||||
我們可以通過數組的方法和屬性來訪問和修改數組,或者下標語法。
|
||||
還可以使用數組的只讀屬性`count`來獲取數組中的數據項數量。
|
||||
|
||||
```swift
|
||||
println("The shopping list contains \(shoppingList.count) items.")
|
||||
// 輸出"The shopping list contains 2 items."(這個數組有2個項)
|
||||
```
|
||||
|
||||
使用布爾項`isEmpty`來作為檢查`count`屬性的值是否為 0 的捷徑。
|
||||
|
||||
```swift
|
||||
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`方法在數組後面添加新的數據項:
|
||||
|
||||
```swift
|
||||
shoppingList.append("Flour")
|
||||
// shoppingList 現在有3個數據項,有人在攤煎餅
|
||||
```
|
||||
|
||||
除此之外,使用加法賦值運算符(`+=`)也可以直接在數組後面添加數據項:
|
||||
|
||||
```swift
|
||||
shoppingList += "Baking Powder"
|
||||
// shoppingList 現在有四項了
|
||||
```
|
||||
|
||||
我們也可以使用加法賦值運算符(`+=`)直接添加擁有相同類型數據的數組。
|
||||
|
||||
```swift
|
||||
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
|
||||
// shoppingList 現在有7項了
|
||||
```
|
||||
|
||||
可以直接使用下標語法來獲取數組中的數據項,把我們需要的數據項的索引值放在直接放在數組名稱的方括號中:
|
||||
|
||||
```swift
|
||||
var firstItem = shoppingList[0]
|
||||
// 第一項是 "Eggs"
|
||||
```
|
||||
|
||||
注意第一項在數組中的索引值是`0`而不是`1`。 Swift 中的數組索引總是從零開始。
|
||||
|
||||
我們也可以用下標來改變某個已有索引值對應的數據值:
|
||||
|
||||
```swift
|
||||
shoppingList[0] = "Six eggs"
|
||||
// 其中的第一項現在是 "Six eggs" 而不是 "Eggs"
|
||||
```
|
||||
|
||||
還可以利用下標來一次改變一系列數據值,即使新數據和原有數據的數量是不一樣的。下面的例子把`"Chocolate Spread"`,`"Cheese"`,和`"Butter"`替換為`"Bananas"`和 `"Apples"`:
|
||||
|
||||
```swift
|
||||
shoppingList[4...6] = ["Bananas", "Apples"]
|
||||
// shoppingList 現在有六項
|
||||
```
|
||||
|
||||
> 注意:
|
||||
>我們不能使用下標語法在數組尾部添加新項。如果我們試著用這種方法對索引越界的數據進行檢索或者設置新值的操作,我們會引發一個運行期錯誤。我們可以使用索引值和數組的`count`屬性進行比較來在使用某個索引之前先檢驗是否有效。除了當`count`等於 0 時(說明這是個空數組),最大索引值一直是`count - 1`,因為數組都是零起索引。
|
||||
|
||||
調用數組的`insert(atIndex:)`方法來在某個具體索引值之前添加數據項:
|
||||
|
||||
```swift
|
||||
shoppingList.insert("Maple Syrup", atIndex: 0)
|
||||
// shoppingList 現在有7項
|
||||
// "Maple Syrup" 現在是這個列表中的第一項
|
||||
```
|
||||
|
||||
這次`insert`函數調用把值為`"Maple Syrup"`的新數據項插入列表的最開始位置,並且使用`0`作為索引值。
|
||||
|
||||
類似的我們可以使用`removeAtIndex`方法來移除數組中的某一項。這個方法把數組在特定索引值中存儲的數據項移除並且返回這個被移除的數據項(我們不需要的時候就可以無視它):
|
||||
|
||||
```swift
|
||||
let mapleSyrup = shoppingList.removeAtIndex(0)
|
||||
// 索引值為0的數據項被移除
|
||||
// shoppingList 現在只有6項,而且不包括Maple Syrup
|
||||
// mapleSyrup常量的值等於被移除數據項的值 "Maple Syrup"
|
||||
```
|
||||
|
||||
數據項被移除後數組中的空出項會被自動填補,所以現在索引值為`0`的數據項的值再次等於`"Six eggs"`:
|
||||
|
||||
```swift
|
||||
firstItem = shoppingList[0]
|
||||
// firstItem 現在等於 "Six eggs"
|
||||
```
|
||||
|
||||
如果我們只想把數組中的最後一項移除,可以使用`removeLast`方法而不是`removeAtIndex`方法來避免我們需要獲取數組的`count`屬性。就像後者一樣,前者也會返回被移除的數據項:
|
||||
|
||||
```swift
|
||||
let apples = shoppingList.removeLast()
|
||||
// 數組的最後一項被移除了
|
||||
// shoppingList現在只有5項,不包括cheese
|
||||
// apples 常量的值現在等於"Apples" 字符串
|
||||
```
|
||||
|
||||
<a name="iterating_over_an_array"></a>
|
||||
### 數組的遍歷
|
||||
|
||||
我們可以使用`for-in`循環來遍歷所有數組中的數據項:
|
||||
|
||||
```swift
|
||||
for item in shoppingList {
|
||||
println(item)
|
||||
}
|
||||
// Six eggs
|
||||
// Milk
|
||||
// Flour
|
||||
// Baking Powder
|
||||
// Bananas
|
||||
```
|
||||
|
||||
如果我們同時需要每個數據項的值和索引值,可以使用全局`enumerate`函數來進行數組遍歷。`enumerate`返回一個由每一個數據項索引值和數據值組成的元組。我們可以把這個元組分解成臨時常量或者變量來進行遍歷:
|
||||
|
||||
```swift
|
||||
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 循環](05_Control_Flow.html#for_loops)。
|
||||
|
||||
<a name="creating_and_initializing_an_array"></a>
|
||||
### 創建並且構造一個數組
|
||||
|
||||
我們可以使用構造語法來創建一個由特定數據類型構成的空數組:
|
||||
|
||||
```swift
|
||||
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[]`。
|
||||
|
||||
除此之外,如果代碼上下文中提供了類型信息, 例如一個函數參數或者一個已經定義好類型的常量或者變量,我們可以使用空數組語句創建一個空數組,它的寫法很簡單:`[]`(一對空方括號):
|
||||
|
||||
```swift
|
||||
someInts.append(3)
|
||||
// someInts 現在包含一個INT值
|
||||
someInts = []
|
||||
// someInts 現在是空數組,但是仍然是Int[]類型的。
|
||||
```
|
||||
|
||||
Swift 中的`Array`類型還提供一個可以創建特定大小並且所有數據都被默認的構造方法。我們可以把準備加入新數組的數據項數量(`count`)和適當類型的初始值(`repeatedValue`)傳入數組構造函數:
|
||||
|
||||
```swift
|
||||
var threeDoubles = Double[](count: 3, repeatedValue:0.0)
|
||||
// threeDoubles 是一種 Double[]數組, 等於 [0.0, 0.0, 0.0]
|
||||
```
|
||||
|
||||
因為類型推斷的存在,我們使用這種構造方法的時候不需要特別指定數組中存儲的數據類型,因為類型可以從默認值推斷出來:
|
||||
|
||||
```swift
|
||||
var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
|
||||
// anotherThreeDoubles is inferred as Double[], and equals [2.5, 2.5, 2.5]
|
||||
```
|
||||
|
||||
最後,我們可以使用加法操作符(`+`)來組合兩種已存在的相同類型數組。新數組的數據類型會被從兩個數組的數據類型中推斷出來:
|
||||
|
||||
```swift
|
||||
var sixDoubles = threeDoubles + anotherThreeDoubles
|
||||
// sixDoubles 被推斷為 Double[], 等於 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
|
||||
```
|
||||
|
||||
<a name="dictionaries"></a>
|
||||
## 字典
|
||||
|
||||
字典是一種存儲多個相同類型的值的容器。每個值(value)都關聯唯一的鍵(key),鍵作為字典中的這個值數據的標識符。和數組中的數據項不同,字典中的數據項並沒有具體順序。我們在需要通過標識符(鍵)訪問數據的時候使用字典,這種方法很大程度上和我們在現實世界中使用字典查字義的方法一樣。
|
||||
|
||||
Swift 的字典使用時需要具體規定可以存儲鍵和值類型。不同於 Objective-C 的`NSDictionary`和`NSMutableDictionary` 類可以使用任何類型的對象來作鍵和值並且不提供任何關於這些對象的本質信息。在 Swift 中,在某個特定字典中可以存儲的鍵和值必須提前定義清楚,方法是通過顯性類型標注或者類型推斷。
|
||||
|
||||
Swift 的字典使用`Dictionary<KeyType, ValueType>`定義,其中`KeyType`是字典中鍵的數據類型,`ValueType`是字典中對應於這些鍵所存儲值的數據類型。
|
||||
|
||||
`KeyType`的唯一限制就是可哈希的,這樣可以保證它是獨一無二的,所有的 Swift 基本類型(例如`String`,`Int`, `Double`和`Bool`)都是默認可哈希的,並且所有這些類型都可以在字典中當做鍵使用。未關聯值的枚舉成員(參見[枚舉](08_Enumerations.html))也是默認可哈希的。
|
||||
|
||||
<a name="dictionary_literals"></a>
|
||||
## 字典字面量
|
||||
|
||||
我們可以使用字典字面量來構造字典,它們和我們剛才介紹過的數組字面量擁有相似語法。一個字典字面量是一個定義擁有一個或者多個鍵值對的字典集合的簡單語句。
|
||||
|
||||
一個鍵值對是一個`key`和一個`value`的結合體。在字典字面量中,每一個鍵值對的鍵和值都由冒號分割。這些鍵值對構成一個列表,其中這些鍵值對由方括號包含並且由逗號分割:
|
||||
|
||||
```swift
|
||||
[key 1: value 1, key 2: value 2, key 3: value 3]
|
||||
```
|
||||
|
||||
下面的例子創建了一個存儲國際機場名稱的字典。在這個字典中鍵是三個字母的國際航空運輸相關代碼,值是機場名稱:
|
||||
|
||||
```swift
|
||||
var airports: Dictionary<String, String> = ["TYO": "Tokyo", "DUB": "Dublin"]
|
||||
```
|
||||
|
||||
`airports`字典被定義為一種`Dictionary<String, String>`,它意味著這個字典的鍵和值都是`String`類型。
|
||||
|
||||
> 注意:
|
||||
> `airports`字典被聲明為變量(用`var`關鍵字)而不是常量(`let`關鍵字)因為後來更多的機場信息會被添加到這個示例字典中。
|
||||
|
||||
`airports`字典使用字典字面量初始化,包含兩個鍵值對。第一對的鍵是`TYO`,值是`Tokyo`。第二對的鍵是`DUB`,值是`Dublin`。
|
||||
|
||||
這個字典語句包含了兩個`String: String`類型的鍵值對。它們對應`airports`變量聲明的類型(一個只有`String`鍵和`String`值的字典)所以這個字典字面量是構造兩個初始數據項的`airport`字典。
|
||||
|
||||
和數組一樣,如果我們使用字面量構造字典就不用把類型定義清楚。`airports`的也可以用這種方法簡短定義:
|
||||
|
||||
```swift
|
||||
var airports = ["TYO": "Tokyo", "DUB": "Dublin"]
|
||||
```
|
||||
|
||||
因為這個語句中所有的鍵和值都分別是相同的數據類型,Swift 可以推斷出`Dictionary<String, String>`是`airports`字典的正確類型。
|
||||
|
||||
<a name="accessing_and_modifying_a_dictionary"></a>
|
||||
### 讀取和修改字典
|
||||
|
||||
我們可以通過字典的方法和屬性來讀取和修改字典,或者使用下標語法。和數組一樣,我們可以通過字典的只讀屬性`count`來獲取某個字典的數據項數量:
|
||||
|
||||
```swift
|
||||
println("The dictionary of airports contains \(airports.count) items.")
|
||||
// 打印 "The dictionary of airports contains 2 items."(這個字典有兩個數據項)
|
||||
```
|
||||
|
||||
我們也可以在字典中使用下標語法來添加新的數據項。可以使用一個合適類型的 key 作為下標索引,並且分配新的合適類型的值:
|
||||
|
||||
```swift
|
||||
airports["LHR"] = "London"
|
||||
// airports 字典現在有三個數據項
|
||||
```
|
||||
|
||||
我們也可以使用下標語法來改變特定鍵對應的值:
|
||||
|
||||
```swift
|
||||
airports["LHR"] = "London Heathrow"
|
||||
// "LHR"對應的值 被改為 "London Heathrow
|
||||
```
|
||||
|
||||
作為另一種下標方法,字典的`updateValue(forKey:)`方法可以設置或者更新特定鍵對應的值。就像上面所示的示例,`updateValue(forKey:)`方法在這個鍵不存在對應值的時候設置值或者在存在時更新已存在的值。和上面的下標方法不一樣,這個方法返回更新值之前的原值。這樣方便我們檢查更新是否成功。
|
||||
|
||||
`updateValue(forKey:)`函數會返回包含一個字典值類型的可選值。舉例來說:對於存儲`String`值的字典,這個函數會返回一個`String?`或者「可選 `String`」類型的值。如果值存在,則這個可選值值等於被替換的值,否則將會是`nil`。
|
||||
|
||||
```swift
|
||||
if let oldValue = airports.updateValue("Dublin Internation", forKey: "DUB") {
|
||||
println("The old value for DUB was \(oldValue).")
|
||||
}
|
||||
// 輸出 "The old value for DUB was Dublin."(DUB原值是dublin)
|
||||
```
|
||||
|
||||
我們也可以使用下標語法來在字典中檢索特定鍵對應的值。由於使用一個沒有值的鍵這種情況是有可能發生的,可選類型返回這個鍵存在的相關值,否則就返回`nil`:
|
||||
|
||||
```swift
|
||||
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 Internation."(機場的名字是都柏林國際)
|
||||
```
|
||||
|
||||
我們還可以使用下標語法來通過給某個鍵的對應值賦值為`nil`來從字典裡移除一個鍵值對:
|
||||
|
||||
```swift
|
||||
airports["APL"] = "Apple Internation"
|
||||
// "Apple Internation"不是真的 APL機場, 刪除它
|
||||
airports["APL"] = nil
|
||||
// APL現在被移除了
|
||||
```
|
||||
|
||||
另外,`removeValueForKey`方法也可以用來在字典中移除鍵值對。這個方法在鍵值對存在的情況下會移除該鍵值對並且返回被移除的value或者在沒有值的情況下返回`nil`:
|
||||
|
||||
```swift
|
||||
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.")
|
||||
}
|
||||
// prints "The removed airport's name is Dublin International."
|
||||
```
|
||||
|
||||
<a name="iterating_over_a_dictionary"></a>
|
||||
### 字典遍歷
|
||||
|
||||
我們可以使用`for-in`循環來遍歷某個字典中的鍵值對。每一個字典中的數據項都由`(key, value)`元組形式返回,並且我們可以使用臨時常量或者變量來分解這些元組:
|
||||
|
||||
```swift
|
||||
for (airportCode, airportName) in airports {
|
||||
println("\(airportCode): \(airportName)")
|
||||
}
|
||||
// TYO: Tokyo
|
||||
// LHR: London Heathrow
|
||||
```
|
||||
|
||||
`for-in`循環請參見[For 循環](05_Control_Flow.html#for_loops)。
|
||||
|
||||
我們也可以通過訪問它的`keys`或者`values`屬性(都是可遍歷集合)檢索一個字典的鍵或者值:
|
||||
|
||||
```swift
|
||||
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
|
||||
```
|
||||
|
||||
如果我們只是需要使用某個字典的鍵集合或者值集合來作為某個接受`Array`實例 API 的參數,可以直接使用`keys`或者`values`屬性直接構造一個新數組:
|
||||
|
||||
```swift
|
||||
let airportCodes = Array(airports.keys)
|
||||
// airportCodes is ["TYO", "LHR"]
|
||||
|
||||
let airportNames = Array(airports.values)
|
||||
// airportNames is ["Tokyo", "London Heathrow"]
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> Swift 的字典類型是無序集合類型。其中字典鍵,值,鍵值對在遍歷的時候會重新排列,而且其中順序是不固定的。
|
||||
|
||||
<a name="creating_an_empty_dictionary"></a>
|
||||
### 創建一個空字典
|
||||
|
||||
我們可以像數組一樣使用構造語法創建一個空字典:
|
||||
|
||||
```swift
|
||||
var namesOfIntegers = Dictionary<Int, String>()
|
||||
// namesOfIntegers 是一個空的 Dictionary<Int, String>
|
||||
```
|
||||
|
||||
這個例子創建了一個`Int, String`類型的空字典來儲存英語對整數的命名。它的鍵是`Int`型,值是`String`型。
|
||||
|
||||
如果上下文已經提供了信息類型,我們可以使用空字典字面量來創建一個空字典,記作`[:]`(中括號中放一個冒號):
|
||||
|
||||
```swift
|
||||
namesOfIntegers[16] = "sixteen"
|
||||
// namesOfIntegers 現在包含一個鍵值對
|
||||
namesOfIntegers = [:]
|
||||
// namesOfIntegers 又成為了一個 Int, String類型的空字典
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 在後台,Swift 的數組和字典都是由泛型集合來實現的,想瞭解更多泛型和集合信息請參見[泛型](22_Generics.html)。
|
||||
|
||||
<a name="mutability_of_collections"></a>
|
||||
## 集合的可變性
|
||||
|
||||
數組和字典都是在單個集合中存儲可變值。如果我們創建一個數組或者字典並且把它分配成一個變量,這個集合將會是可變的。這意味著我們可以在創建之後添加更多或移除已存在的數據項來改變這個集合的大小。與此相反,如果我們把數組或字典分配成常量,那麼它就是不可變的,它的大小不能被改變。
|
||||
|
||||
對字典來說,不可變性也意味著我們不能替換其中任何現有鍵所對應的值。不可變字典的內容在被首次設定之後不能更改。
|
||||
不可變性對數組來說有一點不同,當然我們不能試著改變任何不可變數組的大小,但是我們可以重新設定相對現存索引所對應的值。這使得 Swift 數組在大小被固定的時候依然可以做的很棒。
|
||||
|
||||
Swift 數組的可變性行為同時影響了數組實例如何被分配和修改,想獲取更多信息,請參見[集合在賦值和複製中的行為](09_Classes_and_Structures.html#assignment_and_copy_behavior_for_collection_types)。
|
||||
|
||||
> 注意:
|
||||
> 在我們不需要改變數組大小的時候創建不可變數組是很好的習慣。如此 Swift 編譯器可以優化我們創建的集合。
|
726
source-tw/chapter2/05_Control_Flow.md
Normal file
726
source-tw/chapter2/05_Control_Flow.md
Normal file
@ -0,0 +1,726 @@
|
||||
> 翻譯:[vclwei](https://github.com/vclwei), [coverxit](https://github.com/coverxit), [NicePiao](https://github.com/NicePiao)
|
||||
> 校對:[coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
# 控制流
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [For 循環](#for_loops)
|
||||
- [While 循環](#while_loops)
|
||||
- [條件語句](#conditional_statement)
|
||||
- [控制轉移語句(Control Transfer Statements)](#control_transfer_statements)
|
||||
|
||||
Swift提供了類似 C 語言的流程控制結構,包括可以多次執行任務的`for`和`while`循環,基於特定條件選擇執行不同代碼分支的`if`和`switch`語句,還有控制流程跳轉到其他代碼的`break`和`continue`語句。
|
||||
|
||||
除了 C 語言裡面傳統的 for 條件遞增(`for-condition-increment`)循環,Swift 還增加了`for-in`循環,用來更簡單地遍歷數組(array),字典(dictionary),區間(range),字符串(string)和其他序列類型。
|
||||
|
||||
Swift 的`switch`語句比 C 語言中更加強大。在 C 語言中,如果某個 case 不小心漏寫了`break`,這個 case 就會貫穿(fallthrough)至下一個 case,Swift 無需寫`break`,所以不會發生這種貫穿(fallthrough)的情況。case 還可以匹配更多的類型模式,包括區間匹配(range matching),元組(tuple)和特定類型的描述。`switch`的 case 語句中匹配的值可以是由 case 體內部臨時的常量或者變量決定,也可以由`where`分句描述更複雜的匹配條件。
|
||||
|
||||
<a name="for_loops"></a>
|
||||
## For 循環
|
||||
|
||||
`for`循環用來按照指定的次數多次執行一系列語句。Swift 提供兩種`for`循環形式:
|
||||
|
||||
* `for-in`用來遍歷一個區間(range),序列(sequence),集合(collection),系列(progression)裡面所有的元素執行一系列語句。
|
||||
* for條件遞增(`for-condition-increment`)語句,用來重複執行一系列語句直到達成特定條件達成,一般通過在每次循環完成後增加計數器的值來實現。
|
||||
|
||||
<a name="for_in"></a>
|
||||
### For-In
|
||||
|
||||
你可以使用`for-in`循環來遍歷一個集合裡面的所有元素,例如由數字表示的區間、數組中的元素、字符串中的字符。
|
||||
|
||||
下面的例子用來輸出乘 5 乘法表前面一部分內容:
|
||||
|
||||
```swift
|
||||
for index in 1...5 {
|
||||
println("\(index) times 5 is \(index * 5)")
|
||||
}
|
||||
// 1 times 5 is 5
|
||||
// 2 times 5 is 10
|
||||
// 3 times 5 is 15
|
||||
// 4 times 5 is 20
|
||||
// 5 times 5 is 25
|
||||
```
|
||||
|
||||
例子中用來進行遍歷的元素是一組使用閉區間操作符(`...`)表示的從`1`到`5`的數字。`index`被賦值為閉區間中的第一個數字(`1`),然後循環中的語句被執行一次。在本例中,這個循環只包含一個語句,用來輸出當前`index`值所對應的乘 5 乘法表結果。該語句執行後,`index`的值被更新為閉區間中的第二個數字(`2`),之後`println`方法會再執行一次。整個過程會進行到閉區間結尾為止。
|
||||
|
||||
上面的例子中,`index`是一個每次循環遍歷開始時被自動賦值的常量。這種情況下,`index`在使用前不需要聲明,只需要將它包含在循環的聲明中,就可以對其進行隱式聲明,而無需使用`let`關鍵字聲明。
|
||||
|
||||
>注意:
|
||||
`index`常量只存在於循環的生命週期裡。如果你想在循環完成後訪問`index`的值,又或者想讓`index`成為一個變量而不是常量,你必須在循環之前自己進行聲明。
|
||||
|
||||
如果你不需要知道區間內每一項的值,你可以使用下劃線(`_`)替代變量名來忽略對值的訪問:
|
||||
|
||||
```swift
|
||||
let base = 3
|
||||
let power = 10
|
||||
var answer = 1
|
||||
for _ in 1...power {
|
||||
answer *= base
|
||||
}
|
||||
println("\(base) to the power of \(power) is \(answer)")
|
||||
// 輸出 "3 to the power of 10 is 59049"
|
||||
```
|
||||
|
||||
這個例子計算 base 這個數的 power 次冪(本例中,是`3`的`10`次冪),從`1`(`3`的`0`次冪)開始做`3`的乘法, 進行`10`次,使用`1`到`10`的閉區間循環。這個計算並不需要知道每一次循環中計數器具體的值,只需要執行了正確的循環次數即可。下劃線符號`_`(替代循環中的變量)能夠忽略具體的值,並且不提供循環遍歷時對值的訪問。
|
||||
|
||||
使用`for-in`遍歷一個數組所有元素:
|
||||
|
||||
```swift
|
||||
let names = ["Anna", "Alex", "Brian", "Jack"]
|
||||
for name in names {
|
||||
println("Hello, \(name)!")
|
||||
}
|
||||
// Hello, Anna!
|
||||
// Hello, Alex!
|
||||
// Hello, Brian!
|
||||
// Hello, Jack!
|
||||
```
|
||||
|
||||
你也可以通過遍歷一個字典來訪問它的鍵值對(key-value pairs)。遍歷字典時,字典的每項元素會以`(key, value)`元組的形式返回,你可以在`for-in`循環中使用顯式的常量名稱來解讀`(key, value)`元組。下面的例子中,字典的鍵(key)解讀為常量`animalName`,字典的值會被解讀為常量`legCount`:
|
||||
|
||||
```swift
|
||||
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
|
||||
for (animalName, legCount) in numberOfLegs {
|
||||
println("\(animalName)s have \(legCount) legs")
|
||||
}
|
||||
// spiders have 8 legs
|
||||
// ants have 6 legs
|
||||
// cats have 4 legs
|
||||
```
|
||||
|
||||
字典元素的遍歷順序和插入順序可能不同,字典的內容在內部是無序的,所以遍歷元素時不能保證順序。關於數組和字典,詳情參見[集合類型](../chapter2/04_Collection_Types.html)。
|
||||
|
||||
除了數組和字典,你也可以使用`for-in`循環來遍歷字符串中的字符(`Character`):
|
||||
|
||||
```swift
|
||||
for character in "Hello" {
|
||||
println(character)
|
||||
}
|
||||
// H
|
||||
// e
|
||||
// l
|
||||
// l
|
||||
// o
|
||||
```
|
||||
|
||||
<a name="for_condition_increment"></a>
|
||||
### For條件遞增(for-condition-increment)
|
||||
|
||||
除了`for-in`循環,Swift 提供使用條件判斷和遞增方法的標準 C 樣式`for`循環:
|
||||
|
||||
```swift
|
||||
for var index = 0; index < 3; ++index {
|
||||
println("index is \(index)")
|
||||
}
|
||||
// index is 0
|
||||
// index is 1
|
||||
// index is 2
|
||||
```
|
||||
|
||||
下面是一般情況下這種循環方式的格式:
|
||||
|
||||
> for `initialization`; `condition`; `increment` {
|
||||
> `statements`
|
||||
> }
|
||||
|
||||
和 C 語言中一樣,分號將循環的定義分為 3 個部分,不同的是,Swift 不需要使用圓括號將「initialization; condition; increment」包括起來。
|
||||
|
||||
這個循環執行流程如下:
|
||||
|
||||
1. 循環首次啟動時,初始化表達式(_initialization expression_)被調用一次,用來初始化循環所需的所有常量和變量。
|
||||
2. 條件表達式(_condition expression_)被調用,如果表達式調用結果為`false`,循環結束,繼續執行`for`循環關閉大括號
|
||||
(`}`)之後的代碼。如果表達式調用結果為`true`,則會執行大括號內部的代碼(_statements_)。
|
||||
3. 執行所有語句(_statements_)之後,執行遞增表達式(_increment expression_)。通常會增加或減少計數器的值,或者根據語句(_statements_)輸出來修改某一個初始化的變量。當遞增表達式運行完成後,重複執行第 2 步,條件表達式會再次執行。
|
||||
|
||||
上述描述和循環格式等同於:
|
||||
|
||||
> `initialization`
|
||||
> while `condition` {
|
||||
> `statements`
|
||||
> `increment`
|
||||
> }
|
||||
|
||||
在初始化表達式中聲明的常量和變量(比如`var index = 0`)只在`for`循環的生命週期裡有效。如果想在循環結束後訪問`index`的值,你必須要在循環生命週期開始前聲明`index`。
|
||||
|
||||
```swift
|
||||
var index: Int
|
||||
for index = 0; index < 3; ++index {
|
||||
println("index is \(index)")
|
||||
}
|
||||
// index is 0
|
||||
// index is 1
|
||||
// index is 2
|
||||
println("The loop statements were executed \(index) times")
|
||||
// 輸出 "The loop statements were executed 3 times
|
||||
```
|
||||
|
||||
注意`index`在循環結束後最終的值是`3`而不是`2`。最後一次調用遞增表達式`++index`會將`index`設置為`3`,從而導致`index < 3`條件為`false`,並終止循環。
|
||||
|
||||
<a name="while_loops"></a>
|
||||
## While 循環
|
||||
|
||||
`while`循環運行一系列語句直到條件變成`false`。這類循環適合使用在第一次迭代前迭代次數未知的情況下。Swift 提供兩種`while`循環形式:
|
||||
|
||||
* `while`循環,每次在循環開始時計算條件是否符合;
|
||||
* `do-while`循環,每次在循環結束時計算條件是否符合。
|
||||
|
||||
<a name="while"></a>
|
||||
###While
|
||||
|
||||
`while`循環從計算單一條件開始。如果條件為`true`,會重複運行一系列語句,直到條件變為`false`。
|
||||
|
||||
下面是一般情況下 `while` 循環格式:
|
||||
|
||||
> while `condition` {
|
||||
> `statements`
|
||||
> }
|
||||
|
||||
下面的例子來玩一個叫做_蛇和梯子(Snakes and Ladders)_的小遊戲,也叫做_滑道和梯子(Chutes and Ladders)_:
|
||||
|
||||

|
||||
|
||||
遊戲的規則如下:
|
||||
|
||||
* 遊戲盤面包括 25 個方格,遊戲目標是達到或者超過第 25 個方格;
|
||||
* 每一輪,你通過擲一個 6 邊的骰子來確定你移動方塊的步數,移動的路線由上圖中橫向的虛線所示;
|
||||
* 如果在某輪結束,你移動到了梯子的底部,可以順著梯子爬上去;
|
||||
* 如果在某輪結束,你移動到了蛇的頭部,你會順著蛇的身體滑下去。
|
||||
|
||||
遊戲盤面可以使用一個`Int`數組來表達。數組的長度由一個`finalSquare`常量儲存,用來初始化數組和檢測最終勝利條件。遊戲盤面由 26 個 `Int` 0 值初始化,而不是 25 個(由`0`到`25`,一共 26 個):
|
||||
|
||||
```swift
|
||||
let finalSquare = 25
|
||||
var board = Int[](count: finalSquare + 1, repeatedValue: 0)
|
||||
```
|
||||
|
||||
一些方塊被設置成有蛇或者梯子的指定值。梯子底部的方塊是一個正值,使你可以向上移動,蛇頭處的方塊是一個負值,會讓你向下移動:
|
||||
|
||||
```swift
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
```
|
||||
|
||||
3 號方塊是梯子的底部,會讓你向上移動到 11 號方格,我們使用`board[03]`等於`+08`(來表示`11`和`3`之間的差值)。使用一元加運算符(`+i`)是為了和一元減運算符(`-i`)對稱,為了讓盤面代碼整齊,小於 10 的數字都使用 0 補齊(這些風格上的調整都不是必須的,只是為了讓代碼看起來更加整潔)。
|
||||
|
||||
玩家由左下角編號為 0 的方格開始遊戲。一般來說玩家第一次擲骰子後才會進入遊戲盤面:
|
||||
|
||||
```swift
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
while square < finalSquare {
|
||||
// 擲骰子
|
||||
if ++diceRoll == 7 { diceRoll = 1 }
|
||||
// 根據點數移動
|
||||
square += diceRoll
|
||||
if square < board.count {
|
||||
// 如果玩家還在棋盤上,順著梯子爬上去或者順著蛇滑下去
|
||||
square += board[square]
|
||||
}
|
||||
}
|
||||
println("Game over!")
|
||||
```
|
||||
|
||||
本例中使用了最簡單的方法來模擬擲骰子。 `diceRoll`的值並不是一個隨機數,而是以`0`為初始值,之後每一次`while`循環,`diceRoll`的值使用前置自增操作符(`++i`)來自增 1 ,然後檢測是否超出了最大值。`++diceRoll`調用完成_後_,返回值等於`diceRoll`自增後的值。任何時候如果`diceRoll`的值等於7時,就超過了骰子的最大值,會被重置為`1`。所以`diceRoll`的取值順序會一直是`1`,`2`,`3`,`4`,`5`,`6`,`1`,`2`。
|
||||
|
||||
擲完骰子後,玩家向前移動`diceRoll`個方格,如果玩家移動超過了第 25 個方格,這個時候遊戲結束,相應地,代碼會在`square`增加`board[square]`的值向前或向後移動(遇到了梯子或者蛇)之前,檢測`square`的值是否小於`board`的`count`屬性。
|
||||
|
||||
如果沒有這個檢測(`square < board.count`),`board[square]`可能會越界訪問`board`數組,導致錯誤。例如如果`square`等於`26`, 代碼會去嘗試訪問`board[26]`,超過數組的長度。
|
||||
|
||||
當本輪`while`循環運行完畢,會再檢測循環條件是否需要再運行一次循環。如果玩家移動到或者超過第 25 個方格,循環條件結果為`false`,此時遊戲結束。
|
||||
|
||||
`while` 循環比較適合本例中的這種情況,因為在 `while` 循環開始時,我們並不知道遊戲的長度或者循環的次數,只有在達成指定條件時循環才會結束。
|
||||
|
||||
|
||||
<a name="do_while"></a>
|
||||
###Do-While
|
||||
|
||||
`while`循環的另外一種形式是`do-while`,它和`while`的區別是在判斷循環條件之前,先執行一次循環的代碼塊,然後重複循環直到條件為`false`。
|
||||
|
||||
下面是一般情況下 `do-while`循環的格式:
|
||||
|
||||
> do {
|
||||
> `statements`
|
||||
> } while `condition`
|
||||
|
||||
還是蛇和梯子的遊戲,使用`do-while`循環來替代`while`循環。`finalSquare`、`board`、`square`和`diceRoll`的值初始化同`while`循環一樣:
|
||||
|
||||
``` swift
|
||||
let finalSquare = 25
|
||||
var board = Int[](count: finalSquare + 1, repeatedValue: 0)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
```
|
||||
|
||||
`do-while`的循環版本,循環中_第一步_就需要去檢測是否在梯子或者蛇的方塊上。沒有梯子會讓玩家直接上到第 25 個方格,所以玩家不會通過梯子直接贏得遊戲。這樣在循環開始時先檢測是否踩在梯子或者蛇上是安全的。
|
||||
|
||||
遊戲開始時,玩家在第 0 個方格上,`board[0]`一直等於 0, 不會有什麼影響:
|
||||
|
||||
```swift
|
||||
do {
|
||||
// 順著梯子爬上去或者順著蛇滑下去
|
||||
square += board[square]
|
||||
// 擲骰子
|
||||
if ++diceRoll == 7 { diceRoll = 1 }
|
||||
// 根據點數移動
|
||||
square += diceRoll
|
||||
} while square < finalSquare
|
||||
println("Game over!")
|
||||
```
|
||||
|
||||
檢測完玩家是否踩在梯子或者蛇上之後,開始擲骰子,然後玩家向前移動`diceRoll`個方格,本輪循環結束。
|
||||
|
||||
循環條件(`while square < finalSquare`)和`while`方式相同,但是只會在循環結束後進行計算。在這個遊戲中,`do-while`表現得比`while`循環更好。`do-while`方式會在條件判斷`square`沒有超出後直接運行`square += board[square]`,這種方式可以去掉`while`版本中的數組越界判斷。
|
||||
|
||||
<a name="conditional_statement"></a>
|
||||
## 條件語句
|
||||
|
||||
根據特定的條件執行特定的代碼通常是十分有用的,例如:當錯誤發生時,你可能想運行額外的代碼;或者,當輸入的值太大或太小時,向用戶顯示一條消息等。要實現這些功能,你就需要使用*條件語句*。
|
||||
|
||||
Swift 提供兩種類型的條件語句:`if`語句和`switch`語句。通常,當條件較為簡單且可能的情況很少時,使用`if`語句。而`switch`語句更適用於條件較複雜、可能情況較多且需要用到模式匹配(pattern-matching)的情境。
|
||||
|
||||
<a name="if"></a>
|
||||
### If
|
||||
|
||||
`if`語句最簡單的形式就是只包含一個條件,當且僅當該條件為`true`時,才執行相關代碼:
|
||||
|
||||
```swift
|
||||
var temperatureInFahrenheit = 30
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
println("It's very cold. Consider wearing a scarf.")
|
||||
}
|
||||
// 輸出 "It's very cold. Consider wearing a scarf."
|
||||
```
|
||||
|
||||
上面的例子會判斷溫度是否小於等於 32 華氏度(水的冰點)。如果是,則打印一條消息;否則,不打印任何消息,繼續執行`if`塊後面的代碼。
|
||||
|
||||
當然,`if`語句允許二選一,也就是當條件為`false`時,執行 *else 語句*:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 40
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
println("It's very cold. Consider wearing a scarf.")
|
||||
} else {
|
||||
println("It's not that cold. Wear a t-shirt.")
|
||||
}
|
||||
// 輸出 "It's not that cold. Wear a t-shirt."
|
||||
```
|
||||
|
||||
顯然,這兩條分支中總有一條會被執行。由於溫度已升至 40 華氏度,不算太冷,沒必要再圍圍巾——因此,`else`分支就被觸發了。
|
||||
|
||||
你可以把多個`if`語句鏈接在一起,像下面這樣:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 90
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
println("It's very cold. Consider wearing a scarf.")
|
||||
} else if temperatureInFahrenheit >= 86 {
|
||||
println("It's really warm. Don't forget to wear sunscreen.")
|
||||
} else {
|
||||
println("It's not that cold. Wear a t-shirt.")
|
||||
}
|
||||
// 輸出 "It's really warm. Don't forget to wear sunscreen."
|
||||
```
|
||||
|
||||
在上面的例子中,額外的`if`語句用於判斷是不是特別熱。而最後的`else`語句被保留了下來,用於打印既不冷也不熱時的消息。
|
||||
|
||||
實際上,最後的`else`語句是可選的:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 72
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
println("It's very cold. Consider wearing a scarf.")
|
||||
} else if temperatureInFahrenheit >= 86 {
|
||||
println("It's really warm. Don't forget to wear sunscreen.")
|
||||
}
|
||||
```
|
||||
|
||||
在這個例子中,由於既不冷也不熱,所以不會觸發`if`或`else if`分支,也就不會打印任何消息。
|
||||
|
||||
<a name="switch"></a>
|
||||
### Switch
|
||||
|
||||
`switch`語句會嘗試把某個值與若干個模式(pattern)進行匹配。根據第一個匹配成功的模式,`switch`語句會執行對應的代碼。當有可能的情況較多時,通常用`switch`語句替換`if`語句。
|
||||
|
||||
`switch`語句最簡單的形式就是把某個值與一個或若干個相同類型的值作比較:
|
||||
|
||||
> switch `some value to consider` {
|
||||
> case `value 1`:
|
||||
> `respond to value 1`
|
||||
> case `value 2`,
|
||||
> `value 3`:
|
||||
> `respond to value 2 or 3`
|
||||
> default:
|
||||
> `otherwise, do something else`
|
||||
> }
|
||||
|
||||
`switch`語句都由*多個 case* 構成。為了匹配某些更特定的值,Swift 提供了幾種更複雜的匹配模式,這些模式將在本節的稍後部分提到。
|
||||
|
||||
每一個 case 都是代碼執行的一條分支,這與`if`語句類似。與之不同的是,`switch`語句會決定哪一條分支應該被執行。
|
||||
|
||||
`switch`語句必須是_完備的_。這就是說,每一個可能的值都必須至少有一個 case 分支與之對應。在某些不可能涵蓋所有值的情況下,你可以使用默認(`default`)分支滿足該要求,這個默認分支必須在`switch`語句的最後面。
|
||||
|
||||
下面的例子使用`switch`語句來匹配一個名為`someCharacter`的小寫字符:
|
||||
|
||||
```swift
|
||||
let someCharacter: Character = "e"
|
||||
switch someCharacter {
|
||||
case "a", "e", "i", "o", "u":
|
||||
println("\(someCharacter) is a vowel")
|
||||
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
|
||||
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
|
||||
println("\(someCharacter) is a consonant")
|
||||
default:
|
||||
println("\(someCharacter) is not a vowel or a consonant")
|
||||
}
|
||||
// 輸出 "e is a vowel"
|
||||
```
|
||||
|
||||
在這個例子中,第一個 case 分支用於匹配五個元音,第二個 case 分支用於匹配所有的輔音。
|
||||
|
||||
由於為其它可能的字符寫 case 分支沒有實際的意義,因此在這個例子中使用了默認分支來處理剩下的既不是元音也不是輔音的字符——這就保證了`switch`語句的完備性。
|
||||
|
||||
<a name="no_implicit_fallthrough"></a>
|
||||
#### 不存在隱式的貫穿(No Implicit Fallthrough)
|
||||
|
||||
與 C 語言和 Objective-C 中的`switch`語句不同,在 Swift 中,當匹配的 case 分支中的代碼執行完畢後,程序會終止`switch`語句,而不會繼續執行下一個 case 分支。這也就是說,不需要在 case 分支中顯式地使用`break`語句。這使得`switch`語句更安全、更易用,也避免了因忘記寫`break`語句而產生的錯誤。
|
||||
|
||||
> 注意:
|
||||
你依然可以在 case 分支中的代碼執行完畢前跳出,詳情請參考[Switch 語句中的 break](#break_in_a_switch_statement)。
|
||||
|
||||
每一個 case 分支都*必須*包含至少一條語句。像下面這樣書寫代碼是無效的,因為第一個 case 分支是空的:
|
||||
|
||||
```swift
|
||||
let anotherCharacter: Character = "a"
|
||||
switch anotherCharacter {
|
||||
case "a":
|
||||
case "A":
|
||||
println("The letter A")
|
||||
default:
|
||||
println("Not the letter A")
|
||||
}
|
||||
// this will report a compile-time error
|
||||
```
|
||||
|
||||
不像 C 語言裡的`switch`語句,在 Swift 中,`switch`語句不會同時匹配`"a"`和`"A"`。相反的,上面的代碼會引起編譯期錯誤:`case "a": does not contain any executable statements`——這就避免了意外地從一個 case 分支貫穿到另外一個,使得代碼更安全、也更直觀。
|
||||
|
||||
一個 case 也可以包含多個模式,用逗號把它們分開(如果太長了也可以分行寫):
|
||||
|
||||
> switch `some value to consider` {
|
||||
> case `value 1`,
|
||||
> `value 2`:
|
||||
> `statements`
|
||||
> }
|
||||
|
||||
> 注意:
|
||||
如果想要貫穿至特定的 case 分支中,請使用`fallthrough`語句,詳情請參考[貫穿(Fallthrough)](#fallthrough)。
|
||||
|
||||
<a name="range_matching"></a>
|
||||
#### 區間匹配(Range Matching)
|
||||
|
||||
case 分支的模式也可以是一個值的區間。下面的例子展示了如何使用區間匹配來輸出任意數字對應的自然語言格式:
|
||||
|
||||
```swift
|
||||
let count = 3_000_000_000_000
|
||||
let countedThings = "stars in the Milky Way"
|
||||
var naturalCount: String
|
||||
switch count {
|
||||
case 0:
|
||||
naturalCount = "no"
|
||||
case 1...3:
|
||||
naturalCount = "a few"
|
||||
case 4...9:
|
||||
naturalCount = "several"
|
||||
case 10...99:
|
||||
naturalCount = "tens of"
|
||||
case 100...999:
|
||||
naturalCount = "hundreds of"
|
||||
case 1000...999_999:
|
||||
naturalCount = "thousands of"
|
||||
default:
|
||||
naturalCount = "millions and millions of"
|
||||
}
|
||||
println("There are \(naturalCount) \(countedThings).")
|
||||
// 輸出 "There are millions and millions of stars in the Milky Way."
|
||||
```
|
||||
|
||||
<a name="tuples"></a>
|
||||
#### 元組(Tuple)
|
||||
|
||||
你可以使用元組在同一個`switch`語句中測試多個值。元組中的元素可以是值,也可以是區間。另外,使用下劃線(`_`)來匹配所有可能的值。
|
||||
|
||||
下面的例子展示了如何使用一個`(Int, Int)`類型的元組來分類下圖中的點(x, y):
|
||||
|
||||
```swift
|
||||
let somePoint = (1, 1)
|
||||
switch somePoint {
|
||||
case (0, 0):
|
||||
println("(0, 0) is at the origin")
|
||||
case (_, 0):
|
||||
println("(\(somePoint.0), 0) is on the x-axis")
|
||||
case (0, _):
|
||||
println("(0, \(somePoint.1)) is on the y-axis")
|
||||
case (-2...2, -2...2):
|
||||
println("(\(somePoint.0), \(somePoint.1)) is inside the box")
|
||||
default:
|
||||
println("(\(somePoint.0), \(somePoint.1)) is outside of the box")
|
||||
}
|
||||
// 輸出 "(1, 1) is inside the box"
|
||||
```
|
||||
|
||||

|
||||
|
||||
在上面的例子中,`switch`語句會判斷某個點是否是原點(0, 0),是否在紅色的x軸上,是否在黃色y軸上,是否在一個以原點為中心的4x4的矩形裡,或者在這個矩形外面。
|
||||
|
||||
不像 C 語言,Swift 允許多個 case 匹配同一個值。實際上,在這個例子中,點(0, 0)可以匹配所有_四個 case_。但是,如果存在多個匹配,那麼只會執行第一個被匹配到的 case 分支。考慮點(0, 0)會首先匹配`case (0, 0)`,因此剩下的能夠匹配(0, 0)的 case 分支都會被忽視掉。
|
||||
|
||||
|
||||
<a name="value_bindings"></a>
|
||||
#### 值綁定(Value Bindings)
|
||||
|
||||
case 分支的模式允許將匹配的值綁定到一個臨時的常量或變量,這些常量或變量在該 case 分支裡就可以被引用了——這種行為被稱為*值綁定*(value binding)。
|
||||
|
||||
下面的例子展示了如何在一個`(Int, Int)`類型的元組中使用值綁定來分類下圖中的點(x, y):
|
||||
|
||||
```swift
|
||||
let anotherPoint = (2, 0)
|
||||
switch anotherPoint {
|
||||
case (let x, 0):
|
||||
println("on the x-axis with an x value of \(x)")
|
||||
case (0, let y):
|
||||
println("on the y-axis with a y value of \(y)")
|
||||
case let (x, y):
|
||||
println("somewhere else at (\(x), \(y))")
|
||||
}
|
||||
// 輸出 "on the x-axis with an x value of 2"
|
||||
```
|
||||
|
||||

|
||||
|
||||
在上面的例子中,`switch`語句會判斷某個點是否在紅色的x軸上,是否在黃色y軸上,或者不在坐標軸上。
|
||||
|
||||
這三個 case 都聲明了常量`x`和`y`的佔位符,用於臨時獲取元組`anotherPoint`的一個或兩個值。第一個 case ——`case (let x, 0)`將匹配一個縱坐標為`0`的點,並把這個點的橫坐標賦給臨時的常量`x`。類似的,第二個 case ——`case (0, let y)`將匹配一個橫坐標為`0`的點,並把這個點的縱坐標賦給臨時的常量`y`。
|
||||
|
||||
一旦聲明了這些臨時的常量,它們就可以在其對應的 case 分支裡引用。在這個例子中,它們用於簡化`println`的書寫。
|
||||
|
||||
請注意,這個`switch`語句不包含默認分支。這是因為最後一個 case ——`case let(x, y)`聲明了一個可以匹配餘下所有值的元組。這使得`switch`語句已經完備了,因此不需要再書寫默認分支。
|
||||
|
||||
在上面的例子中,`x`和`y`是常量,這是因為沒有必要在其對應的 case 分支中修改它們的值。然而,它們也可以是變量——程序將會創建臨時變量,並用相應的值初始化它。修改這些變量只會影響其對應的 case 分支。
|
||||
|
||||
<a name="where"></a>
|
||||
#### Where
|
||||
|
||||
case 分支的模式可以使用`where`語句來判斷額外的條件。
|
||||
|
||||
下面的例子把下圖中的點(x, y)進行了分類:
|
||||
|
||||
```swift
|
||||
let yetAnotherPoint = (1, -1)
|
||||
switch yetAnotherPoint {
|
||||
case let (x, y) where x == y:
|
||||
println("(\(x), \(y)) is on the line x == y")
|
||||
case let (x, y) where x == -y:
|
||||
println("(\(x), \(y)) is on the line x == -y")
|
||||
case let (x, y):
|
||||
println("(\(x), \(y)) is just some arbitrary point")
|
||||
}
|
||||
// 輸出 "(1, -1) is on the line x == -y"
|
||||
```
|
||||
|
||||

|
||||
|
||||
在上面的例子中,`switch`語句會判斷某個點是否在綠色的對角線`x == y`上,是否在紫色的對角線`x == -y`上,或者不在對角線上。
|
||||
|
||||
這三個 case 都聲明了常量`x`和`y`的佔位符,用於臨時獲取元組`yetAnotherPoint`的兩個值。這些常量被用作`where`語句的一部分,從而創建一個動態的過濾器(filter)。當且僅當`where`語句的條件為`true`時,匹配到的 case 分支才會被執行。
|
||||
|
||||
就像是值綁定中的例子,由於最後一個 case 分支匹配了餘下所有可能的值,`switch`語句就已經完備了,因此不需要再書寫默認分支。
|
||||
|
||||
<a name="control_transfer_statements"></a>
|
||||
## 控制轉移語句(Control Transfer Statements)
|
||||
|
||||
控制轉移語句改變你代碼的執行順序,通過它你可以實現代碼的跳轉。Swift有四種控制轉移語句。
|
||||
|
||||
- continue
|
||||
- break
|
||||
- fallthrough
|
||||
- return
|
||||
|
||||
我們將會在下面討論`continue`、`break`和`fallthrough`語句。`return`語句將會在[函數](../chapter2/06_Functions.html)章節討論。
|
||||
|
||||
<a name="continue"></a>
|
||||
### Continue
|
||||
|
||||
`continue`語句告訴一個循環體立刻停止本次循環迭代,重新開始下次循環迭代。就好像在說「本次循環迭代我已經執行完了」,但是並不會離開整個循環體。
|
||||
|
||||
>注意:
|
||||
在一個for條件遞增(`for-condition-increment`)循環體中,在調用`continue`語句後,迭代增量仍然會被計算求值。循環體繼續像往常一樣工作,僅僅只是循環體中的執行代碼會被跳過。
|
||||
|
||||
下面的例子把一個小寫字符串中的元音字母和空格字符移除,生成了一個含義模糊的短句:
|
||||
|
||||
```swift
|
||||
let puzzleInput = "great minds think alike"
|
||||
var puzzleOutput = ""
|
||||
for character in puzzleInput {
|
||||
switch character {
|
||||
case "a", "e", "i", "o", "u", " ":
|
||||
continue
|
||||
default:
|
||||
puzzleOutput += character
|
||||
}
|
||||
}
|
||||
println(puzzleOutput)
|
||||
// 輸出 "grtmndsthnklk"
|
||||
```
|
||||
|
||||
在上面的代碼中,只要匹配到元音字母或者空格字符,就調用`continue`語句,使本次循環迭代結束,從新開始下次循環迭代。這種行為使`switch`匹配到元音字母和空格字符時不做處理,而不是讓每一個匹配到的字符都被打印。
|
||||
|
||||
<a name="break"></a>
|
||||
### Break
|
||||
|
||||
`break`語句會立刻結束整個控制流的執行。當你想要更早的結束一個`switch`代碼塊或者一個循環體時,你都可以使用`break`語句。
|
||||
|
||||
<a name="break_in_a_loop_statement"></a>
|
||||
#### 循環語句中的 break
|
||||
|
||||
當在一個循環體中使用`break`時,會立刻中斷該循環體的執行,然後跳轉到表示循環體結束的大括號(`}`)後的第一行代碼。不會再有本次循環迭代的代碼被執行,也不會再有下次的循環迭代產生。
|
||||
|
||||
<a name="break_in_a_switch_statement"></a>
|
||||
#### Switch 語句中的 break
|
||||
|
||||
當在一個`switch`代碼塊中使用`break`時,會立即中斷該`switch`代碼塊的執行,並且跳轉到表示`switch`代碼塊結束的大括號(`}`)後的第一行代碼。
|
||||
|
||||
這種特性可以被用來匹配或者忽略一個或多個分支。因為 Swift 的`switch`需要包含所有的分支而且不允許有為空的分支,有時為了使你的意圖更明顯,需要特意匹配或者忽略某個分支。那麼當你想忽略某個分支時,可以在該分支內寫上`break`語句。當那個分支被匹配到時,分支內的`break`語句立即結束`switch`代碼塊。
|
||||
|
||||
>注意:
|
||||
當一個`switch`分支僅僅包含註釋時,會被報編譯時錯誤。註釋不是代碼語句而且也不能讓`switch`分支達到被忽略的效果。你總是可以使用`break`來忽略某個分支。
|
||||
|
||||
下面的例子通過`switch`來判斷一個`Character`值是否代表下面四種語言之一。為了簡潔,多個值被包含在了同一個分支情況中。
|
||||
|
||||
```swift
|
||||
let numberSymbol: Character = "三" // 簡體中文裡的數字 3
|
||||
var possibleIntegerValue: Int?
|
||||
switch numberSymbol {
|
||||
case "1", "□", "一", "□":
|
||||
possibleIntegerValue = 1
|
||||
case "2", "□", "二", "□":
|
||||
possibleIntegerValue = 2
|
||||
case "3", "□", "三", "□":
|
||||
possibleIntegerValue = 3
|
||||
case "4", "□", "四", "□":
|
||||
possibleIntegerValue = 4
|
||||
default:
|
||||
break
|
||||
}
|
||||
if let integerValue = possibleIntegerValue {
|
||||
println("The integer value of \(numberSymbol) is \(integerValue).")
|
||||
} else {
|
||||
println("An integer value could not be found for \(numberSymbol).")
|
||||
}
|
||||
// 輸出 "The integer value of 三 is 3."
|
||||
```
|
||||
|
||||
這個例子檢查`numberSymbol`是否是拉丁,阿拉伯,中文或者泰語中的`1`到`4`之一。如果被匹配到,該`switch`分支語句給`Int?`類型變量`possibleIntegerValue`設置一個整數值。
|
||||
|
||||
當`switch`代碼塊執行完後,接下來的代碼通過使用可選綁定來判斷`possibleIntegerValue`是否曾經被設置過值。因為是可選類型的緣故,`possibleIntegerValue`有一個隱式的初始值`nil`,所以僅僅當`possibleIntegerValue`曾被`switch`代碼塊的前四個分支中的某個設置過一個值時,可選的綁定將會被判定為成功。
|
||||
|
||||
在上面的例子中,想要把`Character`所有的的可能性都枚舉出來是不現實的,所以使用`default`分支來包含所有上面沒有匹配到字符的情況。由於這個`default`分支不需要執行任何動作,所以它只寫了一條`break`語句。一旦落入到`default`分支中後,`break`語句就完成了該分支的所有代碼操作,代碼繼續向下,開始執行`if let`語句。
|
||||
|
||||
<a name="fallthrough"></a>
|
||||
### 貫穿(Fallthrough)
|
||||
|
||||
Swift 中的`switch`不會從上一個 case 分支落入到下一個 case 分支中。相反,只要第一個匹配到的 case 分支完成了它需要執行的語句,整個`switch`代碼塊完成了它的執行。相比之下,C 語言要求你顯示的插入`break`語句到每個`switch`分支的末尾來阻止自動落入到下一個 case 分支中。Swift 的這種避免默認落入到下一個分支中的特性意味著它的`switch` 功能要比 C 語言的更加清晰和可預測,可以避免無意識地執行多個 case 分支從而引發的錯誤。
|
||||
|
||||
如果你確實需要 C 風格的貫穿(fallthrough)的特性,你可以在每個需要該特性的 case 分支中使用`fallthrough`關鍵字。下面的例子使用`fallthrough`來創建一個數字的描述語句。
|
||||
|
||||
```swift
|
||||
let integerToDescribe = 5
|
||||
var description = "The number \(integerToDescribe) is"
|
||||
switch integerToDescribe {
|
||||
case 2, 3, 5, 7, 11, 13, 17, 19:
|
||||
description += " a prime number, and also"
|
||||
fallthrough
|
||||
default:
|
||||
description += " an integer."
|
||||
}
|
||||
println(description)
|
||||
// 輸出 "The number 5 is a prime number, and also an integer."
|
||||
```
|
||||
|
||||
這個例子定義了一個`String`類型的變量`description`並且給它設置了一個初始值。函數使用`switch`邏輯來判斷`integerToDescribe`變量的值。當`integerToDescribe`的值屬於列表中的質數之一時,該函數添加一段文字在`description`後,來表明這個是數字是一個質數。然後它使用`fallthrough`關鍵字來「貫穿」到`default`分支中。`default`分支添加一段額外的文字在`description`的最後,至此`switch`代碼塊執行完了。
|
||||
|
||||
如果`integerToDescribe`的值不屬於列表中的任何質數,那麼它不會匹配到第一個`switch`分支。而這裡沒有其他特別的分支情況,所以`integerToDescribe`匹配到包含所有的`default`分支中。
|
||||
|
||||
當`switch`代碼塊執行完後,使用`println`函數打印該數字的描述。在這個例子中,數字`5`被準確的識別為了一個質數。
|
||||
|
||||
>注意:
|
||||
`fallthrough`關鍵字不會檢查它下一個將會落入執行的 case 中的匹配條件。`fallthrough`簡單地使代碼執行繼續連接到下一個 case 中的執行代碼,這和 C 語言標準中的`switch`語句特性是一樣的。
|
||||
|
||||
<a name="labeled_statements"></a>
|
||||
### 帶標籤的語句(Labeled Statements)
|
||||
|
||||
在 Swift 中,你可以在循環體和`switch`代碼塊中嵌套循環體和`switch`代碼塊來創造複雜的控制流結構。然而,循環體和`switch`代碼塊兩者都可以使用`break`語句來提前結束整個方法體。因此,顯示地指明`break`語句想要終止的是哪個循環體或者`switch`代碼塊,會很有用。類似地,如果你有許多嵌套的循環體,顯示指明`continue`語句想要影響哪一個循環體也會非常有用。
|
||||
|
||||
為了實現這個目的,你可以使用標籤來標記一個循環體或者`switch`代碼塊,當使用`break`或者`continue`時,帶上這個標籤,可以控制該標籤代表對象的中斷或者執行。
|
||||
|
||||
產生一個帶標籤的語句是通過在該語句的關鍵詞的同一行前面放置一個標籤,並且該標籤後面還需帶著一個冒號。下面是一個`while`循環體的語法,同樣的規則適用於所有的循環體和`switch`代碼塊。
|
||||
|
||||
> `label name`: while `condition` {
|
||||
> `statements`
|
||||
> }
|
||||
|
||||
下面的例子是在一個帶有標籤的`while`循環體中調用`break`和`continue`語句,該循環體是前面章節中_蛇和梯子_的改編版本。這次,遊戲增加了一條額外的規則:
|
||||
|
||||
- 為了獲勝,你必須_剛好_落在第 25 個方塊中。
|
||||
|
||||
如果某次擲骰子使你的移動超出第 25 個方塊,你必須重新擲骰子,直到你擲出的骰子數剛好使你能落在第 25 個方塊中。
|
||||
|
||||
遊戲的棋盤和之前一樣:
|
||||
|
||||

|
||||
|
||||
值`finalSquare`、`board`、`square`和`diceRoll`的初始化也和之前一樣:
|
||||
|
||||
```swift
|
||||
let finalSquare = 25
|
||||
var board = Int[](count: finalSquare + 1, repeatedValue: 0)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
```
|
||||
|
||||
這個版本的遊戲使用`while`循環體和`switch`方法塊來實現遊戲的邏輯。`while`循環體有一個標籤名`gameLoop`,來表明它是蛇與梯子的主循環。
|
||||
|
||||
該`while`循環體的條件判斷語句是`while square !=finalSquare`,這表明你必須剛好落在方格25中。
|
||||
|
||||
```swift
|
||||
gameLoop: while square != finalSquare {
|
||||
if ++diceRoll == 7 { diceRoll = 1 }
|
||||
switch square + diceRoll {
|
||||
case finalSquare:
|
||||
// 到達最後一個方塊,遊戲結束
|
||||
break gameLoop
|
||||
case let newSquare where newSquare > finalSquare:
|
||||
// 超出最後一個方塊,再擲一次骰子
|
||||
continue gameLoop
|
||||
default:
|
||||
// 本次移動有效
|
||||
square += diceRoll
|
||||
square += board[square]
|
||||
}
|
||||
}
|
||||
println("Game over!")
|
||||
```
|
||||
|
||||
每次循環迭代開始時擲骰子。與之前玩家擲完骰子就立即移動不同,這裡使用了`switch`來考慮每次移動可能產生的結果,從而決定玩家本次是否能夠移動。
|
||||
|
||||
- 如果骰子數剛好使玩家移動到最終的方格裡,遊戲結束。`break gameLoop`語句跳轉控制去執行`while`循環體後的第一行代碼,遊戲結束。
|
||||
- 如果骰子數將會使玩家的移動超出最後的方格,那麼這種移動是不合法的,玩家需要重新擲骰子。`continue gameLoop`語句結束本次`while`循環的迭代,開始下一次循環迭代。
|
||||
- 在剩餘的所有情況中,骰子數產生的都是合法的移動。玩家向前移動骰子數個方格,然後遊戲邏輯再處理玩家當前是否處於蛇頭或者梯子的底部。本次循環迭代結束,控制跳轉到`while`循環體的條件判斷語句處,再決定是否能夠繼續執行下次循環迭代。
|
||||
|
||||
>注意:
|
||||
如果上述的`break`語句沒有使用`gameLoop`標籤,那麼它將會中斷`switch`代碼塊而不是`while`循環體。使用`gameLoop`標籤清晰的表明了`break`想要中斷的是哪個代碼塊。
|
||||
同時請注意,當調用`continue gameLoop`去跳轉到下一次循環迭代時,這裡使用`gameLoop`標籤並不是嚴格必須的。因為在這個遊戲中,只有一個循環體,所以`continue`語句會影響到哪個循環體是沒有歧義的。然而,`continue`語句使用`gameLoop`標籤也是沒有危害的。這樣做符合標籤的使用規則,同時參照旁邊的`break gameLoop`,能夠使遊戲的邏輯更加清晰和易於理解。
|
573
source-tw/chapter2/06_Functions.md
Normal file
573
source-tw/chapter2/06_Functions.md
Normal file
@ -0,0 +1,573 @@
|
||||
> 翻譯:[honghaoz](https://github.com/honghaoz)
|
||||
> 校對:[LunaticM](https://github.com/LunaticM)
|
||||
|
||||
# 函數(Functions)
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [函數定義與調用(Defining and Calling Functions)](#Defining_and_Calling_Functions)
|
||||
- [函數參數與返回值(Function Parameters and Return Values)](#Function_Parameters_and_Return_Values)
|
||||
- [函數參數名稱(Function Parameter Names)](#Function_Parameter_Names)
|
||||
- [函數類型(Function Types)](#Function_Types)
|
||||
- [函數嵌套(Nested Functions)](#Nested_Functions)
|
||||
|
||||
函數是用來完成特定任務的獨立的代碼塊。你給一個函數起一個合適的名字,用來標識函數做什麼,並且當函數需要執行的時候,這個名字會被「調用」。
|
||||
|
||||
Swift 統一的函數語法足夠靈活,可以用來表示任何函數,包括從最簡單的沒有參數名字的 C 風格函數,到複雜的帶局部和外部參數名的 Objective-C 風格函數。參數可以提供默認值,以簡化函數調用。參數也可以既當做傳入參數,也當做傳出參數,也就是說,一旦函數執行結束,傳入的參數值可以被修改。
|
||||
|
||||
在 Swift 中,每個函數都有一種類型,包括函數的參數值類型和返回值類型。你可以把函數類型當做任何其他普通變量類型一樣處理,這樣就可以更簡單地把函數當做別的函數的參數,也可以從其他函數中返回函數。函數的定義可以寫在在其他函數定義中,這樣可以在嵌套函數範圍內實現功能封裝。
|
||||
|
||||
<a name="Defining_and_Calling_Functions"></a>
|
||||
## 函數的定義與調用(Defining and Calling Functions)
|
||||
|
||||
當你定義一個函數時,你可以定義一個或多個有名字和類型的值,作為函數的輸入(稱為參數,parameters),也可以定義某種類型的值作為函數執行結束的輸出(稱為返回類型)。
|
||||
|
||||
每個函數有個函數名,用來描述函數執行的任務。要使用一個函數時,你用函數名「調用」,並傳給它匹配的輸入值(稱作實參,arguments)。一個函數的實參必須與函數參數表裡參數的順序一致。
|
||||
|
||||
在下面例子中的函數叫做`"greetingForPerson"`,之所以叫這個名字是因為這個函數用一個人的名字當做輸入,並返回給這個人的問候語。為了完成這個任務,你定義一個輸入參數-一個叫做 `personName` 的 `String` 值,和一個包含給這個人問候語的 `String` 類型的返回值:
|
||||
|
||||
```swift
|
||||
func sayHello(personName: String) -> String {
|
||||
let greeting = "Hello, " + personName + "!"
|
||||
return greeting
|
||||
}
|
||||
```
|
||||
|
||||
所有的這些信息匯總起來成為函數的定義,並以 `func` 作為前綴。指定函數返回類型時,用返回箭頭 `->`(一個連字符後跟一個右尖括號)後跟返回類型的名稱的方式來表示。
|
||||
|
||||
該定義描述了函數做什麼,它期望接收什麼和執行結束時它返回的結果是什麼。這樣的定義使的函數可以在別的地方以一種清晰的方式被調用:
|
||||
|
||||
```swift
|
||||
println(sayHello("Anna"))
|
||||
// prints "Hello, Anna!"
|
||||
println(sayHello("Brian"))
|
||||
// prints "Hello, Brian!"
|
||||
```
|
||||
|
||||
調用 `sayHello` 函數時,在圓括號中傳給它一個 `String` 類型的實參。因為這個函數返回一個 `String` 類型的值,`sayHello` 可以被包含在 `println` 的調用中,用來輸出這個函數的返回值,正如上面所示。
|
||||
|
||||
在 `sayHello` 的函數體中,先定義了一個新的名為 `greeting` 的 `String` 常量,同時賦值了給 `personName` 的一個簡單問候消息。然後用 `return` 關鍵字把這個問候返回出去。一旦 `return greeting` 被調用,該函數結束它的執行並返回 `greeting` 的當前值。
|
||||
|
||||
你可以用不同的輸入值多次調用 `sayHello`。上面的例子展示的是用`"Anna"`和`"Brian"`調用的結果,該函數分別返回了不同的結果。
|
||||
|
||||
為了簡化這個函數的定義,可以將問候消息的創建和返回寫成一句:
|
||||
|
||||
```swift
|
||||
func sayHelloAgain(personName: String) -> String {
|
||||
return "Hello again, " + personName + "!"
|
||||
}
|
||||
println(sayHelloAgain("Anna"))
|
||||
// prints "Hello again, Anna!"
|
||||
```
|
||||
|
||||
<a name="Function_Parameters_and_Return_Values"></a>
|
||||
## 函數參數與返回值(Function Parameters and Return Values)
|
||||
|
||||
函數參數與返回值在Swift中極為靈活。你可以定義任何類型的函數,包括從只帶一個未名參數的簡單函數到複雜的帶有表達性參數名和不同參數選項的複雜函數。
|
||||
|
||||
### 多重輸入參數(Multiple Input Parameters)
|
||||
|
||||
函數可以有多個輸入參數,寫在圓括號中,用逗號分隔。
|
||||
|
||||
下面這個函數用一個半開區間的開始點和結束點,計算出這個範圍內包含多少數字:
|
||||
|
||||
```swift
|
||||
func halfOpenRangeLength(start: Int, end: Int) -> Int {
|
||||
return end - start
|
||||
}
|
||||
println(halfOpenRangeLength(1, 10))
|
||||
// prints "9"
|
||||
```
|
||||
|
||||
### 無參函數(Functions Without Parameters)
|
||||
|
||||
函數可以沒有參數。下面這個函數就是一個無參函數,當被調用時,它返回固定的 `String` 消息:
|
||||
|
||||
```swift
|
||||
func sayHelloWorld() -> String {
|
||||
return "hello, world"
|
||||
}
|
||||
println(sayHelloWorld())
|
||||
// prints "hello, world"
|
||||
```
|
||||
|
||||
儘管這個函數沒有參數,但是定義中在函數名後還是需要一對圓括號。當被調用時,也需要在函數名後寫一對圓括號。
|
||||
|
||||
### 無返回值函數(Functions Without Return Values)
|
||||
|
||||
函數可以沒有返回值。下面是 `sayHello` 函數的另一個版本,叫 `waveGoodbye`,這個函數直接輸出 `String` 值,而不是返回它:
|
||||
|
||||
```swift
|
||||
func sayGoodbye(personName: String) {
|
||||
println("Goodbye, \(personName)!")
|
||||
}
|
||||
sayGoodbye("Dave")
|
||||
// prints "Goodbye, Dave!"
|
||||
```
|
||||
|
||||
因為這個函數不需要返回值,所以這個函數的定義中沒有返回箭頭(->)和返回類型。
|
||||
|
||||
> 注意:
|
||||
> 嚴格上來說,雖然沒有返回值被定義,`sayGoodbye` 函數依然返回了值。沒有定義返回類型的函數會返回特殊的值,叫 `Void`。它其實是一個空的元組(tuple),沒有任何元素,可以寫成`()`。
|
||||
|
||||
被調用時,一個函數的返回值可以被忽略:
|
||||
|
||||
```swift
|
||||
func printAndCount(stringToPrint: String) -> Int {
|
||||
println(stringToPrint)
|
||||
return countElements(stringToPrint)
|
||||
}
|
||||
func printWithoutCounting(stringToPrint: String) {
|
||||
printAndCount(stringToPrint)
|
||||
}
|
||||
printAndCount("hello, world")
|
||||
// prints "hello, world" and returns a value of 12
|
||||
printWithoutCounting("hello, world")
|
||||
// prints "hello, world" but does not return a value
|
||||
|
||||
```
|
||||
|
||||
第一個函數 `printAndCount`,輸出一個字符串並返回 `Int` 類型的字符數。第二個函數 `printWithoutCounting`調用了第一個函數,但是忽略了它的返回值。當第二個函數被調用時,消息依然會由第一個函數輸出,但是返回值不會被用到。
|
||||
|
||||
> 注意:
|
||||
> 返回值可以被忽略,但定義了有返回值的函數必須返回一個值,如果在函數定義底部沒有返回任何值,這將導致編譯錯誤(compile-time error)。
|
||||
|
||||
### 多重返回值函數(Functions with Multiple Return Values)
|
||||
|
||||
你可以用元組(tuple)類型讓多個值作為一個復合值從函數中返回。
|
||||
|
||||
下面的這個例子中,`count` 函數用來計算一個字符串中元音,輔音和其他字母的個數(基於美式英語的標準)。
|
||||
|
||||
```swift
|
||||
func count(string: String) -> (vowels: Int, consonants: Int, others: Int) {
|
||||
var vowels = 0, consonants = 0, others = 0
|
||||
for character in string {
|
||||
switch String(character).lowercaseString {
|
||||
case "a", "e", "i", "o", "u":
|
||||
++vowels
|
||||
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
|
||||
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
|
||||
++consonants
|
||||
default:
|
||||
++others
|
||||
}
|
||||
}
|
||||
return (vowels, consonants, others)
|
||||
}
|
||||
```
|
||||
|
||||
你可以用 `count` 函數來處理任何一個字符串,返回的值將是一個包含三個 `Int` 型值的元組(tuple):
|
||||
|
||||
```swift
|
||||
let total = count("some arbitrary string!")
|
||||
println("\(total.vowels) vowels and \(total.consonants) consonants")
|
||||
// prints "6 vowels and 13 consonants"
|
||||
```
|
||||
|
||||
需要注意的是,元組的成員不需要在函數中返回時命名,因為它們的名字已經在函數返回類型中有了定義。
|
||||
|
||||
<a name="Function_Parameter_Names"></a>
|
||||
## 函數參數名稱(Function Parameter Names)
|
||||
|
||||
以上所有的函數都給它們的參數定義了`參數名(parameter name)`:
|
||||
|
||||
```swift
|
||||
func someFunction(parameterName: Int) {
|
||||
// function body goes here, and can use parameterName
|
||||
// to refer to the argument value for that parameter
|
||||
}
|
||||
```
|
||||
|
||||
但是,這些參數名僅在函數體中使用,不能在函數調用時使用。這種類型的參數名被稱作`局部參數名(local parameter name)`,因為它們只能在函數體中使用。
|
||||
|
||||
### 外部參數名(External Parameter Names)
|
||||
|
||||
有時候,調用函數時,給每個參數命名是非常有用的,因為這些參數名可以指出各個實參的用途是什麼。
|
||||
|
||||
如果你希望函數的使用者在調用函數時提供參數名字,那就需要給每個參數除了局部參數名外再定義一個`外部參數名`。外部參數名寫在局部參數名之前,用空格分隔。
|
||||
|
||||
```swift
|
||||
func someFunction(externalParameterName localParameterName: Int) {
|
||||
// function body goes here, and can use localParameterName
|
||||
// to refer to the argument value for that parameter
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 如果你提供了外部參數名,那麼函數在被調用時,必須使用外部參數名。
|
||||
|
||||
以下是個例子,這個函數使用一個`結合者(joiner)`把兩個字符串聯在一起:
|
||||
|
||||
```swift
|
||||
func join(s1: String, s2: String, joiner: String) -> String {
|
||||
return s1 + joiner + s2
|
||||
}
|
||||
```
|
||||
|
||||
當你調用這個函數時,這三個字符串的用途是不清楚的:
|
||||
|
||||
```swift
|
||||
join("hello", "world", ", ")
|
||||
// returns "hello, world"
|
||||
```
|
||||
|
||||
為了讓這些字符串的用途更為明顯,我們為 `join` 函數添加外部參數名:
|
||||
|
||||
```swift
|
||||
func join(string s1: String, toString s2: String, withJoiner joiner: String) -> String {
|
||||
return s1 + joiner + s2
|
||||
}
|
||||
```
|
||||
|
||||
在這個版本的 `join` 函數中,第一個參數有一個叫 `string` 的外部參數名和 `s1` 的局部參數名,第二個參數有一個叫 `toString` 的外部參數名和 `s2` 的局部參數名,第三個參數有一個叫 `withJoiner` 的外部參數名和 `joiner` 的局部參數名。
|
||||
|
||||
現在,你可以使用這些外部參數名以一種清晰地方式來調用函數了:
|
||||
|
||||
```swift
|
||||
join(string: "hello", toString: "world", withJoiner: ", ")
|
||||
// returns "hello, world"
|
||||
```
|
||||
|
||||
使用外部參數名讓第二個版本的 `join` 函數的調用更為有表現力,更為通順,同時還保持了函數體是可讀的和有明確意圖的。
|
||||
|
||||
> 注意:
|
||||
> 當其他人在第一次讀你的代碼,函數參數的意圖顯得不明顯時,考慮使用外部參數名。如果函數參數名的意圖是很明顯的,那就不需要定義外部參數名了。
|
||||
|
||||
### 簡寫外部參數名(Shorthand External Parameter Names)
|
||||
|
||||
如果你需要提供外部參數名,但是局部參數名已經定義好了,那麼你不需要寫兩次參數名。相反,只寫一次參數名,並用`井號(#)`作為前綴就可以了。這告訴 Swift 使用這個參數名作為局部和外部參數名。
|
||||
|
||||
下面這個例子定義了一個叫 `containsCharacter` 的函數,使用`井號(#)`的方式定義了外部參數名:
|
||||
|
||||
```swift
|
||||
func containsCharacter(#string: String, #characterToFind: Character) -> Bool {
|
||||
for character in string {
|
||||
if character == characterToFind {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
這樣定義參數名,使得函數體更為可讀,清晰,同時也可以以一個不含糊的方式被調用:
|
||||
|
||||
```swift
|
||||
let containsAVee = containsCharacter(string: "aardvark", characterToFind: "v")
|
||||
// containsAVee equals true, because "aardvark" contains a "v」
|
||||
```
|
||||
|
||||
### 默認參數值(Default Parameter Values)
|
||||
|
||||
你可以在函數體中為每個參數定義`默認值`。當默認值被定義後,調用這個函數時可以忽略這個參數。
|
||||
|
||||
> 注意:
|
||||
> 將帶有默認值的參數放在函數參數列表的最後。這樣可以保證在函數調用時,非默認參數的順序是一致的,同時使得相同的函數在不同情況下調用時顯得更為清晰。
|
||||
|
||||
以下是另一個版本的`join`函數,其中`joiner`有了默認參數值:
|
||||
|
||||
```swift
|
||||
func join(string s1: String, toString s2: String, withJoiner joiner: String = " ") -> String {
|
||||
return s1 + joiner + s2
|
||||
}
|
||||
```
|
||||
|
||||
像第一個版本的 `join` 函數一樣,如果 `joiner` 被賦值時,函數將使用這個字符串值來連接兩個字符串:
|
||||
|
||||
```swift
|
||||
join(string: "hello", toString: "world", withJoiner: "-")
|
||||
// returns "hello-world"
|
||||
```
|
||||
|
||||
當這個函數被調用時,如果 `joiner` 的值沒有被指定,函數會使用默認值(" "):
|
||||
|
||||
```swift
|
||||
join(string: "hello", toString:"world")
|
||||
// returns "hello world"
|
||||
```
|
||||
|
||||
### 默認值參數的外部參數名(External Names for Parameters with Default Values)
|
||||
|
||||
在大多數情況下,給帶默認值的參數起一個外部參數名是很有用的。這樣可以保證當函數被調用且帶默認值的參數被提供值時,實參的意圖是明顯的。
|
||||
|
||||
為了使定義外部參數名更加簡單,當你未給帶默認值的參數提供外部參數名時,Swift 會自動提供外部名字。此時外部參數名與局部名字是一樣的,就像你已經在局部參數名前寫了`井號(#)`一樣。
|
||||
|
||||
下面是 `join` 函數的另一個版本,這個版本中並沒有為它的參數提供外部參數名,但是 `joiner` 參數依然有外部參數名:
|
||||
|
||||
```swift
|
||||
func join(s1: String, s2: String, joiner: String = " ") -> String {
|
||||
return s1 + joiner + s2
|
||||
}
|
||||
```
|
||||
|
||||
在這個例子中,Swift 自動為 `joiner` 提供了外部參數名。因此,當函數調用時,外部參數名必須使用,這樣使得參數的用途變得清晰。
|
||||
|
||||
```swift
|
||||
join("hello", "world", joiner: "-")
|
||||
// returns "hello-world"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 你可以使用`下劃線(_)`作為默認值參數的外部參數名,這樣可以在調用時不用提供外部參數名。但是給帶默認值的參數命名總是更加合適的。
|
||||
|
||||
### 可變參數(Variadic Parameters)
|
||||
|
||||
一個`可變參數(variadic parameter)`可以接受一個或多個值。函數調用時,你可以用可變參數來傳入不確定數量的輸入參數。通過在變量類型名後面加入`(...)`的方式來定義可變參數。
|
||||
|
||||
傳入可變參數的值在函數體內當做這個類型的一個數組。例如,一個叫做 `numbers` 的 `Double...` 型可變參數,在函數體內可以當做一個叫 `numbers` 的 `Double[]` 型的數組常量。
|
||||
|
||||
下面的這個函數用來計算一組任意長度數字的算術平均數:
|
||||
|
||||
```swift
|
||||
func arithmeticMean(numbers: Double...) -> Double {
|
||||
var total: Double = 0
|
||||
for number in numbers {
|
||||
total += number
|
||||
}
|
||||
return total / Double(numbers.count)
|
||||
}
|
||||
arithmeticMean(1, 2, 3, 4, 5)
|
||||
// returns 3.0, which is the arithmetic mean of these five numbers
|
||||
arithmeticMean(3, 8, 19)
|
||||
// returns 10.0, which is the arithmetic mean of these three numbers
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 一個函數至多能有一個可變參數,而且它必須是參數表中最後的一個。這樣做是為了避免函數調用時出現歧義。
|
||||
|
||||
如果函數有一個或多個帶默認值的參數,而且還有一個可變參數,那麼把可變參數放在參數表的最後。
|
||||
|
||||
### 常量參數和變量參數(Constant and Variable Parameters)
|
||||
|
||||
函數參數默認是常量。試圖在函數體中更改參數值將會導致編譯錯誤。這意味著你不能錯誤地更改參數值。
|
||||
|
||||
但是,有時候,如果函數中有傳入參數的變量值副本將是很有用的。你可以通過指定一個或多個參數為變量參數,從而避免自己在函數中定義新的變量。變量參數不是常量,你可以在函數中把它當做新的可修改副本來使用。
|
||||
|
||||
通過在參數名前加關鍵字 `var` 來定義變量參數:
|
||||
|
||||
```swift
|
||||
func alignRight(var string: String, count: Int, pad: Character) -> String {
|
||||
let amountToPad = count - countElements(string)
|
||||
for _ in 1...amountToPad {
|
||||
string = pad + string
|
||||
}
|
||||
return string
|
||||
}
|
||||
let originalString = "hello"
|
||||
let paddedString = alignRight(originalString, 10, "-")
|
||||
// paddedString is equal to "-----hello"
|
||||
// originalString is still equal to "hello"
|
||||
```
|
||||
|
||||
這個例子中定義了一個新的叫做 `alignRight` 的函數,用來右對齊輸入的字符串到一個長的輸出字符串中。左側空餘的地方用指定的填充字符填充。這個例子中,字符串`"hello"`被轉換成了`"-----hello"`。
|
||||
|
||||
`alignRight` 函數將參數 `string` 定義為變量參數。這意味著 `string` 現在可以作為一個局部變量,用傳入的字符串值初始化,並且可以在函數體中進行操作。
|
||||
|
||||
該函數首先計算出多少個字符需要被添加到 `string` 的左邊,以右對齊到總的字符串中。這個值存在局部常量 `amountToPad` 中。這個函數然後將 `amountToPad` 多的填充(pad)字符填充到 `string` 左邊,並返回結果。它使用了 `string` 這個變量參數來進行所有字符串操作。
|
||||
|
||||
> 注意:
|
||||
> 對變量參數所進行的修改在函數調用結束後便消失了,並且對於函數體外是不可見的。變量參數僅僅存在於函數調用的生命週期中。
|
||||
|
||||
### 輸入輸出參數(In-Out Parameters)
|
||||
|
||||
變量參數,正如上面所述,僅僅能在函數體內被更改。如果你想要一個函數可以修改參數的值,並且想要在這些修改在函數調用結束後仍然存在,那麼就應該把這個參數定義為輸入輸出參數(In-Out Parameters)。
|
||||
|
||||
定義一個輸入輸出參數時,在參數定義前加 `inout` 關鍵字。一個輸入輸出參數有傳入函數的值,這個值被函數修改,然後被傳出函數,替換原來的值。
|
||||
|
||||
你只能傳入一個變量作為輸入輸出參數。你不能傳入常量或者字面量(literal value),因為這些量是不能被修改的。當傳入的參數作為輸入輸出參數時,需要在參數前加`&`符,表示這個值可以被函數修改。
|
||||
|
||||
> 注意:
|
||||
> 輸入輸出參數不能有默認值,而且可變參數不能用 `inout` 標記。如果你用 `inout` 標記一個參數,這個參數不能被 `var` 或者 `let` 標記。
|
||||
|
||||
下面是例子,`swapTwoInts` 函數,有兩個分別叫做 `a` 和 `b` 的輸入輸出參數:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(inout a: Int, inout b: Int) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
這個 `swapTwoInts` 函數僅僅交換 `a` 與 `b` 的值。該函數先將 `a` 的值存到一個暫時常量 `temporaryA` 中,然後將 `b` 的值賦給 `a`,最後將 `temporaryA` 幅值給 `b`。
|
||||
|
||||
你可以用兩個 `Int` 型的變量來調用 `swapTwoInts`。需要注意的是,`someInt` 和 `anotherInt` 在傳入 `swapTwoInts` 函數前,都加了 `&` 的前綴:
|
||||
|
||||
```swift
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoInts(&someInt, &anotherInt)
|
||||
println("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
||||
// prints "someInt is now 107, and anotherInt is now 3」
|
||||
```
|
||||
|
||||
從上面這個例子中,我們可以看到 `someInt` 和 `anotherInt` 的原始值在 `swapTwoInts` 函數中被修改,儘管它們的定義在函數體外。
|
||||
|
||||
> 注意:
|
||||
> 輸出輸出參數和返回值是不一樣的。上面的 `swapTwoInts` 函數並沒有定義任何返回值,但仍然修改了 `someInt` 和 `anotherInt` 的值。輸入輸出參數是函數對函數體外產生影響的另一種方式。
|
||||
|
||||
<a name="Function_Types"></a>
|
||||
## 函數類型(Function Types)
|
||||
|
||||
每個函數都有種特定的函數類型,由函數的參數類型和返回類型組成。
|
||||
|
||||
例如:
|
||||
|
||||
```swift
|
||||
func addTwoInts(a: Int, b: Int) -> Int {
|
||||
return a + b
|
||||
}
|
||||
func multiplyTwoInts(a: Int, b: Int) -> Int {
|
||||
return a * b
|
||||
}
|
||||
```
|
||||
|
||||
這個例子中定義了兩個簡單的數學函數:`addTwoInts` 和 `multiplyTwoInts`。這兩個函數都傳入兩個 `Int` 類型, 返回一個合適的`Int`值。
|
||||
|
||||
這兩個函數的類型是 `(Int, Int) -> Int`,可以讀作「這個函數類型,它有兩個 `Int` 型的參數並返回一個 `Int` 型的值。」。
|
||||
|
||||
下面是另一個例子,一個沒有參數,也沒有返回值的函數:
|
||||
|
||||
```swift
|
||||
func printHelloWorld() {
|
||||
println("hello, world")
|
||||
}
|
||||
```
|
||||
|
||||
這個函數的類型是:`() -> ()`,或者叫「沒有參數,並返回 `Void` 類型的函數」。沒有指定返回類型的函數總返回 `Void`。在Swift中,`Void` 與空的元組是一樣的。
|
||||
|
||||
### 使用函數類型(Using Function Types)
|
||||
|
||||
在 Swift 中,使用函數類型就像使用其他類型一樣。例如,你可以定義一個類型為函數的常量或變量,並將函數賦值給它:
|
||||
|
||||
```swift
|
||||
var mathFunction: (Int, Int) -> Int = addTwoInts
|
||||
```
|
||||
|
||||
這個可以讀作:
|
||||
|
||||
「定義一個叫做 `mathFunction` 的變量,類型是『一個有兩個 `Int` 型的參數並返回一個 `Int` 型的值的函數』,並讓這個新變量指向 `addTwoInts` 函數」。
|
||||
|
||||
`addTwoInts` 和 `mathFunction` 有同樣的類型,所以這個賦值過程在 Swift 類型檢查中是允許的。
|
||||
|
||||
現在,你可以用 `mathFunction` 來調用被賦值的函數了:
|
||||
|
||||
```swift
|
||||
println("Result: \(mathFunction(2, 3))")
|
||||
// prints "Result: 5"
|
||||
```
|
||||
|
||||
有相同匹配類型的不同函數可以被賦值給同一個變量,就像非函數類型的變量一樣:
|
||||
|
||||
```swift
|
||||
mathFunction = multiplyTwoInts
|
||||
println("Result: \(mathFunction(2, 3))")
|
||||
// prints "Result: 6"
|
||||
```
|
||||
|
||||
就像其他類型一樣,當賦值一個函數給常量或變量時,你可以讓 Swift 來推斷其函數類型:
|
||||
|
||||
```swift
|
||||
let anotherMathFunction = addTwoInts
|
||||
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
|
||||
```
|
||||
|
||||
### 函數類型作為參數類型(Function Types as Parameter Types)
|
||||
|
||||
你可以用`(Int, Int) -> Int`這樣的函數類型作為另一個函數的參數類型。這樣你可以將函數的一部分實現交由給函數的調用者。
|
||||
|
||||
下面是另一個例子,正如上面的函數一樣,同樣是輸出某種數學運算結果:
|
||||
|
||||
```swift
|
||||
func printMathResult(mathFunction: (Int, Int) -> Int, a: Int, b: Int) {
|
||||
println("Result: \(mathFunction(a, b))")
|
||||
}
|
||||
printMathResult(addTwoInts, 3, 5)
|
||||
// prints "Result: 8」
|
||||
```
|
||||
|
||||
這個例子定義了 `printMathResult` 函數,它有三個參數:第一個參數叫 `mathFunction`,類型是`(Int, Int) -> Int`,你可以傳入任何這種類型的函數;第二個和第三個參數叫 `a` 和 `b`,它們的類型都是 `Int`,這兩個值作為已給的函數的輸入值。
|
||||
|
||||
當 `printMathResult` 被調用時,它被傳入 `addTwoInts` 函數和整數`3`和`5`。它用傳入`3`和`5`調用 `addTwoInts`,並輸出結果:`8`。
|
||||
|
||||
`printMathResult` 函數的作用就是輸出另一個合適類型的數學函數的調用結果。它不關心傳入函數是如何實現的,它只關心這個傳入的函數類型是正確的。這使得 `printMathResult` 可以以一種類型安全(type-safe)的方式來保證傳入函數的調用是正確的。
|
||||
|
||||
### 函數類型作為返回類型(Function Type as Return Types)
|
||||
|
||||
你可以用函數類型作為另一個函數的返回類型。你需要做的是在返回箭頭(`->`)後寫一個完整的函數類型。
|
||||
|
||||
下面的這個例子中定義了兩個簡單函數,分別是 `stepForward` 和`stepBackward`。`stepForward` 函數返回一個比輸入值大一的值。`stepBackward` 函數返回一個比輸入值小一的值。這兩個函數的類型都是 `(Int) -> Int`:
|
||||
|
||||
```swift
|
||||
func stepForward(input: Int) -> Int {
|
||||
return input + 1
|
||||
}
|
||||
func stepBackward(input: Int) -> Int {
|
||||
return input - 1
|
||||
}
|
||||
```
|
||||
|
||||
下面這個叫做 `chooseStepFunction` 的函數,它的返回類型是 `(Int) -> Int` 的函數。`chooseStepFunction` 根據布爾值 `backwards` 來返回 `stepForward` 函數或 `stepBackward` 函數:
|
||||
|
||||
```swift
|
||||
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
|
||||
return backwards ? stepBackward : stepForward
|
||||
}
|
||||
```
|
||||
|
||||
你現在可以用 `chooseStepFunction` 來獲得一個函數,不管是那個方向:
|
||||
|
||||
```swift
|
||||
var currentValue = 3
|
||||
let moveNearerToZero = chooseStepFunction(currentValue > 0)
|
||||
// moveNearerToZero now refers to the stepBackward() function
|
||||
```
|
||||
|
||||
上面這個例子中計算出從 `currentValue` 逐漸接近到`0`是需要向正數走還是向負數走。`currentValue` 的初始值是`3`,這意味著 `currentValue > 0` 是真的(`true`),這將使得 `chooseStepFunction` 返回 `stepBackward` 函數。一個指向返回的函數的引用保存在了 `moveNearerToZero` 常量中。
|
||||
|
||||
現在,`moveNearerToZero` 指向了正確的函數,它可以被用來數到`0`:
|
||||
|
||||
```swift
|
||||
println("Counting to zero:")
|
||||
// Counting to zero:
|
||||
while currentValue != 0 {
|
||||
println("\(currentValue)... ")
|
||||
currentValue = moveNearerToZero(currentValue)
|
||||
}
|
||||
println("zero!")
|
||||
// 3...
|
||||
// 2...
|
||||
// 1...
|
||||
// zero!
|
||||
```
|
||||
|
||||
<a name="Nested_Functions"></a>
|
||||
## 嵌套函數(Nested Functions)
|
||||
|
||||
這章中你所見到的所有函數都叫全局函數(global functions),它們定義在全局域中。你也可以把函數定義在別的函數體中,稱作嵌套函數(nested functions)。
|
||||
|
||||
默認情況下,嵌套函數是對外界不可見的,但是可以被他們封閉函數(enclosing function)來調用。一個封閉函數也可以返回它的某一個嵌套函數,使得這個函數可以在其他域中被使用。
|
||||
|
||||
你可以用返回嵌套函數的方式重寫 `chooseStepFunction` 函數:
|
||||
|
||||
```swift
|
||||
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
|
||||
func stepForward(input: Int) -> Int { return input + 1 }
|
||||
func stepBackward(input: Int) -> Int { return input - 1 }
|
||||
return backwards ? stepBackward : stepForward
|
||||
}
|
||||
var currentValue = -4
|
||||
let moveNearerToZero = chooseStepFunction(currentValue > 0)
|
||||
// moveNearerToZero now refers to the nested stepForward() function
|
||||
while currentValue != 0 {
|
||||
println("\(currentValue)... ")
|
||||
currentValue = moveNearerToZero(currentValue)
|
||||
}
|
||||
println("zero!")
|
||||
// -4...
|
||||
// -3...
|
||||
// -2...
|
||||
// -1...
|
||||
// zero!
|
||||
```
|
375
source-tw/chapter2/07_Closures.md
Normal file
375
source-tw/chapter2/07_Closures.md
Normal file
@ -0,0 +1,375 @@
|
||||
> 翻譯:[wh1100717](https://github.com/wh1100717)
|
||||
> 校對:[lyuka](https://github.com/lyuka)
|
||||
|
||||
# 閉包(Closures)
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [閉包表達式(Closure Expressions)](#closure_expressions)
|
||||
- [尾隨閉包(Trailing Closures)](#trailing_closures)
|
||||
- [值捕獲(Capturing Values)](#capturing_values)
|
||||
- [閉包是引用類型(Closures Are Reference Types)](#closures_are_reference_types)
|
||||
|
||||
閉包是自包含的函數代碼塊,可以在代碼中被傳遞和使用。
|
||||
Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的 lambdas 函數比較相似。
|
||||
|
||||
閉包可以捕獲和存儲其所在上下文中任意常量和變量的引用。
|
||||
這就是所謂的閉合併包裹著這些常量和變量,俗稱閉包。Swift 會為您管理在捕獲過程中涉及到的所有內存操作。
|
||||
|
||||
> 注意:
|
||||
> 如果您不熟悉捕獲(capturing)這個概念也不用擔心,您可以在 [值捕獲](#capturing_values) 章節對其進行詳細瞭解。
|
||||
|
||||
在[函數](../chapter2/06_Functions.html) 章節中介紹的全局和嵌套函數實際上也是特殊的閉包,閉包採取如下三種形式之一:
|
||||
|
||||
* 全局函數是一個有名字但不會捕獲任何值的閉包
|
||||
* 嵌套函數是一個有名字並可以捕獲其封閉函數域內值的閉包
|
||||
* 閉包表達式是一個利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包
|
||||
|
||||
Swift 的閉包表達式擁有簡潔的風格,並鼓勵在常見場景中進行語法優化,主要優化如下:
|
||||
|
||||
* 利用上下文推斷參數和返回值類型
|
||||
* 隱式返回單表達式閉包,即單表達式閉包可以省略`return`關鍵字
|
||||
* 參數名稱縮寫
|
||||
* 尾隨(Trailing)閉包語法
|
||||
|
||||
<a name="closure_expressions"></a>
|
||||
## 閉包表達式(Closure Expressions)
|
||||
|
||||
|
||||
[嵌套函數](../chapter2/06_Functions.html#nested_function) 是一個在較複雜函數中方便進行命名和定義自包含代碼模塊的方式。當然,有時候撰寫小巧的沒有完整定義和命名的類函數結構也是很有用處的,尤其是在您處理一些函數並需要將另外一些函數作為該函數的參數時。
|
||||
|
||||
閉包表達式是一種利用簡潔語法構建內聯閉包的方式。
|
||||
閉包表達式提供了一些語法優化,使得撰寫閉包變得簡單明瞭。
|
||||
下面閉包表達式的例子通過使用幾次迭代展示了`sort`函數定義和語法優化的方式。
|
||||
每一次迭代都用更簡潔的方式描述了相同的功能。
|
||||
|
||||
<a name="the_sort_function"></a>
|
||||
### sort 函數(The Sort Function)
|
||||
|
||||
Swift 標準庫提供了`sort`函數,會根據您提供的基於輸出類型排序的閉包涵數將已知類型數組中的值進行排序。
|
||||
一旦排序完成,函數會返回一個與原數組大小相同的新數組,該數組中包含已經正確排序的同類型元素。
|
||||
|
||||
下面的閉包表達式示例使用`sort`函數對一個`String`類型的數組進行字母逆序排序,以下是初始數組值:
|
||||
|
||||
```swift
|
||||
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
|
||||
```
|
||||
|
||||
`sort`函數需要傳入兩個參數:
|
||||
|
||||
* 已知類型的數組
|
||||
* 閉包涵數,該閉包涵數需要傳入與數組類型相同的兩個值,並返回一個布爾類型值來告訴`sort`函數當排序結束後傳入的第一個參數排在第二個參數前面還是後面。如果第一個參數值出現在第二個參數值前面,排序閉包涵數需要返回`true`,反之返回`false`。
|
||||
|
||||
該例子對一個`String`類型的數組進行排序,因此排序閉包涵數類型需為`(String, String) -> Bool`。
|
||||
|
||||
提供排序閉包涵數的一種方式是撰寫一個符合其類型要求的普通函數,並將其作為`sort`函數的第二個參數傳入:
|
||||
|
||||
```swift
|
||||
func backwards(s1: String, s2: String) -> Bool {
|
||||
return s1 > s2
|
||||
}
|
||||
var reversed = sort(names, backwards)
|
||||
// reversed 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
|
||||
```
|
||||
|
||||
如果第一個字符串 (`s1`) 大於第二個字符串 (`s2`),`backwards`函數返回`true`,表示在新的數組中`s1`應該出現在`s2`前。
|
||||
對於字符串中的字符來說,「大於」 表示 「按照字母順序較晚出現」。
|
||||
這意味著字母`"B"`大於字母`"A"`,字符串`"Tom"`大於字符串`"Tim"`。
|
||||
其將進行字母逆序排序,`"Barry"`將會排在`"Alex"`之後。
|
||||
|
||||
然而,這是一個相當冗長的方式,本質上只是寫了一個單表達式函數 (a > b)。
|
||||
在下面的例子中,利用閉合表達式語法可以更好的構造一個內聯排序閉包。
|
||||
|
||||
<a name="closure_expression_syntax"></a>
|
||||
### 閉包表達式語法(Closure Expression Syntax)
|
||||
|
||||
閉包表達式語法有如下一般形式:
|
||||
|
||||
```swift
|
||||
{ (parameters) -> returnType in
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
閉包表達式語法可以使用常量、變量和`inout`類型作為參數,不提供默認值。
|
||||
也可以在參數列表的最後使用可變參數。
|
||||
元組也可以作為參數和返回值。
|
||||
|
||||
下面的例子展示了之前`backwards`函數對應的閉包表達式版本的代碼:
|
||||
|
||||
```swift
|
||||
reversed = sort(names, { (s1: String, s2: String) -> Bool in
|
||||
return s1 > s2
|
||||
})
|
||||
```
|
||||
|
||||
需要注意的是內聯閉包參數和返回值類型聲明與`backwards`函數類型聲明相同。
|
||||
在這兩種方式中,都寫成了`(s1: String, s2: String) -> Bool`。
|
||||
然而在內聯閉包表達式中,函數和返回值類型都寫在大括號內,而不是大括號外。
|
||||
|
||||
閉包的函數體部分由關鍵字`in`引入。
|
||||
該關鍵字表示閉包的參數和返回值類型定義已經完成,閉包涵數體即將開始。
|
||||
|
||||
因為這個閉包的函數體部分如此短以至於可以將其改寫成一行代碼:
|
||||
|
||||
```swift
|
||||
reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } )
|
||||
```
|
||||
|
||||
這說明`sort`函數的整體調用保持不變,一對圓括號仍然包裹住了函數中整個參數集合。而其中一個參數現在變成了內聯閉包(相比於`backwards`版本的代碼)。
|
||||
|
||||
<a name="inferring_type_from_context"></a>
|
||||
### 根據上下文推斷類型(Inferring Type From Context)
|
||||
|
||||
因為排序閉包涵數是作為`sort`函數的參數進行傳入的,Swift可以推斷其參數和返回值的類型。
|
||||
`sort`期望第二個參數是類型為`(String, String) -> Bool`的函數,因此實際上`String`,`String`和`Bool`類型並不需要作為閉包表達式定義中的一部分。
|
||||
因為所有的類型都可以被正確推斷,返回箭頭 (`->`) 和圍繞在參數周圍的括號也可以被省略:
|
||||
|
||||
```swift
|
||||
reversed = sort(names, { s1, s2 in return s1 > s2 } )
|
||||
```
|
||||
|
||||
實際上任何情況下,通過內聯閉包表達式構造的閉包作為參數傳遞給函數時,都可以推斷出閉包的參數和返回值類型,這意味著您幾乎不需要利用完整格式構造任何內聯閉包。
|
||||
|
||||
<a name="implicit_returns_from_single_expression_closures"></a>
|
||||
### 單表達式閉包隱式返回(Implicit Return From Single-Expression Clossures)
|
||||
|
||||
單行表達式閉包可以通過隱藏`return`關鍵字來隱式返回單行表達式的結果,如上版本的例子可以改寫為:
|
||||
|
||||
```swift
|
||||
reversed = sort(names, { s1, s2 in s1 > s2 } )
|
||||
```
|
||||
|
||||
在這個例子中,`sort`函數的第二個參數函數類型明確了閉包必須返回一個`Bool`類型值。
|
||||
因為閉包涵數體只包含了一個單一表達式 (`s1 > s2`),該表達式返回`Bool`類型值,因此這裡沒有歧義,`return`關鍵字可以省略。
|
||||
|
||||
<a name="shorthand_argument_names"></a>
|
||||
### 參數名稱縮寫(Shorthand Argument Names)
|
||||
|
||||
Swift 自動為內聯函數提供了參數名稱縮寫功能,您可以直接通過`$0`,`$1`,`$2`來順序調用閉包的參數。
|
||||
|
||||
如果您在閉包表達式中使用參數名稱縮寫,您可以在閉包參數列表中省略對其的定義,並且對應參數名稱縮寫的類型會通過函數類型進行推斷。
|
||||
`in`關鍵字也同樣可以被省略,因為此時閉包表達式完全由閉包涵數體構成:
|
||||
|
||||
```swift
|
||||
reversed = sort(names, { $0 > $1 } )
|
||||
```
|
||||
|
||||
在這個例子中,`$0`和`$1`表示閉包中第一個和第二個`String`類型的參數。
|
||||
|
||||
<a name="operator_functions"></a>
|
||||
### 運算符函數(Operator Functions)
|
||||
|
||||
實際上還有一種更簡短的方式來撰寫上面例子中的閉包表達式。
|
||||
Swift 的`String`類型定義了關於大於號 (`>`) 的字符串實現,其作為一個函數接受兩個`String`類型的參數並返回`Bool`類型的值。
|
||||
而這正好與`sort`函數的第二個參數需要的函數類型相符合。
|
||||
因此,您可以簡單地傳遞一個大於號,Swift可以自動推斷出您想使用大於號的字符串函數實現:
|
||||
|
||||
```swift
|
||||
reversed = sort(names, >)
|
||||
```
|
||||
|
||||
更多關於運算符表達式的內容請查看 [運算符函數](../chapter2/23_Advanced_Operators.html#operator_functions)。
|
||||
|
||||
<a name="trailing_closures"></a>
|
||||
## 尾隨閉包(Trailing Closures)
|
||||
|
||||
|
||||
如果您需要將一個很長的閉包表達式作為最後一個參數傳遞給函數,可以使用尾隨閉包來增強函數的可讀性。
|
||||
尾隨閉包是一個書寫在函數括號之後的閉包表達式,函數支持將其作為最後一個參數調用。
|
||||
|
||||
```swift
|
||||
func someFunctionThatTakesAClosure(closure: () -> ()) {
|
||||
// 函數體部分
|
||||
}
|
||||
|
||||
// 以下是不使用尾隨閉包進行函數調用
|
||||
someFunctionThatTakesAClosure({
|
||||
// 閉包主體部分
|
||||
})
|
||||
|
||||
// 以下是使用尾隨閉包進行函數調用
|
||||
someFunctionThatTakesAClosure() {
|
||||
// 閉包主體部分
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 如果函數只需要閉包表達式一個參數,當您使用尾隨閉包時,您甚至可以把`()`省略掉。
|
||||
|
||||
在上例中作為`sort`函數參數的字符串排序閉包可以改寫為:
|
||||
|
||||
```swift
|
||||
reversed = sort(names) { $0 > $1 }
|
||||
```
|
||||
|
||||
當閉包非常長以至於不能在一行中進行書寫時,尾隨閉包變得非常有用。
|
||||
舉例來說,Swift 的`Array`類型有一個`map`方法,其獲取一個閉包表達式作為其唯一參數。
|
||||
數組中的每一個元素調用一次該閉包涵數,並返回該元素所映射的值(也可以是不同類型的值)。
|
||||
具體的映射方式和返回值類型由閉包來指定。
|
||||
|
||||
當提供給數組閉包涵數後,`map`方法將返回一個新的數組,數組中包含了與原數組一一對應的映射後的值。
|
||||
|
||||
下例介紹了如何在`map`方法中使用尾隨閉包將`Int`類型數組`[16,58,510]`轉換為包含對應`String`類型的數組`["OneSix", "FiveEight", "FiveOneZero"]`:
|
||||
|
||||
```swift
|
||||
let digitNames = [
|
||||
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
|
||||
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
|
||||
]
|
||||
let numbers = [16, 58, 510]
|
||||
```
|
||||
|
||||
如上代碼創建了一個數字位和它們名字映射的英文版本字典。
|
||||
同時定義了一個準備轉換為字符串的整型數組。
|
||||
|
||||
您現在可以通過傳遞一個尾隨閉包給`numbers`的`map`方法來創建對應的字符串版本數組。
|
||||
需要注意的時調用`numbers.map`不需要在`map`後面包含任何括號,因為其只需要傳遞閉包表達式這一個參數,並且該閉包表達式參數通過尾隨方式進行撰寫:
|
||||
|
||||
```swift
|
||||
let strings = numbers.map {
|
||||
(var number) -> String in
|
||||
var output = ""
|
||||
while number > 0 {
|
||||
output = digitNames[number % 10]! + output
|
||||
number /= 10
|
||||
}
|
||||
return output
|
||||
}
|
||||
// strings 常量被推斷為字符串類型數組,即 String[]
|
||||
// 其值為 ["OneSix", "FiveEight", "FiveOneZero"]
|
||||
```
|
||||
|
||||
`map`在數組中為每一個元素調用了閉包表達式。
|
||||
您不需要指定閉包的輸入參數`number`的類型,因為可以通過要映射的數組類型進行推斷。
|
||||
|
||||
閉包`number`參數被聲明為一個變量參數(變量的具體描述請參看[常量參數和變量參數](../chapter2/06_Functions.html#constant_and_variable_parameters)),因此可以在閉包涵數體內對其進行修改。閉包表達式制定了返回類型為`String`,以表明存儲映射值的新數組類型為`String`。
|
||||
|
||||
閉包表達式在每次被調用的時候創建了一個字符串並返回。
|
||||
其使用求余運算符 (number % 10) 計算最後一位數字並利用`digitNames`字典獲取所映射的字符串。
|
||||
|
||||
> 注意:
|
||||
> 字典`digitNames`下標後跟著一個歎號 (!),因為字典下標返回一個可選值 (optional value),表明即使該 key 不存在也不會查找失敗。
|
||||
> 在上例中,它保證了`number % 10`可以總是作為一個`digitNames`字典的有效下標 key。
|
||||
> 因此歎號可以用於強制解析 (force-unwrap) 存儲在可選下標項中的`String`類型值。
|
||||
|
||||
從`digitNames`字典中獲取的字符串被添加到輸出的前部,逆序建立了一個字符串版本的數字。
|
||||
(在表達式`number % 10`中,如果number為16,則返回6,58返回8,510返回0)。
|
||||
|
||||
`number`變量之後除以10。
|
||||
因為其是整數,在計算過程中未除盡部分被忽略。
|
||||
因此 16變成了1,58變成了5,510變成了51。
|
||||
|
||||
整個過程重複進行,直到`number /= 10`為0,這時閉包會將字符串輸出,而`map`函數則會將字符串添加到所映射的數組中。
|
||||
|
||||
上例中尾隨閉包語法在函數後整潔封裝了具體的閉包功能,而不再需要將整個閉包包裹在`map`函數的括號內。
|
||||
|
||||
<a name="capturing_values"></a>
|
||||
## 捕獲值(Capturing Values)
|
||||
|
||||
|
||||
閉包可以在其定義的上下文中捕獲常量或變量。
|
||||
即使定義這些常量和變量的原域已經不存在,閉包仍然可以在閉包涵數體內引用和修改這些值。
|
||||
|
||||
Swift最簡單的閉包形式是嵌套函數,也就是定義在其他函數的函數體內的函數。
|
||||
嵌套函數可以捕獲其外部函數所有的參數以及定義的常量和變量。
|
||||
|
||||
下例為一個叫做`makeIncrementor`的函數,其包含了一個叫做`incrementor`嵌套函數。
|
||||
嵌套函數`incrementor`從上下文中捕獲了兩個值,`runningTotal`和`amount`。
|
||||
之後`makeIncrementor`將`incrementor`作為閉包返回。
|
||||
每次調用`incrementor`時,其會以`amount`作為增量增加`runningTotal`的值。
|
||||
|
||||
```swift
|
||||
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
|
||||
var runningTotal = 0
|
||||
func incrementor() -> Int {
|
||||
runningTotal += amount
|
||||
return runningTotal
|
||||
}
|
||||
return incrementor
|
||||
}
|
||||
```
|
||||
|
||||
`makeIncrementor`返回類型為`() -> Int`。
|
||||
這意味著其返回的是一個函數,而不是一個簡單類型值。
|
||||
該函數在每次調用時不接受參數只返回一個`Int`類型的值。
|
||||
關於函數返回其他函數的內容,請查看[函數類型作為返回類型](../chapter2/06_Functions.html#function_types_as_return_types)。
|
||||
|
||||
`makeIncrementor`函數定義了一個整型變量`runningTotal`(初始為0) 用來存儲當前跑步總數。
|
||||
該值通過`incrementor`返回。
|
||||
|
||||
`makeIncrementor`有一個`Int`類型的參數,其外部命名為`forIncrement`, 內部命名為`amount`,表示每次`incrementor`被調用時`runningTotal`將要增加的量。
|
||||
|
||||
`incrementor`函數用來執行實際的增加操作。
|
||||
該函數簡單地使`runningTotal`增加`amount`,並將其返回。
|
||||
|
||||
如果我們單獨看這個函數,會發現看上去不同尋常:
|
||||
|
||||
```swift
|
||||
func incrementor() -> Int {
|
||||
runningTotal += amount
|
||||
return runningTotal
|
||||
}
|
||||
```
|
||||
|
||||
`incrementor`函數並沒有獲取任何參數,但是在函數體內訪問了`runningTotal`和`amount`變量。這是因為其通過捕獲在包含它的函數體內已經存在的`runningTotal`和`amount`變量而實現。
|
||||
|
||||
由於沒有修改`amount`變量,`incrementor`實際上捕獲並存儲了該變量的一個副本,而該副本隨著`incrementor`一同被存儲。
|
||||
|
||||
然而,因為每次調用該函數的時候都會修改`runningTotal`的值,`incrementor`捕獲了當前`runningTotal`變量的引用,而不是僅僅複製該變量的初始值。捕獲一個引用保證了當`makeIncrementor`結束時候並不會消失,也保證了當下一次執行`incrementor`函數時,`runningTotal`可以繼續增加。
|
||||
|
||||
> 注意:
|
||||
> Swift 會決定捕獲引用還是拷貝值。
|
||||
> 您不需要標注`amount`或者`runningTotal`來聲明在嵌入的`incrementor`函數中的使用方式。
|
||||
> Swift 同時也處理`runingTotal`變量的內存管理操作,如果不再被`incrementor`函數使用,則會被清除。
|
||||
|
||||
下面代碼為一個使用`makeIncrementor`的例子:
|
||||
|
||||
```swift
|
||||
let incrementByTen = makeIncrementor(forIncrement: 10)
|
||||
```
|
||||
|
||||
該例子定義了一個叫做`incrementByTen`的常量,該常量指向一個每次調用會加10的`incrementor`函數。
|
||||
調用這個函數多次可以得到以下結果:
|
||||
|
||||
```swift
|
||||
incrementByTen()
|
||||
// 返回的值為10
|
||||
incrementByTen()
|
||||
// 返回的值為20
|
||||
incrementByTen()
|
||||
// 返回的值為30
|
||||
```
|
||||
|
||||
如果您創建了另一個`incrementor`,其會有一個屬於自己的獨立的`runningTotal`變量的引用。
|
||||
下面的例子中,`incrementBySevne`捕獲了一個新的`runningTotal`變量,該變量和`incrementByTen`中捕獲的變量沒有任何聯繫:
|
||||
|
||||
```swift
|
||||
let incrementBySeven = makeIncrementor(forIncrement: 7)
|
||||
incrementBySeven()
|
||||
// 返回的值為7
|
||||
incrementByTen()
|
||||
// 返回的值為40
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 如果您將閉包賦值給一個類實例的屬性,並且該閉包通過指向該實例或其成員來捕獲了該實例,您將創建一個在閉包和實例間的強引用環。
|
||||
> Swift 使用捕獲列表來打破這種強引用環。更多信息,請參考 [閉包引起的循環強引用](../chapter2/16_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)。
|
||||
|
||||
<a name="closures_are_reference_types"></a>
|
||||
## 閉包是引用類型(Closures Are Reference Types)
|
||||
|
||||
上面的例子中,`incrementBySeven`和`incrementByTen`是常量,但是這些常量指向的閉包仍然可以增加其捕獲的變量值。
|
||||
這是因為函數和閉包都是引用類型。
|
||||
|
||||
無論您將函數/閉包賦值給一個常量還是變量,您實際上都是將常量/變量的值設置為對應函數/閉包的引用。
|
||||
上面的例子中,`incrementByTen`指向閉包的引用是一個常量,而並非閉包內容本身。
|
||||
|
||||
這也意味著如果您將閉包賦值給了兩個不同的常量/變量,兩個值都會指向同一個閉包:
|
||||
|
||||
```swift
|
||||
let alsoIncrementByTen = incrementByTen
|
||||
alsoIncrementByTen()
|
||||
// 返回的值為50
|
||||
```
|
251
source-tw/chapter2/08_Enumerations.md
Normal file
251
source-tw/chapter2/08_Enumerations.md
Normal file
@ -0,0 +1,251 @@
|
||||
> 翻譯:[yankuangshi](https://github.com/yankuangshi)
|
||||
> 校對:[shinyzhu](https://github.com/shinyzhu)
|
||||
|
||||
# 枚舉(Enumerations)
|
||||
---
|
||||
|
||||
本頁內容包含:
|
||||
|
||||
- [枚舉語法(Enumeration Syntax)](#enumeration_syntax)
|
||||
- [匹配枚舉值與`Swith`語句(Matching Enumeration Values with a Switch Statement)](#matching_enumeration_values_with_a_switch_statement)
|
||||
- [相關值(Associated Values)](#associated_values)
|
||||
- [原始值(Raw Values)](#raw_values)
|
||||
|
||||
枚舉定義了一個通用類型的一組相關的值,使你可以在你的代碼中以一個安全的方式來使用這些值。
|
||||
|
||||
如果你熟悉 C 語言,你就會知道,在 C 語言中枚舉指定相關名稱為一組整型值。Swift 中的枚舉更加靈活,不必給每一個枚舉成員提供一個值。如果一個值(被認為是「原始」值)被提供給每個枚舉成員,則該值可以是一個字符串,一個字符,或是一個整型值或浮點值。
|
||||
|
||||
此外,枚舉成員可以指定任何類型的相關值存儲到枚舉成員值中,就像其他語言中的聯合體(unions)和變體(variants)。你可以定義一組通用的相關成員作為枚舉的一部分,每一組都有不同的一組與它相關的適當類型的數值。
|
||||
|
||||
在 Swift 中,枚舉類型是一等(first-class)類型。它們採用了很多傳統上只被類(class)所支持的特徵,例如計算型屬性(computed properties),用於提供關於枚舉當前值的附加信息,□實例方法(instance methods),用於提供和枚舉所代表的值相關聯的功能。枚舉也可以定義構造函數(initializers)來提供一個初始成員值;可以在原始的實現基礎上擴展它們的功能;可以遵守協議(protocols)來提供標準的功能。
|
||||
|
||||
欲瞭解更多相關功能,請參見[屬性(Properties)](10_Properties.html),[方法(Methods)](11_Methods.html),[構造過程(Initialization)](14_Initialization.html),[擴展(Extensions)](20_Extensions.html)和[協議(Protocols)](21_Protocols.html)。
|
||||
|
||||
<a name="enumeration_syntax"></a>
|
||||
## 枚舉語法
|
||||
|
||||
使用`enum`關鍵詞並且把它們的整個定義放在一對大括號內:
|
||||
|
||||
```swift
|
||||
enum SomeEnumeration {
|
||||
// enumeration definition goes here
|
||||
}
|
||||
```
|
||||
|
||||
以下是指南針四個方向的一個例子:
|
||||
|
||||
```swift
|
||||
enum CompassPoint {
|
||||
case North
|
||||
case South
|
||||
case East
|
||||
case West
|
||||
}
|
||||
```
|
||||
|
||||
一個枚舉中被定義的值(例如 `North`,`South`,`East`和`West`)是枚舉的***成員值***(或者***成員***)。`case`關鍵詞表明新的一行成員值將被定義。
|
||||
|
||||
> 注意:
|
||||
> 不像 C 和 Objective-C 一樣,Swift 的枚舉成員在被創建時不會被賦予一個默認的整數值。在上面的`CompassPoints`例子中,`North`,`South`,`East`和`West`不是隱式的等於`0`,`1`,`2`和`3`。相反的,這些不同的枚舉成員在`CompassPoint`的一種顯示定義中擁有各自不同的值。
|
||||
|
||||
多個成員值可以出現在同一行上,用逗號隔開:
|
||||
|
||||
```swift
|
||||
enum Planet {
|
||||
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
|
||||
}
|
||||
```
|
||||
|
||||
每個枚舉定義了一個全新的類型。像 Swift 中其他類型一樣,它們的名字(例如`CompassPoint`和`Planet`)必須以一個大寫字母開頭。給枚舉類型起一個單數名字而不是複數名字,以便於讀起來更加容易理解:
|
||||
|
||||
```swift
|
||||
var directionToHead = CompassPoint.West
|
||||
```
|
||||
|
||||
`directionToHead`的類型被推斷當它被`CompassPoint`的一個可能值初始化。一旦`directionToHead`被聲明為一個`CompassPoint`,你可以使用更短的點(.)語法將其設置為另一個`CompassPoint`的值:
|
||||
|
||||
```swift
|
||||
directionToHead = .East
|
||||
```
|
||||
|
||||
`directionToHead`的類型已知時,當設定它的值時,你可以不再寫類型名。使用顯式類型的枚舉值可以讓代碼具有更好的可讀性。
|
||||
|
||||
<a name="matching_enumeration_values_with_a_switch_statement"></a>
|
||||
## 匹配枚舉值和`Switch`語句
|
||||
|
||||
你可以匹配單個枚舉值和`switch`語句:
|
||||
|
||||
```swift
|
||||
directionToHead = .South
|
||||
switch directionToHead {
|
||||
case .North:
|
||||
println("Lots of planets have a north")
|
||||
case .South:
|
||||
println("Watch out for penguins")
|
||||
case .East:
|
||||
println("Where the sun rises")
|
||||
case .West:
|
||||
println("Where the skies are blue")
|
||||
}
|
||||
// 輸出 "Watch out for penguins」
|
||||
```
|
||||
|
||||
你可以如此理解這段代碼:
|
||||
|
||||
「考慮`directionToHead`的值。當它等於`.North`,打印`「Lots of planets have a north」`。當它等於`.South`,打印`「Watch out for penguins」`。」
|
||||
|
||||
等等依次類推。
|
||||
|
||||
正如在[控制流(Control Flow)](05_Control_Flow.html)中介紹,當考慮一個枚舉的成員們時,一個`switch`語句必須全面。如果忽略了`.West`這種情況,上面那段代碼將無法通過編譯,因為它沒有考慮到`CompassPoint`的全部成員。全面性的要求確保了枚舉成員不會被意外遺漏。
|
||||
|
||||
當不需要匹配每個枚舉成員的時候,你可以提供一個默認`default`分支來涵蓋所有未明確被提出的任何成員:
|
||||
|
||||
```swift
|
||||
let somePlanet = Planet.Earth
|
||||
switch somePlanet {
|
||||
case .Earth:
|
||||
println("Mostly harmless")
|
||||
default:
|
||||
println("Not a safe place for humans")
|
||||
}
|
||||
// 輸出 "Mostly harmless」
|
||||
```
|
||||
|
||||
<a name="associated_values"></a>
|
||||
## 相關值(Associated Values)
|
||||
|
||||
上一小節的例子演示了一個枚舉的成員是如何被定義(分類)的。你可以為`Planet.Earth`設置一個常量或則變量,並且在之後查看這個值。不管怎樣,如果有時候能夠把其他類型的相關值和成員值一起存儲起來會很有用。這能讓你存儲成員值之外的自定義信息,並且當你每次在代碼中使用該成員時允許這個信息產生變化。
|
||||
|
||||
你可以定義 Swift 的枚舉存儲任何類型的相關值,如果需要的話,每個成員的數據類型可以是各不相同的。枚舉的這種特性跟其他語言中的可辨識聯合(discriminated unions),標籤聯合(tagged unions),或者變體(variants)相似。
|
||||
|
||||
例如,假設一個庫存跟蹤系統需要利用兩種不同類型的條形碼來跟蹤商品。有些商品上標有 UPC-A 格式的一維碼,它使用數字 0 到 9。每一個條形碼都有一個代表「數字系統」的數字,該數字後接 10 個代表「標識符」的數字。最後一個數字是「檢查」位,用來驗證代碼是否被正確掃瞄:
|
||||
|
||||
<img width="252" height="120" alt="" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_UPC_2x.png">
|
||||
|
||||
其他商品上標有 QR 碼格式的二維碼,它可以使用任何 ISO8859-1 字符,並且可以編碼一個最多擁有 2,953 字符的字符串:
|
||||
|
||||
<img width="169" height="169" alt="" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_QR_2x.png">
|
||||
|
||||
對於庫存跟蹤系統來說,能夠把 UPC-A 碼作為三個整型值的元組,和把 QR 碼作為一個任何長度的字符串存儲起來是方便的。
|
||||
|
||||
在 Swift 中,用來定義兩種商品條碼的枚舉是這樣子的:
|
||||
|
||||
```swift
|
||||
enum Barcode {
|
||||
case UPCA(Int, Int, Int)
|
||||
case QRCode(String)
|
||||
}
|
||||
```
|
||||
|
||||
以上代碼可以這麼理解:
|
||||
|
||||
「定義一個名為`Barcode`的枚舉類型,它可以是`UPCA`的一個相關值(`Int`,`Int`,`Int`),或者`QRCode`的一個字符串類型(`String`)相關值。」
|
||||
|
||||
這個定義不提供任何`Int`或`String`的實際值,它只是定義了,當`Barcode`常量和變量等於`Barcode.UPCA`或`Barcode.QRCode`時,相關值的類型。
|
||||
|
||||
然後可以使用任何一種條碼類型創建新的條碼,如:
|
||||
|
||||
```swift
|
||||
var productBarcode = Barcode.UPCA(8, 85909_51226, 3)
|
||||
```
|
||||
|
||||
以上例子創建了一個名為`productBarcode`的新變量,並且賦給它一個`Barcode.UPCA`的相關元組值`(8, 8590951226, 3)`。提供的「標識符」值在整數字中有一個下劃線,使其便於閱讀條形碼。
|
||||
|
||||
同一個商品可以被分配給一個不同類型的條形碼,如:
|
||||
|
||||
```swift
|
||||
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
|
||||
```
|
||||
|
||||
這時,原始的`Barcode.UPCA`和其整數值被新的`Barcode.QRCode`和其字符串值所替代。條形碼的常量和變量可以存儲一個`.UPCA`或者一個`.QRCode`(連同它的相關值),但是在任何指定時間只能存儲其中之一。
|
||||
|
||||
像以前那樣,不同的條形碼類型可以使用一個 switch 語句來檢查,然而這次相關值可以被提取作為 switch 語句的一部分。你可以在`switch`的 case 分支代碼中提取每個相關值作為一個常量(用`let`前綴)或者作為一個變量(用`var`前綴)來使用:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
case .UPCA(let numberSystem, let identifier, let check):
|
||||
println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
|
||||
case .QRCode(let productCode):
|
||||
println("QR code with value of \(productCode).")
|
||||
}
|
||||
// 輸出 "QR code with value of ABCDEFGHIJKLMNOP.」
|
||||
```
|
||||
|
||||
如果一個枚舉成員的所有相關值被提取為常量,或者它們全部被提取為變量,為了簡潔,你可以只放置一個`var`或者`let`標注在成員名稱前:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
case let .UPCA(numberSystem, identifier, check):
|
||||
println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
|
||||
case let .QRCode(productCode):
|
||||
println("QR code with value of \(productCode).")
|
||||
}
|
||||
// 輸出 "QR code with value of ABCDEFGHIJKLMNOP."
|
||||
```
|
||||
|
||||
<a name="raw_values"></a>
|
||||
## 原始值(Raw Values)
|
||||
|
||||
在[Associated Values](#raw_values)小節的條形碼例子中演示了一個枚舉的成員如何聲明它們存儲不同類型的相關值。作為相關值的替代,枚舉成員可以被默認值(稱為原始值)預先填充,其中這些原始值具有相同的類型。
|
||||
|
||||
這裡是一個枚舉成員存儲原始 ASCII 值的例子:
|
||||
|
||||
```swift
|
||||
enum ASCIIControlCharacter: Character {
|
||||
case Tab = "\t"
|
||||
case LineFeed = "\n"
|
||||
case CarriageReturn = "\r"
|
||||
}
|
||||
```
|
||||
|
||||
在這裡,稱為`ASCIIControlCharacter`的枚舉的原始值類型被定義為字符型`Character`,並被設置了一些比較常見的 ASCII 控制字符。字符值的描述請詳見字符串和字符[`Strings and Characters`](03_Strings_and_Characters.html)部分。
|
||||
|
||||
注意,原始值和相關值是不相同的。當你開始在你的代碼中定義枚舉的時候原始值是被預先填充的值,像上述三個 ASCII 碼。對於一個特定的枚舉成員,它的原始值始終是相同的。相關值是當你在創建一個基於枚舉成員的新常量或變量時才會被設置,並且每次當你這麼做得時候,它的值可以是不同的。
|
||||
|
||||
原始值可以是字符串,字符,或者任何整型值或浮點型值。每個原始值在它的枚舉聲明中必須是唯一的。當整型值被用於原始值,如果其他枚舉成員沒有值時,它們會自動遞增。
|
||||
|
||||
下面的枚舉是對之前`Planet`這個枚舉的一個細化,利用原始整型值來表示每個 planet 在太陽系中的順序:
|
||||
|
||||
```swift
|
||||
enum Planet: Int {
|
||||
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
|
||||
}
|
||||
```
|
||||
|
||||
自動遞增意味著`Planet.Venus`的原始值是`2`,依次類推。
|
||||
|
||||
使用枚舉成員的`toRaw`方法可以訪問該枚舉成員的原始值:
|
||||
|
||||
```swift
|
||||
let earthsOrder = Planet.Earth.toRaw()
|
||||
// earthsOrder is 3
|
||||
```
|
||||
|
||||
使用枚舉的`fromRaw`方法來試圖找到具有特定原始值的枚舉成員。這個例子通過原始值`7`識別`Uranus`:
|
||||
|
||||
```swift
|
||||
let possiblePlanet = Planet.fromRaw(7)
|
||||
// possiblePlanet is of type Planet? and equals Planet.Uranus
|
||||
```
|
||||
|
||||
然而,並非所有可能的`Int`值都可以找到一個匹配的行星。正因為如此,`fromRaw`方法可以返回一個***可選***的枚舉成員。在上面的例子中,`possiblePlanet`是`Planet?`類型,或「可選的`Planet`」。
|
||||
|
||||
如果你試圖尋找一個位置為9的行星,通過`fromRaw`返回的可選`Planet`值將是`nil`:
|
||||
|
||||
```swift
|
||||
let positionToFind = 9
|
||||
if let somePlanet = Planet.fromRaw(positionToFind) {
|
||||
switch somePlanet {
|
||||
case .Earth:
|
||||
println("Mostly harmless")
|
||||
default:
|
||||
println("Not a safe place for humans")
|
||||
}
|
||||
} else {
|
||||
println("There isn't a planet at position \(positionToFind)")
|
||||
}
|
||||
// 輸出 "There isn't a planet at position 9
|
||||
```
|
||||
|
||||
這個範例使用可選綁定(optional binding),通過原始值`9`試圖訪問一個行星。`if let somePlanet = Planet.fromRaw(9)`語句獲得一個可選`Planet`,如果可選`Planet`可以被獲得,把`somePlanet`設置成該可選`Planet`的內容。在這個範例中,無法檢索到位置為`9`的行星,所以`else`分支被執行。
|
||||
|
443
source-tw/chapter2/09_Classes_and_Structures.md
Normal file
443
source-tw/chapter2/09_Classes_and_Structures.md
Normal file
@ -0,0 +1,443 @@
|
||||
> 翻譯:[JaySurplus](https://github.com/JaySurplus)
|
||||
> 校對:[sg552](https://github.com/sg552)
|
||||
|
||||
# 類和結構體
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [類和結構體對比](#comparing_classes_and_structures)
|
||||
- [結構體和枚舉是值類型](#structures_and_enumerations_are_value_types)
|
||||
- [類是引用類型](#classes_are_reference_types)
|
||||
- [類和結構體的選擇](#choosing_between_classes_and_structures)
|
||||
- [集合(collection)類型的賦值與複製行為](#assignment_and_copy_behavior_for_collection_types)
|
||||
|
||||
類和結構體是人們構建代碼所用的一種通用且靈活的構造體。為了在類和結構體中實現各種功能,我們必須要嚴格按照常量、變量以及函數所規定的語法規則來定義屬性和添加方法。
|
||||
|
||||
與其他編程語言所不同的是,Swift 並不要求你為自定義類和結構去創建獨立的接口和實現文件。你所要做的是在一個單一文件中定義一個類或者結構體,系統將會自動生成面向其它代碼的外部接口。
|
||||
|
||||
> 注意:
|
||||
通常一個`類`的實例被稱為`對像`。然而在Swift 中,類和結構體的關係要比在其他語言中更加的密切,本章中所討論的大部分功能都可以用在類和結構體上。因此,我們會主要使用`實例`而不是`對像`。
|
||||
|
||||
<a name="comparing_classes_and_structures"></a>
|
||||
###類和結構體對比
|
||||
|
||||
Swift 中類和結構體有很多共同點。共同處在於:
|
||||
|
||||
* 定義屬性用於存儲值
|
||||
* 定義方法用於提供功能
|
||||
* 定義附屬腳本用於訪問值
|
||||
* 定義構造器用於生成初始化值
|
||||
* 通過擴展以增加默認實現的功能
|
||||
* 符合協議以對某類提供標準功能
|
||||
|
||||
更多信息請參見 [屬性](10_Properties.html),[方法](11_Methods.html),[下標腳本](12_Subscripts.html),[初始過程](14_Initialization.html),[擴展](20_Extensions.html),和[協議](21_Protocols.html)。
|
||||
|
||||
與結構體相比,類還有如下的附加功能:
|
||||
|
||||
* 繼承允許一個類繼承另一個類的特徵
|
||||
* 類型轉換允許在運行時檢查和解釋一個類實例的類型
|
||||
* 解構器允許一個類實例釋放任何其所被分配的資源
|
||||
* 引用計數允許對一個類的多次引用
|
||||
|
||||
更多信息請參見[繼承](http://),[類型轉換](http://),[初始化](http://),和[自動引用計數](http://)。
|
||||
|
||||
> 注意:
|
||||
結構體總是通過被複製的方式在代碼中傳遞,因此請不要使用引用計數。
|
||||
|
||||
### 定義
|
||||
|
||||
類和結構體有著類似的定義方式。我們通過關鍵字`class`和`struct`來分別表示類和結構體,並在一對大括號中定義它們的具體內容:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
// class definition goes here
|
||||
}
|
||||
struct SomeStructure {
|
||||
// structure definition goes here
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
在你每次定義一個新類或者結構體的時候,實際上你是有效地定義了一個新的 Swift 類型。因此請使用 `UpperCamelCase` 這種方式來命名(如 `SomeClass` 和`SomeStructure`等),以便符合標準Swift 類型的大寫命名風格(如`String`,`Int`和`Bool`)。相反的,請使用`lowerCamelCase`這種方式為屬性和方法命名(如`framerate`和`incrementCount`),以便和類區分。
|
||||
|
||||
以下是定義結構體和定義類的示例:
|
||||
|
||||
```swift
|
||||
struct Resolution {
|
||||
var width = 0
|
||||
var heigth = 0
|
||||
}
|
||||
class VideoMode {
|
||||
var resolution = Resolution()
|
||||
var interlaced = false
|
||||
var frameRate = 0.0
|
||||
var name: String?
|
||||
}
|
||||
```
|
||||
|
||||
在上面的示例中我們定義了一個名為`Resolution`的結構體,用來描述一個顯示器的像素分辨率。這個結構體包含了兩個名為`width`和`height`的存儲屬性。存儲屬性是捆綁和存儲在類或結構體中的常量或變量。當這兩個屬性被初始化為整數`0`的時候,它們會被推斷為`Int`類型。
|
||||
|
||||
在上面的示例中我們還定義了一個名為`VideoMode`的類,用來描述一個視頻顯示器的特定模式。這個類包含了四個儲存屬性變量。第一個是`分辨率`,它被初始化為一個新的`Resolution`結構體的實例,具有`Resolution`的屬性類型。新`VideoMode`實例同時還會初始化其它三個屬性,它們分別是,初始值為`false`(意為「non-interlaced video」)的`interlaced`,回放幀率初始值為`0.0`的`frameRate`和值為可選`String`的`name`。`name`屬性會被自動賦予一個默認值`nil`,意為「沒有`name`值」,因為它是一個可選類型。
|
||||
|
||||
### 類和結構體實例
|
||||
|
||||
`Resolution`結構體和`VideoMode`類的定義僅描述了什麼是`Resolution`和`VideoMode`。它們並沒有描述一個特定的分辨率(resolution)或者視頻模式(video mode)。為了描述一個特定的分辨率或者視頻模式,我們需要生成一個它們的實例。
|
||||
|
||||
生成結構體和類實例的語法非常相似:
|
||||
|
||||
```swift
|
||||
let someResolution = Resolution()
|
||||
let someVideoMode = VideoMode()
|
||||
```
|
||||
|
||||
結構體和類都使用構造器語法來生成新的實例。構造器語法的最簡單形式是在結構體或者類的類型名稱後跟隨一個空括弧,如`Resolution()`或`VideoMode()`。通過這種方式所創建的類或者結構體實例,其屬性均會被初始化為默認值。[構造過程](14_Initialization.html)章節會對類和結構體的初始化進行更詳細的討論。
|
||||
|
||||
### 屬性訪問
|
||||
|
||||
通過使用*點語法*(*dot syntax*),你可以訪問實例中所含有的屬性。其語法規則是,實例名後面緊跟屬性名,兩者通過點號(.)連接:
|
||||
|
||||
```swift
|
||||
println("The width of someResolution is \(someResolution.width)")
|
||||
// 輸出 "The width of someResolution is 0"
|
||||
```
|
||||
|
||||
在上面的例子中,`someResolution.width`引用`someResolution`的`width`屬性,返回`width`的初始值`0`。
|
||||
|
||||
你也可以訪問子屬性,如何`VideoMode`中`Resolution`屬性的`width`屬性:
|
||||
|
||||
```swift
|
||||
println("The width of someVideoMode is \(someVideoMode.resolution.width)")
|
||||
// 輸出 "The width of someVideoMode is 0"
|
||||
```
|
||||
|
||||
你也可以使用點語法為屬性變量賦值:
|
||||
|
||||
```swift
|
||||
someVideoMode.resolution.width = 12880
|
||||
println("The width of someVideoMode is now \(someVideoMode.resolution.width)")
|
||||
// 輸出 "The width of someVideoMode is now 1280"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
與 Objective-C 語言不同的是,Swift 允許直接設置結構體屬性的子屬性。上面的最後一個例子,就是直接設置了`someVideoMode`中`resolution`屬性的`width`這個子屬性,以上操作並不需要重新設置`resolution`屬性。
|
||||
|
||||
### 結構體類型的成員逐一構造器(Memberwise Initializers for structure Types)
|
||||
|
||||
所有結構體都有一個自動生成的成員逐一構造器,用於初始化新結構體實例中成員的屬性。新實例中各個屬性的初始值可以通過屬性的名稱傳遞到成員逐一構造器之中:
|
||||
|
||||
```swift
|
||||
let vga = resolution(width:640, heigth: 480)
|
||||
```
|
||||
|
||||
與結構體不同,類實例沒有默認的成員逐一構造器。[構造過程](14_Initialization.html)章節會對構造器進行更詳細的討論。
|
||||
|
||||
<a name="structures_and_enumerations_are_value_types"></a>
|
||||
## 結構體和枚舉是值類型
|
||||
|
||||
值類型被賦予給一個變量,常數或者本身被傳遞給一個函數的時候,實際上操作的是其的拷貝。
|
||||
|
||||
在之前的章節中,我們已經大量使用了值類型。實際上,在 Swift 中,所有的基本類型:整數(Integer)、浮點數(floating-point)、布爾值(Booleans)、字符串(string)、數組(array)和字典(dictionaries),都是值類型,並且都是以結構體的形式在後台所實現。
|
||||
|
||||
在 Swift 中,所有的結構體和枚舉都是值類型。這意味著它們的實例,以及實例中所包含的任何值類型屬性,在代碼中傳遞的時候都會被複製。
|
||||
|
||||
請看下面這個示例,其使用了前一個示例中`Resolution`結構體:
|
||||
|
||||
```swift
|
||||
let hd = Resolution(width: 1920, height: 1080)
|
||||
var cinema = hd
|
||||
```
|
||||
|
||||
在以上示例中,聲明了一個名為`hd`的常量,其值為一個初始化為全高清視頻分辨率(1920 像素寬,1080 像素高)的`Resolution`實例。
|
||||
|
||||
然後示例中又聲明了一個名為`cinema`的變量,其值為之前聲明的`hd`。因為`Resolution`是一個結構體,所以`cinema`的值其實是`hd`的一個拷貝副本,而不是`hd`本身。儘管`hd`和`cinema`有著相同的寬(width)和高(height)屬性,但是在後台中,它們是兩個完全不同的實例。
|
||||
|
||||
下面,為了符合數碼影院放映的需求(2048 像素寬,1080 像素高),`cinema`的`width`屬性需要作如下修改:
|
||||
|
||||
```swift
|
||||
cinema.width = 2048
|
||||
```
|
||||
|
||||
這裡,將會顯示`cinema`的`width`屬性確已改為了`2048`:
|
||||
|
||||
```swift
|
||||
println("cinema is now \(cinema.width) pixels wide")
|
||||
// 輸出 "cinema is now 2048 pixels wide"
|
||||
```
|
||||
|
||||
然而,初始的`hd`實例中`width`屬性還是`1920`:
|
||||
|
||||
```swift
|
||||
println("hd is still \(hd.width ) pixels wide")
|
||||
// 輸出 "hd is still 1920 pixels wide"
|
||||
```
|
||||
|
||||
在將`hd`賦予給`cinema`的時候,實際上是將`hd`中所存儲的`值(values)`進行拷貝,然後將拷貝的數據存儲到新的`cinema`實例中。結果就是兩個完全獨立的實例碰巧包含有相同的數值。由於兩者相互獨立,因此將`cinema`的`width`修改為`2048`並不會影響`hd`中的寬(width)。
|
||||
|
||||
枚舉也遵循相同的行為準則:
|
||||
|
||||
```swift
|
||||
enum CompassPoint {
|
||||
case North, South, East, West
|
||||
}
|
||||
var currentDirection = CompassPoint.West
|
||||
let rememberedDirection = currentDirection
|
||||
currentDirection = .East
|
||||
if rememberDirection == .West {
|
||||
println("The remembered direction is still .West")
|
||||
}
|
||||
// 輸出 "The remembered direction is still .West"
|
||||
```
|
||||
|
||||
上例中`rememberedDirection`被賦予了`currentDirection`的值(value),實際上它被賦予的是值(value)的一個拷貝。賦值過程結束後再修改`currentDirection`的值並不影響`rememberedDirection`所儲存的原始值(value)的拷貝。
|
||||
|
||||
<a name="classes_are_reference_types"></a>
|
||||
## 類是引用類型
|
||||
|
||||
與值類型不同,引用類型在被賦予到一個變量、常量或者被傳遞到一個函數時,操作的是引用,其並不是拷貝。因此,引用的是已存在的實例本身而不是其拷貝。
|
||||
|
||||
請看下面這個示例,其使用了之前定義的`VideoMode`類:
|
||||
|
||||
```swift
|
||||
let tenEighty = VideoMode()
|
||||
tenEighty.resolution = hd
|
||||
tenEighty.interlaced = true
|
||||
tenEighty.name = "1080i"
|
||||
tenEighty.frameRate = 25.0
|
||||
```
|
||||
|
||||
以上示例中,聲明了一個名為`tenEighty`的常量,其引用了一個`VideoMode`類的新實例。在之前的示例中,這個視頻模式(video mode)被賦予了HD分辨率(1920*1080)的一個拷貝(`hd`)。同時設置為交錯(interlaced),命名為`「1080i」`。最後,其幀率是`25.0`幀每秒。
|
||||
|
||||
然後,`tenEighty` 被賦予名為`alsoTenEighty`的新常量,同時對`alsoTenEighty`的幀率進行修改:
|
||||
|
||||
```swift
|
||||
let alsoTenEighty = tenEighty
|
||||
alsoTenEighty.frameRate = 30.0
|
||||
```
|
||||
|
||||
因為類是引用類型,所以`tenEight`和`alsoTenEight`實際上引用的是相同的`VideoMode`實例。換句話說,它們是同一個實例的兩種叫法。
|
||||
|
||||
下面,通過查看`tenEighty`的`frameRate`屬性,我們會發現它正確的顯示了基本`VideoMode`實例的新幀率,其值為`30.0`:
|
||||
|
||||
```swift
|
||||
println("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
|
||||
// 輸出 "The frameRate property of theEighty is now 30.0"
|
||||
```
|
||||
|
||||
需要注意的是`tenEighty`和`alsoTenEighty`被聲明為*常量((constants)*而不是變量。然而你依然可以改變`tenEighty.frameRate`和`alsoTenEighty.frameRate`,因為這兩個常量本身不會改變。它們並不`存儲`這個`VideoMode`實例,在後台僅僅是對`VideoMode`實例的引用。所以,改變的是被引用的基礎`VideoMode`的`frameRate`參數,而不改變常量的值。
|
||||
|
||||
### 恆等運算符
|
||||
|
||||
因為類是引用類型,有可能有多個常量和變量在後台同時引用某一個類實例。(對於結構體和枚舉來說,這並不成立。因為它們作為值類型,在被賦予到常量、變量或者傳遞到函數時,其值總是會被拷貝。)
|
||||
|
||||
如果能夠判定兩個常量或者變量是否引用同一個類實例將會很有幫助。為了達到這個目的,Swift 內建了兩個恆等運算符:
|
||||
|
||||
* 等價於 ( === )
|
||||
* 不等價於 ( !== )
|
||||
|
||||
以下是運用這兩個運算符檢測兩個常量或者變量是否引用同一個實例:
|
||||
|
||||
```swift
|
||||
if tenEighty === alsoTenTighty {
|
||||
println("tenTighty and alsoTenEighty refer to the same Resolution instance.")
|
||||
}
|
||||
//輸出 "tenEighty and alsoTenEighty refer to the same Resolution instance."
|
||||
```
|
||||
|
||||
請注意```「等價於"```(用三個等號表示,===) 與```「等於"```(用兩個等號表示,==)的不同:
|
||||
|
||||
* 「等價於」表示兩個類類型(class type)的常量或者變量引用同一個類實例。
|
||||
* 「等於」表示兩個實例的值「相等」或「相同」,判定時要遵照類設計者定義定義的評判標準,因此相比於「相等」,這是一種更加合適的叫法。
|
||||
|
||||
當你在定義你的自定義類和結構體的時候,你有義務來決定判定兩個實例「相等」的標準。在章節[運算符函數(Operator Functions)](23_Advanced_Operators.html#operator_functions)中將會詳細介紹實現自定義「等於」和「不等於」運算符的流程。
|
||||
|
||||
### 指針
|
||||
|
||||
如果你有 C,C++ 或者 Objective-C 語言的經驗,那麼你也許會知道這些語言使用指針來引用內存中的地址。一個 Swift 常量或者變量引用一個引用類型的實例與 C 語言中的指針類似,不同的是並不直接指向內存中的某個地址,而且也不要求你使用星號(*)來表明你在創建一個引用。Swift 中這些引用與其它的常量或變量的定義方式相同。
|
||||
|
||||
<a name="choosing_between_classes_and_structures"></a>
|
||||
## 類和結構體的選擇
|
||||
|
||||
在你的代碼中,你可以使用類和結構體來定義你的自定義數據類型。
|
||||
|
||||
然而,結構體實例總是通過值傳遞,類實例總是通過引用傳遞。這意味兩者適用不同的任務。當你在考慮一個工程項目的數據構造和功能的時候,你需要決定每個數據構造是定義成類還是結構體。
|
||||
|
||||
按照通用的準則,當符合一條或多條以下條件時,請考慮構建結構體:
|
||||
|
||||
* 結構體的主要目的是用來封裝少量相關簡單數據值。
|
||||
* 有理由預計一個結構體實例在賦值或傳遞時,封裝的數據將會被拷貝而不是被引用。
|
||||
* 任何在結構體中儲存的值類型屬性,也將會被拷貝,而不是被引用。
|
||||
* 結構體不需要去繼承另一個已存在類型的屬性或者行為。
|
||||
|
||||
合適的結構體候選者包括:
|
||||
|
||||
* 幾何形狀的大小,封裝一個`width`屬性和`height`屬性,兩者均為`Double`類型。
|
||||
* 一定範圍內的路徑,封裝一個`start`屬性和`length`屬性,兩者均為`Int`類型。
|
||||
* 三維坐標系內一點,封裝`x`,`y`和`z`屬性,三者均為`Double`類型。
|
||||
|
||||
在所有其它案例中,定義一個類,生成一個它的實例,並通過引用來管理和傳遞。實際中,這意味著絕大部分的自定義數據構造都應該是類,而非結構體。
|
||||
|
||||
<a name="assignment_and_copy_behavior_for_collection_types"></a>
|
||||
## 集合(Collection)類型的賦值和拷貝行為
|
||||
|
||||
Swift 中`數組(Array)`和`字典(Dictionary)`類型均以結構體的形式實現。然而當數組被賦予一個常量或變量,或被傳遞給一個函數或方法時,其拷貝行為與字典和其它結構體有些許不同。
|
||||
|
||||
以下對`數組`和`結構體`的行為描述與對`NSArray`和`NSDictionary`的行為描述在本質上不同,後者是以類的形式實現,前者是以結構體的形式實現。`NSArray`和`NSDictionary`實例總是以對已有實例引用,而不是拷貝的方式被賦值和傳遞。
|
||||
|
||||
> 注意:
|
||||
以下是對於數組,字典,字符串和其它值的`拷貝`的描述。
|
||||
在你的代碼中,拷貝好像是確實是在有拷貝行為的地方產生過。然而,在 Swift 的後台中,只有確有必要,`實際(actual)`拷貝才會被執行。Swift 管理所有的值拷貝以確保性能最優化的性能,所以你也沒有必要去避免賦值以保證最優性能。(實際賦值由系統管理優化)
|
||||
|
||||
### 字典類型的賦值和拷貝行為
|
||||
|
||||
無論何時將一個`字典`實例賦給一個常量或變量,或者傳遞給一個函數或方法,這個字典會即會在賦值或調用發生時被拷貝。在章節[結構體和枚舉是值類型](#structures_and_enumerations_are_value_types)中將會對此過程進行詳細介紹。
|
||||
|
||||
如果`字典`實例中所儲存的鍵(keys)和/或值(values)是值類型(結構體或枚舉),當賦值或調用發生時,它們都會被拷貝。相反,如果鍵(keys)和/或值(values)是引用類型,被拷貝的將會是引用,而不是被它們引用的類實例或函數。`字典`的鍵和值的拷貝行為與結構體所儲存的屬性的拷貝行為相同。
|
||||
|
||||
下面的示例定義了一個名為`ages`的字典,其中儲存了四個人的名字和年齡。`ages`字典被賦予了一個名為`copiedAges`的新變量,同時`ages`在賦值的過程中被拷貝。賦值結束後,`ages`和`copiedAges`成為兩個相互獨立的字典。
|
||||
|
||||
```swift
|
||||
var ages = ["Peter": 23, "Wei": 35, "Anish": 65, "Katya": 19]
|
||||
var copiedAges = ages
|
||||
```
|
||||
|
||||
這個字典的鍵(keys)是`字符串(String)`類型,值(values)是`整(Int)`類型。這兩種類型在Swift 中都是值類型(value types),所以當字典被拷貝時,兩者都會被拷貝。
|
||||
|
||||
我們可以通過改變一個字典中的年齡值(age value),檢查另一個字典中所對應的值,來證明`ages`字典確實是被拷貝了。如果在`copiedAges`字典中將`Peter`的值設為`24`,那麼`ages`字典仍然會返回修改前的值`23`:
|
||||
|
||||
```swift
|
||||
copiedAges["Peter"] = 24
|
||||
println(ages["Peter"])
|
||||
// 輸出 "23"
|
||||
```
|
||||
|
||||
### 數組的賦值和拷貝行為
|
||||
|
||||
在Swift 中,`數組(Arrays)`類型的賦值和拷貝行為要比`字典(Dictionary)`類型的複雜的多。當操作數組內容時,`數組(Array)`能提供接近C語言的的性能,並且拷貝行為只有在必要時才會發生。
|
||||
|
||||
如果你將一個`數組(Array)`實例賦給一個變量或常量,或者將其作為參數傳遞給函數或方法調用,在事件發生時數組的內容`不`會被拷貝。相反,數組公用相同的元素序列。當你在一個數組內修改某一元素,修改結果也會在另一數組顯示。
|
||||
|
||||
對數組來說,拷貝行為僅僅當操作有可能修改數組`長度`時才會發生。這種行為包括了附加(appending),插入(inserting),刪除(removing)或者使用範圍下標(ranged subscript)去替換這一範圍內的元素。只有當數組拷貝確要發生時,數組內容的行為規則與字典中鍵值的相同,參見章節[集合(collection)類型的賦值與複製行為](#assignment_and_copy_behavior_for_collection_types。
|
||||
|
||||
下面的示例將一個`整數(Int)`數組賦給了一個名為`a`的變量,繼而又被賦給了變量`b`和`c`:
|
||||
|
||||
```swift
|
||||
var a = [1, 2, 3]
|
||||
var b = a
|
||||
var c = a
|
||||
```
|
||||
|
||||
我們可以在`a`,`b`,`c`上使用下標語法以得到數組的第一個元素:
|
||||
|
||||
```swift
|
||||
println(a[0])
|
||||
// 1
|
||||
println(b[0])
|
||||
// 1
|
||||
println(c[0])
|
||||
// 1
|
||||
```
|
||||
|
||||
如果通過下標語法修改數組中某一元素的值,那麼`a`,`b`,`c`中的相應值都會發生改變。請注意當你用下標語法修改某一值時,並沒有拷貝行為伴隨發生,因為下表語法修改值時沒有改變數組長度的可能:
|
||||
|
||||
```swift
|
||||
a[0] = 42
|
||||
println(a[0])
|
||||
// 42
|
||||
println(b[0])
|
||||
// 42
|
||||
println(c[0])
|
||||
// 42
|
||||
```
|
||||
|
||||
然而,當你給`a`附加新元素時,數組的長度`會`改變。
|
||||
當附加元素這一事件發生時,Swift 會創建這個數組的一個拷貝。從此以後,`a`將會是原數組的一個獨立拷貝。
|
||||
|
||||
拷貝發生後,如果再修改`a`中元素值的話,`a`將會返回與`b`,`c`不同的結果,因為後兩者引用的是原來的數組:
|
||||
|
||||
```swift
|
||||
a.append(4)
|
||||
a[0] = 777
|
||||
println(a[0])
|
||||
// 777
|
||||
println(b[0])
|
||||
// 42
|
||||
println(c[0])
|
||||
// 42
|
||||
```
|
||||
|
||||
### 確保數組的唯一性
|
||||
|
||||
在操作一個數組,或將其傳遞給函數以及方法調用之前是很有必要先確定這個數組是有一個唯一拷貝的。通過在數組變量上調用`unshare`方法來確定數組引用的唯一性。(當數組賦給常量時,不能調用`unshare`方法)
|
||||
|
||||
如果一個數組被多個變量引用,在其中的一個變量上調用`unshare`方法,則會拷貝此數組,此時這個變量將會有屬於它自己的獨立數組拷貝。當數組僅被一個變量引用時,則不會有拷貝發生。
|
||||
|
||||
在上一個示例的最後,`b`和`c`都引用了同一個數組。此時在`b`上調用`unshare`方法則會將`b`變成一個唯一個拷貝:
|
||||
|
||||
```swift
|
||||
b.unshare()
|
||||
```
|
||||
|
||||
在`unshare`方法調用後再修改`b`中第一個元素的值,這三個數組(`a`,`b`,`c`)會返回不同的三個值:
|
||||
|
||||
```swift
|
||||
b[0] = -105
|
||||
println(a[0])
|
||||
// 77
|
||||
println(b[0])
|
||||
// -105
|
||||
println(c[0])
|
||||
// 42
|
||||
```
|
||||
|
||||
|
||||
### 判定兩個數組是否共用相同元素
|
||||
|
||||
我們通過使用恆等運算符(identity operators)( === 和 !==)來判定兩個數組或子數組共用相同的儲存空間或元素。
|
||||
|
||||
下面這個示例使用了「等同(identical to)」 運算符(===) 來判定`b`和`c`是否共用相同的數組元素:
|
||||
|
||||
```swift
|
||||
if b === c {
|
||||
println("b and c still share the same array elements.")
|
||||
} else {
|
||||
println("b and c now refer to two independent sets of array elements.")
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// 輸出 "b and c now refer totwo independent sets of array elements."
|
||||
```
|
||||
|
||||
此外,我們還可以使用恆等運算符來判定兩個子數組是否共用相同的元素。下面這個示例中,比較了`b`的兩個相等的子數組,並且確定了這兩個子數組都引用相同的元素:
|
||||
|
||||
```swift
|
||||
if b[0...1] === b[0...1] {
|
||||
println("These two subarrays share the same elements.")
|
||||
} else {
|
||||
println("These two subarrays do not share the same elements.")
|
||||
}
|
||||
// 輸出 "These two subarrays share the same elements."
|
||||
```
|
||||
|
||||
### 強制複製數組
|
||||
|
||||
我們通過調用數組的`copy`方法進行強制顯式複製。這個方法對數組進行了淺拷貝(shallow copy),並且返回一個包含此拷貝數組的新數組。
|
||||
|
||||
下面這個示例中定義了一個`names`數組,其包含了七個人名。還定義了一個`copiedNames`變量,用以儲存在`names`上調用`copy`方法所返回的結果:
|
||||
|
||||
```swift
|
||||
var names = ["Mohsen", "Hilary", "Justyn", "Amy", "Rich", "Graham", "Vic"]
|
||||
var copiedNames = names.copy()
|
||||
```
|
||||
|
||||
我們可以通過修改數組中某一個元素,並且檢查另一個數組中對應元素的方法來判定`names`數組確已被複製。如果你將`copiedNames`中第一個元素從"`Mohsen`"修改為"`Mo`",則`names`數組返回的仍是拷貝發生前的"`Mohsen`":
|
||||
|
||||
```swift
|
||||
copiedName[0] = "Mo"
|
||||
println(name[0])
|
||||
// 輸出 "Mohsen"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
如果你僅需要確保你對數組的引用是唯一引用,請調用`unshare`方法,而不是`copy`方法。`unshare`方法僅會在確有必要時才會創建數組拷貝。`copy`方法會在任何時候都創建一個新的拷貝,即使引用已經是唯一引用。
|
||||
|
428
source-tw/chapter2/10_Properties.md
Normal file
428
source-tw/chapter2/10_Properties.md
Normal file
@ -0,0 +1,428 @@
|
||||
> 翻譯:[shinyzhu](https://github.com/shinyzhu)
|
||||
> 校對:[pp-prog](https://github.com/pp-prog)
|
||||
|
||||
# 屬性 (Properties)
|
||||
---
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [存儲屬性(Stored Properties)](#stored_properties)
|
||||
- [計算屬性(Computed Properties)](#computed_properties)
|
||||
- [屬性觀察器(Property Observers)](#property_observers)
|
||||
- [全局變量和局部變量(Global and Local Variables)](global_and_local_variables)
|
||||
- [類型屬性(Type Properties)](#type_properties)
|
||||
|
||||
**屬性**將值跟特定的類、結構或枚舉關聯。存儲屬性存儲常量或變量作為實例的一部分,計算屬性計算(而不是存儲)一個值。計算屬性可以用於類、結構體和枚舉裡,存儲屬性只能用於類和結構體。
|
||||
|
||||
存儲屬性和計算屬性通常用於特定類型的實例,但是,屬性也可以直接用於類型本身,這種屬性稱為類型屬性。
|
||||
|
||||
另外,還可以定義屬性觀察器來監控屬性值的變化,以此來觸發一個自定義的操作。屬性觀察器可以添加到自己寫的存儲屬性上,也可以添加到從父類繼承的屬性上。
|
||||
|
||||
<a name="stored_properties"></a>
|
||||
## 存儲屬性
|
||||
|
||||
簡單來說,一個存儲屬性就是存儲在特定類或結構體的實例裡的一個常量或變量,存儲屬性可以是*變量存儲屬性*(用關鍵字`var`定義),也可以是*常量存儲屬性*(用關鍵字`let`定義)。
|
||||
|
||||
可以在定義存儲屬性的時候指定默認值,請參考[構造過程](../chapter2/14_Initialization.html)一章的[默認屬性值](../chapter2/14_Initialization.html#default_property_values)一節。也可以在構造過程中設置或修改存儲屬性的值,甚至修改常量存儲屬性的值,請參考[構造過程](../chapter2/14_Initialization.html)一章的[在初始化階段修改常量存儲屬性](../chapter2/14_Initialization.html#modifying_constant_properties_during_initialization)一節。
|
||||
|
||||
下面的例子定義了一個名為`FixedLengthRange`的結構體,它描述了一個在創建後無法修改值域寬度的區間:
|
||||
|
||||
```swift
|
||||
struct FixedLengthRange {
|
||||
var firstValue: Int
|
||||
let length: Int
|
||||
}
|
||||
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
|
||||
// 該區間表示整數0,1,2
|
||||
rangeOfThreeItems.firstValue = 6
|
||||
// 該區間現在表示整數6,7,8
|
||||
```
|
||||
|
||||
`FixedLengthRange`的實例包含一個名為`firstValue`的變量存儲屬性和一個名為`length`的常量存儲屬性。在上面的例子中,`length`在創建實例的時候被賦值,因為它是一個常量存儲屬性,所以之後無法修改它的值。
|
||||
|
||||
<a name="stored_properties_of_constant_structure_instances"></a>
|
||||
### 常量和存儲屬性
|
||||
|
||||
如果創建了一個結構體的實例並賦值給一個常量,則無法修改實例的任何屬性,即使定義了變量存儲屬性:
|
||||
|
||||
```swift
|
||||
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
|
||||
// 該區間表示整數0,1,2,3
|
||||
rangeOfFourItems.firstValue = 6
|
||||
// 儘管 firstValue 是個變量屬性,這裡還是會報錯
|
||||
```
|
||||
|
||||
因為`rangeOfFourItems`聲明成了常量(用`let`關鍵字),即使`firstValue`是一個變量屬性,也無法再修改它了。
|
||||
|
||||
這種行為是由於結構體(struct)屬於*值類型*。當值類型的實例被聲明為常量的時候,它的所有屬性也就成了常量。
|
||||
|
||||
屬於*引用類型*的類(class)則不一樣,把一個引用類型的實例賦給一個常量後,仍然可以修改實例的變量屬性。
|
||||
|
||||
<a name="lazy_stored_properties"></a>
|
||||
### 延遲存儲屬性
|
||||
|
||||
延遲存儲屬性是指當第一次被調用的時候才會計算其初始值的屬性。在屬性聲明前使用`@lazy`來標示一個延遲存儲屬性。
|
||||
|
||||
> 注意:
|
||||
> 必須將延遲存儲屬性聲明成變量(使用`var`關鍵字),因為屬性的值在實例構造完成之前可能無法得到。而常量屬性在構造過程完成之前必須要有初始值,因此無法聲明成延遲屬性。
|
||||
|
||||
延遲屬性很有用,當屬性的值依賴於在實例的構造過程結束前無法知道具體值的外部因素時,或者當屬性的值需要複雜或大量計算時,可以只在需要的時候來計算它。
|
||||
|
||||
下面的例子使用了延遲存儲屬性來避免複雜類的不必要的初始化。例子中定義了`DataImporter`和`DataManager`兩個類,下面是部分代碼:
|
||||
|
||||
```swift
|
||||
class DataImporter {
|
||||
/*
|
||||
DataImporter 是一個將外部文件中的數據導入的類。
|
||||
這個類的初始化會消耗不少時間。
|
||||
*/
|
||||
var fileName = "data.txt"
|
||||
// 這是提供數據導入功能
|
||||
}
|
||||
|
||||
class DataManager {
|
||||
@lazy var importer = DataImporter()
|
||||
var data = String[]()
|
||||
// 這是提供數據管理功能
|
||||
}
|
||||
|
||||
let manager = DataManager()
|
||||
manager.data += "Some data"
|
||||
manager.data += "Some more data"
|
||||
// DataImporter 實例的 importer 屬性還沒有被創建
|
||||
```
|
||||
|
||||
`DataManager`類包含一個名為`data`的存儲屬性,初始值是一個空的字符串(`String`)數組。雖然沒有寫出全部代碼,`DataManager`類的目的是管理和提供對這個字符串數組的訪問。
|
||||
|
||||
`DataManager`的一個功能是從文件導入數據,該功能由`DataImporter`類提供,`DataImporter`需要消耗不少時間完成初始化:因為它的實例在初始化時可能要打開文件,還要讀取文件內容到內存。
|
||||
|
||||
`DataManager`也可能不從文件中導入數據。所以當`DataManager`的實例被創建時,沒必要創建一個`DataImporter`的實例,更明智的是當用到`DataImporter`的時候才去創建它。
|
||||
|
||||
由於使用了`@lazy`,`importer`屬性只有在第一次被訪問的時候才被創建。比如訪問它的屬性`fileName`時:
|
||||
|
||||
```swift
|
||||
println(manager.importer.fileName)
|
||||
// DataImporter 實例的 importer 屬性現在被創建了
|
||||
// 輸出 "data.txt」
|
||||
```
|
||||
|
||||
<a name="stored_properties_and_instance_variables"></a>
|
||||
### 存儲屬性和實例變量
|
||||
|
||||
如果您有過 Objective-C 經驗,應該知道Objective-C為類實例存儲值和引提供兩種方用。對於屬性來說,也可以使用實例變量作為屬性值的後端存儲。
|
||||
|
||||
Swift 編程語言中把這些理論統一用屬性來實現。Swift 中的屬性沒有對應的實例變量,屬性的後端存儲也無法直接訪問。這就避免了不同場景下訪問方式的困擾,同時也將屬性的定義簡化成一個語句。
|
||||
一個類型中屬性的全部信息——包括命名、類型和內存管理特徵——都在唯一一個地方(類型定義中)定義。
|
||||
|
||||
<a name="computed_properties"></a>
|
||||
## 計算屬性
|
||||
|
||||
除存儲屬性外,類、結構體和枚舉可以定義*計算屬性*,計算屬性不直接存儲值,而是提供一個 getter 來獲取值,一個可選的 setter 來間接設置其他屬性或變量的值。
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set(newCenter) {
|
||||
origin.x = newCenter.x - (size.width / 2)
|
||||
origin.y = newCenter.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
var square = Rect(origin: Point(x: 0.0, y: 0.0),
|
||||
size: Size(width: 10.0, height: 10.0))
|
||||
let initialSquareCenter = square.center
|
||||
square.center = Point(x: 15.0, y: 15.0)
|
||||
println("square.origin is now at (\(square.origin.x), \(square.origin.y))")
|
||||
// 輸出 "square.origin is now at (10.0, 10.0)」
|
||||
```
|
||||
|
||||
這個例子定義了 3 個幾何形狀的結構體:
|
||||
|
||||
- `Point`封裝了一個`(x, y)`的坐標
|
||||
- `Size`封裝了一個`width`和`height`
|
||||
- `Rect`表示一個有原點和尺寸的矩形
|
||||
|
||||
`Rect`也提供了一個名為`center`的計算屬性。一個矩形的中心點可以從原點和尺寸來算出,所以不需要將它以顯式聲明的`Point`來保存。`Rect`的計算屬性`center`提供了自定義的 getter 和 setter 來獲取和設置矩形的中心點,就像它有一個存儲屬性一樣。
|
||||
|
||||
例子中接下來創建了一個名為`square`的`Rect`實例,初始值原點是`(0, 0)`,寬度高度都是`10`。如圖所示藍色正方形。
|
||||
|
||||
`square`的`center`屬性可以通過點運算符(`square.center`)來訪問,這會調用 getter 來獲取屬性的值。跟直接返回已經存在的值不同,getter 實際上通過計算然後返回一個新的`Point`來表示`square`的中心點。如代碼所示,它正確返回了中心點`(5, 5)`。
|
||||
|
||||
`center`屬性之後被設置了一個新的值`(15, 15)`,表示向右上方移動正方形到如圖所示橙色正方形的位置。設置屬性`center`的值會調用 setter 來修改屬性`origin`的`x`和`y`的值,從而實現移動正方形到新的位置。
|
||||
|
||||
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
|
||||
|
||||
<a name="shorthand_setter_declaration"></a>
|
||||
### 便捷 setter 聲明
|
||||
|
||||
如果計算屬性的 setter 沒有定義表示新值的參數名,則可以使用默認名稱`newValue`。下面是使用了便捷 setter 聲明的`Rect`結構體代碼:
|
||||
|
||||
```swift
|
||||
struct AlternativeRect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set {
|
||||
origin.x = newValue.x - (size.width / 2)
|
||||
origin.y = newValue.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="readonly_computed_properties"></a>
|
||||
### 只讀計算屬性
|
||||
|
||||
只有 getter 沒有 setter 的計算屬性就是*只讀計算屬性*。只讀計算屬性總是返回一個值,可以通過點運算符訪問,但不能設置新的值。
|
||||
|
||||
<<<<<<< HEAD
|
||||
> 注意:
|
||||
> 必須使用`var`關鍵字定義計算屬性,包括只讀計算屬性,因為他們的值不是固定的。`let`關鍵字只用來聲明常量屬性,表示初始化後再也無法修改的值。
|
||||
=======
|
||||
> 注意:
|
||||
>
|
||||
> 必須使用`var`關鍵字定義計算屬性,包括只讀計算屬性,因為它們的值不是固定的。`let`關鍵字只用來聲明常量屬性,表示初始化後再也無法修改的值。
|
||||
>>>>>>> a516af6a531a104ec88da0d236ecf389a5ec72af
|
||||
|
||||
只讀計算屬性的聲明可以去掉`get`關鍵字和花括號:
|
||||
|
||||
```swift
|
||||
struct Cuboid {
|
||||
var width = 0.0, height = 0.0, depth = 0.0
|
||||
var volume: Double {
|
||||
return width * height * depth
|
||||
}
|
||||
}
|
||||
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
|
||||
println("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
|
||||
// 輸出 "the volume of fourByFiveByTwo is 40.0"
|
||||
```
|
||||
|
||||
這個例子定義了一個名為`Cuboid`的結構體,表示三維空間的立方體,包含`width`、`height`和`depth`屬性,還有一個名為`volume`的只讀計算屬性用來返回立方體的體積。設置`volume`的值毫無意義,因為通過`width`、`height`和`depth`就能算出`volume`。然而,`Cuboid`提供一個只讀計算屬性來讓外部用戶直接獲取體積是很有用的。
|
||||
|
||||
<a name="property_observers"></a>
|
||||
## 屬性觀察器
|
||||
|
||||
*屬性觀察器*監控和響應屬性值的變化,每次屬性被設置值的時候都會調用屬性觀察器,甚至新的值和現在的值相同的時候也不例外。
|
||||
|
||||
可以為除了延遲存儲屬性之外的其他存儲屬性添加屬性觀察器,也可以通過重載屬性的方式為繼承的屬性(包括存儲屬性和計算屬性)添加屬性觀察器。屬性重載請參考[繼承](chapter/13_Inheritance.html)一章的[重載](chapter/13_Inheritance.html#overriding)。
|
||||
|
||||
> 注意:
|
||||
> 不需要為無法重載的計算屬性添加屬性觀察器,因為可以通過 setter 直接監控和響應值的變化。
|
||||
|
||||
可以為屬性添加如下的一個或全部觀察器:
|
||||
|
||||
- `willSet`在設置新的值之前調用
|
||||
- `didSet`在新的值被設置之後立即調用
|
||||
|
||||
`willSet`觀察器會將新的屬性值作為固定參數傳入,在`willSet`的實現代碼中可以為這個參數指定一個名稱,如果不指定則參數仍然可用,這時使用默認名稱`newValue`表示。
|
||||
|
||||
類似地,`didSet`觀察器會將舊的屬性值作為參數傳入,可以為該參數命名或者使用默認參數名`oldValue`。
|
||||
|
||||
<<<<<<< HEAD
|
||||
> 注意:
|
||||
> `willSet`和`didSet`觀察器在屬性初始化過程中不會被調用,他們只會當屬性的值在初始化之外的地方被設置時被調用。
|
||||
=======
|
||||
> 注意:
|
||||
>
|
||||
> `willSet`和`didSet`觀察器在屬性初始化過程中不會被調用,它們只會當屬性的值在初始化之外的地方被設置時被調用。
|
||||
>>>>>>> a516af6a531a104ec88da0d236ecf389a5ec72af
|
||||
|
||||
這裡是一個`willSet`和`didSet`的實際例子,其中定義了一個名為`StepCounter`的類,用來統計當人步行時的總步數,可以跟計步器或其他日常鍛煉的統計裝置的輸入數據配合使用。
|
||||
|
||||
```swift
|
||||
class StepCounter {
|
||||
var totalSteps: Int = 0 {
|
||||
willSet(newTotalSteps) {
|
||||
println("About to set totalSteps to \(newTotalSteps)")
|
||||
}
|
||||
didSet {
|
||||
if totalSteps > oldValue {
|
||||
println("Added \(totalSteps - oldValue) steps")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let stepCounter = StepCounter()
|
||||
stepCounter.totalSteps = 200
|
||||
// About to set totalSteps to 200
|
||||
// Added 200 steps
|
||||
stepCounter.totalSteps = 360
|
||||
// About to set totalSteps to 360
|
||||
// Added 160 steps
|
||||
stepCounter.totalSteps = 896
|
||||
// About to set totalSteps to 896
|
||||
// Added 536 steps
|
||||
```
|
||||
|
||||
`StepCounter`類定義了一個`Int`類型的屬性`totalSteps`,它是一個存儲屬性,包含`willSet`和`didSet`觀察器。
|
||||
|
||||
當`totalSteps`設置新值的時候,它的`willSet`和`didSet`觀察器都會被調用,甚至當新的值和現在的值完全相同也會調用。
|
||||
|
||||
例子中的`willSet`觀察器將表示新值的參數自定義為`newTotalSteps`,這個觀察器只是簡單的將新的值輸出。
|
||||
|
||||
`didSet`觀察器在`totalSteps`的值改變後被調用,它把新的值和舊的值進行對比,如果總的步數增加了,就輸出一個消息表示增加了多少步。`didSet`沒有提供自定義名稱,所以默認值`oldValue`表示舊值的參數名。
|
||||
|
||||
> 注意:
|
||||
> 如果在`didSet`觀察器裡為屬性賦值,這個值會替換觀察器之前設置的值。
|
||||
|
||||
<a name="global_and_local_variables"></a>
|
||||
##全局變量和局部變量
|
||||
|
||||
計算屬性和屬性觀察器所描述的模式也可以用於*全局變量*和*局部變量*,全局變量是在函數、方法、閉包或任何類型之外定義的變量,局部變量是在函數、方法或閉包內部定義的變量。
|
||||
|
||||
前面章節提到的全局或局部變量都屬於存儲型變量,跟存儲屬性類似,它提供特定類型的存儲空間,並允許讀取和寫入。
|
||||
|
||||
另外,在全局或局部範圍都可以定義計算型變量和為存儲型變量定義觀察器,計算型變量跟計算屬性一樣,返回一個計算的值而不是存儲值,聲明格式也完全一樣。
|
||||
|
||||
> 注意:
|
||||
> 全局的常量或變量都是延遲計算的,跟[延遲存儲屬性](#lazy_stored_properties)相似,不同的地方在於,全局的常量或變量不需要標記`@lazy`特性。
|
||||
> 局部範圍的常量或變量不會延遲計算。
|
||||
|
||||
<a name="type_properties"></a>
|
||||
##類型屬性
|
||||
|
||||
實例的屬性屬於一個特定類型實例,每次類型實例化後都擁有自己的一套屬性值,實例之間的屬性相互獨立。
|
||||
|
||||
也可以為類型本身定義屬性,不管類型有多少個實例,這些屬性都只有唯一一份。這種屬性就是*類型屬性*。
|
||||
|
||||
類型屬性用於定義特定類型所有實例共享的數據,比如所有實例都能用的一個常量(就像 C 語言中的靜態常量),或者所有實例都能訪問的一個變量(就像 C 語言中的靜態變量)。
|
||||
|
||||
對於值類型(指結構體和枚舉)可以定義存儲型和計算型類型屬性,對於類(class)則只能定義計算型類型屬性。
|
||||
|
||||
值類型的存儲型類型屬性可以是變量或常量,計算型類型屬性跟實例的計算屬性一樣定義成變量屬性。
|
||||
|
||||
> 注意:
|
||||
> 跟實例的存儲屬性不同,必須給存儲型類型屬性指定默認值,因為類型本身無法在初始化過程中使用構造器給類型屬性賦值。
|
||||
|
||||
<a name="type_property_syntax"></a>
|
||||
###類型屬性語法
|
||||
|
||||
在 C 或 Objective-C 中,靜態常量和靜態變量的定義是通過特定類型加上`global`關鍵字。在 Swift 編程語言中,類型屬性是作為類型定義的一部分寫在類型最外層的花括號內,因此它的作用範圍也就在類型支持的範圍內。
|
||||
|
||||
使用關鍵字`static`來定義值類型的類型屬性,關鍵字`class`來為類(class)定義類型屬性。下面的例子演示了存儲型和計算型類型屬性的語法:
|
||||
|
||||
```swift
|
||||
struct SomeStructure {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
// 這裡返回一個 Int 值
|
||||
}
|
||||
}
|
||||
enum SomeEnumeration {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
// 這裡返回一個 Int 值
|
||||
}
|
||||
}
|
||||
class SomeClass {
|
||||
class var computedTypeProperty: Int {
|
||||
// 這裡返回一個 Int 值
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 例子中的計算型類型屬性是只讀的,但也可以定義可讀可寫的計算型類型屬性,跟實例計算屬性的語法類似。
|
||||
|
||||
<a name="querying_and_setting_type_properties"></a>
|
||||
###獲取和設置類型屬性的值
|
||||
|
||||
跟實例的屬性一樣,類型屬性的訪問也是通過點運算符來進行,但是,類型屬性是通過類型本身來獲取和設置,而不是通過實例。比如:
|
||||
|
||||
```swift
|
||||
println(SomeClass.computedTypeProperty)
|
||||
// 輸出 "42"
|
||||
|
||||
println(SomeStructure.storedTypeProperty)
|
||||
// 輸出 "Some value."
|
||||
SomeStructure.storedTypeProperty = "Another value."
|
||||
println(SomeStructure.storedTypeProperty)
|
||||
// 輸出 "Another value.」
|
||||
```
|
||||
|
||||
下面的例子定義了一個結構體,使用兩個存儲型類型屬性來表示多個聲道的聲音電平值,每個聲道有一個 0 到 10 之間的整數表示聲音電平值。
|
||||
|
||||
後面的圖表展示了如何聯合使用兩個聲道來表示一個立體聲的聲音電平值。當聲道的電平值是 0,沒有一個燈會亮;當聲道的電平值是 10,所有燈點亮。本圖中,左聲道的電平是 9,右聲道的電平是 7。
|
||||
|
||||
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/staticPropertiesVUMeter_2x.png" alt="Static Properties VUMeter" width="243" height="357" />
|
||||
|
||||
上面所描述的聲道模型使用`AudioChannel`結構體來表示:
|
||||
|
||||
```swift
|
||||
struct AudioChannel {
|
||||
static let thresholdLevel = 10
|
||||
static var maxInputLevelForAllChannels = 0
|
||||
var currentLevel: Int = 0 {
|
||||
didSet {
|
||||
if currentLevel > AudioChannel.thresholdLevel {
|
||||
// 將新電平值設置為閥值
|
||||
currentLevel = AudioChannel.thresholdLevel
|
||||
}
|
||||
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
|
||||
// 存儲當前電平值作為新的最大輸入電平
|
||||
AudioChannel.maxInputLevelForAllChannels = currentLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
結構`AudioChannel`定義了 2 個存儲型類型屬性來實現上述功能。第一個是`thresholdLevel`,表示聲音電平的最大上限閾值,它是一個取值為 10 的常量,對所有實例都可見,如果聲音電平高於 10,則取最大上限值 10(見後面描述)。
|
||||
|
||||
第二個類型屬性是變量存儲型屬性`maxInputLevelForAllChannels`,它用來表示所有`AudioChannel`實例的電平值的最大值,初始值是 0。
|
||||
|
||||
`AudioChannel`也定義了一個名為`currentLevel`的實例存儲屬性,表示當前聲道現在的電平值,取值為 0 到 10。
|
||||
|
||||
屬性`currentLevel`包含`didSet`屬性觀察器來檢查每次新設置後的屬性值,有如下兩個檢查:
|
||||
|
||||
- 如果`currentLevel`的新值大於允許的閾值`thresholdLevel`,屬性觀察器將`currentLevel`的值限定為閾值`thresholdLevel`。
|
||||
- 如果修正後的`currentLevel`值大於任何之前任意`AudioChannel`實例中的值,屬性觀察器將新值保存在靜態屬性`maxInputLevelForAllChannels`中。
|
||||
|
||||
> 注意:
|
||||
> 在第一個檢查過程中,`didSet`屬性觀察器將`currentLevel`設置成了不同的值,但這時不會再次調用屬性觀察器。
|
||||
|
||||
可以使用結構體`AudioChannel`來創建表示立體聲系統的兩個聲道`leftChannel`和`rightChannel`:
|
||||
|
||||
```swift
|
||||
var leftChannel = AudioChannel()
|
||||
var rightChannel = AudioChannel()
|
||||
```
|
||||
|
||||
如果將左聲道的電平設置成 7,類型屬性`maxInputLevelForAllChannels`也會更新成 7:
|
||||
|
||||
```swift
|
||||
leftChannel.currentLevel = 7
|
||||
println(leftChannel.currentLevel)
|
||||
// 輸出 "7"
|
||||
println(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 輸出 "7"
|
||||
```
|
||||
|
||||
如果試圖將右聲道的電平設置成 11,則會將右聲道的`currentLevel`修正到最大值 10,同時`maxInputLevelForAllChannels`的值也會更新到 10:
|
||||
|
||||
```swift
|
||||
rightChannel.currentLevel = 11
|
||||
println(rightChannel.currentLevel)
|
||||
// 輸出 "10"
|
||||
println(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 輸出 "10"
|
||||
```
|
309
source-tw/chapter2/11_Methods.md
Normal file
309
source-tw/chapter2/11_Methods.md
Normal file
@ -0,0 +1,309 @@
|
||||
> 翻譯:[pp-prog](https://github.com/pp-prog)
|
||||
> 校對:[zqp](https://github.com/zqp)
|
||||
|
||||
# 方法(Methods)
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [實例方法(Instance Methods](#instance_methods)
|
||||
- [類型方法(Type Methods)](#type_methods)
|
||||
|
||||
**方法**是與某些特定類型相關聯的函數。類、結構體、枚舉都可以定義實例方法;實例方法為給定類型的實例封裝了具體的任務與功能。類、結構體、枚舉也可以定義類型方法;類型方法與類型本身相關聯。類型方法與 Objective-C 中的類方法(class methods)相似。
|
||||
|
||||
結構體和枚舉能夠定義方法是 Swift 與 C/Objective-C 的主要區別之一。在 Objective-C 中,類是唯一能定義方法的類型。但在 Swift 中,你不僅能選擇是否要定義一個類/結構體/枚舉,還能靈活的在你創建的類型(類/結構體/枚舉)上定義方法。
|
||||
|
||||
<a name="instance_methods"></a>
|
||||
## 實例方法(Instance Methods)
|
||||
|
||||
**實例方法**是屬於某個特定類、結構體或者枚舉類型實例的方法。實例方法提供訪問和修改實例屬性的方法或提供與實例目的相關的功能,並以此來支撐實例的功能。實例方法的語法與函數完全一致,詳情參見[函數](../charpter2/06_Functions.md)。
|
||||
|
||||
實例方法要寫在它所屬的類型的前後大括號之間。實例方法能夠隱式訪問它所屬類型的所有的其他實例方法和屬性。實例方法只能被它所屬的類的某個特定實例調用。實例方法不能脫離於現存的實例而被調用。
|
||||
|
||||
下面的例子,定義一個很簡單的類`Counter`,`Counter`能被用來對一個動作發生的次數進行計數:
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count = 0
|
||||
func increment() {
|
||||
count++
|
||||
}
|
||||
func incrementBy(amount: Int) {
|
||||
count += amount
|
||||
}
|
||||
func reset() {
|
||||
count = 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Counter`類定義了三個實例方法:
|
||||
- `increment`讓計數器按一遞增;
|
||||
- `incrementBy(amount: Int)`讓計數器按一個指定的整數值遞增;
|
||||
- `reset`將計數器重置為0。
|
||||
|
||||
`Counter`這個類還聲明了一個可變屬性`count`,用它來保持對當前計數器值的追蹤。
|
||||
|
||||
和調用屬性一樣,用點語法(dot syntax)調用實例方法:
|
||||
|
||||
```swift
|
||||
let counter = Counter()
|
||||
// 初始計數值是0
|
||||
counter.increment()
|
||||
// 計數值現在是1
|
||||
counter.incrementBy(5)
|
||||
// 計數值現在是6
|
||||
counter.reset()
|
||||
// 計數值現在是0
|
||||
```
|
||||
|
||||
<a name="local_and_external_parameter"></a>
|
||||
### 方法的局部參數名稱和外部參數名稱(Local and External Parameter Names for Methods)
|
||||
|
||||
函數參數可以同時有一個局部名稱(在函數體內部使用)和一個外部名稱(在調用函數時使用),詳情參見[函數的外部參數名](06_Functions.html)。方法參數也一樣(因為方法就是函數,只是這個函數與某個類型相關聯了)。但是,方法和函數的局部名稱和外部名稱的默認行為是不一樣的。
|
||||
|
||||
Swift 中的方法和 Objective-C 中的方法極其相似。像在 Objective-C 中一樣,Swift 中方法的名稱通常用一個介詞指向方法的第一個參數,比如:`with`,`for`,`by`等等。前面的`Counter`類的例子中`incrementBy`方法就是這樣的。介詞的使用讓方法在被調用時能像一個句子一樣被解讀。和函數參數不同,對於方法的參數,Swift 使用不同的默認處理方式,這可以讓方法命名規範更容易寫。
|
||||
|
||||
具體來說,Swift 默認僅給方法的第一個參數名稱一個局部參數名稱;默認同時給第二個和後續的參數名稱局部參數名稱和外部參數名稱。這個約定與典型的命名和調用約定相適應,與你在寫 Objective-C 的方法時很相似。這個約定還讓表達式方法在調用時不需要再限定參數名稱。
|
||||
|
||||
看看下面這個`Counter`的另一個版本(它定義了一個更複雜的`incrementBy`方法):
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count: Int = 0
|
||||
func incrementBy(amount: Int, numberOfTimes: Int) {
|
||||
count += amount * numberOfTimes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`incrementBy`方法有兩個參數: `amount`和`numberOfTimes`。默認情況下,Swift 只把`amount`當作一個局部名稱,但是把`numberOfTimes`即看作局部名稱又看作外部名稱。下面調用這個方法:
|
||||
|
||||
```swift
|
||||
let counter = Counter()
|
||||
counter.incrementBy(5, numberOfTimes: 3)
|
||||
// counter value is now 15
|
||||
```
|
||||
|
||||
你不必為第一個參數值再定義一個外部變量名:因為從函數名`incrementBy`已經能很清楚地看出它的作用。但是第二個參數,就要被一個外部參數名稱所限定,以便在方法被調用時明確它的作用。
|
||||
|
||||
這種默認的行為能夠有效的處理方法(method),類似於在參數`numberOfTimes`前寫一個井號(`#`):
|
||||
|
||||
```swift
|
||||
func incrementBy(amount: Int, #numberOfTimes: Int) {
|
||||
count += amount * numberOfTimes
|
||||
}
|
||||
```
|
||||
|
||||
這種默認行為使上面代碼意味著:在 Swift 中定義方法使用了與 Objective-C 同樣的語法風格,並且方法將以自然表達式的方式被調用。
|
||||
|
||||
<a name="modifying_external_parameter"></a>
|
||||
### 修改方法的外部參數名稱(Modifying External Parameter Name Behavior for Methods)
|
||||
|
||||
有時為方法的第一個參數提供一個外部參數名稱是非常有用的,儘管這不是默認的行為。你可以自己添加一個顯式的外部名稱或者用一個井號(`#`)作為第一個參數的前綴來把這個局部名稱當作外部名稱使用。
|
||||
|
||||
相反,如果你不想為方法的第二個及後續的參數提供一個外部名稱,可以通過使用下劃線(`_`)作為該參數的顯式外部名稱,這樣做將覆蓋默認行為。
|
||||
|
||||
<a name="self_property"></a>
|
||||
## `self`屬性(The self Property)
|
||||
|
||||
類型的每一個實例都有一個隱含屬性叫做`self`,`self`完全等同於該實例本身。你可以在一個實例的實例方法中使用這個隱含的`self`屬性來引用當前實例。
|
||||
|
||||
上面例子中的`increment`方法還可以這樣寫:
|
||||
|
||||
```swift
|
||||
func increment() {
|
||||
self.count++
|
||||
}
|
||||
```
|
||||
|
||||
實際上,你不必在你的代碼裡面經常寫`self`。不論何時,只要在一個方法中使用一個已知的屬性或者方法名稱,如果你沒有明確的寫`self`,Swift 假定你是指當前實例的屬性或者方法。這種假定在上面的`Counter`中已經示範了:`Counter`中的三個實例方法中都使用的是`count`(而不是`self.count`)。
|
||||
|
||||
使用這條規則的主要場景是實例方法的某個參數名稱與實例的某個屬性名稱相同的時候。在這種情況下,參數名稱享有優先權,並且在引用屬性時必須使用一種更嚴格的方式。這時你可以使用`self`屬性來區分參數名稱和屬性名稱。
|
||||
|
||||
下面的例子中,`self`消除方法參數`x`和實例屬性`x`之間的歧義:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
func isToTheRightOfX(x: Double) -> Bool {
|
||||
return self.x > x
|
||||
}
|
||||
}
|
||||
let somePoint = Point(x: 4.0, y: 5.0)
|
||||
if somePoint.isToTheRightOfX(1.0) {
|
||||
println("This point is to the right of the line where x == 1.0")
|
||||
}
|
||||
// 輸出 "This point is to the right of the line where x == 1.0"(這個點在x等於1.0這條線的右邊)
|
||||
```
|
||||
|
||||
如果不使用`self`前綴,Swift 就認為兩次使用的`x`都指的是名稱為`x`的函數參數。
|
||||
|
||||
<a name="modifying_value_types"></a>
|
||||
### 在實例方法中修改值類型(Modifying Value Types from Within Instance Methods)
|
||||
|
||||
結構體和枚舉是**值類型**。一般情況下,值類型的屬性不能在它的實例方法中被修改。
|
||||
|
||||
但是,如果你確實需要在某個具體的方法中修改結構體或者枚舉的屬性,你可以選擇`變異(mutating)`這個方法,然後方法就可以從方法內部改變它的屬性;並且它做的任何改變在方法結束時還會保留在原始結構中。方法還可以給它隱含的`self`屬性賦值一個全新的實例,這個新實例在方法結束後將替換原來的實例。
|
||||
|
||||
要使用`變異`方法, 將關鍵字`mutating` 放到方法的`func`關鍵字之前就可以了:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveByX(deltaX: Double, y deltaY: Double) {
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
}
|
||||
}
|
||||
var somePoint = Point(x: 1.0, y: 1.0)
|
||||
somePoint.moveByX(2.0, y: 3.0)
|
||||
println("The point is now at (\(somePoint.x), \(somePoint.y))")
|
||||
// 輸出 "The point is now at (3.0, 4.0)"
|
||||
```
|
||||
|
||||
上面的`Point`結構體定義了一個變異方法(mutating method)`moveByX`,`moveByX`用來移動點。`moveByX`方法在被調用時修改了這個點,而不是返回一個新的點。方法定義時加上`mutating`關鍵字,這才讓方法可以修改值類型的屬性。
|
||||
|
||||
注意:不能在結構體類型常量上調用變異方法,因為常量的屬性不能被改變,即使想改變的是常量的變量屬性也不行,詳情參見[存儲屬性和實例變量]("10_Properties.html")
|
||||
|
||||
```swift
|
||||
let fixedPoint = Point(x: 3.0, y: 3.0)
|
||||
fixedPoint.moveByX(2.0, y: 3.0)
|
||||
// this will report an error
|
||||
```
|
||||
|
||||
<a name="mutating_method_self"></a>
|
||||
### 在變異方法中給self賦值(Assigning to self Within a Mutating Method)
|
||||
|
||||
變異方法能夠賦給隱含屬性`self`一個全新的實例。上面`Point`的例子可以用下面的方式改寫:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveByX(deltaX: Double, y deltaY: Double) {
|
||||
self = Point(x: x + deltaX, y: y + deltaY)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
新版的變異方法`moveByX`創建了一個新的結構(它的 x 和 y 的值都被設定為目標值)。調用這個版本的方法和調用上個版本的最終結果是一樣的。
|
||||
|
||||
枚舉的變異方法可以把`self`設置為相同的枚舉類型中不同的成員:
|
||||
|
||||
```swift
|
||||
enum TriStateSwitch {
|
||||
case Off, Low, High
|
||||
mutating func next() {
|
||||
switch self {
|
||||
case Off:
|
||||
self = Low
|
||||
case Low:
|
||||
self = High
|
||||
case High:
|
||||
self = Off
|
||||
}
|
||||
}
|
||||
}
|
||||
var ovenLight = TriStateSwitch.Low
|
||||
ovenLight.next()
|
||||
// ovenLight 現在等於 .High
|
||||
ovenLight.next()
|
||||
// ovenLight 現在等於 .Off
|
||||
```
|
||||
|
||||
上面的例子中定義了一個三態開關的枚舉。每次調用`next`方法時,開關在不同的電源狀態(`Off`,`Low`,`High`)之前循環切換。
|
||||
|
||||
<a name="type_methods"></a>
|
||||
## 類型方法(Type Methods)
|
||||
|
||||
實例方法是被類型的某個實例調用的方法。你也可以定義類型本身調用的方法,這種方法就叫做**類型方法**。聲明類的類型方法,在方法的`func`關鍵字之前加上關鍵字`class`;聲明結構體和枚舉的類型方法,在方法的`func`關鍵字之前加上關鍵字`static`。
|
||||
|
||||
> 注意:
|
||||
> 在 Objective-C 裡面,你只能為 Objective-C 的類定義類型方法(type-level methods)。在 Swift 中,你可以為所有的類、結構體和枚舉定義類型方法:每一個類型方法都被它所支持的類型顯式包含。
|
||||
|
||||
類型方法和實例方法一樣用點語法調用。但是,你是在類型層面上調用這個方法,而不是在實例層面上調用。下面是如何在`SomeClass`類上調用類型方法的例子:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
class func someTypeMethod() {
|
||||
// type method implementation goes here
|
||||
}
|
||||
}
|
||||
SomeClass.someTypeMethod()
|
||||
```
|
||||
|
||||
在類型方法的方法體(body)中,`self`指向這個類型本身,而不是類型的某個實例。對於結構體和枚舉來說,這意味著你可以用`self`來消除靜態屬性和靜態方法參數之間的歧義(類似於我們在前面處理實例屬性和實例方法參數時做的那樣)。
|
||||
|
||||
一般來說,任何未限定的方法和屬性名稱,將會來自於本類中另外的類型級別的方法和屬性。一個類型方法可以調用本類中另一個類型方法的名稱,而無需在方法名稱前面加上類型名稱的前綴。同樣,結構體和枚舉的類型方法也能夠直接通過靜態屬性的名稱訪問靜態屬性,而不需要類型名稱前綴。
|
||||
|
||||
下面的例子定義了一個名為`LevelTracker`結構體。它監測玩家的遊戲發展情況(遊戲的不同層次或階段)。這是一個單人遊戲,但也可以存儲多個玩家在同一設備上的遊戲信息。
|
||||
|
||||
遊戲初始時,所有的遊戲等級(除了等級 1)都被鎖定。每次有玩家完成一個等級,這個等級就對這個設備上的所有玩家解鎖。`LevelTracker`結構體用靜態屬性和方法監測遊戲的哪個等級已經被解鎖。它還監測每個玩家的當前等級。
|
||||
|
||||
```swift
|
||||
struct LevelTracker {
|
||||
static var highestUnlockedLevel = 1
|
||||
static func unlockLevel(level: Int) {
|
||||
if level > highestUnlockedLevel { highestUnlockedLevel = level }
|
||||
}
|
||||
static func levelIsUnlocked(level: Int) -> Bool {
|
||||
return level <= highestUnlockedLevel
|
||||
}
|
||||
var currentLevel = 1
|
||||
mutating func advanceToLevel(level: Int) -> Bool {
|
||||
if LevelTracker.levelIsUnlocked(level) {
|
||||
currentLevel = level
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`LevelTracker`監測玩家的已解鎖的最高等級。這個值被存儲在靜態屬性`highestUnlockedLevel`中。
|
||||
|
||||
`LevelTracker`還定義了兩個類型方法與`highestUnlockedLevel`配合工作。第一個類型方法是`unlockLevel`:一旦新等級被解鎖,它會更新`highestUnlockedLevel`的值。第二個類型方法是`levelIsUnlocked`:如果某個給定的等級已經被解鎖,它將返回`true`。(注意:儘管我們沒有使用類似`LevelTracker.highestUnlockedLevel`的寫法,這個類型方法還是能夠訪問靜態屬性`highestUnlockedLevel`)
|
||||
|
||||
除了靜態屬性和類型方法,`LevelTracker`還監測每個玩家的進度。它用實例屬性`currentLevel`來監測玩家當前的等級。
|
||||
|
||||
為了便於管理`currentLevel`屬性,`LevelTracker`定義了實例方法`advanceToLevel`。這個方法會在更新`currentLevel`之前檢查所請求的新等級是否已經解鎖。`advanceToLevel`方法返回布爾值以指示是否能夠設置`currentLevel`。
|
||||
|
||||
下面,`Player`類使用`LevelTracker`來監測和更新每個玩家的發展進度:
|
||||
|
||||
```swift
|
||||
class Player {
|
||||
var tracker = LevelTracker()
|
||||
let playerName: String
|
||||
func completedLevel(level: Int) {
|
||||
LevelTracker.unlockLevel(level + 1)
|
||||
tracker.advanceToLevel(level + 1)
|
||||
}
|
||||
init(name: String) {
|
||||
playerName = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Player`類創建一個新的`LevelTracker`實例來監測這個用戶的發展進度。它提供了`completedLevel`方法:一旦玩家完成某個指定等級就調用它。這個方法為所有玩家解鎖下一等級,並且將當前玩家的進度更新為下一等級。(我們忽略了`advanceToLevel`返回的布爾值,因為之前調用`LevelTracker.unlockLevel`時就知道了這個等級已經被解鎖了)。
|
||||
|
||||
你還可以為一個新的玩家創建一個`Player`的實例,然後看這個玩家完成等級一時發生了什麼:
|
||||
|
||||
```swift
|
||||
var player = Player(name: "Argyrios")
|
||||
player.completedLevel(1)
|
||||
println("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
|
||||
// 輸出 "highest unlocked level is now 2"(最高等級現在是2)
|
||||
```
|
||||
|
||||
如果你創建了第二個玩家,並嘗試讓它開始一個沒有被任何玩家解鎖的等級,那麼這次設置玩家當前等級的嘗試將會失敗:
|
||||
|
||||
```swift
|
||||
player = Player(name: "Beto")
|
||||
if player.tracker.advanceToLevel(6) {
|
||||
println("player is now on level 6")
|
||||
} else {
|
||||
println("level 6 has not yet been unlocked")
|
||||
}
|
||||
// 輸出 "level 6 has not yet been unlocked"(等級6還沒被解鎖)
|
||||
```
|
167
source-tw/chapter2/12_Subscripts.md
Normal file
167
source-tw/chapter2/12_Subscripts.md
Normal file
@ -0,0 +1,167 @@
|
||||
> 翻譯:[siemenliu](https://github.com/siemenliu)
|
||||
> 校對:[zq54zquan](https://github.com/zq54zquan)
|
||||
|
||||
|
||||
# 下標腳本(Subscripts)
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [下標腳本語法](#subscript_syntax)
|
||||
- [下標腳本用法](#subscript_usage)
|
||||
- [下標腳本選項](#subscript_options)
|
||||
|
||||
*下標腳本* 可以定義在類(Class)、結構體(structure)和枚舉(enumeration)這些目標中,可以認為是訪問對像、集合或序列的快捷方式,不需要再調用實例的特定的賦值和訪問方法。舉例來說,用下標腳本訪問一個數組(Array)實例中的元素可以這樣寫 `someArray[index]` ,訪問字典(Dictionary)實例中的元素可以這樣寫 `someDictionary[key]`。
|
||||
|
||||
對於同一個目標可以定義多個下標腳本,通過索引值類型的不同來進行重載,而且索引值的個數可以是多個。
|
||||
|
||||
> 譯者:這裡附屬腳本重載在本小節中原文並沒有任何演示
|
||||
|
||||
<a name="subscript_syntax"></a>
|
||||
## 下標腳本語法
|
||||
|
||||
下標腳本允許你通過在實例後面的方括號中傳入一個或者多個的索引值來對實例進行訪問和賦值。語法類似於實例方法和計算型屬性的混合。與定義實例方法類似,定義下標腳本使用`subscript`關鍵字,顯式聲明入參(一個或多個)和返回類型。與實例方法不同的是下標腳本可以設定為讀寫或只讀。這種方式又有點像計算型屬性的getter和setter:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
get {
|
||||
// 返回與入參匹配的Int類型的值
|
||||
}
|
||||
|
||||
set(newValue) {
|
||||
// 執行賦值操作
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`newValue`的類型必須和下標腳本定義的返回類型相同。與計算型屬性相同的是set的入參聲明`newValue`就算不寫,在set代碼塊中依然可以使用默認的`newValue`這個變量來訪問新賦的值。
|
||||
|
||||
與只讀計算型屬性一樣,可以直接將原本應該寫在`get`代碼塊中的代碼寫在`subscript`中:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
// 返回與入參匹配的Int類型的值
|
||||
}
|
||||
```
|
||||
|
||||
下面代碼演示了一個在`TimesTable`結構體中使用只讀下標腳本的用法,該結構體用來展示傳入整數的*n*倍。
|
||||
|
||||
```swift
|
||||
struct TimesTable {
|
||||
let multiplier: Int
|
||||
subscript(index: Int) -> Int {
|
||||
return multiplier * index
|
||||
}
|
||||
}
|
||||
let threeTimesTable = TimesTable(multiplier: 3)
|
||||
println("3的6倍是\(threeTimesTable[6])")
|
||||
// 輸出 "3的6倍是18"
|
||||
```
|
||||
|
||||
在上例中,通過`TimesTable`結構體創建了一個用來表示索引值三倍的實例。數值`3`作為結構體`構造函數`入參初始化實例成員`multiplier`。
|
||||
|
||||
你可以通過下標腳本來得到結果,比如`threeTimesTable[6]`。這條語句訪問了`threeTimesTable`的第六個元素,返回`6`的`3`倍即`18`。
|
||||
|
||||
>注意:
|
||||
> `TimesTable`例子是基於一個固定的數學公式。它並不適合開放寫權限來對`threeTimesTable[someIndex]`進行賦值操作,這也是為什麼附屬腳本只定義為只讀的原因。
|
||||
|
||||
<a name="subscript_usage"></a>
|
||||
## 下標腳本用法
|
||||
|
||||
根據使用場景不同下標腳本也具有不同的含義。通常下標腳本是用來訪問集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以在你自己特定的類或結構體中自由的實現下標腳本來提供合適的功能。
|
||||
|
||||
例如,Swift 的字典(Dictionary)實現了通過下標腳本來對其實例中存放的值進行存取操作。在下標腳本中使用和字典索引相同類型的值,並且把一個字典值類型的值賦值給這個下標腳本來為字典設值:
|
||||
|
||||
```swift
|
||||
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
|
||||
numberOfLegs["bird"] = 2
|
||||
```
|
||||
|
||||
上例定義一個名為`numberOfLegs`的變量並用一個字典字面量初始化出了包含三對鍵值的字典實例。`numberOfLegs`的字典存放值類型推斷為`Dictionary<String, Int>`。字典實例創建完成之後通過下標腳本的方式將整型值`2`賦值到字典實例的索引為`bird`的位置中。
|
||||
|
||||
更多關於字典(Dictionary)下標腳本的信息請參考[讀取和修改字典](../chapter2/04_Collection_Types.html)
|
||||
|
||||
> 注意:
|
||||
> Swift 中字典的附屬腳本實現中,在`get`部分返回值是`Int?`,上例中的`numberOfLegs`字典通過附屬腳本返回的是一個`Int?`或者說「可選的int」,不是每個字典的索引都能得到一個整型值,對於沒有設過值的索引的訪問返回的結果就是`nil`;同樣想要從字典實例中刪除某個索引下的值也只需要給這個索引賦值為`nil`即可。
|
||||
|
||||
<a name="subscript_options"></a>
|
||||
## 下標腳本選項
|
||||
|
||||
下標腳本允許任意數量的入參索引,並且每個入參類型也沒有限制。下標腳本的返回值也可以是任何類型。下標腳本可以使用變量參數和可變參數,但使用寫入讀出(in-out)參數或給參數設置默認值都是不允許的。
|
||||
|
||||
一個類或結構體可以根據自身需要提供多個下標腳本實現,在定義下標腳本時通過入參個類型進行區分,使用下標腳本時會自動匹配合適的下標腳本實現運行,這就是*下標腳本的重載*。
|
||||
|
||||
一個下標腳本入參是最常見的情況,但只要有合適的場景也可以定義多個下標腳本入參。如下例定義了一個`Matrix`結構體,將呈現一個`Double`類型的二維矩陣。`Matrix`結構體的下標腳本需要兩個整型參數:
|
||||
|
||||
```swift
|
||||
struct Matrix {
|
||||
let rows: Int, columns: Int
|
||||
var grid: Double[]
|
||||
init(rows: Int, columns: Int) {
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
grid = Array(count: rows * columns, repeatedValue: 0.0)
|
||||
}
|
||||
func indexIsValidForRow(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
subscript(row: Int, column: Int) -> Double {
|
||||
get {
|
||||
assert(indexIsValidForRow(row, column: column), "Index out of range")
|
||||
return grid[(row * columns) + column]
|
||||
}
|
||||
set {
|
||||
assert(indexIsValidForRow(row, column: column), "Index out of range")
|
||||
grid[(row * columns) + columns] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Matrix`提供了一個兩個入參的構造方法,入參分別是`rows`和`columns`,創建了一個足夠容納`rows * columns`個數的`Double`類型數組。為了存儲,將數組的大小和數組每個元素初始值0.0,都傳入數組的構造方法中來創建一個正確大小的新數組。關於數組的構造方法和析構方法請參考[創建並且構造一個數組](../chapter2/04_Collection_Types.html)。
|
||||
|
||||
你可以通過傳入合適的`row`和`column`的數量來構造一個新的`Matrix`實例:
|
||||
|
||||
```swift
|
||||
var matrix = Matrix(rows: 2, columns: 2)
|
||||
```
|
||||
|
||||
上例中創建了一個新的兩行兩列的`Matrix`實例。在閱讀順序從左上到右下的`Matrix`實例中的數組實例`grid`是矩陣二維數組的扁平化存儲:
|
||||
|
||||
```swift
|
||||
// 示意圖
|
||||
grid = [0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
col0 col1
|
||||
row0 [0.0, 0.0,
|
||||
row1 0.0, 0.0]
|
||||
```
|
||||
|
||||
將值賦給帶有`row`和`column`下標腳本的`matrix`實例表達式可以完成賦值操作,下標腳本入參使用逗號分割
|
||||
|
||||
```swift
|
||||
matrix[0, 1] = 1.5
|
||||
matrix[1, 0] = 3.2
|
||||
```
|
||||
|
||||
上面兩條語句分別`讓matrix`的右上值為 1.5,坐下值為 3.2:
|
||||
|
||||
```swift
|
||||
[0.0, 1.5,
|
||||
3.2, 0.0]
|
||||
```
|
||||
|
||||
`Matrix`下標腳本的`getter`和`setter`中同時調用了下標腳本入參的`row`和`column`是否有效的判斷。為了方便進行斷言,`Matrix`包含了一個名為`indexIsValid`的成員方法,用來確認入參的`row`或`column`值是否會造成數組越界:
|
||||
|
||||
```swift
|
||||
func indexIsValidForRow(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
```
|
||||
|
||||
斷言在下標腳本越界時觸發:
|
||||
|
||||
```swift
|
||||
let someValue = matrix[2, 2]
|
||||
// 斷言將會觸發,因為 [2, 2] 已經超過了matrix的最大長度
|
||||
```
|
270
source-tw/chapter2/13_Inheritance.md
Normal file
270
source-tw/chapter2/13_Inheritance.md
Normal file
@ -0,0 +1,270 @@
|
||||
> 翻譯:[Hawstein](https://github.com/Hawstein)
|
||||
> 校對:[menlongsheng](https://github.com/menlongsheng)
|
||||
|
||||
# 繼承(Inheritance)
|
||||
-------------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [定義一個基類(Base class)](#defining_a_base_class)
|
||||
- [子類生成(Subclassing)](#subclassing)
|
||||
- [重寫(Overriding)](#overriding)
|
||||
- [防止重寫](#preventing_overrides)
|
||||
|
||||
一個類可以*繼承(inherit)*另一個類的方法(methods),屬性(property)和其它特性。當一個類繼承其它類時,繼承類叫*子類(subclass)*,被繼承類叫*超類(或父類,superclass)*。在 Swift 中,繼承是區分「類」與其它類型的一個基本特徵。
|
||||
|
||||
在 Swift 中,類可以調用和訪問超類的方法,屬性和下標腳本(subscripts),並且可以重寫(override)這些方法,屬性和下標腳本來優化或修改它們的行為。Swift 會檢查你的重寫定義在超類中是否有匹配的定義,以此確保你的重寫行為是正確的。
|
||||
|
||||
可以為類中繼承來的屬性添加屬性觀察器(property observer),這樣一來,當屬性值改變時,類就會被通知到。可以為任何屬性添加屬性觀察器,無論它原本被定義為存儲型屬性(stored property)還是計算型屬性(computed property)。
|
||||
|
||||
<a name="defining_a_base_class"></a>
|
||||
## 定義一個基類(Base class)
|
||||
|
||||
不繼承於其它類的類,稱之為*基類(base calss)*。
|
||||
|
||||
> 注意:
|
||||
Swift 中的類並不是從一個通用的基類繼承而來。如果你不為你定義的類指定一個超類的話,這個類就自動成為基類。
|
||||
|
||||
下面的例子定義了一個叫`Vehicle`的基類。這個基類聲明了兩個對所有車輛都通用的屬性(`numberOfWheels`和`maxPassengers`)。這些屬性在`description`方法中使用,這個方法返回一個`String`類型的,對車輛特徵的描述:
|
||||
|
||||
```swift
|
||||
class Vehicle {
|
||||
var numberOfWheels: Int
|
||||
var maxPassengers: Int
|
||||
func description() -> String {
|
||||
return "\(numberOfWheels) wheels; up to \(maxPassengers) passengers"
|
||||
}
|
||||
init() {
|
||||
numberOfWheels = 0
|
||||
maxPassengers = 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Vehicle`類定義了*構造器(initializer)*來設置屬性的值。構造器會在[構造過程](../chapter2/_14Initialization.html)一節中詳細介紹,這裡我們做一下簡單介紹,以便於講解子類中繼承來的屬性如何被修改。
|
||||
|
||||
構造器用於創建某個類型的一個新實例。儘管構造器並不是方法,但在語法上,兩者很相似。構造器的工作是準備新實例以供使用,並確保實例中的所有屬性都擁有有效的初始化值。
|
||||
|
||||
構造器的最簡單形式就像一個沒有參數的實例方法,使用`init`關鍵字:
|
||||
|
||||
```swift
|
||||
init() {
|
||||
// 執行構造過程
|
||||
}
|
||||
```
|
||||
|
||||
如果要創建一個`Vehicle`類的新實例,使用*構造器*語法調用上面的初始化器,即類名後面跟一個空的小括號:
|
||||
|
||||
```swift
|
||||
let someVehicle = Vehicle()
|
||||
```
|
||||
|
||||
這個`Vehicle`類的構造器為任意的一輛車設置一些初始化屬性值(`numberOfWheels = 0 `和`maxPassengers = 1`)。
|
||||
|
||||
`Vehicle`類定義了車輛的共同特性,但這個類本身並沒太大用處。為了使它更為實用,你需要進一步細化它來描述更具體的車輛。
|
||||
|
||||
<a name="subclassing"></a>
|
||||
## 子類生成(Subclassing)
|
||||
|
||||
*子類生成(Subclassing)*指的是在一個已有類的基礎上創建一個新的類。子類繼承超類的特性,並且可以優化或改變它。你還可以為子類添加新的特性。
|
||||
|
||||
為了指明某個類的超類,將超類名寫在子類名的後面,用冒號分隔:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeSuperclass {
|
||||
// 類的定義
|
||||
}
|
||||
```
|
||||
|
||||
下一個例子,定義一個更具體的車輛類叫`Bicycle`。這個新類是在 `Vehicle`類的基礎上創建起來。因此你需要將`Vehicle`類放在 `Bicycle`類後面,用冒號分隔。
|
||||
|
||||
我們可以將這讀作:
|
||||
|
||||
「定義一個新的類叫`Bicycle `,它繼承了`Vehicle`的特性」;
|
||||
|
||||
```swift
|
||||
class Bicycle: Vehicle {
|
||||
init() {
|
||||
super.init()
|
||||
numberOfWheels = 2
|
||||
}
|
||||
}
|
||||
```
|
||||
preview
|
||||
`Bicycle`是`Vehicle`的子類,`Vehicle`是`Bicycle`的超類。新的`Bicycle`類自動獲得`Vehicle`類的特性,比如 `maxPassengers`和`numberOfWheels`屬性。你可以在子類中定制這些特性,或添加新的特性來更好地描述`Bicycle`類。
|
||||
|
||||
`Bicycle`類定義了一個構造器來設置它定制的特性(自行車只有2個輪子)。`Bicycle`的構造器調用了它父類`Vehicle`的構造器 `super.init()`,以此確保在`Bicycle`類試圖修改那些繼承來的屬性前`Vehicle`類已經初始化過它們了。
|
||||
|
||||
> 注意:
|
||||
不像 Objective-C,在 Swift 中,初始化器默認是不繼承的,見[初始化器的繼承與重寫](../chapter2/_14Initialization.html#initializer_inheritance_and_ overriding)
|
||||
|
||||
`Vehicle`類中`maxPassengers`的默認值對自行車來說已經是正確的,因此在`Bicycle`的構造器中並沒有改變它。而`numberOfWheels`原來的值對自行車來說是不正確的,因此在初始化器中將它更改為 2。
|
||||
|
||||
`Bicycle`不僅可以繼承`Vehicle`的屬性,還可以繼承它的方法。如果你創建了一個`Bicycle`類的實例,你就可以調用它繼承來的`description`方法,並且可以看到,它輸出的屬性值已經發生了變化:
|
||||
|
||||
```swift
|
||||
let bicycle = Bicycle()
|
||||
println("Bicycle: \(bicycle.description())")
|
||||
// Bicycle: 2 wheels; up to 1 passengers
|
||||
```
|
||||
|
||||
子類還可以繼續被其它類繼承:
|
||||
|
||||
```swift
|
||||
class Tandem: Bicycle {
|
||||
init() {
|
||||
super.init()
|
||||
maxPassengers = 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面的例子創建了`Bicycle`的一個子類:雙人自行車(tandem)。`Tandem`從`Bicycle`繼承了兩個屬性,而這兩個屬性是`Bicycle`從`Vehicle`繼承而來的。`Tandem`並不修改輪子的數量,因為它仍是一輛自行車,有 2 個輪子。但它需要修改`maxPassengers`的值,因為雙人自行車可以坐兩個人。
|
||||
|
||||
> 注意:
|
||||
子類只允許修改從超類繼承來的變量屬性,而不能修改繼承來的常量屬性。
|
||||
|
||||
創建一個`Tandem`類的實例,打印它的描述,即可看到它的屬性已被更新:
|
||||
|
||||
```swift
|
||||
let tandem = Tandem()
|
||||
println("Tandem: \(tandem.description())")
|
||||
// Tandem: 2 wheels; up to 2 passengers
|
||||
```
|
||||
|
||||
注意,`Tandem`類也繼承了`description`方法。一個類的實例方法會被這個類的所有子類繼承。
|
||||
|
||||
<a name="overriding"></a>
|
||||
## 重寫(Overriding)
|
||||
|
||||
子類可以為繼承來的實例方法(instance method),類方法(class method),實例屬性(instance property),或下標腳本(subscript)提供自己定制的實現(implementation)。我們把這種行為叫*重寫(overriding)*。
|
||||
|
||||
如果要重寫某個特性,你需要在重寫定義的前面加上`override`關鍵字。這麼做,你就表明了你是想提供一個重寫版本,而非錯誤地提供了一個相同的定義。意外的重寫行為可能會導致不可預知的錯誤,任何缺少`override`關鍵字的重寫都會在編譯時被診斷為錯誤。
|
||||
|
||||
`override`關鍵字會提醒 Swift 編譯器去檢查該類的超類(或其中一個父類)是否有匹配重寫版本的聲明。這個檢查可以確保你的重寫定義是正確的。
|
||||
|
||||
### 訪問超類的方法,屬性及下標腳本
|
||||
|
||||
當你在子類中重寫超類的方法,屬性或下標腳本時,有時在你的重寫版本中使用已經存在的超類實現會大有裨益。比如,你可以優化已有實現的行為,或在一個繼承來的變量中存儲一個修改過的值。
|
||||
|
||||
在合適的地方,你可以通過使用`super`前綴來訪問超類版本的方法,屬性或下標腳本:
|
||||
|
||||
* 在方法`someMethod`的重寫實現中,可以通過`super.someMethod()`來調用超類版本的`someMethod`方法。
|
||||
* 在屬性`someProperty`的 getter 或 setter 的重寫實現中,可以通過`super.someProperty`來訪問超類版本的`someProperty`屬性。
|
||||
* 在下標腳本的重寫實現中,可以通過`super[someIndex]`來訪問超類版本中的相同下標腳本。
|
||||
|
||||
### 重寫方法
|
||||
|
||||
在子類中,你可以重寫繼承來的實例方法或類方法,提供一個定制或替代的方法實現。
|
||||
|
||||
下面的例子定義了`Vehicle`的一個新的子類,叫`Car`,它重寫了從`Vehicle`類繼承來的`description`方法:
|
||||
|
||||
```swift
|
||||
class Car: Vehicle {
|
||||
var speed: Double = 0.0
|
||||
init() {
|
||||
super.init()
|
||||
maxPassengers = 5
|
||||
numberOfWheels = 4
|
||||
}
|
||||
override func description() -> String {
|
||||
return super.description() + "; "
|
||||
+ "traveling at \(speed) mph"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Car`聲明了一個新的存儲型屬性`speed`,它是`Double`類型的,默認值是`0.0`,表示「時速是0英里」。`Car`有自己的初始化器,它將乘客的最大數量設為5,輪子數量設為4。
|
||||
|
||||
`Car`重寫了繼承來的`description`方法,它的聲明與`Vehicle`中的`description`方法一致,聲明前面加上了`override`關鍵字。
|
||||
|
||||
`Car`中的`description`方法並非完全自定義,而是通過`super.description`使用了超類`Vehicle`中的`description`方法,然後再追加一些額外的信息,比如汽車的當前速度。
|
||||
|
||||
如果你創建一個`Car`的新實例,並打印`description`方法的輸出,你就會發現描述信息已經發生了改變:
|
||||
|
||||
```swift
|
||||
let car = Car()
|
||||
println("Car: \(car.description())")
|
||||
// Car: 4 wheels; up to 5 passengers; traveling at 0.0 mph
|
||||
```
|
||||
|
||||
### 重寫屬性
|
||||
|
||||
你可以重寫繼承來的實例屬性或類屬性,提供自己定制的getter和setter,或添加屬性觀察器使重寫的屬性觀察屬性值什麼時候發生改變。
|
||||
|
||||
#### 重寫屬性的Getters和Setters
|
||||
|
||||
你可以提供定制的 getter(或 setter)來重寫任意繼承來的屬性,無論繼承來的屬性是存儲型的還是計算型的屬性。子類並不知道繼承來的屬性是存儲型的還是計算型的,它只知道繼承來的屬性會有一個名字和類型。你在重寫一個屬性時,必需將它的名字和類型都寫出來。這樣才能使編譯器去檢查你重寫的屬性是與超類中同名同類型的屬性相匹配的。
|
||||
|
||||
你可以將一個繼承來的只讀屬性重寫為一個讀寫屬性,只需要你在重寫版本的屬性裡提供 getter 和 setter 即可。但是,你不可以將一個繼承來的讀寫屬性重寫為一個只讀屬性。
|
||||
|
||||
> 注意:
|
||||
如果你在重寫屬性中提供了 setter,那麼你也一定要提供 getter。如果你不想在重寫版本中的 getter 裡修改繼承來的屬性值,你可以直接返回`super.someProperty`來返回繼承來的值。正如下面的`SpeedLimitedCar`的例子所示。
|
||||
|
||||
以下的例子定義了一個新類,叫`SpeedLimitedCar`,它是`Car`的子類。類`SpeedLimitedCar`表示安裝了限速裝置的車,它的最高速度只能達到40mph。你可以通過重寫繼承來的`speed`屬性來實現這個速度限制:
|
||||
|
||||
```swift
|
||||
class SpeedLimitedCar: Car {
|
||||
override var speed: Double {
|
||||
get {
|
||||
return super.speed
|
||||
}
|
||||
set {
|
||||
super.speed = min(newValue, 40.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
當你設置一個`SpeedLimitedCar`實例的`speed`屬性時,屬性setter的實現會去檢查新值與限制值40mph的大小,它會將超類的`speed`設置為`newValue`和`40.0`中較小的那個。這兩個值哪個較小由`min`函數決定,它是Swift標準庫中的一個全局函數。`min`函數接收兩個或更多的數,返回其中最小的那個。
|
||||
|
||||
如果你嘗試將`SpeedLimitedCar`實例的`speed`屬性設置為一個大於40mph的數,然後打印`description`函數的輸出,你會發現速度被限制在40mph:
|
||||
|
||||
```swift
|
||||
let limitedCar = SpeedLimitedCar()
|
||||
limitedCar.speed = 60.0
|
||||
println("SpeedLimitedCar: \(limitedCar.description())")
|
||||
// SpeedLimitedCar: 4 wheels; up to 5 passengers; traveling at 40.0 mph
|
||||
```
|
||||
|
||||
#### 重寫屬性觀察器(Property Observer)
|
||||
|
||||
你可以在屬性重寫中為一個繼承來的屬性添加屬性觀察器。這樣一來,當繼承來的屬性值發生改變時,你就會被通知到,無論那個屬性原本是如何實現的。關於屬性觀察器的更多內容,請看[屬性觀察器](../chapter2/_10Properties.html#property_observer)。
|
||||
|
||||
> 注意:
|
||||
你不可以為繼承來的常量存儲型屬性或繼承來的只讀計算型屬性添加屬性觀察器。這些屬性的值是不可以被設置的,所以,為它們提供`willSet`或`didSet`實現是不恰當。此外還要注意,你不可以同時提供重寫的 setter 和重寫的屬性觀察器。如果你想觀察屬性值的變化,並且你已經為那個屬性提供了定制的 setter,那麼你在 setter 中就可以觀察到任何值變化了。
|
||||
|
||||
下面的例子定義了一個新類叫`AutomaticCar`,它是`Car`的子類。`AutomaticCar`表示自動擋汽車,它可以根據當前的速度自動選擇合適的擋位。`AutomaticCar`也提供了定制的`description`方法,可以輸出當前擋位。
|
||||
|
||||
```swift
|
||||
class AutomaticCar: Car {
|
||||
var gear = 1
|
||||
override var speed: Double {
|
||||
didSet {
|
||||
gear = Int(speed / 10.0) + 1
|
||||
}
|
||||
}
|
||||
override func description() -> String {
|
||||
return super.description() + " in gear \(gear)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
當你設置`AutomaticCar`的`speed`屬性,屬性的`didSet`觀察器就會自動地設置`gear`屬性,為新的速度選擇一個合適的擋位。具體來說就是,屬性觀察器將新的速度值除以10,然後向下取得最接近的整數值,最後加1來得到檔位`gear`的值。例如,速度為10.0時,擋位為1;速度為35.0時,擋位為4:
|
||||
|
||||
```swift
|
||||
let automatic = AutomaticCar()
|
||||
automatic.speed = 35.0
|
||||
println("AutomaticCar: \(automatic.description())")
|
||||
// AutomaticCar: 4 wheels; up to 5 passengers; traveling at 35.0 mph in gear 4
|
||||
```
|
||||
|
||||
<a name="preventing_overrides"></a>
|
||||
## 防止重寫
|
||||
|
||||
你可以通過把方法,屬性或下標腳本標記為*`final`*來防止它們被重寫,只需要在聲明關鍵字前加上`@final`特性即可。(例如:`@final var`, `@final func`, `@final class func`, 以及 `@final subscript`)
|
||||
|
||||
如果你重寫了`final`方法,屬性或下標腳本,在編譯時會報錯。在擴展中,你添加到類裡的方法,屬性或下標腳本也可以在擴展的定義裡標記為 final。
|
||||
|
||||
你可以通過在關鍵字`class`前添加`@final`特性(`@final class`)來將整個類標記為 final 的,這樣的類是不可被繼承的,否則會報編譯錯誤。
|
||||
|
646
source-tw/chapter2/14_Initialization.md
Normal file
646
source-tw/chapter2/14_Initialization.md
Normal file
@ -0,0 +1,646 @@
|
||||
> 翻譯:[lifedim](https://github.com/lifedim)
|
||||
> 校對:[lifedim](https://github.com/lifedim)
|
||||
|
||||
# 構造過程(Initialization)
|
||||
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [存儲型屬性的初始賦值](#setting_initial_values_for_stored_properties)
|
||||
- [定制化構造過程](#customizing_initialization)
|
||||
- [默認構造器](#default_initializers)
|
||||
- [值類型的構造器代理](#initializer_delegation_for_value_types)
|
||||
- [類的繼承和構造過程](#class_inheritance_and_initialization)
|
||||
- [通過閉包和函數來設置屬性的默認值](#setting_a_default_property_value_with_a_closure_or_function)
|
||||
|
||||
|
||||
構造過程是為了使用某個類、結構體或枚舉類型的實例而進行的準備過程。這個過程包含了為實例中的每個屬性設置初始值和為其執行必要的準備和初始化任務。
|
||||
|
||||
構造過程是通過定義構造器(`Initializers`)來實現的,這些構造器可以看做是用來創建特定類型實例的特殊方法。與 Objective-C 中的構造器不同,Swift 的構造器無需返回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化。
|
||||
|
||||
類實例也可以通過定義析構器(`deinitializer`)在類實例釋放之前執行特定的清除工作。想瞭解更多關於析構器的內容,請參考[析構過程](../chapter2/15_Deinitialization.html)。
|
||||
|
||||
<a name="setting_initial_values_for_stored_properties"></a>
|
||||
## 存儲型屬性的初始賦值
|
||||
|
||||
類和結構體在實例創建時,必須為所有存儲型屬性設置合適的初始值。存儲型屬性的值不能處於一個未知的狀態。
|
||||
|
||||
你可以在構造器中為存儲型屬性賦初值,也可以在定義屬性時為其設置默認值。以下章節將詳細介紹這兩種方法。
|
||||
|
||||
>注意:
|
||||
當你為存儲型屬性設置默認值或者在構造器中為其賦值時,它們的值是被直接設置的,不會觸發任何屬性觀測器(`property observers`)。
|
||||
|
||||
### 構造器
|
||||
|
||||
構造器在創建某特定類型的新實例時調用。它的最簡形式類似於一個不帶任何參數的實例方法,以關鍵字`init`命名。
|
||||
|
||||
下面例子中定義了一個用來保存華氏溫度的結構體`Fahrenheit`,它擁有一個`Double`類型的存儲型屬性`temperature`:
|
||||
|
||||
```swift
|
||||
struct Fahrenheit {
|
||||
var temperature: Double
|
||||
init() {
|
||||
temperature = 32.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
var f = Fahrenheit()
|
||||
println("The default temperature is \(f.temperature)° Fahrenheit")
|
||||
// 輸出 "The default temperature is 32.0° Fahrenheit」
|
||||
```
|
||||
|
||||
這個結構體定義了一個不帶參數的構造器`init`,並在裡面將存儲型屬性`temperature`的值初始化為`32.0`(華攝氏度下水的冰點)。
|
||||
|
||||
### 默認屬性值
|
||||
|
||||
如前所述,你可以在構造器中為存儲型屬性設置初始值;同樣,你也可以在屬性聲明時為其設置默認值。
|
||||
|
||||
>注意:
|
||||
如果一個屬性總是使用同一個初始值,可以為其設置一個默認值。無論定義默認值還是在構造器中賦值,最終它們實現的效果是一樣的,只不過默認值跟屬性構造過程結合的更緊密。使用默認值能讓你的構造器更簡潔、更清晰,且能通過默認值自動推導出屬性的類型;同時,它也能讓你充分利用默認構造器、構造器繼承(後續章節將講到)等特性。
|
||||
|
||||
你可以使用更簡單的方式在定義結構體`Fahrenheit`時為屬性`temperature`設置默認值:
|
||||
|
||||
```swift
|
||||
struct Fahrenheit {
|
||||
var temperature = 32.0
|
||||
}
|
||||
```
|
||||
|
||||
<a name="customizing_initialization"></a>
|
||||
## 定制化構造過程
|
||||
|
||||
你可以通過輸入參數和可選屬性類型來定制構造過程,也可以在構造過程中修改常量屬性。這些都將在後面章節中提到。
|
||||
|
||||
### 構造參數
|
||||
|
||||
你可以在定義構造器時提供構造參數,為其提供定制化構造所需值的類型和名字。構造器參數的功能和語法跟函數和方法參數相同。
|
||||
|
||||
下面例子中定義了一個包含攝氏度溫度的結構體`Celsius`。它定義了兩個不同的構造器:`init(fromFahrenheit:)`和`init(fromKelvin:)`,二者分別通過接受不同刻度表示的溫度值來創建新的實例:
|
||||
|
||||
```swift
|
||||
struct Celsius {
|
||||
var temperatureInCelsius: Double = 0.0
|
||||
init(fromFahrenheit fahrenheit: Double) {
|
||||
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
|
||||
}
|
||||
init(fromKelvin kelvin: Double) {
|
||||
temperatureInCelsius = kelvin - 273.15
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
|
||||
// boilingPointOfWater.temperatureInCelsius 是 100.0
|
||||
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
|
||||
// freezingPointOfWater.temperatureInCelsius 是 0.0」
|
||||
```
|
||||
|
||||
第一個構造器擁有一個構造參數,其外部名字為`fromFahrenheit`,內部名字為`fahrenheit`;第二個構造器也擁有一個構造參數,其外部名字為`fromKelvin`,內部名字為`kelvin`。這兩個構造器都將唯一的參數值轉換成攝氏溫度值,並保存在屬性`temperatureInCelsius`中。
|
||||
|
||||
### 內部和外部參數名
|
||||
|
||||
跟函數和方法參數相同,構造參數也存在一個在構造器內部使用的參數名字和一個在調用構造器時使用的外部參數名字。
|
||||
|
||||
然而,構造器並不像函數和方法那樣在括號前有一個可辨別的名字。所以在調用構造器時,主要通過構造器中的參數名和類型來確定需要調用的構造器。正因為參數如此重要,如果你在定義構造器時沒有提供參數的外部名字,Swift 會為每個構造器的參數自動生成一個跟內部名字相同的外部名,就相當於在每個構造參數之前加了一個哈希符號。
|
||||
|
||||
> 注意:
|
||||
如果你不希望為構造器的某個參數提供外部名字,你可以使用下劃線`_`來顯示描述它的外部名,以此覆蓋上面所說的默認行為。
|
||||
|
||||
以下例子中定義了一個結構體`Color`,它包含了三個常量:`red`、`green`和`blue`。這些屬性可以存儲0.0到1.0之間的值,用來指示顏色中紅、綠、藍成分的含量。
|
||||
|
||||
`Color`提供了一個構造器,其中包含三個`Double`類型的構造參數:
|
||||
|
||||
```swift
|
||||
struct Color {
|
||||
let red = 0.0, green = 0.0, blue = 0.0
|
||||
init(red: Double, green: Double, blue: Double) {
|
||||
self.red = red
|
||||
self.green = green
|
||||
self.blue = blue
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
每當你創建一個新的`Color`實例,你都需要通過三種顏色的外部參數名來傳值,並調用構造器。
|
||||
|
||||
```swift
|
||||
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
|
||||
```
|
||||
|
||||
注意,如果不通過外部參數名字傳值,你是沒法調用這個構造器的。只要構造器定義了某個外部參數名,你就必須使用它,忽略它將導致編譯錯誤:
|
||||
|
||||
```swift
|
||||
let veryGreen = Color(0.0, 1.0, 0.0)
|
||||
// 報編譯時錯誤,需要外部名稱
|
||||
```
|
||||
|
||||
### 可選屬性類型
|
||||
|
||||
如果你定制的類型包含一個邏輯上允許取值為空的存儲型屬性--不管是因為它無法在初始化時賦值,還是因為它可以在之後某個時間點可以賦值為空--你都需要將它定義為可選類型`optional type`。可選類型的屬性將自動初始化為空`nil`,表示這個屬性是故意在初始化時設置為空的。
|
||||
|
||||
下面例子中定義了類`SurveyQuestion`,它包含一個可選字符串屬性`response`:
|
||||
|
||||
```swift
|
||||
class SurveyQuestion {
|
||||
var text: String
|
||||
var response: String?
|
||||
init(text: String) {
|
||||
self.text = text
|
||||
}
|
||||
func ask() {
|
||||
println(text)
|
||||
}
|
||||
}
|
||||
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
|
||||
cheeseQuestion.ask()
|
||||
// 輸出 "Do you like cheese?"
|
||||
cheeseQuestion.response = "Yes, I do like cheese.
|
||||
```
|
||||
|
||||
調查問題在問題提出之後,我們才能得到回答。所以我們將屬性回答`response`聲明為`String?`類型,或者說是可選字符串類型`optional String`。當`SurveyQuestion`實例化時,它將自動賦值為空`nil`,表明暫時還不存在此字符串。
|
||||
|
||||
### 構造過程中常量屬性的修改
|
||||
|
||||
只要在構造過程結束前常量的值能確定,你可以在構造過程中的任意時間點修改常量屬性的值。
|
||||
|
||||
>注意:
|
||||
對某個類實例來說,它的常量屬性只能在定義它的類的構造過程中修改;不能在子類中修改。
|
||||
|
||||
你可以修改上面的`SurveyQuestion`示例,用常量屬性替代變量屬性`text`,指明問題內容`text`在其創建之後不會再被修改。儘管`text`屬性現在是常量,我們仍然可以在其類的構造器中設置它的值:
|
||||
|
||||
```swift
|
||||
class SurveyQuestion {
|
||||
let text: String
|
||||
var response: String?
|
||||
init(text: String) {
|
||||
self.text = text
|
||||
}
|
||||
func ask() {
|
||||
println(text)
|
||||
}
|
||||
}
|
||||
let beetsQuestion = SurveyQuestion(text: "How about beets?")
|
||||
beetsQuestion.ask()
|
||||
// 輸出 "How about beets?"
|
||||
beetsQuestion.response = "I also like beets. (But not with cheese.)
|
||||
```
|
||||
|
||||
<a name="default_initializers"></a>
|
||||
## 默認構造器
|
||||
|
||||
Swift 將為所有屬性已提供默認值的且自身沒有定義任何構造器的結構體或基類,提供一個默認的構造器。這個默認構造器將簡單的創建一個所有屬性值都設置為默認值的實例。
|
||||
|
||||
下面例子中創建了一個類`ShoppingListItem`,它封裝了購物清單中的某一項的屬性:名字(`name`)、數量(`quantity`)和購買狀態 `purchase state`。
|
||||
|
||||
```swift
|
||||
class ShoppingListItem {
|
||||
var name: String?
|
||||
var quantity = 1
|
||||
var purchased = false
|
||||
}
|
||||
var item = ShoppingListItem()
|
||||
```
|
||||
|
||||
由於`ShoppingListItem`類中的所有屬性都有默認值,且它是沒有父類的基類,它將自動獲得一個可以為所有屬性設置默認值的默認構造器(儘管代碼中沒有顯式為`name`屬性設置默認值,但由於`name`是可選字符串類型,它將默認設置為`nil`)。上面例子中使用默認構造器創造了一個`ShoppingListItem`類的實例(使用`ShoppingListItem()`形式的構造器語法),並將其賦值給變量`item`。
|
||||
|
||||
### 結構體的逐一成員構造器
|
||||
|
||||
除上面提到的默認構造器,如果結構體對所有存儲型屬性提供了默認值且自身沒有提供定制的構造器,它們能自動獲得一個逐一成員構造器。
|
||||
|
||||
逐一成員構造器是用來初始化結構體新實例裡成員屬性的快捷方法。我們在調用逐一成員構造器時,通過與成員屬性名相同的參數名進行傳值來完成對成員屬性的初始賦值。
|
||||
|
||||
下面例子中定義了一個結構體`Size`,它包含兩個屬性`width`和`height`。Swift 可以根據這兩個屬性的初始賦值`0.0`自動推導出它們的類型`Double`。
|
||||
|
||||
由於這兩個存儲型屬性都有默認值,結構體`Size`自動獲得了一個逐一成員構造器 `init(width:height:)`。 你可以用它來為`Size`創建新的實例:
|
||||
|
||||
```swift
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
let twoByTwo = Size(width: 2.0, height: 2.0)
|
||||
```
|
||||
|
||||
<a name="initializer_delegation_for_value_types"></a>
|
||||
## 值類型的構造器代理
|
||||
|
||||
構造器可以通過調用其它構造器來完成實例的部分構造過程。這一過程稱為構造器代理,它能減少多個構造器間的代碼重複。
|
||||
|
||||
構造器代理的實現規則和形式在值類型和類類型中有所不同。值類型(結構體和枚舉類型)不支持繼承,所以構造器代理的過程相對簡單,因為它們只能代理給本身提供的其它構造器。類則不同,它可以繼承自其它類(請參考[繼承](../chapter2/13_Inheritance.html)),這意味著類有責任保證其所有繼承的存儲型屬性在構造時也能正確的初始化。這些責任將在後續章節[類的繼承和構造過程](#class_inheritance_and_initialization)中介紹。
|
||||
|
||||
對於值類型,你可以使用`self.init`在自定義的構造器中引用其它的屬於相同值類型的構造器。並且你只能在構造器內部調用`self.init`。
|
||||
|
||||
注意,如果你為某個值類型定義了一個定制的構造器,你將無法訪問到默認構造器(如果是結構體,則無法訪問逐一對像構造器)。這個限制可以防止你在為值類型定義了一個更複雜的,完成了重要準備構造器之後,別人還是錯誤的使用了那個自動生成的構造器。
|
||||
|
||||
>注意:
|
||||
假如你想通過默認構造器、逐一對像構造器以及你自己定制的構造器為值類型創建實例,我們建議你將自己定制的構造器寫到擴展(`extension`)中,而不是跟值類型定義混在一起。想查看更多內容,請查看[擴展](../chapter2/20_Extensions.html)章節。
|
||||
|
||||
下面例子將定義一個結構體`Rect`,用來代表幾何矩形。這個例子需要兩個輔助的結構體`Size`和`Point`,它們各自為其所有的屬性提供了初始值`0.0`。
|
||||
|
||||
```swift
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
```
|
||||
|
||||
你可以通過以下三種方式為`Rect`創建實例--使用默認的0值來初始化`origin`和`size`屬性;使用特定的`origin`和`size`實例來初始化;使用特定的`center`和`size`來初始化。在下面`Rect`結構體定義中,我們為這三種方式提供了三個自定義的構造器:
|
||||
|
||||
```swift
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
init() {}
|
||||
init(origin: Point, size: Size) {
|
||||
self.origin = origin
|
||||
self.size = size
|
||||
}
|
||||
init(center: Point, size: Size) {
|
||||
let originX = center.x - (size.width / 2)
|
||||
let originY = center.y - (size.height / 2)
|
||||
self.init(origin: Point(x: originX, y: originY), size: size)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
第一個`Rect`構造器`init()`,在功能上跟沒有自定義構造器時自動獲得的默認構造器是一樣的。這個構造器是一個空函數,使用一對大括號`{}`來描述,它沒有執行任何定制的構造過程。調用這個構造器將返回一個`Rect`實例,它的`origin`和`size`屬性都使用定義時的默認值`Point(x: 0.0, y: 0.0)`和`Size(width: 0.0, height: 0.0)`:
|
||||
|
||||
```swift
|
||||
let basicRect = Rect()
|
||||
// basicRect 的原點是 (0.0, 0.0),尺寸是 (0.0, 0.0)
|
||||
```
|
||||
|
||||
第二個`Rect`構造器`init(origin:size:)`,在功能上跟結構體在沒有自定義構造器時獲得的逐一成員構造器是一樣的。這個構造器只是簡單地將`origin`和`size`的參數值賦給對應的存儲型屬性:
|
||||
|
||||
```swift
|
||||
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
|
||||
size: Size(width: 5.0, height: 5.0))
|
||||
// originRect 的原點是 (2.0, 2.0),尺寸是 (5.0, 5.0)
|
||||
```
|
||||
|
||||
第三個`Rect`構造器`init(center:size:)`稍微複雜一點。它先通過`center`和`size`的值計算出`origin`的坐標。然後再調用(或代理給)`init(origin:size:)`構造器來將新的`origin`和`size`值賦值到對應的屬性中:
|
||||
|
||||
```swift
|
||||
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
size: Size(width: 3.0, height: 3.0))
|
||||
// centerRect 的原點是 (2.5, 2.5),尺寸是 (3.0, 3.0)
|
||||
```
|
||||
|
||||
構造器`init(center:size:)`可以自己將`origin`和`size`的新值賦值到對應的屬性中。然而盡量利用現有的構造器和它所提供的功能來實現`init(center:size:)`的功能,是更方便、更清晰和更直觀的方法。
|
||||
|
||||
>注意:
|
||||
如果你想用另外一種不需要自己定義`init()`和`init(origin:size:)`的方式來實現這個例子,請參考[擴展](../chapter2/20_Extensions.html)。
|
||||
|
||||
<a name="class_inheritance_and_initialization"></a>
|
||||
## 類的繼承和構造過程
|
||||
|
||||
類裡面的所有存儲型屬性--包括所有繼承自父類的屬性--都必須在構造過程中設置初始值。
|
||||
|
||||
Swift 提供了兩種類型的類構造器來確保所有類實例中存儲型屬性都能獲得初始值,它們分別是指定構造器和便利構造器。
|
||||
|
||||
### 指定構造器和便利構造器
|
||||
|
||||
指定構造器是類中最主要的構造器。一個指定構造器將初始化類中提供的所有屬性,並根據父類鏈往上調用父類的構造器來實現父類的初始化。
|
||||
|
||||
每一個類都必須擁有至少一個指定構造器。在某些情況下,許多類通過繼承了父類中的指定構造器而滿足了這個條件。具體內容請參考後續章節[自動構造器的繼承](#automatic_initializer_inheritance)。
|
||||
|
||||
便利構造器是類中比較次要的、輔助型的構造器。你可以定義便利構造器來調用同一個類中的指定構造器,並為其參數提供默認值。你也可以定義便利構造器來創建一個特殊用途或特定輸入的實例。
|
||||
|
||||
你應當只在必要的時候為類提供便利構造器,比方說某種情況下通過使用便利構造器來快捷調用某個指定構造器,能夠節省更多開發時間並讓類的構造過程更清晰明瞭。
|
||||
|
||||
<a name="initialization_chain"></a>
|
||||
### 構造器鏈
|
||||
|
||||
為了簡化指定構造器和便利構造器之間的調用關係,Swift 採用以下三條規則來限制構造器之間的代理調用:
|
||||
|
||||
#### 規則 1
|
||||
指定構造器必須調用其直接父類的的指定構造器。
|
||||
|
||||
#### 規則 2
|
||||
便利構造器必須調用同一類中定義的其它構造器。
|
||||
|
||||
#### 規則 3
|
||||
便利構造器必須最終以調用一個指定構造器結束。
|
||||
|
||||
一個更方便記憶的方法是:
|
||||
|
||||
- 指定構造器必須總是向上代理
|
||||
- 便利構造器必須總是橫向代理
|
||||
|
||||
這些規則可以通過下面圖例來說明:
|
||||
|
||||

|
||||
|
||||
如圖所示,父類中包含一個指定構造器和兩個便利構造器。其中一個便利構造器調用了另外一個便利構造器,而後者又調用了唯一的指定構造器。這滿足了上面提到的規則2和3。這個父類沒有自己的父類,所以規則1沒有用到。
|
||||
|
||||
子類中包含兩個指定構造器和一個便利構造器。便利構造器必須調用兩個指定構造器中的任意一個,因為它只能調用同一個類裡的其他構造器。這滿足了上面提到的規則2和3。而兩個指定構造器必須調用父類中唯一的指定構造器,這滿足了規則1。
|
||||
|
||||
> 注意:
|
||||
這些規則不會影響使用時,如何用類去創建實例。任何上圖中展示的構造器都可以用來完整創建對應類的實例。這些規則只在實現類的定義時有影響。
|
||||
|
||||
下面圖例中展示了一種針對四個類的更複雜的類層級結構。它演示了指定構造器是如何在類層級中充當「管道」的作用,在類的構造器鏈上簡化了類之間的相互關係。
|
||||
|
||||

|
||||
|
||||
<a name="two_phase_initialization"></a>
|
||||
### 兩段式構造過程
|
||||
|
||||
Swift 中類的構造過程包含兩個階段。第一個階段,每個存儲型屬性通過引入它們的類的構造器來設置初始值。當每一個存儲型屬性值被確定後,第二階段開始,它給每個類一次機會在新實例準備使用之前進一步定制它們的存儲型屬性。
|
||||
|
||||
兩段式構造過程的使用讓構造過程更安全,同時在整個類層級結構中給予了每個類完全的靈活性。兩段式構造過程可以防止屬性值在初始化之前被訪問;也可以防止屬性被另外一個構造器意外地賦予不同的值。
|
||||
|
||||
> 注意:
|
||||
Swift的兩段式構造過程跟 Objective-C 中的構造過程類似。最主要的區別在於階段 1,Objective-C 給每一個屬性賦值`0`或空值(比如說`0`或`nil`)。Swift 的構造流程則更加靈活,它允許你設置定制的初始值,並自如應對某些屬性不能以`0`或`nil`作為合法默認值的情況。
|
||||
|
||||
Swift 編譯器將執行 4 種有效的安全檢查,以確保兩段式構造過程能順利完成:
|
||||
|
||||
#### 安全檢查 1
|
||||
|
||||
指定構造器必須保證它所在類引入的所有屬性都必須先初始化完成,之後才能將其它構造任務向上代理給父類中的構造器。
|
||||
|
||||
如上所述,一個對象的內存只有在其所有存儲型屬性確定之後才能完全初始化。為了滿足這一規則,指定構造器必須保證它所在類引入的屬性在它往上代理之前先完成初始化。
|
||||
|
||||
#### 安全檢查 2
|
||||
|
||||
指定構造器必須先向上代理調用父類構造器,然後再為繼承的屬性設置新值。如果沒這麼做,指定構造器賦予的新值將被父類中的構造器所覆蓋。
|
||||
|
||||
#### 安全檢查 3
|
||||
|
||||
便利構造器必須先代理調用同一類中的其它構造器,然後再為任意屬性賦新值。如果沒這麼做,便利構造器賦予的新值將被同一類中其它指定構造器所覆蓋。
|
||||
|
||||
#### 安全檢查 4
|
||||
|
||||
構造器在第一階段構造完成之前,不能調用任何實例方法、不能讀取任何實例屬性的值,也不能引用`self`的值。
|
||||
|
||||
以下是兩段式構造過程中基於上述安全檢查的構造流程展示:
|
||||
|
||||
#### 階段 1
|
||||
|
||||
- 某個指定構造器或便利構造器被調用;
|
||||
- 完成新實例內存的分配,但此時內存還沒有被初始化;
|
||||
- 指定構造器確保其所在類引入的所有存儲型屬性都已賦初值。存儲型屬性所屬的內存完成初始化;
|
||||
- 指定構造器將調用父類的構造器,完成父類屬性的初始化;
|
||||
- 這個調用父類構造器的過程沿著構造器鏈一直往上執行,直到到達構造器鏈的最頂部;
|
||||
- 當到達了構造器鏈最頂部,且已確保所有實例包含的存儲型屬性都已經賦值,這個實例的內存被認為已經完全初始化。此時階段1完成。
|
||||
|
||||
#### 階段 2
|
||||
|
||||
- 從頂部構造器鏈一直往下,每個構造器鏈中類的指定構造器都有機會進一步定制實例。構造器此時可以訪問`self`、修改它的屬性並調用實例方法等等。
|
||||
- 最終,任意構造器鏈中的便利構造器可以有機會定制實例和使用`self`。
|
||||
|
||||
下圖展示了在假定的子類和父類之間構造的階段1:
|
||||
·
|
||||

|
||||
|
||||
在這個例子中,構造過程從對子類中一個便利構造器的調用開始。這個便利構造器此時沒法修改任何屬性,它把構造任務代理給同一類中的指定構造器。
|
||||
|
||||
如安全檢查1所示,指定構造器將確保所有子類的屬性都有值。然後它將調用父類的指定構造器,並沿著造器鏈一直往上完成父類的構建過程。
|
||||
|
||||
父類中的指定構造器確保所有父類的屬性都有值。由於沒有更多的父類需要構建,也就無需繼續向上做構建代理。
|
||||
|
||||
一旦父類中所有屬性都有了初始值,實例的內存被認為是完全初始化,而階段1也已完成。
|
||||
|
||||
以下展示了相同構造過程的階段2:
|
||||
|
||||

|
||||
|
||||
父類中的指定構造器現在有機會進一步來定制實例(儘管它沒有這種必要)。
|
||||
|
||||
一旦父類中的指定構造器完成調用,子類的構指定構造器可以執行更多的定制操作(同樣,它也沒有這種必要)。
|
||||
|
||||
最終,一旦子類的指定構造器完成調用,最開始被調用的便利構造器可以執行更多的定制操作。
|
||||
|
||||
### 構造器的繼承和重載
|
||||
|
||||
跟 Objective-C 中的子類不同,Swift 中的子類不會默認繼承父類的構造器。Swift 的這種機制可以防止一個父類的簡單構造器被一個更專業的子類繼承,並被錯誤的用來創建子類的實例。
|
||||
|
||||
假如你希望自定義的子類中能實現一個或多個跟父類相同的構造器--也許是為了完成一些定制的構造過程--你可以在你定制的子類中提供和重載與父類相同的構造器。
|
||||
|
||||
如果你重載的構造器是一個指定構造器,你可以在子類裡重載它的實現,並在自定義版本的構造器中調用父類版本的構造器。
|
||||
|
||||
如果你重載的構造器是一個便利構造器,你的重載過程必須通過調用同一類中提供的其它指定構造器來實現。這一規則的詳細內容請參考[構造器鏈](#initialization_chain)。
|
||||
|
||||
>注意:
|
||||
與方法、屬性和下標不同,在重載構造器時你沒有必要使用關鍵字`override`。
|
||||
|
||||
<a name="automatic_initializer_inheritance"></a>
|
||||
### 自動構造器的繼承
|
||||
|
||||
如上所述,子類不會默認繼承父類的構造器。但是如果特定條件可以滿足,父類構造器是可以被自動繼承的。在實踐中,這意味著對於許多常見場景你不必重載父類的構造器,並且在盡可能安全的情況下以最小的代價來繼承父類的構造器。
|
||||
|
||||
假設要為子類中引入的任意新屬性提供默認值,請遵守以下2個規則:
|
||||
|
||||
#### 規則 1
|
||||
|
||||
如果子類沒有定義任何指定構造器,它將自動繼承所有父類的指定構造器。
|
||||
|
||||
#### 規則 2
|
||||
|
||||
如果子類提供了所有父類指定構造器的實現--不管是通過規則1繼承過來的,還是通過自定義實現的--它將自動繼承所有父類的便利構造器。
|
||||
|
||||
即使你在子類中添加了更多的便利構造器,這兩條規則仍然適用。
|
||||
|
||||
>注意:
|
||||
子類可以通過部分滿足規則2的方式,使用子類便利構造器來實現父類的指定構造器。
|
||||
|
||||
### 指定構造器和便利構造器的語法
|
||||
|
||||
類的指定構造器的寫法跟值類型簡單構造器一樣:
|
||||
|
||||
```swift
|
||||
init(parameters) {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
便利構造器也採用相同樣式的寫法,但需要在`init`關鍵字之前放置`convenience`關鍵字,並使用空格將它們倆分開:
|
||||
|
||||
```swift
|
||||
convenience init(parameters) {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
### 指定構造器和便利構造器實戰
|
||||
|
||||
接下來的例子將在實戰中展示指定構造器、便利構造器和自動構造器的繼承。它定義了包含三個類`Food`、`RecipeIngredient`以及`ShoppingListItem`的類層次結構,並將演示它們的構造器是如何相互作用的。
|
||||
|
||||
類層次中的基類是`Food`,它是一個簡單的用來封裝食物名字的類。`Food`類引入了一個叫做`name`的`String`類型屬性,並且提供了兩個構造器來創建`Food`實例:
|
||||
|
||||
```swift
|
||||
class Food {
|
||||
var name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
convenience init() {
|
||||
self.init(name: "[Unnamed]")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下圖中展示了`Food`的構造器鏈:
|
||||
|
||||

|
||||
|
||||
類沒有提供一個默認的逐一成員構造器,所以`Food`類提供了一個接受單一參數`name`的指定構造器。這個構造器可以使用一個特定的名字來創建新的`Food`實例:
|
||||
|
||||
```swift
|
||||
let namedMeat = Food(name: "Bacon")
|
||||
// namedMeat 的名字是 "Bacon」
|
||||
```
|
||||
|
||||
`Food`類中的構造器`init(name: String)`被定義為一個指定構造器,因為它能確保所有新`Food`實例的中存儲型屬性都被初始化。`Food`類沒有父類,所以`init(name: String)`構造器不需要調用`super.init()`來完成構造。
|
||||
|
||||
`Food`類同樣提供了一個沒有參數的便利構造器 `init()`。這個`init()`構造器為新食物提供了一個默認的佔位名字,通過代理調用同一類中定義的指定構造器`init(name: String)`並給參數`name`傳值`[Unnamed]`來實現:
|
||||
|
||||
```swift
|
||||
let mysteryMeat = Food()
|
||||
// mysteryMeat 的名字是 [Unnamed]
|
||||
```
|
||||
|
||||
類層級中的第二個類是`Food`的子類`RecipeIngredient`。`RecipeIngredient`類構建了食譜中的一味調味劑。它引入了`Int`類型的數量屬性`quantity`(以及從`Food`繼承過來的`name`屬性),並且定義了兩個構造器來創建`RecipeIngredient`實例:
|
||||
|
||||
```swift
|
||||
class RecipeIngredient: Food {
|
||||
var quantity: Int
|
||||
init(name: String, quantity: Int) {
|
||||
self.quantity = quantity
|
||||
super.init(name: name)
|
||||
}
|
||||
convenience init(name: String) {
|
||||
self.init(name: name, quantity: 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下圖中展示了`RecipeIngredient`類的構造器鏈:
|
||||
|
||||

|
||||
|
||||
`RecipeIngredient`類擁有一個指定構造器`init(name: String, quantity: Int)`,它可以用來產生新`RecipeIngredient`實例的所有屬性值。這個構造器一開始先將傳入的`quantity`參數賦值給`quantity`屬性,這個屬性也是唯一在`RecipeIngredient`中新引入的屬性。隨後,構造器將任務向上代理給父類`Food`的`init(name: String)`。這個過程滿足[兩段式構造過程](#two_phase_initialization)中的安全檢查1。
|
||||
|
||||
`RecipeIngredient`也定義了一個便利構造器`init(name: String)`,它只通過`name`來創建`RecipeIngredient`的實例。這個便利構造器假設任意`RecipeIngredient`實例的`quantity`為1,所以不需要顯示指明數量即可創建出實例。這個便利構造器的定義可以讓創建實例更加方便和快捷,並且避免了使用重複的代碼來創建多個`quantity`為 1 的`RecipeIngredient`實例。這個便利構造器只是簡單的將任務代理給了同一類裡提供的指定構造器。
|
||||
|
||||
注意,`RecipeIngredient`的便利構造器`init(name: String)`使用了跟`Food`中指定構造器`init(name: String)`相同的參數。儘管`RecipeIngredient`這個構造器是便利構造器,`RecipeIngredient`依然提供了對所有父類指定構造器的實現。因此,`RecipeIngredient`也能自動繼承了所有父類的便利構造器。
|
||||
|
||||
在這個例子中,`RecipeIngredient`的父類是`Food`,它有一個便利構造器`init()`。這個構造器因此也被`RecipeIngredient`繼承。這個繼承的`init()`函數版本跟`Food`提供的版本是一樣的,除了它是將任務代理給`RecipeIngredient`版本的`init(name: String)`而不是`Food`提供的版本。
|
||||
|
||||
所有的這三種構造器都可以用來創建新的`RecipeIngredient`實例:
|
||||
|
||||
```swift
|
||||
let oneMysteryItem = RecipeIngredient()
|
||||
let oneBacon = RecipeIngredient(name: "Bacon")
|
||||
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
|
||||
```
|
||||
|
||||
類層級中第三個也是最後一個類是`RecipeIngredient`的子類,叫做`ShoppingListItem`。這個類構建了購物單中出現的某一種調味料。
|
||||
|
||||
購物單中的每一項總是從`unpurchased`未購買狀態開始的。為了展現這一事實,`ShoppingListItem`引入了一個布爾類型的屬性`purchased`,它的默認值是`false`。`ShoppingListItem`還添加了一個計算型屬性`description`,它提供了關於`ShoppingListItem`實例的一些文字描述:
|
||||
|
||||
```swift
|
||||
class ShoppingListItem: RecipeIngredient {
|
||||
var purchased = false
|
||||
var description: String {
|
||||
var output = "\(quantity) x \(name.lowercaseString)"
|
||||
output += purchased ? " □" : " □"
|
||||
return output
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
`ShoppingListItem`沒有定義構造器來為`purchased`提供初始化值,這是因為任何添加到購物單的項的初始狀態總是未購買。
|
||||
|
||||
由於它為自己引入的所有屬性都提供了默認值,並且自己沒有定義任何構造器,`ShoppingListItem`將自動繼承所有父類中的指定構造器和便利構造器。
|
||||
|
||||
下圖種展示了所有三個類的構造器鏈:
|
||||
|
||||

|
||||
|
||||
你可以使用全部三個繼承來的構造器來創建`ShoppingListItem`的新實例:
|
||||
|
||||
```swift
|
||||
var breakfastList = [
|
||||
ShoppingListItem(),
|
||||
ShoppingListItem(name: "Bacon"),
|
||||
ShoppingListItem(name: "Eggs", quantity: 6),
|
||||
]
|
||||
breakfastList[0].name = "Orange juice"
|
||||
breakfastList[0].purchased = true
|
||||
for item in breakfastList {
|
||||
println(item.description)
|
||||
}
|
||||
// 1 x orange juice □
|
||||
// 1 x bacon □
|
||||
// 6 x eggs □
|
||||
```
|
||||
|
||||
如上所述,例子中通過字面量方式創建了一個新數組`breakfastList`,它包含了三個新的`ShoppingListItem`實例,因此數組的類型也能自動推導為`ShoppingListItem[]`。在數組創建完之後,數組中第一個`ShoppingListItem`實例的名字從`[Unnamed]`修改為`Orange juice`,並標記為已購買。接下來通過遍歷數組每個元素並打印它們的描述值,展示了所有項當前的默認狀態都已按照預期完成了賦值。
|
||||
|
||||
<a name="setting_a_default_property_value_with_a_closure_or_function"></a>
|
||||
## 通過閉包和函數來設置屬性的默認值
|
||||
|
||||
如果某個存儲型屬性的默認值需要特別的定制或準備,你就可以使用閉包或全局函數來為其屬性提供定制的默認值。每當某個屬性所屬的新類型實例創建時,對應的閉包或函數會被調用,而它們的返回值會當做默認值賦值給這個屬性。
|
||||
|
||||
這種類型的閉包或函數一般會創建一個跟屬性類型相同的臨時變量,然後修改它的值以滿足預期的初始狀態,最後將這個臨時變量的值作為屬性的默認值進行返回。
|
||||
|
||||
下面列舉了閉包如何提供默認值的代碼概要:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
let someProperty: SomeType = {
|
||||
// 在這個閉包中給 someProperty 創建一個默認值
|
||||
// someValue 必須和 SomeType 類型相同
|
||||
return someValue
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
注意閉包結尾的大括號後面接了一對空的小括號。這是用來告訴 Swift 需要立刻執行此閉包。如果你忽略了這對括號,相當於是將閉包本身作為值賦值給了屬性,而不是將閉包的返回值賦值給屬性。
|
||||
|
||||
>注意:
|
||||
如果你使用閉包來初始化屬性的值,請記住在閉包執行時,實例的其它部分都還沒有初始化。這意味著你不能夠在閉包裡訪問其它的屬性,就算這個屬性有默認值也不允許。同樣,你也不能使用隱式的`self`屬性,或者調用其它的實例方法。
|
||||
|
||||
下面例子中定義了一個結構體`Checkerboard`,它構建了西洋跳棋遊戲的棋盤:
|
||||
|
||||

|
||||
|
||||
西洋跳棋遊戲在一副黑白格交替的 10x10 的棋盤中進行。為了呈現這副遊戲棋盤,`Checkerboard`結構體定義了一個屬性`boardColors`,它是一個包含 100 個布爾值的數組。數組中的某元素布爾值為`true`表示對應的是一個黑格,布爾值為`false`表示對應的是一個白格。數組中第一個元素代表棋盤上左上角的格子,最後一個元素代表棋盤上右下角的格子。
|
||||
|
||||
`boardColor`數組是通過一個閉包來初始化和組裝顏色值的:
|
||||
|
||||
```swift
|
||||
struct Checkerboard {
|
||||
let boardColors: Bool[] = {
|
||||
var temporaryBoard = Bool[]()
|
||||
var isBlack = false
|
||||
for i in 1...10 {
|
||||
for j in 1...10 {
|
||||
temporaryBoard.append(isBlack)
|
||||
isBlack = !isBlack
|
||||
}
|
||||
isBlack = !isBlack
|
||||
}
|
||||
return temporaryBoard
|
||||
}()
|
||||
func squareIsBlackAtRow(row: Int, column: Int) -> Bool {
|
||||
return boardColors[(row * 10) + column]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
每當一個新的`Checkerboard`實例創建時,對應的賦值閉包會執行,一系列顏色值會被計算出來作為默認值賦值給`boardColors`。上面例子中描述的閉包將計算出棋盤中每個格子合適的顏色,將這些顏色值保存到一個臨時數組`temporaryBoard`中,並在構建完成時將此數組作為閉包返回值返回。這個返回的值將保存到`boardColors`中,並可以通`squareIsBlackAtRow`這個工具函數來查詢。
|
||||
|
||||
```swift
|
||||
let board = Checkerboard()
|
||||
println(board.squareIsBlackAtRow(0, column: 1))
|
||||
// 輸出 "true"
|
||||
println(board.squareIsBlackAtRow(9, column: 9))
|
||||
// 輸出 "false"
|
||||
```
|
108
source-tw/chapter2/15_Deinitialization.md
Normal file
108
source-tw/chapter2/15_Deinitialization.md
Normal file
@ -0,0 +1,108 @@
|
||||
> 翻譯:[bruce0505](https://github.com/bruce0505)
|
||||
> 校對:[fd5788](https://github.com/fd5788)
|
||||
|
||||
# 析構過程(Deinitialization)
|
||||
---------------------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [析構過程原理](#how_deinitialization_works)
|
||||
- [析構函數操作](#deinitializers_in_action)
|
||||
|
||||
在一個類的實例被釋放之前,析構函數被立即調用。用關鍵字`deinit`來標示析構函數,類似於初始化函數用`init`來標示。析構函數只適用於類類型。
|
||||
|
||||
<a name="how_deinitialization_works"></a>
|
||||
##析構過程原理
|
||||
|
||||
Swift 會自動釋放不再需要的實例以釋放資源。如[自動引用計數](16_Automatic_Reference_Counting.html)那一章描述,Swift 通過_自動引用計數_(ARC)處理實例的內存管理。通常當你的實例被釋放時不需要手動地去清理。但是,當使用自己的資源時,你可能需要進行一些額外的清理。例如,如果創建了一個自定義的類來打開一個文件,並寫入一些數據,你可能需要在類實例被釋放之前關閉該文件。
|
||||
|
||||
在類的定義中,每個類最多只能有一個析構函數。析構函數不帶任何參數,在寫法上不帶括號:
|
||||
|
||||
```swift
|
||||
deinit {
|
||||
// 執行析構過程
|
||||
}
|
||||
```
|
||||
|
||||
析構函數是在實例釋放發生前一步被自動調用。不允許主動調用自己的析構函數。子類繼承了父類的析構函數,並且在子類析構函數實現的最後,父類的析構函數被自動調用。即使子類沒有提供自己的析構函數,父類的析構函數也總是被調用。
|
||||
|
||||
因為直到實例的析構函數被調用時,實例才會被釋放,所以析構函數可以訪問所有請求實例的屬性,並且根據那些屬性可以修改它的行為(比如查找一個需要被關閉的文件的名稱)。
|
||||
|
||||
<a name="deinitializers_in_action"></a>
|
||||
##析構函數操作
|
||||
|
||||
這裡是一個析構函數操作的例子。這個例子是一個簡單的遊戲,定義了兩種新類型,`Bank`和`Player`。`Bank`結構體管理一個虛擬貨幣的流通,在這個流通中`Bank`永遠不可能擁有超過 10,000 的硬幣。在這個遊戲中有且只能有一個`Bank`存在,因此`Bank`由帶有靜態屬性和靜態方法的結構體實現,從而存儲和管理其當前的狀態。
|
||||
|
||||
```swift
|
||||
struct Bank {
|
||||
static var coinsInBank = 10_000
|
||||
static func vendCoins(var numberOfCoinsToVend: Int) -> Int {
|
||||
numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank)
|
||||
coinsInBank -= numberOfCoinsToVend
|
||||
return numberOfCoinsToVend
|
||||
}
|
||||
static func receiveCoins(coins: Int) {
|
||||
coinsInBank += coins
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Bank`根據它的`coinsInBank`屬性來跟蹤當前它擁有的硬幣數量。銀行還提供兩個方法——`vendCoins`和`receiveCoins`——用來處理硬幣的分發和收集。
|
||||
|
||||
`vendCoins`方法在 bank 分發硬幣之前檢查是否有足夠的硬幣。如果沒有足夠多的硬幣,`Bank`返回一個比請求時小的數字(如果沒有硬幣留在 bank 中就返回 0)。`vendCoins`方法聲明`numberOfCoinsToVend`為一個變量參數,這樣就可以在方法體的內部修改數字,而不需要定義一個新的變量。`vendCoins`方法返回一個整型值,表明了提供的硬幣的實際數目。
|
||||
|
||||
`receiveCoins`方法只是將 bank 的硬幣存儲和接收到的硬幣數目相加,再保存回 bank。
|
||||
|
||||
`Player`類描述了遊戲中的一個玩家。每一個 player 在任何時刻都有一定數量的硬幣存儲在他們的錢包中。這通過 player 的`coinsInPurse`屬性來體現:
|
||||
|
||||
```swift
|
||||
class Player {
|
||||
var coinsInPurse: Int
|
||||
init(coins: Int) {
|
||||
coinsInPurse = Bank.vendCoins(coins)
|
||||
}
|
||||
func winCoins(coins: Int) {
|
||||
coinsInPurse += Bank.vendCoins(coins)
|
||||
}
|
||||
deinit {
|
||||
Bank.receiveCoins(coinsInPurse)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
每個`Player`實例都由一個指定數目硬幣組成的啟動額度初始化,這些硬幣在 bank 初始化的過程中得到。如果沒有足夠的硬幣可用,`Player`實例可能收到比指定數目少的硬幣。
|
||||
|
||||
`Player`類定義了一個`winCoins`方法,該方法從銀行獲取一定數量的硬幣,並把它們添加到玩家的錢包。`Player`類還實現了一個析構函數,這個析構函數在`Player`實例釋放前一步被調用。這裡析構函數只是將玩家的所有硬幣都返回給銀行:
|
||||
|
||||
```swift
|
||||
var playerOne: Player? = Player(coins: 100)
|
||||
println("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
|
||||
// 輸出 "A new player has joined the game with 100 coins"
|
||||
println("There are now \(Bank.coinsInBank) coins left in the bank")
|
||||
// 輸出 "There are now 9900 coins left in the bank"
|
||||
```
|
||||
|
||||
一個新的`Player`實例隨著一個 100 個硬幣(如果有)的請求而被創建。這`個Player`實例存儲在一個名為`playerOne`的可選`Player`變量中。這裡使用一個可選變量,是因為玩家可以隨時離開遊戲。設置為可選使得你可以跟蹤當前是否有玩家在遊戲中。
|
||||
|
||||
因為`playerOne`是可選的,所以由一個感歎號(`!`)來修飾,每當其`winCoins`方法被調用時,`coinsInPurse`屬性被訪問並打印出它的默認硬幣數目。
|
||||
|
||||
```swift
|
||||
playerOne!.winCoins(2_000)
|
||||
println("PlayerOne won 2000 coins & now has \ (playerOne!.coinsInPurse) coins")
|
||||
// 輸出 "PlayerOne won 2000 coins & now has 2100 coins"
|
||||
println("The bank now only has \(Bank.coinsInBank) coins left")
|
||||
// 輸出 "The bank now only has 7900 coins left"
|
||||
```
|
||||
|
||||
這裡,player 已經贏得了 2,000 硬幣。player 的錢包現在有 2,100 硬幣,bank 只剩餘 7,900 硬幣。
|
||||
|
||||
```swift
|
||||
playerOne = nil
|
||||
println("PlayerOne has left the game")
|
||||
// 輸出 "PlayerOne has left the game"
|
||||
println("The bank now has \(Bank.coinsInBank) coins")
|
||||
// 輸出 "The bank now has 10000 coins"
|
||||
```
|
||||
|
||||
玩家現在已經離開了遊戲。這表明是要將可選的`playerOne`變量設置為`nil`,意思是「沒有`Player`實例」。當這種情況發生的時候,`playerOne`變量對`Player`實例的引用被破壞了。沒有其它屬性或者變量引用`Player`實例,因此為了清空它佔用的內存從而釋放它。在這發生前一步,其析構函數被自動調用,其硬幣被返回到銀行。
|
555
source-tw/chapter2/16_Automatic_Reference_Counting.md
Normal file
555
source-tw/chapter2/16_Automatic_Reference_Counting.md
Normal file
@ -0,0 +1,555 @@
|
||||
> 翻譯:[TimothyYe](https://github.com/TimothyYe)
|
||||
> 校對:[Hawstein](https://github.com/Hawstein)
|
||||
|
||||
# 自動引用計數
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [自動引用計數的工作機制](#how_arc_works)
|
||||
- [自動引用計數實踐](#arc_in_action)
|
||||
- [類實例之間的循環強引用](#strong_reference_cycles_between_class_instances)
|
||||
- [解決實例之間的循環強引用](#resolving_strong_reference_cycles_between_class_instances)
|
||||
- [閉包引起的循環強引用](#strong_reference_cycles_for_closures)
|
||||
- [解決閉包引起的循環強引用](#resolving_strong_reference_cycles_for_closures)
|
||||
|
||||
Swift 使用自動引用計數(ARC)這一機制來跟蹤和管理你的應用程序的內存。通常情況下,Swift 的內存管理機制會一直起著作用,你無須自己來考慮內存的管理。ARC 會在類的實例不再被使用時,自動釋放其佔用的內存。
|
||||
|
||||
然而,在少數情況下,ARC 為了能幫助你管理內存,需要更多的關於你的代碼之間關係的信息。本章描述了這些情況,並且為你示範怎樣啟用 ARC 來管理你的應用程序的內存。
|
||||
|
||||
> 注意:
|
||||
引用計數僅僅應用於類的實例。結構體和枚舉類型是值類型,不是引用類型,也不是通過引用的方式存儲和傳遞。
|
||||
|
||||
<a name="how_arc_works"></a>
|
||||
## 自動引用計數的工作機制
|
||||
|
||||
當你每次創建一個類的新的實例的時候,ARC 會分配一大塊內存用來儲存實例的信息。內存中會包含實例的類型信息,以及這個實例所有相關屬性的值。此外,當實例不再被使用時,ARC 釋放實例所佔用的內存,並讓釋放的內存能挪作他用。這確保了不再被使用的實例,不會一直佔用內存空間。
|
||||
|
||||
然而,當 ARC 收回和釋放了正在被使用中的實例,該實例的屬性和方法將不能再被訪問和調用。實際上,如果你試圖訪問這個實例,你的應用程序很可能會崩潰。
|
||||
|
||||
為了確保使用中的實例不會被銷毀,ARC 會跟蹤和計算每一個實例正在被多少屬性,常量和變量所引用。哪怕實例的引用數為一,ARC都不會銷毀這個實例。
|
||||
|
||||
為了使之成為可能,無論你將實例賦值給屬性,常量或者是變量,屬性,常量或者變量,都會對此實例創建強引用。之所以稱之為強引用,是因為它會將實例牢牢的保持住,只要強引用還在,實例是不允許被銷毀的。
|
||||
|
||||
<a name="arc_in_action"></a>
|
||||
## 自動引用計數實踐
|
||||
|
||||
下面的例子展示了自動引用計數的工作機制。例子以一個簡單的`Person`類開始,並定義了一個叫`name`的常量屬性:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
let name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
println("\(name) is being initialized")
|
||||
}
|
||||
deinit {
|
||||
println("\(name) is being deinitialized")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Person`類有一個構造函數,此構造函數為實例的`name`屬性賦值並打印出信息,以表明初始化過程生效。`Person`類同時也擁有析構函數,同樣會在實例被銷毀的時候打印出信息。
|
||||
|
||||
接下來的代碼片段定義了三個類型為`Person?`的變量,用來按照代碼片段中的順序,為新的`Person`實例建立多個引用。由於這些變量是被定義為可選類型(Person?,而不是Person),它們的值會被自動初始化為`nil`,目前還不會引用到`Person`類的實例。
|
||||
|
||||
```swift
|
||||
var reference1: Person?
|
||||
var reference2: Person?
|
||||
var reference3: Person?
|
||||
```
|
||||
|
||||
現在你可以創建`Person`類的新實例,並且將它賦值給三個變量其中的一個:
|
||||
|
||||
```swift
|
||||
reference1 = Person(name: "John Appleseed")
|
||||
// prints "John Appleseed is being initialized」
|
||||
```
|
||||
|
||||
應當注意到當你調用`Person`類的構造函數的時候,"John Appleseed is being initialized」會被打印出來。由此可以確定構造函數被執行。
|
||||
|
||||
由於`Person`類的新實例被賦值給了`reference1`變量,所以`reference1`到`Person`類的新實例之間建立了一個強引用。正是因為這個強引用,ARC 會保證`Person`實例被保持在內存中不被銷毀。
|
||||
|
||||
如果你將同樣的`Person`實例也賦值給其他兩個變量,該實例又會多出兩個強引用:
|
||||
|
||||
```swift
|
||||
reference2 = reference1
|
||||
reference3 = reference1
|
||||
```
|
||||
|
||||
現在這個`Person`實例已經有三個強引用了。
|
||||
|
||||
如果你通過給兩個變量賦值`nil`的方式斷開兩個強引用()包括最先的那個強引用),只留下一個強引用,`Person`實例不會被銷毀:
|
||||
|
||||
```swift
|
||||
reference1 = nil
|
||||
reference2 = nil
|
||||
```
|
||||
|
||||
ARC 會在第三個,也即最後一個強引用被斷開的時候,銷毀`Person`實例,這也意味著你不再使用這個`Person`實例:
|
||||
|
||||
```swift
|
||||
reference3 = nil
|
||||
// prints "John Appleseed is being deinitialized"
|
||||
```
|
||||
|
||||
<a name="strong_reference_cycles_between_class_instances"></a>
|
||||
## 類實例之間的循環強引用
|
||||
|
||||
在上面的例子中,ARC 會跟蹤你所新創建的`Person`實例的引用數量,並且會在`Person`實例不再被需要時銷毀它。
|
||||
|
||||
然而,我們可能會寫出這樣的代碼,一個類永遠不會有0個強引用。這種情況發生在兩個類實例互相保持對方的強引用,並讓對方不被銷毀。這就是所謂的循環強引用。
|
||||
|
||||
你可以通過定義類之間的關係為弱引用或者無主引用,以此替代強引用,從而解決循環強引用的問題。具體的過程在[解決類實例之間的循環強引用](#resolving_strong_reference_cycles_between_class_instances)中有描述。不管怎樣,在你學習怎樣解決循環強引用之前,很有必要瞭解一下它是怎樣產生的。
|
||||
|
||||
下面展示了一個不經意產生循環強引用的例子。例子定義了兩個類:`Person`和`Apartment`,用來建模公寓和它其中的居民:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
var apartment: Apartment?
|
||||
deinit { println("\(name) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class Apartment {
|
||||
let number: Int
|
||||
init(number: Int) { self.number = number }
|
||||
var tenant: Person?
|
||||
deinit { println("Apartment #\(number) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
每一個`Person`實例有一個類型為`String`,名字為`name`的屬性,並有一個可選的初始化為`nil`的`apartment`屬性。`apartment`屬性是可選的,因為一個人並不總是擁有公寓。
|
||||
|
||||
類似的,每個`Apartment`實例有一個叫`number`,類型為`Int`的屬性,並有一個可選的初始化為`nil`的`tenant`屬性。`tenant`屬性是可選的,因為一棟公寓並不總是有居民。
|
||||
|
||||
這兩個類都定義了析構函數,用以在類實例被析構的時候輸出信息。這讓你能夠知曉`Person`和`Apartment`的實例是否像預期的那樣被銷毀。
|
||||
|
||||
接下來的代碼片段定義了兩個可選類型的變量`john`和`number73`,並分別被設定為下面的`Apartment`和`Person`的實例。這兩個變量都被初始化為`nil`,並為可選的:
|
||||
|
||||
```swift
|
||||
var john: Person?
|
||||
var number73: Apartment?
|
||||
```
|
||||
|
||||
現在你可以創建特定的`Person`和`Apartment`實例並將類實例賦值給`john`和`number73`變量:
|
||||
|
||||
```swift
|
||||
john = Person(name: "John Appleseed")
|
||||
number73 = Apartment(number: 73)
|
||||
```
|
||||
|
||||
在兩個實例被創建和賦值後,下圖表現了強引用的關係。變量`john`現在有一個指向`Person`實例的強引用,而變量`number73`有一個指向`Apartment`實例的強引用:
|
||||
|
||||

|
||||
|
||||
現在你能夠將這兩個實例關聯在一起,這樣人就能有公寓住了,而公寓也有了房客。注意感歎號是用來展開和訪問可選變量`john`和`number73`中的實例,這樣實例的屬性才能被賦值:
|
||||
|
||||
```swift
|
||||
john!.apartment = number73
|
||||
number73!.tenant = john
|
||||
```
|
||||
|
||||
在將兩個實例聯繫在一起之後,強引用的關係如圖所示:
|
||||
|
||||

|
||||
|
||||
不幸的是,將這兩個實例關聯在一起之後,一個循環強引用被創建了。`Person`實例現在有了一個指向`Apartment`實例的強引用,而`Apartment`實例也有了一個指向`Person`實例的強引用。因此,當你斷開`john`和`number73`變量所持有的強引用時,引用計數並不會降為 0,實例也不會被 ARC 銷毀:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
number73 = nil
|
||||
```
|
||||
|
||||
注意,當你把這兩個變量設為`nil`時,沒有任何一個析構函數被調用。強引用循環阻止了`Person`和`Apartment`類實例的銷毀,並在你的應用程序中造成了內存洩漏。
|
||||
|
||||
在你將`john`和`number73`賦值為`nil`後,強引用關係如下圖:
|
||||
|
||||

|
||||
|
||||
`Person`和`Apartment`實例之間的強引用關係保留了下來並且不會被斷開。
|
||||
|
||||
<a name="resolving_strong_reference_cycles_between_class_instances"></a>
|
||||
## 解決實例之間的循環強引用
|
||||
|
||||
Swift 提供了兩種辦法用來解決你在使用類的屬性時所遇到的循環強引用問題:弱引用(weak reference)和無主引用(unowned reference)。
|
||||
|
||||
弱引用和無主引用允許循環引用中的一個實例引用另外一個實例而不保持強引用。這樣實例能夠互相引用而不產生循環強引用。
|
||||
|
||||
對於生命週期中會變為`nil`的實例使用弱引用。相反的,對於初始化賦值後再也不會被賦值為`nil`的實例,使用無主引用。
|
||||
|
||||
### 弱引用
|
||||
|
||||
弱引用不會牢牢保持住引用的實例,並且不會阻止 ARC 銷毀被引用的實例。這種行為阻止了引用變為循環強引用。聲明屬性或者變量時,在前面加上`weak`關鍵字表明這是一個弱引用。
|
||||
|
||||
在實例的生命週期中,如果某些時候引用沒有值,那麼弱引用可以阻止循環強引用。如果引用總是有值,則可以使用無主引用,在[無主引用](#2)中有描述。在上面`Apartment`的例子中,一個公寓的生命週期中,有時是沒有「居民」的,因此適合使用弱引用來解決循環強引用。
|
||||
|
||||
> 注意:
|
||||
> 弱引用必須被聲明為變量,表明其值能在運行時被修改。弱引用不能被聲明為常量。
|
||||
|
||||
因為弱引用可以沒有值,你必須將每一個弱引用聲明為可選類型。可選類型是在 Swift 語言中推薦的用來表示可能沒有值的類型。
|
||||
|
||||
因為弱引用不會保持所引用的實例,即使引用存在,實例也有可能被銷毀。因此,ARC 會在引用的實例被銷毀後自動將其賦值為`nil`。你可以像其他可選值一樣,檢查弱引用的值是否存在,你永遠也不會遇到被銷毀了而不存在的實例。
|
||||
|
||||
下面的例子跟上面`Person`和`Apartment`的例子一致,但是有一個重要的區別。這一次,`Apartment`的`tenant`屬性被聲明為弱引用:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
var apartment: Apartment?
|
||||
deinit { println("\(name) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class Apartment {
|
||||
let number: Int
|
||||
init(number: Int) { self.number = number }
|
||||
weak var tenant: Person?
|
||||
deinit { println("Apartment #\(number) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
然後跟之前一樣,建立兩個變量(john和number73)之間的強引用,並關聯兩個實例:
|
||||
|
||||
```swift
|
||||
var john: Person?
|
||||
var number73: Apartment?
|
||||
|
||||
john = Person(name: "John Appleseed")
|
||||
number73 = Apartment(number: 73)
|
||||
|
||||
john!.apartment = number73
|
||||
number73!.tenant = john
|
||||
```
|
||||
|
||||
現在,兩個關聯在一起的實例的引用關係如下圖所示:
|
||||
|
||||

|
||||
|
||||
`Person`實例依然保持對`Apartment`實例的強引用,但是`Apartment`實例只是對`Person`實例的弱引用。這意味著當你斷開`john`變量所保持的強引用時,再也沒有指向`Person`實例的強引用了:
|
||||
|
||||

|
||||
|
||||
由於再也沒有指向`Person`實例的強引用,該實例會被銷毀:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
// prints "John Appleseed is being deinitialized"
|
||||
```
|
||||
|
||||
唯一剩下的指向`Apartment`實例的強引用來自於變量`number73`。如果你斷開這個強引用,再也沒有指向`Apartment`實例的強引用了:
|
||||
|
||||

|
||||
|
||||
由於再也沒有指向`Apartment`實例的強引用,該實例也會被銷毀:
|
||||
|
||||
```swift
|
||||
number73 = nil
|
||||
// prints "Apartment #73 is being deinitialized"
|
||||
```
|
||||
|
||||
上面的兩段代碼展示了變量`john`和`number73`在被賦值為`nil`後,`Person`實例和`Apartment`實例的析構函數都打印出「銷毀」的信息。這證明了引用循環被打破了。
|
||||
|
||||
<a name="2"></a>
|
||||
### 無主引用
|
||||
|
||||
和弱引用類似,無主引用不會牢牢保持住引用的實例。和弱引用不同的是,無主引用是永遠有值的。因此,無主引用總是被定義為非可選類型(non-optional type)。你可以在聲明屬性或者變量時,在前面加上關鍵字`unowned`表示這是一個無主引用。
|
||||
|
||||
由於無主引用是非可選類型,你不需要在使用它的時候將它展開。無主引用總是可以被直接訪問。不過 ARC 無法在實例被銷毀後將無主引用設為`nil`,因為非可選類型的變量不允許被賦值為`nil`。
|
||||
|
||||
> 注意:
|
||||
>如果你試圖在實例被銷毀後,訪問該實例的無主引用,會觸發運行時錯誤。使用無主引用,你必須確保引用始終指向一個未銷毀的實例。
|
||||
> 還需要注意的是如果你試圖訪問實例已經被銷毀的無主引用,程序會直接崩潰,而不會發生無法預期的行為。所以你應當避免這樣的事情發生。
|
||||
|
||||
下面的例子定義了兩個類,`Customer`和`CreditCard`,模擬了銀行客戶和客戶的信用卡。這兩個類中,每一個都將另外一個類的實例作為自身的屬性。這種關係會潛在的創造循環強引用。
|
||||
|
||||
`Customer`和`CreditCard`之間的關係與前面弱引用例子中`Apartment`和`Person`的關係截然不同。在這個數據模型中,一個客戶可能有或者沒有信用卡,但是一張信用卡總是關聯著一個客戶。為了表示這種關係,`Customer`類有一個可選類型的`card`屬性,但是`CreditCard`類有一個非可選類型的`customer`屬性。
|
||||
|
||||
此外,只能通過將一個`number`值和`customer`實例傳遞給`CreditCard`構造函數的方式來創建`CreditCard`實例。這樣可以確保當創建`CreditCard`實例時總是有一個`customer`實例與之關聯。
|
||||
|
||||
由於信用卡總是關聯著一個客戶,因此將`customer`屬性定義為無主引用,用以避免循環強引用:
|
||||
|
||||
```swift
|
||||
class Customer {
|
||||
let name: String
|
||||
var card: CreditCard?
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
deinit { println("\(name) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class CreditCard {
|
||||
let number: Int
|
||||
unowned let customer: Customer
|
||||
init(number: Int, customer: Customer) {
|
||||
self.number = number
|
||||
self.customer = customer
|
||||
}
|
||||
deinit { println("Card #\(number) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
下面的代碼片段定義了一個叫`john`的可選類型`Customer`變量,用來保存某個特定客戶的引用。由於是可選類型,所以變量被初始化為`nil`。
|
||||
|
||||
```swift
|
||||
var john: Customer?
|
||||
```
|
||||
|
||||
現在你可以創建`Customer`類的實例,用它初始化`CreditCard`實例,並將新創建的`CreditCard`實例賦值為客戶的`card`屬性。
|
||||
|
||||
```swift
|
||||
john = Customer(name: "John Appleseed")
|
||||
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
|
||||
```
|
||||
|
||||
在你關聯兩個實例後,它們的引用關係如下圖所示:
|
||||
|
||||

|
||||
|
||||
`Customer`實例持有對`CreditCard`實例的強引用,而`CreditCard`實例持有對`Customer`實例的無主引用。
|
||||
|
||||
由於`customer`的無主引用,當你斷開`john`變量持有的強引用時,再也沒有指向`Customer`實例的強引用了:
|
||||
|
||||

|
||||
|
||||
由於再也沒有指向`Customer`實例的強引用,該實例被銷毀了。其後,再也沒有指向`CreditCard`實例的強引用,該實例也隨之被銷毀了:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
// prints "John Appleseed is being deinitialized"
|
||||
// prints "Card #1234567890123456 is being deinitialized"
|
||||
```
|
||||
|
||||
最後的代碼展示了在`john`變量被設為`nil`後`Customer`實例和`CreditCard`實例的構造函數都打印出了「銷毀」的信息。
|
||||
|
||||
###無主引用以及隱式解析可選屬性
|
||||
|
||||
上面弱引用和無主引用的例子涵蓋了兩種常用的需要打破循環強引用的場景。
|
||||
|
||||
`Person`和`Apartment`的例子展示了兩個屬性的值都允許為`nil`,並會潛在的產生循環強引用。這種場景最適合用弱引用來解決。
|
||||
|
||||
`Customer`和`CreditCard`的例子展示了一個屬性的值允許為`nil`,而另一個屬性的值不允許為`nil`,並會潛在的產生循環強引用。這種場景最適合通過無主引用來解決。
|
||||
|
||||
然而,存在著第三種場景,在這種場景中,兩個屬性都必須有值,並且初始化完成後不能為`nil`。在這種場景中,需要一個類使用無主屬性,而另外一個類使用隱式解析可選屬性。
|
||||
|
||||
這使兩個屬性在初始化完成後能被直接訪問(不需要可選展開),同時避免了循環引用。這一節將為你展示如何建立這種關係。
|
||||
|
||||
下面的例子定義了兩個類,`Country`和`City`,每個類將另外一個類的實例保存為屬性。在這個模型中,每個國家必須有首都,而每一個城市必須屬於一個國家。為了實現這種關係,`Country`類擁有一個`capitalCity`屬性,而`City`類有一個`country`屬性:
|
||||
|
||||
```swift
|
||||
class Country {
|
||||
let name: String
|
||||
let capitalCity: City!
|
||||
init(name: String, capitalName: String) {
|
||||
self.name = name
|
||||
self.capitalCity = City(name: capitalName, country: self)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class City {
|
||||
let name: String
|
||||
unowned let country: Country
|
||||
init(name: String, country: Country) {
|
||||
self.name = name
|
||||
self.country = country
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
為了建立兩個類的依賴關係,`City`的構造函數有一個`Country`實例的參數,並且將實例保存為`country`屬性。
|
||||
|
||||
`Country`的構造函數調用了`City`的構造函數。然而,只有`Country`的實例完全初始化完後,`Country`的構造函數才能把`self`傳給`City`的構造函數。([在兩段式構造過程中有具體描述](14_Initialization.html))
|
||||
|
||||
為了滿足這種需求,通過在類型結尾處加上感歎號(City!)的方式,將`Country`的`capitalCity`屬性聲明為隱式解析可選類型的屬性。這表示像其他可選類型一樣,`capitalCity`屬性的默認值為`nil`,但是不需要展開它的值就能訪問它。([在隱式解析可選類型中有描述](01_The_Basics.html))
|
||||
|
||||
由於`capitalCity`默認值為`nil`,一旦`Country`的實例在構造函數中給`name`屬性賦值後,整個初始化過程就完成了。這代表一旦`name`屬性被賦值後,`Country`的構造函數就能引用並傳遞隱式的`self`。`Country`的構造函數在賦值`capitalCity`時,就能將`self`作為參數傳遞給`City`的構造函數。
|
||||
|
||||
以上的意義在於你可以通過一條語句同時創建`Country`和`City`的實例,而不產生循環強引用,並且`capitalCity`的屬性能被直接訪問,而不需要通過感歎號來展開它的可選值:
|
||||
|
||||
```swift
|
||||
var country = Country(name: "Canada", capitalName: "Ottawa")
|
||||
println("\(country.name)'s capital city is called \(country.capitalCity.name)")
|
||||
// prints "Canada's capital city is called Ottawa"
|
||||
```
|
||||
|
||||
在上面的例子中,使用隱式解析可選值的意義在於滿足了兩個類構造函數的需求。`capitalCity`屬性在初始化完成後,能像非可選值一樣使用和存取同時還避免了循環強引用。
|
||||
|
||||
<a name="strong_reference_cycles_for_closures"></a>
|
||||
##閉包引起的循環強引用
|
||||
|
||||
前面我們看到了循環強引用環是在兩個類實例屬性互相保持對方的強引用時產生的,還知道了如何用弱引用和無主引用來打破循環強引用。
|
||||
|
||||
循環強引用還會發生在當你將一個閉包賦值給類實例的某個屬性,並且這個閉包體中又使用了實例。這個閉包體中可能訪問了實例的某個屬性,例如`self.someProperty`,或者閉包中調用了實例的某個方法,例如`self.someMethod`。這兩種情況都導致了閉包 「捕獲" `self`,從而產生了循環強引用。
|
||||
|
||||
循環強引用的產生,是因為閉包和類相似,都是引用類型。當你把一個閉包賦值給某個屬性時,你也把一個引用賦值給了這個閉包。實質上,這跟之前的問題是一樣的-兩個強引用讓彼此一直有效。但是,和兩個類實例不同,這次一個是類實例,另一個是閉包。
|
||||
|
||||
Swift 提供了一種優雅的方法來解決這個問題,稱之為閉包佔用列表(closuer capture list)。同樣的,在學習如何用閉包佔用列表破壞循環強引用之前,先來瞭解一下循環強引用是如何產生的,這對我們是很有幫助的。
|
||||
|
||||
下面的例子為你展示了當一個閉包引用了`self`後是如何產生一個循環強引用的。例子中定義了一個叫`HTMLElement`的類,用一種簡單的模型表示 HTML 中的一個單獨的元素:
|
||||
|
||||
```swift
|
||||
class HTMLElement {
|
||||
|
||||
let name: String
|
||||
let text: String?
|
||||
|
||||
@lazy var asHTML: () -> String = {
|
||||
if let text = self.text {
|
||||
return "<\(self.name)>\(text)</\(self.name)>"
|
||||
} else {
|
||||
return "<\(self.name) />"
|
||||
}
|
||||
}
|
||||
|
||||
init(name: String, text: String? = nil) {
|
||||
self.name = name
|
||||
self.text = text
|
||||
}
|
||||
|
||||
deinit {
|
||||
println("\(name) is being deinitialized")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
`HTMLElement`類定義了一個`name`屬性來表示這個元素的名稱,例如代表段落的"p",或者代表換行的"br"。`HTMLElement`還定義了一個可選屬性`text`,用來設置和展現 HTML 元素的文本。
|
||||
|
||||
除了上面的兩個屬性,`HTMLElement`還定義了一個`lazy`屬性`asHTML`。這個屬性引用了一個閉包,將`name`和`text`組合成 HTML 字符串片段。該屬性是`() -> String`類型,或者可以理解為「一個沒有參數,返回`String`的函數」。
|
||||
|
||||
默認情況下,閉包賦值給了`asHTML`屬性,這個閉包返回一個代表 HTML 標籤的字符串。如果`text`值存在,該標籤就包含可選值`text`;如果`text`不存在,該標籤就不包含文本。對於段落元素,根據`text`是"some text"還是`nil`,閉包會返回"`<p>some text</p>`"或者"`<p />`"。
|
||||
|
||||
可以像實例方法那樣去命名、使用`asHTML`屬性。然而,由於`asHTML`是閉包而不是實例方法,如果你想改變特定元素的 HTML 處理的話,可以用自定義的閉包來取代默認值。
|
||||
|
||||
> 注意:
|
||||
`asHTML`聲明為`lazy`屬性,因為只有當元素確實需要處理為HTML輸出的字符串時,才需要使用`asHTML`。也就是說,在默認的閉包中可以使用`self`,因為只有當初始化完成以及`self`確實存在後,才能訪問`lazy`屬性。
|
||||
|
||||
`HTMLElement`類只提供一個構造函數,通過`name`和`text`(如果有的話)參數來初始化一個元素。該類也定義了一個析構函數,當`HTMLElement`實例被銷毀時,打印一條消息。
|
||||
|
||||
下面的代碼展示了如何用`HTMLElement`類創建實例並打印消息。
|
||||
|
||||
```swift
|
||||
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
|
||||
println(paragraph!.asHTML())
|
||||
// prints"hello, world"
|
||||
```
|
||||
|
||||
>注意:
|
||||
上面的`paragraph`變量定義為`可選HTMLElement`,因此我們可以賦值`nil`給它來演示循環強引用。
|
||||
|
||||
不幸的是,上面寫的`HTMLElement`類產生了類實例和`asHTML`默認值的閉包之間的循環強引用。循環強引用如下圖所示:
|
||||
|
||||

|
||||
|
||||
實例的`asHTML`屬性持有閉包的強引用。但是,閉包在其閉包體內使用了`self`(引用了`self.name`和`self.text`),因此閉包捕獲了`self`,這意味著閉包又反過來持有了`HTMLElement`實例的強引用。這樣兩個對象就產生了循環強引用。(更多關於閉包捕獲值的信息,請參考[值捕獲](07_Closures.html))。
|
||||
|
||||
>注意:
|
||||
雖然閉包多次使用了`self`,它只捕獲`HTMLElement`實例的一個強引用。
|
||||
|
||||
如果設置`paragraph`變量為`nil`,打破它持有的`HTMLElement`實例的強引用,`HTMLElement`實例和它的閉包都不會被銷毀,也是因為循環強引用:
|
||||
|
||||
```swift
|
||||
paragraph = nil
|
||||
```
|
||||
|
||||
注意`HTMLElementdeinitializer`中的消息並沒有別打印,證明了`HTMLElement`實例並沒有被銷毀。
|
||||
|
||||
<a name="resolving_strong_reference_cycles_for_closures"></a>
|
||||
##解決閉包引起的循環強引用
|
||||
|
||||
在定義閉包時同時定義捕獲列表作為閉包的一部分,通過這種方式可以解決閉包和類實例之間的循環強引用。捕獲列表定義了閉包體內捕獲一個或者多個引用類型的規則。跟解決兩個類實例間的循環強引用一樣,聲明每個捕獲的引用為弱引用或無主引用,而不是強引用。應當根據代碼關係來決定使用弱引用還是無主引用。
|
||||
|
||||
>注意:
|
||||
Swift 有如下要求:只要在閉包內使用`self`的成員,就要用`self.someProperty`或者`self.someMethod`(而不只是`someProperty`或`someMethod`)。這提醒你可能會不小心就捕獲了`self`。
|
||||
|
||||
###定義捕獲列表
|
||||
|
||||
捕獲列表中的每個元素都是由`weak`或者`unowned`關鍵字和實例的引用(如`self`或`someInstance`)成對組成。每一對都在方括號中,通過逗號分開。
|
||||
|
||||
捕獲列表放置在閉包參數列表和返回類型之前:
|
||||
|
||||
```swift
|
||||
@lazy var someClosure: (Int, String) -> String = {
|
||||
[unowned self] (index: Int, stringToProcess: String) -> String in
|
||||
// closure body goes here
|
||||
}
|
||||
```
|
||||
|
||||
如果閉包沒有指定參數列表或者返回類型,則可以通過上下文推斷,那麼可以捕獲列表放在閉包開始的地方,跟著是關鍵字`in`:
|
||||
|
||||
```swift
|
||||
@lazy var someClosure: () -> String = {
|
||||
[unowned self] in
|
||||
// closure body goes here
|
||||
}
|
||||
```
|
||||
|
||||
###弱引用和無主引用
|
||||
|
||||
當閉包和捕獲的實例總是互相引用時並且總是同時銷毀時,將閉包內的捕獲定義為無主引用。
|
||||
|
||||
相反的,當捕獲引用有時可能會是`nil`時,將閉包內的捕獲定義為弱引用。弱引用總是可選類型,並且當引用的實例被銷毀後,弱引用的值會自動置為`nil`。這使我們可以在閉包內檢查它們是否存在。
|
||||
|
||||
>注意:
|
||||
如果捕獲的引用絕對不會置為`nil`,應該用無主引用,而不是弱引用。
|
||||
|
||||
前面的`HTMLElement`例子中,無主引用是正確的解決循環強引用的方法。這樣編寫`HTMLElement`類來避免循環強引用:
|
||||
|
||||
```swift
|
||||
class HTMLElement {
|
||||
|
||||
let name: String
|
||||
let text: String?
|
||||
|
||||
@lazy var asHTML: () -> String = {
|
||||
[unowned self] in
|
||||
if let text = self.text {
|
||||
return "<\(self.name)>\(text)</\(self.name)>"
|
||||
} else {
|
||||
return "<\(self.name) />"
|
||||
}
|
||||
}
|
||||
|
||||
init(name: String, text: String? = nil) {
|
||||
self.name = name
|
||||
self.text = text
|
||||
}
|
||||
|
||||
deinit {
|
||||
println("\(name) is being deinitialized")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
上面的`HTMLElement`實現和之前的實現一致,只是在`asHTML`閉包中多了一個捕獲列表。這裡,捕獲列表是`[unowned self]`,表示「用無主引用而不是強引用來捕獲`self`」。
|
||||
|
||||
和之前一樣,我們可以創建並打印`HTMLElement`實例:
|
||||
|
||||
```swift
|
||||
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
|
||||
println(paragraph!.asHTML())
|
||||
// prints "<p>hello, world</p>"
|
||||
```
|
||||
|
||||
使用捕獲列表後引用關係如下圖所示:
|
||||
|
||||

|
||||
|
||||
這一次,閉包以無主引用的形式捕獲`self`,並不會持有`HTMLElement`實例的強引用。如果將`paragraph`賦值為`nil`,`HTMLElement`實例將會被銷毀,並能看到它的析構函數打印出的消息。
|
||||
|
||||
```swift
|
||||
paragraph = nil
|
||||
// prints "p is being deinitialized"
|
||||
```
|
||||
|
321
source-tw/chapter2/17_Optional_Chaining.md
Normal file
321
source-tw/chapter2/17_Optional_Chaining.md
Normal file
@ -0,0 +1,321 @@
|
||||
> 翻譯:[Jasonbroker](https://github.com/Jasonbroker)
|
||||
> 校對:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
# Optional Chaining
|
||||
-----------------
|
||||
|
||||
本頁包含內容:
|
||||
|
||||
- [可選鏈可替代強制解析](#optional_chaining_as_an_alternative_to_forced_unwrapping)
|
||||
- [為可選鏈定義模型類](#defining_model_classes_for_optional_chaining)
|
||||
- [通過可選鏈調用屬性](#calling_properties_through_optional_chaining)
|
||||
- [通過可選鏈調用方法](#calling_methods_through_optional_chaining)
|
||||
- [使用可選鏈調用子腳本](#calling_subscripts_through_optional_chaining)
|
||||
- [連接多層鏈接](#linking_multiple_levels_of_chaining)
|
||||
- [鏈接可選返回值的方法](#chaining_on_methods_with_optional_return_values)
|
||||
|
||||
可選鏈(Optional Chaining)是一種可以請求和調用屬性、方法及子腳本的過程,它的可選性體現於請求或調用的目標當前可能為空(`nil`)。如果可選的目標有值,那麼調用就會成功;相反,如果選擇的目標為空(`nil`),則這種調用將返回空(`nil`)。多次請求或調用可以被鏈接在一起形成一個鏈,如果任何一個節點為空(`nil`)將導致整個鏈失效。
|
||||
|
||||
> 注意:
|
||||
Swift 的可選鏈和 Objective-C 中的消息為空有些相像,但是 Swift 可以使用在任意類型中,並且失敗與否可以被檢測到。
|
||||
|
||||
<a name="optional_chaining_as_an_alternative_to_forced_unwrapping"></a>
|
||||
## 可選鏈可替代強制解析
|
||||
|
||||
通過在想調用的屬性、方法、或子腳本的可選值(`optional value`)(非空)後面放一個問號,可以定義一個可選鏈。這一點很像在可選值後面放一個歎號來強制拆得其封包內的值。它們的主要的區別在於當可選值為空時可選鏈即刻失敗,然而一般的強制解析將會引發運行時錯誤。
|
||||
|
||||
為了反映可選鏈可以調用空(`nil`),不論你調用的屬性、方法、子腳本等返回的值是不是可選值,它的返回結果都是一個可選值。你可以利用這個返回值來檢測你的可選鏈是否調用成功,有返回值即成功,返回nil則失敗。
|
||||
|
||||
調用可選鏈的返回結果與原本的返回結果具有相同的類型,但是原本的返回結果被包裝成了一個可選值,當可選鏈調用成功時,一個應該返回`Int`的屬性將會返回`Int?`。
|
||||
|
||||
下面幾段代碼將解釋可選鏈和強制解析的不同。
|
||||
|
||||
首先定義兩個類`Person`和`Residence`。
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||
|
||||
class Residence {
|
||||
var numberOfRooms = 1
|
||||
}
|
||||
```
|
||||
|
||||
`Residence`具有一個`Int`類型的`numberOfRooms`,其值為 1。`Person`具有一個可選`residence`屬性,它的類型是`Residence?`。
|
||||
|
||||
如果你創建一個新的`Person`實例,它的`residence`屬性由於是被定義為可選型的,此屬性將默認初始化為空:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
```
|
||||
|
||||
如果你想使用感歎號(`!`)強制解析獲得這個人`residence`屬性`numberOfRooms`屬性值,將會引發運行時錯誤,因為這時沒有可以供解析的`residence`值。
|
||||
|
||||
```swift
|
||||
let roomCount = john.residence!.numberOfRooms
|
||||
//將導致運行時錯誤
|
||||
```
|
||||
當`john.residence`不是`nil`時,會運行通過,且會將`roomCount` 設置為一個`int`類型的合理值。然而,如上所述,當`residence`為空時,這個代碼將會導致運行時錯誤。
|
||||
|
||||
可選鏈提供了一種另一種獲得`numberOfRooms`的方法。利用可選鏈,使用問號來代替原來`!`的位置:
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
println("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
println("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// 打印 "Unable to retrieve the number of rooms.
|
||||
```
|
||||
|
||||
這告訴 Swift 來鏈接可選`residence?`屬性,如果`residence`存在則取回`numberOfRooms`的值。
|
||||
|
||||
因為這種嘗試獲得`numberOfRooms`的操作有可能失敗,可選鏈會返回`Int?`類型值,或者稱作「可選`Int`」。當`residence`是空的時候(上例),選擇`Int`將會為空,因此會出先無法訪問`numberOfRooms`的情況。
|
||||
|
||||
要注意的是,即使numberOfRooms是非可選`Int`(`Int?`)時這一點也成立。只要是通過可選鏈的請求就意味著最後`numberOfRooms`總是返回一個`Int?`而不是`Int`。
|
||||
|
||||
你可以自己定義一個`Residence`實例給`john.residence`,這樣它就不再為空了:
|
||||
|
||||
```swift
|
||||
john.residence = Residence()
|
||||
```
|
||||
|
||||
`john.residence` 現在有了實際存在的實例而不是nil了。如果你想使用和前面一樣的可選鏈來獲得`numberOfRoooms`,它將返回一個包含默認值 1 的`Int?`:
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
println("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
println("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// 打印 "John's residence has 1 room(s)"。
|
||||
```
|
||||
|
||||
<a name="defining_model_classes_for_optional_chaining"></a>
|
||||
##為可選鏈定義模型類
|
||||
|
||||
你可以使用可選鏈來多層調用屬性,方法,和子腳本。這讓你可以利用它們之間的複雜模型來獲取更底層的屬性,並檢查是否可以成功獲取此類底層屬性。
|
||||
|
||||
後面的代碼定義了四個將在後面使用的模型類,其中包括多層可選鏈。這些類是由上面的`Person`和`Residence`模型通過添加一個`Room`和一個`Address`類拓展來。
|
||||
|
||||
`Person`類定義與之前相同。
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||