diff --git a/Using Swift with Cocoa and ObjectiveC-tw/02Interoperability/01Interacting with Objective-C APIs.md b/Using Swift with Cocoa and ObjectiveC-tw/02Interoperability/01Interacting with Objective-C APIs.md new file mode 100644 index 00000000..5e51887d --- /dev/null +++ b/Using Swift with Cocoa and ObjectiveC-tw/02Interoperability/01Interacting with Objective-C APIs.md @@ -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年來做得最大的一件事。 diff --git a/Using Swift with Cocoa and ObjectiveC-tw/03Mix and Match/DAG_2x.png b/Using Swift with Cocoa and ObjectiveC-tw/03Mix and Match/DAG_2x.png new file mode 100644 index 00000000..3c15390b Binary files /dev/null and b/Using Swift with Cocoa and ObjectiveC-tw/03Mix and Match/DAG_2x.png differ diff --git a/Using Swift with Cocoa and ObjectiveC-tw/03Mix and Match/Swift and Objective-C in the Same Project.md b/Using Swift with Cocoa and ObjectiveC-tw/03Mix and Match/Swift and Objective-C in the Same Project.md new file mode 100644 index 00000000..e69de29b diff --git a/Using Swift with Cocoa and ObjectiveC-tw/03Mix and Match/bridgingheader_2x.png b/Using Swift with Cocoa and ObjectiveC-tw/03Mix and Match/bridgingheader_2x.png new file mode 100644 index 00000000..0e678eec Binary files /dev/null and b/Using Swift with Cocoa and ObjectiveC-tw/03Mix and Match/bridgingheader_2x.png differ diff --git a/source-tw/README.md b/source-tw/README.md new file mode 100644 index 00000000..8f69747a --- /dev/null +++ b/source-tw/README.md @@ -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) diff --git a/source-tw/SUMMARY.md b/source-tw/SUMMARY.md new file mode 100644 index 00000000..2c410c00 --- /dev/null +++ b/source-tw/SUMMARY.md @@ -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) + diff --git a/source-tw/chapter1/01_swift.md b/source-tw/chapter1/01_swift.md new file mode 100644 index 00000000..3b5536ed --- /dev/null +++ b/source-tw/chapter1/01_swift.md @@ -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 充滿信心,你還在等什麼! diff --git a/source-tw/chapter1/02_a_swift_tour.md b/source-tw/chapter1/02_a_swift_tour.md new file mode 100644 index 00000000..cb254464 --- /dev/null +++ b/source-tw/chapter1/02_a_swift_tour.md @@ -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 當中使用代碼預覽功能。代碼預覽功能可以讓你編輯代碼並實時看到運行結果。 +> 打開Playground + + +## 簡單值 + +使用`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() +``` + +如果類型信息可以被推斷出來,你可以用`[]`和`[:]`來創建空數組和空字典——就像你聲明變量或者給函數傳參數的時候一樣。 + +```swift +shoppingList = [] // 去逛街並買點東西 +``` + + +## 控制流 + +使用`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 +``` + +使用`..`創建的範圍不包含上界,如果想包含的話需要使用`...`。 + + +## 函數和閉包 + +使用`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 } +``` + + +## 對像和類 + +使用`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 +``` + + +## 枚舉和結構體 + +使用`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`中提取日昇和日落時間。 + + +## 協議和擴展 + +使用`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`。這表示你不能調用類在它實現的協議之外實現的方法或者屬性。 + + +## 泛型 + +在尖括號裡寫一個名字來創建一個泛型函數或者類型。 + +```swift +func repeat(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 { + case None + case Some(T) +} +var possibleInteger: OptionalValue = .None +possibleInteger = .Some(100) +``` + +在類型名後面使用`where`來指定對類型的需求,比如,限定類型實現某一個協議,限定兩個類型是相同的,或者限定某個類必須有一個特定的父類 + +```swift +func anyCommonElements (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`,只在冒號後面寫協議或者類名。` `和``是等價的。 diff --git a/source-tw/chapter1/GuidedTour.playground.zip b/source-tw/chapter1/GuidedTour.playground.zip new file mode 100644 index 00000000..13e2c05e Binary files /dev/null and b/source-tw/chapter1/GuidedTour.playground.zip differ diff --git a/source-tw/chapter1/chapter1.md b/source-tw/chapter1/chapter1.md new file mode 100644 index 00000000..269d5fbb --- /dev/null +++ b/source-tw/chapter1/chapter1.md @@ -0,0 +1,4 @@ +# 歡迎使用 Swift + +在本章中您將瞭解 Swift 的特性和開發歷史,並對 Swift 有一個初步的瞭解。 + diff --git a/source-tw/chapter2/01_The_Basics.md b/source-tw/chapter2/01_The_Basics.md new file mode 100644 index 00000000..4974c382 --- /dev/null +++ b/source-tw/chapter2/01_The_Basics.md @@ -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`。你可以在開發階段盡早發現並修正錯誤。 + + +## 常量和變量 + +常量和變量把一個名字(比如`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)。 + + +## 註釋 +請將你的代碼中的非執行文本註釋成提示或者筆記以方便你將來閱讀。Swift 的編譯器將會在編譯代碼時自動忽略掉註釋部分。 + +Swift 中的註釋與C 語言的註釋非常相似。單行註釋以雙正斜槓(`//`)作為起始標記: + +```swift +// 這是一個註釋 +``` + +你也可以進行多行註釋,其起始標記為單個正斜槓後跟隨一個星號(`/*`),終止標記為一個星號後跟隨單個正斜槓(`*/`): + +```swift +/* 這是一個, +多行註釋 */ +``` + +與 C 語言多行註釋不同,Swift 的多行註釋可以嵌套在其它的多行註釋之中。你可以先生成一個多行註釋塊,然後在這個註釋塊之中再嵌套成第二個多行註釋。終止註釋時先插入第二個註釋塊的終止標記,然後再插入第一個註釋塊的終止標記: + +```swift +/* 這是第一個多行註釋的開頭 +/* 這是第二個被嵌套的多行註釋 */ +這是第一個多行註釋的結尾 */ +``` + +通過運用嵌套多行註釋,你可以快速方便的註釋掉一大段代碼,即使這段代碼之中已經含有了多行註釋塊。 + + +## 分號 +與其他大部分編程語言不同,Swift 並不強制要求你在每條語句的結尾處使用分號(`;`),當然,你也可以按照你自己的習慣添加分號。有一種情況下必須要用分號,即你打算在同一行內寫多條獨立的語句: + +```swift +let cat = "□□ println(cat) +// 輸出 "□□ +``` + + +## 整數 + +整數就是沒有小數部分的數字,比如`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)。 + + +## 浮點數 + +浮點數是有小數部分的數字,比如`3.14159`,`0.1`和`-273.15`。 + +浮點類型比整數類型表示的範圍更大,可以存儲比`Int`類型更大或者更小的數字。Swift 提供了兩種有符號浮點數類型: + +* `Double`表示64位浮點數。當你需要存儲很大或者很高精度的浮點數時請使用此類型。 +* `Float`表示32位浮點數。精度要求不高的話可以使用此類型。 + +> 注意: +`Double`精確度很高,至少有15位數字,而`Float`最少只有6位數字。選擇哪個類型取決於你的代碼需要處理的值的範圍。 + + +## 類型安全和類型推斷 + +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`類型。 + + +## 數值型字面量 + +整數字面量可以被寫作: + +* 一個十進制數,沒有前綴 +* 一個二進制數,前綴是`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 +``` + + +## 數值型類型轉換 + +通常來講,即使代碼中的整數常量和變量已知非負,也請使用`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`相加,因為數字字面量本身沒有明確的類型。它們的類型只在編譯器需要求值的時候被推測。 + + +## 類型別名 + +_類型別名(type aliases)_就是給現有類型定義另一個名字。你可以使用`typealias`關鍵字來定義類型別名。 + +當你想要給現有類型起一個更有意義的名字時,類型別名非常有用。假設你正在處理特定長度的外部資源的數據: + +```swift +typealias AudioSample = UInt16 +``` + +定義了一個類型別名之後,你可以在任何使用原始名的地方使用別名: + +```swift +var maxAmplitudeFound = AudioSample.min +// maxAmplitudeFound 現在是 0 +``` + +本例中,`AudioSample`被定義為`UInt16`的一個別名。因為它是別名,`AudioSample.min`實際上是`UInt16.min`,所以會給`maxAmplitudeFound`賦一個初值`0`。 + + +## 布爾值 + +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 中的其他類型安全的例子一樣,這個方法可以避免錯誤並保證這塊代碼的意圖總是清晰的。 + + +## 元組 + +_元組(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)。 + + +## 可選類型 + +使用_可選類型(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`的值。 + + +### 可選綁定 + +使用_可選綁定(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`的話,請使用普通可選類型。 + + +## 斷言 + +可選類型可以讓你判斷值是否存在,你可以在代碼中優雅地處理值缺失的情況。然而,在某些情況下,如果值缺失或者值並不滿足特定的條件,你的代碼可能並不需要繼續執行。這時,你可以在你的代碼中觸發一個_斷言(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)。 + +> 注意: +斷言可能導致你的應用終止運行,所以你應當仔細設計你的代碼來讓非法條件不會出現。然而,在你的應用發佈之前,有時候非法條件可能出現,這時使用斷言可以快速發現問題。 + diff --git a/source-tw/chapter2/02_Basic_Operators.md b/source-tw/chapter2/02_Basic_Operators.md new file mode 100644 index 00000000..3732be2c --- /dev/null +++ b/source-tw/chapter2/02_Basic_Operators.md @@ -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`)。一元運算符分前置符和後置運算符,前置運算符需緊排操作對像之前(如`!b`),後置運算符需緊跟操作對像之後(如`i++`)。 +- 二元運算符操作兩個操作對像(如`2 + 3`),是中置的,因為它們出現在兩個操作對像之間。 +- 三元運算符操作三個操作對象,和 C 語言一樣,Swift 只有一個三元運算符,就是三元條件運算符(`a ? b : c`)。 + +受運算符影響的值叫操作數,在表達式`1 + 2`中,加號`+`是二元運算符,它的兩個操作數是值`1`和`2`。 + + +## 賦值運算符 + +賦值運算(`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 從底層幫你避免了這些錯誤代碼。 + + +## 數值運算 + +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`中: + +![Art/remainderInteger_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/remainderInteger_2x.png "Art/remainderInteger_2x.png") + +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`。 + +![Art/remainderFloat_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/remainderFloat_2x.png "Art/remainderFloat_2x.png") + +### 自增和自增運算 + +和 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 +``` +雖然一元`+`做無用功,但當你在使用一元負號來表達負數時,你可以使用一元正號來表達正數,如此你的代碼會具有對稱美。 + + + +## 復合賦值(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)章節裡有復合運算符的完整列表。 +□ + +## 比較運算 + +所有標準 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)。 + + +## 三元條件運算(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`語句中改變。 + +三元條件運算提供有效率且便捷的方式來表達二選一的選擇。需要注意的事,過度使用三元條件運算就會由簡潔的代碼變成難懂的代碼。我們應避免在一個組合語句使用多個三元條件運算符。 + + +## 區間運算符 + +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)。 + + +## 邏輯運算 + +邏輯運算的操作對象是邏輯布爾值。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!" +``` + +這括號使得前兩個值被看成整個邏輯表達中獨立的一個部分。雖然有括號和沒括號的輸出結果是一樣的,但對於讀代碼的人來說有括號的代碼更清晰。可讀性比簡潔性更重要,請在可以讓你代碼變清晰地地方加個括號吧! diff --git a/source-tw/chapter2/03_Strings_and_Characters.md b/source-tw/chapter2/03_Strings_and_Characters.md new file mode 100644 index 00000000..0ed55d68 --- /dev/null +++ b/source-tw/chapter2/03_Strings_and_Characters.md @@ -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)。 + + +## 字符串字面量(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 +``` + + +## 初始化空字符串 (Initializing an Empty String) + +為了構造一個很長的字符串,可以創建一個空字符串作為初始值。 +可以將空的字符串字面量賦值給變量,也可以初始化一個新的`String`實例: + +```swift +var emptyString = "" // 空字符串字面量 +var anotherEmptyString = String() // 初始化 String 實例 +// 兩個字符串均為空並等價。 +``` + +您可以通過檢查其`Boolean`類型的`isEmpty`屬性來判斷該字符串是否為空: + +```swift +if emptyString.isEmpty { + println("什麼都沒有") +} +// 打印輸出:"什麼都沒有" +``` + + +## 字符串可變性 (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 中的字符串是否可以修改僅通過定義的是變量還是常量來決定,實現了多種類型可變性操作的統一。 + + +## 字符串是值類型(Strings Are Value Types) + +Swift 的`String`類型是值類型。 +如果您創建了一個新的字符串,那麼當其進行常量、變量賦值操作或在函數/方法中傳遞時,會進行值拷貝。 +任何情況下,都會對已有字符串值創建新副本,並對該新副本進行傳遞或賦值操作。 +值類型在 [結構體和枚舉是值類型](09_Classes_and_Structures.html#structures_and_enumerations_are_value_types) 中進行了說明。 + +> 注意: +與 Cocoa 中的`NSString`不同,當您在 Cocoa 中創建了一個`NSString`實例,並將其傳遞給一個函數/方法,或者賦值給一個變量,您傳遞或賦值的是該`NSString`實例的一個引用,除非您特別要求進行值拷貝,否則字符串不會生成新的副本來進行賦值操作。 + +Swift 默認字符串拷貝的方式保證了在函數/方法中傳遞的是字符串的值。 +很明顯無論該值來自於哪裡,都是您獨自擁有的。 +您可以放心您傳遞的字符串本身不會被更改。 + +在實際編譯時,Swift 編譯器會優化字符串的使用,使實際的複製只發生在絕對必要的情況下,這意味著您將字符串作為值類型的同時可以獲得極高的性能。 + + +## 使用字符(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 = "¥" +``` + + +## 計算字符數量 (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`。 + + +## 連接字符串和字符 (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!" +``` + +> 注意: +您不能將一個字符串或者字符添加到一個已經存在的字符變量上,因為字符變量只能包含一個字符。 + + +## 字符串插值 (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)`並包含在字符串字面量中。 + +> 注意: +插值字符串中寫在括號中的表達式不能包含非轉義雙引號 (`"`) 和反斜槓 (`\`),並且不能包含回車或換行符。 + + +## 比較字符串 (Comparing Strings) + +Swift 提供了三種方式來比較字符串的值:字符串相等、前綴相等和後綴相等。 + + +### 字符串相等 (String Equality) + +如果兩個字符串以同一順序包含完全相同的字符,則認為兩者字符串相等: + +```swift +let quotation = "我們是一樣一樣滴." +let sameQuotation = "我們是一樣一樣滴." +if quotation == sameQuotation { + println("這兩個字符串被認為是相同的") +} +// 打印輸出:"這兩個字符串被認為是相同的" +``` + + +### 前綴/後綴相等 (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」 +``` + + +### 大寫和小寫字符串(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?" +``` + + +## Unicode + +Unicode 是一個國際標準,用於文本的編碼和表示。 +它使您可以用標準格式表示來自任意語言幾乎所有的字符,並能夠對文本文件或網頁這樣的外部資源中的字符進行讀寫操作。 + +Swift 的字符串和字符類型是完全兼容 Unicode 標準的,它支持如下所述的一系列不同的 Unicode 編碼。 + + +### Unicode 術語(Unicode Terminology) + +Unicode 中每一個字符都可以被解釋為一個或多個 unicode 標量。 +字符的 unicode 標量是一個唯一的21位數字(和名稱),例如`U+0061`表示小寫的拉丁字母A ("a"),`U+1F425`表示小雞表情 ("□墩 + +當 Unicode 字符串被寫進文本文件或其他存儲結構當中,這些 unicode 標量將會按照 Unicode 定義的集中格式之一進行編碼。其包括`UTF-8`(以8位代碼單元進行編碼) 和`UTF-16`(以16位代碼單元進行編碼)。 + + +### 字符串的 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!□皂 +``` + + +### 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 表示。 + + +### 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)。 + + +### 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 +// ! +// □捊``` diff --git a/source-tw/chapter2/04_Collection_Types.md b/source-tw/chapter2/04_Collection_Types.md new file mode 100644 index 00000000..0e9d2948 --- /dev/null +++ b/source-tw/chapter2/04_Collection_Types.md @@ -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)章節。 + + +## 數組 + +數組使用有序列表存儲同一類型的多個值。相同的值可以多次出現在一個數組的不同位置中。 + +Swift 數組特定於它所存儲元素的類型。這與 Objective-C 的 NSArray 和 NSMutableArray 不同,這兩個類可以存儲任意類型的對象,並且不提供所返回對象的任何特別信息。在 Swift 中,數據值在被存儲進入某個數組之前類型必須明確,方法是通過顯式的類型標注或類型推斷,而且不是必須是`class`類型。例如: 如果我們創建了一個`Int`值類型的數組,我們不能往其中插入任何不是`Int`類型的數據。 Swift 中的數組是類型安全的,並且它們中包含的類型必須明確。 + + +### 數組的簡單語法 + +寫 Swift 數組應該遵循像`Array`這樣的形式,其中`SomeType`是這個數組中唯一允許存在的數據類型。 我們也可以使用像`SomeType[]`這樣的簡單語法。 儘管兩種形式在功能上是一樣的,但是推薦較短的那種,而且在本文中都會使用這種形式來使用數組。 + + +### 數組構造語句 + +我們可以使用字面量來進行數組構造,這是一種用一個或者多個數值構造數組的簡單方法。字面量是一系列由逗號分割並由方括號包含的數值。 +`[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`中變量的正確類型。 + + +### 訪問和修改數組 + +我們可以通過數組的方法和屬性來訪問和修改數組,或者下標語法。 +還可以使用數組的只讀屬性`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" 字符串 +``` + + +### 數組的遍歷 + +我們可以使用`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)。 + + +### 創建並且構造一個數組 + +我們可以使用構造語法來創建一個由特定數據類型構成的空數組: + +```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] +``` + + +## 字典 + +字典是一種存儲多個相同類型的值的容器。每個值(value)都關聯唯一的鍵(key),鍵作為字典中的這個值數據的標識符。和數組中的數據項不同,字典中的數據項並沒有具體順序。我們在需要通過標識符(鍵)訪問數據的時候使用字典,這種方法很大程度上和我們在現實世界中使用字典查字義的方法一樣。 + +Swift 的字典使用時需要具體規定可以存儲鍵和值類型。不同於 Objective-C 的`NSDictionary`和`NSMutableDictionary` 類可以使用任何類型的對象來作鍵和值並且不提供任何關於這些對象的本質信息。在 Swift 中,在某個特定字典中可以存儲的鍵和值必須提前定義清楚,方法是通過顯性類型標注或者類型推斷。 + +Swift 的字典使用`Dictionary`定義,其中`KeyType`是字典中鍵的數據類型,`ValueType`是字典中對應於這些鍵所存儲值的數據類型。 + +`KeyType`的唯一限制就是可哈希的,這樣可以保證它是獨一無二的,所有的 Swift 基本類型(例如`String`,`Int`, `Double`和`Bool`)都是默認可哈希的,並且所有這些類型都可以在字典中當做鍵使用。未關聯值的枚舉成員(參見[枚舉](08_Enumerations.html))也是默認可哈希的。 + + +## 字典字面量 + +我們可以使用字典字面量來構造字典,它們和我們剛才介紹過的數組字面量擁有相似語法。一個字典字面量是一個定義擁有一個或者多個鍵值對的字典集合的簡單語句。 + +一個鍵值對是一個`key`和一個`value`的結合體。在字典字面量中,每一個鍵值對的鍵和值都由冒號分割。這些鍵值對構成一個列表,其中這些鍵值對由方括號包含並且由逗號分割: + +```swift +[key 1: value 1, key 2: value 2, key 3: value 3] +``` + +下面的例子創建了一個存儲國際機場名稱的字典。在這個字典中鍵是三個字母的國際航空運輸相關代碼,值是機場名稱: + +```swift +var airports: Dictionary = ["TYO": "Tokyo", "DUB": "Dublin"] +``` + +`airports`字典被定義為一種`Dictionary`,它意味著這個字典的鍵和值都是`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`是`airports`字典的正確類型。 + + +### 讀取和修改字典 + +我們可以通過字典的方法和屬性來讀取和修改字典,或者使用下標語法。和數組一樣,我們可以通過字典的只讀屬性`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." +``` + + +### 字典遍歷 + +我們可以使用`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 的字典類型是無序集合類型。其中字典鍵,值,鍵值對在遍歷的時候會重新排列,而且其中順序是不固定的。 + + +### 創建一個空字典 + +我們可以像數組一樣使用構造語法創建一個空字典: + +```swift +var namesOfIntegers = Dictionary() +// namesOfIntegers 是一個空的 Dictionary +``` + +這個例子創建了一個`Int, String`類型的空字典來儲存英語對整數的命名。它的鍵是`Int`型,值是`String`型。 + +如果上下文已經提供了信息類型,我們可以使用空字典字面量來創建一個空字典,記作`[:]`(中括號中放一個冒號): + +```swift +namesOfIntegers[16] = "sixteen" +// namesOfIntegers 現在包含一個鍵值對 +namesOfIntegers = [:] +// namesOfIntegers 又成為了一個 Int, String類型的空字典 +``` + +> 注意: +> 在後台,Swift 的數組和字典都是由泛型集合來實現的,想瞭解更多泛型和集合信息請參見[泛型](22_Generics.html)。 + + +## 集合的可變性 + +數組和字典都是在單個集合中存儲可變值。如果我們創建一個數組或者字典並且把它分配成一個變量,這個集合將會是可變的。這意味著我們可以在創建之後添加更多或移除已存在的數據項來改變這個集合的大小。與此相反,如果我們把數組或字典分配成常量,那麼它就是不可變的,它的大小不能被改變。 + +對字典來說,不可變性也意味著我們不能替換其中任何現有鍵所對應的值。不可變字典的內容在被首次設定之後不能更改。 +不可變性對數組來說有一點不同,當然我們不能試著改變任何不可變數組的大小,但是我們可以重新設定相對現存索引所對應的值。這使得 Swift 數組在大小被固定的時候依然可以做的很棒。 + +Swift 數組的可變性行為同時影響了數組實例如何被分配和修改,想獲取更多信息,請參見[集合在賦值和複製中的行為](09_Classes_and_Structures.html#assignment_and_copy_behavior_for_collection_types)。 + +> 注意: +> 在我們不需要改變數組大小的時候創建不可變數組是很好的習慣。如此 Swift 編譯器可以優化我們創建的集合。 \ No newline at end of file diff --git a/source-tw/chapter2/05_Control_Flow.md b/source-tw/chapter2/05_Control_Flow.md new file mode 100644 index 00000000..675d1232 --- /dev/null +++ b/source-tw/chapter2/05_Control_Flow.md @@ -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`分句描述更複雜的匹配條件。 + + +## For 循環 + +`for`循環用來按照指定的次數多次執行一系列語句。Swift 提供兩種`for`循環形式: + +* `for-in`用來遍歷一個區間(range),序列(sequence),集合(collection),系列(progression)裡面所有的元素執行一系列語句。 +* for條件遞增(`for-condition-increment`)語句,用來重複執行一系列語句直到達成特定條件達成,一般通過在每次循環完成後增加計數器的值來實現。 + + +### 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 +``` + + +### 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`,並終止循環。 + + +## While 循環 + +`while`循環運行一系列語句直到條件變成`false`。這類循環適合使用在第一次迭代前迭代次數未知的情況下。Swift 提供兩種`while`循環形式: + +* `while`循環,每次在循環開始時計算條件是否符合; +* `do-while`循環,每次在循環結束時計算條件是否符合。 + + +###While + +`while`循環從計算單一條件開始。如果條件為`true`,會重複運行一系列語句,直到條件變為`false`。 + +下面是一般情況下 `while` 循環格式: + +> while `condition` { +> `statements` +> } + +下面的例子來玩一個叫做_蛇和梯子(Snakes and Ladders)_的小遊戲,也叫做_滑道和梯子(Chutes and Ladders)_: + +![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png) + +遊戲的規則如下: + +* 遊戲盤面包括 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` 循環開始時,我們並不知道遊戲的長度或者循環的次數,只有在達成指定條件時循環才會結束。 + + + +###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`版本中的數組越界判斷。 + + +## 條件語句 + +根據特定的條件執行特定的代碼通常是十分有用的,例如:當錯誤發生時,你可能想運行額外的代碼;或者,當輸入的值太大或太小時,向用戶顯示一條消息等。要實現這些功能,你就需要使用*條件語句*。 + +Swift 提供兩種類型的條件語句:`if`語句和`switch`語句。通常,當條件較為簡單且可能的情況很少時,使用`if`語句。而`switch`語句更適用於條件較複雜、可能情況較多且需要用到模式匹配(pattern-matching)的情境。 + + +### 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`分支,也就不會打印任何消息。 + + +### 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`語句的完備性。 + + +#### 不存在隱式的貫穿(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)。 + + +#### 區間匹配(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." +``` + + +#### 元組(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" +``` + +![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphSimple_2x.png) + +在上面的例子中,`switch`語句會判斷某個點是否是原點(0, 0),是否在紅色的x軸上,是否在黃色y軸上,是否在一個以原點為中心的4x4的矩形裡,或者在這個矩形外面。 + +不像 C 語言,Swift 允許多個 case 匹配同一個值。實際上,在這個例子中,點(0, 0)可以匹配所有_四個 case_。但是,如果存在多個匹配,那麼只會執行第一個被匹配到的 case 分支。考慮點(0, 0)會首先匹配`case (0, 0)`,因此剩下的能夠匹配(0, 0)的 case 分支都會被忽視掉。 + + + +#### 值綁定(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" +``` + +![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphMedium_2x.png) + +在上面的例子中,`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 分支。 + + +#### 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" +``` + +![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphComplex_2x.png) + +在上面的例子中,`switch`語句會判斷某個點是否在綠色的對角線`x == y`上,是否在紫色的對角線`x == -y`上,或者不在對角線上。 + +這三個 case 都聲明了常量`x`和`y`的佔位符,用於臨時獲取元組`yetAnotherPoint`的兩個值。這些常量被用作`where`語句的一部分,從而創建一個動態的過濾器(filter)。當且僅當`where`語句的條件為`true`時,匹配到的 case 分支才會被執行。 + +就像是值綁定中的例子,由於最後一個 case 分支匹配了餘下所有可能的值,`switch`語句就已經完備了,因此不需要再書寫默認分支。 + + +## 控制轉移語句(Control Transfer Statements) + +控制轉移語句改變你代碼的執行順序,通過它你可以實現代碼的跳轉。Swift有四種控制轉移語句。 + +- continue +- break +- fallthrough +- return + +我們將會在下面討論`continue`、`break`和`fallthrough`語句。`return`語句將會在[函數](../chapter2/06_Functions.html)章節討論。 + + +### 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`匹配到元音字母和空格字符時不做處理,而不是讓每一個匹配到的字符都被打印。 + + +### Break + +`break`語句會立刻結束整個控制流的執行。當你想要更早的結束一個`switch`代碼塊或者一個循環體時,你都可以使用`break`語句。 + + +#### 循環語句中的 break + +當在一個循環體中使用`break`時,會立刻中斷該循環體的執行,然後跳轉到表示循環體結束的大括號(`}`)後的第一行代碼。不會再有本次循環迭代的代碼被執行,也不會再有下次的循環迭代產生。 + + +#### 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`語句。 + + +### 貫穿(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`語句特性是一樣的。 + + +### 帶標籤的語句(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 個方塊中。 + +遊戲的棋盤和之前一樣: + +![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png) + +值`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`,能夠使遊戲的邏輯更加清晰和易於理解。 diff --git a/source-tw/chapter2/06_Functions.md b/source-tw/chapter2/06_Functions.md new file mode 100644 index 00000000..6e3c5e5b --- /dev/null +++ b/source-tw/chapter2/06_Functions.md @@ -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 中,每個函數都有一種類型,包括函數的參數值類型和返回值類型。你可以把函數類型當做任何其他普通變量類型一樣處理,這樣就可以更簡單地把函數當做別的函數的參數,也可以從其他函數中返回函數。函數的定義可以寫在在其他函數定義中,這樣可以在嵌套函數範圍內實現功能封裝。 + + +## 函數的定義與調用(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!" +``` + + +## 函數參數與返回值(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" +``` + +需要注意的是,元組的成員不需要在函數中返回時命名,因為它們的名字已經在函數返回類型中有了定義。 + + +## 函數參數名稱(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` 的值。輸入輸出參數是函數對函數體外產生影響的另一種方式。 + + +## 函數類型(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! +``` + + +## 嵌套函數(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! +``` diff --git a/source-tw/chapter2/07_Closures.md b/source-tw/chapter2/07_Closures.md new file mode 100644 index 00000000..73b12ee4 --- /dev/null +++ b/source-tw/chapter2/07_Closures.md @@ -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)閉包語法 + + +## 閉包表達式(Closure Expressions) + + +[嵌套函數](../chapter2/06_Functions.html#nested_function) 是一個在較複雜函數中方便進行命名和定義自包含代碼模塊的方式。當然,有時候撰寫小巧的沒有完整定義和命名的類函數結構也是很有用處的,尤其是在您處理一些函數並需要將另外一些函數作為該函數的參數時。 + +閉包表達式是一種利用簡潔語法構建內聯閉包的方式。 +閉包表達式提供了一些語法優化,使得撰寫閉包變得簡單明瞭。 +下面閉包表達式的例子通過使用幾次迭代展示了`sort`函數定義和語法優化的方式。 +每一次迭代都用更簡潔的方式描述了相同的功能。 + + +### 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)。 +在下面的例子中,利用閉合表達式語法可以更好的構造一個內聯排序閉包。 + + +### 閉包表達式語法(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`版本的代碼)。 + + +### 根據上下文推斷類型(Inferring Type From Context) + +因為排序閉包涵數是作為`sort`函數的參數進行傳入的,Swift可以推斷其參數和返回值的類型。 +`sort`期望第二個參數是類型為`(String, String) -> Bool`的函數,因此實際上`String`,`String`和`Bool`類型並不需要作為閉包表達式定義中的一部分。 +因為所有的類型都可以被正確推斷,返回箭頭 (`->`) 和圍繞在參數周圍的括號也可以被省略: + +```swift +reversed = sort(names, { s1, s2 in return s1 > s2 } ) +``` + +實際上任何情況下,通過內聯閉包表達式構造的閉包作為參數傳遞給函數時,都可以推斷出閉包的參數和返回值類型,這意味著您幾乎不需要利用完整格式構造任何內聯閉包。 + + +### 單表達式閉包隱式返回(Implicit Return From Single-Expression Clossures) + +單行表達式閉包可以通過隱藏`return`關鍵字來隱式返回單行表達式的結果,如上版本的例子可以改寫為: + +```swift +reversed = sort(names, { s1, s2 in s1 > s2 } ) +``` + +在這個例子中,`sort`函數的第二個參數函數類型明確了閉包必須返回一個`Bool`類型值。 +因為閉包涵數體只包含了一個單一表達式 (`s1 > s2`),該表達式返回`Bool`類型值,因此這裡沒有歧義,`return`關鍵字可以省略。 + + +### 參數名稱縮寫(Shorthand Argument Names) + +Swift 自動為內聯函數提供了參數名稱縮寫功能,您可以直接通過`$0`,`$1`,`$2`來順序調用閉包的參數。 + +如果您在閉包表達式中使用參數名稱縮寫,您可以在閉包參數列表中省略對其的定義,並且對應參數名稱縮寫的類型會通過函數類型進行推斷。 +`in`關鍵字也同樣可以被省略,因為此時閉包表達式完全由閉包涵數體構成: + +```swift +reversed = sort(names, { $0 > $1 } ) +``` + +在這個例子中,`$0`和`$1`表示閉包中第一個和第二個`String`類型的參數。 + + +### 運算符函數(Operator Functions) + +實際上還有一種更簡短的方式來撰寫上面例子中的閉包表達式。 +Swift 的`String`類型定義了關於大於號 (`>`) 的字符串實現,其作為一個函數接受兩個`String`類型的參數並返回`Bool`類型的值。 +而這正好與`sort`函數的第二個參數需要的函數類型相符合。 +因此,您可以簡單地傳遞一個大於號,Swift可以自動推斷出您想使用大於號的字符串函數實現: + +```swift +reversed = sort(names, >) +``` + +更多關於運算符表達式的內容請查看 [運算符函數](../chapter2/23_Advanced_Operators.html#operator_functions)。 + + +## 尾隨閉包(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`函數的括號內。 + + +## 捕獲值(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)。 + + +## 閉包是引用類型(Closures Are Reference Types) + +上面的例子中,`incrementBySeven`和`incrementByTen`是常量,但是這些常量指向的閉包仍然可以增加其捕獲的變量值。 +這是因為函數和閉包都是引用類型。 + +無論您將函數/閉包賦值給一個常量還是變量,您實際上都是將常量/變量的值設置為對應函數/閉包的引用。 +上面的例子中,`incrementByTen`指向閉包的引用是一個常量,而並非閉包內容本身。 + +這也意味著如果您將閉包賦值給了兩個不同的常量/變量,兩個值都會指向同一個閉包: + +```swift +let alsoIncrementByTen = incrementByTen +alsoIncrementByTen() +// 返回的值為50 +``` diff --git a/source-tw/chapter2/08_Enumerations.md b/source-tw/chapter2/08_Enumerations.md new file mode 100644 index 00000000..4d02d0be --- /dev/null +++ b/source-tw/chapter2/08_Enumerations.md @@ -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)。 + + +## 枚舉語法 + +使用`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`的類型已知時,當設定它的值時,你可以不再寫類型名。使用顯式類型的枚舉值可以讓代碼具有更好的可讀性。 + + +## 匹配枚舉值和`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」 +``` + + +## 相關值(Associated Values) + +上一小節的例子演示了一個枚舉的成員是如何被定義(分類)的。你可以為`Planet.Earth`設置一個常量或則變量,並且在之後查看這個值。不管怎樣,如果有時候能夠把其他類型的相關值和成員值一起存儲起來會很有用。這能讓你存儲成員值之外的自定義信息,並且當你每次在代碼中使用該成員時允許這個信息產生變化。 + +你可以定義 Swift 的枚舉存儲任何類型的相關值,如果需要的話,每個成員的數據類型可以是各不相同的。枚舉的這種特性跟其他語言中的可辨識聯合(discriminated unions),標籤聯合(tagged unions),或者變體(variants)相似。 + +例如,假設一個庫存跟蹤系統需要利用兩種不同類型的條形碼來跟蹤商品。有些商品上標有 UPC-A 格式的一維碼,它使用數字 0 到 9。每一個條形碼都有一個代表「數字系統」的數字,該數字後接 10 個代表「標識符」的數字。最後一個數字是「檢查」位,用來驗證代碼是否被正確掃瞄: + + + +其他商品上標有 QR 碼格式的二維碼,它可以使用任何 ISO8859-1 字符,並且可以編碼一個最多擁有 2,953 字符的字符串: + + + +對於庫存跟蹤系統來說,能夠把 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." +``` + + +## 原始值(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`分支被執行。 + diff --git a/source-tw/chapter2/09_Classes_and_Structures.md b/source-tw/chapter2/09_Classes_and_Structures.md new file mode 100644 index 00000000..a19a2fa8 --- /dev/null +++ b/source-tw/chapter2/09_Classes_and_Structures.md @@ -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 中,類和結構體的關係要比在其他語言中更加的密切,本章中所討論的大部分功能都可以用在類和結構體上。因此,我們會主要使用`實例`而不是`對像`。 + + +###類和結構體對比 + +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)章節會對構造器進行更詳細的討論。 + + +## 結構體和枚舉是值類型 + +值類型被賦予給一個變量,常數或者本身被傳遞給一個函數的時候,實際上操作的是其的拷貝。 + +在之前的章節中,我們已經大量使用了值類型。實際上,在 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)的拷貝。 + + +## 類是引用類型 + +與值類型不同,引用類型在被賦予到一個變量、常量或者被傳遞到一個函數時,操作的是引用,其並不是拷貝。因此,引用的是已存在的實例本身而不是其拷貝。 + +請看下面這個示例,其使用了之前定義的`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 中這些引用與其它的常量或變量的定義方式相同。 + + +## 類和結構體的選擇 + +在你的代碼中,你可以使用類和結構體來定義你的自定義數據類型。 + +然而,結構體實例總是通過值傳遞,類實例總是通過引用傳遞。這意味兩者適用不同的任務。當你在考慮一個工程項目的數據構造和功能的時候,你需要決定每個數據構造是定義成類還是結構體。 + +按照通用的準則,當符合一條或多條以下條件時,請考慮構建結構體: + +* 結構體的主要目的是用來封裝少量相關簡單數據值。 +* 有理由預計一個結構體實例在賦值或傳遞時,封裝的數據將會被拷貝而不是被引用。 +* 任何在結構體中儲存的值類型屬性,也將會被拷貝,而不是被引用。 +* 結構體不需要去繼承另一個已存在類型的屬性或者行為。 + +合適的結構體候選者包括: + +* 幾何形狀的大小,封裝一個`width`屬性和`height`屬性,兩者均為`Double`類型。 +* 一定範圍內的路徑,封裝一個`start`屬性和`length`屬性,兩者均為`Int`類型。 +* 三維坐標系內一點,封裝`x`,`y`和`z`屬性,三者均為`Double`類型。 + +在所有其它案例中,定義一個類,生成一個它的實例,並通過引用來管理和傳遞。實際中,這意味著絕大部分的自定義數據構造都應該是類,而非結構體。 + + +## 集合(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`方法會在任何時候都創建一個新的拷貝,即使引用已經是唯一引用。 + diff --git a/source-tw/chapter2/10_Properties.md b/source-tw/chapter2/10_Properties.md new file mode 100644 index 00000000..e9d83cd5 --- /dev/null +++ b/source-tw/chapter2/10_Properties.md @@ -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) + +**屬性**將值跟特定的類、結構或枚舉關聯。存儲屬性存儲常量或變量作為實例的一部分,計算屬性計算(而不是存儲)一個值。計算屬性可以用於類、結構體和枚舉裡,存儲屬性只能用於類和結構體。 + +存儲屬性和計算屬性通常用於特定類型的實例,但是,屬性也可以直接用於類型本身,這種屬性稱為類型屬性。 + +另外,還可以定義屬性觀察器來監控屬性值的變化,以此來觸發一個自定義的操作。屬性觀察器可以添加到自己寫的存儲屬性上,也可以添加到從父類繼承的屬性上。 + + +## 存儲屬性 + +簡單來說,一個存儲屬性就是存儲在特定類或結構體的實例裡的一個常量或變量,存儲屬性可以是*變量存儲屬性*(用關鍵字`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`在創建實例的時候被賦值,因為它是一個常量存儲屬性,所以之後無法修改它的值。 + + +### 常量和存儲屬性 + +如果創建了一個結構體的實例並賦值給一個常量,則無法修改實例的任何屬性,即使定義了變量存儲屬性: + +```swift +let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4) +// 該區間表示整數0,1,2,3 +rangeOfFourItems.firstValue = 6 +// 儘管 firstValue 是個變量屬性,這裡還是會報錯 +``` + +因為`rangeOfFourItems`聲明成了常量(用`let`關鍵字),即使`firstValue`是一個變量屬性,也無法再修改它了。 + +這種行為是由於結構體(struct)屬於*值類型*。當值類型的實例被聲明為常量的時候,它的所有屬性也就成了常量。 + +屬於*引用類型*的類(class)則不一樣,把一個引用類型的實例賦給一個常量後,仍然可以修改實例的變量屬性。 + + +### 延遲存儲屬性 + +延遲存儲屬性是指當第一次被調用的時候才會計算其初始值的屬性。在屬性聲明前使用`@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」 +``` + + +### 存儲屬性和實例變量 + +如果您有過 Objective-C 經驗,應該知道Objective-C為類實例存儲值和引提供兩種方用。對於屬性來說,也可以使用實例變量作為屬性值的後端存儲。 + +Swift 編程語言中把這些理論統一用屬性來實現。Swift 中的屬性沒有對應的實例變量,屬性的後端存儲也無法直接訪問。這就避免了不同場景下訪問方式的困擾,同時也將屬性的定義簡化成一個語句。 +一個類型中屬性的全部信息——包括命名、類型和內存管理特徵——都在唯一一個地方(類型定義中)定義。 + + +## 計算屬性 + +除存儲屬性外,類、結構體和枚舉可以定義*計算屬性*,計算屬性不直接存儲值,而是提供一個 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`的值,從而實現移動正方形到新的位置。 + +Computed Properties sample + + +### 便捷 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) + } + } +} +``` + + +### 只讀計算屬性 + +只有 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`提供一個只讀計算屬性來讓外部用戶直接獲取體積是很有用的。 + + +## 屬性觀察器 + +*屬性觀察器*監控和響應屬性值的變化,每次屬性被設置值的時候都會調用屬性觀察器,甚至新的值和現在的值相同的時候也不例外。 + +可以為除了延遲存儲屬性之外的其他存儲屬性添加屬性觀察器,也可以通過重載屬性的方式為繼承的屬性(包括存儲屬性和計算屬性)添加屬性觀察器。屬性重載請參考[繼承](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`觀察器裡為屬性賦值,這個值會替換觀察器之前設置的值。 + + +##全局變量和局部變量 + +計算屬性和屬性觀察器所描述的模式也可以用於*全局變量*和*局部變量*,全局變量是在函數、方法、閉包或任何類型之外定義的變量,局部變量是在函數、方法或閉包內部定義的變量。 + +前面章節提到的全局或局部變量都屬於存儲型變量,跟存儲屬性類似,它提供特定類型的存儲空間,並允許讀取和寫入。 + +另外,在全局或局部範圍都可以定義計算型變量和為存儲型變量定義觀察器,計算型變量跟計算屬性一樣,返回一個計算的值而不是存儲值,聲明格式也完全一樣。 + +> 注意: +> 全局的常量或變量都是延遲計算的,跟[延遲存儲屬性](#lazy_stored_properties)相似,不同的地方在於,全局的常量或變量不需要標記`@lazy`特性。 +> 局部範圍的常量或變量不會延遲計算。 + + +##類型屬性 + +實例的屬性屬於一個特定類型實例,每次類型實例化後都擁有自己的一套屬性值,實例之間的屬性相互獨立。 + +也可以為類型本身定義屬性,不管類型有多少個實例,這些屬性都只有唯一一份。這種屬性就是*類型屬性*。 + +類型屬性用於定義特定類型所有實例共享的數據,比如所有實例都能用的一個常量(就像 C 語言中的靜態常量),或者所有實例都能訪問的一個變量(就像 C 語言中的靜態變量)。 + +對於值類型(指結構體和枚舉)可以定義存儲型和計算型類型屬性,對於類(class)則只能定義計算型類型屬性。 + +值類型的存儲型類型屬性可以是變量或常量,計算型類型屬性跟實例的計算屬性一樣定義成變量屬性。 + +> 注意: +> 跟實例的存儲屬性不同,必須給存儲型類型屬性指定默認值,因為類型本身無法在初始化過程中使用構造器給類型屬性賦值。 + + +###類型屬性語法 + +在 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 值 + } +} +``` + +> 注意: +> 例子中的計算型類型屬性是只讀的,但也可以定義可讀可寫的計算型類型屬性,跟實例計算屬性的語法類似。 + + +###獲取和設置類型屬性的值 + +跟實例的屬性一樣,類型屬性的訪問也是通過點運算符來進行,但是,類型屬性是通過類型本身來獲取和設置,而不是通過實例。比如: + +```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。 + +Static Properties VUMeter + +上面所描述的聲道模型使用`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" +``` diff --git a/source-tw/chapter2/11_Methods.md b/source-tw/chapter2/11_Methods.md new file mode 100644 index 00000000..9e35a555 --- /dev/null +++ b/source-tw/chapter2/11_Methods.md @@ -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 中,你不僅能選擇是否要定義一個類/結構體/枚舉,還能靈活的在你創建的類型(類/結構體/枚舉)上定義方法。 + + +## 實例方法(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 +``` + + +### 方法的局部參數名稱和外部參數名稱(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 同樣的語法風格,並且方法將以自然表達式的方式被調用。 + + +### 修改方法的外部參數名稱(Modifying External Parameter Name Behavior for Methods) + +有時為方法的第一個參數提供一個外部參數名稱是非常有用的,儘管這不是默認的行為。你可以自己添加一個顯式的外部名稱或者用一個井號(`#`)作為第一個參數的前綴來把這個局部名稱當作外部名稱使用。 + +相反,如果你不想為方法的第二個及後續的參數提供一個外部名稱,可以通過使用下劃線(`_`)作為該參數的顯式外部名稱,這樣做將覆蓋默認行為。 + + +## `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`的函數參數。 + + +### 在實例方法中修改值類型(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 +``` + + +### 在變異方法中給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`)之前循環切換。 + + +## 類型方法(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還沒被解鎖) +``` diff --git a/source-tw/chapter2/12_Subscripts.md b/source-tw/chapter2/12_Subscripts.md new file mode 100644 index 00000000..16a66470 --- /dev/null +++ b/source-tw/chapter2/12_Subscripts.md @@ -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]`。 + +對於同一個目標可以定義多個下標腳本,通過索引值類型的不同來進行重載,而且索引值的個數可以是多個。 + +> 譯者:這裡附屬腳本重載在本小節中原文並沒有任何演示 + + +## 下標腳本語法 + +下標腳本允許你通過在實例後面的方括號中傳入一個或者多個的索引值來對實例進行訪問和賦值。語法類似於實例方法和計算型屬性的混合。與定義實例方法類似,定義下標腳本使用`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]`進行賦值操作,這也是為什麼附屬腳本只定義為只讀的原因。 + + +## 下標腳本用法 + +根據使用場景不同下標腳本也具有不同的含義。通常下標腳本是用來訪問集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以在你自己特定的類或結構體中自由的實現下標腳本來提供合適的功能。 + +例如,Swift 的字典(Dictionary)實現了通過下標腳本來對其實例中存放的值進行存取操作。在下標腳本中使用和字典索引相同類型的值,並且把一個字典值類型的值賦值給這個下標腳本來為字典設值: + +```swift +var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] +numberOfLegs["bird"] = 2 +``` + +上例定義一個名為`numberOfLegs`的變量並用一個字典字面量初始化出了包含三對鍵值的字典實例。`numberOfLegs`的字典存放值類型推斷為`Dictionary`。字典實例創建完成之後通過下標腳本的方式將整型值`2`賦值到字典實例的索引為`bird`的位置中。 + +更多關於字典(Dictionary)下標腳本的信息請參考[讀取和修改字典](../chapter2/04_Collection_Types.html) + +> 注意: +> Swift 中字典的附屬腳本實現中,在`get`部分返回值是`Int?`,上例中的`numberOfLegs`字典通過附屬腳本返回的是一個`Int?`或者說「可選的int」,不是每個字典的索引都能得到一個整型值,對於沒有設過值的索引的訪問返回的結果就是`nil`;同樣想要從字典實例中刪除某個索引下的值也只需要給這個索引賦值為`nil`即可。 + + +## 下標腳本選項 + +下標腳本允許任意數量的入參索引,並且每個入參類型也沒有限制。下標腳本的返回值也可以是任何類型。下標腳本可以使用變量參數和可變參數,但使用寫入讀出(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的最大長度 +``` diff --git a/source-tw/chapter2/13_Inheritance.md b/source-tw/chapter2/13_Inheritance.md new file mode 100644 index 00000000..2a4fe7ee --- /dev/null +++ b/source-tw/chapter2/13_Inheritance.md @@ -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)。 + + +## 定義一個基類(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`類定義了車輛的共同特性,但這個類本身並沒太大用處。為了使它更為實用,你需要進一步細化它來描述更具體的車輛。 + + +## 子類生成(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`方法。一個類的實例方法會被這個類的所有子類繼承。 + + +## 重寫(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 +``` + + +## 防止重寫 + +你可以通過把方法,屬性或下標腳本標記為*`final`*來防止它們被重寫,只需要在聲明關鍵字前加上`@final`特性即可。(例如:`@final var`, `@final func`, `@final class func`, 以及 `@final subscript`) + +如果你重寫了`final`方法,屬性或下標腳本,在編譯時會報錯。在擴展中,你添加到類裡的方法,屬性或下標腳本也可以在擴展的定義裡標記為 final。 + +你可以通過在關鍵字`class`前添加`@final`特性(`@final class`)來將整個類標記為 final 的,這樣的類是不可被繼承的,否則會報編譯錯誤。 + diff --git a/source-tw/chapter2/14_Initialization.md b/source-tw/chapter2/14_Initialization.md new file mode 100644 index 00000000..7fd17ecc --- /dev/null +++ b/source-tw/chapter2/14_Initialization.md @@ -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)。 + + +## 存儲型屬性的初始賦值 + +類和結構體在實例創建時,必須為所有存儲型屬性設置合適的初始值。存儲型屬性的值不能處於一個未知的狀態。 + +你可以在構造器中為存儲型屬性賦初值,也可以在定義屬性時為其設置默認值。以下章節將詳細介紹這兩種方法。 + +>注意: +當你為存儲型屬性設置默認值或者在構造器中為其賦值時,它們的值是被直接設置的,不會觸發任何屬性觀測器(`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 +} +``` + + +## 定制化構造過程 + +你可以通過輸入參數和可選屬性類型來定制構造過程,也可以在構造過程中修改常量屬性。這些都將在後面章節中提到。 + +### 構造參數 + +你可以在定義構造器時提供構造參數,為其提供定制化構造所需值的類型和名字。構造器參數的功能和語法跟函數和方法參數相同。 + +下面例子中定義了一個包含攝氏度溫度的結構體`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.) +``` + + +## 默認構造器 + +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) +``` + + +## 值類型的構造器代理 + +構造器可以通過調用其它構造器來完成實例的部分構造過程。這一過程稱為構造器代理,它能減少多個構造器間的代碼重複。 + +構造器代理的實現規則和形式在值類型和類類型中有所不同。值類型(結構體和枚舉類型)不支持繼承,所以構造器代理的過程相對簡單,因為它們只能代理給本身提供的其它構造器。類則不同,它可以繼承自其它類(請參考[繼承](../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)。 + + +## 類的繼承和構造過程 + +類裡面的所有存儲型屬性--包括所有繼承自父類的屬性--都必須在構造過程中設置初始值。 + +Swift 提供了兩種類型的類構造器來確保所有類實例中存儲型屬性都能獲得初始值,它們分別是指定構造器和便利構造器。 + +### 指定構造器和便利構造器 + +指定構造器是類中最主要的構造器。一個指定構造器將初始化類中提供的所有屬性,並根據父類鏈往上調用父類的構造器來實現父類的初始化。 + +每一個類都必須擁有至少一個指定構造器。在某些情況下,許多類通過繼承了父類中的指定構造器而滿足了這個條件。具體內容請參考後續章節[自動構造器的繼承](#automatic_initializer_inheritance)。 + +便利構造器是類中比較次要的、輔助型的構造器。你可以定義便利構造器來調用同一個類中的指定構造器,並為其參數提供默認值。你也可以定義便利構造器來創建一個特殊用途或特定輸入的實例。 + +你應當只在必要的時候為類提供便利構造器,比方說某種情況下通過使用便利構造器來快捷調用某個指定構造器,能夠節省更多開發時間並讓類的構造過程更清晰明瞭。 + + +### 構造器鏈 + +為了簡化指定構造器和便利構造器之間的調用關係,Swift 採用以下三條規則來限制構造器之間的代理調用: + +#### 規則 1 +指定構造器必須調用其直接父類的的指定構造器。 + +#### 規則 2 +便利構造器必須調用同一類中定義的其它構造器。 + +#### 規則 3 +便利構造器必須最終以調用一個指定構造器結束。 + +一個更方便記憶的方法是: + +- 指定構造器必須總是向上代理 +- 便利構造器必須總是橫向代理 + +這些規則可以通過下面圖例來說明: + +![構造器代理圖](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializerDelegation01_2x.png) + +如圖所示,父類中包含一個指定構造器和兩個便利構造器。其中一個便利構造器調用了另外一個便利構造器,而後者又調用了唯一的指定構造器。這滿足了上面提到的規則2和3。這個父類沒有自己的父類,所以規則1沒有用到。 + +子類中包含兩個指定構造器和一個便利構造器。便利構造器必須調用兩個指定構造器中的任意一個,因為它只能調用同一個類裡的其他構造器。這滿足了上面提到的規則2和3。而兩個指定構造器必須調用父類中唯一的指定構造器,這滿足了規則1。 + +> 注意: +這些規則不會影響使用時,如何用類去創建實例。任何上圖中展示的構造器都可以用來完整創建對應類的實例。這些規則只在實現類的定義時有影響。 + +下面圖例中展示了一種針對四個類的更複雜的類層級結構。它演示了指定構造器是如何在類層級中充當「管道」的作用,在類的構造器鏈上簡化了類之間的相互關係。 + +![複雜構造器代理圖](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializerDelegation02_2x.png) + + +### 兩段式構造過程 + +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](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/twoPhaseInitialization01_2x.png) + +在這個例子中,構造過程從對子類中一個便利構造器的調用開始。這個便利構造器此時沒法修改任何屬性,它把構造任務代理給同一類中的指定構造器。 + +如安全檢查1所示,指定構造器將確保所有子類的屬性都有值。然後它將調用父類的指定構造器,並沿著造器鏈一直往上完成父類的構建過程。 + +父類中的指定構造器確保所有父類的屬性都有值。由於沒有更多的父類需要構建,也就無需繼續向上做構建代理。 + +一旦父類中所有屬性都有了初始值,實例的內存被認為是完全初始化,而階段1也已完成。 + +以下展示了相同構造過程的階段2: + +![構建過程階段2](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/twoPhaseInitialization02_2x.png) + +父類中的指定構造器現在有機會進一步來定制實例(儘管它沒有這種必要)。 + +一旦父類中的指定構造器完成調用,子類的構指定構造器可以執行更多的定制操作(同樣,它也沒有這種必要)。 + +最終,一旦子類的指定構造器完成調用,最開始被調用的便利構造器可以執行更多的定制操作。 + +### 構造器的繼承和重載 + +跟 Objective-C 中的子類不同,Swift 中的子類不會默認繼承父類的構造器。Swift 的這種機制可以防止一個父類的簡單構造器被一個更專業的子類繼承,並被錯誤的用來創建子類的實例。 + +假如你希望自定義的子類中能實現一個或多個跟父類相同的構造器--也許是為了完成一些定制的構造過程--你可以在你定制的子類中提供和重載與父類相同的構造器。 + +如果你重載的構造器是一個指定構造器,你可以在子類裡重載它的實現,並在自定義版本的構造器中調用父類版本的構造器。 + +如果你重載的構造器是一個便利構造器,你的重載過程必須通過調用同一類中提供的其它指定構造器來實現。這一規則的詳細內容請參考[構造器鏈](#initialization_chain)。 + +>注意: +與方法、屬性和下標不同,在重載構造器時你沒有必要使用關鍵字`override`。 + + +### 自動構造器的繼承 + +如上所述,子類不會默認繼承父類的構造器。但是如果特定條件可以滿足,父類構造器是可以被自動繼承的。在實踐中,這意味著對於許多常見場景你不必重載父類的構造器,並且在盡可能安全的情況下以最小的代價來繼承父類的構造器。 + +假設要為子類中引入的任意新屬性提供默認值,請遵守以下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構造器鏈](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializersExample01_2x.png) + +類沒有提供一個默認的逐一成員構造器,所以`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構造器](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializersExample02_2x.png) + +`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`將自動繼承所有父類中的指定構造器和便利構造器。 + +下圖種展示了所有三個類的構造器鏈: + +![三類構造器圖](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializersExample03_2x.png) + +你可以使用全部三個繼承來的構造器來創建`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`,並標記為已購買。接下來通過遍歷數組每個元素並打印它們的描述值,展示了所有項當前的默認狀態都已按照預期完成了賦值。 + + +## 通過閉包和函數來設置屬性的默認值 + +如果某個存儲型屬性的默認值需要特別的定制或準備,你就可以使用閉包或全局函數來為其屬性提供定制的默認值。每當某個屬性所屬的新類型實例創建時,對應的閉包或函數會被調用,而它們的返回值會當做默認值賦值給這個屬性。 + +這種類型的閉包或函數一般會創建一個跟屬性類型相同的臨時變量,然後修改它的值以滿足預期的初始狀態,最後將這個臨時變量的值作為屬性的默認值進行返回。 + +下面列舉了閉包如何提供默認值的代碼概要: + +```swift +class SomeClass { + let someProperty: SomeType = { + // 在這個閉包中給 someProperty 創建一個默認值 + // someValue 必須和 SomeType 類型相同 + return someValue + }() +} +``` + +注意閉包結尾的大括號後面接了一對空的小括號。這是用來告訴 Swift 需要立刻執行此閉包。如果你忽略了這對括號,相當於是將閉包本身作為值賦值給了屬性,而不是將閉包的返回值賦值給屬性。 + +>注意: +如果你使用閉包來初始化屬性的值,請記住在閉包執行時,實例的其它部分都還沒有初始化。這意味著你不能夠在閉包裡訪問其它的屬性,就算這個屬性有默認值也不允許。同樣,你也不能使用隱式的`self`屬性,或者調用其它的實例方法。 + +下面例子中定義了一個結構體`Checkerboard`,它構建了西洋跳棋遊戲的棋盤: + +![西洋跳棋棋盤](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/checkersBoard_2x.png) + +西洋跳棋遊戲在一副黑白格交替的 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" +``` diff --git a/source-tw/chapter2/15_Deinitialization.md b/source-tw/chapter2/15_Deinitialization.md new file mode 100644 index 00000000..bc2e0d3b --- /dev/null +++ b/source-tw/chapter2/15_Deinitialization.md @@ -0,0 +1,108 @@ +> 翻譯:[bruce0505](https://github.com/bruce0505) +> 校對:[fd5788](https://github.com/fd5788) + +# 析構過程(Deinitialization) +--------------------------- + +本頁包含內容: + +- [析構過程原理](#how_deinitialization_works) +- [析構函數操作](#deinitializers_in_action) + +在一個類的實例被釋放之前,析構函數被立即調用。用關鍵字`deinit`來標示析構函數,類似於初始化函數用`init`來標示。析構函數只適用於類類型。 + + +##析構過程原理 + +Swift 會自動釋放不再需要的實例以釋放資源。如[自動引用計數](16_Automatic_Reference_Counting.html)那一章描述,Swift 通過_自動引用計數_(ARC)處理實例的內存管理。通常當你的實例被釋放時不需要手動地去清理。但是,當使用自己的資源時,你可能需要進行一些額外的清理。例如,如果創建了一個自定義的類來打開一個文件,並寫入一些數據,你可能需要在類實例被釋放之前關閉該文件。 + +在類的定義中,每個類最多只能有一個析構函數。析構函數不帶任何參數,在寫法上不帶括號: + +```swift +deinit { + // 執行析構過程 +} +``` + +析構函數是在實例釋放發生前一步被自動調用。不允許主動調用自己的析構函數。子類繼承了父類的析構函數,並且在子類析構函數實現的最後,父類的析構函數被自動調用。即使子類沒有提供自己的析構函數,父類的析構函數也總是被調用。 + +因為直到實例的析構函數被調用時,實例才會被釋放,所以析構函數可以訪問所有請求實例的屬性,並且根據那些屬性可以修改它的行為(比如查找一個需要被關閉的文件的名稱)。 + + +##析構函數操作 + +這裡是一個析構函數操作的例子。這個例子是一個簡單的遊戲,定義了兩種新類型,`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`實例,因此為了清空它佔用的內存從而釋放它。在這發生前一步,其析構函數被自動調用,其硬幣被返回到銀行。 diff --git a/source-tw/chapter2/16_Automatic_Reference_Counting.md b/source-tw/chapter2/16_Automatic_Reference_Counting.md new file mode 100644 index 00000000..15fab276 --- /dev/null +++ b/source-tw/chapter2/16_Automatic_Reference_Counting.md @@ -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 來管理你的應用程序的內存。 + +> 注意: +引用計數僅僅應用於類的實例。結構體和枚舉類型是值類型,不是引用類型,也不是通過引用的方式存儲和傳遞。 + + +## 自動引用計數的工作機制 + +當你每次創建一個類的新的實例的時候,ARC 會分配一大塊內存用來儲存實例的信息。內存中會包含實例的類型信息,以及這個實例所有相關屬性的值。此外,當實例不再被使用時,ARC 釋放實例所佔用的內存,並讓釋放的內存能挪作他用。這確保了不再被使用的實例,不會一直佔用內存空間。 + +然而,當 ARC 收回和釋放了正在被使用中的實例,該實例的屬性和方法將不能再被訪問和調用。實際上,如果你試圖訪問這個實例,你的應用程序很可能會崩潰。 + +為了確保使用中的實例不會被銷毀,ARC 會跟蹤和計算每一個實例正在被多少屬性,常量和變量所引用。哪怕實例的引用數為一,ARC都不會銷毀這個實例。 + +為了使之成為可能,無論你將實例賦值給屬性,常量或者是變量,屬性,常量或者變量,都會對此實例創建強引用。之所以稱之為強引用,是因為它會將實例牢牢的保持住,只要強引用還在,實例是不允許被銷毀的。 + + +## 自動引用計數實踐 + +下面的例子展示了自動引用計數的工作機制。例子以一個簡單的`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" +``` + + +## 類實例之間的循環強引用 + +在上面的例子中,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`實例的強引用: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle01_2x.png) + +現在你能夠將這兩個實例關聯在一起,這樣人就能有公寓住了,而公寓也有了房客。注意感歎號是用來展開和訪問可選變量`john`和`number73`中的實例,這樣實例的屬性才能被賦值: + +```swift +john!.apartment = number73 +number73!.tenant = john +``` + +在將兩個實例聯繫在一起之後,強引用的關係如圖所示: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle02_2x.png) + +不幸的是,將這兩個實例關聯在一起之後,一個循環強引用被創建了。`Person`實例現在有了一個指向`Apartment`實例的強引用,而`Apartment`實例也有了一個指向`Person`實例的強引用。因此,當你斷開`john`和`number73`變量所持有的強引用時,引用計數並不會降為 0,實例也不會被 ARC 銷毀: + +```swift +john = nil +number73 = nil +``` + +注意,當你把這兩個變量設為`nil`時,沒有任何一個析構函數被調用。強引用循環阻止了`Person`和`Apartment`類實例的銷毀,並在你的應用程序中造成了內存洩漏。 + +在你將`john`和`number73`賦值為`nil`後,強引用關係如下圖: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle03_2x.png) + +`Person`和`Apartment`實例之間的強引用關係保留了下來並且不會被斷開。 + + +## 解決實例之間的循環強引用 + +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 +``` + +現在,兩個關聯在一起的實例的引用關係如下圖所示: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference01_2x.png) + +`Person`實例依然保持對`Apartment`實例的強引用,但是`Apartment`實例只是對`Person`實例的弱引用。這意味著當你斷開`john`變量所保持的強引用時,再也沒有指向`Person`實例的強引用了: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference02_2x.png) + +由於再也沒有指向`Person`實例的強引用,該實例會被銷毀: + +```swift +john = nil +// prints "John Appleseed is being deinitialized" +``` + +唯一剩下的指向`Apartment`實例的強引用來自於變量`number73`。如果你斷開這個強引用,再也沒有指向`Apartment`實例的強引用了: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference03_2x.png) + +由於再也沒有指向`Apartment`實例的強引用,該實例也會被銷毀: + +```swift +number73 = nil +// prints "Apartment #73 is being deinitialized" +``` + +上面的兩段代碼展示了變量`john`和`number73`在被賦值為`nil`後,`Person`實例和`Apartment`實例的析構函數都打印出「銷毀」的信息。這證明了引用循環被打破了。 + + +### 無主引用 + +和弱引用類似,無主引用不會牢牢保持住引用的實例。和弱引用不同的是,無主引用是永遠有值的。因此,無主引用總是被定義為非可選類型(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!) +``` + +在你關聯兩個實例後,它們的引用關係如下圖所示: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/unownedReference01_2x.png) + +`Customer`實例持有對`CreditCard`實例的強引用,而`CreditCard`實例持有對`Customer`實例的無主引用。 + +由於`customer`的無主引用,當你斷開`john`變量持有的強引用時,再也沒有指向`Customer`實例的強引用了: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/unownedReference02_2x.png) + +由於再也沒有指向`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`屬性在初始化完成後,能像非可選值一樣使用和存取同時還避免了循環強引用。 + + +##閉包引起的循環強引用 + +前面我們看到了循環強引用環是在兩個類實例屬性互相保持對方的強引用時產生的,還知道了如何用弱引用和無主引用來打破循環強引用。 + +循環強引用還會發生在當你將一個閉包賦值給類實例的某個屬性,並且這個閉包體中又使用了實例。這個閉包體中可能訪問了實例的某個屬性,例如`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)" + } 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`,閉包會返回"`

some text

`"或者"`

`"。 + +可以像實例方法那樣去命名、使用`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`默認值的閉包之間的循環強引用。循環強引用如下圖所示: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle01_2x.png) + +實例的`asHTML`屬性持有閉包的強引用。但是,閉包在其閉包體內使用了`self`(引用了`self.name`和`self.text`),因此閉包捕獲了`self`,這意味著閉包又反過來持有了`HTMLElement`實例的強引用。這樣兩個對象就產生了循環強引用。(更多關於閉包捕獲值的信息,請參考[值捕獲](07_Closures.html))。 + +>注意: +雖然閉包多次使用了`self`,它只捕獲`HTMLElement`實例的一個強引用。 + +如果設置`paragraph`變量為`nil`,打破它持有的`HTMLElement`實例的強引用,`HTMLElement`實例和它的閉包都不會被銷毀,也是因為循環強引用: + +```swift +paragraph = nil +``` + +注意`HTMLElementdeinitializer`中的消息並沒有別打印,證明了`HTMLElement`實例並沒有被銷毀。 + + +##解決閉包引起的循環強引用 + +在定義閉包時同時定義捕獲列表作為閉包的一部分,通過這種方式可以解決閉包和類實例之間的循環強引用。捕獲列表定義了閉包體內捕獲一個或者多個引用類型的規則。跟解決兩個類實例間的循環強引用一樣,聲明每個捕獲的引用為弱引用或無主引用,而不是強引用。應當根據代碼關係來決定使用弱引用還是無主引用。 + +>注意: +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)" + } 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 "

hello, world

" +``` + +使用捕獲列表後引用關係如下圖所示: + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle02_2x.png) + +這一次,閉包以無主引用的形式捕獲`self`,並不會持有`HTMLElement`實例的強引用。如果將`paragraph`賦值為`nil`,`HTMLElement`實例將會被銷毀,並能看到它的析構函數打印出的消息。 + +```swift +paragraph = nil +// prints "p is being deinitialized" +``` + diff --git a/source-tw/chapter2/17_Optional_Chaining.md b/source-tw/chapter2/17_Optional_Chaining.md new file mode 100644 index 00000000..10a6755e --- /dev/null +++ b/source-tw/chapter2/17_Optional_Chaining.md @@ -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 可以使用在任意類型中,並且失敗與否可以被檢測到。 + + +## 可選鏈可替代強制解析 + +通過在想調用的屬性、方法、或子腳本的可選值(`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)"。 +``` + + +##為可選鏈定義模型類 + +你可以使用可選鏈來多層調用屬性,方法,和子腳本。這讓你可以利用它們之間的複雜模型來獲取更底層的屬性,並檢查是否可以成功獲取此類底層屬性。 + +後面的代碼定義了四個將在後面使用的模型類,其中包括多層可選鏈。這些類是由上面的`Person`和`Residence`模型通過添加一個`Room`和一個`Address`類拓展來。 + +`Person`類定義與之前相同。 + +```swift +class Person { + var residence: Residence? +} +``` + +`Residence`類比之前複雜些。這次,它定義了一個變量 `rooms`,它被初始化為一個`Room[]`類型的空數組: + +```swift +class Residence { + var rooms = Room[]() + var numberOfRooms: Int { + return rooms.count + } + subscript(i: Int) -> Room { + return rooms[i] + } + func printNumberOfRooms() { + println("The number of rooms is \(numberOfRooms)") + } + var address: Address? +} +``` + +因為`Residence`存儲了一個`Room`實例的數組,它的`numberOfRooms`屬性值不是一個固定的存儲值,而是通過計算而來的。`numberOfRooms`屬性值是由返回`rooms`數組的`count`屬性值得到的。 + +為了能快速訪問`rooms`數組,`Residence`定義了一個只讀的子腳本,通過插入數組的元素角標就可以成功調用。如果該角標存在,子腳本則將該元素返回。 + +`Residence`中也提供了一個`printNumberOfRooms`的方法,即簡單的打印房間個數。 + +最後,`Residence`定義了一個可選屬性叫`address`(`address?`)。`Address`類的屬性將在後面定義。 +用於`rooms`數組的`Room`類是一個很簡單的類,它只有一個`name`屬性和一個設定`room`名的初始化器。 + +```swift +class Room { + let name: String + init(name: String) { self.name = name } +} +``` + + +這個模型中的最終類叫做`Address`。它有三個類型是`String?`的可選屬性。前面兩個可選屬性`buildingName`和 `buildingNumber`作為地址的一部分,是定義某個建築物的兩種方式。第三個屬性`street`,用於命名地址的街道名: + +```swift +class Address { + var buildingName: String? + var buildingNumber: String? + var street: String? + func buildingIdentifier() -> String? { + if buildingName { + return buildingName + } else if buildingNumber { + return buildingNumber + } else { + return nil + } + } +} +``` + +`Address`類還提供了一個`buildingIdentifier`的方法,它的返回值類型為`String?`。這個方法檢查`buildingName`和`buildingNumber`的屬性,如果`buildingName`有值則將其返回,或者如果`buildingNumber`有值則將其返回,再或如果沒有一個屬性有值,返回空。 + + +##通過可選鏈調用屬性 + +正如上面「 [可選鏈可替代強制解析](#optional_chaining_as_an_alternative_to_forced_unwrapping)」中所述,你可以利用可選鏈的可選值獲取屬性,並且檢查屬性是否獲取成功。然而,你不能使用可選鏈為屬性賦值。 + +使用上述定義的類來創建一個人實例,並再次嘗試後去它的`numberOfRooms`屬性: + +```swift +let john = Person() +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。 +``` + +由於`john.residence`是空,所以這個可選鏈和之前一樣失敗了,但是沒有運行時錯誤。 + + +##通過可選鏈調用方法 + +你可以使用可選鏈的來調用可選值的方法並檢查方法調用是否成功。即使這個方法沒有返回值,你依然可以使用可選鏈來達成這一目的。 + +`Residence`的`printNumberOfRooms`方法會打印`numberOfRooms`的當前值。方法如下: + +```swift +func printNumberOfRooms(){ + println(「The number of rooms is \(numberOfRooms)」) +} +``` + +這個方法沒有返回值。但是,沒有返回值類型的函數和方法有一個隱式的返回值類型`Void`(參見Function Without Return Values)。 + +如果你利用可選鏈調用此方法,這個方法的返回值類型將是`Void?`,而不是`Void`,因為當通過可選鏈調用方法時返回值總是可選類型(optional type)。即使這個方法本身沒有定義返回值,你也可以使用`if`語句來檢查是否能成功調用`printNumberOfRooms`方法:如果方法通過可選鏈調用成功,`printNumberOfRooms`的隱式返回值將會是`Void`,如果沒有成功,將返回`nil`: + +```swift +if john.residence?.printNumberOfRooms() { + println("It was possible to print the number of rooms.") +} else { + println("It was not possible to print the number of rooms.") +} +// 打印 "It was not possible to print the number of rooms."。 +``` + + +##使用可選鏈調用子腳本 + +你可以使用可選鏈來嘗試從子腳本獲取值並檢查子腳本的調用是否成功,然而,你不能通過可選鏈來設置子代碼。 + +> 注意: +當你使用可選鏈來獲取子腳本的時候,你應該將問號放在子腳本括號的前面而不是後面。可選鏈的問號一般直接跟在表達語句的後面。 + +下面這個例子用在`Residence`類中定義的子腳本來獲取`john.residence`數組中第一個房間的名字。因為`john.residence`現在是`nil`,子腳本的調用失敗了。 + +```swift +if let firstRoomName = john.residence?[0].name { + println("The first room name is \(firstRoomName).") +} else { + println("Unable to retrieve the first room name.") +} +// 打印 "Unable to retrieve the first room name."。 +``` + +在子代碼調用中可選鏈的問號直接跟在`john.residence`的後面,在子腳本括號的前面,因為`john.residence`是可選鏈試圖獲得的可選值。 + +如果你創建一個`Residence`實例給`john.residence`,且在他的`rooms`數組中有一個或多個`Room`實例,那麼你可以使用可選鏈通過`Residence`子腳本來獲取在`rooms`數組中的實例了: + +```swift +let johnsHouse = Residence() +johnsHouse.rooms += Room(name: "Living Room") +johnsHouse.rooms += Room(name: "Kitchen") +john.residence = johnsHouse + +if let firstRoomName = john.residence?[0].name { + println("The first room name is \(firstRoomName).") +} else { + println("Unable to retrieve the first room name.") +} +// 打印 "The first room name is Living Room."。 +``` + + +##連接多層鏈接 + +你可以將多層可選鏈連接在一起,可以掘取模型內更下層的屬性方法和子腳本。然而多層可選鏈不能再添加比已經返回的可選值更多的層。 +也就是說: + +如果你試圖獲得的類型不是可選類型,由於使用了可選鏈它將變成可選類型。 +如果你試圖獲得的類型已經是可選類型,由於可選鏈它也不會提高可選性。 + +因此: + +如果你試圖通過可選鏈獲得`Int`值,不論使用了多少層鏈接返回的總是`Int?`。 +相似的,如果你試圖通過可選鏈獲得`Int?`值,不論使用了多少層鏈接返回的總是`Int?`。 + +下面的例子試圖獲取`john`的`residence`屬性裡的`address`的`street`屬性。這裡使用了兩層可選鏈來聯繫`residence`和`address`屬性,它們兩者都是可選類型: + +```swift +if let johnsStreet = john.residence?.address?.street { + println("John's street name is \(johnsStreet).") +} else { + println("Unable to retrieve the address.") +} +// 打印 "Unable to retrieve the address.」。 +``` + +`john.residence`的值現在包含一個`Residence`實例,然而`john.residence.address`現在是`nil`,因此`john.residence?.address?.street`調用失敗。 + +從上面的例子發現,你試圖獲得`street`屬性值。這個屬性的類型是`String?`。因此儘管在可選類型屬性前使用了兩層可選鏈,`john.residence?.address?.street`的返回值類型也是`String?`。 + +如果你為`Address`設定一個實例來作為`john.residence.address`的值,並為`address`的`street`屬性設定一個實際值,你可以通過多層可選鏈來得到這個屬性值。 + +```swift +let johnsAddress = Address() +johnsAddress.buildingName = "The Larches" +johnsAddress.street = "Laurel Street" +john.residence!.address = johnsAddress +``` + +```swift +if let johnsStreet = john.residence?.address?.street { + println("John's street name is \(johnsStreet).") +} else { + println("Unable to retrieve the address.") +} +// 打印 "John's street name is Laurel Street."。 +``` + +值得注意的是,「`!`」符號在給`john.residence.address`分配`address`實例時的使用。`john.residence`屬性是一個可選類型,因此你需要在它獲取`address`屬性之前使用`!`解析以獲得它的實際值。 + + +##鏈接可選返回值的方法 + +前面的例子解釋了如何通過可選鏈來獲得可選類型屬性值。你也可以通過可選鏈調用一個返回可選類型值的方法並按需鏈接該方法的返回值。 + +下面的例子通過可選鏈調用了`Address`類中的`buildingIdentifier` 方法。這個方法的返回值類型是`String?`。如上所述,這個方法在可選鏈調用後最終的返回值類型依然是`String?`: + +```swift +if let buildingIdentifier = john.residence?.address?.buildingIdentifier() { + println("John's building identifier is \(buildingIdentifier).") +} +// 打印 "John's building identifier is The Larches."。 +``` + +如果你還想進一步對方法返回值執行可選鏈,將可選鏈問號符放在方法括號的後面: + +```swift +if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString { + println("John's uppercase building identifier is \(upper).") +} +// 打印 "John's uppercase building identifier is THE LARCHES."。 +``` + +> 注意: +在上面的例子中,你將可選鏈問號符放在括號後面是因為你想要鏈接的可選值是`buildingIdentifier`方法的返回值,不是`buildingIdentifier`方法本身。 diff --git a/source-tw/chapter2/18_Type_Casting.md b/source-tw/chapter2/18_Type_Casting.md new file mode 100644 index 00000000..11b353d4 --- /dev/null +++ b/source-tw/chapter2/18_Type_Casting.md @@ -0,0 +1,252 @@ +> 翻譯:[xiehurricane](https://github.com/xiehurricane) +> 校對:[happyming](https://github.com/happyming) + +# 類型轉換(Type Casting) +----------------- + +本頁包含內容: + +- [定義一個類層次作為例子](#defining_a_class_hierarchy_for_type_casting) +- [檢查類型](#checking_type) +- [向下轉型(Downcasting)](#downcasting) +- [`Any`和`AnyObject`的類型轉換](#type_casting_for_any_and_anyobject) + + +_類型轉換_是一種檢查類實例的方式,並且或者也是讓實例作為它的父類或者子類的一種方式。 + +類型轉換在 Swift 中使用`is` 和 `as`操作符實現。這兩個操作符提供了一種簡單達意的方式去檢查值的類型或者轉換它的類型。 + +你也可以用來檢查一個類是否實現了某個協議,就像在 [Checking for Protocol Conformance](Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_363)部分講述的一樣。 + + +## 定義一個類層次作為例子 + +你可以將它用在類和子類的層次結構上,檢查特定類實例的類型並且轉換這個類實例的類型成為這個層次結構中的其他類型。這下面的三個代碼段定義了一個類層次和一個包含了幾個這些類實例的數組,作為類型轉換的例子。 + +第一個代碼片段定義了一個新的基礎類`MediaItem`。這個類為任何出現在數字媒體庫的媒體項提供基礎功能。特別的,它聲明了一個 `String` 類型的 `name` 屬性,和一個`init name`初始化器。(它假定所有的媒體項都有個名稱。) + +```swift +class MediaItem { + var name: String + init(name: String) { + self.name = name + } +} +``` + +下一個代碼段定義了 `MediaItem` 的兩個子類。第一個子類`Movie`,在父類(或者說基類)的基礎上增加了一個 `director`(導演) 屬性,和相應的初始化器。第二個類在父類的基礎上增加了一個 `artist`(藝術家) 屬性,和相應的初始化器: + +```swift +class Movie: MediaItem { + var director: String + init(name: String, director: String) { + self.director = director + super.init(name: name) + } +} + +class Song: MediaItem { + var artist: String + init(name: String, artist: String) { + self.artist = artist + super.init(name: name) + } +} +``` + +最後一個代碼段創建了一個數組常量 `library`,包含兩個`Movie`實例和三個`Song`實例。`library`的類型是在它被初始化時根據它數組中所包含的內容推斷來的。Swift 的類型檢測器能夠演繹出`Movie` 和 `Song` 有共同的父類 `MediaItem` ,所以它推斷出 `MediaItem[]` 類作為 `library` 的類型。 + +```swift +let library = [ + Movie(name: "Casablanca", director: "Michael Curtiz"), + Song(name: "Blue Suede Shoes", artist: "Elvis Presley"), + Movie(name: "Citizen Kane", director: "Orson Welles"), + Song(name: "The One And Only", artist: "Chesney Hawkes"), + Song(name: "Never Gonna Give You Up", artist: "Rick Astley") +] +// the type of "library" is inferred to be MediaItem[] +``` + +在幕後`library` 裡存儲的媒體項依然是 `Movie` 和 `Song` 類型的,但是,若你迭代它,取出的實例會是 `MediaItem` 類型的,而不是 `Movie` 和 `Song` 類型的。為了讓它們作為它們本來的類型工作,你需要檢查它們的類型或者向下轉換它們的類型到其它類型,就像下面描述的一樣。 + + +## 檢查類型(Checking Type) + +用類型檢查操作符(`is`)來檢查一個實例是否屬於特定子類型。若實例屬於那個子類型,類型檢查操作符返回 `true` ,否則返回 `false` 。 + +下面的例子定義了兩個變量,`movieCount` 和 `songCount`,用來計算數組`library` 中 `Movie` 和 `Song` 類型的實例數量。 + +```swift +var movieCount = 0 +var songCount = 0 + +for item in library { + if item is Movie { + ++movieCount + } else if item is Song { + ++songCount + } +} + +println("Media library contains \(movieCount) movies and \(songCount) songs") +// prints "Media library contains 2 movies and 3 songs" +``` + +示例迭代了數組 `library` 中的所有項。每一次, `for`-`in` 循環設置 +`item` 為數組中的下一個 `MediaItem`。 + +若當前 `MediaItem` 是一個 `Movie` 類型的實例, `item is Movie` 返回 +`true`,相反返回 `false`。同樣的,`item is +Song`檢查item是否為`Song`類型的實例。在循環結束後,`movieCount` 和 `songCount`的值就是被找到屬於各自的類型的實例數量。 + + +## 向下轉型(Downcasting) + +某類型的一個常量或變量可能在幕後實際上屬於一個子類。你可以相信,上面就是這種情況。你可以嘗試向下轉到它的子類型,用類型轉換操作符(`as`) + +因為向下轉型可能會失敗,類型轉型操作符帶有兩種不同形式。可選形式( optional form) `as?` 返回一個你試圖下轉成的類型的可選值(optional value)。強制形式 `as` 把試圖向下轉型和強制解包(force-unwraps)結果作為一個混合動作。 + +當你不確定向下轉型可以成功時,用類型轉換的可選形式(`as?`)。可選形式的類型轉換總是返回一個可選值(optional value),並且若下轉是不可能的,可選值將是 `nil` 。這使你能夠檢查向下轉型是否成功。 + +只有你可以確定向下轉型一定會成功時,才使用強制形式。當你試圖向下轉型為一個不正確的類型時,強制形式的類型轉換會觸發一個運行時錯誤。 + +下面的例子,迭代了`library`裡的每一個 `MediaItem` ,並打印出適當的描述。要這樣做,`item`需要真正作為`Movie` 或 `Song`的類型來使用。不僅僅是作為 `MediaItem`。為了能夠使用`Movie` 或 `Song`的 `director` 或 `artist`屬性,這是必要的。 + +在這個示例中,數組中的每一個`item`可能是 `Movie` 或 `Song`。 事前你不知道每個`item`的真實類型,所以這裡使用可選形式的類型轉換 (`as?`)去檢查循環裡的每次下轉。 + +```swift +for item in library { + if let movie = item as? Movie { + println("Movie: '\(movie.name)', dir. \(movie.director)") + } else if let song = item as? Song { + println("Song: '\(song.name)', by \(song.artist)") + } +} + +// Movie: 'Casablanca', dir. Michael Curtiz +// Song: 'Blue Suede Shoes', by Elvis Presley +// Movie: 'Citizen Kane', dir. Orson Welles +// Song: 'The One And Only', by Chesney Hawkes +// Song: 'Never Gonna Give You Up', by Rick Astley +``` + +示例首先試圖將 `item` 下轉為 `Movie`。因為 `item` 是一個 `MediaItem` +類型的實例,它可能是一個`Movie`;同樣,它可能是一個 `Song`,或者僅僅是基類 +`MediaItem`。因為不確定,`as?`形式在試圖下轉時將返還一個可選值。 `item as Movie` 的返回值是`Movie?`類型或 「optional `Movie`」。 + +當向下轉型為 `Movie` 應用在兩個 `Song` +實例時將會失敗。為了處理這種情況,上面的例子使用了可選綁定(optional binding)來檢查可選 `Movie`真的包含一個值(這個是為了判斷下轉是否成功。)可選綁定是這樣寫的「`if let movie = item as? Movie`」,可以這樣解讀: + +「嘗試將 `item` 轉為 `Movie`類型。若成功,設置一個新的臨時常量 `movie` 來存儲返回的可選`Movie`」 + +若向下轉型成功,然後`movie`的屬性將用於打印一個`Movie`實例的描述,包括它的導演的名字`director`。當`Song`被找到時,一個相近的原理被用來檢測 `Song` 實例和打印它的描述。 + +> 注意: +轉換沒有真的改變實例或它的值。潛在的根本的實例保持不變;只是簡單地把它作為它被轉換成的類來使用。 + + +## `Any`和`AnyObject`的類型轉換 + +Swift為不確定類型提供了兩種特殊類型別名: + +* `AnyObject`可以代表任何class類型的實例。 +* `Any`可以表示任何類型,除了方法類型(function types)。 + +> 注意: +只有當你明確的需要它的行為和功能時才使用`Any`和`AnyObject`。在你的代碼裡使用你期望的明確的類型總是更好的。 + +### `AnyObject`類型 + +當需要在工作中使用 Cocoa APIs,它一般接收一個`AnyObject[]`類型的數組,或者說「一個任何對像類型的數組」。這是因為 Objective-C 沒有明確的類型化數組。但是,你常常可以確定包含在僅從你知道的 API 信息提供的這樣一個數組中的對象的類型。 + +在這些情況下,你可以使用強制形式的類型轉換(`as`)來下轉在數組中的每一項到比 `AnyObject` 更明確的類型,不需要可選解析(optional unwrapping)。 + +下面的示例定義了一個 `AnyObject[]` 類型的數組並填入三個`Movie`類型的實例: + +```swift +let someObjects: AnyObject[] = [ + Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"), + Movie(name: "Moon", director: "Duncan Jones"), + Movie(name: "Alien", director: "Ridley Scott") +] +``` + +因為知道這個數組只包含 `Movie` 實例,你可以直接用(`as`)下轉並解包到不可選的`Movie`類型(ps:其實就是我們常用的正常類型,這裡是為了和可選類型相對比)。 + +```swift +for object in someObjects { + let movie = object as Movie + println("Movie: '\(movie.name)', dir. \(movie.director)") +} +// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick +// Movie: 'Moon', dir. Duncan Jones +// Movie: 'Alien', dir. Ridley Scott +``` + +為了變為一個更短的形式,下轉`someObjects`數組為`Movie[]`類型來代替下轉每一項方式。 + +```swift +for movie in someObjects as Movie[] { + println("Movie: '\(movie.name)', dir. \(movie.director)") +} +// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick +// Movie: 'Moon', dir. Duncan Jones +// Movie: 'Alien', dir. Ridley Scott +``` + +### `Any`類型 + +這裡有個示例,使用 `Any` 類型來和混合的不同類型一起工作,包括非`class`類型。它創建了一個可以存儲`Any`類型的數組 `things`。 + +```swift +var things = Any[]() + +things.append(0) +things.append(0.0) +things.append(42) +things.append(3.14159) +things.append("hello") +things.append((3.0, 5.0)) +things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman")) +``` + +`things` 數組包含兩個 `Int` 值,2個 `Double` 值,1個 `String` 值,一個元組 `(Double, Double)` ,Ivan Reitman 導演的電影「Ghostbusters」。 + +你可以在 `switch` `cases`裡用`is` 和 `as` 操作符來發覺只知道是 `Any` 或 `AnyObject`的常量或變量的類型。 下面的示例迭代 `things`數組中的每一項的並用`switch`語句查找每一項的類型。這幾種`switch`語句的情形綁定它們匹配的值到一個規定類型的常量,讓它們可以打印它們的值: + +```swift +for thing in things { + switch thing { + case 0 as Int: + println("zero as an Int") + case 0 as Double: + println("zero as a Double") + case let someInt as Int: + println("an integer value of \(someInt)") + case let someDouble as Double where someDouble > 0: + println("a positive double value of \(someDouble)") + case is Double: + println("some other double value that I don't want to print") + case let someString as String: + println("a string value of \"\(someString)\"") + case let (x, y) as (Double, Double): + println("an (x, y) point at \(x), \(y)") + case let movie as Movie: + println("a movie called '\(movie.name)', dir. \(movie.director)") + default: + println("something else") + } +} + +// zero as an Int +// zero as a Double +// an integer value of 42 +// a positive double value of 3.14159 +// a string value of "hello" +// an (x, y) point at 3.0, 5.0 +// a movie called 'Ghostbusters', dir. Ivan Reitman +``` + + +> 注意: +在一個switch語句的case中使用強制形式的類型轉換操作符(as, 而不是 as?)來檢查和轉換到一個明確的類型。在 switch case 語句的內容中這種檢查總是安全的。 diff --git a/source-tw/chapter2/19_Nested_Types.md b/source-tw/chapter2/19_Nested_Types.md new file mode 100644 index 00000000..ba976994 --- /dev/null +++ b/source-tw/chapter2/19_Nested_Types.md @@ -0,0 +1,97 @@ +> 翻譯:[Lin-H](https://github.com/Lin-H) +> 校對:[shinyzhu](https://github.com/shinyzhu) + +# 嵌套類型 +----------------- + +本頁包含內容: + +- [嵌套類型實例](#nested_types_in_action) +- [嵌套類型的引用](#referring_to_nested_types) + +枚舉類型常被用於實現特定類或結構體的功能。也能夠在有多種變量類型的環境中,方便地定義通用類或結構體來使用,為了實現這種功能,Swift允許你定義嵌套類型,可以在枚舉類型、類和結構體中定義支持嵌套的類型。 + +要在一個類型中嵌套另一個類型,將需要嵌套的類型的定義寫在被嵌套類型的區域{}內,而且可以根據需要定義多級嵌套。 + + +##嵌套類型實例 + +下面這個例子定義了一個結構體`BlackjackCard`(二十一點),用來模擬`BlackjackCard`中的撲克牌點數。`BlackjackCard`結構體包含2個嵌套定義的枚舉類型`Suit` 和 `Rank`。 + +在`BlackjackCard`規則中,`Ace`牌可以表示1或者11,`Ace`牌的這一特徵用一個嵌套在枚舉型`Rank`的結構體`Values`來表示。 + +```swift +struct BlackjackCard { + // 嵌套定義枚舉型Suit + enum Suit: Character { + case Spades = "□", Hearts = "□", Diamonds = "□", Clubs = "□" + } + + // 嵌套定義枚舉型Rank + enum Rank: Int { + case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten + case Jack, Queen, King, Ace + struct Values { + let first: Int, second: Int? + } + var values: Values { + switch self { + case .Ace: + return Values(first: 1, second: 11) + case .Jack, .Queen, .King: + return Values(first: 10, second: nil) + default: + return Values(first: self.toRaw(), second: nil) + } + } + } + + // BlackjackCard 的屬性和方法 + let rank: Rank, suit: Suit + var description: String { + var output = "suit is \(suit.toRaw())," + output += " value is \(rank.values.first)" + if let second = rank.values.second { + output += " or \(second)" + } + return output + } +} +``` + +枚舉型的`Suit`用來描述撲克牌的四種花色,並分別用一個`Character`類型的值代表花色符號。 + +枚舉型的`Rank`用來描述撲克牌從`Ace`~10,`J`,`Q`,`K`,13張牌,並分別用一個`Int`類型的值表示牌的面值。(這個`Int`類型的值不適用於`Ace`,`J`,`Q`,`K`的牌)。 + +如上文所提到的,枚舉型`Rank`在自己內部定義了一個嵌套結構體`Values`。這個結構體包含兩個變量,只有`Ace`有兩個數值,其餘牌都只有一個數值。結構體`Values`中定義的兩個屬性: + +`first`, 為` Int` +`second`, 為 `Int?`, 或 「optional `Int`」 + +`Rank`定義了一個計算屬性`values`,這個計算屬性會根據牌的面值,用適當的數值去初始化`Values`實例,並賦值給`values`。對於`J`,`Q`,`K`,`Ace`會使用特殊數值,對於數字面值的牌使用`Int`類型的值。 + +`BlackjackCard`結構體自身有兩個屬性—`rank`與`suit`,也同樣定義了一個計算屬性`description`,`description`屬性用`rank`和`suit`的中內容來構建對這張撲克牌名字和數值的描述,並用可選類型`second`來檢查是否存在第二個值,若存在,則在原有的描述中增加對第二數值的描述。 + +因為`BlackjackCard`是一個沒有自定義構造函數的結構體,在[Memberwise Initializers for Structure Types](https://github.com/CocoaChina-editors/Welcome-to-Swift/blob/master/The%20Swift%20Programming%20Language/02Language%20Guide/14Initialization.md)中知道結構體有默認的成員構造函數,所以你可以用默認的`initializer`去初始化新的常量`theAceOfSpades`: + +```swift +let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades) +println("theAceOfSpades: \(theAceOfSpades.description)") +// 打印出 "theAceOfSpades: suit is □, value is 1 or 11" +``` + +儘管`Rank`和`Suit`嵌套在`BlackjackCard`中,但仍可被引用,所以在初始化實例時能夠通過枚舉類型中的成員名稱單獨引用。在上面的例子中`description`屬性能正確得輸出對`Ace`牌有1和11兩個值。 + + +##嵌套類型的引用 + +在外部對嵌套類型的引用,以被嵌套類型的名字為前綴,加上所要引用的屬性名: + +```swift +let heartsSymbol = BlackjackCard.Suit.Hearts.toRaw() +// 紅心的符號 為 "□" +``` + +對於上面這個例子,這樣可以使`Suit`, `Rank`, 和 `Values`的名字盡可能的短,因為它們的名字會自然的由被定義的上下文來限定。 + +preview diff --git a/source-tw/chapter2/20_Extensions.md b/source-tw/chapter2/20_Extensions.md new file mode 100644 index 00000000..6f77a562 --- /dev/null +++ b/source-tw/chapter2/20_Extensions.md @@ -0,0 +1,300 @@ +> 翻譯:[lyuka](https://github.com/lyuka) +> 校對:[Hawstein](https://github.com/Hawstein) + +#擴展(Extensions) +---- + +本頁包含內容: + +- [擴展語法](#extension_syntax) +- [計算型屬性](#computed_properties) +- [構造器](#initializers) +- [方法](#methods) +- [下標](#subscripts) +- [嵌套類型](#nested_types) + +*擴展*就是向一個已有的類、結構體或枚舉類型添加新功能(functionality)。這包括在沒有權限獲取原始源代碼的情況下擴展類型的能力(即*逆向建模*)。擴展和 Objective-C 中的分類(categories)類似。(不過與Objective-C不同的是,Swift 的擴展沒有名字。) + +Swift 中的擴展可以: + +- 添加計算型屬性和計算靜態屬性 +- 定義實例方法和類型方法 +- 提供新的構造器 +- 定義下標 +- 定義和使用新的嵌套類型 +- 使一個已有類型符合某個協議 + + +>注意: +如果你定義了一個擴展向一個已有類型添加新功能,那麼這個新功能對該類型的所有已有實例中都是可用的,即使它們是在你的這個擴展的前面定義的。 + + +## 擴展語法(Extension Syntax) + +聲明一個擴展使用關鍵字`extension`: + +```swift +extension SomeType { + // 加到SomeType的新功能寫到這裡 +} +``` + +一個擴展可以擴展一個已有類型,使其能夠適配一個或多個協議(protocol)。當這種情況發生時,協議的名字應該完全按照類或結構體的名字的方式進行書寫: + +```swift +extension SomeType: SomeProtocol, AnotherProctocol { + // 協議實現寫到這裡 +} +``` + +按照這種方式添加的協議遵循者(protocol conformance)被稱之為[在擴展中添加協議遵循者](21_Protocols.html#adding_protocol_conformance_with_an_extension) + + +## 計算型屬性(Computed Properties) + +擴展可以向已有類型添加計算型實例屬性和計算型類型屬性。下面的例子向 Swift 的內建`Double`類型添加了5個計算型實例屬性,從而提供與距離單位協作的基本支持。 + +```swift +extension Double { + var km: Double { return self * 1_000.0 } + var m : Double { return self } + var cm: Double { return self / 100.0 } + var mm: Double { return self / 1_000.0 } + var ft: Double { return self / 3.28084 } +} +let oneInch = 25.4.mm +println("One inch is \(oneInch) meters") +// 打印輸出:"One inch is 0.0254 meters" +let threeFeet = 3.ft +println("Three feet is \(threeFeet) meters") +// 打印輸出:"Three feet is 0.914399970739201 meters" +``` + +這些計算屬性表達的含義是把一個`Double`型的值看作是某單位下的長度值。即使它們被實現為計算型屬性,但這些屬性仍可以接一個帶有dot語法的浮點型字面值,而這恰恰是使用這些浮點型字面量實現距離轉換的方式。 + +在上述例子中,一個`Double`型的值`1.0`被用來表示「1米」。這就是為什麼`m`計算型屬性返回`self`——表達式`1.m`被認為是計算`1.0`的`Double`值。 + +其它單位則需要一些轉換來表示在米下測量的值。1千米等於1,000米,所以`km`計算型屬性要把值乘以`1_000.00`來轉化成單位米下的數值。類似地,1米有3.28024英尺,所以`ft`計算型屬性要把對應的`Double`值除以`3.28024`來實現英尺到米的單位換算。 + +這些屬性是只讀的計算型屬性,所有從簡考慮它們不用`get`關鍵字表示。它們的返回值是`Double`型,而且可以用於所有接受`Double`的數學計算中: + +```swift +let aMarathon = 42.km + 195.m +println("A marathon is \(aMarathon) meters long") +// 打印輸出:"A marathon is 42495.0 meters long" +``` + + +>注意: +擴展可以添加新的計算屬性,但是不可以添加存儲屬性,也不可以向已有屬性添加屬性觀測器(property observers)。 + + +## 構造器(Initializers) + +擴展可以向已有類型添加新的構造器。這可以讓你擴展其它類型,將你自己的定制類型作為構造器參數,或者提供該類型的原始實現中沒有包含的額外初始化選項。 + +擴展能向類中添加新的便利構造器,但是它們不能向類中添加新的指定構造器或析構函數。指定構造器和析構函數必須總是由原始的類實現來提供。 + +> 注意: +如果你使用擴展向一個值類型添加一個構造器,在該值類型已經向所有的存儲屬性提供默認值,而且沒有定義任何定制構造器(custom initializers)時,你可以在值類型的擴展構造器中調用默認構造器(default initializers)和逐一成員構造器(memberwise initializers)。 + +正如在值類型的構造器委託中描述的,如果你已經把構造器寫成值類型原始實現的一部分,上述規則不再適用。 + + +下面的例子定義了一個用於描述幾何矩形的定制結構體`Rect`。這個例子同時定義了兩個輔助結構體`Size`和`Point`,它們都把`0.0`作為所有屬性的默認值: + +```swift +struct Size { + var width = 0.0, height = 0.0 +} +struct Point { + var x = 0.0, y = 0.0 +} +struct Rect { + var origin = Point() + var size = Size() +} +``` + +因為結構體`Rect`提供了其所有屬性的默認值,所以正如默認構造器中描述的,它可以自動接受一個默認的構造器和一個成員級構造器。這些構造器可以用於構造新的`Rect`實例: + +```swift +let defaultRect = Rect() +let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0), + size: Size(width: 5.0, height: 5.0)) +``` + +你可以提供一個額外的使用特殊中心點和大小的構造器來擴展`Rect`結構體: + +```swift +extension Rect { + 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) + } +} +``` + +這個新的構造器首先根據提供的`center`和`size`值計算一個合適的原點。然後調用該結構體自動的成員構造器`init(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) +``` + + +>注意: +如果你使用擴展提供了一個新的構造器,你依舊有責任保證構造過程能夠讓所有實例完全初始化。 + + +## 方法(Methods) + +擴展可以向已有類型添加新的實例方法和類型方法。下面的例子向`Int`類型添加一個名為`repetitions`的新實例方法: + +```swift +extension Int { + func repetitions(task: () -> ()) { + for i in 0..self { + task() + } + } +} +``` + +這個`repetitions`方法使用了一個`() -> ()`類型的單參數(single argument),表明函數沒有參數而且沒有返回值。 + +定義該擴展之後,你就可以對任意整數調用`repetitions`方法,實現的功能則是多次執行某任務: + +```swift +3.repetitions({ + println("Hello!") + }) +// Hello! +// Hello! +// Hello! +``` + +可以使用 trailing 閉包使調用更加簡潔: + +```swift +3.repetitions{ + println("Goodbye!") +} +// Goodbye! +// Goodbye! +// Goodbye! +``` + + +### 修改實例方法(Mutating Instance Methods) + +通過擴展添加的實例方法也可以修改該實例本身。結構體和枚舉類型中修改`self`或其屬性的方法必須將該實例方法標注為`mutating`,正如來自原始實現的修改方法一樣。 + +下面的例子向Swift的`Int`類型添加了一個新的名為`square`的修改方法,來實現一個原始值的平方計算: + +```swift +extension Int { + mutating func square() { + self = self * self + } +} +var someInt = 3 +someInt.square() +// someInt 現在值是 9 +``` + + +## 下標(Subscripts) + +擴展可以向一個已有類型添加新下標。這個例子向Swift內建類型`Int`添加了一個整型下標。該下標`[n]`返回十進制數字從右向左數的第n個數字 + +- 123456789[0]返回9 +- 123456789[1]返回8 + +...等等 + +```swift +extension Int { + subscript(digitIndex: Int) -> Int { + var decimalBase = 1 + for _ in 1...digitIndex { + decimalBase *= 10 + } + return (self / decimalBase) % 10 + } +} +746381295[0] +// returns 5 +746381295[1] +// returns 9 +746381295[2] +// returns 2 +746381295[8] +// returns 7 +``` + +如果該`Int`值沒有足夠的位數,即下標越界,那麼上述實現的下標會返回0,因為它會在數字左邊自動補0: + +```swift +746381295[9] +//returns 0, 即等同於: +0746381295[9] +``` + + +## 嵌套類型(Nested Types) + +擴展可以向已有的類、結構體和枚舉添加新的嵌套類型: + +```swift +extension Character { + enum Kind { + case Vowel, Consonant, Other + } + var kind: Kind { + switch String(self).lowercaseString { + case "a", "e", "i", "o", "u": + return .Vowel + case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", + "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": + return .Consonant + default: + return .Other + } + } +} +``` + +該例子向`Character`添加了新的嵌套枚舉。這個名為`Kind`的枚舉表示特定字符的類型。具體來說,就是表示一個標準的拉丁腳本中的字符是元音還是輔音(不考慮口語和地方變種),或者是其它類型。 + +這個例子還向`Character`添加了一個新的計算實例屬性,即`kind`,用來返回合適的`Kind`枚舉成員。 + +現在,這個嵌套枚舉可以和一個`Character`值聯合使用了: + +```swift +func printLetterKinds(word: String) { + println("'\\(word)' is made up of the following kinds of letters:") + for character in word { + switch character.kind { + case .Vowel: + print("vowel ") + case .Consonant: + print("consonant ") + case .Other: + print("other ") + } + } + print("\n") +} +printLetterKinds("Hello") +// 'Hello' is made up of the following kinds of letters: +// consonant vowel consonant consonant vowel +``` + +函數`printLetterKinds`的輸入是一個`String`值並對其字符進行迭代。在每次迭代過程中,考慮當前字符的`kind`計算屬性,並打印出合適的類別描述。所以`printLetterKinds`就可以用來打印一個完整單詞中所有字母的類型,正如上述單詞`"hello"`所展示的。 + +>注意: +由於已知`character.kind`是`Character.Kind`型,所以`Character.Kind`中的所有成員值都可以使用`switch`語句裡的形式簡寫,比如使用 `.Vowel`代替`Character.Kind.Vowel` + diff --git a/source-tw/chapter2/21_Protocols.md b/source-tw/chapter2/21_Protocols.md new file mode 100644 index 00000000..a8eab7e0 --- /dev/null +++ b/source-tw/chapter2/21_Protocols.md @@ -0,0 +1,732 @@ +> 翻譯:[geek5nan](https://github.com/geek5nan) +> 校對:[dabing1022](https://github.com/dabing1022) + +# 協議 +----------------- + +本頁包含內容: + +- [協議的語法(Protocol Syntax)](#protocol_syntax) +- [對屬性的規定(Property Requirements)](#property_requirements) +- [對方法的規定(Method Requirements)](#method_requirements) +- [對突變方法的的規定(Mutating Method Requirements)](#mutating_method_requirements) +- [協議類型(Protocols as Types)](#protocols_as_types) +- [委託(代理)模式(Delegation)](#delegation) +- [在擴展中添加協議成員(Adding Protocol Conformance with an Extension)](#adding_protocol_conformance_with_an_extension) +- [通過擴展補充協議聲明(Declaring Protocol Adoption with an Extension)](#declaring_protocol_adoption_with_an_extension) +- [集合中的協議類型(Collections of Protocol Types)](#collections_of_protocol_types) +- [協議的繼承(Protocol Inheritance)](#protocol_inheritance) +- [協議合成(Protocol Composition)](#protocol_composition) +- [檢驗協議的一致性(Checking for Protocol Conformance)](#checking_for_protocol_conformance) +- [對可選協議的規定(Optional Protocol Requirements)](#optional_protocol_requirements) + +`協議(Protocol)`用於定義完成某項任務或功能所必須的方法和屬性,協議實際上並不提供這些功能或任務的具體`實現(Implementation)`--而只用來描述這些實現應該是什麼樣的。類,結構體,枚舉通過提供協議所要求的方法,屬性的具體實現來`採用(adopt)`協議。任意能夠滿足協議要求的類型被稱為協議的`遵循者`。 + +`協議`可以要求其`遵循者`提供特定的實例屬性,實例方法,類方法,操作符或`下標(subscripts)`等。 + + +## 協議的語法 + +`協議`的定義方式與`類,結構體,枚舉`的定義都非常相似,如下所示: + +```swift +protocol SomeProtocol { + // 協議內容 +} +``` + +在類型名稱後加上`協議名稱`,中間以冒號`:`分隔即可實現協議;實現多個協議時,各協議之間用逗號`,`分隔,如下所示: + +```swift +struct SomeStructure: FirstProtocol, AnotherProtocol { + // 結構體內容 +} +``` + +如果一個類在含有`父類`的同時也採用了協議,應當把`父類`放在所有的`協議`之前,如下所示: + +```swift +class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol { + // 類的內容 +} +``` + + +## 對屬性的規定 + +協議可以規定其`遵循者`提供特定名稱與類型的`實例屬性(instance property)`或`類屬性(type property)`,而不管其是`存儲型屬性(stored property)`還是`計算型屬性(calculate property)`。此外也可以指定屬性是只讀的還是可讀寫的。 + +如果協議要求屬性是可讀寫的,那麼這個屬性不能是常量`存儲型屬性`或只讀`計算型屬性`;如果協議要求屬性是只讀的(gettable),那麼`計算型屬性`或`存儲型屬性`都能滿足協議對屬性的規定,在你的代碼中,即使為只讀屬性實現了寫方法(settable)也依然有效。 + +協議中的屬性經常被加以`var`前綴聲明其為變量屬性,在聲明後加上`{ set get }`來表示屬性是可讀寫的,只讀的屬性則寫作`{ get }`,如下所示: + +```swift +protocol SomeProtocol { + var musBeSettable : Int { get set } + var doesNotNeedToBeSettable: Int { get } +} +``` + +如下所示,通常在協議的定義中使用`class`前綴表示該屬性為類成員;在枚舉和結構體實現協議時中,需要使用`static`關鍵字作為前綴。 + +```swift +protocol AnotherProtocol { + class var someTypeProperty: Int { get set } +} + +如下所示,這是一個含有一個實例屬性要求的協議: + +protocol FullyNamed { + var fullName: String { get } +} +``` + +`FullyNamed`協議定義了任何擁有`fullName`的類型。它並不指定具體類型,而只是要求類型必須提供一個`fullName`。任何`FullyNamed`類型都得有一個只讀的`fullName`屬性,類型為`String`。 + +如下所示,這是一個實現了`FullyNamed`協議的簡單結構體: + +```swift +struct Person: FullyNamed{ + var fullName: String +} +let john = Person(fullName: "John Appleseed") +//john.fullName 為 "John Appleseed" +``` + +這個例子中定義了一個叫做`Person`的結構體,用來表示具有指定名字的人。從第一行代碼中可以看出,它採用了`FullyNamed`協議。 + +`Person`結構體的每一個實例都有一個叫做`fullName`,`String`類型的存儲型屬性,這正好匹配了`FullyNamed`協議的要求,也就意味著,`Person`結構體完整的`遵循`了協議。(如果協議要求未被完全滿足,在編譯時會報錯) + +這有一個更為複雜的類,它採用並實現了`FullyNamed`協議,如下所示: + +```swift +class Starship: FullyNamed { + var prefix: String? + var name: String + init(name: String, prefix: String? = nil ) { + self.anme = name + self.prefix = prefix + } + var fullName: String { + return (prefix ? prefix ! + " " : " ") + name + } +} +var ncc1701 = Starship(name: "Enterprise", prefix: "USS") +// ncc1701.fullName == "USS Enterprise" +``` + +`Starship`類把`fullName`屬性實現為只讀的`計算型屬性`。每一個`Starship`類的實例都有一個名為`name`的必備屬性和一個名為`prefix`的可選屬性。 當`prefix`存在時,將`prefix`插入到`name`之前來為`Starship`構建`fullName`,`prefix`不存在時,則將直接用`name`構建`fullName` + + +## 對方法的規定 + +`協議`可以要求其`遵循者`實現某些指定的`實例方法`或`類方法`。這些方法作為協議的一部分,像普通的方法一樣清晰的放在協議的定義中,而不需要大括號和方法體。 + +>注意: +>協議中的方法支持`變長參數(variadic parameter)`,不支持`參數默認值(default value)`。 + +如下所示,協議中類方法的定義與類屬性的定義相似,在協議定義的方法前置`class`關鍵字來表示。當在`枚舉`或`結構體`實現類方法時,需要使用`static`關鍵字來代替。 + +```swift +protocol SomeProtocol { + class func someTypeMethod() +} +``` + +如下所示,定義了含有一個實例方法的的協議。 + +``` +protocol RandomNumberGenerator { + func random() -> Double +} +``` + +`RandomNumberGenerator`協議要求其`遵循者`必須擁有一個名為`random`, 返回值類型為`Double`的實例方法。 (儘管這裡並未指明,但是我們假設返回值在[0,1]區間內)。 + +`RandomNumberGenerator`協議並不在意每一個隨機數是怎樣生成的,它只強調這裡有一個隨機數生成器。 + +如下所示,下邊的是一個遵循了`RandomNumberGenerator`協議的類。該類實現了一個叫做*線性同餘生成器(linear congruential generator)*的偽隨機數算法。 + + +```swift +class LinearCongruentialGenerator: RandomNumberGenerator { + var lastRandom = 42.0 + let m = 139968.0 + let a = 3877.0 + let c = 29573.0 + func random() -> Double { + lastRandom = ((lastRandom * a + c) % m) + return lastRandom / m + } +} +let generator = LinearCongruentialGenerator() +println("Here's a random number: \(generator.random())") +// 輸出 : "Here's a random number: 0.37464991998171" +println("And another one: \(generator.random())") +// 輸出 : "And another one: 0.729023776863283" +``` + + +## 對突變方法的規定 + +有時不得不在方法中更改實例的所屬類型。在基於`值類型(value types)`(結構體,枚舉)的實例方法中,將`mutating`關鍵字作為函數的前綴,寫在`func`之前,表示可以在該方法中修改實例及其屬性的所屬類型。這一過程在[Modifyting Value Types from Within Instance Methods](1)章節中有詳細描述。 + +如果協議中的實例方法打算改變其`遵循者`實例的類型,那麼在協議定義時需要在方法前加`mutating`關鍵字,才能使`結構體,枚舉`來採用並滿足協議中對方法的規定。 + + +>注意: +>用`類`實現協議中的`mutating`方法時,不用寫`mutating`關鍵字;用`結構體`,`枚舉`實現協議中的`mutating`方法時,必須寫`mutating`關鍵字。 + +如下所示,`Togglable`協議含有名為`toggle`的突變實例方法。根據名稱推測,`toggle`方法應該是用於切換或恢復其`遵循者`實例或其屬性的類型。 + +```swift +protocol Togglable { + mutating func toggle() +} +``` + +當使用`枚舉`或`結構體`來實現`Togglabl`協議時,需要提供一個帶有`mutating`前綴的`toggle`方法。 + +如下所示,`OnOffSwitch`枚舉`遵循`了`Togglable`協議,`On`,`Off`兩個成員用於表示當前狀態。枚舉的`toggle`方法被標記為`mutating`,用以匹配`Togglabel`協議的規定。 + +```swift +enum OnOffSwitch: Togglable { + case Off, On + mutating func toggle() { + switch self { + case Off: + self = On + case On: + self = Off + } + } +} +var lightSwitch = OnOffSwitch.Off +lightSwitch.toggle() +//lightSwitch 現在的值為 .On +``` + + +## 協議類型 + +儘管`協議`本身並不實現任何功能,但是`協議`可以被當做類型來使用。 + +使用場景: + +* `協議類型`作為函數、方法或構造器中的參數類型或返回值類型 +* `協議類型`作為常量、變量或屬性的類型 +* `協議類型`作為數組、字典或其他容器中的元素類型 + +> 注意: 協議是一種類型,因此協議類型的名稱應與其他類型(Int,Double,String)的寫法相同,使用駝峰式寫法 + +如下所示,這個示例中將協議當做類型來使用 + +```swift +class Dice { + let sides: Int + let generator: RandomNumberGenerator + init(sides: Int, generator: RandomNumberGenerator) { + self.sides = sides + self.generator = generator + } + func roll() -> Int { + return Int(generator.random() * Double(sides)) +1 + } +} +``` + +例子中又一個`Dice`類,用來代表桌游中的擁有N個面的骰子。`Dice`的實例含有`sides`和`generator`兩個屬性,前者是整型,用來表示骰子有幾個面,後者為骰子提供一個隨機數生成器。 + + `generator`屬性的類型為`RandomNumberGenerator`,因此任何遵循了`RandomNumberGenerator`協議的類型的實例都可以賦值給`generator`,除此之外,無其他要求。 + +`Dice`類中也有一個`構造器(initializer)`,用來進行初始化操作。構造器中含有一個名為`generator`,類型為`RandomNumberGenerator`的形參。在調用構造方法時創建`Dice`的實例時,可以傳入任何遵循`RandomNumberGenerator`協議的實例給generator。 + +`Dice`類也提供了一個名為`roll`的實例方法用來模擬骰子的面值。它先使用`generator`的`random`方法來創建一個[0-1]區間內的隨機數種子,然後加工這個隨機數種子生成骰子的面值。generator被認為是遵循了`RandomNumberGenerator`的類型,因而保證了`random`方法可以被調用。 + +如下所示,這裡展示了如何使用`LinearCongruentialGenerator`的實例作為隨機數生成器創建一個六面骰子: + +```swift +var d6 = Dice(sides: 6,generator: LinearCongruentialGenerator()) +for _ in 1...5 { + println("Random dice roll is \(d6.roll())") +} +//輸出結果 +//Random dice roll is 3 +//Random dice roll is 5 +//Random dice roll is 4 +//Random dice roll is 5 +//Random dice roll is 4 +``` + + +## 委託(代理)模式 + +委託是一種設計模式(*譯者注: 想起了那年 UITableViewDelegate 中的奔跑,那是我逝去的Objective-C。。。*),它允許`類`或`結構體`將一些需要它們負責的功能`交由(委託)`給其他的類型的實例。 + +委託模式的實現很簡單: 定義`協議`來`封裝`那些需要被委託的`函數和方法`, 使其`遵循者`擁有這些被委託的`函數和方法`。 + +委託模式可以用來響應特定的動作或接收外部數據源提供的數據,而無需要知道外部數據源的所屬類型(*譯者注:只要求外部數據源`遵循`某協議*)。 + +下文是兩個基於骰子遊戲的協議: + +```swift +protocol DiceGame { + var dice: Dice { get } + func play() +} + +protocol DiceGameDelegate { + func gameDidStart(game: DiceGame) + func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int) + func gameDidEnd(game: DiceGame) +} +``` + +`DiceGame`協議可以在任意含有骰子的遊戲中實現,`DiceGameDelegate`協議可以用來追蹤`DiceGame`的遊戲過程 + +如下所示,`SnakesAndLadders`是`Snakes and Ladders`(譯者注:[Control Flow](2)章節有該遊戲的詳細介紹)遊戲的新版本。新版本使用`Dice`作為骰子,並且實現了`DiceGame`和`DiceGameDelegate`協議,後者用來記錄遊戲的過程: + +```swift +class SnakesAndLadders: DiceGame { + let finalSquare = 25 + let dic = Dice(sides: 6, generator: LinearCongruentialGenerator()) + var square = 0 + var board: Int[] + init() { + board = Int[](count: finalSquare + 1, repeatedValue: 0) + board[03] = +08; board[06] = +11; borad[09] = +09; board[10] = +02 + borad[14] = -10; board[19] = -11; borad[22] = -02; board[24] = -08 + } + var delegate: DiceGameDelegate? + func play() { + square = 0 + delegate?.gameDidStart(self) + gameLoop: while square != finalSquare { + let diceRoll = dice.roll() + delegate?.game(self,didStartNewTurnWithDiceRoll: diceRoll) + switch square + diceRoll { + case finalSquare: + break gameLoop + case let newSquare where newSquare > finalSquare: + continue gameLoop + default: + square += diceRoll + square += board[square] + } + } + delegate?.gameDIdEnd(self) + } +} +``` + +這個版本的遊戲封裝到了`SnakesAndLadders`類中,該類採用了`DiceGame`協議,並且提供了`dice`屬性和`play`實例方法用來`遵循`協議。(`dice`屬性在構造之後就不在改變,且協議只要求`dice`為只讀的,因此將`dice`聲明為常量屬性。) + +在`SnakesAndLadders`類的`構造器(initializer)`初始化遊戲。所有的遊戲邏輯被轉移到了`play`方法中,`play`方法使用協議規定的`dice`屬性提供骰子搖出的值。 + +> 注意:`delegate`並不是遊戲的必備條件,因此`delegate`被定義為遵循`DiceGameDelegate`協議的可選屬性,`delegate`使用`nil`作為初始值。 + +`DicegameDelegate`協議提供了三個方法用來追蹤遊戲過程。被放置於遊戲的邏輯中,即`play()`方法內。分別在遊戲開始時,新一輪開始時,遊戲結束時被調用。 + +因為`delegate`是一個遵循`DiceGameDelegate`的可選屬性,因此在`play()`方法中使用了`可選鏈`來調用委託方法。 若`delegate`屬性為`nil`, 則delegate所調用的方法失效。若`delegate`不為`nil`,則方法能夠被調用 + +如下所示,`DiceGameTracker`遵循了`DiceGameDelegate`協議 + +```swift +class DiceGameTracker: DiceGameDelegate { + var numberOfTurns = 0 + func gameDidStart(game: DiceGame) { + numberOfTurns = 0 + if game is SnakesAndLadders { + println("Started a new game of Snakes and Ladders") + } + println("The game is using a \(game.dice.sides)-sided dice") + } + func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) { + ++numberOfTurns + println("Rolled a \(diceRoll)") + } + func gameDidEnd(game: DiceGame) { + println("The game lasted for \(numberOfTurns) turns") + } +} +``` + +`DiceGameTracker`實現了`DiceGameDelegate`協議規定的三個方法,用來記錄遊戲已經進行的輪數。 當遊戲開始時,`numberOfTurns`屬性被賦值為0; 在每新一輪中遞加; 遊戲結束後,輸出打印遊戲的總輪數。 + +`gameDidStart`方法從`game`參數獲取遊戲信息並輸出。`game`在方法中被當做`DiceGame`類型而不是`SnakeAndLadders`類型,所以方法中只能訪問`DiceGame`協議中的成員。 + +`DiceGameTracker`的運行情況,如下所示: + +```swift +let tracker = DiceGameTracker() +let game = SnakesAndLadders() +game.delegate = tracker +game.play() +// Started a new game of Snakes and Ladders +// The game is using a 6-sided dice +// Rolled a 3 +// Rolled a 5 +// Rolled a 4 +// Rolled a 5 +// The game lasted for 4 turns +``` + + +## 在擴展中添加協議成員 + +即便無法修改源代碼,依然可以通過`擴展(Extension)`來擴充已存在類型(*譯者注: 類,結構體,枚舉等*)。`擴展`可以為已存在的類型添加`屬性`,`方法`,`下標`,`協議`等成員。詳情請在[擴展](4)章節中查看。 + +> 注意: 通過`擴展`為已存在的類型`遵循`協議時,該類型的所有實例也會隨之添加協議中的方法 + +`TextRepresentable`協議含有一個`asText`,如下所示: + +```swift +protocol TextRepresentable { + func asText() -> String +} +``` + +通過`擴展`為上一節中提到的`Dice`類遵循`TextRepresentable`協議 + +```swift +extension Dice: TextRepresentable { + func asText() -> String { + return "A \(sides)-sided dice" + } +} +``` + +從現在起,`Dice`類型的實例可被當作`TextRepresentable`類型: + +```swift +let d12 = Dice(sides: 12,generator: LinearCongruentialGenerator()) +println(d12.asText()) +// 輸出 "A 12-sided dice" +``` + +`SnakesAndLadders`類也可以通過`擴展`的方式來遵循協議: + +```swift +extension SnakeAndLadders: TextRepresentable { + func asText() -> String { + return "A game of Snakes and Ladders with \(finalSquare) squares" + } +} +println(game.asText()) +// 輸出 "A game of Snakes and Ladders with 25 squares" +``` + + +## 通過擴展補充協議聲明 + +當一個類型已經實現了協議中的所有要求,卻沒有聲明時,可以通過`擴展`來補充協議聲明: + +```swift +struct Hamster { + var name: String + func asText() -> String { + return "A hamster named \(name)" + } +} +extension Hamster: TextRepresentable {} +``` + +從現在起,`Hamster`的實例可以作為`TextRepresentable`類型使用 + +```swift +let simonTheHamster = Hamster(name: "Simon") +let somethingTextRepresentable: TextRepresentable = simonTheHamester +println(somethingTextRepresentable.asText()) +// 輸出 "A hamster named Simon" +``` + +> 注意: 即時滿足了協議的所有要求,類型也不會自動轉變,因此你必須為它做出明顯的協議聲明 + + +## 集合中的協議類型 + +協議類型可以被集合使用,表示集合中的元素均為協議類型: + +```swift +let things: TextRepresentable[] = [game,d12,simoTheHamster] +``` + +如下所示,`things`數組可以被直接遍歷,並調用其中元素的`asText()`函數: + +```swift +for thing in things { + println(thing.asText()) +} +// A game of Snakes and Ladders with 25 squares +// A 12-sided dice +// A hamster named Simon +``` + +`thing`被當做是`TextRepresentable`類型而不是`Dice`,`DiceGame`,`Hamster`等類型。因此能且僅能調用`asText`方法 + + +## 協議的繼承 + +協議能夠繼承一到多個其他協議。語法與類的繼承相似,多個協議間用逗號`,`分隔 + +```swift +protocol InheritingProtocol: SomeProtocol, AnotherProtocol { + // 協議定義 +} +``` + +如下所示,`PrettyTextRepresentable`協議繼承了`TextRepresentable`協議 + +```swift +protocol PrettyTextRepresentable: TextRepresentable { + func asPrettyText() -> String +} +``` + +遵循`PrettyTextRepresentable`協議的同時,也需要遵循`TextRepresentable`協議。 + +如下所示,用`擴展`為`SnakesAndLadders`遵循`PrettyTextRepresentable`協議: + +```swift +extension SnakesAndLadders: PrettyTextRepresentable { + func asPrettyText() -> String { + var output = asText() + ":\n" + for index in 1...finalSquare { + switch board[index] { + case let ladder where ladder > 0: + output += "▲ " + case let snake where snake < 0: + output += "▼ " + default: + output += "○ " + } + } + return output + } +} +``` + +在`for in`中迭代出了`board`數組中的每一個元素: + +* 當從數組中迭代出的元素的值大於0時,用`▲`表示 +* 當從數組中迭代出的元素的值小於0時,用`▼`表示 +* 當從數組中迭代出的元素的值等於0時,用`○`表示 + +任意`SankesAndLadders`的實例都可以使用`asPrettyText()`方法。 + +```swift +println(game.asPrettyText()) +// A game of Snakes and Ladders with 25 squares: +// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○ +``` + + +## 協議合成 + +一個協議可由多個協議採用`protocol`這樣的格式進行組合,稱為`協議合成(protocol composition)`。 + +舉個例子: + +```swift +protocol Named { + var name: String { get } +} +protocol Aged { + var age: Int { get } +} +struct Person: Named, Aged { + var name: String + var age: Int +} +func wishHappyBirthday(celebrator: protocol) { + println("Happy birthday \(celebrator.name) - you're \(celebrator.age)!") +} +let birthdayPerson = Person(name: "Malcolm", age: 21) +wishHappyBirthday(birthdayPerson) +// 輸出 "Happy birthday Malcolm - you're 21! +``` + +`Named`協議包含`String`類型的`name`屬性;`Aged`協議包含`Int`類型的`age`屬性。`Person`結構體`遵循`了這兩個協議。 + +`wishHappyBirthday`函數的形參`celebrator`的類型為`protocol`。可以傳入任意`遵循`這兩個協議的類型的實例 + +> 注意: `協議合成`並不會生成一個新協議類型,而是將多個協議合成為一個臨時的協議,超出範圍後立即失效。 + + +## 檢驗協議的一致性 + +使用`is`和`as`操作符來檢查協議的一致性或轉化協議類型。檢查和轉化的語法和之前相同(*詳情查看[Typy Casting章節](5)*): + +* `is`操作符用來檢查實例是否`遵循`了某個`協議`。 +* `as?`返回一個可選值,當實例`遵循`協議時,返回該協議類型;否則返回`nil` +* `as`用以強制向下轉型。 + +```swift +@objc protocol HasArea { + var area: Double { get } +} +``` + +> 注意: `@objc`用來表示協議是可選的,也可以用來表示暴露給`Objective-C`的代碼,此外,`@objc`型協議只對`類`有效,因此只能在`類`中檢查協議的一致性。詳情查看*[Using Siwft with Cocoa and Objectivei-c](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)*。 + +如下所示,定義了`Circle`和`Country`類,它們都遵循了`haxArea`協議 + +```swift +class Circle: HasArea { + let pi = 3.1415927 + var radius: Double + var area: Double { return pi * radius * radius } + init(radius: Double) { self.radius = radius } +} +class Country: HasArea { + var area: Double + init(area: Double) { self.area = area } +} +``` + +`Circle`類把`area`實現為基於`存儲型屬性`radius的`計算型屬性`,`Country`類則把`area`實現為`存儲型屬性`。這兩個類都`遵循`了`haxArea`協議。 + +如下所示,Animal是一個沒有實現`HasArea`協議的類 + +```swift +class Animal { + var legs: Int + init(legs: Int) { self.legs = legs } +} +``` + +`Circle,Country,Animal`並沒有一個相同的基類,因而採用`AnyObject`類型的數組來裝載在他們的實例,如下所示: + +```swift +let objects: AnyObject[] = [ + Circle(radius: 2.0), + Country(area: 243_610), + Animal(legs: 4) +] +``` + +`objects`數組使用字面量初始化,數組包含一個`radius`為2。0的`Circle`的實例,一個保存了英國面積的`Country`實例和一個`legs`為4的`Animal`實例。 + +如下所示,`objects`數組可以被迭代,對迭代出的每一個元素進行檢查,看它是否遵循`了`HasArea`協議: + +```swift +for object in objects { + if let objectWithArea = object as? HasArea { + println("Area is \(objectWithArea.area)") + } else { + println("Something that doesn't have an area") + } +} +// Area is 12.5663708 +// Area is 243610.0 +// Something that doesn't have an area +``` + +當迭代出的元素遵循`HasArea`協議時,通過`as?`操作符將其`可選綁定(optional binding)`到`objectWithArea`常量上。`objectWithArea`是`HasArea`協議類型的實例,因此`area`屬性是可以被訪問和打印的。 + +`objects`數組中元素的類型並不會因為`向下轉型`而改變,它們仍然是`Circle`,`Country`,`Animal`類型。然而,當它們被賦值給`objectWithArea`常量時,則只被視為`HasArea`類型,因此只有`area`屬性能夠被訪問。 + + +## 對可選協議的規定 + +可選協議含有可選成員,其`遵循者`可以選擇是否實現這些成員。在協議中使用`@optional`關鍵字作為前綴來定義可選成員。 + +可選協議在調用時使用`可選鏈`,詳細內容在[Optional Chaning](7)章節中查看。 + +像`someOptionalMethod?(someArgument)`這樣,你可以在可選方法名稱後加上`?`來檢查該方法是否被實現。`可選方法`和`可選屬性`都會返回一個`可選值(optional value)`,當其不可訪問時,`?`之後語句不會執行,並整體返回`nil` + +> 注意: 可選協議只能在含有`@objc`前綴的協議中生效。且`@objc`的協議只能被`類`遵循 + +如下所示,`Counter`類使用含有兩個可選成員的`CounterDataSource`協議類型的外部數據源來提供`增量值(increment amount)` + +```swift +@objc protocol CounterDataSource { + @optional func incrementForCount(count: Int) -> Int + @optional var fixedIncrement: Int { get } +} +``` + +`CounterDataSource`含有`incrementForCount`的`可選方法`和`fiexdIncrement`的`可選屬性`,它們使用了不同的方法來從數據源中獲取合適的增量值。 + +> 注意: `CounterDataSource`中的屬性和方法都是可選的,因此可以在類中聲明但不實現這些成員,儘管技術上允許這樣做,不過最好不要這樣寫。 + +`Counter`類含有`CounterDataSource?`類型的可選屬性`dataSource`,如下所示: + +```swift +@objc class Counter { + var count = 0 + var dataSource: CounterDataSource? + func increment() { + if let amount = dataSource?.incrementForCount?(count) { + count += amount + } else if let amount = dataSource?.fixedIncrement? { + count += amount + } + } +} +``` + +`count`屬性用於存儲當前的值,`increment`方法用來為`count`賦值。 + +`increment`方法通過`可選鏈`,嘗試從兩種`可選成員`中獲取`count`。 + +1. 由於`dataSource`可能為`nil`,因此在`dataSource`後邊加上了`?`標記來表明只在`dataSource`非空時才去調用`incrementForCount`方法。 + +2. 即使`dataSource`存在,但是也無法保證其是否實現了`incrementForCount`方法,因此在`incrementForCount`方法後邊也加有`?`標記 + +在調用`incrementForCount`方法後,`Int`型`可選值`通過`可選綁定(optional binding)`自動拆包並賦值給常量`amount`。 + +當`incrementForCount`不能被調用時,嘗試使用可選屬性`fixedIncrement`來代替。 + +`ThreeSource`實現了`CounterDataSource`協議,如下所示: + + class ThreeSource: CounterDataSource { + let fixedIncrement = 3 + } + +使用`ThreeSource`作為數據源開實例化一個`Counter`: + +```swift +var counter = Counter() +counter.dataSource = ThreeSource() +for _ in 1...4 { + counter.increment() + println(counter.count) +} +// 3 +// 6 +// 9 +// 12 +``` + +`TowardsZeroSource`實現了`CounterDataSource`協議中的`incrementForCount`方法,如下所示: + +```swift +class TowardsZeroSource: CounterDataSource { +func incrementForCount(count: Int) -> Int { + if count == 0 { + return 0 + } else if count < 0 { + return 1 + } else { + return -1 + } + } +} +``` + +下邊是執行的代碼: + +```swift +counter.count = -4 +counter.dataSource = TowardsZeroSource() +for _ in 1...5 { + counter.increment() + println(counter.count) +} +// -3 +// -2 +// -1 +// 0 +// 0 +``` diff --git a/source-tw/chapter2/22_Generics.md b/source-tw/chapter2/22_Generics.md new file mode 100644 index 00000000..0772f022 --- /dev/null +++ b/source-tw/chapter2/22_Generics.md @@ -0,0 +1,518 @@ + +> 翻譯:[takalard](https://github.com/takalard) +> 校對:[lifedim](https://github.com/lifedim) + +# 泛型 + +------ + +本頁包含內容: + +- [泛型所解決的問題](#the_problem_that_generics_solve) +- [泛型函數](#generic_functions) +- [類型參數](#type_parameters) +- [命名類型參數](#naming_type_parameters) +- [泛型類型](#generic_types) +- [類型約束](#type_constraints) +- [關聯類型](#associated_types) +- [`Where`語句](#where_clauses) + +*泛型代碼*可以讓你寫出根據自我需求定義、適用於任何類型的,靈活且可重用的函數和類型。它的可以讓你避免重複的代碼,用一種清晰和抽像的方式來表達代碼的意圖。 + +泛型是 Swift 強大特徵中的其中一個,許多 Swift 標準庫是通過泛型代碼構建出來的。事實上,泛型的使用貫穿了整本語言手冊,只是你沒有發現而已。例如,Swift 的數組和字典類型都是泛型集。你可以創建一個`Int`數組,也可創建一個`String`數組,或者甚至於可以是任何其他 Swift 的類型數據數組。同樣的,你也可以創建存儲任何指定類型的字典(dictionary),而且這些類型可以是沒有限制的。 + + +## 泛型所解決的問題 + +這裡是一個標準的,非泛型函數`swapTwoInts`,用來交換兩個Int值: + +```swift +func swapTwoInts(inout a: Int, inout b: Int) + let temporaryA = a + a = b + b = temporaryA +} +``` + +這個函數使用寫入讀出(in-out)參數來交換`a`和`b`的值,請參考[寫入讀出參數](../chapter2/06_Functions.html)。 + +`swapTwoInts`函數可以交換`b`的原始值到`a`,也可以交換a的原始值到`b`,你可以調用這個函數交換兩個`Int`變量值: + +```swift +var someInt = 3 +var anotherInt = 107 +swapTwoInts(&someInt, &anotherInt) +println("someInt is now \(someInt), and anotherInt is now \(anotherInt)") +// 輸出 "someInt is now 107, and anotherInt is now 3" +``` + + +`swapTwoInts`函數是非常有用的,但是它只能交換`Int`值,如果你想要交換兩個`String`或者`Double`,就不得不寫更多的函數,如 `swapTwoStrings`和`swapTwoDoublesfunctions `,如同如下所示: + +```swift +func swapTwoStrings(inout a: String, inout b: String) { + let temporaryA = a + a = b + b = temporaryA +} + +func swapTwoDoubles(inout a: Double, inout b: Double) { + let temporaryA = a + a = b + b = temporaryA +} +``` + +你可能注意到 `swapTwoInts`、 `swapTwoStrings`和`swapTwoDoubles`函數功能都是相同的,唯一不同之處就在於傳入的變量類型不同,分別是`Int`、`String`和`Double`。 + +但實際應用中通常需要一個用處更強大並且盡可能的考慮到更多的靈活性單個函數,可以用來交換兩個任何類型值,很幸運的是,泛型代碼幫你解決了這種問題。(一個這種泛型函數後面已經定義好了。) + +>注意: +在所有三個函數中,`a`和`b`的類型是一樣的。如果`a`和`b`不是相同的類型,那它們倆就不能互換值。Swift 是類型安全的語言,所以它不允許一個`String`類型的變量和一個`Double`類型的變量互相交換值。如果一定要做,Swift 將報編譯錯誤。 + + +## 泛型函數 + +`泛型函數`可以工作於任何類型,這裡是一個上面`swapTwoInts`函數的泛型版本,用於交換兩個值: + +```swift +func swapTwoValues(inout a: T, inout b: T) { + let temporaryA = a + a = b + b = temporaryA +} +``` + +`swapTwoValues`函數主體和`swapTwoInts`函數是一樣的,它只在第一行稍微有那麼一點點不同於`swapTwoInts`,如下所示: + +```swift +func swapTwoInts(inout a: Int, inout b: Int) +func swapTwoValues(inout a: T, inout b: T) +``` + + +這個函數的泛型版本使用了佔位類型名字(通常此情況下用字母`T`來表示)來代替實際類型名(如`In`、`String`或`Doubl`)。佔位類型名沒有提示`T`必須是什麼類型,但是它提示了`a`和`b`必須是同一類型`T`,而不管`T`表示什麼類型。只有`swapTwoValues`函數在每次調用時所傳入的實際類型才能決定`T`所代表的類型。 + +另外一個不同之處在於這個泛型函數名後面跟著的展位類型名字(T)是用尖括號括起來的(``)。這個尖括號告訴 Swift 那個`T`是`swapTwoValues`函數所定義的一個類型。因為`T`是一個佔位命名類型,Swift 不會去查找命名為T的實際類型。 + +`swapTwoValues`函數除了要求傳入的兩個任何類型值是同一類型外,也可以作為`swapTwoInts`函數被調用。每次`swapTwoValues`被調用,T所代表的類型值都會傳給函數。 + +在下面的兩個例子中,`T`分別代表`Int`和`String`: + +```swift +var someInt = 3 +var anotherInt = 107 +swapTwoValues(&someInt, &anotherInt) +// someInt is now 107, and anotherInt is now 3 +``` + +```swift +var someString = "hello" +var anotherString = "world" +swapTwoValues(&someString, &anotherString) +// someString is now "world", and anotherString is now "hello" +``` + + +>注意 +上面定義的函數`swapTwoValues`是受`swap`函數啟發而實現的。`swap`函數存在於 Swift 標準庫,並可以在其它類中任意使用。如果你在自己代碼中需要類似`swapTwoValues`函數的功能,你可以使用已存在的交換函數`swap`函數。 + + +## 類型參數 + +在上面的`swapTwoValues`例子中,佔位類型`T`是一種類型參數的示例。類型參數指定並命名為一個佔位類型,並且緊隨在函數名後面,使用一對尖括號括起來(如``)。 + +一旦一個類型參數被指定,那麼其可以被使用來定義一個函數的參數類型(如`swapTwoValues`函數中的參數`a`和`b`),或作為一個函數返回類型,或用作函數主體中的註釋類型。在這種情況下,被類型參數所代表的佔位類型不管函數任何時候被調用,都會被實際類型所替換(在上面`swapTwoValues`例子中,當函數第一次被調用時,`T`被`Int`替換,第二次調用時,被`String`替換。)。 + +你可支持多個類型參數,命名在尖括號中,用逗號分開。 + + +## 命名類型參數 + +在簡單的情況下,泛型函數或泛型類型需要指定一個佔位類型(如上面的`swapTwoValues`泛型函數,或一個存儲單一類型的泛型集,如數組),通常用一單個字母`T`來命名類型參數。不過,你可以使用任何有效的標識符來作為類型參數名。 + +如果你使用多個參數定義更複雜的泛型函數或泛型類型,那麼使用更多的描述類型參數是非常有用的。例如,Swift 字典(Dictionary)類型有兩個類型參數,一個是鍵,另外一個是值。如果你自己寫字典,你或許會定義這兩個類型參數為`KeyType`和`ValueType`,用來記住它們在你的泛型代碼中的作用。 + +>注意 +請始終使用大寫字母開頭的駝峰式命名法(例如`T`和`KeyType`)來給類型參數命名,以表明它們是類型的佔位符,而非類型值。 + + +## 泛型類型 + + +通常在泛型函數中,Swift 允許你定義你自己的泛型類型。這些自定義類、結構體和枚舉作用於任何類型,如同`Array`和`Dictionary`的用法。 + +這部分向你展示如何寫一個泛型集類型--`Stack`(棧)。一個棧是一系列值域的集合,和`Array`(數組)類似,但其是一個比 Swift 的`Array`類型更多限制的集合。一個數組可以允許其裡面任何位置的插入/刪除操作,而棧,只允許在集合的末端添加新的項(如同*push*一個新值進棧)。同樣的一個棧也只能從末端移除項(如同*pop*一個值出棧)。 + +>注意 +棧的概念已被`UINavigationController`類使用來模擬試圖控制器的導航結構。你通過調用`UINavigationController`的`pushViewController:animated:`方法來為導航棧添加(add)新的試圖控制器;而通過`popViewControllerAnimated:`的方法來從導航棧中移除(pop)某個試圖控制器。每當你需要一個嚴格的`後進先出`方式來管理集合,堆棧都是最實用的模型。 + +下圖展示了一個棧的壓棧(push)/出棧(pop)的行為: + +![此處輸入圖片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png) + +1. 現在有三個值在棧中; +2. 第四個值「pushed」到棧的頂部; +3. 現在有四個值在棧中,最近的那個在頂部; +4. 棧中最頂部的那個項被移除,或稱之為「popped」; +5. 移除掉一個值後,現在棧又重新只有三個值。 + +這裡展示了如何寫一個非泛型版本的棧,`Int`值型的棧: + +```swift +struct IntStack { + var items = Int[]() + mutating func push(item: Int) { + items.append(item) + } + mutating func pop() -> Int { + return items.removeLast() + } +} +``` + +這個結構體在棧中使用一個`Array`性質的`items`存儲值。`Stack`提供兩個方法:`push`和`pop`,從棧中壓進一個值和移除一個值。這些方法標記為可變的,因為它們需要修改(或*轉換*)結構體的`items`數組。 + +上面所展現的`IntStack`類型只能用於`Int`值,不過,其對於定義一個泛型`Stack`類(可以處理*任何*類型值的棧)是非常有用的。 + +這裡是一個相同代碼的泛型版本: + + +```swift +struct Stack { + var items = T[]() + mutating func push(item: T) { + items.append(item) + } + mutating func pop() -> T { + return items.removeLast() + } +} +``` + + +注意到`Stack`的泛型版本基本上和非泛型版本相同,但是泛型版本的佔位類型參數為T代替了實際`Int`類型。這種類型參數包含在一對尖括號裡(``),緊隨在結構體名字後面。 + +`T`定義了一個名為「某種類型T」的節點提供給後來用。這種將來類型可以在結構體的定義裡任何地方表示為「T」。在這種情況下,`T`在如下三個地方被用作節點: + +- 創建一個名為`items`的屬性,使用空的T類型值數組對其進行初始化; +- 指定一個包含一個參數名為`item`的`push`方法,該參數必須是T類型; +- 指定一個`pop`方法的返回值,該返回值將是一個T類型值。 + +當創建一個新單例並初始化時, 通過用一對緊隨在類型名後的尖括號裡寫出實際指定棧用到類型,創建一個`Stack`實例,同創建`Array`和`Dictionary`一樣: + +```swift +var stackOfStrings = Stack() +stackOfStrings.push("uno") +stackOfStrings.push("dos") +stackOfStrings.push("tres") +stackOfStrings.push("cuatro") +// 現在棧已經有4個string了 +``` + +下圖將展示`stackOfStrings`如何`push`這四個值進棧的過程: + +![此處輸入圖片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png) + +從棧中`pop`並移除值"cuatro": + +```swift +let fromTheTop = stackOfStrings.pop() +// fromTheTop is equal to "cuatro", and the stack now contains 3 strings +``` + +下圖展示了如何從棧中pop一個值的過程: +![此處輸入圖片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png) + +由於`Stack`是泛型類型,所以在 Swift 中其可以用來創建任何有效類型的棧,這種方式如同`Array`和`Dictionary`。 + + +##類型約束 + +`swapTwoValues`函數和`Stack`類型可以作用於任何類型,不過,有的時候對使用在泛型函數和泛型類型上的類型強制約束為某種特定類型是非常有用的。類型約束指定了一個必須繼承自指定類的類型參數,或者遵循一個特定的協議或協議構成。 + +例如,Swift 的`Dictionary`類型對作用於其鍵的類型做了些限制。在[字典](../chapter2/04_Collection_Types.html)的描述中,字典的鍵類型必須是*可哈希*,也就是說,必須有一種方法可以使其被唯一的表示。`Dictionary`之所以需要其鍵是可哈希是為了以便於其檢查其是否已經包含某個特定鍵的值。如無此需求,`Dictionary`既不會告訴是否插入或者替換了某個特定鍵的值,也不能查找到已經存儲在字典裡面的給定鍵值。 + +這個需求強制加上一個類型約束作用於`Dictionary`的鍵上,當然其鍵類型必須遵循`Hashable`協議(Swift 標準庫中定義的一個特定協議)。所有的 Swift 基本類型(如`String`,`Int`, `Double`和 `Bool`)默認都是可哈希。 + +當你創建自定義泛型類型時,你可以定義你自己的類型約束,當然,這些約束要支持泛型編程的強力特徵中的多數。抽像概念如`可哈希`具有的類型特徵是根據它們概念特徵來界定的,而不是它們的直接類型特徵。 + +### 類型約束語法 + +你可以寫一個在一個類型參數名後面的類型約束,通過冒號分割,來作為類型參數鏈的一部分。這種作用於泛型函數的類型約束的基礎語法如下所示(和泛型類型的語法相同): + +```swift +func someFunction(someT: T, someU: U) { + // function body goes here +} +``` + +上面這個假定函數有兩個類型參數。第一個類型參數`T`,有一個需要`T`必須是`SomeClass`子類的類型約束;第二個類型參數`U`,有一個需要`U`必須遵循`SomeProtocol`協議的類型約束。 + +### 類型約束行為 + +這裡有個名為`findStringIndex`的非泛型函數,該函數功能是去查找包含一給定`String`值的數組。若查找到匹配的字符串,`findStringIndex`函數返回該字符串在數組中的索引值(`Int`),反之則返回`nil`: + +```swift +func findStringIndex(array: String[], valueToFind: String) -> Int? { + for (index, value) in enumerate(array) { + if value == valueToFind { + return index + } + } + return nil +} +``` + + +`findStringIndex`函數可以作用於查找一字符串數組中的某個字符串: + +```swift +let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] +if let foundIndex = findStringIndex(strings, "llama") { + println("The index of llama is \(foundIndex)") +} +// 輸出 "The index of llama is 2" +``` + +如果只是針對字符串而言查找在數組中的某個值的索引,用處不是很大,不過,你可以寫出相同功能的泛型函數`findIndex`,用某個類型`T`值替換掉提到的字符串。 + +這裡展示如何寫一個你或許期望的`findStringIndex`的泛型版本`findIndex`。請注意這個函數仍然返回`Int`,是不是有點迷惑呢,而不是泛型類型?那是因為函數返回的是一個可選的索引數,而不是從數組中得到的一個可選值。需要提醒的是,這個函數不會編譯,原因在例子後面會說明: + +```swift +func findIndex(array: T[], valueToFind: T) -> Int? { + for (index, value) in enumerate(array) { + if value == valueToFind { + return index + } + } + return nil +} +``` + +上面所寫的函數不會編譯。這個問題的位置在等式的檢查上,`「if value == valueToFind」`。不是所有的 Swift 中的類型都可以用等式符(==)進行比較。例如,如果你創建一個你自己的類或結構體來表示一個複雜的數據模型,那麼 Swift 沒法猜到對於這個類或結構體而言「等於」的意思。正因如此,這部分代碼不能可能保證工作於每個可能的類型`T`,當你試圖編譯這部分代碼時估計會出現相應的錯誤。 + +不過,所有的這些並不會讓我們無從下手。Swift 標準庫中定義了一個`Equatable`協議,該協議要求任何遵循的類型實現等式符(==)和不等符(!=)對任何兩個該類型進行比較。所有的 Swift 標準類型自動支持`Equatable`協議。 + +任何`Equatable`類型都可以安全的使用在`findIndex`函數中,因為其保證支持等式操作。為了說明這個事實,當你定義一個函數時,你可以寫一個`Equatable`類型約束作為類型參數定義的一部分: + +```swift +func findIndex(array: T[], valueToFind: T) -> Int? { + for (index, value) in enumerate(array) { + if value == valueToFind { + return index + } + } + return nil +} +``` + + +`findIndex`中這個單個類型參數寫做:`T: Equatable`,也就意味著「任何T類型都遵循`Equatable`協議」。 + +`findIndex`函數現在則可以成功的編譯過,並且作用於任何遵循`Equatable`的類型,如`Double`或`String`: + +```swift +let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3) +// doubleIndex is an optional Int with no value, because 9.3 is not in the array +let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea") +// stringIndex is an optional Int containing a value of 2 +``` + + +##關聯類型(Associated Types) + +當定義一個協議時,有的時候聲明一個或多個關聯類型作為協議定義的一部分是非常有用的。一個關聯類型作為協議的一部分,給定了類型的一個佔位名(或別名)。作用於關聯類型上實際類型在協議被實現前是不需要指定的。關聯類型被指定為`typealias`關鍵字。 + +### 關聯類型行為 + +這裡是一個`Container`協議的例子,定義了一個ItemType關聯類型: + +```swift +protocol Container { + typealias ItemType + mutating func append(item: ItemType) + var count: Int { get } + subscript(i: Int) -> ItemType { get } +} +``` + +`Container`協議定義了三個任何容器必須支持的兼容要求: + +- 必須可能通過`append`方法添加一個新item到容器裡; +- 必須可能通過使用`count`屬性獲取容器裡items的數量,並返回一個`Int`值; +- 必須可能通過容器的`Int`索引值下標可以檢索到每一個item。 + +這個協議沒有指定容器裡item是如何存儲的或何種類型是允許的。這個協議只指定三個任何遵循`Container`類型所必須支持的功能點。一個遵循的類型在滿足這三個條件的情況下也可以提供其他額外的功能。 + +任何遵循`Container`協議的類型必須指定存儲在其裡面的值類型,必須保證只有正確類型的items可以加進容器裡,必須明確可以通過其下標返回item類型。 + +為了定義這三個條件,`Container`協議需要一個方法指定容器裡的元素將會保留,而不需要知道特定容器的類型。`Container`協議需要指定任何通過`append`方法添加到容器裡的值和容器裡元素是相同類型,並且通過容器下標返回的容器元素類型的值的類型是相同類型。 + +為了達到此目的,`Container`協議聲明了一個ItemType的關聯類型,寫作`typealias ItemType`。這個協議不會定義`ItemType`是什麼的別名,這個信息將由任何遵循協議的類型來提供。儘管如此,`ItemType`別名提供了一種識別Container中Items類型的方法,並且用於`append`方法和`subscript`方法的類型定義,以便保證任何`Container`期望的行為能夠被執行。 + +這裡是一個早前IntStack類型的非泛型版本,遵循Container協議: + +```swift +struct IntStack: Container { + // IntStack的原始實現 + var items = Int[]() + mutating func push(item: Int) { + items.append(item) + } + mutating func pop() -> Int { + return items.removeLast() + } + // 遵循Container協議的實現 + typealias ItemType = Int + mutating func append(item: Int) { + self.push(item) + } + var count: Int { + return items.count + } + subscript(i: Int) -> Int { + return items[i] + } +} +``` + + +`IntStack`類型實現了`Container`協議的所有三個要求,在`IntStack`類型的每個包含部分的功能都滿足這些要求。 + +此外,`IntStack`指定了`Container`的實現,適用的ItemType被用作`Int`類型。對於這個`Container`協議實現而言,定義 `typealias ItemType = Int`,將抽像的`ItemType`類型轉換為具體的`Int`類型。 + +感謝Swift類型參考,你不用在`IntStack`定義部分聲明一個具體的`Int`的`ItemType`。由於`IntStack`遵循`Container`協議的所有要求,只要通過簡單的查找`append`方法的item參數類型和下標返回的類型,Swift就可以推斷出合適的`ItemType`來使用。確實,如果上面的代碼中你刪除了 `typealias ItemType = Int`這一行,一切仍舊可以工作,因為它清楚的知道ItemType使用的是何種類型。 + +你也可以生成遵循`Container`協議的泛型`Stack`類型: + +```swift +struct Stack: Container { + // original Stack implementation + var items = T[]() + mutating func push(item: T) { + items.append(item) + } + mutating func pop() -> T { + return items.removeLast() + } + // conformance to the Container protocol + mutating func append(item: T) { + self.push(item) + } + var count: Int { + return items.count + } + subscript(i: Int) -> T { + return items[i] + } +} +``` + +這個時候,佔位類型參數`T`被用作`append`方法的item參數和下標的返回類型。Swift 因此可以推斷出被用作這個特定容器的`ItemType`的`T`的合適類型。 + + +### 擴展一個存在的類型為一指定關聯類型 + +在[使用擴展來添加協議兼容性](../chapter2/21_Protocols.html)中有描述擴展一個存在的類型添加遵循一個協議。這個類型包含一個關聯類型的協議。 + +Swift的`Array`已經提供`append`方法,一個`count`屬性和通過下標來查找一個自己的元素。這三個功能都達到`Container`協議的要求。也就意味著你可以擴展`Array`去遵循`Container`協議,只要通過簡單聲明`Array`適用於該協議而已。如何實踐這樣一個空擴展,在[使用擴展來聲明協議的採納](../chapter2/21_Protocols.html)中有描述這樣一個實現一個空擴展的行為: + +```swift +extension Array: Container {} +``` + +如同上面的泛型`Stack`類型一樣,`Array的append`方法和下標保證`Swift`可以推斷出`ItemType`所使用的適用的類型。定義了這個擴展後,你可以將任何`Array`當作`Container`來使用。 + + +## Where 語句 + +[類型約束](#type_constraints)能夠確保類型符合泛型函數或類的定義約束。 + +對關聯類型定義約束是非常有用的。你可以在參數列表中通過*where*語句定義參數的約束。一個`where`語句能夠使一個關聯類型遵循一個特定的協議,以及(或)那個特定的類型參數和關聯類型可以是相同的。你可以寫一個`where`語句,緊跟在在類型參數列表後面,where語句後跟一個或者多個針對關聯類型的約束,以及(或)一個或多個類型和關聯類型間的等價(equality)關係。 + +下面的例子定義了一個名為`allItemsMatch`的泛型函數,用來檢查兩個`Container`實例是否包含相同順序的相同元素。如果所有的元素能夠匹配,那麼返回一個為`true`的`Boolean`值,反之則為`false`。 + +被檢查的兩個`Container`可以不是相同類型的容器(雖然它們可以是),但它們確實擁有相同類型的元素。這個需求通過一個類型約束和`where`語句結合來表示: + +```swift +func allItemsMatch< + C1: Container, C2: Container + where C1.ItemType == C2.ItemType, C1.ItemType: Equatable> + (someContainer: C1, anotherContainer: C2) -> Bool { + + // 檢查兩個Container的元素個數是否相同 + if someContainer.count != anotherContainer.count { + return false + } + + // 檢查兩個Container相應位置的元素彼此是否相等 + for i in 0..someContainer.count { + if someContainer[i] != anotherContainer[i] { + return false + } + } + + // 如果所有元素檢查都相同則返回true + return true + +} +``` + + +這個函數用了兩個參數:`someContainer`和`anotherContainer`。`someContainer`參數是類型`C1`,`anotherContainer`參數是類型`C2`。`C1`和`C2`是容器的兩個佔位類型參數,決定了這個函數何時被調用。 + +這個函數的類型參數列緊隨在兩個類型參數需求的後面: + +- `C1`必須遵循`Container`協議 (寫作 `C1: Container`)。 +- `C2`必須遵循`Container`協議 (寫作 `C2: Container`)。 +- `C1`的`ItemType`同樣是C2的`ItemType`(寫作 `C1.ItemType == C2.ItemType`)。 +- `C1`的`ItemType`必須遵循`Equatable`協議 (寫作 `C1.ItemType: Equatable`)。 + +第三個和第四個要求被定義為一個`where`語句的一部分,寫在關鍵字`where`後面,作為函數類型參數鏈的一部分。 + +這些要求意思是: + +`someContainer`是一個`C1`類型的容器。 +`anotherContainer`是一個`C2`類型的容器。 +`someContainer`和`anotherContainer`包含相同的元素類型。 +`someContainer`中的元素可以通過不等於操作(`!=`)來檢查它們是否彼此不同。 + +第三個和第四個要求結合起來的意思是`anotherContainer`中的元素也可以通過 `!=` 操作來檢查,因為它們在`someContainer`中元素確實是相同的類型。 + +這些要求能夠使`allItemsMatch`函數比較兩個容器,即便它們是不同的容器類型。 + +`allItemsMatch`首先檢查兩個容器是否擁有同樣數目的items,如果它們的元素數目不同,沒有辦法進行匹配,函數就會`false`。 + +檢查完之後,函數通過`for-in`循環和半閉區間操作(..)來迭代`someContainer`中的所有元素。對於每個元素,函數檢查是否`someContainer`中的元素不等於對應的`anotherContainer`中的元素,如果這兩個元素不等,則這兩個容器不匹配,返回`false`。 + +如果循環體結束後未發現沒有任何的不匹配,那表明兩個容器匹配,函數返回`true`。 + +這裡演示了allItemsMatch函數運算的過程: + +```swift +var stackOfStrings = Stack() +stackOfStrings.push("uno") +stackOfStrings.push("dos") +stackOfStrings.push("tres") + +var arrayOfStrings = ["uno", "dos", "tres"] + +if allItemsMatch(stackOfStrings, arrayOfStrings) { + println("All items match.") +} else { + println("Not all items match.") +} +// 輸出 "All items match." +``` + + 上面的例子創建一個`Stack`單例來存儲`String`,然後壓了三個字符串進棧。這個例子也創建了一個`Array`單例,並初始化包含三個同棧裡一樣的原始字符串。即便棧和數組是不同的類型,但它們都遵循`Container`協議,而且它們都包含同樣的類型值。因此你可以調用`allItemsMatch`函數,用這兩個容器作為它的參數。在上面的例子中,`allItemsMatch`函數正確的顯示了所有的這兩個容器的`items`匹配。 + + [1]: ../chapter2/06_Functions.html + [2]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png + [3]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png + [4]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png + [5]: ../chapter2/04_Collection_Types.html + [6]: ../chapter2/21_Protocols.html + [7]: ../chapter2/21_Protocols.html + [8]: #type_constraints diff --git a/source-tw/chapter2/23_Advanced_Operators.md b/source-tw/chapter2/23_Advanced_Operators.md new file mode 100644 index 00000000..586dc8e5 --- /dev/null +++ b/source-tw/chapter2/23_Advanced_Operators.md @@ -0,0 +1,484 @@ +> 翻譯:[xielingwang](https://github.com/xielingwang) +> 校對:[numbbbbb](https://github.com/numbbbbb) + +# 高級運算符 +----------------- + +本頁內容包括: + +- [位運算符](#bitwise_operators) +- [溢出運算符](#overflow_operators) +- [優先級和結合性(Precedence and Associativity)](#precedence_and_associativity) +- [運算符函數(Operator Functions)](#operator_functions) +- [自定義運算符](#custom_operators) + +除了[基本操作符](02_Basic_Operators.html)中所講的運算符,Swift還有許多複雜的高級運算符,包括了C語言和Objective-C中的位運算符和移位運算。 + +不同於C語言中的數值計算,Swift的數值計算默認是不可溢出的。溢出行為會被捕獲並報告為錯誤。你是故意的?好吧,你可以使用Swift為你準備的另一套默認允許溢出的數值運算符,如可溢出的加號為`&+`。所有允許溢出的運算符都是以`&`開始的。 + +自定義的結構,類和枚舉,是否可以使用標準的運算符來定義操作?當然可以!在Swift中,你可以為你創建的所有類型定制運算符的操作。 + +可定制的運算符並不限於那些預設的運算符,你可以自定義中置,前置,後置及賦值運算符,當然還有優先級和結合性。這些運算符在代碼中可以像預設的運算符一樣使用,你也可以擴展已有的類型以支持你自定義的運算符。 + + +## 位運算符 + +位操作符可以操作數據結構中原始數據的每個比特位。位操作符通常在諸如圖像處理和創建設備驅動等底層開發中使用,位操作符在同外部資源的數據進行交互的時候也很有用,比如在使用用戶協議進行通信的時候,運用位運算符來對原始數據進行編碼和解碼。 + +Swift支持如下所有C語言的位運算符: + +### 按位取反運算符 + +按位取反運算符`~`對一個操作數的每一位都取反。 + +![Art/bitwiseNOT_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseNOT_2x.png "Art/bitwiseNOT_2x.png") + +這個運算符是前置的,所以請不加任何空格地寫在操作數之前。 + +```swift +let initialBits: UInt8 = 0b00001111 +let invertedBits = ~initialBits // 等於 0b11110000 +``` + +`UInt8`是8位無符整型,可以存儲0~255之間的任意數。這個例子初始化一個整型為二進制值`00001111`(前4位為`0`,後4位為`1`),它的十進制值為`15`。 + +使用按位取反運算`~`對`initialBits`操作,然後賦值給`invertedBits`這個新常量。這個新常量的值等於所有位都取反的`initialBits`,即`1`變成`0`,`0`變成`1`,變成了`11110000`,十進制值為`240`。 + +### 按位與運算符 + +按位與運算符對兩個數進行操作,然後返回一個新的數,這個數的每個位都需要兩個輸入數的同一位都為1時才為1。 + +![Art/bitwiseAND_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseAND_2x.png "Art/bitwiseAND_2x.png") + +以下代碼,`firstSixBits`和`lastSixBits`中間4個位都為1。對它倆進行按位與運算後,就得到了`00111100`,即十進制的`60`。 + +```swift +let firstSixBits: UInt8 = 0b11111100 +let lastSixBits: UInt8 = 0b00111111 +let middleFourBits = firstSixBits & lastSixBits // 等於 00111100 +``` + +### 按位或運算 + +按位或運算符`|`比較兩個數,然後返回一個新的數,這個數的每一位設置1的條件是兩個輸入數的同一位都不為0(即任意一個為1,或都為1)。 + +![Art/bitwiseOR_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseOR_2x.png "Art/bitwiseOR_2x.png") + +如下代碼,`someBits`和`moreBits`在不同位上有`1`。按位或運行的結果是`11111110`,即十進制的`254`。 + +```swift +let someBits: UInt8 = 0b10110010 +let moreBits: UInt8 = 0b01011110 +let combinedbits = someBits | moreBits // 等於 11111110 +``` + +### 按位異或運算符 + +按位異或運算符`^`比較兩個數,然後返回一個數,這個數的每個位設為`1`的條件是兩個輸入數的同一位不同,如果相同就設為`0`。 + +![Art/bitwiseXOR_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseXOR_2x.png "Art/bitwiseXOR_2x.png") + +以下代碼,`firstBits`和`otherBits`都有一個`1`跟另一個數不同的。所以按位異或的結果是把它這些位置為`1`,其他都置為`0`。 + +```swift +let firstBits: UInt8 = 0b00010100 +let otherBits: UInt8 = 0b00000101 +let outputBits = firstBits ^ otherBits // 等於 00010001 +``` + +### 按位左移/右移運算符 + +左移運算符`<<`和右移運算符`>>`會把一個數的所有比特位按以下定義的規則向左或向右移動指定位數。 + +按位左移和按位右移的效果相當把一個整數乘於或除於一個因子為`2`的整數。向左移動一個整型的比特位相當於把這個數乘於`2`,向右移一位就是除於`2`。 + +#### 無符整型的移位操作 + +對無符整型的移位的效果如下: + +已經存在的比特位向左或向右移動指定的位數。被移出整型存儲邊界的的位數直接拋棄,移動留下的空白位用零`0`來填充。這種方法稱為邏輯移位。 + +以下這張把展示了 `11111111 << 1`(`11111111`向左移1位),和 `11111111 >> 1`(`11111111`向右移1位)。藍色的是被移位的,灰色是被拋棄的,橙色的`0`是被填充進來的。 + +![Art/bitshiftUnsigned_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftUnsigned_2x.png "Art/bitshiftUnsigned_2x.png") + +```swift +let shiftBits: UInt8 = 4 // 即二進制的00000100 +shiftBits << 1 // 00001000 +shiftBits << 2 // 00010000 +shiftBits << 5 // 10000000 +shiftBits << 6 // 00000000 +shiftBits >> 2 // 00000001 +``` + +你可以使用移位操作進行其他數據類型的編碼和解碼。 + +```swift +let pink: UInt32 = 0xCC6699 +let redComponent = (pink & 0xFF0000) >> 16 // redComponent 是 0xCC, 即 204 +let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 是 0x66, 即 102 +let blueComponent = pink & 0x0000FF // blueComponent 是 0x99, 即 153 +``` + +這個例子使用了一個`UInt32`的命名為`pink`的常量來存儲層疊樣式表`CSS`中粉色的顏色值,`CSS`顏色`#CC6699`在Swift用十六進制`0xCC6699`來表示。然後使用按位與(&)和按位右移就可以從這個顏色值中解析出紅(CC),綠(66),藍(99)三個部分。 + +對`0xCC6699`和`0xFF0000`進行按位與`&`操作就可以得到紅色部分。`0xFF0000`中的`0`了遮蓋了`OxCC6699`的第二和第三個字節,這樣`6699`被忽略了,只留下`0xCC0000`。 + +然後,按向右移動16位,即 `>> 16`。十六進制中每兩個字符是8比特位,所以移動16位的結果是把`0xCC0000`變成`0x0000CC`。這和`0xCC`是相等的,就是十進制的`204`。 + +同樣的,綠色部分來自於`0xCC6699`和`0x00FF00`的按位操作得到`0x006600`。然後向右移動8位,得到`0x66`,即十進制的`102`。 + +最後,藍色部分對`0xCC6699`和`0x0000FF`進行按位與運算,得到`0x000099`,無需向右移位了,所以結果就是`0x99`,即十進制的`153`。 + +#### 有符整型的移位操作 + +有符整型的移位操作相對複雜得多,因為正負號也是用二進制位表示的。(這裡舉的例子雖然都是8位的,但它的原理是通用的。) + +有符整型通過第1個比特位(稱為符號位)來表達這個整數是正數還是負數。`0`代表正數,`1`代表負數。 + +其餘的比特位(稱為數值位)存儲其實值。有符正整數和無符正整數在計算機裡的存儲結果是一樣的,下來我們來看`+4`內部的二進制結構。 + +![Art/bitshiftSignedFour_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedFour_2x.png "Art/bitshiftSignedFour_2x.png") + +符號位為`0`,代表正數,另外7比特位二進製表示的實際值就剛好是`4`。 + +負數呢,跟正數不同。負數存儲的是2的n次方減去它的絕對值,n為數值位的位數。一個8比特的數有7個數值位,所以是2的7次方,即128。 + +我們來看`-4`存儲的二進制結構。 + +![Art/bitshiftSignedMinusFour_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedMinusFour_2x.png "Art/bitshiftSignedMinusFour_2x.png") + +現在符號位為`1`,代表負數,7個數值位要表達的二進制值是124,即128 - 4。 + +![Art/bitshiftSignedMinusFourValue_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedMinusFourValue_2x.png "Art/bitshiftSignedMinusFourValue_2x.png") + +負數的編碼方式稱為二進制補碼表示。這種表示方式看起來很奇怪,但它有幾個優點。 + +首先,只需要對全部8個比特位(包括符號)做標準的二進制加法就可以完成 `-1 + -4` 的操作,忽略加法過程產生的超過8個比特位表達的任何信息。 + +![Art/bitshiftSignedAddition_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedAddition_2x.png "Art/bitshiftSignedAddition_2x.png") + +第二,由於使用二進制補碼表示,我們可以和正數一樣對負數進行按位左移右移的,同樣也是左移1位時乘於`2`,右移1位時除於`2`。要達到此目的,對有符整型的右移有一個特別的要求: + +對有符整型按位右移時,不使用0填充空白位,而是根據符號位(正數為`0`,負數為`1`)填充空白位。 + +![Art/bitshiftSigned_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSigned_2x.png "Art/bitshiftSigned_2x.png") + +這就確保了在右移的過程中,有符整型的符號不會發生變化。這稱為算術移位。 + +正因為正數和負數特殊的存儲方式,向右移位使它接近於`0`。移位過程中保持符號會不變,負數在接近`0`的過程中一直是負數。 + + +## 溢出運算符 + +默認情況下,當你往一個整型常量或變量賦於一個它不能承載的大數時,Swift不會讓你這麼幹的,它會報錯。這樣,在操作過大或過小的數的時候就很安全了。 + +例如,`Int16`整型能承載的整數範圍是`-32768`到`32767`,如果給它賦上超過這個範圍的數,就會報錯: + +```swift +var potentialOverflow = Int16.max +// potentialOverflow 等於 32767, 這是 Int16 能承載的最大整數 +potentialOverflow += 1 +// 噢, 出錯了 +``` + +對過大或過小的數值進行錯誤處理讓你的數值邊界條件更靈活。 + +當然,你有意在溢出時對有效位進行截斷,你可採用溢出運算,而非錯誤處理。Swfit為整型計算提供了5個`&`符號開頭的溢出運算符。 + +- 溢出加法 `&+` +- 溢出減法 `&-` +- 溢出乘法 `&*` +- 溢出除法 `&/` +- 溢出求余 `&%` + +### 值的上溢出 + +下面例子使用了溢出加法`&+`來解剖的無符整數的上溢出 + +```swift +var willOverflow = UInt8.max +// willOverflow 等於UInt8的最大整數 255 +willOverflow = willOverflow &+ 1 +// 此時 willOverflow 等於 0 +``` + +`willOverflow`用`Int8`所能承載的最大值`255`(二進制`11111111`),然後用`&+`加1。然後`UInt8`就無法表達這個新值的二進制了,也就導致了這個新值上溢出了,大家可以看下圖。溢出後,新值在`UInt8`的承載範圍內的那部分是`00000000`,也就是`0`。 + +![Art/overflowAddition_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowAddition_2x.png "Art/overflowAddition_2x.png") + +### 值的下溢出 + +數值也有可能因為太小而越界。舉個例子: + +`UInt8`的最小值是`0`(二進制為`00000000`)。使用`&-`進行溢出減1,就會得到二進制的`11111111`即十進制的`255`。 + +![Art/overflowUnsignedSubtraction_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowUnsignedSubtraction_2x.png "Art/overflowUnsignedSubtraction_2x.png") + +Swift代碼是這樣的: + +```swift +var willUnderflow = UInt8.min +// willUnderflow 等於UInt8的最小值0 +willUnderflow = willUnderflow &- 1 +// 此時 willUnderflow 等於 255 +``` + +有符整型也有類似的下溢出,有符整型所有的減法也都是對包括在符號位在內的二進制數進行二進制減法的,這在 "按位左移/右移運算符" 一節提到過。最小的有符整數是`-128`,即二進制的`10000000`。用溢出減法減去去1後,變成了`01111111`,即UInt8所能承載的最大整數`127`。 + +![Art/overflowSignedSubtraction_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowSignedSubtraction_2x.png "Art/overflowSignedSubtraction_2x.png") + +來看看Swift代碼: + +```swift +var signedUnderflow = Int8.min +// signedUnderflow 等於最小的有符整數 -128 +signedUnderflow = signedUnderflow &- 1 +// 此時 signedUnderflow 等於 127 +``` + +### 除零溢出 + +一個數除於0 `i / 0`,或者對0求餘數 `i % 0`,就會產生一個錯誤。 + +```swift +let x = 1 +let y = x / 0 +``` + +使用它們對應的可溢出的版本的運算符`&/`和`&%`進行除0操作時就會得到`0`值。 + +```swift +let x = 1 +let y = x &/ 0 +// y 等於 0 +``` + + +## 優先級和結合性 + +運算符的優先級使得一些運算符優先於其他運算符,高優先級的運算符會先被計算。 + +結合性定義相同優先級的運算符在一起時是怎麼組合或關聯的,是和左邊的一組呢,還是和右邊的一組。意思就是,到底是和左邊的表達式結合呢,還是和右邊的表達式結合? + +在混合表達式中,運算符的優先級和結合性是非常重要的。舉個例子,為什麼下列表達式的結果為`4`? + +```swift +2 + 3 * 4 % 5 +// 結果是 4 +``` + +如果嚴格地從左計算到右,計算過程會是這樣: + + +- 2 + 3 = 5 +- 5 * 4 = 20 +- 20 / 5 = 4 余 0 + +但是正確答案是`4`而不是`0`。優先級高的運算符要先計算,在Swift和C語言中,都是先乘除後加減的。所以,執行完乘法和求余運算才能執行加減運算。 + +乘法和求余擁有相同的優先級,在運算過程中,我們還需要結合性,乘法和求余運算都是左結合的。這相當於在表達式中有隱藏的括號讓運算從左開始。 + +```swift +2 + ((3 * 4) % 5) +``` + +3 * 4 = 12,所以這相當於: + + +```swift +2 + (12 % 5) +``` + +12 % 5 = 2,所這又相當於 + +```swift +2 + 2 +``` + +計算結果為 4。 + +查閱Swift運算符的優先級和結合性的完整列表,請看[表達式](../chapter3/04_Expressions.html)。 + +> 注意: +Swift的運算符較C語言和Objective-C來得更簡單和保守,這意味著跟基於C的語言可能不一樣。所以,在移植已有代碼到Swift時,注意去確保代碼按你想的那樣去執行。 + + +## 運算符函數 + +讓已有的運算符也可以對自定義的類和結構進行運算,這稱為運算符重載。 + +這個例子展示了如何用`+`讓一個自定義的結構做加法。算術運算符`+`是一個兩目運算符,因為它有兩個操作數,而且它必須出現在兩個操作數之間。 + +例子中定義了一個名為`Vector2D`的二維坐標向量 `(x,y)` 的結構,然後定義了讓兩個`Vector2D`的對象相加的運算符函數。 + +```swift +struct Vector2D { + var x = 0.0, y = 0.0 +} +@infix func + (left: Vector2D, right: Vector2D) -> Vector2D { + return Vector2D(x: left.x + right.x, y: left.y + right.y) +} +``` + +該運算符函數定義了一個全局的`+`函數,這個函數需要兩個`Vector2D`類型的參數,返回值也是`Vector2D`類型。需要定義和實現一個中置運算的時候,在關鍵字`func`之前寫上屬性 `@infix` 就可以了。 + +在這個代碼實現中,參數被命名為了`left`和`right`,代表`+`左邊和右邊的兩個`Vector2D`對象。函數返回了一個新的`Vector2D`的對象,這個對象的`x`和`y`分別等於兩個參數對象的`x`和`y`的和。 + +這個函數是全局的,而不是`Vector2D`結構的成員方法,所以任意兩個`Vector2D`對象都可以使用這個中置運算符。 + +```swift +let vector = Vector2D(x: 3.0, y: 1.0) +let anotherVector = Vector2D(x: 2.0, y: 4.0) +let combinedVector = vector + anotherVector +// combinedVector 是一個新的Vector2D, 值為 (5.0, 5.0) +``` + +這個例子實現兩個向量 `(3.0,1.0)` 和 `(2.0,4.0)` 相加,得到向量 `(5.0,5.0)` 的過程。如下圖示: + +![Art/vectorAddition_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/vectorAddition_2x.png "Art/vectorAddition_2x.png") + +### 前置和後置運算符 + +上個例子演示了一個雙目中置運算符的自定義實現,同樣我們也可以玩標準單目運算符的實現。單目運算符只有一個操作數,在操作數之前就是前置的,如`-a`; 在操作數之後就是後置的,如`i++`。 + +實現一個前置或後置運算符時,在定義該運算符的時候於關鍵字`func`之前標注 `@prefix` 或 `@postfix` 屬性。 + +```swift +@prefix func - (vector: Vector2D) -> Vector2D { + return Vector2D(x: -vector.x, y: -vector.y) +} +``` + +這段代碼為`Vector2D`類型提供了單目減運算`-a`,`@prefix`屬性表明這是個前置運算符。 + +對於數值,單目減運算符可以把正數變負數,把負數變正數。對於`Vector2D`,單目減運算將其`x`和`y`都進進行單目減運算。 + +```swift +let positive = Vector2D(x: 3.0, y: 4.0) +let negative = -positive +// negative 為 (-3.0, -4.0) +let alsoPositive = -negative +// alsoPositive 為 (3.0, 4.0) +``` + +### 組合賦值運算符 + +組合賦值是其他運算符和賦值運算符一起執行的運算。如`+=`把加運算和賦值運算組合成一個操作。實現一個組合賦值符號需要使用`@assignment`屬性,還需要把運算符的左參數設置成`inout`,因為這個參數會在運算符函數內直接修改它的值。 + +```swift +@assignment func += (inout left: Vector2D, right: Vector2D) { + left = left + right +} +``` + +因為加法運算在之前定義過了,這裡無需重新定義。所以,加賦運算符函數使用已經存在的高級加法運算符函數來執行左值加右值的運算。 + +```swift +var original = Vector2D(x: 1.0, y: 2.0) +let vectorToAdd = Vector2D(x: 3.0, y: 4.0) +original += vectorToAdd +// original 現在為 (4.0, 6.0) +``` + +你可以將 `@assignment` 屬性和 `@prefix` 或 `@postfix` 屬性起來組合,實現一個`Vector2D`的前置運算符。 + +```swift +@prefix @assignment func ++ (inout vector: Vector2D) -> Vector2D { + vector += Vector2D(x: 1.0, y: 1.0) + return vector +} +``` + +這個前置使用了已經定義好的高級加賦運算,將自己加上一個值為 `(1.0,1.0)` 的對象然後賦給自己,然後再將自己返回。 + +```swift +var toIncrement = Vector2D(x: 3.0, y: 4.0) +let afterIncrement = ++toIncrement +// toIncrement 現在是 (4.0, 5.0) +// afterIncrement 現在也是 (4.0, 5.0) +``` + +>注意: +默認的賦值符(=)是不可重載的。只有組合賦值符可以重載。三目條件運算符 `a?b:c` 也是不可重載。 + +### 比較運算符 + +Swift無所知道自定義類型是否相等或不等,因為等於或者不等於由你的代碼說了算了。所以自定義的類和結構要使用比較符`==`或`!=`就需要重載。 + +定義相等運算符函數跟定義其他中置運算符雷同: + +```swift +@infix func == (left: Vector2D, right: Vector2D) -> Bool { + return (left.x == right.x) && (left.y == right.y) +} + +@infix func != (left: Vector2D, right: Vector2D) -> Bool { + return !(left == right) +} +``` + +上述代碼實現了相等運算符`==`來判斷兩個`Vector2D`對象是否有相等的值,相等的概念就是它們有相同的`x`值和相同的`y`值,我們就用這個邏輯來實現。接著使用`==`的結果實現了不相等運算符`!=`。 + +現在我們可以使用這兩個運算符來判斷兩個`Vector2D`對象是否相等。 + +```swift +let twoThree = Vector2D(x: 2.0, y: 3.0) +let anotherTwoThree = Vector2D(x: 2.0, y: 3.0) +if twoThree == anotherTwoThree { + println("這兩個向量是相等的.") +} +// prints "這兩個向量是相等的." +``` + +### 自定義運算符 + +標準的運算符不夠玩,那你可以聲明一些個性的運算符,但個性的運算符只能使用這些字符 `/ = - + * % < >!& | ^。~`。 + +新的運算符聲明需在全局域使用`operator`關鍵字聲明,可以聲明為前置,中置或後置的。 + +```swift +operator prefix +++ {} +``` + + +這段代碼定義了一個新的前置運算符叫`+++`,此前Swift並不存在這個運算符。此處為了演示,我們讓`+++`對`Vector2D`對象的操作定義為 `雙自增` 這樣一個獨有的操作,這個操作使用了之前定義的加賦運算實現了自已加上自己然後返回的運算。 + +```swift +@prefix @assignment func +++ (inout vector: Vector2D) -> Vector2D { + vector += vector + return vector +} +``` + +`Vector2D` 的 `+++` 的實現和 `++` 的實現很接近, 唯一不同的是前者是加自己, 後者是加值為 `(1.0, 1.0)` 的向量. + +```swift +var toBeDoubled = Vector2D(x: 1.0, y: 4.0) +let afterDoubling = +++toBeDoubled +// toBeDoubled 現在是 (2.0, 8.0) +// afterDoubling 現在也是 (2.0, 8.0) +``` + +### 自定義中置運算符的優先級和結合性 + +可以為自定義的中置運算符指定優先級和結合性。可以回頭看看[優先級和結合性](#PrecedenceandAssociativity)解釋這兩個因素是如何影響多種中置運算符混合的表達式的計算的。 + +結合性(associativity)的值可取的值有`left`,`right`和`none`。左結合運算符跟其他優先級相同的左結合運算符寫在一起時,會跟左邊的操作數結合。同理,右結合運算符會跟右邊的操作數結合。而非結合運算符不能跟其他相同優先級的運算符寫在一起。 + +結合性(associativity)的值默認為`none`,優先級(precedence)默認為`100`。 + +以下例子定義了一個新的中置符`+-`,是左結合的`left`,優先級為`140`。 + +```swift +operator infix +- { associativity left precedence 140 } +func +- (left: Vector2D, right: Vector2D) -> Vector2D { + return Vector2D(x: left.x + right.x, y: left.y - right.y) +} +let firstVector = Vector2D(x: 1.0, y: 2.0) +let secondVector = Vector2D(x: 3.0, y: 4.0) +let plusMinusVector = firstVector +- secondVector +// plusMinusVector 此時的值為 (4.0, -2.0) +``` + +這個運算符把兩個向量的`x`相加,把向量的`y`相減。因為他實際是屬於加減運算,所以讓它保持了和加法一樣的結合性和優先級(`left`和`140`)。查閱完整的Swift默認結合性和優先級的設置,請移步[表達式](../chapter3/04_Expressions.html); diff --git a/source-tw/chapter2/chapter2.md b/source-tw/chapter2/chapter2.md new file mode 100644 index 00000000..ae5e206c --- /dev/null +++ b/source-tw/chapter2/chapter2.md @@ -0,0 +1,3 @@ +# Swift 教程 + +本章介紹了 Swift 的各種特性及其使用方法,是全書的核心部分。 diff --git a/source-tw/chapter3/01_About_the_Language_Reference.md b/source-tw/chapter3/01_About_the_Language_Reference.md new file mode 100644 index 00000000..abd207c7 --- /dev/null +++ b/source-tw/chapter3/01_About_the_Language_Reference.md @@ -0,0 +1,38 @@ +> 翻譯:[dabing1022](https://github.com/dabing1022) +> 校對:[numbbbbb](https://github.com/numbbbbb) + + +# 關於語言附註 +----------------- + +本頁內容包括: + +- [如何閱讀語法](#how_to_read_the_grammar) + +本書的這一節描述了Swift編程語言的形式語法。這裡描述的語法是為了幫助您更詳細的瞭解該語言,而不是讓您直接實現一個解析器或編譯器。 + + +Swift語言相對小點,這是由於在Swift代碼中幾乎無處不在的許多常見的的類型,函數以及運算符都由Swift標準庫來定義。雖然這些類型,函數和運算符不是Swift語言本身的一部分,但是它們被廣泛用於這本書的討論和代碼範例。 + + +## 如何閱讀語法 + +用來描述Swift編程語言形式語法的記法遵循下面幾個約定: + +-](https://github.com/numbbbbb)箭頭(→)用來標記語法產式,可以被理](https://github.com/numbbbbb)解為「可以包含」。 +- 句法範疇由*斜體*文字表示,並出現在一個語法產式規則兩側。 +- 義詞和標點符號由粗體固定寬度的文本顯示和只出現在一個語法產式規則的右邊。 +- 選擇性的語法產式由豎線(|)分隔。當可選用的語法產式太多時,為了閱讀方便,它們將被拆分為多行語法產式規則。 +- 在少數情況下,常規字體文字用來描述語法產式規則的右邊。 +- 可選的句法範疇和文字用尾標`opt`來標記。 + +舉個例子,getter-setter的語法塊的定義如下: + +> GRAMMAR OF A GETTER-SETTER BLOCK +> *getter-setter-block* → {- [*getter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/getter-clause) [-*setter-clause*-](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/setter-clause)*opt* -}- | {- [*setter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/setter-clause) [-*getter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/getter-clause)-}- + +這個定義表明,一個getter-setter方法□□塊可以由一個getter子句後跟一個可選的setter子句構成,用大括號括起來,或者由一個setter子句後跟一個getter子句構成,用大括號括起來。上述的文法產生等價於下面的兩個產生,明確闡明如何二中擇一: + +> GRAMMAR OF A GETTER-SETTER BLOCK +> getter-setter-block → {- [*getter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/getter-clause) [*-setter-clause*-](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/setter-clause)*opt* -}-- +> getter-setter-block → {- [*setter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/setter-clause) [*-getter-clause*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/getter-clause)-}- diff --git a/source-tw/chapter3/02_Lexical_Structure.md b/source-tw/chapter3/02_Lexical_Structure.md new file mode 100644 index 00000000..1deb94ef --- /dev/null +++ b/source-tw/chapter3/02_Lexical_Structure.md @@ -0,0 +1,238 @@ +> 翻譯:[superkam](https://github.com/superkam) +> 校對:[numbbbbb](https://github.com/numbbbbb) + +# 詞法結構 +----------------- + +本頁包含內容: + +- [空白與註釋(*Whitespace and Comments*)](#whitespace_and_comments) +- [標識符(*Identifiers*)](#identifiers) +- [關鍵字(*Keywords*)](#keywords) +- [字面量(*Literals*)](#literals) +- [運算符(*Operators*)](#operators) + +Swift 的「詞法結構(*lexical structure*)」描述了如何在該語言中用字符序列構建合法標記,組成該語言中最底層的代碼塊,並在之後的章節中用於描述語言的其他部分。 + +通常,標記在隨後介紹的語法約束下,由 Swift 源文件的輸入文本中提取可能的最長子串生成。這種方法稱為「最長匹配項(*longest match*)」,或者「最大適合」(*maximal munch*)。 + + +## 空白與註釋 + +空白(*whitespace*)有兩個用途:分隔源文件中的標記和區分運算符屬於前綴還是後綴,(參見 [運算符](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_871))在其他情況下則會被忽略。以下的字符會被當作空白:空格(*space*)(U+0020)、換行符(*line feed*)(U+000A)、回車符(*carriage return*)(U+000D)、水平 tab(*horizontal tab*)(U+0009)、垂直 tab(*vertical tab*)(U+000B)、換頁符(*form feed*)(U+000C)以及空(*null*)(U+0000)。 + +註釋(*comments*)被編譯器當作空白處理。單行註釋由 `//` 開始直到該行結束。多行註釋由 `/*` 開始,以 `*/` 結束。可以嵌套註釋,但注意註釋標記必須匹配。 + + +## 標識符 + +標識符(*identifiers*)可以由以下的字符開始:大寫或小寫的字母 `A` 到 `Z`、下劃線 `_`、基本多語言面(*Basic Multilingual Plane*)中的 Unicode 非組合字符以及基本多語言面以外的非專用區(*Private Use Area*)字符。首字符之後,標識符允許使用數字和 Unicode 字符組合。 + +使用保留字(*reserved word*)作為標識符,需要在其前後增加反引號 \`。例如,class 不是合法的標識符,但可以使用 \`class\`。反引號不屬於標識符的一部分,\`x\` 和 `x` 表示同一標識符。 + +閉包(*closure*)中如果沒有明確指定參數名稱,參數將被隱式命名為 $0$1$2... 這些命名在閉包作用域內是合法的標識符。 + +> 標識符語法 +> *標識符* → [*標識符頭(Head)*](LexicalStructure.html#identifier_head) [*標識符字符列表*](LexicalStructure.html#identifier_characters) _可選_ +> *標識符* → **`** [*標識符頭(Head)*](LexicalStructure.html#identifier_head) [*標識符字符列表*](LexicalStructure.html#identifier_characters) _可選_ **`** +> *標識符* → [*隱式參數名*](LexicalStructure.html#implicit_parameter_name) +> *標識符列表* → [*標識符*](LexicalStructure.html#identifier) | [*標識符*](LexicalStructure.html#identifier) **,** [*標識符列表*](LexicalStructure.html#identifier_list) +> *標識符頭(Head)* → Upper- or lowercase letter A through Z +> *標識符頭(Head)* → U+00A8, U+00AA, U+00AD, U+00AF, U+00B2–U+00B5, or U+00B7–U+00BA +> *標識符頭(Head)* → U+00BC–U+00BE, U+00C0–U+00D6, U+00D8–U+00F6, or U+00F8–U+00FF +> *標識符頭(Head)* → U+0100–U+02FF, U+0370–U+167F, U+1681–U+180D, or U+180F–U+1DBF +> *標識符頭(Head)* → U+1E00–U+1FFF +> *標識符頭(Head)* → U+200B–U+200D, U+202A–U+202E, U+203F–U+2040, U+2054, or U+2060–U+206F +> *標識符頭(Head)* → U+2070–U+20CF, U+2100–U+218F, U+2460–U+24FF, or U+2776–U+2793 +> *標識符頭(Head)* → U+2C00–U+2DFF or U+2E80–U+2FFF +> *標識符頭(Head)* → U+3004–U+3007, U+3021–U+302F, U+3031–U+303F, or U+3040–U+D7FF +> *標識符頭(Head)* → U+F900–U+FD3D, U+FD40–U+FDCF, U+FDF0–U+FE1F, or U+FE30–U+FE44 +> *標識符頭(Head)* → U+FE47–U+FFFD +> *標識符頭(Head)* → U+10000–U+1FFFD, U+20000–U+2FFFD, U+30000–U+3FFFD, or U+40000–U+4FFFD +> *標識符頭(Head)* → U+50000–U+5FFFD, U+60000–U+6FFFD, U+70000–U+7FFFD, or U+80000–U+8FFFD +> *標識符頭(Head)* → U+90000–U+9FFFD, U+A0000–U+AFFFD, U+B0000–U+BFFFD, or U+C0000–U+CFFFD +> *標識符頭(Head)* → U+D0000–U+DFFFD or U+E0000–U+EFFFD +> *標識符字符* → 數值 0 到 9 +> *標識符字符* → U+0300–U+036F, U+1DC0–U+1DFF, U+20D0–U+20FF, or U+FE20–U+FE2F +> *標識符字符* → [*標識符頭(Head)*](LexicalStructure.html#identifier_head) +> *標識符字符列表* → [*標識符字符*](LexicalStructure.html#identifier_character) [*標識符字符列表*](LexicalStructure.html#identifier_characters) _可選_ +> *隱式參數名* → **$** [*十進制數字列表*](LexicalStructure.html#decimal_digits) + + +## 關鍵字 + +被保留的關鍵字(*keywords*)不允許用作標識符,除非被反引號轉義,參見 [標識符](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_796)。 + +* **用作聲明的關鍵字:** *class*、*deinit*、*enum*、*extension*、*func*、*import*、*init*、*let*、*protocol*、*static*、*struct*、*subscript*、*typealias*、*var* +* **用作語句的關鍵字:** *break*、*case*、*continue*、*default*、*do*、*else*、*fallthrough*、*if*、*in*、*for*、*return*、*switch*、*where*、*while* +* **用作表達和類型的關鍵字:** *as*、*dynamicType*、*is*、*new*、*super*、*self*、*Self*、*Type*、*\_\_COLUMN\_\_*、*\_\_FILE\_\_*、*\_\_FUNCTION\_\_*、*\_\_LINE\_\_* +* **特定上下文中被保留的關鍵字:** *associativity*、*didSet*、*get*、*infix*、*inout*、*left*、*mutating*、*none*、*nonmutating*、*operator*、*override*、*postfix*、 + *precedence*、*prefix*、*right*、*set*、*unowned*、*unowned(safe)*、*unowned(unsafe)*、*weak*、*willSet*,這些關鍵字在特定上下文之外可以被用於標識符。 + + +## 字面量 + +字面值表示整型、浮點型數字或文本類型的值,舉例如下: + +```swift +42 // 整型字面量 +3.14159 // 浮點型字面量 +"Hello, world!" // 文本型字面量 +``` + +> 字面量語法 +> *字面量* → [*整型字面量*](LexicalStructure.html#integer_literal) | [*浮點數字面量*](LexicalStructure.html#floating_point_literal) | [*字符串字面量*](LexicalStructure.html#string_literal) + +### 整型字面量 + +整型字面量(*integer literals*)表示未指定精度整型數的值。整型字面量默認用十進製表示,可以加前綴來指定其他的進制,二進制字面量加 `0b`,八進制字面量加 `0o`,十六進制字面量加 `0x`。 + +十進制字面量包含數字 `0` 至 `9`。二進制字面量只包含 `0` 或 `1`,八進制字面量包含數字 `0` 至 `7`,十六進制字面量包含數字 `0` 至 `9` 以及字母 `A` 至 `F` (大小寫均可)。 + +負整數的字面量在數字前加減號 `-`,比如 `-42`。 + +允許使用下劃線 `_` 來增加數字的可讀性,下劃線不會影響字面量的值。整型字面量也可以在數字前加 `0`,同樣不會影響字面量的值。 + +```swift +1000_000 // 等於 1000000 +005 // 等於 5 +``` + +除非特殊指定,整型字面量的默認類型為 Swift 標準庫類型中的 `Int`。Swift 標準庫還定義了其他不同長度以及是否帶符號的整數類型,請參考 [整數類型](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_411)。 + +> 整型字面量語法 +> *整型字面量* → [*二進制字面量*](LexicalStructure.html#binary_literal) +> *整型字面量* → [*八進制字面量*](LexicalStructure.html#octal_literal) +> *整型字面量* → [*十進制字面量*](LexicalStructure.html#decimal_literal) +> *整型字面量* → [*十六進制字面量*](LexicalStructure.html#hexadecimal_literal) +> *二進制字面量* → **0b** [*二進制數字*](LexicalStructure.html#binary_digit) [*二進制字面量字符列表*](LexicalStructure.html#binary_literal_characters) _可選_ +> *二進制數字* → 數值 0 到 1 +> *二進制字面量字符* → [*二進制數字*](LexicalStructure.html#binary_digit) | **_** +> *二進制字面量字符列表* → [*二進制字面量字符*](LexicalStructure.html#binary_literal_character) [*二進制字面量字符列表*](LexicalStructure.html#binary_literal_characters) _可選_ +> *八進制字面量* → **0o** [*八進字數字*](LexicalStructure.html#octal_digit) [*八進制字符列表*](LexicalStructure.html#octal_literal_characters) _可選_ +> *八進字數字* → 數值 0 到 7 +> *八進制字符* → [*八進字數字*](LexicalStructure.html#octal_digit) | **_** +> *八進制字符列表* → [*八進制字符*](LexicalStructure.html#octal_literal_character) [*八進制字符列表*](LexicalStructure.html#octal_literal_characters) _可選_ +> *十進制字面量* → [*十進制數字*](LexicalStructure.html#decimal_digit) [*十進制字符列表*](LexicalStructure.html#decimal_literal_characters) _可選_ +> *十進制數字* → 數值 0 到 9 +> *十進制數字列表* → [*十進制數字*](LexicalStructure.html#decimal_digit) [*十進制數字列表*](LexicalStructure.html#decimal_digits) _可選_ +> *十進制字符* → [*十進制數字*](LexicalStructure.html#decimal_digit) | **_** +> *十進制字符列表* → [*十進制字符*](LexicalStructure.html#decimal_literal_character) [*十進制字符列表*](LexicalStructure.html#decimal_literal_characters) _可選_ +> *十六進制字面量* → **0x** [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制字面量字符列表*](LexicalStructure.html#hexadecimal_literal_characters) _可選_ +> *十六進制數字* → 數值 0 到 9, a through f, or A through F +> *十六進制字符* → [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) | **_** +> *十六進制字面量字符列表* → [*十六進制字符*](LexicalStructure.html#hexadecimal_literal_character) [*十六進制字面量字符列表*](LexicalStructure.html#hexadecimal_literal_characters) _可選_ + +### 浮點型字面量 + +浮點型字面量(*floating-point literals*)表示未指定精度浮點數的值。 + +浮點型字面量默認用十進製表示(無前綴),也可以用十六進製表示(加前綴 `0x`)。 + +十進制浮點型字面量(*decimal floating-point literals*)由十進制數字串後跟小數部分或指數部分(或兩者皆有)組成。十進制小數部分由小數點 `.` 後跟十進制數字串組成。指數部分由大寫或小寫字母 `e` 後跟十進制數字串組成,這串數字表示 `e` 之前的數量乘以 10 的幾次方。例如:`1.25e2` 表示 `1.25 □ 10^2`,也就是 `125.0`;同樣,`1.25e-2` 表示 `1.25 □ 10^-2`,也就是 `0.0125`。 + +十六進制浮點型字面量(*hexadecimal floating-point literals*)由前綴 `0x` 後跟可選的十六進制小數部分以及十六進制指數部分組成。十六進制小數部分由小數點後跟十六進制數字串組成。指數部分由大寫或小寫字母 `p` 後跟十進制數字串組成,這串數字表示 `p` 之前的數量乘以 2 的幾次方。例如:`0xFp2` 表示 `15 □ 2^2`,也就是 `60`;同樣,`0xFp-2` 表示 `15 □ 2^-2`,也就是 `3.75`。 + +與整型字面量不同,負的浮點型字面量由一元運算符減號 `-` 和浮點型字面量組成,例如 `-42.0`。這代表一個表達式,而不是一個浮點整型字面量。 + +允許使用下劃線 `_` 來增強可讀性,下劃線不會影響字面量的值。浮點型字面量也可以在數字前加 `0`,同樣不會影響字面量的值。 + +```swift +10_000.56 // 等於 10000.56 +005000.76 // 等於 5000.76 +``` + +除非特殊指定,浮點型字面量的默認類型為 Swift 標準庫類型中的 `Double`,表示64位浮點數。Swift 標準庫也定義 `Float` 類型,表示32位浮點數。 + +> 浮點型字面量語法 +> *浮點數字面量* → [*十進制字面量*](LexicalStructure.html#decimal_literal) [*十進制分數*](LexicalStructure.html#decimal_fraction) _可選_ [*十進制指數*](LexicalStructure.html#decimal_exponent) _可選_ +> *浮點數字面量* → [*十六進制字面量*](LexicalStructure.html#hexadecimal_literal) [*十六進制分數*](LexicalStructure.html#hexadecimal_fraction) _可選_ [*十六進制指數*](LexicalStructure.html#hexadecimal_exponent) +> *十進制分數* → **.** [*十進制字面量*](LexicalStructure.html#decimal_literal) +> *十進制指數* → [*浮點數e*](LexicalStructure.html#floating_point_e) [*正負號*](LexicalStructure.html#sign) _可選_ [*十進制字面量*](LexicalStructure.html#decimal_literal) +> *十六進制分數* → **.** [*十六進制字面量*](LexicalStructure.html#hexadecimal_literal) _可選_ +> *十六進制指數* → [*浮點數p*](LexicalStructure.html#floating_point_p) [*正負號*](LexicalStructure.html#sign) _可選_ [*十六進制字面量*](LexicalStructure.html#hexadecimal_literal) +> *浮點數e* → **e** | **E** +> *浮點數p* → **p** | **P** +> *正負號* → **+** | **-** + +### 文本型字面量 + +文本型字面量(*string literal*)由雙引號中的字符串組成,形式如下: + +```swift +"characters" +``` + +文本型字面量中不能包含未轉義的雙引號 `"`、未轉義的反斜線`\`、回車符(*carriage return*)或換行符(*line feed*)。 + +可以在文本型字面量中使用的轉義特殊符號如下: + +* 空字符(Null Character)`\0` +* 反斜線(Backslash)`\\` +* 水平 Tab (Horizontal Tab)`\t` +* 換行符(Line Feed)`\n` +* 回車符(Carriage Return)`\r` +* 雙引號(Double Quote)`\"` +* 單引號(Single Quote)`\'` + +字符也可以用以下方式表示: + +* `\x` 後跟兩位十六進制數字 +* `\u` 後跟四位十六進制數字 +* `\U` 後跟八位十六進制數字 + +後跟的數字表示一個 Unicode 碼點。 + +文本型字面量允許在反斜線小括號 `\()` 中插入表達式的值。插入表達式(*interpolated expression*)不能包含未轉義的雙引號 `"`、反斜線 `\`、回車符或者換行符。表達式值的類型必須在 *String* 類中有對應的初始化方法。 + +例如,以下所有文本型字面量的值相同: + +```swift +"1 2 3" +"1 2 \(3)" +"1 2 \(1 + 2)" +var x = 3; "1 2 \(x)" +``` + +文本型字面量的默認類型為 `String`。組成字符串的字符類型為 `Character`。更多有關 `String` 和 `Character` 的信息請參照 [字符串和字符](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_368)。 + +> 字符型字面量語法 +> *字符串字面量* → **"** [*引用文本*](LexicalStructure.html#quoted_text) **"** +> *引用文本* → [*引用文本條目*](LexicalStructure.html#quoted_text_item) [*引用文本*](LexicalStructure.html#quoted_text) _可選_ +> *引用文本條目* → [*轉義字符*](LexicalStructure.html#escaped_character) +> *引用文本條目* → **\(** [*表達式*](..\chapter3\04_Expressions.html#expression) **)** +> *引用文本條目* → 除了"-, \-, U+000A, or U+000D的所有Unicode的字符 +> *轉義字符* → **\0** | **\\** | **\t** | **\n** | **\r** | **\"** | **\'** +> *轉義字符* → **\x** [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) +> *轉義字符* → **\u** [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) +> *轉義字符* → **\U** [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) [*十六進制數字*](LexicalStructure.html#hexadecimal_digit) + + +## 運算符 + +Swift 標準庫定義了許多可供使用的運算符,其中大部分在 [基礎運算符](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_70) 和 [高級運算符](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_28) 中進行了闡述。這裡將描述哪些字符能用作運算符。 + +運算符由一個或多個以下字符組成: +`/`、`=`、`-`、`+`、`!`、`*`、`%`、`<`、`>`、`&`、`|`、`^`、`~`、`.`。也就是說,標記 `=`, `->`、`//`、`/*`、`*/`、`.` 以及一元前綴運算符 `&` 屬於保留字,這些標記不能被重寫或用於自定義運算符。 + +運算符兩側的空白被用來區分該運算符是否為前綴運算符(*prefix operator*)、後綴運算符(*postfix operator*)或二元運算符(*binary operator*)。規則總結如下: + +* 如果運算符兩側都有空白或兩側都無空白,將被看作二元運算符。例如:`a+b` 和 `a + b` 中的運算符 `+` 被看作二元運算符。 +* 如果運算符只有左側空白,將被看作前綴一元運算符。例如 `a ++b` 中的 `++` 被看作前綴一元運算符。 +* 如果運算符只有右側空白,將被看作後綴一元運算符。例如 `a++ b` 中的 `++` 被看作後綴一元運算符。 +* 如果運算符左側沒有空白並緊跟 `.`,將被看作後綴一元運算符。例如 `a++.b` 中的 `++` 被看作後綴一元運算符(同理, `a++ . b` 中的 `++` 是後綴一元運算符而 `a ++ .b` 中的 `++` 不是). + +鑒於這些規則,運算符前的字符 `(`、`[` 和 `{` ;運算符後的字符 `)`、`]` 和 `}` 以及字符 `,`、`;` 和 `:` 都將用於空白檢測。 + +以上規則需注意一點,如果運算符 `!` 或 `?` 左側沒有空白,則不管右側是否有空白都將被看作後綴運算符。如果將 `?` 用作可選類型(*optional type*)修飾,左側必須無空白。如果用於條件運算符 `? :`,必須兩側都有空白。 + +在特定構成中 ,以 `<` 或 `>` 開頭的運算符會被分離成兩個或多個標記,剩餘部分以同樣的方式會被再次分離。因此,在 `Dictionary>` 中沒有必要添加空白來消除閉合字符 `>` 的歧義。在這個例子中, 閉合字符 `>` 被看作單字符標記,而不會被誤解為移位運算符 `>>`。 + +要學習如何自定義新的運算符,請參考 [自定義操作符](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_48) 和 [運算符聲明](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_644)。學習如何重寫現有運算符,請參考 [運算符方法](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_43)。 + +> 運算符語法語法 +> *運算符* → [*運算符字符*](LexicalStructure.html#operator_character) [*運算符*](LexicalStructure.html#operator) _可選_ +> *運算符字符* → **/** | **=** | **-** | **+** | **!** | ***** | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **.** +> *二元運算符* → [*運算符*](LexicalStructure.html#operator) +> *前置運算符* → [*運算符*](LexicalStructure.html#operator) +> *後置運算符* → [*運算符*](LexicalStructure.html#operator) diff --git a/source-tw/chapter3/03_Types.md b/source-tw/chapter3/03_Types.md new file mode 100644 index 00000000..778a545e --- /dev/null +++ b/source-tw/chapter3/03_Types.md @@ -0,0 +1,300 @@ +> 翻譯:[lyuka](https://github.com/lyuka) +> 校對:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai) + +# 類型(Types) +----------------- + +本頁包含內容: + +- [類型註解(Type Annotation)](#type_annotation) +- [類型標識符(Type Identifier)](#type_identifier) +- [元組類型(Tuple Type)](#tuple_type) +- [函數類型(Function Type)](#function_type) +- [數組類型(Array Type)](#array_type) +- [可選類型(Optional Type)](#optional_type) +- [隱式解析可選類型(Implicitly Unwrapped Optional Type)](#implicitly_unwrapped_optional_type) +- [協議合成類型(Protocol Composition Type)](#protocol_composition_type) +- [元類型(Metatype Type)](#metatype_type) +- [類型繼承子句(Type Inheritance Clause)](#type_inheritance_clause) +- [類型推斷(Type Inference)](#type_inference) + +Swift 語言存在兩種類型:命名型類型和複合型類型。*命名型類型*是指定義時可以給定名字的類型。命名型類型包括類、結構體、枚舉和協議。比如,一個用戶定義的類`MyClass`的實例擁有類型`MyClass`。除了用戶定義的命名型類型,Swift 標準庫也定義了很多常用的命名型類型,包括那些表示數組、字典和可選值的類型。 + +那些通常被其它語言認為是基本或初級的數據型類型(Data types)——比如表示數字、字符和字符串——實際上就是命名型類型,Swift 標準庫是使用結構體定義和實現它們的。因為它們是命名型類型,因此你可以按照「擴展和擴展聲明」章節裡討論的那樣,聲明一個擴展來增加它們的行為以適應你程序的需求。 + +*複合型類型*是沒有名字的類型,它由 Swift 本身定義。Swift 存在兩種複合型類型:函數類型和元組類型。一個複合型類型可以包含命名型類型和其它複合型類型。例如,元組類型`(Int, (Int, Int))`包含兩個元素:第一個是命名型類型`Int`,第二個是另一個複合型類型`(Int, Int)`. + +本節討論 Swift 語言本身定義的類型,並描述 Swift 中的類型推斷行為。 + +> 類型語法 +> *類型* → [*數組類型*](..\chapter3\03_Types.html#array_type) | [*函數類型*](..\chapter3\03_Types.html#function_type) | [*類型標識*](..\chapter3\03_Types.html#type_identifier) | [*元組類型*](..\chapter3\03_Types.html#tuple_type) | [*可選類型*](..\chapter3\03_Types.html#optional_type) | [*隱式解析可選類型*](..\chapter3\03_Types.html#implicitly_unwrapped_optional_type) | [*協議合成類型*](..\chapter3\03_Types.html#protocol_composition_type) | [*元型類型*](..\chapter3\03_Types.html#metatype_type) + + +##類型註解 + +類型註解顯式地指定一個變量或表達式的值。類型註解始於冒號`:`終於類型,比如下面兩個例子: + +```swift +let someTuple: (Double, Double) = (3.14159, 2.71828) +func someFunction(a: Int){ /* ... */ } +``` +在第一個例子中,表達式`someTuple`的類型被指定為`(Double, Double)`。在第二個例子中,函數`someFunction`的參數`a`的類型被指定為`Int`。 + +類型註解可以在類型之前包含一個類型特性(type attributes)的可選列表。 + +> 類型註解語法 +> *類型註解* → **:** [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*類型*](..\chapter3\03_Types.html#type) + + +##類型標識符 + +類型標識符引用命名型類型或者是命名型/複合型類型的別名。 + +大多數情況下,類型標識符引用的是同名的命名型類型。例如類型標識符`Int`引用命名型類型`Int`,同樣,類型標識符`Dictionary`引用命名型類型`Dictionary`。 + +在兩種情況下類型標識符引用的不是同名的類型。情況一,類型標識符引用的是命名型/複合型類型的類型別名。比如,在下面的例子中,類型標識符使用`Point`來引用元組`(Int, Int)`: + +```swift +typealias Point = (Int, Int) +let origin: Point = (0, 0) +``` + +情況二,類型標識符使用dot(`.`)語法來表示在其它模塊(modules)或其它類型嵌套內聲明的命名型類型。例如,下面例子中的類型標識符引用在`ExampleModule`模塊中聲明的命名型類型`MyType`: + +```swift +var someValue: ExampleModule.MyType +``` + +> 類型標識語法 +> *類型標識* → [*類型名稱*](..\chapter3\03_Types.html#type_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_argument_clause) _可選_ | [*類型名稱*](..\chapter3\03_Types.html#type_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_argument_clause) _可選_ **.** [*類型標識*](..\chapter3\03_Types.html#type_identifier) +> *類名* → [*標識符*](LexicalStructure.html#identifier) + + +##元組類型 + +元組類型使用逗號隔開並使用括號括起來的0個或多個類型組成的列表。 + +你可以使用元組類型作為一個函數的返回類型,這樣就可以使函數返回多個值。你也可以命名元組類型中的元素,然後用這些名字來引用每個元素的值。元素的名字由一個標識符和`:`組成。「函數和多返回值」章節裡有一個展示上述特性的例子。 + +`void`是空元組類型`()`的別名。如果括號內只有一個元素,那麼該類型就是括號內元素的類型。比如,`(Int)`的類型是`Int`而不是`(Int)`。所以,只有當元組類型包含兩個元素以上時才可以標記元組元素。 + +> 元組類型語法 +> *元組類型* → **(** [*元組類型主體*](..\chapter3\03_Types.html#tuple_type_body) _可選_ **)** +> *元組類型主體* → [*元組類型的元素列表*](..\chapter3\03_Types.html#tuple_type_element_list) **...** _可選_ +> *元組類型的元素列表* → [*元組類型的元素*](..\chapter3\03_Types.html#tuple_type_element) | [*元組類型的元素*](..\chapter3\03_Types.html#tuple_type_element) **,** [*元組類型的元素列表*](..\chapter3\03_Types.html#tuple_type_element_list) +> *元組類型的元素* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **inout** _可選_ [*類型*](..\chapter3\03_Types.html#type) | **inout** _可選_ [*元素名*](..\chapter3\03_Types.html#element_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) +> *元素名* → [*標識符*](LexicalStructure.html#identifier) + + +##函數類型 + +函數類型表示一個函數、方法或閉包的類型,它由一個參數類型和返回值類型組成,中間用箭頭`->`隔開: + +- `parameter type` -> `return type` + +由於 *參數類型* 和 *返回值類型* 可以是元組類型,所以函數類型可以讓函數與方法支持多參數與多返回值。 + +你可以對函數類型應用帶有參數類型`()`並返回表達式類型的`auto_closure`屬性(見類型屬性章節)。一個自動閉包涵數捕獲特定表達式上的隱式閉包而非表達式本身。下面的例子使用`auto_closure`屬性來定義一個很簡單的assert函數: + +```swift +func simpleAssert(condition: @auto_closure () -> Bool, message: String){ + if !condition(){ + println(message) + } +} +let testNumber = 5 +simpleAssert(testNumber % 2 == 0, "testNumber isn't an even number.") +// prints "testNumber isn't an even number." +``` +函數類型可以擁有一個可變長參數作為參數類型中的最後一個參數。從語法角度上講,可變長參數由一個基礎類型名字和`...`組成,如`Int...`。可變長參數被認為是一個包含了基礎類型元素的數組。即`Int...`就是`Int[]`。關於使用可變長參數的例子,見章節「可變長參數」。 + +為了指定一個`in-out`參數,可以在參數類型前加`inout`前綴。但是你不可以對可變長參數或返回值類型使用`inout`。關於In-Out參數的討論見章節In-Out參數部分。 + +柯裡化函數(curried function)的類型相當於一個嵌套函數類型。例如,下面的柯裡化函數`addTwoNumber()()`的類型是`Int -> Int -> Int`: + +```swift +func addTwoNumbers(a: Int)(b: Int) -> Int{ + return a + b +} +addTwoNumbers(4)(5) // returns 9 +``` + +柯裡化函數的函數類型從右向左組成一組。例如,函數類型`Int -> Int -> Int`可以被理解為`Int -> (Int -> Int)`——也就是說,一個函數傳入一個`Int`然後輸出作為另一個函數的輸入,然後又返回一個`Int`。例如,你可以使用如下嵌套函數來重寫柯裡化函數`addTwoNumbers()()`: + +```swift +func addTwoNumbers(a: Int) -> (Int -> Int){ + func addTheSecondNumber(b: Int) -> Int{ + return a + b + } + return addTheSecondNumber +} +addTwoNumbers(4)(5) // Returns 9 +``` + +> 函數類型語法 +> *函數類型* → [*類型*](..\chapter3\03_Types.html#type) **->** [*類型*](..\chapter3\03_Types.html#type) + + +##數組類型 + +Swift語言使用類型名緊接中括號`[]`來簡化標準庫中定義的命名型類型`Array`。換句話說,下面兩個聲明是等價的: + +```swift +let someArray: String[] = ["Alex", "Brian", "Dave"] +let someArray: Array = ["Alex", "Brian", "Dave"] +``` +上面兩種情況下,常量`someArray`都被聲明為字符串數組。數組的元素也可以通過`[]`獲取訪問:`someArray[0]`是指第0個元素`「Alex」`。 + +上面的例子同時顯示,你可以使用`[]`作為初始值構造數組,空的`[]`則用來來構造指定類型的空數組。 + +```swift +var emptyArray: Double[] = [] +``` +你也可以使用鏈接起來的多個`[]`集合來構造多維數組。例如,下例使用三個`[]`集合來構造三維整型數組: + +```swift +var array3D: Int[][][] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +``` +訪問一個多維數組的元素時,最左邊的下標指向最外層數組的相應位置元素。接下來往右的下標指向第一層嵌入的相應位置元素,依次類推。這就意味著,在上面的例子中,`array3D[0]`是指`[[1, 2], [3, 4]]`,`array3D[0][1]`是指`[3, 4]`,`array3D[0][1][1]`則是指值`4`。 + +關於Swift標準庫中`Array`類型的細節討論,見章節Arrays。 + +> 數組類型語法 +> *數組類型* → [*類型*](..\chapter3\03_Types.html#type) **[** **]** | [*數組類型*](..\chapter3\03_Types.html#array_type) **[** **]** + + +##可選類型 + +Swift定義後綴`?`來作為標準庫中的定義的命名型類型`Optional`的簡寫。換句話說,下面兩個聲明是等價的: + +```swift +var optionalInteger: Int? +var optionalInteger: Optional +``` +在上述兩種情況下,變量`optionalInteger`都被聲明為可選整型類型。注意在類型和`?`之間沒有空格。 + +類型`Optional`是一個枚舉,有兩種形式,`None`和`Some(T)`,又來代表可能出現或可能不出現的值。任意類型都可以被顯式的聲明(或隱式的轉換)為可選類型。當聲明一個可選類型時,確保使用括號給`?`提供合適的作用範圍。比如說,聲明一個整型的可選數組,應寫作`(Int[])?`,寫成`Int[]?`的話則會出錯。 + +如果你在聲明或定義可選變量或特性的時候沒有提供初始值,它的值則會自動賦成缺省值`nil`。 + +可選符合`LogicValue`協議,因此可以出現在布爾值環境下。此時,如果一個可選類型`T?`實例包含有類型為`T`的值(也就是說值為`Optional.Some(T)`),那麼此可選類型就為`true`,否則為`false`。 + +如果一個可選類型的實例包含一個值,那麼你就可以使用後綴操作符`!`來獲取該值,正如下面描述的: + +```swift +optionalInteger = 42 +optionalInteger! // 42 +``` +使用`!`操作符獲取值為`nil`的可選項會導致運行錯誤(runtime error)。 + +你也可以使用可選鏈和可選綁定來選擇性的執行可選表達式上的操作。如果值為`nil`,不會執行任何操作因此也就沒有運行錯誤產生。 + +更多細節以及更多如何使用可選類型的例子,見章節「可選」。 + +> 可選類型語法 +> *可選類型* → [*類型*](..\chapter3\03_Types.html#type) **?** + + +##隱式解析可選類型 + +Swift語言定義後綴`!`作為標準庫中命名類型`ImplicitlyUnwrappedOptional`的簡寫。換句話說,下面兩個聲明等價: + +```swift +var implicitlyUnwrappedString: String! +var implicitlyUnwrappedString: ImplicitlyUnwrappedOptional +``` +上述兩種情況下,變量`implicitlyUnwrappedString`被聲明為一個隱式解析可選類型的字符串。注意類型與`!`之間沒有空格。 + +你可以在使用可選的地方同樣使用隱式解析可選。比如,你可以將隱式解析可選的值賦給變量、常量和可選特性,反之亦然。 + +有了可選,你在聲明隱式解析可選變量或特性的時候就不用指定初始值,因為它有缺省值`nil`。 + +由於隱式解析可選的值會在使用時自動解析,所以沒必要使用操作符`!`來解析它。也就是說,如果你使用值為`nil`的隱式解析可選,就會導致運行錯誤。 + +使用可選鏈會選擇性的執行隱式解析可選表達式上的某一個操作。如果值為`nil`,就不會執行任何操作,因此也不會產生運行錯誤。 + +關於隱式解析可選的更多細節,見章節「隱式解析可選」。 + +> 隱式解析可選類型(Implicitly Unwrapped Optional Type)語法 +> *隱式解析可選類型* → [*類型*](..\chapter3\03_Types.html#type) **!** + + +##協議合成類型 + +協議合成類型是一種符合每個協議的指定協議列表類型。協議合成類型可能會用在類型註解和泛型參數中。 + +協議合成類型的形式如下: + +```swift +protocol +``` + +協議合成類型允許你指定一個值,其類型可以適配多個協議的條件,而且不需要定義一個新的命名型協議來繼承其它想要適配的各個協議。比如,協議合成類型`protocol`等效於一個從`Protocol A`,`Protocol B`, `Protocol C`繼承而來的新協議`Protocol D`,很顯然這樣做有效率的多,甚至不需引入一個新名字。 + +協議合成列表中的每項必須是協議名或協議合成類型的類型別名。如果列表為空,它就會指定一個空協議合成列表,這樣每個類型都能適配。 + +> 協議合成類型語法 +> *協議合成類型* → **protocol** **<** [*協議標識符列表*](..\chapter3\03_Types.html#protocol_identifier_list) _可選_ **>** +> *協議標識符列表* → [*協議標識符*](..\chapter3\03_Types.html#protocol_identifier) | [*協議標識符*](..\chapter3\03_Types.html#protocol_identifier) **,** [*協議標識符列表*](..\chapter3\03_Types.html#protocol_identifier_list) +> *協議標識符* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) + + +##元類型 + +元類型是指所有類型的類型,包括類、結構體、枚舉和協議。 + +類、結構體或枚舉類型的元類型是相應的類型名緊跟`.Type`。協議類型的元類型——並不是運行時適配該協議的具體類型——是該協議名字緊跟`.Protocol`。比如,類`SomeClass`的元類型就是`SomeClass.Type`,協議`SomeProtocol`的元類型就是`SomeProtocal.Protocol`。 + +你可以使用後綴`self`表達式來獲取類型。比如,`SomeClass.self`返回`SomeClass`本身,而不是`SomeClass`的一個實例。同樣,`SomeProtocol.self`返回`SomeProtocol`本身,而不是運行時適配`SomeProtocol`的某個類型的實例。還可以對類型的實例使用`dynamicType`表達式來獲取該實例在運行階段的類型,如下所示: + +```swift +class SomeBaseClass { + class func printClassName() { + println("SomeBaseClass") + } +} +class SomeSubClass: SomeBaseClass { + override class func printClassName() { + println("SomeSubClass") + } +} +let someInstance: SomeBaseClass = SomeSubClass() +// someInstance is of type SomeBaseClass at compile time, but +// someInstance is of type SomeSubClass at runtime +someInstance.dynamicType.printClassName() +// prints "SomeSubClass +``` + +> 元(Metatype)類型語法 +> *元類型* → [*類型*](..\chapter3\03_Types.html#type) **.** **Type** | [*類型*](..\chapter3\03_Types.html#type) **.** **Protocol** x + + +##類型繼承子句 + +類型繼承子句被用來指定一個命名型類型繼承哪個類且適配哪些協議。類型繼承子句開始於冒號`:`,緊跟由`,`隔開的類型標識符列表。 + +類可以繼承單個超類,適配任意數量的協議。當定義一個類時,超類的名字必須出現在類型標識符列表首位,然後跟上該類需要適配的任意數量的協議。如果一個類不是從其它類繼承而來,那麼列表可以以協議開頭。關於類繼承更多的討論和例子,見章節「繼承」。 + +其它命名型類型可能只繼承或適配一個協議列表。協議類型可能繼承於其它任意數量的協議。當一個協議類型繼承於其它協議時,其它協議的條件集合會被集成在一起,然後其它從當前協議繼承的任意類型必須適配所有這些條件。 + +枚舉定義中的類型繼承子句可以是一個協議列表,或是指定原始值的枚舉,一個單獨的指定原始值類型的命名型類型。使用類型繼承子句來指定原始值類型的枚舉定義的例子,見章節「原始值」。 + +> 類型繼承子句語法 +> *類型繼承子句* → **:** [*類型繼承列表*](..\chapter3\03_Types.html#type_inheritance_list) +> *類型繼承列表* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) | [*類型標識*](..\chapter3\03_Types.html#type_identifier) **,** [*類型繼承列表*](..\chapter3\03_Types.html#type_inheritance_list) + + +##類型推斷 + +Swift廣泛的使用類型推斷,從而允許你可以忽略很多變量和表達式的類型或部分類型。比如,對於`var x: Int = 0`,你可以完全忽略類型而簡寫成`var x = 0`——編譯器會正確的推斷出`x`的類型`Int`。類似的,當完整的類型可以從上下文推斷出來時,你也可以忽略類型的一部分。比如,如果你寫了`let dict: Dictionary = ["A": 1]`,編譯提也能推斷出`dict`的類型是`Dictionary`。 + +在上面的兩個例子中,類型信息從表達式樹(expression tree)的葉子節點傳向根節點。也就是說,`var x: Int = 0`中`x`的類型首先根據`0`的類型進行推斷,然後將該類型信息傳遞到根節點(變量`x`)。 + +在Swift中,類型信息也可以反方向流動——從根節點傳向葉子節點。在下面的例子中,常量`eFloat`上的顯式類型註解(`:Float`)導致數字字面量`2.71828`的類型是`Float`而非`Double`。 + +```swift +let e = 2.71828 // The type of e is inferred to be Double. +let eFloat: Float = 2.71828 // The type of eFloat is Float. +``` + +Swift中的類型推斷在單獨的表達式或語句水平上進行。這意味著所有用於推斷類型的信息必須可以從表達式或其某個子表達式的類型檢查中獲取。 diff --git a/source-tw/chapter3/04_Expressions.md b/source-tw/chapter3/04_Expressions.md new file mode 100644 index 00000000..c368d9d4 --- /dev/null +++ b/source-tw/chapter3/04_Expressions.md @@ -0,0 +1,637 @@ +> 翻譯:[sg552](https://github.com/sg552) +> 校對:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai) + +# 表達式(Expressions) +----------------- + +本頁包含內容: + +- [前綴表達式(Prefix Expressions)](#prefix_expressions) +- [二元表達式(Binary Expressions)](#binary_expressions) +- [賦值表達式(Assignment Operator)](#assignment_operator) +- [三元條件運算符(Ternary Conditional Operator)](#ternary_conditional_operator) +- [類型轉換運算符(Type-Casting Operators)](#type-casting_operators) +- [主要表達式(Primary Expressions)](#primary_expressions) +- [後綴表達式(Postfix Expressions)](#postfix_expressions) + +Swift 中存在四種表達式: 前綴(prefix)表達式,二元(binary)表達式,主要(primary)表達式和後綴(postfix)表達式。表達式可以返回一個值,以及運行某些邏輯(causes a side effect)。 + +前綴表達式和二元表達式就是對某些表達式使用各種運算符(operators)。 主要表達式是最短小的表達式,它提供了獲取(變量的)值的一種途徑。 後綴表達式則允許你建立複雜的表達式,例如配合函數調用和成員訪問。 每種表達式都在下面有詳細論述~ + +> 表達式語法 +> *表達式* → [*前置表達式*](..\chapter3\04_Expressions.html#prefix_expression) [*二元表達式列表*](..\chapter3\04_Expressions.html#binary_expressions) _可選_ +> *表達式列表* → [*表達式*](..\chapter3\04_Expressions.html#expression) | [*表達式*](..\chapter3\04_Expressions.html#expression) **,** [*表達式列表*](..\chapter3\04_Expressions.html#expression_list) + + +## 前綴表達式(Prefix Expressions) + +前綴表達式由 前綴符號和表達式組成。(這個前綴符號只能接收一個參數) + +Swift 標準庫支持如下的前綴操作符: + +- ++ 自增1 (increment) +- -- 自減1 (decrement) +- ! 邏輯否 (Logical NOT ) +- ~ 按位否 (Bitwise NOT ) +- \+ 加(Unary plus) +- \- 減(Unary minus) + +對於這些操作符的使用,請參見: Basic Operators and Advanced Operators + +作為對上面標準庫運算符的補充,你也可以對 某個函數的參數使用 '&'運算符。 更多信息,請參見: "In-Out parameters". + +> 前置表達式語法 +> *前置表達式* → [*前置運算符*](LexicalStructure.html#prefix_operator) _可選_ [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) +> *前置表達式* → [*寫入寫出(in-out)表達式*](..\chapter3\04_Expressions.html#in_out_expression) +> *寫入寫出(in-out)表達式* → **&** [*標識符*](LexicalStructure.html#identifier) + + +## 二元表達式(Binary Expressions) + +二元表達式由 "左邊參數" + "二元運算符" + "右邊參數" 組成, 它有如下的形式: + +> `left-hand argument` `operator` `right-hand argument` + +Swift 標準庫提供了如下的二元運算符: + +- 求冪相關(無結合,優先級160) + - << 按位左移(Bitwise left shift) + - >> 按位右移(Bitwise right shift) +- 乘除法相關(左結合,優先級150) + - \* 乘 + - / 除 + - % 求余 + - &* 乘法,忽略溢出( Multiply, ignoring overflow) + - &/ 除法,忽略溢出(Divide, ignoring overflow) + - &% 求余, 忽略溢出( Remainder, ignoring overflow) + - & 位與( Bitwise AND) +- 加減法相關(左結合, 優先級140) + - \+ 加 + - \- 減 + - &+ Add with overflow + - &- Subtract with overflow + - | 按位或(Bitwise OR ) + - ^ 按位異或(Bitwise XOR) +- Range (無結合,優先級 135) + - .. 半閉值域 Half-closed range + - ... 全閉值域 Closed range +- 類型轉換 (無結合,優先級 132) + - is 類型檢查( type check) + - as 類型轉換( type cast) +- Comparative (無結合,優先級 130) + - < 小於 + - <= 小於等於 + - > 大於 + - >= 大於等於 + - == 等於 + - != 不等 + - === 恆等於 + - !== 不恆等 + - ~= 模式匹配( Pattern match) +- 合取( Conjunctive) (左結合,優先級 120) + - && 邏輯與(Logical AND) +- 析取(Disjunctive) (左結合,優先級 110) + - || 邏輯或( Logical OR) +- 三元條件(Ternary Conditional )(右結合,優先級 100) + - ?: 三元條件 Ternary conditional +- 賦值 (Assignment) (右結合, 優先級 90) + - = 賦值(Assign) + - *= Multiply and assign + - /= Divide and assign + - %= Remainder and assign + - += Add and assign + - -= Subtract and assign + - <<= Left bit shift and assign + - >>= Right bit shift and assign + - &= Bitwise AND and assign + - ^= Bitwise XOR and assign + - |= Bitwise OR and assign + - &&= Logical AND and assign + - ||= Logical OR and assign + +關於這些運算符(operators)的更多信息,請參見:Basic Operators and Advanced Operators. + +> 注意 +> 在解析時, 一個二元表達式表示為一個一級數組(a flat list), 這個數組(List)根據運算符的先後順序,被轉換成了一個tree. 例如: 2 + 3 * 5 首先被認為是: 2, + , `` 3``, *, 5. 隨後它被轉換成 tree (2 + (3 * 5)) + +

+ +> 二元表達式語法 +> *二元表達式* → [*二元運算符*](LexicalStructure.html#binary_operator) [*前置表達式*](..\chapter3\04_Expressions.html#prefix_expression) +> *二元表達式* → [*賦值運算符*](..\chapter3\04_Expressions.html#assignment_operator) [*前置表達式*](..\chapter3\04_Expressions.html#prefix_expression) +> *二元表達式* → [*條件運算符*](..\chapter3\04_Expressions.html#conditional_operator) [*前置表達式*](..\chapter3\04_Expressions.html#prefix_expression) +> *二元表達式* → [*類型轉換運算符*](..\chapter3\04_Expressions.html#type_casting_operator) +> *二元表達式列表* → [*二元表達式*](..\chapter3\04_Expressions.html#binary_expression) [*二元表達式列表*](..\chapter3\04_Expressions.html#binary_expressions) _可選_ + + + +## 賦值表達式(Assignment Operator) + +賦值表達式會對某個給定的表達式賦值。 它有如下的形式; + +> `expression` = `value` + +就是把右邊的 *value* 賦值給左邊的 *expression*. 如果左邊的*expression* 需要接收多個參數(是一個tuple ),那麼右邊必須也是一個具有同樣數量參數的tuple. (允許嵌套的tuple) + +```swift +(a, _, (b, c)) = ("test", 9.45, (12, 3)) +// a is "test", b is 12, c is 3, and 9.45 is ignored +``` + +賦值運算符不返回任何值。 + +> 賦值運算符語法 +> *賦值運算符* → **=** + + +## 三元條件運算符(Ternary Conditional Operator) + +三元條件運算符 是根據條件來獲取值。 形式如下: + +> `condition` ? `expression used if true` : `expression used if false` + +如果 `condition` 是true, 那麼返回 第一個表達式的值(此時不會調用第二個表達式), 否則返回第二個表達式的值(此時不會調用第一個表達式)。 + +想看三元條件運算符的例子,請參見: Ternary Conditional Operator. + +> 三元條件運算符語法 +> *三元條件運算符* → **?** [*表達式*](..\chapter3\04_Expressions.html#expression) **:** + + +## 類型轉換運算符(Type-Casting Operators) + +有兩種類型轉換操作符: as 和 is. 它們有如下的形式: + +> `expression` as `type` +> `expression` as? `type` +> `expression` is `type` + +as 運算符會把`目標表達式`轉換成指定的`類型`(specified type),過程如下: + +- 如果類型轉換成功, 那麼目標表達式就會返回指定類型的實例(instance). 例如:把子類(subclass)變成父類(superclass)時. +- 如果轉換失敗,則會拋出編譯錯誤( compile-time error)。 +- 如果上述兩個情況都不是(也就是說,編譯器在編譯時期無法確定轉換能否成功,) 那麼目標表達式就會變成指定的類型的optional. (is an optional of the specified type ) 然後在運行時,如果轉換成功, 目標表達式就會作為 optional的一部分來返回, 否則,目標表達式返回nil. 對應的例子是: 把一個 superclass 轉換成一個 subclass. + +```swift +class SomeSuperType {} +class SomeType: SomeSuperType {} +class SomeChildType: SomeType {} +let s = SomeType() + +let x = s as SomeSuperType // known to succeed; type is SomeSuperType +let y = s as Int // known to fail; compile-time error +let z = s as SomeChildType // might fail at runtime; type is SomeChildType? +``` + +使用'as'做類型轉換跟正常的類型聲明,對於編譯器來說是一樣的。例如: + +```swift +let y1 = x as SomeType // Type information from 'as' +let y2: SomeType = x // Type information from an annotation +``` + +'is' 運算符在「運行時(runtime)」會做檢查。 成功會返回true, 否則 false + +上述檢查在「編譯時(compile time)」不能使用。 例如下面的使用是錯誤的: + +```swift +"hello" is String +"hello" is Int +``` + +關於類型轉換的更多內容和例子,請參見: Type Casting. + +> 類型轉換運算符(type-casting-operator)語法 +> *類型轉換運算符* → **is** [*類型*](..\chapter3\03_Types.html#type) | **as** **?** _可選_ [*類型*](..\chapter3\03_Types.html#type) + + +## 主表達式(Primary Expressions) + +`主表達式`是最基本的表達式。 它們可以跟 前綴表達式,二元表達式,後綴表達式以及其他主要表達式組合使用。 + +> 主表達式語法 +> *主表達式* → [*標識符*](LexicalStructure.html#identifier) [*泛型參數子句*](GenericParametersAndArguments.html#generic_argument_clause) _可選_ +> *主表達式* → [*字面量表達式*](..\chapter3\04_Expressions.html#literal_expression) +> *主表達式* → [*self表達式*](..\chapter3\04_Expressions.html#self_expression) +> *主表達式* → [*超類表達式*](..\chapter3\04_Expressions.html#superclass_expression) +> *主表達式* → [*閉包表達式*](..\chapter3\04_Expressions.html#closure_expression) +> *主表達式* → [*圓括號表達式*](..\chapter3\04_Expressions.html#parenthesized_expression) +> *主表達式* → [*隱式成員表達式*](..\chapter3\04_Expressions.html#implicit_member_expression) +> *主表達式* → [*通配符表達式*](..\chapter3\04_Expressions.html#wildcard_expression) + +### 字符型表達式(Literal Expression) + +由這些內容組成:普通的字符(string, number) , 一個字符的字典或者數組,或者下面列表中的特殊字符。 + +字符(Literal) | 類型(Type) | 值(Value) +------------- | ---------- | ---------- +\__FILE__ | String | 所在的文件名 +\__LINE__ | Int | 所在的行數 +\__COLUMN__ | Int | 所在的列數 +\__FUNCTION__ | String | 所在的function 的名字 + +在某個函數(function)中,`__FUNCTION__` 會返回當前函數的名字。 在某個方法(method)中,它會返回當前方法的名字。 在某個property 的getter/setter中會返回這個屬性的名字。 在特殊的成員如init/subscript中 會返回這個關鍵字的名字,在某個文件的頂端(the top level of a file),它返回的是當前module的名字。 + +一個array literal,是一個有序的值的集合。 它的形式是: + +> [`value 1`, `value 2`, `...`] + +數組中的最後一個表達式可以緊跟一個逗號(','). []表示空數組 。 array literal的type是 T[], 這個T就是數組中元素的type. 如果該數組中有多種type, T則是跟這些type的公共supertype最接近的type.(closest common supertype) + +一個`dictionary literal` 是一個包含無序的鍵值對(key-value pairs)的集合,它的形式是: + +> [`key 1`: `value 1`, `key 2`: `value 2`, `...`] + +dictionary 的最後一個表達式可以是一個逗號(','). [:] 表示一個空的dictionary. 它的type是 Dictionary (這裡KeyType表示 key的type, ValueType表示 value的type) 如果這個dictionary 中包含多種 types, 那麼KeyType, Value 則對應著它們的公共supertype最接近的type( closest common supertype). + +> 字面量表達式語法 +> *字面量表達式* → [*字面量*](LexicalStructure.html#literal) +> *字面量表達式* → [*數組字面量*](..\chapter3\04_Expressions.html#array_literal) | [*字典字面量*](..\chapter3\04_Expressions.html#dictionary_literal) +> *字面量表達式* → **__FILE__** | **__LINE__** | **__COLUMN__** | **__FUNCTION__** +> *數組字面量* → **[** [*數組字面量項列表*](..\chapter3\04_Expressions.html#array_literal_items) _可選_ **]** +> *數組字面量項列表* → [*數組字面量項*](..\chapter3\04_Expressions.html#array_literal_item) **,** _可選_ | [*數組字面量項*](..\chapter3\04_Expressions.html#array_literal_item) **,** [*數組字面量項列表*](..\chapter3\04_Expressions.html#array_literal_items) +> *數組字面量項* → [*表達式*](..\chapter3\04_Expressions.html#expression) +> *字典字面量* → **[** [*字典字面量項列表*](..\chapter3\04_Expressions.html#dictionary_literal_items) **]** | **[** **:** **]** +> *字典字面量項列表* → [*字典字面量項*](..\chapter3\04_Expressions.html#dictionary_literal_item) **,** _可選_ | [*字典字面量項*](..\chapter3\04_Expressions.html#dictionary_literal_item) **,** [*字典字面量項列表*](..\chapter3\04_Expressions.html#dictionary_literal_items) +> *字典字面量項* → [*表達式*](..\chapter3\04_Expressions.html#expression) **:** [*表達式*](..\chapter3\04_Expressions.html#expression) + +### self表達式(Self Expression) + +self表達式是對 當前type 或者當前instance的引用。它的形式如下: + +> self +> self.`member name` +> self[`subscript index`] +> self(`initializer arguments`) +> self.init(`initializer arguments`) + +如果在 initializer, subscript, instance method中,self等同於當前type的instance. 在一個靜態方法(static method), 類方法(class method)中, self等同於當前的type. + +當訪問 member(成員變量時), self 用來區分重名變量(例如函數的參數). 例如, +(下面的 self.greeting 指的是 var greeting: String, 而不是 init(greeting: String) ) + +```swift +class SomeClass { + var greeting: String + init(greeting: String) { + self.greeting = greeting + } +} +``` + +在mutating 方法中, 你可以使用self 對 該instance進行賦值。 + +```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) + } +} +``` + +> Self 表達式語法 +> *self表達式* → **self** +> *self表達式* → **self** **.** [*標識符*](LexicalStructure.html#identifier) +> *self表達式* → **self** **[** [*表達式*](..\chapter3\04_Expressions.html#expression) **]** +> *self表達式* → **self** **.** **init** + +### 超類表達式(Superclass Expression) + +超類表達式可以使我們在某個class中訪問它的超類. 它有如下形式: + +> super.`member name` +> super[`subscript index`] +> super.init(`initializer arguments`) + +形式1 用來訪問超類的某個成員(member). 形式2 用來訪問該超類的 subscript 實現。 形式3 用來訪問該超類的 initializer. + +子類(subclass)可以通過超類(superclass)表達式在它們的 member, subscripting 和 initializers 中來利用它們超類中的某些實現(既有的方法或者邏輯)。 + +> 超類(superclass)表達式語法 +> *超類表達式* → [*超類方法表達式*](..\chapter3\04_Expressions.html#superclass_method_expression) | [*超類下標表達式*](..\chapter3\04_Expressions.html#超類下標表達式) | [*超類構造器表達式*](..\chapter3\04_Expressions.html#superclass_initializer_expression) +> *超類方法表達式* → **super** **.** [*標識符*](LexicalStructure.html#identifier) +> *超類下標表達式* → **super** **[** [*表達式*](..\chapter3\04_Expressions.html#expression) **]** +> *超類構造器表達式* → **super** **.** **init** + +### 閉包表達式(Closure Expression) + +閉包(closure) 表達式可以建立一個閉包(在其他語言中也叫 lambda, 或者 匿名函數(anonymous function)). 跟函數(function)的聲明一樣, 閉包(closure)包含了可執行的代碼(跟方法主體(statement)類似) 以及接收(capture)的參數。 它的形式如下: + +```swift +{ (parameters) -> return type in + statements +} +``` + +閉包的參數聲明形式跟方法中的聲明一樣, 請參見:Function Declaration. + +閉包還有幾種特殊的形式, 讓使用更加簡潔: + +- 閉包可以省略 它的參數的type 和返回值的type. 如果省略了參數和參數類型,就也要省略 'in'關鍵字。 如果被省略的type 無法被編譯器獲知(inferred) ,那麼就會拋出編譯錯誤。 +- 閉包可以省略參數,轉而在方法體(statement)中使用 $0, $1, $2 來引用出現的第一個,第二個,第三個參數。 +- 如果閉包中只包含了一個表達式,那麼該表達式就會自動成為該閉包的返回值。 在執行 'type inference '時,該表達式也會返回。 + +下面幾個 閉包表達式是 等價的: + +```swift +myFunction { + (x: Int, y: Int) -> Int in + return x + y +} + +myFunction { + (x, y) in + return x + y +} + +myFunction { return $0 + $1 } + +myFunction { $0 + $1 } +``` + +關於 向閉包中傳遞參數的內容,參見: Function Call Expression. + +閉包表達式可以通過一個參數列表(capture list) 來顯式指定它需要的參數。 參數列表 由中括號 [] 括起來,裡面的參數由逗號','分隔。一旦使用了參數列表,就必須使用'in'關鍵字(在任何情況下都得這樣做,包括忽略參數的名字,type, 返回值時等等)。 + +在閉包的參數列表( capture list)中, 參數可以聲明為 'weak' 或者 'unowned' . + +```swift +myFunction { print(self.title) } // strong capture +myFunction { [weak self] in print(self!.title) } // weak capture +myFunction { [unowned self] in print(self.title) } // unowned capture +``` + +在參數列表中,也可以使用任意表達式來賦值. 該表達式會在 閉包被執行時賦值,然後按照不同的力度來獲取(這句話請慎重理解)。(captured with the specified strength. ) 例如: + +```swift +// Weak capture of "self.parent" as "parent" +myFunction { [weak parent = self.parent] in print(parent!.title) } +``` + +關於閉包表達式的更多信息和例子,請參見: Closure Expressions. + +> 閉包表達式語法 +> *閉包表達式* → **{** [*閉包簽名(Signational)*](..\chapter3\04_Expressions.html#closure_signature) _可選_ [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) **}** +> *閉包簽名(Signational)* → [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ **in** +> *閉包簽名(Signational)* → [*標識符列表*](LexicalStructure.html#identifier_list) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ **in** +> *閉包簽名(Signational)* → [*捕獲(Capature)列表*](..\chapter3\04_Expressions.html#capture_list) [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ **in** +> *閉包簽名(Signational)* → [*捕獲(Capature)列表*](..\chapter3\04_Expressions.html#capture_list) [*標識符列表*](LexicalStructure.html#identifier_list) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ **in** +> *閉包簽名(Signational)* → [*捕獲(Capature)列表*](..\chapter3\04_Expressions.html#capture_list) **in** +> *捕獲(Capature)列表* → **[** [*捕獲(Capature)說明符*](..\chapter3\04_Expressions.html#capture_specifier) [*表達式*](..\chapter3\04_Expressions.html#expression) **]** +> *捕獲(Capature)說明符* → **weak** | **unowned** | **unowned(safe)** | **unowned(unsafe)** + +### 隱式成員表達式(Implicit Member Expression) + +在可以判斷出類型(type)的上下文(context)中,隱式成員表達式是訪問某個type的member( 例如 class method, enumeration case) 的簡潔方法。 它的形式是: + +> .`member name` + +例子: + +```swift +var x = MyEnumeration.SomeValue +x = .AnotherValue +``` + +> 隱式成員表達式語法 +> *隱式成員表達式* → **.** [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) + +### 圓括號表達式(Parenthesized Expression) + +圓括號表達式由多個子表達式和逗號','組成。 每個子表達式前面可以有 identifier x: 這樣的可選前綴。形式如下: + +>(`identifier 1`: `expression 1`, `identifier 2`: `expression 2`, `...`) + +圓括號表達式用來建立tuples , 然後把它做為參數傳遞給 function. 如果某個圓括號表達式中只有一個 子表達式,那麼它的type就是 子表達式的type。例如: (1)的 type是Int, 而不是(Int) + +> 圓括號表達式(Parenthesized Expression)語法 +> *圓括號表達式* → **(** [*表達式元素列表*](..\chapter3\04_Expressions.html#expression_element_list) _可選_ **)** +> *表達式元素列表* → [*表達式元素*](..\chapter3\04_Expressions.html#expression_element) | [*表達式元素*](..\chapter3\04_Expressions.html#expression_element) **,** [*表達式元素列表*](..\chapter3\04_Expressions.html#expression_element_list) +> *表達式元素* → [*表達式*](..\chapter3\04_Expressions.html#expression) | [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) **:** [*表達式*](..\chapter3\04_Expressions.html#expression) + +### 通配符表達式(Wildcard Expression) + +通配符表達式用來忽略傳遞進來的某個參數。例如:下面的代碼中,10被傳遞給x, 20被忽略(譯註:好奇葩的語法。。。) + +```swift +(x, _) = (10, 20) +// x is 10, 20 is ignored +``` + +> 通配符表達式語法 +> *通配符表達式* → **_** + + +## 後綴表達式(Postfix Expressions) + +後綴表達式就是在某個表達式的後面加上 操作符。 嚴格的講,每個主要表達式(primary expression)都是一個後綴表達式 + +Swift 標準庫提供了下列後綴表達式: + +- ++ Increment +- -- Decrement + +對於這些操作符的使用,請參見: Basic Operators and Advanced Operators + +> 後置表達式語法 +> *後置表達式* → [*主表達式*](..\chapter3\04_Expressions.html#primary_expression) +> *後置表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) [*後置運算符*](..\chapter3\02_Lexical_Structure.html#postfix_operator) +> *後置表達式* → [*函數調用表達式*](..\chapter3\04_Expressions.html#function_call_expression) +> *後置表達式* → [*構造器表達式*](..\chapter3\04_Expressions.html#initializer_expression) +> *後置表達式* → [*顯示成員表達式*](..\chapter3\04_Expressions.html#explicit_member_expression) +> *後置表達式* → [*後置self表達式*](..\chapter3\04_Expressions.html#postfix_self_expression) +> *後置表達式* → [*動態類型表達式*](..\chapter3\04_Expressions.html#dynamic_type_expression) +> *後置表達式* → [*下標表達式*](..\chapter3\04_Expressions.html#subscript_expression) +> *後置表達式* → [*強制取值(Forced Value)表達式*](..\chapter3\04_Expressions.html#forced_value_expression) +> *後置表達式* → [*可選鏈(Optional Chaining)表達式*](..\chapter3\04_Expressions.html#optional_chaining_expression) + +### 函數調用表達式(Function Call Expression) + +函數調用表達式由函數名和參數列表組成。它的形式如下: + +> `function name`(`argument value 1`, `argument value 2`) + +The function name can be any expression whose value is of a function type. +(不用翻譯了, 太囉嗦) + +如果該function 的聲明中指定了參數的名字,那麼在調用的時候也必須得寫出來. 例如: + +> `function name`(`argument name 1`: `argument value 1`, `argument name 2`: `argument value 2`) + +可以在 函數調用表達式的尾部(最後一個參數之後)加上 一個閉包(closure) , 該閉包會被目標函數理解並執行。它具有如下兩種寫法: + +```swift +// someFunction takes an integer and a closure as its arguments +someFunction(x, {$0 == 13}) +someFunction(x) {$0 == 13} +``` + +如果閉包是該函數的唯一參數,那麼圓括號可以省略。 + +```swift +// someFunction takes a closure as its only argument +myData.someMethod() {$0 == 13} +myData.someMethod {$0 == 13} +``` + +> 函數調用表達式語法 +> *函數調用表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) [*圓括號表達式*](..\chapter3\04_Expressions.html#parenthesized_expression) +> *函數調用表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) [*圓括號表達式*](..\chapter3\04_Expressions.html#parenthesized_expression) _可選_ [*後置閉包(Trailing Closure)*](..\chapter3\04_Expressions.html#trailing_closure) +> *後置閉包(Trailing Closure)* → [*閉包表達式*](..\chapter3\04_Expressions.html#closure_expression) + +### 初始化函數表達式(Initializer Expression) + +Initializer表達式用來給某個Type初始化。 它的形式如下: + +> `expression`.init(`initializer arguments`) + +(Initializer表達式用來給某個Type初始化。) 跟函數(function)不同, initializer 不能返回值。 + +```swift +var x = SomeClass.someClassFunction // ok +var y = SomeClass.init // error +``` + +可以通過 initializer 表達式來委託調用(delegate to )到superclass的initializers. + +```swift +class SomeSubClass: SomeSuperClass { + init() { + // subclass initialization goes here + super.init() + } +} +``` + +> 構造器表達式語法 +> *構造器表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** **init** + +### 顯式成員表達式(Explicit Member Expression) + +顯示成員表達式允許我們訪問type, tuple, module的成員變量。它的形式如下: + +> `expression`.`member name` + +該member 就是某個type在聲明時候所定義(declaration or extension) 的變量, 例如: + +```swift +class SomeClass { + var someProperty = 42 +} +let c = SomeClass() +let y = c.someProperty // Member access +``` + +對於tuple, 要根據它們出現的順序(0, 1, 2...)來使用: + +```swift +var t = (10, 20, 30) +t.0 = t.1 +// Now t is (20, 20, 30) +``` + +The members of a module access the top-level declarations of that module. +(不確定:對於某個module的member的調用,只能調用在top-level聲明中的member.) + +> 顯式成員表達式語法 +> *顯示成員表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** [*十進制數字*](..\chapter3\02_Lexical_Structure.html#decimal_digit) +> *顯示成員表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) [*泛型參數子句*](GenericParametersAndArguments.html#generic_argument_clause) _可選_ + +### 後綴self表達式(Postfix Self Expression) + +後綴表達式由 某個表達式 + '.self' 組成. 形式如下: + +> `expression`.self +> `type`.self + +形式1 表示會返回 expression 的值。例如: x.self 返回 x + +形式2:返回對應的type。我們可以用它來動態的獲取某個instance的type。 + +> 後置Self 表達式語法 +> *後置self表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** **self** + +### dynamic表達式(Dynamic Type Expression) + +(因為dynamicType是一個獨有的方法,所以這裡保留了英文單詞,未作翻譯, --- 類似與self expression) + +dynamicType 表達式由 某個表達式 + '.dynamicType' 組成。 + +> `expression`.dynamicType + +上面的形式中, expression 不能是某type的名字(當然了,如果我都知道它的名字了還需要動態來獲取它嗎)。動態類型表達式會返回"運行時"某個instance的type, 具體請看下面的列子: + +```swift +class SomeBaseClass { + class func printClassName() { + println("SomeBaseClass") + } +} +class SomeSubClass: SomeBaseClass { + override class func printClassName() { + println("SomeSubClass") + } +} +let someInstance: SomeBaseClass = SomeSubClass() + +// someInstance is of type SomeBaseClass at compile time, but +// someInstance is of type SomeSubClass at runtime +someInstance.dynamicType.printClassName() +// prints "SomeSubClass" +``` + +> 動態類型表達式語法 +> *動態類型表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** **dynamicType** + +### 下標腳本表達式(Subscript Expression) + +下標腳本表達式提供了通過下標腳本訪問getter/setter 的方法。它的形式是: + +> `expression`[`index expressions`] + +可以通過下標腳本表達式通過getter獲取某個值,或者通過setter賦予某個值. + +關於subscript的聲明,請參見: Protocol Subscript Declaration. + +> 附屬腳本表達式語法 +> *附屬腳本表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **[** [*表達式列表*](..\chapter3\04_Expressions.html#expression_list) **]** + +### 強制取值表達式(Forced-Value Expression) + +強制取值表達式用來獲取某個目標表達式的值(該目標表達式的值必須不是nil )。它的形式如下: + +> `expression`! + +如果該表達式的值不是nil, 則返回對應的值。 否則,拋出運行時錯誤(runtime error)。 + +> 強制取值(Forced Value)語法 +> *強制取值(Forced Value)表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **!** + +### 可選鏈表達式(Optional-Chaining Expression) + +可選鏈表達式由目標表達式 + '?' 組成,形式如下: + +> `expression`? + +後綴'?' 返回目標表達式的值,把它做為可選的參數傳遞給後續的表達式 + +如果某個後綴表達式包含了可選鏈表達式,那麼它的執行過程就比較特殊: 首先先判斷該可選鏈表達式的值,如果是 nil, 整個後綴表達式都返回 nil, 如果該可選鏈的值不是nil, 則正常返回該後綴表達式的值(依次執行它的各個子表達式)。在這兩種情況下,該後綴表達式仍然是一個optional type(In either case, the value of the postfix expression is still of an optional type) + +如果某個"後綴表達式"的"子表達式"中包含了"可選鏈表達式",那麼只有最外層的表達式返回的才是一個optional type. 例如,在下面的例子中, 如果c 不是nil, 那麼 c?.property.performAction() 這句代碼在執行時,就會先獲得c 的property方法,然後調用 performAction()方法。 然後對於 "c?.property.performAction()" 這個整體,它的返回值是一個optional type. + +```swift +var c: SomeClass? +var result: Bool? = c?.property.performAction() +``` + +如果不使用可選鏈表達式,那麼 上面例子的代碼跟下面例子等價: + +```swift +if let unwrappedC = c { + result = unwrappedC.property.performAction() +} +``` + +> 可選鏈表達式語法 +> *可選鏈表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **?** diff --git a/source-tw/chapter3/05_Declarations.md b/source-tw/chapter3/05_Declarations.md new file mode 100644 index 00000000..c1ec0799 --- /dev/null +++ b/source-tw/chapter3/05_Declarations.md @@ -0,0 +1,855 @@ +> 翻譯:[marsprince](https://github.com/marsprince) +> 校對:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai) + +# 聲明 +----------------- + +本頁包含內容: + +- [模塊範圍](#module_scope) +- [代碼塊](#code_blocks) +- [引入聲明](#import_declaration) +- [常量聲明](#constant_declaration) +- [變量聲明](#variable_declaration) +- [類型的別名聲明](#type_alias_declaration) +- [函數聲明](#function_declaration) +- [枚舉聲明](#enumeration_declaration) +- [結構體聲明](#structure_declaration) +- [類聲明](#class_declaration) +- [協議聲明](#protocol_declaration) +- [構造器聲明](#initializer_declaration) +- [析構聲明](#deinitializer_declaration) +- [擴展聲明](#extension_declaration) +- [下標腳本聲明](#subscript_declaration) +- [運算符聲明](#operator_declaration) + +一條聲明可以在你的程序裡引入新的名字和構造。舉例來說,你可以使用聲明來引入函數和方法,變量和常量,或者來定義 +新的命名好的枚舉,結構,類和協議類型。你也可以使用一條聲明來延長一個已經存在的命名好的類型的行為。或者在你的 +程序裡引入在其他地方聲明的符號。 + +在swift中,大多數聲明在某種意義上講也是執行或同事聲明它們的初始化定義。這意味著,因為協議和它們的成員不匹配, +大多數協議成員需要單獨的聲明。為了方便起見,也因為這些區別在swift裡不是很重要,聲明語句同時包含了聲明和定義。 + +> 聲明語法 +> *聲明* → [*導入聲明*](..\chapter3\05_Declarations.html#import_declaration) +> *聲明* → [*常量聲明*](..\chapter3\05_Declarations.html#constant_declaration) +> *聲明* → [*變量聲明*](..\chapter3\05_Declarations.html#variable_declaration) +> *聲明* → [*類型別名聲明*](..\chapter3\05_Declarations.html#typealias_declaration) +> *聲明* → [*函數聲明*](..\chapter3\05_Declarations.html#function_declaration) +> *聲明* → [*枚舉聲明*](..\chapter3\05_Declarations.html#enum_declaration) +> *聲明* → [*結構體聲明*](..\chapter3\05_Declarations.html#struct_declaration) +> *聲明* → [*類聲明*](..\chapter3\05_Declarations.html#class_declaration) +> *聲明* → [*協議聲明*](..\chapter3\05_Declarations.html#protocol_declaration) +> *聲明* → [*構造器聲明*](..\chapter3\05_Declarations.html#initializer_declaration) +> *聲明* → [*析構器聲明*](..\chapter3\05_Declarations.html#deinitializer_declaration) +> *聲明* → [*擴展聲明*](..\chapter3\05_Declarations.html#extension_declaration) +> *聲明* → [*附屬腳本聲明*](..\chapter3\05_Declarations.html#subscript_declaration) +> *聲明* → [*運算符聲明*](..\chapter3\05_Declarations.html#operator_declaration) +> *聲明(Declarations)列表* → [*聲明*](..\chapter3\05_Declarations.html#declaration) [*聲明(Declarations)列表*](..\chapter3\05_Declarations.html#declarations) _可選_ +> *聲明描述符(Specifiers)列表* → [*聲明描述符(Specifier)*](..\chapter3\05_Declarations.html#declaration_specifier) [*聲明描述符(Specifiers)列表*](..\chapter3\05_Declarations.html#declaration_specifiers) _可選_ +> *聲明描述符(Specifier)* → **class** | **mutating** | **nonmutating** | **override** | **static** | **unowned** | **unowned(safe)** | **unowned(unsafe)** | **weak** + + +##模塊範圍 + +模塊範圍定義了對模塊中其他源文件可見的代碼。(註:待改進)在swift的源文件中,最高級別的代碼由零個或多個語句, +聲明和表達組成。變量,常量和其他的聲明語句在一個源文件的最頂級被聲明,使得它們對同一模塊中的每個源文件都是可見的。 + +> 頂級(Top Level) 聲明語法 +> *頂級聲明* → [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) _可選_ + + +##代碼塊 + +代碼塊用來將一些聲明和控制結構的語句組織在一起。它有如下的形式: + +> { +> `statements` +> } + +代碼塊中的語句包括聲明,表達式和各種其他類型的語句,它們按照在源碼中的出現順序被依次執行。 + +> 代碼塊語法 +> *代碼塊* → **{** [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) _可選_ **}** + + +##引入聲明 + +引入聲明使你可以使用在其他文件中聲明的內容。引入語句的基本形式是引入整個代碼模塊;它由import關鍵字開始,後面 +緊跟一個模塊名: + +> import `module` + +你可以提供更多的細節來限制引入的符號,如聲明一個特殊的子模塊或者在一個模塊或子模塊中做特殊的聲明。(待改進) +當你使用了這些細節後,在當前的程序匯總只有引入的符號是可用的(並不是聲明的整個模塊)。 + +> import `import kind` `module`.`symbol name` +> import `module`.`submodule` + +

+ +> 導入(Import)聲明語法 +> *導入聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **import** [*導入類型*](..\chapter3\05_Declarations.html#import_kind) _可選_ [*導入路徑*](..\chapter3\05_Declarations.html#import_path) +> *導入類型* → **typealias** | **struct** | **class** | **enum** | **protocol** | **var** | **func** +> *導入路徑* → [*導入路徑標識符*](..\chapter3\05_Declarations.html#import_path_identifier) | [*導入路徑標識符*](..\chapter3\05_Declarations.html#import_path_identifier) **.** [*導入路徑*](..\chapter3\05_Declarations.html#import_path) +> *導入路徑標識符* → [*標識符*](LexicalStructure.html#identifier) | [*運算符*](LexicalStructure.html#operator) + + +##常量聲明 + +常量聲明可以在你的程序裡命名一個常量。常量以關鍵詞let來聲明,遵循如下的格式: + +> let `constant name`: `type` = `expression` + +當常量的值被給定後,常量就將常量名稱和表達式初始值不變的結合在了一起,而且不能更改。 +這意味著如果常量以類的形式被初始化,類本身的內容是可以改變的,但是常量和類之間的結合關係是不能改變的。 +當一個常量被聲明為全局變量,它必須被給定一個初始值。當一個常量在類或者結構體中被聲明時,它被認為是一個常量 +屬性。常量並不是可計算的屬性,因此不包含getters和setters。(譯者註:getters和setters不知道怎麼翻譯,待改進) + +如果常量名是一個元祖形式,元祖中的每一項初始化表達式中都要有對應的值 + +```swift +let (firstNumber, secondNumber) = (10, 42) +``` + +在上例中,firstNumber是一個值為10的常量,secnodeName是一個值為42的常量。所有常量都可以獨立的使用: + +```swift +println("The first number is \(firstNumber).") +// prints "The first number is 10." +println("The second number is \(secondNumber).") +// prints "The second number is 42." +``` + +類型註釋(:type)在常量聲明中是一個可選項,它可以用來描述在類型推斷(type inference)中找到的類型。 + +聲明一個靜態常量要使用關鍵字static。靜態屬性在類型屬性(type propetries)中有介紹。 + +如果還想獲得更多關於常量的信息或者想在使用中獲得幫助,請查看常量和變量(constants and variables), +存儲屬性(stored properties)等節。 + +> 常數聲明語法 +> *常量聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*聲明描述符(Specifiers)列表*](..\chapter3\05_Declarations.html#declaration_specifiers) _可選_ **let** [*模式構造器列表*](..\chapter3\05_Declarations.html#pattern_initializer_list) +> *模式構造器列表* → [*模式構造器*](..\chapter3\05_Declarations.html#pattern_initializer) | [*模式構造器*](..\chapter3\05_Declarations.html#pattern_initializer) **,** [*模式構造器列表*](..\chapter3\05_Declarations.html#pattern_initializer_list) +> *模式構造器* → [*模式*](..\chapter3\07_Patterns.html#pattern) [*構造器*](..\chapter3\05_Declarations.html#initializer) _可選_ +> *構造器* → **=** [*表達式*](..\chapter3\04_Expressions.html#expression) + + +##變量聲明 + +變量聲明可以在你的程序裡聲明一個變量,它以關鍵字var來聲明。根據聲明變量類型和值的不同,如存儲和計算 +變量和屬性,存儲變量和屬性監視,和靜態變量屬性,有著不同的聲明形式。(待改進) +所使用的聲明形式取決於變量所聲明的範圍和你打算聲明的變量類型。 + +>注意: +你也可以在協議聲明的上下文聲明屬性,詳情參見類型屬性聲明。 + +###存儲型變量和存儲型屬性 + +下面的形式聲明了一個存儲型變量或存儲型變量屬性 + +> var `variable name`: `type` = `expression` + +你可以在全局,函數內,或者在類和結構體的聲明(context)中使用這種形式來聲明一個變量。當變量以這種形式 +在全局或者一個函數內被聲明時,它代表一個存儲型變量。當它在類或者結構體中被聲明時,它代表一個存儲型變量屬性。 + +初始化的表達式不可以在協議(protocol)的定義中出現,在其他情況下,初始化表達式是可選的(optional),如果沒有初始化表達式,那麼變量定義時必須顯示的聲明變量類型(:type) + +對於常量的定義,如果名字是一個元祖(tuple),元祖每一項的`name`都要和初始化表達式`expression`中的相應值一致。 + +正如名字一樣,存儲型變量的值或存儲型變量屬性存儲在內存中。 + +###計算型變量和計算型屬性 + +如下形式聲明一個一個存儲型變量或存儲型屬性: + +> var `variable name`: `type` { +> get { +> `statements` +> } +> set(`setter name`) { +> `statements` +> } +> } + +你可以在全局,函數體內或者類,結構體,枚舉,擴展聲明的上下文中使用這種形式的聲明。 +當變量以這種形式在全局或者一個函數內被聲明時,它代表一個計算型變量。當它在類,結構體,枚舉,擴展聲明的上下文 +中中被聲明時,它代表一個計算型變量屬性。 + +getter用來讀取變量值,setter用來寫入變量值。setter子句是可選擇的,只有getter是必需的,你可以將這些語句 +都省略,只是簡單的直接返回請求值,正如在只讀計算屬性(read-only computed properites)中描述的那樣。 +但是如果你提供了一個setter語句,你也必需提供一個getter語句。 + +setter的名字和圓括號內的語句是可選的。如果你寫了一個setter名,它就會作為setter的參數被使用。如果你不寫setter名, +setter的初始名為newValue,正如在setter聲明速記(shorthand setter declaration)中提到的那樣。 + +不像存儲型變量和存儲型屬性那樣,計算型屬性和計算型變量的值不存儲在內存中。 + +獲得更多信息,查看更多關於計算型屬性的例子,請查看計算型屬性(computed properties)一節。 + +###存儲型變量監視器和屬性監視器 + +你可以用willset和didset監視器來聲明一個存儲型變量或屬性。一個包含監視器的存儲型變量或屬性按如下的形式聲明: + +> var `variable name`: `type` = expression { +> willSet(setter name) { +> `statements` +> } +> didSet(`setter name`) { +> `statements` +> } +> } + +你可以在全局,函數體內或者類,結構體,枚舉,擴展聲明的上下文中使用這種形式的聲明。 +當變量以這種形式在全局或者一個函數內被聲明時,監視器代表一個存儲型變量監視器; +當它在類,結構體,枚舉,擴展聲明的上下文中被聲明時,監視器代表屬性監視器。 + +你可以為適合的監視器添加任何存儲型屬性。你也可以通過重寫子類屬性的方式為適合的監視器添加任何繼承的屬性 +(無論是存儲型還是計算型的),參見重寫屬性監視器(overriding properyt observers)。 + +初始化表達式在類或者結構體的聲明中是可選的,但是在其他地方是必需的。無論在什麼地方聲明, +所有包含監視器的變量聲明都必須有類型註釋(type annotation)。 + +當變量或屬性的值被改變時,willset和didset監視器提供了一個監視方法(適當的回應)。 +監視器不會在變量或屬性第一次初始化時運行,它們只有在值被外部初始化語句改變時才會被運行。 + +willset監視器只有在變量或屬性值被改變之前運行。新的值作為一個常量經過過willset監視器,因此不可以在 +willset語句中改變它。didset監視器在變量或屬性值被改變後立即運行。和willset監視器相反,為了以防止你仍然 +需要獲得舊的數據,舊變量值或者屬性會經過didset監視器。這意味著,如果你在變量或屬性自身的didiset監視器語句 +中設置了一個值,你設置的新值會取代剛剛在willset監視器中經過的那個值。 + +在willset和didset語句中,setter名和圓括號的語句是可選的。如果你寫了一個setter名,它就會作為willset和didset的參數被使用。如果你不寫setter名, +willset監視器初始名為newvalue,didset監視器初始名為oldvalue。 + +當你提供一個willset語句時,didset語句是可選的。同樣的,在你提供了一個didset語句時,willset語句是可選的。 + +獲得更多信息,查看如何使用屬性監視器的例子,請查看屬性監視器(prpperty observers)一節。 + +###類和靜態變量屬性 + +class關鍵字用來聲明類的計算型屬性。static關鍵字用來聲明類的靜態變量屬性。類和靜態變量在類型屬性(type properties)中有詳細討論。 + +> 變量聲明語法 +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*模式構造器列表*](..\chapter3\05_Declarations.html#pattern_initializer_list) +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*getter-setter塊*](..\chapter3\05_Declarations.html#getter_setter_block) +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*getter-setter關鍵字(Keyword)塊*](..\chapter3\05_Declarations.html#getter_setter_keyword_block) +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*構造器*](..\chapter3\05_Declarations.html#initializer) _可選_ [*willSet-didSet代碼塊*](..\chapter3\05_Declarations.html#willSet_didSet_block) +> *變量聲明頭(Head)* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*聲明描述符(Specifiers)列表*](..\chapter3\05_Declarations.html#declaration_specifiers) _可選_ **var** +> *變量名稱* → [*標識符*](LexicalStructure.html#identifier) +> *getter-setter塊* → **{** [*getter子句*](..\chapter3\05_Declarations.html#getter_clause) [*setter子句*](..\chapter3\05_Declarations.html#setter_clause) _可選_ **}** +> *getter-setter塊* → **{** [*setter子句*](..\chapter3\05_Declarations.html#setter_clause) [*getter子句*](..\chapter3\05_Declarations.html#getter_clause) **}** +> *getter子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **get** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *setter子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **set** [*setter名稱*](..\chapter3\05_Declarations.html#setter_name) _可選_ [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *setter名稱* → **(** [*標識符*](LexicalStructure.html#identifier) **)** +> *getter-setter關鍵字(Keyword)塊* → **{** [*getter關鍵字(Keyword)子句*](..\chapter3\05_Declarations.html#getter_keyword_clause) [*setter關鍵字(Keyword)子句*](..\chapter3\05_Declarations.html#setter_keyword_clause) _可選_ **}** +> *getter-setter關鍵字(Keyword)塊* → **{** [*setter關鍵字(Keyword)子句*](..\chapter3\05_Declarations.html#setter_keyword_clause) [*getter關鍵字(Keyword)子句*](..\chapter3\05_Declarations.html#getter_keyword_clause) **}** +> *getter關鍵字(Keyword)子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **get** +> *setter關鍵字(Keyword)子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **set** +> *willSet-didSet代碼塊* → **{** [*willSet子句*](..\chapter3\05_Declarations.html#willSet_clause) [*didSet子句*](..\chapter3\05_Declarations.html#didSet_clause) _可選_ **}** +> *willSet-didSet代碼塊* → **{** [*didSet子句*](..\chapter3\05_Declarations.html#didSet_clause) [*willSet子句*](..\chapter3\05_Declarations.html#willSet_clause) **}** +> *willSet子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **willSet** [*setter名稱*](..\chapter3\05_Declarations.html#setter_name) _可選_ [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *didSet子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **didSet** [*setter名稱*](..\chapter3\05_Declarations.html#setter_name) _可選_ [*代碼塊*](..\chapter3\05_Declarations.html#code_block) + + +##類型的別名聲明 + +類型別名的聲明可以在你的程序裡為一個已存在的類型聲明一個別名。類型的別名聲明以關鍵字typealias開始,遵循如下的 +形式: + +> `typealias name` = `existing type` + +當聲明一個類型的別名後,你可以在你程序的任何地方使用別名來代替已存在的類型。已存在的類型可以是已經被命名的 +類型或者是混合類型。類型的別名不產生新的類型,它只是簡單的和已存在的類型做名稱替換。 + +查看更多Protocol Associated Type Declaration. + +> 類型別名聲明語法 +> *類型別名聲明* → [*類型別名頭(Head)*](..\chapter3\05_Declarations.html#typealias_head) [*類型別名賦值*](..\chapter3\05_Declarations.html#typealias_assignment) +> *類型別名頭(Head)* → **typealias** [*類型別名名稱*](..\chapter3\05_Declarations.html#typealias_name) +> *類型別名名稱* → [*標識符*](LexicalStructure.html#identifier) +> *類型別名賦值* → **=** [*類型*](..\chapter3\03_Types.html#type) + + +##函數聲明 + +你可以使用函數聲明在你的程序裡引入新的函數。函數可以在類的上下文,結構體,枚舉,或者作為方法的協議中被聲明。 +函數聲明使用關鍵字func,遵循如下的形式: + +> func `function name`(`parameters`) -> `return type` { +> `statements` +> } + +如果函數不返回任何值,返回類型可以被忽略,如下所示: + +> func `function name`(`parameters`) { +> `statements` +> } + +每個參數的類型都要標明,它們不能被推斷出來。初始時函數的參數是常量。在這些參數前面添加var使它們成為變量, +作用域內任何對變量的改變只在函數體內有效,或者用inout使的這些改變可以在調用域內生效。 +更多關於in-out參數的討論,參見in-out參數(in-out parameters) + +函數可以使用元組類型作為返回值來返回多個變量。 + +函數定義可以出現在另一個函數聲明內。這種函數被稱作nested函數。更多關於nested函數的討論,參見nestde functions。 + +###參數名 + +函數的參數是一個以逗號分隔的列表 。函數調用是的變量順序必須和函數聲明時的參數順序一致。 +最簡單的參數列表有著如下的形式: + +> `parameter name`: `parameter type` + +對於函數參數來講,參數名在函數體內被使用,而不是在函數調用時使用。對於方法參數,參數名在函數體內被使用, +同時也在方法被調用時作為標籤被使用。該方法的第一個參數名僅僅在函數體內被使用,就像函數的參數一樣,舉例來講: + +```swift +func f(x: Int, y: String) -> String { + return y + String(x) +} +f(7, "hello") // x and y have no name +``` + +```swift +class C { + func f(x: Int, y: String) -> String { + return y + String(x) + } +} +let c = C() +c.f(7, y: "hello") // x沒有名稱,y有名稱 +``` + +你可以按如下的形式,重寫參數名被使用的過程: + +> `external parameter name` `local parameter name`: `parameter type` +> #`parameter name`: `parameter type` +> _ `local parameter name`: `parameter type` + +在本地參數前命名的第二名稱(second name)使得參數有一個擴展名。且不同於本地的參數名。 +擴展參數名在函數被調用時必須被使用。對應的參數在方法或函數被調用時必須有擴展名 。 + +在參數名前所寫的哈希符號(#)代表著這個參數名可以同時作為外部或本體參數名來使用。等同於書寫兩次本地參數名。 +在函數或方法調用時,與其對應的語句必須包含這個名字。 + +本地參數名前的強調字符(_)使參數在函數被調用時沒有名稱。在函數或方法調用時,與其對應的語句必須沒有名字。 + +###特殊類型的參數 + +參數可以被忽略,值可以是變化的,並且提供一個初始值,這種方法有著如下的形式: + +> _ : <#parameter type#. +> `parameter name`: `parameter type`... +> `parameter name`: `parameter type` = `default argument value` + +以強調符(_)命名的參數明確的在函數體內不能被訪問。 + +一個以基礎類型名的參數,如果緊跟著三個點(...),被理解為是可變參數。一個函數至多可以擁有一個可變參數, +且必須是最後一個參數。可變參數被作為該基本類型名的數組來看待。舉例來講,可變參數int...被看做是int[]。 +查看可變參數的使用例子,詳見可變參數(variadic parameters)一節。 + +在參數的類型後面有一個以等號(=)連接的表達式,這樣的參數被看做有著給定表達式的初始值。如果參數在函數 +調用時被省略了,就會使用初始值。如果參數沒有省略,那麼它在函數調用是必須有自己的名字.舉例來講, +f()和f(x:7)都是只有一個變量x的函數的有效調用,但是f(7)是非法的,因為它提供了一個值而不是名稱。 + +###特殊方法 + +以self修飾的枚舉或結構體方法必須以mutating關鍵字作為函數聲明頭。 + +子類重寫的方法必須以override關鍵字作為函數聲明頭。不用override關鍵字重寫的方法,使用了override關鍵字 +卻並沒有重寫父類方法都會報錯。 + +和類型相關而不是和類型實例相關的方法必須在static聲明的結構以或枚舉內,亦或是以class關鍵字定義的類內。 + +###柯裡化函數和方法(Curried Functions and Methods) + +柯裡化函數或方法有著如下的形式: + +> func `function name`(`parameters`)(`parameters`) -> `return type` { +> `statements` +> } + +以這種形式定義的函數的返回值是另一個函數。舉例來說,下面的兩個聲明是等價的: + +```swift +func addTwoNumbers(a: Int)(b: Int) -> Int { + return a + b +} +func addTwoNumbers(a: Int) -> (Int -> Int) { + func addTheSecondNumber(b: Int) -> Int { + return a + b + } + return addTheSecondNumber +} +``` + +```swift +addTwoNumbers(4)(5) // Returns 9 +``` + +多級柯裡化應用如下 + +> 函數聲明語法 +> *函數聲明* → [*函數頭*](..\chapter3\05_Declarations.html#function_head) [*函數名*](..\chapter3\05_Declarations.html#function_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*函數簽名(Signature)*](..\chapter3\05_Declarations.html#function_signature) [*函數體*](..\chapter3\05_Declarations.html#function_body) +> *函數頭* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*聲明描述符(Specifiers)列表*](..\chapter3\05_Declarations.html#declaration_specifiers) _可選_ **func** +> *函數名* → [*標識符*](LexicalStructure.html#identifier) | [*運算符*](LexicalStructure.html#operator) +> *函數簽名(Signature)* → [*parameter-clauses*](..\chapter3\05_Declarations.html#parameter_clauses) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ +> *函數結果* → **->** [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*類型*](..\chapter3\03_Types.html#type) +> *函數體* → [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *parameter-clauses* → [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) [*parameter-clauses*](..\chapter3\05_Declarations.html#parameter_clauses) _可選_ +> *參數子句* → **(** **)** | **(** [*參數列表*](..\chapter3\05_Declarations.html#parameter_list) **...** _可選_ **)** +> *參數列表* → [*參數*](..\chapter3\05_Declarations.html#parameter) | [*參數*](..\chapter3\05_Declarations.html#parameter) **,** [*參數列表*](..\chapter3\05_Declarations.html#parameter_list) +> *參數* → **inout** _可選_ **let** _可選_ **#** _可選_ [*參數名*](..\chapter3\05_Declarations.html#parameter_name) [*本地參數名*](..\chapter3\05_Declarations.html#local_parameter_name) _可選_ [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*默認參數子句*](..\chapter3\05_Declarations.html#default_argument_clause) _可選_ +> *參數* → **inout** _可選_ **var** **#** _可選_ [*參數名*](..\chapter3\05_Declarations.html#parameter_name) [*本地參數名*](..\chapter3\05_Declarations.html#local_parameter_name) _可選_ [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*默認參數子句*](..\chapter3\05_Declarations.html#default_argument_clause) _可選_ +> *參數* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*類型*](..\chapter3\03_Types.html#type) +> *參數名* → [*標識符*](LexicalStructure.html#identifier) | **_** +> *本地參數名* → [*標識符*](LexicalStructure.html#identifier) | **_** +> *默認參數子句* → **=** [*表達式*](..\chapter3\04_Expressions.html#expression) + + +##枚舉聲明 + +在你的程序裡使用枚舉聲明來引入一個枚舉類型。 + +枚舉聲明有兩種基本的形式,使用關鍵字enum來聲明。枚舉聲明體使用從零開始的變量——叫做枚舉事件,和任意數量的 +聲明,包括計算型屬性,實例方法,靜態方法,構造器,類型別名,甚至其他枚舉,結構體,和類。枚舉聲明不能 +包含析構器或者協議聲明。 + +不像類或者結構體。枚舉類型並不提供隱式的初始構造器,所有構造器必須顯式的聲明。構造器可以委託枚舉中的其他 +構造器,但是構造過程僅當構造器將一個枚舉時間完成後才全部完成。 + +和結構體類似但是和類不同,枚舉是值類型:枚舉實例在賦予變量或常量時,或者被函數調用時被複製。 +更多關於值類型的信息,參見結構體和枚舉都是值類型(Structures and Enumerations Are Value Types)一節。 + +你可以擴展枚舉類型,正如在擴展名聲明(Extension Declaration)中討論的一樣。 + +###任意事件類型的枚舉 + +如下的形式聲明了一個包含任意類型枚舉時間的枚舉變量 + +> enum `enumeration name` { +> case `enumeration case 1` +> case `enumeration case 2`(`associated value types`) +> } + +這種形式的枚舉聲明在其他語言中有時被叫做可識別聯合(discrinminated)。 + +這種形式中,每一個事件塊由關鍵字case開始,後面緊接著一個或多個以逗號分隔的枚舉事件。每一個事件名必須是 +獨一無二的。每一個事件也可以指定它所存儲的指定類型的值,這些類型在關聯值類型的元祖裡被指定,立即書寫在事件 +名後。獲得更多關於關聯值類型的信息和例子,請查看關聯值(associated values)一節。 + +###使用原始事件值的枚舉 + +以下的形式聲明了一個包含相同基礎類型的枚舉事件的枚舉: + +> enum `enumeration name`: `raw value type` { +> case `enumeration case 1` = `raw value 1` +> case `enumeration case 2` = `raw value 2` +> } + +在這種形式中,每一個事件塊由case關鍵字開始,後面緊接著一個或多個以逗號分隔的枚舉事件。和第一種形式的枚舉 +事件不同,這種形式的枚舉事件包含一個同類型的基礎值,叫做原始值(raw value)。這些值的類型在原始值類型(raw value type) +中被指定,必須是字面上的整數,浮點數,字符或者字符串。 + +每一個事件必須有唯一的名字,必須有一個唯一的初始值。如果初始值類型被指定為int,則不必為事件顯式的指定值, +它們會隱式的被標為值0,1,2等。每一個沒有被賦值的Int類型時間會隱式的賦予一個初始值,它們是自動遞增的。 + +```swift +num ExampleEnum: Int { + case A, B, C = 5, D +} +``` + +在上面的例子中,ExampleEnum.A的值是0,ExampleEnum.B的值是。因為ExampleEnum.C的值被顯式的設定為5,因此 +ExampleEnum.D的值會自動增長為6. + +枚舉事件的初始值可以調用方法roRaw獲得,如ExampleEnum.B.toRaw()。你也可以通過調用fromRaw方法來使用初始值找到 +其對應的事件,並返回一個可選的事件。查看更多信息和獲取初始值類型事件的信息,參閱初始值(raw values)。 + +###獲得枚舉事件 + +使用點(.)來引用枚舉類型的事件,如 EnumerationType.EnumerationCase。當枚舉類型可以上下文推斷出時,你可以 +省略它(.仍然需要),參照枚舉語法(Enumeration Syntax)和顯式成員表達(Implicit Member Expression). + +使用switch語句來檢驗枚舉事件的值,正如使用switch語句匹配枚舉值(Matching Enumeration Values with a Switch Statement)一節描述的那樣。 + +枚舉類型是模式匹配(pattern-matched)的,和其相反的是switch語句case塊中枚舉事件匹配,在枚舉事件類型(Enumeration Case Pattern)中有描述。 + +> 枚舉聲明語法 +> *枚舉聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*聯合式枚舉*](..\chapter3\05_Declarations.html#union_style_enum) | [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*原始值式枚舉*](..\chapter3\05_Declarations.html#raw_value_style_enum) +> *聯合式枚舉* → [*枚舉名*](..\chapter3\05_Declarations.html#enum_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ **{** [*union-style-enum-members*](..\chapter3\05_Declarations.html#union_style_enum_members) _可選_ **}** +> *union-style-enum-members* → [*union-style-enum-member*](..\chapter3\05_Declarations.html#union_style_enum_member) [*union-style-enum-members*](..\chapter3\05_Declarations.html#union_style_enum_members) _可選_ +> *union-style-enum-member* → [*聲明*](..\chapter3\05_Declarations.html#declaration) | [*聯合式(Union Style)的枚舉case子句*](..\chapter3\05_Declarations.html#union_style_enum_case_clause) +> *聯合式(Union Style)的枚舉case子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **case** [*聯合式(Union Style)的枚舉case列表*](..\chapter3\05_Declarations.html#union_style_enum_case_list) +> *聯合式(Union Style)的枚舉case列表* → [*聯合式(Union Style)的case*](..\chapter3\05_Declarations.html#union_style_enum_case) | [*聯合式(Union Style)的case*](..\chapter3\05_Declarations.html#union_style_enum_case) **,** [*聯合式(Union Style)的枚舉case列表*](..\chapter3\05_Declarations.html#union_style_enum_case_list) +> *聯合式(Union Style)的case* → [*枚舉的case名*](..\chapter3\05_Declarations.html#enum_case_name) [*元組類型*](..\chapter3\03_Types.html#tuple_type) _可選_ +> *枚舉名* → [*標識符*](LexicalStructure.html#identifier) +> *枚舉的case名* → [*標識符*](LexicalStructure.html#identifier) +> *原始值式枚舉* → [*枚舉名*](..\chapter3\05_Declarations.html#enum_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ **:** [*類型標識*](..\chapter3\03_Types.html#type_identifier) **{** [*原始值式枚舉成員列表*](..\chapter3\05_Declarations.html#raw_value_style_enum_members) _可選_ **}** +> *原始值式枚舉成員列表* → [*原始值式枚舉成員*](..\chapter3\05_Declarations.html#raw_value_style_enum_member) [*原始值式枚舉成員列表*](..\chapter3\05_Declarations.html#raw_value_style_enum_members) _可選_ +> *原始值式枚舉成員* → [*聲明*](..\chapter3\05_Declarations.html#declaration) | [*原始值式枚舉case子句*](..\chapter3\05_Declarations.html#raw_value_style_enum_case_clause) +> *原始值式枚舉case子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **case** [*原始值式枚舉case列表*](..\chapter3\05_Declarations.html#raw_value_style_enum_case_list) +> *原始值式枚舉case列表* → [*原始值式枚舉case*](..\chapter3\05_Declarations.html#raw_value_style_enum_case) | [*原始值式枚舉case*](..\chapter3\05_Declarations.html#raw_value_style_enum_case) **,** [*原始值式枚舉case列表*](..\chapter3\05_Declarations.html#raw_value_style_enum_case_list) +> *原始值式枚舉case* → [*枚舉的case名*](..\chapter3\05_Declarations.html#enum_case_name) [*原始值賦值*](..\chapter3\05_Declarations.html#raw_value_assignment) _可選_ +> *原始值賦值* → **=** [*字面量*](LexicalStructure.html#literal) + + +##結構體聲明 + +使用結構體聲明可以在你的程序裡引入一個結構體類型。結構體聲明使用struct關鍵字,遵循如下的形式: + +> struct `structure name`: `adopted protocols` { +> `declarations` +> } + +結構體內包含零或多個聲明。這些聲明可以包括存儲型和計算型屬性,靜態屬性,實例方法,靜態方法,構造器, +類型別名,甚至其他結構體,類,和枚舉聲明。結構體聲明不能包含析構器或者協議聲明。詳細討論和包含多種結構體 +聲明的實例,參見類和結構體一節。 + +結構體可以包含任意數量的協議,但是不能繼承自類,枚舉或者其他結構體。 + +有三種方法可以創建一個聲明過的結構體實例: + +-調用結構體內聲明的構造器,參照構造器(initializers)一節。 + +—如果沒有聲明構造器,調用結構體的逐個構造器,詳情參見Memberwise Initializers for Structure Types. + +—如果沒有聲明析構器,結構體的所有屬性都有初始值,調用結構體的默認構造器,詳情參見默認構造器(Default Initializers). + +結構體的構造過程參見初始化(initiaization)一節。 + +結構體實例屬性可以用點(.)來獲得,詳情參見獲得屬性(Accessing Properties)一節。 + +結構體是值類型;結構體的實例在被賦予變量或常量,被函數調用時被複製。獲得關於值類型更多信息,參見 +結構體和枚舉都是值類型(Structures and Enumerations Are Value Types)一節。 + +你可以使用擴展聲明來擴展結構體類型的行為,參見擴展聲明(Extension Declaration). + +> 結構體聲明語法 +> *結構體聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **struct** [*結構體名稱*](..\chapter3\05_Declarations.html#struct_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*結構體主體*](..\chapter3\05_Declarations.html#struct_body) +> *結構體名稱* → [*標識符*](LexicalStructure.html#identifier) +> *結構體主體* → **{** [*聲明(Declarations)列表*](..\chapter3\05_Declarations.html#declarations) _可選_ **}** + + +##類聲明 + +你可以在你的程序中使用類聲明來引入一個類。類聲明使用關鍵字class,遵循如下的形式: + +> class `class name`: `superclass`, `adopted protocols` { +> `declarations` +> } + +一個類內包含零或多個聲明。這些聲明可以包括存儲型和計算型屬性,實例方法,類方法,構造器,單獨的析構器方法, +類型別名,甚至其他結構體,類,和枚舉聲明。類聲明不能包含協議聲明。詳細討論和包含多種類聲明的實例,參見類和 +結構體一節。 + +一個類只能繼承一個父類,超類,但是可以包含任意數量的協議。這些超類第一次在type-inheritance-clause出現,遵循任意協議。 + +正如在初始化聲明(Initializer Declaration)談及的那樣,類可以有指定(designated)和方便(convenience)構造器。當你聲明任一種構造器時, +你可以使用required變量來標記構造器,要求任意子類來重寫它。指定類的構造器必須初始化類所有的已聲明的屬性, +它必須在子類構造器調用前被執行。 + +類可以重寫屬性,方法和它的超類的構造器。重寫的方法和屬性必須以override標注。 + +雖然超類的屬性和方法聲明可以被當前類繼承,但是超類聲明的指定構造器卻不能。這意味著,如果當前類重寫了超類 +的所有指定構造器,它就繼承了超類的方便構造器。Swift的類並不是繼承自一個全局基礎類。 + +有兩種方法來創建已聲明的類的實例: + +- 調用類的一個構造器,參見構造器(initializers)。 +- 如果沒有聲明構造器,而且類的所有屬性都被賦予了初始值,調用類的默認構造器,參見默認構造器(default initializers). + +類實例屬性可以用點(.)來獲得,詳情參見獲得屬性(Accessing Properties)一節。 + +類是引用類型;當被賦予常量或變量,函數調用時,類的實例是被引用,而不是複製。獲得更多關於引用類型的信息, +結構體和枚舉都是值類型(Structures and Enumerations Are Value Types)一節。 + +你可以使用擴展聲明來擴展類的行為,參見擴展聲明(Extension Declaration). + +> 類聲明語法 +> *類聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **class** [*類名*](..\chapter3\05_Declarations.html#class_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*類主體*](..\chapter3\05_Declarations.html#class_body) +> *類名* → [*標識符*](LexicalStructure.html#identifier) +> *類主體* → **{** [*聲明(Declarations)列表*](..\chapter3\05_Declarations.html#declarations) _可選_ **}** + + +##協議聲明(translated by 小一) + +一個協議聲明為你的程序引入一個命名了的協議類型。協議聲明使用 `protocol` 關鍵詞來進行聲明並有下面這樣的形式: + +> protocol `protocol name`: `inherited protocols` { +> `protocol member declarations` +> } + +協議的主體包含零或多個協議成員聲明,這些成員描述了任何採用該協議必須滿足的一致性要求。特別的,一個協議可以聲明必須實現某些屬性、方法、初始化程序及下標腳本的一致性類型。協議也可以聲明專用種類的類型別名,叫做關聯類型,它可以指定協議的不同聲明之間的關係。協議成員聲明會在下面的詳情裡進行討論。 + +協議類型可以從很多其它協議那繼承。當一個協議類型從其它協議那繼承的時候,來自其它協議的所有要求就集合了,而且從當前協議繼承的任何類型必須符合所有的這些要求。對於如何使用協議繼承的例子,查看[協議繼承](../chapter2/21_Protocols.html#protocol_inheritance) + +> 注意: +你也可以使用協議合成類型集合多個協議的一致性要求,詳情參見[協議合成類型](../chapter3/03_Types.html#protocol_composition_type)和[協議合成](../chapter2/21_Protocols.html#protocol_composition) + +你可以通過採用在類型的擴展聲明中的協議來為之前聲明的類型添加協議一致性。在擴展中你必須實現所有採用協議的要求。如果該類型已經實現了所有的要求,你可以讓這個擴展聲明的主題留空。 + +默認地,符合某一個協議的類型必須實現所有聲明在協議中的屬性、方法和下標腳本。也就是說,你可以用`optional`屬性標注這些協議成員聲明以指定它們的一致性類型實現是可選的。`optional`屬性僅僅可以用於使用`objc`屬性標記過的協議。這樣的結果就是僅僅類類型可以採用並符合包含可選成員要求的協議。更多關於如何使用`optional`屬性的信息及如何訪問可選協議成員的指導——比如當你不能肯定是否一致性的類型實現了它們——參見[可選協議要求](../chapter2/21_Protocols.html#optional_protocol_requirements) + +為了限制協議的採用僅僅針對類類型,需要使用`class_protocol`屬性標記整個協議聲明。任意繼承自標記有`class_protocol`屬性協議的協議都可以智能地僅能被類類型採用。 + +>注意: +如果協議已經用`object`屬性標記了,`class_protocol`屬性就隱性地應用於該協議;沒有必要再明確地使用`class_protocol`屬性來標記該協議了。 + +協議是命名的類型,因此它們可以以另一個命名類型出現在你代碼的所有地方,就像[協議類型](../chapter2/21_Protocols.html#protocols_as_types)裡討論的那樣。然而你不能構造一個協議的實例,因為協議實際上不提供它們指定的要求的實現。 + +你可以使用協議來聲明一個類的代理的方法或者應該實現的結構,就像[委託(代理)模式](../chapter2/21_Protocols.html#delegation)描述的那樣。 + +> 協議(Protocol)聲明語法 +> *協議聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **protocol** [*協議名*](..\chapter3\05_Declarations.html#protocol_name) [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*協議主體*](..\chapter3\05_Declarations.html#protocol_body) +> *協議名* → [*標識符*](LexicalStructure.html#identifier) +> *協議主體* → **{** [*協議成員聲明(Declarations)列表*](..\chapter3\05_Declarations.html#protocol_member_declarations) _可選_ **}** +> *協議成員聲明* → [*協議屬性聲明*](..\chapter3\05_Declarations.html#protocol_property_declaration) +> *協議成員聲明* → [*協議方法聲明*](..\chapter3\05_Declarations.html#protocol_method_declaration) +> *協議成員聲明* → [*協議構造器聲明*](..\chapter3\05_Declarations.html#protocol_initializer_declaration) +> *協議成員聲明* → [*協議附屬腳本聲明*](..\chapter3\05_Declarations.html#protocol_subscript_declaration) +> *協議成員聲明* → [*協議關聯類型聲明*](..\chapter3\05_Declarations.html#protocol_associated_type_declaration) +> *協議成員聲明(Declarations)列表* → [*協議成員聲明*](..\chapter3\05_Declarations.html#protocol_member_declaration) [*協議成員聲明(Declarations)列表*](..\chapter3\05_Declarations.html#protocol_member_declarations) _可選_ + + +###協議屬性聲明 + +協議聲明了一致性類型必須在協議聲明的主體裡通過引入一個協議屬性聲明來實現一個屬性。協議屬性聲明有一種特殊的類型聲明形式: + +> var `property name`: `type` { get set } + +同其它協議成員聲明一樣,這些屬性聲明僅僅針對符合該協議的類型聲明了`getter`和`setter`要求。結果就是你不需要在協議裡它被聲明的地方實現`getter`和`setter`。 + +`getter`和`setter`要求可以通過一致性類型以各種方式滿足。如果屬性聲明包含`get`和`set`關鍵詞,一致性類型就可以用可讀寫(實現了`getter`和`setter`)的存儲型變量屬性或計算型屬性,但是屬性不能以常量屬性或只讀計算型屬性實現。如果屬性聲明僅僅包含`get`關鍵詞的話,它可以作為任意類型的屬性被實現。比如說實現了協議的屬性要求的一致性類型,參見[屬性要求](../chapter2/21_Protocols.html#property_requirements) + +更多參見[變量聲明](../chapter3/05_Declarations.html#variable_declaration) + +> 協議屬性聲明語法 +> *協議屬性聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*getter-setter關鍵字(Keyword)塊*](..\chapter3\05_Declarations.html#getter_setter_keyword_block) + +###協議方法聲明 + +協議聲明了一致性類型必須在協議聲明的主體裡通過引入一個協議方法聲明來實現一個方法. +協議方法聲明和函數方法聲明有著相同的形式,包含如下兩條規則:它們不包括函數體,你不能在類的聲明內為它們的 +參數提供初始值.舉例來說,符合的類型執行協議必需的方法。參見必需方法一節。 + +使用關鍵字class可以在協議聲明中聲明一個類或必需的靜態方法。執行這些方法的類也用關鍵字class聲明。 +相反的,執行這些方法的結構體必須以關鍵字static聲明。如果你想使用擴展方法,在擴展類時使用class關鍵字, +在擴展結構體時使用static關鍵字。 + +更多請參閱函數聲明。 + +> 協議方法聲明語法 +> *協議方法聲明* → [*函數頭*](..\chapter3\05_Declarations.html#function_head) [*函數名*](..\chapter3\05_Declarations.html#function_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*函數簽名(Signature)*](..\chapter3\05_Declarations.html#function_signature) + +###協議構造器聲明 + +協議聲明了一致性類型必須在協議聲明的主體裡通過引入一個協議構造器聲明來實現一個構造器。協議構造器聲明 +除了不包含構造器體外,和構造器聲明有著相同的形式, + +更多請參閱構造器聲明。 + +> 協議構造器聲明語法 +> *協議構造器聲明* → [*構造器頭(Head)*](..\chapter3\05_Declarations.html#initializer_head) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) + +###協議下標腳本聲明 + +協議聲明了一致性類型必須在協議聲明的主體裡通過引入一個協議下標腳本聲明來實現一個下標腳本。協議屬性聲明 +對下標腳本聲明有一個特殊的形式: + +> subscript (`parameters`) -> `return type` { get set } + +下標腳本聲明只為和協議一致的類型聲明了必需的最小數量的的getter和setter。如果下標腳本申明包含get和set關鍵字, +一致的類型也必須有一個getter和setter語句。如果下標腳本聲明值包含get關鍵字,一致的類型必須至少包含一個 +getter語句,可以選擇是否包含setter語句。 + +更多參閱下標腳本聲明。 + +> 協議附屬腳本聲明語法 +> *協議附屬腳本聲明* → [*附屬腳本頭(Head)*](..\chapter3\05_Declarations.html#subscript_head) [*附屬腳本結果(Result)*](..\chapter3\05_Declarations.html#subscript_result) [*getter-setter關鍵字(Keyword)塊*](..\chapter3\05_Declarations.html#getter_setter_keyword_block) + +###協議相關類型聲明 + +協議聲明相關類型使用關鍵字typealias。相關類型為作為協議聲明的一部分的類型提供了一個別名。相關類型和參數 +語句中的類型參數很相似,但是它們在聲明的協議中包含self關鍵字。在這些語句中,self指代和協議一致的可能的類型。 +獲得更多信息和例子,查看相關類型或類型別名聲明。 + +> 協議關聯類型聲明語法 +> *協議關聯類型聲明* → [*類型別名頭(Head)*](..\chapter3\05_Declarations.html#typealias_head) [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*類型別名賦值*](..\chapter3\05_Declarations.html#typealias_assignment) _可選_ + + +##構造器聲明 + +構造器聲明會為程序內的類,結構體或枚舉引入構造器。構造器使用關鍵字Init來聲明,遵循兩條基本形式。 + +結構體,枚舉,類可以有任意數量的構造器,但是類的構造器的規則和行為是不一樣的。不像結構體和枚舉那樣,類 +有兩種結構體,designed initializers 和convenience initializers,參見構造器一節。 + +如下的形式聲明了結構體,枚舉和類的指定構造器: + +> init(`parameters`) { +> `statements` +> } + +類的指定構造器將類的所有屬性直接初始化。如果類有超類,它不能調用該類的其他構造器,它只能調用超類的一個 +指定構造器。如果該類從它的超類處繼承了任何屬性,這些屬性在當前類內被賦值或修飾時,必須調用一個超類的 +指定構造器。 + +指定構造器可以在類聲明的上下文中聲明,因此它不能用擴展聲明的方法加入一個類中。 + +結構體和枚舉的構造器可以調用其他的已聲明的構造器,委託其中一個或所有的構造器進行初始化過程。 + +以關鍵字convenience來聲明一個類的便利構造器: + +> convenience init(`parameters`) { +> `statements` +> } + +便利構造器可以將初始化過程委託給另一個便利構造器或類的一個指定構造器。這意味著,類的初始化過程必須 +以一個將所有類屬性完全初始化的指定構造器的調用作為結束。便利構造器不能調用超類的構造器。 + +你可以使用required關鍵字,將便利構造器和指定構造器標記為每個子類的構造器都必須擁有的。因為指定構造器 +不被子類繼承,它們必須被立即執行。當子類直接執行所有超類的指定構造器(或使用便利構造器重寫指定構造器)時, +必需的便利構造器可以被隱式的執行,亦可以被繼承。不像方法,下標腳本那樣,你不需要為這些重寫的構造器標注 +overrride關鍵字。 + +查看更多關於不同聲明方法的構造器的例子,參閱構造過程一節。 + +> 構造器聲明語法 +> *構造器聲明* → [*構造器頭(Head)*](..\chapter3\05_Declarations.html#initializer_head) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) [*構造器主體*](..\chapter3\05_Declarations.html#initializer_body) +> *構造器頭(Head)* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **convenience** _可選_ **init** +> *構造器主體* → [*代碼塊*](..\chapter3\05_Declarations.html#code_block) + + +##析構聲明 + +析構聲明為類聲明了一個析構器。析構器沒有參數,遵循如下的格式: + +> deinit { +> `statements` +> } + +當類沒有任何語句時將要被釋放時,析構器會自動的被調用。析構器在類的聲明體內只能被聲明一次——但是不能在 +類的擴展聲明內,每個類最多只能有一個。 + +子類繼承了它的超類的析構器,在子類將要被釋放時隱式的調用。子類在所有析構器被執行完畢前不會被釋放。 + +析構器不會被直接調用。 + +查看例子和如何在類的聲明中使用析構器,參見析構過程一節。 + +> 析構器聲明語法 +> *析構器聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **deinit** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) + + +##擴展聲明 + +擴展聲明用於擴展一個現存的類,結構體,枚舉的行為。擴展聲明以關鍵字extension開始,遵循如下的規則: + +> extension `type`: `adopted protocols` { +> `declarations` +> } + +一個擴展聲明體包括零個或多個聲明。這些聲明可以包括計算型屬性,計算型靜態屬性,實例方法,靜態和類方法,構造器, +下標腳本聲明,甚至其他結構體,類,和枚舉聲明。擴展聲明不能包含析構器,協議聲明,存儲型屬性,屬性監測器或其他 +的擴展屬性。詳細討論和查看包含多種擴展聲明的實例,參見擴展一節。 + +擴展聲明可以向現存的類,結構體,枚舉內添加一致的協議。擴展聲明不能向一個類中添加繼承的類,因此 +type-inheritance-clause是一個只包含協議列表的擴展聲明。 + +屬性,方法,現存類型的構造器不能被它們類型的擴展所重寫。 + +擴展聲明可以包含構造器聲明,這意味著,如果你擴展的類型在其他模塊中定義,構造器聲明必須委託另一個在 +那個模塊裡聲明的構造器來恰當的初始化。 + +> 擴展(Extension)聲明語法 +> *擴展聲明* → **extension** [*類型標識*](..\chapter3\03_Types.html#type_identifier) [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*extension-body*](..\chapter3\05_Declarations.html#extension_body) +> *extension-body* → **{** [*聲明(Declarations)列表*](..\chapter3\05_Declarations.html#declarations) _可選_ **}** + + +##下標腳本聲明(translated by 林) + +附屬腳本用於向特定類型添加附屬腳本支持,通常為訪問集合,列表和序列的元素時提供語法便利。附屬腳本聲明使用關鍵字`subscript`,聲明形式如下: + +> subscript (`parameter`) -> (return type){ +> get{ +> `statements` +> } +> set(`setter name`){ +> `statements` +> } +> } + +附屬腳本聲明只能在類,結構體,枚舉,擴展和協議聲明的上下文進行聲明。 + +_變量(parameters)_指定一個或多個用於在相關類型的下標腳本中訪問元素的索引(例如,表達式`object[i]`中的`i`)。儘管用於元素訪問的索引可以是任意類型的,但是每個變量必須包含一個用於指定每種索引類型的類型標注。_返回類型(return type)_指定被訪問的元素的類型。 + +和計算性屬性一樣,下標腳本聲明支持對訪問元素的讀寫操作。getter用於讀取值,setter用於寫入值。setter子句是可選的,當僅需要一個getter子句時,可以將二者都忽略且直接返回請求的值即可。也就是說,如果使用了setter子句,就必須使用getter子句。 + +setter的名字和封閉的括號是可選的。如果使用了setter名稱,它會被當做傳給setter的變量的名稱。如果不使用setter名稱,那麼傳給setter的變量的名稱默認是`value`。setter名稱的類型必須與_返回類型(return type)_的類型相同。 + +可以在下標腳本聲明的類型中,可以重載下標腳本,只要_變量(parameters)_或_返回類型(return type)_與先前的不同即可。此時,必須使用`override`關鍵字聲明那個被覆蓋的下標腳本。(註:好亂啊!到底是重載還是覆蓋?!) + +同樣可以在協議聲明的上下文中聲明下標腳本,[Protocol Subscript Declaration](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_619)中有所描述。 + +更多關於下標腳本和下標腳本聲明的例子,請參考[Subscripts](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Subscripts.html#//apple_ref/doc/uid/TP40014097-CH16-XID_393)。 + +> 附屬腳本聲明語法 +> *附屬腳本聲明* → [*附屬腳本頭(Head)*](..\chapter3\05_Declarations.html#subscript_head) [*附屬腳本結果(Result)*](..\chapter3\05_Declarations.html#subscript_result) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *附屬腳本聲明* → [*附屬腳本頭(Head)*](..\chapter3\05_Declarations.html#subscript_head) [*附屬腳本結果(Result)*](..\chapter3\05_Declarations.html#subscript_result) [*getter-setter塊*](..\chapter3\05_Declarations.html#getter_setter_block) +> *附屬腳本聲明* → [*附屬腳本頭(Head)*](..\chapter3\05_Declarations.html#subscript_head) [*附屬腳本結果(Result)*](..\chapter3\05_Declarations.html#subscript_result) [*getter-setter關鍵字(Keyword)塊*](..\chapter3\05_Declarations.html#getter_setter_keyword_block) +> *附屬腳本頭(Head)* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **subscript** [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) +> *附屬腳本結果(Result)* → **->** [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*類型*](..\chapter3\03_Types.html#type) + + +##運算符聲明(translated by 林) + +運算符聲明會向程序中引入中綴、前綴或後綴運算符,它使用上下文關鍵字`operator`聲明。 +可以聲明三種不同的綴性:中綴、前綴和後綴。操作符的綴性描述了操作符與它的操作數的相對位置。 +運算符聲明有三種基本形式,每種綴性各一種。運算符的綴性通過在`operator`和運算符之間添加上下文關鍵字`infix`,`prefix`或`postfix`來指定。每種形式中,運算符的名字只能包含[Operators](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_871)中定義的運算符字符。 + +下面的這種形式聲明了一個新的中綴運算符: +> operator infix `operator name`{ +> previewprecedence `precedence level` +> associativity `associativity` +> } + +_中綴_運算符是二元運算符,它可以被置於兩個操作數之間,比如表達式`1 + 2` 中的加法運算符(`+`)。 + +中綴運算符可以可選地指定優先級,結合性,或兩者同時指定。 + +運算符的_優先級_可以指定在沒有括號包圍的情況下,運算符與它的操作數如何緊密綁定的。可以使用上下文關鍵字`precedence`並_優先級(precedence level)_一起來指定一個運算符的優先級。_優先級_可以是0到255之間的任何一個數字(十進制整數);與十進制整數字面量不同的是,它不可以包含任何下劃線字符。儘管優先級是一個特定的數字,但它僅用作與另一個運算符比較(大小)。也就是說,一個操作數可以同時被兩個運算符使用時,例如`2 + 3 * 5`,優先級更高的運算符將優先與操作數綁定。 + +運算符的_結合性_可以指定在沒有括號包圍的情況下,優先級相同的運算符以何種順序被分組的。可以使用上下文關鍵字`associativity`並_結合性(associativity)_一起來指定一個運算符的結合性,其中_結合性_可以說是上下文關鍵字`left`,`right`或`none`中的任何一個。左結合運算符以從左到右的形式分組。例如,減法運算符(`-`)具有左結合性,因此`4 - 5 - 6`被以`(4 - 5) - 6`的形式分組,其結果為`-7`。 +右結合運算符以從右到左的形式分組,對於設置為`none`的非結合運算符,它們不以任何形式分組。具有相同優先級的非結合運算符,不可以互相鄰接。例如,表達式`1 < 2 < 3`非法的。 + +聲明時不指定任何優先級或結合性的中綴運算符,它們的優先級會被初始化為100,結合性被初始化為`none`。 + +下面的這種形式聲明了一個新的前綴運算符: +> operator prefix `operator name`{} + +緊跟在操作數前邊的_前綴運算符(prefix operator)_是一元運算符,例如表達式`++i`中的前綴遞增運算符(`++`)。 + +前綴運算符的聲明中不指定優先級。前綴運算符是非結合的。 + +下面的這種形式聲明了一個新的後綴運算符: + +> operator postfix `operator name`{} + +緊跟在操作數後邊的_後綴運算符(postfix operator)_是一元運算符,例如表達式`i++`中的前綴遞增運算符(`++`)。 + +和前綴運算符一樣,後綴運算符的聲明中不指定優先級。後綴運算符是非結合的。 + +聲明了一個新的運算符以後,需要聲明一個跟這個運算符同名的函數來實現這個運算符。如何實現一個新的運算符,請參考[Custom Operators](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_48)。 + +> 運算符聲明語法 +> *運算符聲明* → [*前置運算符聲明*](..\chapter3\05_Declarations.html#prefix_operator_declaration) | [*後置運算符聲明*](..\chapter3\05_Declarations.html#postfix_operator_declaration) | [*中置運算符聲明*](..\chapter3\05_Declarations.html#infix_operator_declaration) +> *前置運算符聲明* → **運算符** **prefix** [*運算符*](LexicalStructure.html#operator) **{** **}** +> *後置運算符聲明* → **運算符** **postfix** [*運算符*](LexicalStructure.html#operator) **{** **}** +> *中置運算符聲明* → **運算符** **infix** [*運算符*](LexicalStructure.html#operator) **{** [*中置運算符屬性*](..\chapter3\05_Declarations.html#infix_operator_attributes) _可選_ **}** +> *中置運算符屬性* → [*優先級子句*](..\chapter3\05_Declarations.html#precedence_clause) _可選_ [*結和性子句*](..\chapter3\05_Declarations.html#associativity_clause) _可選_ +> *優先級子句* → **precedence** [*優先級水平*](..\chapter3\05_Declarations.html#precedence_level) +> *優先級水平* → 數值 0 到 255 +> *結和性子句* → **associativity** [*結和性*](..\chapter3\05_Declarations.html#associativity) +> *結和性* → **left** | **right** | **none** diff --git a/source-tw/chapter3/06_Attributes.md b/source-tw/chapter3/06_Attributes.md new file mode 100644 index 00000000..11d88a10 --- /dev/null +++ b/source-tw/chapter3/06_Attributes.md @@ -0,0 +1,124 @@ +> 翻譯:[Hawstein](https://github.com/Hawstein) +> 校對:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai) + +# 特性 +----------------- + +本頁內容包括: + +- [聲明特性](#declaration_attributes) +- [類型特性](#type_attributes) + +特性提供了關於聲明和類型的更多信息。在Swift中有兩類特性,用於修飾聲明的以及用於修飾類型的。例如,`required`特性,當應用於一個類的指定或便利初始化器聲明時,表明它的每個子類都必須實現那個初始化器。再比如`noreturn`特性,當應用於函數或方法類型時,表明該函數或方法不會返回到它的調用者。 + +通過以下方式指定一個特性:符號`@`後面跟特性名,如果包含參數,則把參數帶上: + +> @`attribute name` +> @`attribute name`(`attribute arguments`) + +有些聲明特性通過接收參數來指定特性的更多信息以及它是如何修飾一個特定的聲明的。這些特性的參數寫在小括號內,它們的格式由它們所屬的特性來定義。 + + +## 聲明特性 + +聲明特性只能應用於聲明。然而,你也可以將`noreturn`特性應用於函數或方法類型。 + +`assignment` + +該特性用於修飾重載了復合賦值運算符的函數。重載了復合賦值運算符的函數必需將它們的初始輸入參數標記為`inout`。如何使用`assignment`特性的一個例子,請見:[復合賦值運算符]()。 + +`class_protocol` + +該特性用於修飾一個協議表明該協議只能被類類型採用[待改:adopted]。 + +如果你用`objc`特性修飾一個協議,`class_protocol`特性就會隱式地應用到該協議,因此無需顯式地用`class_protocol`特性標記該協議。 + +`exported` + +該特性用於修飾導入聲明,以此來導出已導入的模塊,子模塊,或當前模塊的聲明。如果另一個模塊導入了當前模塊,那麼那個模塊可以訪問當前模塊的導出項。 + +`final` + +該特性用於修飾一個類或類中的屬性,方法,以及下標成員。如果用它修飾一個類,那麼這個類則不能被繼承。如果用它修飾類中的屬性,方法或下標,則表示在子類中,它們不能被重寫。 + +`lazy` + +該特性用於修飾類或結構體中的存儲型變量屬性,表示該屬性的初始值最多只被計算和存儲一次,且發生在第一次訪問它時。如何使用`lazy`特性的一個例子,請見:[惰性存儲型屬性]()。 + +`noreturn` + +該特性用於修飾函數或方法聲明,表明該函數或方法的對應類型,`T`,是`@noreturn T`。你可以用這個特性修飾函數或方法的類型,這樣一來,函數或方法就不會返回到它的調用者中去。 + +對於一個沒有用`noreturn`特性標記的函數或方法,你可以將它重寫(override)為用該特性標記的。相反,對於一個已經用`noreturn`特性標記的函數或方法,你則不可以將它重寫為沒使用該特性標記的。相同的規則試用於當你在一個comforming類型中實現一個協議方法時。 + +`NSCopying` + +該特性用於修飾一個類的存儲型變量屬性。該特性將使屬性的setter與屬性值的一個副本合成,由`copyWithZone`方法返回,而不是屬性本身的值。該屬性的類型必需遵循`NSCopying`協議。 + +`NSCopying`特性的行為與Objective-C中的`copy`特性相似。 + +`NSManaged` + +該特性用於修飾`NSManagedObject`子類中的存儲型變量屬性,表明屬性的存儲和實現由Core Data在運行時基於相關實體描述動態提供。 + +`objc` + +該特性用於修飾任意可以在Objective-C中表示的聲明,比如,非嵌套類,協議,類和協議中的屬性和方法(包含getter和setter),初始化器,析構器,以下下標。`objc`特性告訴編譯器該聲明可以在Objective-C代碼中使用。 + +如果你將`objc`特性應用於一個類或協議,它也會隱式地應用於那個類或協議的成員。對於標記了`objc`特性的類,編譯器會隱式地為它的子類添加`objc`特性。標記了`objc`特性的協議不能繼承自沒有標記`objc`的協議。 + +`objc`特性有一個可選的參數,由標記符組成。當你想把`objc`所修飾的實體以一個不同的名字暴露給Objective-C,你就可以使用這個特性參數。你可以使用這個參數來命名類,協議,方法,getters,setters,以及初始化器。下面的例子把`ExampleClass`中`enabled`屬性的getter暴露給Objective-C,名字是`isEnabled`,而不是它原來的屬性名。 + +```swift +@objc +class ExampleClass { + var enabled: Bool { + @objc(isEnabled) get { + // Return the appropriate value + } + } +} +``` + +`optional` + +用該特性修飾協議的屬性,方法或下標成員,表示實現這些成員並不需要一致性類型(conforming type)。 + +你只能用`optional`特性修飾那些標記了`objc`特性的協議。因此,只有類類型可以adopt和comform to那些包含可選成員需求的協議。更多關於如何使用`optional`特性以及如何訪問可選協議成員的指導,例如,當你不確定一個conforming類型是否實現了它們,請見:[可選協議需求]()。 + +`required` + +用該特性修飾一個類的指定或便利初始化器,表示該類的所有子類都必需實現該初始化器。 + +加了該特性的指定初始化器必需顯式地實現,而便利初始化器既可顯式地實現,也可以在子類實現了超類所有指定初始化器後繼承而來(或者當子類使用便利初始化器重寫了指定初始化器)。 + +### Interface Builder使用的聲明特性 + +Interface Builder特性是Interface Builder用來與Xcode同步的聲明特性。Swift提供了以下的Interface Builder特性:`IBAction`,`IBDesignable`,`IBInspectable`,以及`IBOutlet`。這些特性與Objective-C中對應的特性在概念上是相同的。 + +`IBOutlet`和`IBInspectable`用於修飾一個類的屬性聲明;`IBAction`特性用於修飾一個類的方法聲明;`IBDesignable`用於修飾類的聲明。 + + +## 類型特性 + +類型特性只能用於修飾類型。然而,你也可以用`noreturn`特性去修飾函數或方法聲明。 + +`auto_closure` + +這個特性通過自動地將表達式封閉到一個無參數閉包中來延遲表達式的求值。使用該特性修飾無參的函數或方法類型,返回表達式的類型。一個如何使用`auto_closure`特性的例子,見[函數類型]() + +`noreturn` + +該特性用於修飾函數或方法的類型,表明該函數或方法不會返回到它的調用者中去。你也可以用它標記函數或方法的聲明,表示函數或方法的相應類型,`T`,是`@noreturn T`。 + +> 特性語法 +> *特性* → **@** [*特性名*](..\chapter3\06_Attributes.html#attribute_name) [*特性參數子句*](..\chapter3\06_Attributes.html#attribute_argument_clause) _可選_ +> *特性名* → [*標識符*](LexicalStructure.html#identifier) +> *特性參數子句* → **(** [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ **)** +> *特性(Attributes)列表* → [*特色*](..\chapter3\06_Attributes.html#attribute) [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ +> *平衡令牌列表* → [*平衡令牌*](..\chapter3\06_Attributes.html#balanced_token) [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ +> *平衡令牌* → **(** [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ **)** +> *平衡令牌* → **[** [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ **]** +> *平衡令牌* → **{** [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ **}** +> *平衡令牌* → **任意標識符, 關鍵字, 字面量或運算符** +> *平衡令牌* → **任意標點除了(, ), [, ], {, 或 }** diff --git a/source-tw/chapter3/07_Patterns.md b/source-tw/chapter3/07_Patterns.md new file mode 100644 index 00000000..17748586 --- /dev/null +++ b/source-tw/chapter3/07_Patterns.md @@ -0,0 +1,182 @@ +> 翻譯:[honghaoz](https://github.com/honghaoz) +> 校對:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai) + +# 模式(Patterns) +----------------- + +本頁內容包括: + +- [通配符模式(Wildcard Pattern)](#wildcard_pattern) +- [標識符模式(Identifier Pattern)](#identifier_pattern) +- [值綁定模式(Value-Binding Pattern)](#value-binding_pattern) +- [元組模式(Tuple Pattern)](#tuple_pattern) +- [枚舉用例模式(Enumeration Case Pattern)](#enumeration_case_pattern) +- [類型轉換模式(Type-Casting Patterns)](#type-casting_patterns) +- [表達式模式(Expression Pattern)](#expression_pattern) + +模式(pattern)代表了單個值或者復合值的結構。例如,元組`(1, 2)`的結構是逗號分隔的,包含兩個元素的列表。因為模式代表一種值的結構,而不是特定的某個值,你可以把模式和各種同類型的值匹配起來。比如,`(x, y)`可以匹配元組`(1, 2)`,以及任何含兩個元素的元組。除了將模式與一個值匹配外,你可以從合成值中提取出部分或全部,然後分別把各個部分和一個常量或變量綁定起來。 + +在Swift中,模式出現在變量和常量的聲明(在它們的左側),`for-in`語句和`switch`語句(在它們的case標籤)中。儘管任何模式都可以出現在`switch`語句的case標籤中,但在其他情況下,只有通配符模式(wildcard pattern),標識符模式(identifier pattern)和包含這兩種模式的模式才能出現。 + +你可以為通配符模式(wildcard pattern),標識符模式(identifier pattern)和元組模式(tuple pattern)指定類型註釋,用來限制這種模式只匹配某種類型的值。 + +> 模式(Patterns) 語法 +> *模式* → [*通配符模式*](..\chapter3\07_Patterns.html#wildcard_pattern) [*類型註解*](..\chapter3\03_Types.html#type_annotation) _可選_ +> *模式* → [*標識符模式*](..\chapter3\07_Patterns.html#identifier_pattern) [*類型註解*](..\chapter3\03_Types.html#type_annotati(Value Binding)on) _可選_ +> *模式* → [*值綁定模式*](..\chapter3\07_Patterns.html#value_binding_pattern) +> *模式* → [*元組模式*](..\chapter3\07_Patterns.html#tuple_pattern) [*類型註解*](..\chapter3\03_Types.html#type_annotation) _可選_ +> *模式* → [*enum-case-pattern*](..\chapter3\07_Patterns.html#enum_case_pattern) +> *模式* → [*type-casting-pattern*](..\chapter3\07_Patterns.html#type_casting_pattern) +> *模式* → [*表達式模式*](..\chapter3\07_Patterns.html#expression_pattern) + + +## 通配符模式(Wildcard Pattern) + +通配符模式匹配並忽略任何值,包含一個下劃線(_)。當你不關心被匹配的值時,可以使用此模式。例如,下面這段代碼進行了`1...3`的循環,並忽略了每次循環的值: + +```swift +for _ in 1...3 { + // Do something three times. +} +``` + +> 通配符模式語法 +> *通配符模式* → **_** + + +## 標識符模式(Identifier Pattern) + +標識符模式匹配任何值,並將匹配的值和一個變量或常量綁定起來。例如,在下面的常量申明中,`someValue`是一個標識符模式,匹配了類型是`Int`的`42`。 + +```swift +let someValue = 42 +``` + +當匹配成功時,`42`被綁定(賦值)給常量`someValue`。 + +當一個變量或常量申明的左邊是標識符模式時,此時,標識符模式是隱式的值綁定模式(value-binding pattern)。 + +> 標識符模式語法 +> *標識符模式* → [*標識符*](LexicalStructure.html#identifier) + + +## 值綁定模式(Value-Binding Pattern) + +值綁定模式綁定匹配的值到一個變量或常量。當綁定匹配值給常量時,用關鍵字`let`,綁定給變量時,用關鍵字`var`。 + +標識符模式包含在值綁定模式中,綁定新的變量或常量到匹配的值。例如,你可以分解一個元組的元素,並把每個元素綁定到相應的標識符模式中。 + +```swift +let point = (3, 2) +switch point { + // Bind x and y to the elements of point. +case let (x, y): + println("The point is at (\(x), \(y)).") +} +// prints "The point is at (3, 2).」 +``` + +在上面這個例子中,`let`將元組模式`(x, y)`分配到各個標識符模式。因為這種行為,`switch`語句中`case let (x, y):`和`case (let x, let y):`匹配的值是一樣的。 + +> 值綁定(Value Binding)模式語法 +> *值綁定模式* → **var** [*模式*](..\chapter3\07_Patterns.html#pattern) | **let** [*模式*](..\chapter3\07_Patterns.html#pattern) + + +## 元組模式(Tuple Pattern) + +元組模式是逗號分隔的列表,包含一個或多個模式,並包含在一對圓括號中。元組模式匹配相應元組類型的值。 + +你可以使用類型註釋來限制一個元組模式來匹配某種元組類型。例如,在常量申明`let (x, y): (Int, Int) = (1, 2)`中的元組模式`(x, y): (Int, Int)`,只匹配兩個元素都是`Int`這種類型的元組。如果僅需要限制一個元組模式中的某幾個元素,只需要直接對這幾個元素提供類型註釋即可。例如,在`let (x: String, y)`中的元組模式,只要某個元組類型是包含兩個元素,且第一個元素類型是`String`,則被匹配。 + +當元組模式被用在`for-in`語句或者變量或常量申明時,它可以包含通配符模式,標識符模式或者其他包含這兩種模式的模式。例如,下面這段代碼是不正確的,因為`(x, 0)`中的元素`0`是一個表達式模式: + +```swift +let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)] +// This code isn't valid. +for (x, 0) in points { + /* ... */ +} +``` + +對於只包含一個元素的元組,括號是不起作用的。模式匹配那個單個元素的類型。例如,下面是等效的: + +```swift +let a = 2 // a: Int = 2 +let (a) = 2 // a: Int = 2 +let (a): Int = 2 // a: Int = 2 +``` + +> 元組模式語法 +> *元組模式* → **(** [*元組模式元素列表*](..\chapter3\07_Patterns.html#tuple_pattern_element_list) _可選_ **)** +> *元組模式元素列表* → [*元組模式元素*](..\chapter3\07_Patterns.html#tuple_pattern_element) | [*元組模式元素*](..\chapter3\07_Patterns.html#tuple_pattern_element) **,** [*元組模式元素列表*](..\chapter3\07_Patterns.html#tuple_pattern_element_list) +> *元組模式元素* → [*模式*](..\chapter3\07_Patterns.html#pattern) + + +## 枚舉用例模式(Enumeration Case Pattern) + +枚舉用例模式匹配現有的枚舉類型的某種用例。枚舉用例模式僅在`switch`語句中的`case`標籤中出現。 + +如果你準備匹配的枚舉用例有任何關聯的值,則相應的枚舉用例模式必須指定一個包含每個關聯值元素的元組模式。關於使用`switch`語句來匹配包含關聯值枚舉用例的例子,請參閱`Associated Values`. + +> 枚舉用例模式語法 +> *enum-case-pattern* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) _可選_ **.** [*枚舉的case名*](..\chapter3\05_Declarations.html#enum_case_name) [*元組模式*](..\chapter3\07_Patterns.html#tuple_pattern) _可選_ + + +## 類型轉換模式(Type-Casting Patterns) + +有兩種類型轉換模式,`is`模式和`as`模式。這兩種模式均只出現在`switch`語句中的`case`標籤中。`is`模式和`as`模式有以下形式: + +> is `type` +> `pattern` as `type` + +`is`模式匹配一個值,如果這個值的類型在運行時(runtime)和`is`模式右邊的指定類型(或者那個類型的子類)是一致的。`is`模式和`is`操作符一樣,它們都進行類型轉換,但是拋棄了返回的類型。 + +`as`模式匹配一個值,如果這個值的類型在運行時(runtime)和`as`模式右邊的指定類型(或者那個類型的子類)是一致的。一旦匹配成功,匹配的值的類型被轉換成`as`模式左邊指定的模式。 + +關於使用`switch`語句來匹配`is`模式和`as`模式值的例子,請參閱`Type Casting for Any and AnyObject`。 + +> 類型轉換模式語法 +> *type-casting-pattern* → [*is模式*](..\chapter3\07_Patterns.html#is_pattern) | [*as模式*](..\chapter3\07_Patterns.html#as_pattern) +> *is模式* → **is** [*類型*](..\chapter3\03_Types.html#type) +> *as模式* → [*模式*](..\chapter3\07_Patterns.html#pattern) **as** [*類型*](..\chapter3\03_Types.html#type) + + +## 表達式模式(Expression Pattern) + +表達式模式代表了一個表達式的值。這個模式只出現在`switch`語句中的`case`標籤中。 + +由表達式模式所代表的表達式用Swift標準庫中的`~=`操作符與輸入表達式的值進行比較。如果`~=`操作符返回`true`,則匹配成功。默認情況下,`~=`操作符使用`==`操作符來比較兩個相同類型的值。它也可以匹配一個整數值與一個`Range`對像中的整數範圍,正如下面這個例子所示: + +```swift +let point = (1, 2) +switch point { +case (0, 0): + println("(0, 0) is at the origin.") +case (-2...2, -2...2): + println("(\(point.0), \(point.1)) is near the origin.") +default: + println("The point is at (\(point.0), \(point.1)).") +} +// prints "(1, 2) is near the origin.」 +``` + +你可以重載`~=`操作符來提供自定義的表達式行為。例如,你可以重寫上面的例子,以實現用字符串表達的點來比較`point`表達式。 + +```swift +// Overload the ~= operator to match a string with an integer +func ~=(pattern: String, value: Int) -> Bool { + return pattern == "\(value)" +} +switch point { +case ("0", "0"): + println("(0, 0) is at the origin.") +case ("-2...2", "-2...2"): + println("(\(point.0), \(point.1)) is near the origin.") +default: + println("The point is at (\(point.0), \(point.1)).") +} +// prints "(1, 2) is near the origin.」 +``` + +> 表達式模式語法 +> *表達式模式* → [*表達式*](..\chapter3\04_Expressions.html#expression) \ No newline at end of file diff --git a/source-tw/chapter3/08_Generic_Parameters_and_Arguments.md b/source-tw/chapter3/08_Generic_Parameters_and_Arguments.md new file mode 100644 index 00000000..3f878c48 --- /dev/null +++ b/source-tw/chapter3/08_Generic_Parameters_and_Arguments.md @@ -0,0 +1,106 @@ +> 翻譯:[fd5788](https://github.com/fd5788) +> 校對:[yankuangshi](https://github.com/yankuangshi), [stanzhai](https://github.com/stanzhai) + +# 泛型參數 +--------- + +本頁包含內容: + +- [泛型形參子句](#generic_parameter) +- [泛型實參子句](#generic_argument) + +本節涉及泛型類型、泛型函數以及泛型構造器的參數,包括形參和實參。聲明泛型類型、函數或構造器時,須指定相應的類型參數。類型參數相當於一個佔位符,當實例化泛型類型、調用泛型函數或泛型構造器時,就用具體的類型實參替代之。 + +關於 Swift 語言的泛型概述,見[泛型](../charpter2/22_Generics.md)(第二部分第22章)。 + + +## 泛型形參子句 + +泛型形參子句指定泛型類型或函數的類型形參,以及這些參數的關聯約束和要求。泛型形參子句用尖括號(<>)包住,並且有以下兩種形式: + +> <`generic parameter list`> +> <`generic parameter list` where `requirements`> + +泛型形參列表中泛型形參用逗號分開,每一個採用以下形式: + +> `type parameter` : `constrain` + +泛型形參由兩部分組成:類型形參及其後的可選約束。類型形參只是佔位符類型(如T,U,V,KeyType,ValueType等)的名字而已。你可以在泛型類型、函數的其餘部分或者構造器聲明,以及函數或構造器的簽名中使用它。 + +約束用於指明該類型形參繼承自某個類或者遵守某個協議或協議的一部分。例如,在下面的泛型中,泛型形參`T: Comparable`表示任何用於替代類型形參`T`的類型實參必須滿足`Comparable`協議。 + +```swift +func simpleMin(x: T, y: T) -> T { + if x < y { + return y + } + return x +} +``` + +如,`Int`和`Double`均滿足`Comparable`協議,該函數接受任何一種類型。與泛型類型相反,調用泛型函數或構造器時不需要指定泛型實參子句。類型實參由傳遞給函數或構造器的實參推斷而出。 + +```swift +simpleMin(17, 42) // T is inferred to be Int +simpleMin(3.14159, 2.71828) // T is inferred to be Double +``` + +## Where 子句 + +要想對類型形參及其關聯類型指定額外要求,可以在泛型形參列表之後添加`where`子句。`where`子句由關鍵字`where`及其後的用逗號分割的多個要求組成。 + +`where`子句中的要求用於指明該類型形參繼承自某個類或遵守某個協議或協議的一部分。儘管`where`子句有助於表達類型形參上的簡單約束(如`T: Comparable`等同於`T where T: Comparable`,等等),但是依然可以用來對類型形參及其關聯約束提供更複雜的約束。如,``表示泛型類型`T`繼承自類`C`且遵守協議`P`。 + +如上所述,可以強制約束類型形參的關聯類型遵守某個協議。``表示`T`遵守`Generator`協議,而且`T`的關聯類型`T.Element`遵守`Eauatable`協議(`T`有關聯類型是因為`Generator`聲明了`Element`,而`T`遵守`Generator`協議)。 + +也可以用操作符`==`來指定兩個類型等效的要求。例如,有這樣一個約束:`T`和`U`遵守`Generator`協議,同時要求它們的關聯類型等同,可以這樣來表達:``。 + +當然,替代類型形參的類型實參必須滿足所有類型形參所要求的約束和要求。 + +泛型函數或構造器可以重載,但在泛型形參子句中的類型形參必須有不同的約束或要求,抑或二者皆不同。當調用重載的泛型函數或構造器時,編譯器會用這些約束來決定調用哪個重載函數或構造器。 + +泛型類可以生成一個子類,但是這個子類也必須是泛型類。 + +> 泛型形參子句語法 +> *泛型參數子句* → **<** [*泛型參數列表*](GenericParametersAndArguments.html#generic_parameter_list) [*約束子句*](GenericParametersAndArguments.html#requirement_clause) _可選_ **>** +> *泛型參數列表* → [*泛形參數*](GenericParametersAndArguments.html#generic_parameter) | [*泛形參數*](GenericParametersAndArguments.html#generic_parameter) **,** [*泛型參數列表*](GenericParametersAndArguments.html#generic_parameter_list) +> *泛形參數* → [*類型名稱*](..\chapter3\03_Types.html#type_name) +> *泛形參數* → [*類型名稱*](..\chapter3\03_Types.html#type_name) **:** [*類型標識*](..\chapter3\03_Types.html#type_identifier) +> *泛形參數* → [*類型名稱*](..\chapter3\03_Types.html#type_name) **:** [*協議合成類型*](..\chapter3\03_Types.html#protocol_composition_type) +> *約束子句* → **where** [*約束列表*](GenericParametersAndArguments.html#requirement_list) +> *約束列表* → [*約束*](GenericParametersAndArguments.html#requirement) | [*約束*](GenericParametersAndArguments.html#requirement) **,** [*約束列表*](GenericParametersAndArguments.html#requirement_list) +> *約束* → [*一致性約束*](GenericParametersAndArguments.html#conformance_requirement) | [*同類型約束*](GenericParametersAndArguments.html#same_type_requirement) +> *一致性約束* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) **:** [*類型標識*](..\chapter3\03_Types.html#type_identifier) +> *一致性約束* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) **:** [*協議合成類型*](..\chapter3\03_Types.html#protocol_composition_type) +> *同類型約束* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) **==** [*類型標識*](..\chapter3\03_Types.html#type_identifier) + + + +## 泛型實參子句 + +泛型實參子句指定_泛型類型_的類型實參。泛型實參子句用尖括號(<>)包住,形式如下: + +> <`generic argument list`> + +泛型實參列表中類型實參有逗號分開。類型實參是實際具體類型的名字,用來替代泛型類型的泛型形參子句中的相應的類型形參。從而得到泛型類型的一個特化版本。如,Swift標準庫的泛型字典類型定義如下: + +```swift +struct Dictionary: Collection, DictionaryLiteralConvertible { + /* .. */ +} +``` + +泛型`Dictionary`類型的特化版本,`Dictionary`就是用具體的`String`和`Int`類型替代泛型類型`KeyType: Hashable`和`ValueType`產生的。每一個類型實參必須滿足它所替代的泛型形參的所有約束,包括任何`where`子句所指定的額外的要求。上面的例子中,類型形參`KeyType`要求滿足`Hashable`協議,因此`String`也必須滿足`Hashable`協議。 + +可以用本身就是泛型類型的特化版本的類型實參替代類型形參(假設已滿足合適的約束和要求)。例如,為了生成一個元素類型是整型數組的數組,可以用數組的特化版本`Array`替代泛型類型`Array`的類型形參`T`來實現。 + +```swift +let arrayOfArrays: Array> = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] +``` + +如[泛型形參子句](#generic_parameter)所述,不能用泛型實參子句來指定泛型函數或構造器的類型實參。 + +> 泛型實參子句語法 +> *(泛型參數子句Generic Argument Clause)* → **<** [*泛型參數列表*](GenericParametersAndArguments.html#generic_argument_list) **>** +> *泛型參數列表* → [*泛型參數*](GenericParametersAndArguments.html#generic_argument) | [*泛型參數*](GenericParametersAndArguments.html#generic_argument) **,** [*泛型參數列表*](GenericParametersAndArguments.html#generic_argument_list) +> *泛型參數* → [*類型*](..\chapter3\03_Types.html#type) \ No newline at end of file diff --git a/source-tw/chapter3/09_Summary_of_the_Grammar.md b/source-tw/chapter3/09_Summary_of_the_Grammar.md new file mode 100644 index 00000000..644d4a65 --- /dev/null +++ b/source-tw/chapter3/09_Summary_of_the_Grammar.md @@ -0,0 +1,743 @@ +> 翻譯:[stanzhai](https://github.com/stanzhai) +> 校對:[xielingwang](https://github.com/xielingwang) + +# 語法總結 +_________________ + +本頁包含內容: + +* [語句(Statements)](#statements) +* [泛型參數(Generic Parameters and Arguments)](#generic_parameters_and_arguments) +* [聲明(Declarations)](#declarations) +* [模式(Patterns)](#patterns) +* [特性(Attributes)](#attributes) +* [表達式(Expressions)](#expressions) +* [詞法結構(Lexical Structure)](#lexical_structure) +* [類型(Types)](#types) + + +## 語句 + +> 語句語法 +> *語句* → [*表達式*](..\chapter3\04_Expressions.html#expression) **;** _可選_ +> *語句* → [*聲明*](..\chapter3\05_Declarations.html#declaration) **;** _可選_ +> *語句* → [*循環語句*](..\chapter3\10_Statements.html#loop_statement) **;** _可選_ +> *語句* → [*分支語句*](..\chapter3\10_Statements.html#branch_statement) **;** _可選_ +> *語句* → [*標記語句(Labeled Statement)*](..\chapter3\10_Statements.html#labeled_statement) +> *語句* → [*控制轉移語句*](..\chapter3\10_Statements.html#control_transfer_statement) **;** _可選_ +> *多條語句(Statements)* → [*語句*](..\chapter3\10_Statements.html#statement) [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) _可選_ + + + +> 循環語句語法 +> *循環語句* → [*for語句*](..\chapter3\10_Statements.html#for_statement) +> *循環語句* → [*for-in語句*](..\chapter3\10_Statements.html#for_in_statement) +> *循環語句* → [*while語句*](..\chapter3\10_Statements.html#wheetatype類型ile_statement) +> *循環語句* → [*do-while語句*](..\chapter3\10_Statements.html#do_while_statement) + + + +> For 循環語法 +> *for語句* → **for** [*for初始條件*](..\chapter3\10_Statements.html#for_init) _可選_ **;** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ **;** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *for語句* → **for** **(** [*for初始條件*](..\chapter3\10_Statements.html#for_init) _可選_ **;** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ **;** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ **)** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *for初始條件* → [*變量聲明*](..\chapter3\05_Declarations.html#variable_declaration) | [*表達式列表*](..\chapter3\04_Expressions.html#expression_list) + + + +> For-In 循環語法 +> *for-in語句* → **for** [*模式*](..\chapter3\07_Patterns.html#pattern) **in** [*表達式*](..\chapter3\04_Expressions.html#expression) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) + + + +> While 循環語法 +> *while語句* → **while** [*while條件*](..\chapter3\10_Statements.html#while_condition) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *while條件* → [*表達式*](..\chapter3\04_Expressions.html#expression) | [*聲明*](..\chapter3\05_Declarations.html#declaration) + + + +> Do-While 循環語法 +> *do-while語句* → **do** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) **while** [*while條件*](..\chapter3\10_Statements.html#while_condition) + + + +> 分支語句語法 +> *分支語句* → [*if語句*](..\chapter3\10_Statements.html#if_statement) +> *分支語句* → [*switch語句*](..\chapter3\10_Statements.html#switch_statement) + + + +> If語句語法 +> *if語句* → **if** [*if條件*](..\chapter3\10_Statements.html#if_condition) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) [*else子句(Clause)*](..\chapter3\10_Statements.html#else_clause) _可選_ +> *if條件* → [*表達式*](..\chapter3\04_Expressions.html#expression) | [*聲明*](..\chapter3\05_Declarations.html#declaration) +> *else子句(Clause)* → **else** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) | **else** [*if語句*](..\chapter3\10_Statements.html#if_statement) + + + +> Switch語句語法 +> *switch語句* → **switch** [*表達式*](..\chapter3\04_Expressions.html#expression) **{** [*SwitchCase列表*](..\chapter3\10_Statements.html#switch_cases) _可選_ **}** +> *SwitchCase列表* → [*SwitchCase*](..\chapter3\10_Statements.html#switch_case) [*SwitchCase列表*](..\chapter3\10_Statements.html#switch_cases) _可選_ +> *SwitchCase* → [*case標籤*](..\chapter3\10_Statements.html#case_label) [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) | [*default標籤*](..\chapter3\10_Statements.html#default_label) [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) +> *SwitchCase* → [*case標籤*](..\chapter3\10_Statements.html#case_label) **;** | [*default標籤*](..\chapter3\10_Statements.html#default_label) **;** +> *case標籤* → **case** [*case項列表*](..\chapter3\10_Statements.html#case_item_list) **:** +> *case項列表* → [*模式*](..\chapter3\07_Patterns.html#pattern) [*guard-clause*](..\chapter3\10_Statements.html#guard_clause) _可選_ | [*模式*](..\chapter3\07_Patterns.html#pattern) [*guard-clause*](..\chapter3\10_Statements.html#guard_clause) _可選_ **,** [*case項列表*](..\chapter3\10_Statements.html#case_item_list) +> *default標籤* → **default** **:** +> *guard-clause* → **where** [*guard-expression*](..\chapter3\10_Statements.html#guard_expression) +> *guard-expression* → [*表達式*](..\chapter3\04_Expressions.html#expression) + + + +> 標記語句語法 +> *標記語句(Labeled Statement)* → [*語句標籤*](..\chapter3\10_Statements.html#statement_label) [*循環語句*](..\chapter3\10_Statements.html#loop_statement) | [*語句標籤*](..\chapter3\10_Statements.html#statement_label) [*switch語句*](..\chapter3\10_Statements.html#switch_statement) +> *語句標籤* → [*標籤名稱*](..\chapter3\10_Statements.html#label_name) **:** +> *標籤名稱* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) + + + +> 控制傳遞語句(Control Transfer Statement) 語法 +> *控制傳遞語句* → [*break語句*](..\chapter3\10_Statements.html#break_statement) +> *控制傳遞語句* → [*continue語句*](..\chapter3\10_Statements.html#continue_statement) +> *控制傳遞語句* → [*fallthrough語句*](..\chapter3\10_Statements.html#fallthrough_statement) +> *控制傳遞語句* → [*return語句*](..\chapter3\10_Statements.html#return_statement) + + + +> Break 語句語法 +> *break語句* → **break** [*標籤名稱*](..\chapter3\10_Statements.html#label_name) _可選_ + + + +> Continue 語句語法 +> *continue語句* → **continue** [*標籤名稱*](..\chapter3\10_Statements.html#label_name) _可選_ + + + +> Fallthrough 語句語法 +> *fallthrough語句* → **fallthrough** + + + +> Return 語句語法 +> *return語句* → **return** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ + + +## 泛型參數 + +> 泛型形參子句(Generic Parameter Clause) 語法 +> *泛型參數子句* → **<** [*泛型參數列表*](GenericParametersAndArguments.html#generic_parameter_list) [*約束子句*](GenericParametersAndArguments.html#requirement_clause) _可選_ **>** +> *泛型參數列表* → [*泛形參數*](GenericParametersAndArguments.html#generic_parameter) | [*泛形參數*](GenericParametersAndArguments.html#generic_parameter) **,** [*泛型參數列表*](GenericParametersAndArguments.html#generic_parameter_list) +> *泛形參數* → [*類型名稱*](..\chapter3\03_Types.html#type_name) +> *泛形參數* → [*類型名稱*](..\chapter3\03_Types.html#type_name) **:** [*類型標識*](..\chapter3\03_Types.html#type_identifier) +> *泛形參數* → [*類型名稱*](..\chapter3\03_Types.html#type_name) **:** [*協議合成類型*](..\chapter3\03_Types.html#protocol_composition_type) +> *約束子句* → **where** [*約束列表*](GenericParametersAndArguments.html#requirement_list) +> *約束列表* → [*約束*](GenericParametersAndArguments.html#requirement) | [*約束*](GenericParametersAndArguments.html#requirement) **,** [*約束列表*](GenericParametersAndArguments.html#requirement_list) +> *約束* → [*一致性約束*](GenericParametersAndArguments.html#conformance_requirement) | [*同類型約束*](GenericParametersAndArguments.html#same_type_requirement) +> *一致性約束* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) **:** [*類型標識*](..\chapter3\03_Types.html#type_identifier) +> *一致性約束* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) **:** [*協議合成類型*](..\chapter3\03_Types.html#protocol_composition_type) +> *同類型約束* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) **==** [*類型標識*](..\chapter3\03_Types.html#type_identifier) + + + +> 泛型實參子句語法 +> *(泛型參數子句Generic Argument Clause)* → **<** [*泛型參數列表*](GenericParametersAndArguments.html#generic_argument_list) **>** +> *泛型參數列表* → [*泛型參數*](GenericParametersAndArguments.html#generic_argument) | [*泛型參數*](GenericParametersAndArguments.html#generic_argument) **,** [*泛型參數列表*](GenericParametersAndArguments.html#generic_argument_list) +> *泛型參數* → [*類型*](..\chapter3\03_Types.html#type) + + +## 聲明 (Declarations) + +> 聲明語法 +> *聲明* → [*導入聲明*](..\chapter3\05_Declarations.html#import_declaration) +> *聲明* → [*常量聲明*](..\chapter3\05_Declarations.html#constant_declaration) +> *聲明* → [*變量聲明*](..\chapter3\05_Declarations.html#variable_declaration) +> *聲明* → [*類型別名聲明*](..\chapter3\05_Declarations.html#typealias_declaration) +> *聲明* → [*函數聲明*](..\chapter3\05_Declarations.html#function_declaration) +> *聲明* → [*枚舉聲明*](..\chapter3\05_Declarations.html#enum_declaration) +> *聲明* → [*結構體聲明*](..\chapter3\05_Declarations.html#struct_declaration) +> *聲明* → [*類聲明*](..\chapter3\05_Declarations.html#class_declaration) +> *聲明* → [*協議聲明*](..\chapter3\05_Declarations.html#protocol_declaration) +> *聲明* → [*構造器聲明*](..\chapter3\05_Declarations.html#initializer_declaration) +> *聲明* → [*析構器聲明*](..\chapter3\05_Declarations.html#deinitializer_declaration) +> *聲明* → [*擴展聲明*](..\chapter3\05_Declarations.html#extension_declaration) +> *聲明* → [*下標腳本聲明*](..\chapter3\05_Declarations.html#subscript_declaration) +> *聲明* → [*運算符聲明*](..\chapter3\05_Declarations.html#operator_declaration) +> *聲明(Declarations)列表* → [*聲明*](..\chapter3\05_Declarations.html#declaration) [*聲明(Declarations)列表*](..\chapter3\05_Declarations.html#declarations) _可選_ +> *聲明描述符(Specifiers)列表* → [*聲明描述符(Specifier)*](..\chapter3\05_Declarations.html#declaration_specifier) [*聲明描述符(Specifiers)列表*](..\chapter3\05_Declarations.html#declaration_specifiers) _可選_ +> *聲明描述符(Specifier)* → **class** | **mutating** | **nonmutating** | **override** | **static** | **unowned** | **unowned(safe)** | **unowned(unsafe)** | **weak** + + + +> 頂級(Top Level) 聲明語法 +> *頂級聲明* → [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) _可選_ + + + +> 代碼塊語法 +> *代碼塊* → **{** [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) _可選_ **}** + + + +> 導入(Import)聲明語法 +> *導入聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **import** [*導入類型*](..\chapter3\05_Declarations.html#import_kind) _可選_ [*導入路徑*](..\chapter3\05_Declarations.html#import_path) +> *導入類型* → **typealias** | **struct** | **class** | **enum** | **protocol** | **var** | **func** +> *導入路徑* → [*導入路徑標識符*](..\chapter3\05_Declarations.html#import_path_identifier) | [*導入路徑標識符*](..\chapter3\05_Declarations.html#import_path_identifier) **.** [*導入路徑*](..\chapter3\05_Declarations.html#import_path) +> *導入路徑標識符* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) | [*運算符*](..\chapter3\02_Lexical_Structure.html#operator) + + + +> 常數聲明語法 +> *常量聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*聲明描述符(Specifiers)列表*](..\chapter3\05_Declarations.html#declaration_specifiers) _可選_ **let** [*模式構造器列表*](..\chapter3\05_Declarations.html#pattern_initializer_list) +> *模式構造器列表* → [*模式構造器*](..\chapter3\05_Declarations.html#pattern_initializer) | [*模式構造器*](..\chapter3\05_Declarations.html#pattern_initializer) **,** [*模式構造器列表*](..\chapter3\05_Declarations.html#pattern_initializer_list) +> *模式構造器* → [*模式*](..\chapter3\07_Patterns.html#pattern) [*構造器*](..\chapter3\05_Declarations.html#initializer) _可選_ +> *構造器* → **=** [*表達式*](..\chapter3\04_Expressions.html#expression) + + + +> 變量聲明語法 +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*模式構造器列表*](..\chapter3\05_Declarations.html#pattern_initializer_list) +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*getter-setter塊*](..\chapter3\05_Declarations.html#getter_setter_block) +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*getter-setter關鍵字(Keyword)塊*](..\chapter3\05_Declarations.html#getter_setter_keyword_block) +> *變量聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*構造器*](..\chapter3\05_Declarations.html#initializer) _可選_ [*willSet-didSet代碼塊*](..\chapter3\05_Declarations.html#willSet_didSet_block) +> *變量聲明頭(Head)* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*聲明描述符(Specifiers)列表*](..\chapter3\05_Declarations.html#declaration_specifiers) _可選_ **var** +> *變量名稱* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *getter-setter塊* → **{** [*getter子句*](..\chapter3\05_Declarations.html#getter_clause) [*setter子句*](..\chapter3\05_Declarations.html#setter_clause) _可選_ **}** +> *getter-setter塊* → **{** [*setter子句*](..\chapter3\05_Declarations.html#setter_clause) [*getter子句*](..\chapter3\05_Declarations.html#getter_clause) **}** +> *getter子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **get** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *setter子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **set** [*setter名稱*](..\chapter3\05_Declarations.html#setter_name) _可選_ [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *setter名稱* → **(** [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) **)** +> *getter-setter關鍵字(Keyword)塊* → **{** [*getter關鍵字(Keyword)子句*](..\chapter3\05_Declarations.html#getter_keyword_clause) [*setter關鍵字(Keyword)子句*](..\chapter3\05_Declarations.html#setter_keyword_clause) _可選_ **}** +> *getter-setter關鍵字(Keyword)塊* → **{** [*setter關鍵字(Keyword)子句*](..\chapter3\05_Declarations.html#setter_keyword_clause) [*getter關鍵字(Keyword)子句*](..\chapter3\05_Declarations.html#getter_keyword_clause) **}** +> *getter關鍵字(Keyword)子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **get** +> *setter關鍵字(Keyword)子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **set** +> *willSet-didSet代碼塊* → **{** [*willSet子句*](..\chapter3\05_Declarations.html#willSet_clause) [*didSet子句*](..\chapter3\05_Declarations.html#didSet_clause) _可選_ **}** +> *willSet-didSet代碼塊* → **{** [*didSet子句*](..\chapter3\05_Declarations.html#didSet_clause) [*willSet子句*](..\chapter3\05_Declarations.html#willSet_clause) **}** +> *willSet子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **willSet** [*setter名稱*](..\chapter3\05_Declarations.html#setter_name) _可選_ [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *didSet子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **didSet** [*setter名稱*](..\chapter3\05_Declarations.html#setter_name) _可選_ [*代碼塊*](..\chapter3\05_Declarations.html#code_block) + + + +> 類型別名聲明語法 +> *類型別名聲明* → [*類型別名頭(Head)*](..\chapter3\05_Declarations.html#typealias_head) [*類型別名賦值*](..\chapter3\05_Declarations.html#typealias_assignment) +> *類型別名頭(Head)* → **typealias** [*類型別名名稱*](..\chapter3\05_Declarations.html#typealias_name) +> *類型別名名稱* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *類型別名賦值* → **=** [*類型*](..\chapter3\03_Types.html#type) + + + +> 函數聲明語法 +> *函數聲明* → [*函數頭*](..\chapter3\05_Declarations.html#function_head) [*函數名*](..\chapter3\05_Declarations.html#function_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*函數簽名(Signature)*](..\chapter3\05_Declarations.html#function_signature) [*函數體*](..\chapter3\05_Declarations.html#function_body) +> *函數頭* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*聲明描述符(Specifiers)列表*](..\chapter3\05_Declarations.html#declaration_specifiers) _可選_ **func** +> *函數名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) | [*運算符*](..\chapter3\02_Lexical_Structure.html#operator) +> *函數簽名(Signature)* → [*parameter-clauses*](..\chapter3\05_Declarations.html#parameter_clauses) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ +> *函數結果* → **->** [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*類型*](..\chapter3\03_Types.html#type) +> *函數體* → [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *parameter-clauses* → [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) [*parameter-clauses*](..\chapter3\05_Declarations.html#parameter_clauses) _可選_ +> *參數子句* → **(** **)** | **(** [*參數列表*](..\chapter3\05_Declarations.html#parameter_list) **...** _可選_ **)** +> *參數列表* → [*參數*](..\chapter3\05_Declarations.html#parameter) | [*參數*](..\chapter3\05_Declarations.html#parameter) **,** [*參數列表*](..\chapter3\05_Declarations.html#parameter_list) +> *參數* → **inout** _可選_ **let** _可選_ **#** _可選_ [*參數名*](..\chapter3\05_Declarations.html#parameter_name) [*本地參數名*](..\chapter3\05_Declarations.html#local_parameter_name) _可選_ [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*默認參數子句*](..\chapter3\05_Declarations.html#default_argument_clause) _可選_ +> *參數* → **inout** _可選_ **var** **#** _可選_ [*參數名*](..\chapter3\05_Declarations.html#parameter_name) [*本地參數名*](..\chapter3\05_Declarations.html#local_parameter_name) _可選_ [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*默認參數子句*](..\chapter3\05_Declarations.html#default_argument_clause) _可選_ +> *參數* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*類型*](..\chapter3\03_Types.html#type) +> *參數名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) | **_** +> *本地參數名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) | **_** +> *默認參數子句* → **=** [*表達式*](..\chapter3\04_Expressions.html#expression) + + + +> 枚舉聲明語法 +> *枚舉聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*聯合式枚舉*](..\chapter3\05_Declarations.html#union_style_enum) | [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*原始值式枚舉*](..\chapter3\05_Declarations.html#raw_value_style_enum) +> *聯合式枚舉* → [*枚舉名*](..\chapter3\05_Declarations.html#enum_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ **{** [*union-style-enum-members*](..\chapter3\05_Declarations.html#union_style_enum_members) _可選_ **}** +> *union-style-enum-members* → [*union-style-enum-member*](..\chapter3\05_Declarations.html#union_style_enum_member) [*union-style-enum-members*](..\chapter3\05_Declarations.html#union_style_enum_members) _可選_ +> *union-style-enum-member* → [*聲明*](..\chapter3\05_Declarations.html#declaration) | [*聯合式(Union Style)的枚舉case子句*](..\chapter3\05_Declarations.html#union_style_enum_case_clause) +> *聯合式(Union Style)的枚舉case子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **case** [*聯合式(Union Style)的枚舉case列表*](..\chapter3\05_Declarations.html#union_style_enum_case_list) +> *聯合式(Union Style)的枚舉case列表* → [*聯合式(Union Style)的case*](..\chapter3\05_Declarations.html#union_style_enum_case) | [*聯合式(Union Style)的case*](..\chapter3\05_Declarations.html#union_style_enum_case) **,** [*聯合式(Union Style)的枚舉case列表*](..\chapter3\05_Declarations.html#union_style_enum_case_list) +> *聯合式(Union Style)的case* → [*枚舉的case名*](..\chapter3\05_Declarations.html#enum_case_name) [*元組類型*](..\chapter3\03_Types.html#tuple_type) _可選_ +> *枚舉名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *枚舉的case名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *原始值式枚舉* → [*枚舉名*](..\chapter3\05_Declarations.html#enum_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ **:** [*類型標識*](..\chapter3\03_Types.html#type_identifier) **{** [*原始值式枚舉成員列表*](..\chapter3\05_Declarations.html#raw_value_style_enum_members) _可選_ **}** +> *原始值式枚舉成員列表* → [*原始值式枚舉成員*](..\chapter3\05_Declarations.html#raw_value_style_enum_member) [*原始值式枚舉成員列表*](..\chapter3\05_Declarations.html#raw_value_style_enum_members) _可選_ +> *原始值式枚舉成員* → [*聲明*](..\chapter3\05_Declarations.html#declaration) | [*原始值式枚舉case子句*](..\chapter3\05_Declarations.html#raw_value_style_enum_case_clause) +> *原始值式枚舉case子句* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **case** [*原始值式枚舉case列表*](..\chapter3\05_Declarations.html#raw_value_style_enum_case_list) +> *原始值式枚舉case列表* → [*原始值式枚舉case*](..\chapter3\05_Declarations.html#raw_value_style_enum_case) | [*原始值式枚舉case*](..\chapter3\05_Declarations.html#raw_value_style_enum_case) **,** [*原始值式枚舉case列表*](..\chapter3\05_Declarations.html#raw_value_style_enum_case_list) +> *原始值式枚舉case* → [*枚舉的case名*](..\chapter3\05_Declarations.html#enum_case_name) [*原始值賦值*](..\chapter3\05_Declarations.html#raw_value_assignment) _可選_ +> *原始值賦值* → **=** [*字面量*](..\chapter3\02_Lexical_Structure.html#literal) + + + +> 結構體聲明語法 +> *結構體聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **struct** [*結構體名稱*](..\chapter3\05_Declarations.html#struct_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*結構體主體*](..\chapter3\05_Declarations.html#struct_body) +> *結構體名稱* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *結構體主體* → **{** [*聲明(Declarations)列表*](..\chapter3\05_Declarations.html#declarations) _可選_ **}** + + + +> 類聲明語法 +> *類聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **class** [*類名*](..\chapter3\05_Declarations.html#class_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*類主體*](..\chapter3\05_Declarations.html#class_body) +> *類名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *類主體* → **{** [*聲明(Declarations)列表*](..\chapter3\05_Declarations.html#declarations) _可選_ **}** + + + +> 協議(Protocol)聲明語法 +> *協議聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **protocol** [*協議名*](..\chapter3\05_Declarations.html#protocol_name) [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*協議主體*](..\chapter3\05_Declarations.html#protocol_body) +> *協議名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *協議主體* → **{** [*協議成員聲明(Declarations)列表*](..\chapter3\05_Declarations.html#protocol_member_declarations) _可選_ **}** +> *協議成員聲明* → [*協議屬性聲明*](..\chapter3\05_Declarations.html#protocol_property_declaration) +> *協議成員聲明* → [*協議方法聲明*](..\chapter3\05_Declarations.html#protocol_method_declaration) +> *協議成員聲明* → [*協議構造器聲明*](..\chapter3\05_Declarations.html#protocol_initializer_declaration) +> *協議成員聲明* → [*協議下標腳本聲明*](..\chapter3\05_Declarations.html#protocol_subscript_declaration) +> *協議成員聲明* → [*協議關聯類型聲明*](..\chapter3\05_Declarations.html#protocol_associated_type_declaration) +> *協議成員聲明(Declarations)列表* → [*協議成員聲明*](..\chapter3\05_Declarations.html#protocol_member_declaration) [*協議成員聲明(Declarations)列表*](..\chapter3\05_Declarations.html#protocol_member_declarations) _可選_ + + + +> 協議屬性聲明語法 +> *協議屬性聲明* → [*變量聲明頭(Head)*](..\chapter3\05_Declarations.html#variable_declaration_head) [*變量名*](..\chapter3\05_Declarations.html#variable_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) [*getter-setter關鍵字(Keyword)塊*](..\chapter3\05_Declarations.html#getter_setter_keyword_block) + + + +> 協議方法聲明語法 +> *協議方法聲明* → [*函數頭*](..\chapter3\05_Declarations.html#function_head) [*函數名*](..\chapter3\05_Declarations.html#function_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*函數簽名(Signature)*](..\chapter3\05_Declarations.html#function_signature) + + + +> 協議構造器聲明語法 +> *協議構造器聲明* → [*構造器頭(Head)*](..\chapter3\05_Declarations.html#initializer_head) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) + + + +> 協議下標腳本聲明語法 +> *協議下標腳本聲明* → [*下標腳本頭(Head)*](..\chapter3\05_Declarations.html#subscript_head) [*下標腳本結果(Result)*](..\chapter3\05_Declarations.html#subscript_result) [*getter-setter關鍵字(Keyword)塊*](..\chapter3\05_Declarations.html#getter_setter_keyword_block) + + + +> 協議關聯類型聲明語法 +> *協議關聯類型聲明* → [*類型別名頭(Head)*](..\chapter3\05_Declarations.html#typealias_head) [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*類型別名賦值*](..\chapter3\05_Declarations.html#typealias_assignment) _可選_ + + + +> 構造器聲明語法 +> *構造器聲明* → [*構造器頭(Head)*](..\chapter3\05_Declarations.html#initializer_head) [*泛型參數子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可選_ [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) [*構造器主體*](..\chapter3\05_Declarations.html#initializer_body) +> *構造器頭(Head)* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **convenience** _可選_ **init** +> *構造器主體* → [*代碼塊*](..\chapter3\05_Declarations.html#code_block) + + + +> 析構器聲明語法 +> *析構器聲明* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **deinit** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) + + + +> 擴展(Extension)聲明語法 +> *擴展聲明* → **extension** [*類型標識*](..\chapter3\03_Types.html#type_identifier) [*類型繼承子句*](..\chapter3\03_Types.html#type_inheritance_clause) _可選_ [*extension-body*](..\chapter3\05_Declarations.html#extension_body) +> *extension-body* → **{** [*聲明(Declarations)列表*](..\chapter3\05_Declarations.html#declarations) _可選_ **}** + + + +> 下標腳本聲明語法 +> *下標腳本聲明* → [*下標腳本頭(Head)*](..\chapter3\05_Declarations.html#subscript_head) [*下標腳本結果(Result)*](..\chapter3\05_Declarations.html#subscript_result) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *下標腳本聲明* → [*下標腳本頭(Head)*](..\chapter3\05_Declarations.html#subscript_head) [*下標腳本結果(Result)*](..\chapter3\05_Declarations.html#subscript_result) [*getter-setter塊*](..\chapter3\05_Declarations.html#getter_setter_block) +> *下標腳本聲明* → [*下標腳本頭(Head)*](..\chapter3\05_Declarations.html#subscript_head) [*下標腳本結果(Result)*](..\chapter3\05_Declarations.html#subscript_result) [*getter-setter關鍵字(Keyword)塊*](..\chapter3\05_Declarations.html#getter_setter_keyword_block) +> *下標腳本頭(Head)* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **subscript** [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) +> *下標腳本結果(Result)* → **->** [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*類型*](..\chapter3\03_Types.html#type) + + + +> 運算符聲明語法 +> *運算符聲明* → [*前置運算符聲明*](..\chapter3\05_Declarations.html#prefix_operator_declaration) | [*後置運算符聲明*](..\chapter3\05_Declarations.html#postfix_operator_declaration) | [*中置運算符聲明*](..\chapter3\05_Declarations.html#infix_operator_declaration) +> *前置運算符聲明* → **運算符** **prefix** [*運算符*](..\chapter3\02_Lexical_Structure.html#operator) **{** **}** +> *後置運算符聲明* → **運算符** **postfix** [*運算符*](..\chapter3\02_Lexical_Structure.html#operator) **{** **}** +> *中置運算符聲明* → **運算符** **infix** [*運算符*](..\chapter3\02_Lexical_Structure.html#operator) **{** [*中置運算符屬性*](..\chapter3\05_Declarations.html#infix_operator_attributes) _可選_ **}** +> *中置運算符屬性* → [*優先級子句*](..\chapter3\05_Declarations.html#precedence_clause) _可選_ [*結和性子句*](..\chapter3\05_Declarations.html#associativity_clause) _可選_ +> *優先級子句* → **precedence** [*優先級水平*](..\chapter3\05_Declarations.html#precedence_level) +> *優先級水平* → 數值 0 到 255 +> *結和性子句* → **associativity** [*結和性*](..\chapter3\05_Declarations.html#associativity) +> *結和性* → **left** | **right** | **none** + + +## 模式 + +> 模式(Patterns) 語法 +> *模式* → [*通配符模式*](..\chapter3\07_Patterns.html#wildcard_pattern) [*類型註解*](..\chapter3\03_Types.html#type_annotation) _可選_ +> *模式* → [*標識符模式*](..\chapter3\07_Patterns.html#identifier_pattern) [*類型註解*](..\chapter3\03_Types.html#type_annotati(Value Binding)on) _可選_ +> *模式* → [*值綁定模式*](..\chapter3\07_Patterns.html#value_binding_pattern) +> *模式* → [*元組模式*](..\chapter3\07_Patterns.html#tuple_pattern) [*類型註解*](..\chapter3\03_Types.html#type_annotation) _可選_ +> *模式* → [*enum-case-pattern*](..\chapter3\07_Patterns.html#enum_case_pattern) +> *模式* → [*type-casting-pattern*](..\chapter3\07_Patterns.html#type_casting_pattern) +> *模式* → [*表達式模式*](..\chapter3\07_Patterns.html#expression_pattern) + + + +> 通配符模式語法 +> *通配符模式* → **_** + + + +> 標識符模式語法 +> *標識符模式* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) + + + +> 值綁定(Value Binding)模式語法 +> *值綁定模式* → **var** [*模式*](..\chapter3\07_Patterns.html#pattern) | **let** [*模式*](..\chapter3\07_Patterns.html#pattern) + + + +> 元組模式語法 +> *元組模式* → **(** [*元組模式元素列表*](..\chapter3\07_Patterns.html#tuple_pattern_element_list) _可選_ **)** +> *元組模式元素列表* → [*元組模式元素*](..\chapter3\07_Patterns.html#tuple_pattern_element) | [*元組模式元素*](..\chapter3\07_Patterns.html#tuple_pattern_element) **,** [*元組模式元素列表*](..\chapter3\07_Patterns.html#tuple_pattern_element_list) +> *元組模式元素* → [*模式*](..\chapter3\07_Patterns.html#pattern) + + + +> 枚舉用例模式語法 +> *enum-case-pattern* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) _可選_ **.** [*枚舉的case名*](..\chapter3\05_Declarations.html#enum_case_name) [*元組模式*](..\chapter3\07_Patterns.html#tuple_pattern) _可選_ + + + +> 類型轉換模式語法 +> *type-casting-pattern* → [*is模式*](..\chapter3\07_Patterns.html#is_pattern) | [*as模式*](..\chapter3\07_Patterns.html#as_pattern) +> *is模式* → **is** [*類型*](..\chapter3\03_Types.html#type) +> *as模式* → [*模式*](..\chapter3\07_Patterns.html#pattern) **as** [*類型*](..\chapter3\03_Types.html#type) + + + +> 表達式模式語法 +> *表達式模式* → [*表達式*](..\chapter3\04_Expressions.html#expression) + + +## 特性 + +> 特性語法 +> *特色* → **@** [*特性名*](..\chapter3\06_Attributes.html#attribute_name) [*特性參數子句*](..\chapter3\06_Attributes.html#attribute_argument_clause) _可選_ +> *特性名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *特性參數子句* → **(** [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ **)** +> *特性(Attributes)列表* → [*特色*](..\chapter3\06_Attributes.html#attribute) [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ +> *平衡令牌列表* → [*平衡令牌*](..\chapter3\06_Attributes.html#balanced_token) [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ +> *平衡令牌* → **(** [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ **)** +> *平衡令牌* → **[** [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ **]** +> *平衡令牌* → **{** [*平衡令牌列表*](..\chapter3\06_Attributes.html#balanced_tokens) _可選_ **}** +> *平衡令牌* → **任意標識符, 關鍵字, 字面量或運算符** +> *平衡令牌* → **任意標點除了(, ), [, ], {, 或 }** + + +## 表達式 + +> 表達式語法 +> *表達式* → [*前置表達式*](..\chapter3\04_Expressions.html#prefix_expression) [*二元表達式列表*](..\chapter3\04_Expressions.html#binary_expressions) _可選_ +> *表達式列表* → [*表達式*](..\chapter3\04_Expressions.html#expression) | [*表達式*](..\chapter3\04_Expressions.html#expression) **,** [*表達式列表*](..\chapter3\04_Expressions.html#expression_list) + + + +> 前置表達式語法 +> *前置表達式* → [*前置運算符*](..\chapter3\02_Lexical_Structure.html#prefix_operator) _可選_ [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) +> *前置表達式* → [*寫入寫出(in-out)表達式*](..\chapter3\04_Expressions.html#in_out_expression) +> *寫入寫出(in-out)表達式* → **&** [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) + + + +> 二元表達式語法 +> *二元表達式* → [*二元運算符*](..\chapter3\02_Lexical_Structure.html#binary_operator) [*前置表達式*](..\chapter3\04_Expressions.html#prefix_expression) +> *二元表達式* → [*賦值運算符*](..\chapter3\04_Expressions.html#assignment_operator) [*前置表達式*](..\chapter3\04_Expressions.html#prefix_expression) +> *二元表達式* → [*條件運算符*](..\chapter3\04_Expressions.html#conditional_operator) [*前置表達式*](..\chapter3\04_Expressions.html#prefix_expression) +> *二元表達式* → [*類型轉換運算符*](..\chapter3\04_Expressions.html#type_casting_operator) +> *二元表達式列表* → [*二元表達式*](..\chapter3\04_Expressions.html#binary_expression) [*二元表達式列表*](..\chapter3\04_Expressions.html#binary_expressions) _可選_ + + + +> 賦值運算符語法 +> *賦值運算符* → **=** + + + +> 三元條件運算符語法 +> *三元條件運算符* → **?** [*表達式*](..\chapter3\04_Expressions.html#expression) **:** + + + +> 類型轉換運算符語法 +> *類型轉換運算符* → **is** [*類型*](..\chapter3\03_Types.html#type) | **as** **?** _可選_ [*類型*](..\chapter3\03_Types.html#type) + + + +> 主表達式語法 +> *主表達式* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) [*泛型參數子句*](GenericParametersAndArguments.html#generic_argument_clause) _可選_ +> *主表達式* → [*字面量表達式*](..\chapter3\04_Expressions.html#literal_expression) +> *主表達式* → [*self表達式*](..\chapter3\04_Expressions.html#self_expression) +> *主表達式* → [*超類表達式*](..\chapter3\04_Expressions.html#superclass_expression) +> *主表達式* → [*閉包表達式*](..\chapter3\04_Expressions.html#closure_expression) +> *主表達式* → [*圓括號表達式*](..\chapter3\04_Expressions.html#parenthesized_expression) +> *主表達式* → [*隱式成員表達式*](..\chapter3\04_Expressions.html#implicit_member_expression) +> *主表達式* → [*通配符表達式*](..\chapter3\04_Expressions.html#wildcard_expression) + + + +> 字面量表達式語法 +> *字面量表達式* → [*字面量*](..\chapter3\02_Lexical_Structure.html#literal) +> *字面量表達式* → [*數組字面量*](..\chapter3\04_Expressions.html#array_literal) | [*字典字面量*](..\chapter3\04_Expressions.html#dictionary_literal) +> *字面量表達式* → **__FILE__** | **__LINE__** | **__COLUMN__** | **__FUNCTION__** +> *數組字面量* → **[** [*數組字面量項列表*](..\chapter3\04_Expressions.html#array_literal_items) _可選_ **]** +> *數組字面量項列表* → [*數組字面量項*](..\chapter3\04_Expressions.html#array_literal_item) **,** _可選_ | [*數組字面量項*](..\chapter3\04_Expressions.html#array_literal_item) **,** [*數組字面量項列表*](..\chapter3\04_Expressions.html#array_literal_items) +> *數組字面量項* → [*表達式*](..\chapter3\04_Expressions.html#expression) +> *字典字面量* → **[** [*字典字面量項列表*](..\chapter3\04_Expressions.html#dictionary_literal_items) **]** | **[** **:** **]** +> *字典字面量項列表* → [*字典字面量項*](..\chapter3\04_Expressions.html#dictionary_literal_item) **,** _可選_ | [*字典字面量項*](..\chapter3\04_Expressions.html#dictionary_literal_item) **,** [*字典字面量項列表*](..\chapter3\04_Expressions.html#dictionary_literal_items) +> *字典字面量項* → [*表達式*](..\chapter3\04_Expressions.html#expression) **:** [*表達式*](..\chapter3\04_Expressions.html#expression) + + + +> Self 表達式語法 +> *self表達式* → **self** +> *self表達式* → **self** **.** [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *self表達式* → **self** **[** [*表達式*](..\chapter3\04_Expressions.html#expression) **]** +> *self表達式* → **self** **.** **init** + + + +> 超類表達式語法 +> *超類表達式* → [*超類方法表達式*](..\chapter3\04_Expressions.html#superclass_method_expression) | [*超類下標表達式*](..\chapter3\04_Expressions.html#超類下標表達式) | [*超類構造器表達式*](..\chapter3\04_Expressions.html#superclass_initializer_expression) +> *超類方法表達式* → **super** **.** [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) +> *超類下標表達式* → **super** **[** [*表達式*](..\chapter3\04_Expressions.html#expression) **]** +> *超類構造器表達式* → **super** **.** **init** + + + +> 閉包表達式語法 +> *閉包表達式* → **{** [*閉包簽名(Signational)*](..\chapter3\04_Expressions.html#closure_signature) _可選_ [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) **}** +> *閉包簽名(Signational)* → [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ **in** +> *閉包簽名(Signational)* → [*標識符列表*](..\chapter3\02_Lexical_Structure.html#identifier_list) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ **in** +> *閉包簽名(Signational)* → [*捕獲(Capature)列表*](..\chapter3\04_Expressions.html#capture_list) [*參數子句*](..\chapter3\05_Declarations.html#parameter_clause) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ **in** +> *閉包簽名(Signational)* → [*捕獲(Capature)列表*](..\chapter3\04_Expressions.html#capture_list) [*標識符列表*](..\chapter3\02_Lexical_Structure.html#identifier_list) [*函數結果*](..\chapter3\05_Declarations.html#function_result) _可選_ **in** +> *閉包簽名(Signational)* → [*捕獲(Capature)列表*](..\chapter3\04_Expressions.html#capture_list) **in** +> *捕獲(Capature)列表* → **[** [*捕獲(Capature)說明符*](..\chapter3\04_Expressions.html#capture_specifier) [*表達式*](..\chapter3\04_Expressions.html#expression) **]** +> *捕獲(Capature)說明符* → **weak** | **unowned** | **unowned(safe)** | **unowned(unsafe)** + + + +> 隱式成員表達式語法 +> *隱式成員表達式* → **.** [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) + + + +> 圓括號表達式(Parenthesized Expression)語法 +> *圓括號表達式* → **(** [*表達式元素列表*](..\chapter3\04_Expressions.html#expression_element_list) _可選_ **)** +> *表達式元素列表* → [*表達式元素*](..\chapter3\04_Expressions.html#expression_element) | [*表達式元素*](..\chapter3\04_Expressions.html#expression_element) **,** [*表達式元素列表*](..\chapter3\04_Expressions.html#expression_element_list) +> *表達式元素* → [*表達式*](..\chapter3\04_Expressions.html#expression) | [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) **:** [*表達式*](..\chapter3\04_Expressions.html#expression) + + + +> 通配符表達式語法 +> *通配符表達式* → **_** + + + +> 後置表達式語法 +> *後置表達式* → [*主表達式*](..\chapter3\04_Expressions.html#primary_expression) +> *後置表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) [*後置運算符*](..\chapter3\02_Lexical_Structure.html#postfix_operator) +> *後置表達式* → [*函數調用表達式*](..\chapter3\04_Expressions.html#function_call_expression) +> *後置表達式* → [*構造器表達式*](..\chapter3\04_Expressions.html#initializer_expression) +> *後置表達式* → [*顯示成員表達式*](..\chapter3\04_Expressions.html#explicit_member_expression) +> *後置表達式* → [*後置self表達式*](..\chapter3\04_Expressions.html#postfix_self_expression) +> *後置表達式* → [*動態類型表達式*](..\chapter3\04_Expressions.html#dynamic_type_expression) +> *後置表達式* → [*下標表達式*](..\chapter3\04_Expressions.html#subscript_expression) +> *後置表達式* → [*強制取值(Forced Value)表達式*](..\chapter3\04_Expressions.html#forced_value_expression) +> *後置表達式* → [*可選鏈(Optional Chaining)表達式*](..\chapter3\04_Expressions.html#optional_chaining_expression) + + + +> 函數調用表達式語法 +> *函數調用表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) [*圓括號表達式*](..\chapter3\04_Expressions.html#parenthesized_expression) +> *函數調用表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) [*圓括號表達式*](..\chapter3\04_Expressions.html#parenthesized_expression) _可選_ [*後置閉包(Trailing Closure)*](..\chapter3\04_Expressions.html#trailing_closure) +> *後置閉包(Trailing Closure)* → [*閉包表達式*](..\chapter3\04_Expressions.html#closure_expression) + + + +> 構造器表達式語法 +> *構造器表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** **init** + + + +> 顯式成員表達式語法 +> *顯示成員表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** [*十進制數字*](..\chapter3\02_Lexical_Structure.html#decimal_digit) +> *顯示成員表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) [*泛型參數子句*](GenericParametersAndArguments.html#generic_argument_clause) _可選_ + + + +> 後置Self 表達式語法 +> *後置self表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** **self** + + + +> 動態類型表達式語法 +> *動態類型表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **.** **dynamicType** + + + +> 附屬腳本表達式語法 +> *附屬腳本表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **[** [*表達式列表*](..\chapter3\04_Expressions.html#expression_list) **]** + + + +> 強制取值(Forced Value)語法 +> *強制取值(Forced Value)表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **!** + + + +> 可選鏈表達式語法 +> *可選鏈表達式* → [*後置表達式*](..\chapter3\04_Expressions.html#postfix_expression) **?** + + +## 詞法結構 + +> 標識符語法 +> *標識符* → [*標識符頭(Head)*](..\chapter3\02_Lexical_Structure.html#identifier_head) [*標識符字符列表*](..\chapter3\02_Lexical_Structure.html#identifier_characters) _可選_ +> *標識符* → **`** [*標識符頭(Head)*](..\chapter3\02_Lexical_Structure.html#identifier_head) [*標識符字符列表*](..\chapter3\02_Lexical_Structure.html#identifier_characters) _可選_ **`** +> *標識符* → [*隱式參數名*](..\chapter3\02_Lexical_Structure.html#implicit_parameter_name) +> *標識符列表* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) | [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) **,** [*標識符列表*](..\chapter3\02_Lexical_Structure.html#identifier_list) +> *標識符頭(Head)* → Upper- or lowercase letter A through Z +> *標識符頭(Head)* → U+00A8, U+00AA, U+00AD, U+00AF, U+00B2–U+00B5, or U+00B7–U+00BA +> *標識符頭(Head)* → U+00BC–U+00BE, U+00C0–U+00D6, U+00D8–U+00F6, or U+00F8–U+00FF +> *標識符頭(Head)* → U+0100–U+02FF, U+0370–U+167F, U+1681–U+180D, or U+180F–U+1DBF +> *標識符頭(Head)* → U+1E00–U+1FFF +> *標識符頭(Head)* → U+200B–U+200D, U+202A–U+202E, U+203F–U+2040, U+2054, or U+2060–U+206F +> *標識符頭(Head)* → U+2070–U+20CF, U+2100–U+218F, U+2460–U+24FF, or U+2776–U+2793 +> *標識符頭(Head)* → U+2C00–U+2DFF or U+2E80–U+2FFF +> *標識符頭(Head)* → U+3004–U+3007, U+3021–U+302F, U+3031–U+303F, or U+3040–U+D7FF +> *標識符頭(Head)* → U+F900–U+FD3D, U+FD40–U+FDCF, U+FDF0–U+FE1F, or U+FE30–U+FE44 +> *標識符頭(Head)* → U+FE47–U+FFFD +> *標識符頭(Head)* → U+10000–U+1FFFD, U+20000–U+2FFFD, U+30000–U+3FFFD, or U+40000–U+4FFFD +> *標識符頭(Head)* → U+50000–U+5FFFD, U+60000–U+6FFFD, U+70000–U+7FFFD, or U+80000–U+8FFFD +> *標識符頭(Head)* → U+90000–U+9FFFD, U+A0000–U+AFFFD, U+B0000–U+BFFFD, or U+C0000–U+CFFFD +> *標識符頭(Head)* → U+D0000–U+DFFFD or U+E0000–U+EFFFD +> *標識符字符* → 數值 0 到 9 +> *標識符字符* → U+0300–U+036F, U+1DC0–U+1DFF, U+20D0–U+20FF, or U+FE20–U+FE2F +> *標識符字符* → [*標識符頭(Head)*](..\chapter3\02_Lexical_Structure.html#identifier_head) +> *標識符字符列表* → [*標識符字符*](..\chapter3\02_Lexical_Structure.html#identifier_character) [*標識符字符列表*](..\chapter3\02_Lexical_Structure.html#identifier_characters) _可選_ +> *隱式參數名* → **$** [*十進制數字列表*](..\chapter3\02_Lexical_Structure.html#decimal_digits) + + + +> 字面量語法 +> *字面量* → [*整型字面量*](..\chapter3\02_Lexical_Structure.html#integer_literal) | [*浮點數字面量*](..\chapter3\02_Lexical_Structure.html#floating_point_literal) | [*字符串字面量*](..\chapter3\02_Lexical_Structure.html#string_literal) + + + +> 整型字面量語法 +> *整型字面量* → [*二進制字面量*](..\chapter3\02_Lexical_Structure.html#binary_literal) +> *整型字面量* → [*八進制字面量*](..\chapter3\02_Lexical_Structure.html#octal_literal) +> *整型字面量* → [*十進制字面量*](..\chapter3\02_Lexical_Structure.html#decimal_literal) +> *整型字面量* → [*十六進制字面量*](..\chapter3\02_Lexical_Structure.html#hexadecimal_literal) +> *二進制字面量* → **0b** [*二進制數字*](..\chapter3\02_Lexical_Structure.html#binary_digit) [*二進制字面量字符列表*](..\chapter3\02_Lexical_Structure.html#binary_literal_characters) _可選_ +> *二進制數字* → 數值 0 到 1 +> *二進制字面量字符* → [*二進制數字*](..\chapter3\02_Lexical_Structure.html#binary_digit) | **_** +> *二進制字面量字符列表* → [*二進制字面量字符*](..\chapter3\02_Lexical_Structure.html#binary_literal_character) [*二進制字面量字符列表*](..\chapter3\02_Lexical_Structure.html#binary_literal_characters) _可選_ +> *八進制字面量* → **0o** [*八進字數字*](..\chapter3\02_Lexical_Structure.html#octal_digit) [*八進制字符列表*](..\chapter3\02_Lexical_Structure.html#octal_literal_characters) _可選_ +> *八進字數字* → 數值 0 到 7 +> *八進制字符* → [*八進字數字*](..\chapter3\02_Lexical_Structure.html#octal_digit) | **_** +> *八進制字符列表* → [*八進制字符*](..\chapter3\02_Lexical_Structure.html#octal_literal_character) [*八進制字符列表*](..\chapter3\02_Lexical_Structure.html#octal_literal_characters) _可選_ +> *十進制字面量* → [*十進制數字*](..\chapter3\02_Lexical_Structure.html#decimal_digit) [*十進制字符列表*](..\chapter3\02_Lexical_Structure.html#decimal_literal_characters) _可選_ +> *十進制數字* → 數值 0 到 9 +> *十進制數字列表* → [*十進制數字*](..\chapter3\02_Lexical_Structure.html#decimal_digit) [*十進制數字列表*](..\chapter3\02_Lexical_Structure.html#decimal_digits) _可選_ +> *十進制字符* → [*十進制數字*](..\chapter3\02_Lexical_Structure.html#decimal_digit) | **_** +> *十進制字符列表* → [*十進制字符*](..\chapter3\02_Lexical_Structure.html#decimal_literal_character) [*十進制字符列表*](..\chapter3\02_Lexical_Structure.html#decimal_literal_characters) _可選_ +> *十六進制字面量* → **0x** [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制字面量字符列表*](..\chapter3\02_Lexical_Structure.html#hexadecimal_literal_characters) _可選_ +> *十六進制數字* → 數值 0 到 9, a through f, or A through F +> *十六進制字符* → [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) | **_** +> *十六進制字面量字符列表* → [*十六進制字符*](..\chapter3\02_Lexical_Structure.html#hexadecimal_literal_character) [*十六進制字面量字符列表*](..\chapter3\02_Lexical_Structure.html#hexadecimal_literal_characters) _可選_ + + + +> 浮點型字面量語法 +> *浮點數字面量* → [*十進制字面量*](..\chapter3\02_Lexical_Structure.html#decimal_literal) [*十進制分數*](..\chapter3\02_Lexical_Structure.html#decimal_fraction) _可選_ [*十進制指數*](..\chapter3\02_Lexical_Structure.html#decimal_exponent) _可選_ +> *浮點數字面量* → [*十六進制字面量*](..\chapter3\02_Lexical_Structure.html#hexadecimal_literal) [*十六進制分數*](..\chapter3\02_Lexical_Structure.html#hexadecimal_fraction) _可選_ [*十六進制指數*](..\chapter3\02_Lexical_Structure.html#hexadecimal_exponent) +> *十進制分數* → **.** [*十進制字面量*](..\chapter3\02_Lexical_Structure.html#decimal_literal) +> *十進制指數* → [*浮點數e*](..\chapter3\02_Lexical_Structure.html#floating_point_e) [*正負號*](..\chapter3\02_Lexical_Structure.html#sign) _可選_ [*十進制字面量*](..\chapter3\02_Lexical_Structure.html#decimal_literal) +> *十六進制分數* → **.** [*十六進制字面量*](..\chapter3\02_Lexical_Structure.html#hexadecimal_literal) _可選_ +> *十六進制指數* → [*浮點數p*](..\chapter3\02_Lexical_Structure.html#floating_point_p) [*正負號*](..\chapter3\02_Lexical_Structure.html#sign) _可選_ [*十六進制字面量*](..\chapter3\02_Lexical_Structure.html#hexadecimal_literal) +> *浮點數e* → **e** | **E** +> *浮點數p* → **p** | **P** +> *正負號* → **+** | **-** + + + +> 字符型字面量語法 +> *字符串字面量* → **"** [*引用文本*](..\chapter3\02_Lexical_Structure.html#quoted_text) **"** +> *引用文本* → [*引用文本條目*](..\chapter3\02_Lexical_Structure.html#quoted_text_item) [*引用文本*](..\chapter3\02_Lexical_Structure.html#quoted_text) _可選_ +> *引用文本條目* → [*轉義字符*](..\chapter3\02_Lexical_Structure.html#escaped_character) +> *引用文本條目* → **\(** [*表達式*](..\chapter3\04_Expressions.html#expression) **)** +> *引用文本條目* → 除了"-, \-, U+000A, or U+000D的所有Unicode的字符 +> *轉義字符* → **\0** | **\\** | **\t** | **\n** | **\r** | **\"** | **\'** +> *轉義字符* → **\x** [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) +> *轉義字符* → **\u** [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) +> *轉義字符* → **\U** [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) [*十六進制數字*](..\chapter3\02_Lexical_Structure.html#hexadecimal_digit) + + + +> 運算符語法語法 +> *運算符* → [*運算符字符*](..\chapter3\02_Lexical_Structure.html#operator_character) [*運算符*](..\chapter3\02_Lexical_Structure.html#operator) _可選_ +> *運算符字符* → **/** | **=** | **-** | **+** | **!** | ***** | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **.** +> *二元運算符* → [*運算符*](..\chapter3\02_Lexical_Structure.html#operator) +> *前置運算符* → [*運算符*](..\chapter3\02_Lexical_Structure.html#operator) +> *後置運算符* → [*運算符*](..\chapter3\02_Lexical_Structure.html#operator) + + +## 類型 + +> 類型語法 +> *類型* → [*數組類型*](..\chapter3\03_Types.html#array_type) | [*函數類型*](..\chapter3\03_Types.html#function_type) | [*類型標識*](..\chapter3\03_Types.html#type_identifier) | [*元組類型*](..\chapter3\03_Types.html#tuple_type) | [*可選類型*](..\chapter3\03_Types.html#optional_type) | [*隱式解析可選類型*](..\chapter3\03_Types.html#implicitly_unwrapped_optional_type) | [*協議合成類型*](..\chapter3\03_Types.html#protocol_composition_type) | [*元型類型*](..\chapter3\03_Types.html#metatype_type) + + + +> 類型註解語法 +> *類型註解* → **:** [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ [*類型*](..\chapter3\03_Types.html#type) + + + +> 類型標識語法 +> *類型標識* → [*類型名稱*](..\chapter3\03_Types.html#type_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_argument_clause) _可選_ | [*類型名稱*](..\chapter3\03_Types.html#type_name) [*泛型參數子句*](GenericParametersAndArguments.html#generic_argument_clause) _可選_ **.** [*類型標識*](..\chapter3\03_Types.html#type_identifier) +> *類名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) + + + +> 元組類型語法 +> *元組類型* → **(** [*元組類型主體*](..\chapter3\03_Types.html#tuple_type_body) _可選_ **)** +> *元組類型主體* → [*元組類型的元素列表*](..\chapter3\03_Types.html#tuple_type_element_list) **...** _可選_ +> *元組類型的元素列表* → [*元組類型的元素*](..\chapter3\03_Types.html#tuple_type_element) | [*元組類型的元素*](..\chapter3\03_Types.html#tuple_type_element) **,** [*元組類型的元素列表*](..\chapter3\03_Types.html#tuple_type_element_list) +> *元組類型的元素* → [*特性(Attributes)列表*](..\chapter3\06_Attributes.html#attributes) _可選_ **inout** _可選_ [*類型*](..\chapter3\03_Types.html#type) | **inout** _可選_ [*元素名*](..\chapter3\03_Types.html#element_name) [*類型註解*](..\chapter3\03_Types.html#type_annotation) +> *元素名* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) + + + +> 函數類型語法 +> *函數類型* → [*類型*](..\chapter3\03_Types.html#type) **->** [*類型*](..\chapter3\03_Types.html#type) + + + +> 數組類型語法 +> *數組類型* → [*類型*](..\chapter3\03_Types.html#type) **[** **]** | [*數組類型*](..\chapter3\03_Types.html#array_type) **[** **]** + + + +> 可選類型語法 +> *可選類型* → [*類型*](..\chapter3\03_Types.html#type) **?** + + + +> 隱式解析可選類型(Implicitly Unwrapped Optional Type)語法 +> *隱式解析可選類型* → [*類型*](..\chapter3\03_Types.html#type) **!** + + + +> 協議合成類型語法 +> *協議合成類型* → **protocol** **<** [*協議標識符列表*](..\chapter3\03_Types.html#protocol_identifier_list) _可選_ **>** +> *協議標識符列表* → [*協議標識符*](..\chapter3\03_Types.html#protocol_identifier) | [*協議標識符*](..\chapter3\03_Types.html#protocol_identifier) **,** [*協議標識符列表*](..\chapter3\03_Types.html#protocol_identifier_list) +> *協議標識符* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) + + + +> 元(Metatype)類型語法 +> *元類型* → [*類型*](..\chapter3\03_Types.html#type) **.** **Type** | [*類型*](..\chapter3\03_Types.html#type) **.** **Protocol** + + + +> 類型繼承子句語法 +> *類型繼承子句* → **:** [*類型繼承列表*](..\chapter3\03_Types.html#type_inheritance_list) +> *類型繼承列表* → [*類型標識*](..\chapter3\03_Types.html#type_identifier) | [*類型標識*](..\chapter3\03_Types.html#type_identifier) **,** [*類型繼承列表*](..\chapter3\03_Types.html#type_inheritance_list) diff --git a/source-tw/chapter3/10_Statements.md b/source-tw/chapter3/10_Statements.md new file mode 100644 index 00000000..f5413f4a --- /dev/null +++ b/source-tw/chapter3/10_Statements.md @@ -0,0 +1,322 @@ +> 翻譯:[coverxit](https://github.com/coverxit) +> 校對:[numbbbbb](https://github.com/numbbbbb), [coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai) + +# 語句 +----------------- + +本頁包含內容: + +- [循環語句](#loop_statements) +- [分支語句](#branch_statements) +- [帶標籤的語句](#labeled_statement) +- [控制傳遞語句](#control_transfer_statements) + +在 Swift 中,有兩種類型的語句:簡單語句和控制流語句。簡單語句是最常見的,用於構造表達式和聲明。控制流語句則用於控制程序執行的流程,Swift 中有三種類型的控制流語句:循環語句、分支語句和控制傳遞語句。 + +循環語句用於重複執行代碼塊;分支語句用於執行滿足特定條件的代碼塊;控制傳遞語句則用於修改代碼的執行順序。在稍後的敘述中,將會詳細地介紹每一種類型的控制流語句。 + +是否將分號(`;`)添加到語句的結尾處是可選的。但若要在同一行內寫多條獨立語句,請務必使用分號。 + +> 語句語法 +> *語句* → [*表達式*](..\chapter3\04_Expressions.html#expression) **;** _可選_ +> *語句* → [*聲明*](..\chapter3\05_Declarations.html#declaration) **;** _可選_ +> *語句* → [*循環語句*](..\chapter3\10_Statements.html#loop_statement) **;** _可選_ +> *語句* → [*分支語句*](..\chapter3\10_Statements.html#branch_statement) **;** _可選_ +> *語句* → [*標記語句(Labeled Statement)*](..\chapter3\10_Statements.html#labeled_statement) +> *語句* → [*控制轉移語句*](..\chapter3\10_Statements.html#control_transfer_statement) **;** _可選_ +> *多條語句(Statements)* → [*語句*](..\chapter3\10_Statements.html#statement) [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) _可選_ + + +## 循環語句 + +取決於特定的循環條件,循環語句允許重複執行代碼塊。Swift 提供四種類型的循環語句:`for`語句、`for-in`語句、`while`語句和`do-while`語句。 + +通過`break`語句和`continue`語句可以改變循環語句的控制流。有關這兩條語句,詳情參見 [Break 語句](#break_statement)和 [Continue 語句](#continue_statement)。 + +> 循環語句語法 +> *循環語句* → [*for語句*](..\chapter3\10_Statements.html#for_statement) +> *循環語句* → [*for-in語句*](..\chapter3\10_Statements.html#for_in_statement) +> *循環語句* → [*while語句*](..\chapter3\10_Statements.html#wheetatype類型ile_statement) +> *循環語句* → [*do-while語句*](..\chapter3\10_Statements.html#do_while_statement) + +### For 語句 + +`for`語句允許在重複執行代碼塊的同時,遞增一個計數器。 + +`for`語句的形式如下: + +> for `initialzation`; `condition`; `increment` { +> `statements` +> } + +*initialzation*、*condition* 和 *increment* 之間的分號,以及包圍循環體 *statements* 的大括號都是不可省略的。 + +`for`語句的執行流程如下: + +1. *initialzation* 只會被執行一次,通常用於聲明和初始化在接下來的循環中需要使用的變量。 +2. 計算 *condition* 表達式: + 如果為`true`,*statements* 將會被執行,然後轉到第3步。如果為`false`,*statements* 和 *increment* 都不會被執行,`for`至此執行完畢。 +3. 計算 *increment* 表達式,然後轉到第2步。 + +定義在 *initialzation* 中的變量僅在`for`語句的作用域以內有效。*condition* 表達式的值的類型必須遵循`LogicValue`協議。 + +> For 循環語法 +> *for語句* → **for** [*for初始條件*](..\chapter3\10_Statements.html#for_init) _可選_ **;** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ **;** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *for語句* → **for** **(** [*for初始條件*](..\chapter3\10_Statements.html#for_init) _可選_ **;** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ **;** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ **)** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *for初始條件* → [*變量聲明*](..\chapter3\05_Declarations.html#variable_declaration) | [*表達式列表*](..\chapter3\04_Expressions.html#expression_list) + +### For-In 語句 + +`for-in`語句允許在重複執行代碼塊的同時,迭代集合(或遵循`Sequence`協議的任意類型)中的每一項。 + +`for-in`語句的形式如下: + +> for `item` in `collection` { +> `statements` +> } + +`for-in`語句在循環開始前會調用 *collection* 表達式的`generate`方法來獲取一個生成器類型(這是一個遵循`Generator`協議的類型)的值。接下來循環開始,調用 *collection* 表達式的`next`方法。如果其返回值不是`None`,它將會被賦給 *item*,然後執行 *statements*,執行完畢後回到循環開始處;否則,將不會賦值給 *item* 也不會執行 *statements*,`for-in`至此執行完畢。 + +> For-In 循環語法 +> *for-in語句* → **for** [*模式*](..\chapter3\07_Patterns.html#pattern) **in** [*表達式*](..\chapter3\04_Expressions.html#expression) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) + +### While 語句 + +`while`語句允許重複執行代碼塊。 + +`while`語句的形式如下: + +> while `condition` { +> `statements` +> } + +`while`語句的執行流程如下: + +1. 計算 *condition* 表達式: + 如果為真`true`,轉到第2步。如果為`false`,`while`至此執行完畢。 +2. 執行 *statements* ,然後轉到第1步。 + +由於 *condition* 的值在 *statements* 執行前就已計算出,因此`while`語句中的 *statements* 可能會被執行若干次,也可能不會被執行。 + +*condition* 表達式的值的類型必須遵循`LogicValue`協議。同時,*condition* 表達式也可以使用可選綁定,詳情參見[可選綁定](../chapter2/01_The_Basics.html#optional_binding)。 + +> While 循環語法 +> *while語句* → **while** [*while條件*](..\chapter3\10_Statements.html#while_condition) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) +> *while條件* → [*表達式*](..\chapter3\04_Expressions.html#expression) | [*聲明*](..\chapter3\05_Declarations.html#declaration) + +### Do-While 語句 + +`do-while`語句允許代碼塊被執行一次或多次。 + +`do-while`語句的形式如下: + +> do { +> `statements` +> } while `condition` + +`do-while`語句的執行流程如下: + +1. 執行 *statements*,然後轉到第2步。 +2. 計算 *condition* 表達式: + 如果為`true`,轉到第1步。如果為`false`,`do-while`至此執行完畢。 + +由於 *condition* 表達式的值是在 *statements* 執行後才計算出,因此`do-while`語句中的 *statements* 至少會被執行一次。 + +*condition* 表達式的值的類型必須遵循`LogicValue`協議。同時,*condition* 表達式也可以使用可選綁定,詳情參見[可選綁定](../chapter2/01_The_Basics.html#optional_binding)。 + +> Do-While 循環語法 +> *do-while語句* → **do** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) **while** [*while條件*](..\chapter3\10_Statements.html#while_condition) + + +## 分支語句 + +取決於一個或者多個條件的值,分支語句允許程序執行指定部分的代碼。顯然,分支語句中條件的值將會決定如何分支以及執行哪一塊代碼。Swift 提供兩種類型的分支語句:`if`語句和`switch`語句。 + +`switch`語句中的控制流可以用`break`語句修改,詳情請見[Break 語句](#break_statement)。 + +> 分支語句語法 +> *分支語句* → [*if語句*](..\chapter3\10_Statements.html#if_statement) +> *分支語句* → [*switch語句*](..\chapter3\10_Statements.html#switch_statement) + +### If 語句 + +取決於一個或多個條件的值,`if`語句將決定執行哪一塊代碼。 + +`if`語句有兩種標準形式,在這兩種形式裡都必須有大括號。 + +第一種形式是當且僅當條件為真時執行代碼,像下面這樣: + +> if `condition` { +> `statements` +> } + +第二種形式是在第一種形式的基礎上添加 *else 語句*,當只有一個 else 語句時,像下面這樣: + +> if `condition` { +> `statements to execute if condition is true` +> } else { +> `statements to execute if condition is false` +> } + +同時,else 語句也可包含`if`語句,從而形成一條鏈來測試更多的條件,像下面這樣: + +> if `condition 1` { +> `statements to execute if condition 1 is true` +> } else if `condition 2` { +> `statements to execute if condition 2 is true` +> } +> else { +> `statements to execute if both conditions are false` +> } + +`if`語句中條件的值的類型必須遵循`LogicValue`協議。同時,條件也可以使用可選綁定,詳情參見[可選綁定](../chapter2/01_The_Basics.html#optional_binding)。 + +> If語句語法 +> *if語句* → **if** [*if條件*](..\chapter3\10_Statements.html#if_condition) [*代碼塊*](..\chapter3\05_Declarations.html#code_block) [*else子句(Clause)*](..\chapter3\10_Statements.html#else_clause) _可選_ +> *if條件* → [*表達式*](..\chapter3\04_Expressions.html#expression) | [*聲明*](..\chapter3\05_Declarations.html#declaration) +> *else子句(Clause)* → **else** [*代碼塊*](..\chapter3\05_Declarations.html#code_block) | **else** [*if語句*](..\chapter3\10_Statements.html#if_statement) + +### Switch 語句 + +取決於`switch`語句的*控制表達式(control expression)*,`switch`語句將決定執行哪一塊代碼。 + +`switch`語句的形式如下: + +> switch `control expression` { +> case `pattern 1`: +> `statements` +> case `pattern 2` where `condition`: +> `statements` +> case `pattern 3` where `condition`, +> `pattern 4` where `condition`: +> `statements` +> default: +> `statements` +> } + +`switch`語句的*控制表達式(control expression)*會首先被計算,然後與每一個 case 的模式(pattern)進行匹配。如果匹配成功,程序將會執行對應的 case 分支裡的 *statements*。另外,每一個 case 分支都不能為空,也就是說在每一個 case 分支中至少有一條語句。如果你不想在匹配到的 case 分支中執行代碼,只需在該分支裡寫一條`break`語句即可。 + +可以用作控制表達式的值是十分靈活的,除了標量類型(scalar types,如`Int`、`Character`)外,你可以使用任何類型的值,包括浮點數、字符串、元組、自定義類的實例和可選(optional)類型,甚至是枚舉類型中的成員值和指定的範圍(range)等。關於在`switch`語句中使用這些類型,詳情參見[控制流](../chapter2/05_Control_Flow.html)一章的 [Switch](../chapter2/05_Control_Flow.html#switch)。 + +你可以在模式後面添加一個起保護作用的表達式(guard expression)。*起保護作用的表達式*是這樣構成的:關鍵字`where`後面跟著一個作為額外測試條件的表達式。因此,當且僅當*控制表達式*匹配一個*case*的某個模式且起保護作用的表達式為真時,對應 case 分支中的 *statements* 才會被執行。在下面的例子中,*控制表達式*只會匹配含兩個相等元素的元組,如`(1, 1)`: + +```swift +case let (x, y) where x == y: +``` + +正如上面這個例子,也可以在模式中使用`let`(或`var`)語句來綁定常量(或變量)。這些常量(或變量)可以在其對應的起保護作用的表達式和其對應的*case*塊裡的代碼中引用。但是,如果 case 中有多個模式匹配控制表達式,那麼這些模式都不能綁定常量(或變量)。 + +`switch`語句也可以包含默認(`default`)分支,只有其它 case 分支都無法匹配控制表達式時,默認分支中的代碼才會被執行。一個`switch`語句只能有一個默認分支,而且必須在`switch`語句的最後面。 + +儘管模式匹配操作實際的執行順序,特別是模式的計算順序是不可知的,但是 Swift 規定`switch`語句中的模式匹配的順序和書寫源代碼的順序保持一致。因此,當多個模式含有相同的值且能夠匹配控制表達式時,程序只會執行源代碼中第一個匹配的 case 分支中的代碼。 + +#### Switch 語句必須是完備的 + +在 Swift 中,`switch`語句中控制表達式的每一個可能的值都必須至少有一個 case 分支與之對應。在某些情況下(例如,表達式的類型是`Int`),你可以使用默認塊滿足該要求。 + +#### 不存在隱式的貫穿(fall through) + +當匹配的 case 分支中的代碼執行完畢後,程序會終止`switch`語句,而不會繼續執行下一個 case 分支。這就意味著,如果你想執行下一個 case 分支,需要顯式地在你需要的 case 分支裡使用`fallthrough`語句。關於`fallthrough`語句的更多信息,詳情參見 [Fallthrough 語句](#fallthrough_statement)。 + +> Switch語句語法 +> *switch語句* → **switch** [*表達式*](..\chapter3\04_Expressions.html#expression) **{** [*SwitchCase列表*](..\chapter3\10_Statements.html#switch_cases) _可選_ **}** +> *SwitchCase列表* → [*SwitchCase*](..\chapter3\10_Statements.html#switch_case) [*SwitchCase列表*](..\chapter3\10_Statements.html#switch_cases) _可選_ +> *SwitchCase* → [*case標籤*](..\chapter3\10_Statements.html#case_label) [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) | [*default標籤*](..\chapter3\10_Statements.html#default_label) [*多條語句(Statements)*](..\chapter3\10_Statements.html#statements) +> *SwitchCase* → [*case標籤*](..\chapter3\10_Statements.html#case_label) **;** | [*default標籤*](..\chapter3\10_Statements.html#default_label) **;** +> *case標籤* → **case** [*case項列表*](..\chapter3\10_Statements.html#case_item_list) **:** +> *case項列表* → [*模式*](..\chapter3\07_Patterns.html#pattern) [*guard-clause*](..\chapter3\10_Statements.html#guard_clause) _可選_ | [*模式*](..\chapter3\07_Patterns.html#pattern) [*guard-clause*](..\chapter3\10_Statements.html#guard_clause) _可選_ **,** [*case項列表*](..\chapter3\10_Statements.html#case_item_list) +> *default標籤* → **default** **:** +> *guard-clause* → **where** [*guard-expression*](..\chapter3\10_Statements.html#guard_expression) +> *guard-expression* → [*表達式*](..\chapter3\04_Expressions.html#expression) + + + 帶標籤的語句 + +你可以在循環語句或`switch`語句前面加上*標籤*,它由標籤名和緊隨其後的冒號(:)組成。在`break`和`continue`後面跟上標籤名可以顯式地在循環語句或`switch`語句中更改控制流,把控制權傳遞給指定標籤標記的語句。關於這兩條語句用法,詳情參見 [Break 語句](#break_statement)和 [Continue 語句](#continue_statement)。 + +標籤的作用域是該標籤所標記的語句之後的所有語句。你可以不使用帶標籤的語句,但只要使用它,標籤名就必唯一。 + +關於使用帶標籤的語句的例子,詳情參見[控制流](../chapter2/05_Control_Flow.html)一章的[帶標籤的語句](../chapter2/05_Control_Flow.html#labeled_statements)。 + +> 標記語句語法 +> *標記語句(Labeled Statement)* → [*語句標籤*](..\chapter3\10_Statements.html#statement_label) [*循環語句*](..\chapter3\10_Statements.html#loop_statement) | [*語句標籤*](..\chapter3\10_Statements.html#statement_label) [*switch語句*](..\chapter3\10_Statements.html#switch_statement) +> *語句標籤* → [*標籤名稱*](..\chapter3\10_Statements.html#label_name) **:** +> *標籤名稱* → [*標識符*](..\chapter3\02_Lexical_Structure.html#identifier) + +## 控制傳遞語句 + +通過無條件地把控制權從一片代碼傳遞到另一片代碼,控制傳遞語句能夠改變代碼執行的順序。Swift 提供四種類型的控制傳遞語句:`break`語句、`continue`語句、`fallthrough`語句和`return`語句。 + +> 控制傳遞語句(Control Transfer Statement) 語法 +> *控制傳遞語句* → [*break語句*](..\chapter3\10_Statements.html#break_statement) +> *控制傳遞語句* → [*continue語句*](..\chapter3\10_Statements.html#continue_statement) +> *控制傳遞語句* → [*fallthrough語句*](..\chapter3\10_Statements.html#fallthrough_statement) +> *控制傳遞語句* → [*return語句*](..\chapter3\10_Statements.html#return_statement) + + +### Break 語句 + +`break`語句用於終止循環或`switch`語句的執行。使用`break`語句時,可以只寫`break`這個關鍵詞,也可以在`break`後面跟上標籤名(label name),像下面這樣: + +> break +> break `label name` + +當`break`語句後面帶標籤名時,可用於終止由這個標籤標記的循環或`switch`語句的執行。 + +而當只寫`break`時,則會終止`switch`語句或上下文中包含`break`語句的最內層循環的執行。 + +在這兩種情況下,控制權都會被傳遞給循環或`switch`語句外面的第一行語句。 + +關於使用`break`語句的例子,詳情參見[控制流](../chapter2/05_Control_Flow.html)一章的 [Break](../chapter2/05_Control_Flow.html#break) 和[帶標籤的語句](../chapter2/05_Control_Flow.html#labeled_statements)。 + +> Break 語句語法 +> *break語句* → **break** [*標籤名稱*](..\chapter3\10_Statements.html#label_name) _可選_ + + +### Continue 語句 + +`continue`語句用於終止循環中當前迭代的執行,但不會終止該循環的執行。使用`continue`語句時,可以只寫`continue`這個關鍵詞,也可以在`continue`後面跟上標籤名(label name),像下面這樣: + +> continue +> continue `label name` + +當`continue`語句後面帶標籤名時,可用於終止由這個標籤標記的循環中當前迭代的執行。 + +而當只寫`break`時,可用於終止上下文中包含`continue`語句的最內層循環中當前迭代的執行。 + +在這兩種情況下,控制權都會被傳遞給循環外面的第一行語句。 + +在`for`語句中,`continue`語句執行後,*increment* 表達式還是會被計算,這是因為每次循環體執行完畢後 *increment* 表達式都會被計算。 + +關於使用`continue`語句的例子,詳情參見[控制流](../chapter2/05_Control_Flow.html)一章的 [Continue](../chapter2/05_Control_Flow.html#continue) 和[帶標籤的語句](../chapter2/05_Control_Flow.html#labeled_statements)。 + +> Continue 語句語法 +> *continue語句* → **continue** [*標籤名稱*](..\chapter3\10_Statements.html#label_name) _可選_ + + +### Fallthrough 語句 + +`fallthrough`語句用於在`switch`語句中傳遞控制權。`fallthrough`語句會把控制權從`switch`語句中的一個 case 傳遞給下一個 case 。這種傳遞是無條件的,即使下一個 case 的模式與`switch`語句的控制表達式的值不匹配。 + +`fallthrough`語句可出現在`switch`語句中的任意 case 裡,但不能出現在最後一個 case 分支中。同時,`fallthrough`語句也不能把控制權傳遞給使用了可選綁定的 case 分支。 + +關於在`switch`語句中使用`fallthrough`語句的例子,詳情參見[控制流](../chapter2/05_Control_Flow.html)一章的[控制傳遞語句](../chapter2/05_Control_Flow.html#control_transfer_statements)。 + +> Fallthrough 語句語法 +> *fallthrough語句* → **fallthrough** + +### Return 語句 + +`return`語句用於在函數或方法的實現中將控制權傳遞給調用者,接著程序將會從調用者的位置繼續向下執行。 + +使用`return`語句時,可以只寫`return`這個關鍵詞,也可以在`return`後面跟上表達式,像下面這樣: + +> return +> return `expression` + +當`return`語句後面帶表達式時,表達式的值將會返回給調用者。如果表達式值的類型與調用者期望的類型不匹配,Swift 則會在返回表達式的值之前將表達式值的類型轉換為調用者期望的類型。 + +而當只寫`return`時,僅僅是將控制權從該函數或方法傳遞給調用者,而不返回一個值。(這就是說,該函數或方法的返回類型為`Void`或`()`) + +> Return 語句語法 +> *return語句* → **return** [*表達式*](..\chapter3\04_Expressions.html#expression) _可選_ diff --git a/source-tw/chapter3/chapter3.md b/source-tw/chapter3/chapter3.md new file mode 100644 index 00000000..e69de29b diff --git a/source-tw/cover.jpg b/source-tw/cover.jpg new file mode 100644 index 00000000..ec72c5a4 Binary files /dev/null and b/source-tw/cover.jpg differ diff --git a/source-tw/cover_small.jpg b/source-tw/cover_small.jpg new file mode 100644 index 00000000..04bafd22 Binary files /dev/null and b/source-tw/cover_small.jpg differ