diff --git a/CNAME b/CNAME index eb06dc03..23971755 100755 --- a/CNAME +++ b/CNAME @@ -1 +1 @@ -www.swiftguide.cn \ No newline at end of file +gg.swiftguide.cn diff --git a/README.md b/README.md index 24b42d6a..0c8dbb3d 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ 《The Swift Programming Language》in Chinese ============================================= -中文版Apple官方Swift教程《The Swift Programming Language》 +中文版 Apple 官方 Swift 教程《The Swift Programming Language》 [英文原版](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097-CH3-ID0) @@ -9,13 +9,17 @@ # 在线阅读 -使用Gitbook制作,可以直接[在线阅读](http://swiftguide.cn/)。 +使用 Gitbook 制作,可以直接[在线阅读](http://swiftguide.cn/)。 # 当前阶段 -已经更新到Swift 2.0,对应苹果官方文档7.23的更新。 +已经更新到 Swift 2.2。 -# 2.0译者记录 +# 2.1 & 2.2 译者记录 + +详见各章节开头位置。 + +# 2.0 译者记录 - About Swift ([xtymichael](https://github.com/xtymichael)) - A Swift Tour([xtymichael](https://github.com/xtymichael)) @@ -55,7 +59,7 @@ - Generic Parameters and Arguments([wardenNScaiyi](https://github.com/wardenNScaiyi)) - Summary of the Grammar([miaosiqi](https://github.com/miaosiqi)) -# 1.0译者记录 +# 1.0 译者记录 * 欢迎使用 Swift * 关于 Swift ([numbbbbb]) diff --git a/cover/cover.jpg b/cover/cover.jpg index e247cc81..b4dd6e8c 100644 Binary files a/cover/cover.jpg and b/cover/cover.jpg differ diff --git a/cover/cover_2.0.jpg b/cover/cover_2.0.jpg new file mode 100644 index 00000000..e247cc81 Binary files /dev/null and b/cover/cover_2.0.jpg differ diff --git a/source-tw/chapter2/06_Functions.md b/source-tw/chapter2/06_Functions.md index 6e3c5e5b..f67c6daf 100644 --- a/source-tw/chapter2/06_Functions.md +++ b/source-tw/chapter2/06_Functions.md @@ -76,7 +76,7 @@ println(sayHelloAgain("Anna")) func halfOpenRangeLength(start: Int, end: Int) -> Int { return end - start } -println(halfOpenRangeLength(1, 10)) +println(halfOpenRangeLength(1, end:10)) // prints "9" ``` diff --git a/source/README.md b/source/README.md index 252787d3..d1cf2e96 100755 --- a/source/README.md +++ b/source/README.md @@ -1,16 +1,15 @@ +> 已同步更新到 Swift 2.2 + # 2.0 新的开始 > Swift 兴趣交流群:`131595168`, `146932759`, `151336833`, `153549217`. **加入一个群即可,请勿重复添加** -> Swift 开发者社区 - -> Swift 资源汇总 - -> Swift 优秀newsletter +> 订阅 Swift 开发者周报,每周获取最新 Swift 资源 > 如果您觉得这个项目不错,请点击Star一下,您的支持是我们最大的动力。 + ## 1 开源项目完成难,维护更难。 @@ -72,5 +71,6 @@ Swift 2.0 参与者名单(按照章节顺序): - [ray16897188](https://github.com/ray16897188) - [wardenNScaiyi](https://github.com/wardenNScaiyi) - [miaosiqi](https://github.com/miaosiqi) +- [Realank](https://github.com/Realank) -最后,感谢[极客学院](http://wiki.jikexueyuan.com)提供的wiki系统,在国内访问起来速度很快,优化后的样式看起来也更舒服。 \ No newline at end of file +最后,感谢[极客学院](http://wiki.jikexueyuan.com)提供的wiki系统,在国内访问起来速度很快,优化后的样式看起来也更舒服。 diff --git a/source/SUMMARY.md b/source/SUMMARY.md index 36344074..4b033a43 100755 --- a/source/SUMMARY.md +++ b/source/SUMMARY.md @@ -17,7 +17,7 @@ * [类和结构体](chapter2/09_Classes_and_Structures.md) * [属性](chapter2/10_Properties.md) * [方法](chapter2/11_Methods.md) - * [下标脚本](chapter2/12_Subscripts.md) + * [下标](chapter2/12_Subscripts.md) * [继承](chapter2/13_Inheritance.md) * [构造过程](chapter2/14_Initialization.md) * [析构过程](chapter2/15_Deinitialization.md) @@ -29,8 +29,8 @@ * [扩展](chapter2/21_Extensions.md) * [协议](chapter2/22_Protocols.md) * [泛型](chapter2/23_Generics.md) - * [权限控制](chapter2/24_Access_Control.md) - * [高级操作符](chapter2/25_Advanced_Operators.md) + * [访问控制](chapter2/24_Access_Control.md) + * [高级运算符](chapter2/25_Advanced_Operators.md) * 语言参考 * [关于语言参考](chapter3/01_About_the_Language_Reference.md) * [词法结构](chapter3/02_Lexical_Structure.md) diff --git a/source/chapter1/02_a_swift_tour.md b/source/chapter1/02_a_swift_tour.md index 823358e2..8e78f23b 100755 --- a/source/chapter1/02_a_swift_tour.md +++ b/source/chapter1/02_a_swift_tour.md @@ -9,6 +9,9 @@ > 2.0 > 翻译+校对:[xtymichael](https://github.com/xtymichael) +> 2.2 +> 翻译:[175](https://github.com/Brian175),2016-04-09 校对:[SketchK](https://github.com/SketchK),2016-05-11 + 本页内容包括: - [简单值(Simple Values)](#simple_values) @@ -17,6 +20,7 @@ - [对象和类(Objects and Classes)](#objects_and_classes) - [枚举和结构体(Enumerations and Structures)](#enumerations_and_structures) - [协议和扩展(Protocols and Extensions)](#protocols_and_extensions) +- [错误处理(Error Handling)](#error_handling) - [泛型(Generics)](#generics) 通常来说,编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现: @@ -30,8 +34,7 @@ print("Hello, world!") 这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解。 > 注意: -> 为了获得最好的体验,在 Xcode 当中使用代码预览功能。代码预览功能可以让你编辑代码并实时看到运行结果。 -> 下载Playground +> 在 Mac 上,下载 Playground 并双击文件在 Xcode 里打开:[https://developer.apple.com/go/?id=swift-tour](https://developer.apple.com/go/?id=swift-tour) ## 简单值 @@ -142,7 +145,13 @@ if let name = optionalName { > 练习: > 把`optionalName`改成`nil`,greeting会是什么?添加一个`else`语句,当`optionalName`是`nil`时给greeting赋一个不同的值。 -如果变量的可选值是`nil`,条件会判断为`false`,大括号中的代码会被跳过。如果不是`nil`,会将值赋给`let`后面的常量,这样代码块中就可以使用这个值了。 +如果变量的可选值是`nil`,条件会判断为`false`,大括号中的代码会被跳过。如果不是`nil`,会将值赋给`let`后面的常量,这样代码块中就可以使用这个值了。 +另一种处理可选值的方法是通过使用 ?? 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。 +```swift +let nickName: String? = nil +let fullName: String = "John Appleseed" +let informalGreeting = "Hi \(nickName ?? fullName)" +``` `switch`支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。 @@ -188,7 +197,7 @@ print(largest) ``` > 练习: -> 添加另一个变量来记录现在和之前最大数字的类型。 +> 添加另一个变量来记录最大数字的种类(kind),同时仍然记录这个最大数字的值。 使用`while`来重复运行一段代码直到不满足条件。循环条件也可以在结尾,保证能至少循环一次。 @@ -206,20 +215,14 @@ repeat { print(m) ``` -你可以在循环中使用`..<`来表示范围,也可以使用传统的写法,两者是等价的: +你可以在循环中使用`..<`来表示范围。 ```swift -var firstForLoop = 0 +var total = 0 for i in 0..<4 { - firstForLoop += i + total += i } -print(firstForLoop) - -var secondForLoop = 0 -for var i = 0; i < 4; ++i { - secondForLoop += i -} -print(secondForLoop) +print(total) ``` 使用`..<`创建的范围不包含上界,如果想包含的话需要使用`...`。 @@ -534,7 +537,7 @@ let aceRawValue = ace.rawValue > 练习: > 写一个函数,通过比较它们的原始值来比较两个`Rank`值。 -在上面的例子中,枚举原始值的类型是`Int`,所以你只需要设置第一个原始值。剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用`rawValue`属性来访问一个枚举成员的原始值。 +默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,`Ace`被显式赋值为 1,并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用`rawValue`属性来访问一个枚举成员的原始值。 使用`init?(rawValue:)`初始化构造器在原始值和枚举值之间进行转换。 @@ -544,7 +547,7 @@ if let convertedRank = Rank(rawValue: 3) { } ``` -枚举的成员值是实际值,并不是原始值的另一种表达方法。实际上,以防原始值没有意义,你不需要设置。 +枚举的成员值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。 ```swift enum Suit { @@ -595,24 +598,24 @@ let threeOfSpadesDescription = threeOfSpades.simpleDescription() ```swift enum ServerResponse { case Result(String, String) - case Error(String) + case Failure(String) } let success = ServerResponse.Result("6:00 am", "8:09 pm") -let failure = ServerResponse.Error("Out of cheese.") +let failure = ServerResponse.Failure("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)" +case let .Failure(message): + print("Failure... \(message)") } ``` > 练习: > 给`ServerResponse`和`switch`添加第三种情况。 -注意如何从`ServerResponse`中提取日升和日落时间并用得到的值用来和`switch`的情况作比较。 +注意日升和日落时间是如何从`ServerResponse`中提取到并与`switch`的`case`相匹配的。 ## 协议和扩展 @@ -683,6 +686,88 @@ print(protocolValue.simpleDescription) 即使`protocolValue`变量运行时的类型是`simpleClass`,编译器会把它的类型当做`ExampleProtocol`。这表示你不能调用类在它实现的协议之外实现的方法或者属性。 + +## 错误处理 + +使用采用`ErrorType`协议的类型来表示错误。 + +```swift +enum PrinterError: ErrorType { + case OutOfPaper + case NoToner + case OnFire +} +``` + +使用`throw`来抛出一个错误并使用`throws`来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并且调用该函数的代码会进行错误处理。 + +```swift +func sendToPrinter(printerName: String) throws -> String { + if printerName == "Never Has Toner" { + throw PrinterError.NoToner + } + return "Job sent" +} +``` + +有多种方式可以用来进行错误处理。一种方式是使用`do-catch`。在`do`代码块中,使用`try`来标记可以抛出错误的代码。在`catch`代码块中,除非你另外命名,否则错误会自动命名为`error`。 + +```swift +do { + let printerResponse = try sendToPrinter("Bi Sheng") + print(printerResponse) +} catch { + print(error) +} +``` + +> 练习: +> 将 printer name 改为`"Never Has Toner"`使`sendToPrinter(_:)`函数抛出错误。 + +可以使用多个`catch`块来处理特定的错误。参照 switch 中的`case`风格来写`catch`。 + +```swift +do { + let printerResponse = try sendToPrinter("Gutenberg") + print(printerResponse) +} catch PrinterError.OnFire { + print("I'll just put this over here, with the rest of the fire.") +} catch let printerError as PrinterError { + print("Printer error: \(printerError).") +} catch { + print(error) +} +``` + +> 练习: +> 在`do`代码块中添加抛出错误的代码。你需要抛出哪种错误来使第一个`catch`块进行接收?怎么使第二个和第三个`catch`进行接收呢? + +另一种处理错误的方式使用`try?`将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为`nil`。否则的话,结果会是一个包含函数返回值的可选值。 + +```swift +let printerSuccess = try? sendToPrinter("Mergenthaler") +let printerFailure = try? sendToPrinter("Never Has Toner") +``` + +使用`defer`代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用`defer`,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执行时机截然不同。 + +```swift +var fridgeIsOpen = false +let fridgeContent = ["milk", "eggs", "leftovers"] + +func fridgeContains(itemName: String) -> Bool { + fridgeIsOpen = true + defer { + fridgeIsOpen = false + } + + let result = fridgeContent.contains(itemName) + return result +} +fridgeContains("banana") +print(fridgeIsOpen) +``` + ## 泛型 diff --git a/source/chapter1/03_revision_history.md b/source/chapter1/03_revision_history.md index c734c5a4..00a796fe 100644 --- a/source/chapter1/03_revision_history.md +++ b/source/chapter1/03_revision_history.md @@ -1,37 +1,44 @@ -# Swift 版本历史记录 +# Swift 文档修订历史 --- +> 1.0 > 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/) > 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117) +[changkun](http://changkun.us/about/) +> +> 1.1 +> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/) +> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117) +[changkun](http://changkun.us/about/) +> +> 1.2 +> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/) +> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117) +[changkun](http://changkun.us/about/) +> +> 2.0 +> 翻译+校对:[changkun](http://changkun.us/about/) +> +> 2.1 +> 翻译+校对:[changkun](http://changkun.us/about/) +> +> 2.2 +> 翻译+校对:[changkun](http://changkun.us/about/) + +本页面根据 [Document Revision History](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/RevisionHistory.html) 进行适配更新。 本页内容包括: -- [XCode6.4 Beta Swift语法文档更新](#xcode6_4_Beta) -- [XCode6.3正式版 Swift语法文档更新](#xcode6_3) -- [XCode6.2正式版 Swift语法文档更新](#xcode6_2) -- [XCode6.2 Beta3 Swift语法文档更新](#xcode6_2_Beta3) -- [XCode6.2 Beta2 Swift语法文档更新](#xcode6_2_Beta2) -- [XCode6.2 Beta1 Swift语法文档更新](#xcode6_2_Beta1) -- [XCode6.1.1正式版 Swift语法文档更新](#xcode6_1_1) -- [XCode6.1 Swift语法文档更新](#xcode6_1) -- [XCode6.1 Beta2 Swift语法文档更新](#xcode6_1_Beta2) -- [XCode6.1 Beta1 Swift语法文档更新](#xcode6_1_Beta1) -- [XCode6 Beta7 Swift语法文档更新](#xcode6_beta7) -- [XCode6 Beta6 Swift语法文档更新](#xcode6_beta6) -- [XCode6 Beta5 Swift语法文档更新](#xcode6_beta5) -- [XCode6 Beta4 Swift语法文档更新](#xcode6_beta4) -- [XCode6 Beta3 Swift语法文档更新](#xcode6_beta3) -- [XCode6 Beta2 Swift语法文档更新](#xcode6_beta2) -- [XCode6 Beta1 Swift语法文档更新](#xcode6_beta1) -- XCode6下载: [老码云盘下载](http://pan.baidu.com/disk/home#from=share_pan_logo&path=%252F%25E8%2580%2581%25E7%25A0%2581%25E4%25BA%2591%25E7%259B%2598-XCode6%252FXCode6-Beta5) +- [Swift 2.2 更新](#swift_2_2) +- [Swift 2.1 更新](#swift_2_1) +- [Swift 2.0 更新](#swift_2_0) +- [Swift 1.2 更新](#swift_1_2) +- [Swift 1.1 更新](#swift_1_1) +- [Swift 1.0 更新](#swift_1_0) -以下部分是针对XCode6每一次Beta版本直至正式版发布,Swift语法部分的更新归类 - - -### XCode6.4 Beta中Swift语法更新 - -***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.4 Beta Release Note总结的更改说明:*** + +### Swift 2.2 更新 @@ -42,10 +49,55 @@ - + @@ -53,9 +105,195 @@
2015-04-132016-03-21
  • - XCode6.4包含了对于构建和调试基于iOS8.4 App的支持 + 更新至 Swift 2.2。 +
  • +
  • + 增加了编译配置语句一节中关于如何根据 Swift 版本进行条件编译。 +
  • +
  • + 增加了显示成员表达式一节中关于如何区分只有参数名不同的方法和构造器的信息。 +
  • +
  • + 增加了选择器表达式一节中针对 Objective-C 选择器的 #selector 语法。 +
  • +
  • + 更新了关联类型协议关联类型声明,使用 associatedtype 关键词修改了对于关联类型的讨论。 +
  • +
  • + 更新了可失败构造器一节中关于当构造器在实例完全初始化之前返回 nil的相关信息。 +
  • +
  • + 增加了比较运算符一节中关于比较元组的信息。 +
  • +
  • + 增加了关键字和标点符号一节中关于使用关键字作为外部参数名的信息。 +
  • +
  • + 增加了声明特性一节中关于@objc特性的讨论,并指出枚举(Enumeration)和枚举用例(Enumaration Case)。 +
  • +
  • + 增加了操作符一节中对于自定义运算符的讨论包含了.。 +
  • +
  • + 增加了重新抛出错误的函数和方法一节中关于重新抛出错误函数不能直接抛出错误的笔记。 +
  • +
  • + 增加了属性观察器一节中关于当作为 in-out 参数传递属性时,属性观察器的调用行为。 +
  • +
  • + 增加了Swift 初见一节中关于错误处理的内容。 +
  • +
  • + 更新了弱引用一节中的图片用以更清楚的展示重新分配过程。 +
  • +
  • + 删除了 C 语言风格的 for 循环,++ 前缀和后缀运算符,以及-- 前缀和后缀运算符。 +
  • +
  • + 删除了对变量函数参数和柯里化函数的特殊语法的讨论。
+ +### Swift 2.1 更新 + + + + + + + + + + + + + + +
发布日期语法变更记录
2015-10-20 +
+ + +### Swift 2.0 更新 + + + + + + + + + + + + + + +
发布日期语法变更记录
2015-09-16
    +
  • + 更新至 Swift 2.0。 +
  • +
  • + 增加了对于错误处理相关内容,包括 错误处理一章、Do 语句Throw 语句Defer 语句以及try 运算符 的多个小节。 +
  • +
  • + 更新了表示并抛出错误一节,现在所有类型均可遵循 ErrorType 协议。 +
  • +
  • + 增加了将错误转换成可选值一节 try? 关键字的相关信息。 +
  • +
  • + 增加了枚举一章的递归枚举一节和声明一章的任意类型用例的枚举一节中关于递归枚举的内容。 +
  • +
  • + 增加了控制流一章中a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID523">检查 API 可用性一节和语句一章中可用性条件一节中关于 API 可用性检查的内容。 +
  • + +
  • + 增加了控制流一章的早期退出一节和语句一章的guard语句中关于新 guard 语句的内容。 +
  • +
  • + 增加了协议一章中协议扩展一节中关于协议扩展的内容。 +
  • +
  • + 增加了访问控制一章中单元测试 target 的访问级别一节中关于单元测试的访问控制相关的内容。 +
  • +
  • + 增加了模式一章中可选模式一节中的新可选模式。 +
  • +
  • + 更新了 Repeat-While 一节中关于repeat-while循环的信息。 +
  • +
  • + 更新了字符串和字符一章,现在String在 Swift 标准库中不再遵循CollectionType协议。 +
  • +
  • + 增加了打印常量和变量一节中关于新 Swift 标准库中关于 print(_:separator:terminator) 的信息。 +
  • +
  • + 增加了枚举一章中原始值的隐式赋值一节和声明一章的包含原始值类型的枚举一节中关于包含String原始值的枚举用例的行为。 +
  • +
  • + 增加了自闭包一节中关于@autoclosure特性的相关信息,包括它的@autoclosure(escaping)形式。 +
  • +
  • + 更新了声明特性一节中关于@avaliablewarn_unused_result特性的相关内容。 +
  • +
  • + 更新了类型特性一节中关于@convention特性的相关信息。 +
  • +
  • + 增加了可选绑定一节中关于使用where子句进行多可选绑定的内容。 +
  • +
  • + 增加了字符串字面量一节中关于在编译时使用 + 运算符凭借字符串字面量的相关信息。 +
  • +
  • + 增加了元类型一节中关于元类型值的比较和使用它们通过构造器表达式构造实例。 +
  • +
  • + 增加了断言调试一节中关于用户定义断言是被警用的相关内容。 +
  • +
  • + 更新了声明特性一节中,对@NSManaged特性的讨论,现在这个特性可以被应用到一个确定实例方法。 +
  • +
  • + 更新了可变参数一节,现在可变参数可以声明在函数参数列表的任意位置中。 +
  • +
  • + 增加了重写可失败构造器一节中,关于非可失败构造器相当于一个可失败构造器通过父类构造器的结果进行强制拆包的相关内容。 +
  • +
  • + 增加了任意类型用例的枚举一节中关于枚举用例作为函数的内容。 +
  • +
  • + 增加了构造器表达式一节中关于显式引用一个构造器的内容。 +
  • +
  • + 更新了编译控制语句一节中关于编译信息以及行控制语句的相关信息。 +
  • +
  • + 更新了元类型一节中关于如何从元类型值中构造类实例。 +
  • +
  • + 更新了弱引用一节中关于弱引用作为缓存的显存的不足。 +
  • +
  • + 更新了类型特性一节,提到了存储型特性其实是懒加载。 +
  • +
  • + 更新了捕获类型一节,阐明了变量和常量在闭包中如何被捕获。 +
  • +
  • + 更新了声明特性一节用以描述如何在类中使用@objc关键字。 +
  • +
  • + 增加了错误处理一节中关于执行throw语句的性能的讨论。增加了 Do 语句一节中相似的信息。 +
  • +
  • + 更新了类型特性一节中关于类、结构体和枚举的存储型和计算型特性的信息。 +
  • +
  • + 更新了Break 语句一节中关于带标签的 break 语句。 +
  • +
  • + 更新了属性观察器一节,阐明了willSetdidSet观察器的行为。 +
  • +
  • + 增加了访问级一节中关于private作用域访问的相关信息。 +
  • +
  • + 增加了弱引用一节中关于若应用在垃圾回收系统和 ARC 之间的区别。 +
  • +
  • + 更新了字符串字面量中特殊字符一节中对 Unicode 标量更精确的定义。 +
  • +
+
+ + + +### Swift 1.2 更新 -### XCode6.3中Swift语法更新 -***注意:苹果此时发布了统一的版本XCode6.3,其中将以前的XCode6.3 Beta系列版本合并, 而XCode6.3共计发布了4次Beta版本,[老码团队](http://weibo.com/u/5241713117)通过Release Note总结的详细更改说明请参看:[Swift语法更新记录表格](https://docs.baihui.com/sheet/published.do?rid=mxpis6d36a8b7bc254c36ae2a808c64c2361e)*** @@ -68,6 +306,9 @@ - - -
2015-4-8
    +
  • + 更新至 Swift 1.2。 +
  • Swift现在自身提供了一个Set集合类型,更多信息请看集合 @@ -85,7 +326,7 @@ 增加了一个新的指导章节,它是关于字符串索引
  • - 从溢出运算符中移除了溢出除运算符和求余溢出运算符 + 从溢出运算符中移除了溢出除运算符(&/)和求余溢出运算符(&%)。
  • 更新了常量和常量属性在声明和构造时的规则,更多信息,请看常量声明 @@ -111,29 +352,7 @@
  • 更新了运算符章节来明确指明一些例子来说明自定义运算符所支持的特性,如数学运算符,各种符号,Unicode符号块等
  • -
-
- - -### XCode6.2正式版中Swift语法更新 - -***注意:苹果此时发布了统一的版本XCode6.2,其中将以前的XCode6.2 Beta系列版本合并*** - - - - - - - - - - - -
发布日期语法变更记录
2015-02-09
    -
  • +
  • 在函数作用域中的常量声明时可以不被初始化,它必须在第一次使用前被赋值。更多的信息,请看常量声明
  • @@ -157,139 +376,9 @@
- -### XCode6.2 Beta3中Swift语法更新 -***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.2 Beta3 Release Note总结的更改说明:*** - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-12-19 -
    -
  • - 在对Watch App做消息通知模拟调试时,第一个payload.apns文件将会被默认选择 -
  • -
  • - 在为Watch App使用asset catalog时,38mm和42mm尺寸的图片就会被使用 -
  • -
  • - 在做Watch App开发时,@IBAction属性支持WKInterfaceSwitchWKInterfaceSlider Swift类型了 -
  • -
  • - 现在可以通过Device窗口安装,删除和访问App容器中的数据了。 -
  • -
-
- - -### XCode6.2 Beta2中Swift语法更新 - -***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.2 Beta2 Release Note总结的更改说明:*** - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-12-10
    -
  • - 现在在Interface Builder中可以针对特定的Device设备自定义Watch应用的Layout布局了 -
  • -
-
- - -### XCode6.2 Beta1中Swift语法更新 - -***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.2 Beta1 Release Note总结的更改说明:*** - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-11-28
    -
  • - XCode6.2包含了iOS8.2 SDK,该SDK中包含WatchKit用来开发Apple Watch应用。 -
  • -
  • - 在工具集中增加了对WatchKit的支持: - 1)UI设计工具增加了Apple Watch应用的界面组件,通知和小部件。 - 2)增加了调试和性能统计功能 - 3)增加Apple Watch应用的模拟器帮助调试应用功能 -
  • -
  • - 为了使Apple Watch应用能够正常工作,一些具体的参数必须设置: - 1)WatchKit中扩展配置文件Info.plist中的NSExtensionAttributes配置项WKAppBundleIdentifier必须和WatchKit App中的通用配置文件中的属性CFBundleIdentifier项目保持一致。2)WatchKit中的CFBundleIdentifier配置项必须和WKCompanionAppBundleIdentifier中的配置项保持一致 -
  • -
-
- - -### XCode6.1.1中Swift语法更新 - -***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode6.1.1 Release Note总结的更改说明:*** - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-12-2
    -
  • - 在SourceKit中一些导致Crash的常见问题被修复,比如名字冲突和遗留废弃数据的问题等。 -
  • -
  • - 把纯正的Swift类对象实例赋值给AnyObject量不会再Crash了。 -
  • -
  • - 在泛型使用场景下,遵循了协议类要求的构造器方法或者类型方法可以直接调用继承类中的方法了。 -
  • -
  • - 修正了InterfaceBuild中如果图片名字含有“/”时,会在OSX10.10上Crash或者无法打开的问题 -
  • -
-
- - -### XCode6.1中Swift语法更新 - -***注意:苹果此时发布了统一的版本XCode6.1,其中将以前的XCode6.0.1和XCode6.1 Beta系列版本合并*** + +### Swift 1.1 更新 @@ -303,112 +392,32 @@ - - -
2014-10-16 -
- - -### XCode6.1 Beta2中Swift语法更新 - -***注意:苹果此时发布了XCode6.0.1版本(也称为XCode6正式版),此版本用于iOS的开发,同时也发布子版本XCode6.1 Beta2,此版本为OSX开发做准备,以下所述的更改仅对XCode6.1 Beta2有效*** - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-09-15
  • - 带有原始值的枚举类型增加了一个rawValue属性替代toRaw()方法,同时使用了一个以rawValue为参数的失败构造器来替代fromRaw()方法。更多的信息,请看原始值(Raw Values)带原始值的枚举类型(Enumerations with Cases of a Raw-Value Type)部分 + 常量和变量的 Any 类型现可以包含函数实例。更新了关于 Any 相关的示例来展示如何在 switch 语句中如何检查并转换到一个函数类型。
  • -
-
- - -### XCode6.1 Beta1中Swift语法更新 - -***注意:苹果此时发布了XCode6 GM版本,此版本用于iOS的开发,同时也发布子版本XCode6.1 Beta1,此版本为OSX开发做准备,以下所述的更改仅对XCode6.1 Beta1有效*** - - - - - - - - - - - -
发布日期语法变更记录
2014-09-09
- -### XCode6 Beta7中Swift语法更新 - -***注意:苹果在这个版本发布后没有及时的更新Swift Programming Language文档,以下是[老码团队](http://weibo.com/u/5241713117)通过XCode Beta7 Release Note总结的更改说明:*** - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-09-03
    -
  • - 实现了内部库的修改和适配,主要包括如下: - 1)大量内部类或者函数遵循Optional类型和协议 - 2)移除大部分函数返回类型隐式解封可选类型的使用 - -
  • -
  • - 对于泛型的类库函数或接口统一从T!更换为T?T,这样使得语法更加严谨,明确了可能返回为空和不为空的情况 -
  • -
  • - 字符类型不能使用+运算法链接,可以以String(C1)+String(2) 的方式实现字符间链接 -
  • -
  • - 重写了Sort函数,解决了栈溢出的问题 -
  • -
-
- - -### XCode6 Beta6中Swift语法更新 + +### Swift 1.0 更新 @@ -421,6 +430,9 @@ - - -
2014-08-18 -
- - -### XCode6 Beta5中Swift语法更新 - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-08-04 -
- - -#### XCode6 Beta4中Swift语法更新 - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-07-21 -
- - -#### XCode6 Beta3中Swift语法更新 - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-07-7 -
- - -#### XCode6 Beta2中Swift语法更新 - - - - - - - - - - - - - - -
发布日期语法变更记录
2014-07-7
    -
  • - 发布新的文档用以详述Swift - 苹果公司针对iOS和OS X应用的全新开发语言 -
  • -
-
- - -#### XCode6 Beta1中Swift语法更新 - - - - - - - - - - - - diff --git a/source/chapter2/01_The_Basics.md b/source/chapter2/01_The_Basics.md index 842a85b8..84e4c1db 100755 --- a/source/chapter2/01_The_Basics.md +++ b/source/chapter2/01_The_Basics.md @@ -6,8 +6,19 @@ > 校对:[lslxdx](https://github.com/lslxdx) > 2.0 -> 翻译+校对:[xtymichael](https://github.com/xtymichael) -> 定稿:[shanks](http://codebuild.me) +> 翻译+校对:[xtymichael](https://github.com/xtymichael) + +> 2.1 +> 翻译:[Prayer](https://github.com/futantan) +> 校对:[shanks](http://codebuild.me),[overtrue](https://github.com/overtrue) +> +> 2.2 +> 校对:[SketchK](https://github.com/SketchK) +> +> 3.0 +> 校对:[CMB](https://github.com/chenmingbiao) +> +> 版本日期:2016-09-13 本页包含内容: @@ -39,27 +50,27 @@ - [错误处理](#error_handling) - [断言](#assertions) -Swift 是一门开发 iOS, OS X 和 watchOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。 +Swift 是一门开发 iOS, macOS, watchOS 和 tvOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。 -Swift 包含了 C 和 Objective-C 上所有基础数据类型,`Int`表示整型值;`Double`和`Float`表示浮点型值;`Bool`是布尔型值;`String`是文本型数据。Swift 还提供了三个基本的集合类型,`Array`,`Set`和`Dictionary`,详见[集合类型](04_Collection_Types.html)。 +Swift 包含了 C 和 Objective-C 上所有基础数据类型,`Int`表示整型值; `Double` 和 `Float` 表示浮点型值; `Bool` 是布尔型值;`String` 是文本型数据。 Swift 还提供了三个基本的集合类型,`Array` ,`Set` 和 `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 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示 “那儿有一个值,并且它等于 x ” 或者 “那儿没有值” 。可选有点像在 Objective-C 中使用 `nil` ,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的 `nil` 指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。 -Swift 是一门类型安全的语言,可选类型就是一个很好的例子。Swift 可以让你清楚地知道值的类型。如果你的代码期望得到一个`String`,类型安全会阻止你不小心传入一个`Int`。你可以在开发阶段尽早发现并修正错误。 +Swift 是一门类型安全的语言,可选类型就是一个很好的例子。Swift 可以让你清楚地知道值的类型。如果你的代码期望得到一个 `String` ,类型安全会阻止你不小心传入一个 `Int` 。同样的,如果你的代码期望得到一个 `String`,类型安全会阻止你意外传入一个可选的 `String` 。你可以在开发阶段尽早发现并修正错误。 ## 常量和变量 -常量和变量把一个名字(比如`maximumNumberOfLoginAttempts`或者`welcomeMessage`)和一个指定类型的值(比如数字`10`或者字符串`"Hello"`)关联起来。常量的值一旦设定就不能改变,而变量的值可以随意更改。 +常量和变量把一个名字(比如 `maximumNumberOfLoginAttempts` 或者 `welcomeMessage` )和一个指定类型的值(比如数字 `10` 或者字符串 `"Hello"` )关联起来。常量的值一旦设定就不能改变,而变量的值可以随意更改。 ### 声明常量和变量 -常量和变量必须在使用前声明,用`let`来声明常量,用`var`来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数: +常量和变量必须在使用前声明,用 `let` 来声明常量,用 `var` 来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数: ```swift let maximumNumberOfLoginAttempts = 10 @@ -68,7 +79,7 @@ var currentLoginAttempt = 0 这两行代码可以被理解为: -“声明一个名字是`maximumNumberOfLoginAttempts`的新常量,并给它一个值`10`。然后,声明一个名字是`currentLoginAttempt`的变量并将它的值初始化为`0`。” +“声明一个名字是 `maximumNumberOfLoginAttempts` 的新常量,并给它一个值 `10` 。然后,声明一个名字是 `currentLoginAttempt` 的变量并将它的值初始化为 `0` 。” 在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。 @@ -78,15 +89,15 @@ var currentLoginAttempt = 0 var x = 0.0, y = 0.0, z = 0.0 ``` ->注意: -如果你的代码中有不需要改变的值,请使用`let`关键字将它声明为常量。只将需要改变的值声明为变量。 +> 注意: +> 如果你的代码中有不需要改变的值,请使用 `let` 关键字将它声明为常量。只将需要改变的值声明为变量。 ### 类型标注 当你声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型。如果要添加类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。 -这个例子给`welcomeMessage`变量添加了类型标注,表示这个变量可以存储`String`类型的值: +这个例子给 `welcomeMessage` 变量添加了类型标注,表示这个变量可以存储 `String` 类型的值: ```swift var welcomeMessage: String @@ -94,11 +105,11 @@ var welcomeMessage: String 声明中的冒号代表着“是...类型”,所以这行代码可以被理解为: -“声明一个类型为`String`,名字为`welcomeMessage`的变量。” +“声明一个类型为 `String` ,名字为 `welcomeMessage` 的变量。” -“类型为`String`”的意思是“可以存储任意`String`类型的值。” +“类型为 `String` ”的意思是“可以存储任意 `String` 类型的值。” -`welcomeMessage`变量现在可以被设置成任意字符串: +`welcomeMessage` 变量现在可以被设置成任意字符串: ```swift welcomeMessage = "Hello" @@ -111,7 +122,7 @@ var red, green, blue: Double ``` > 注意: -一般来说你很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值,Swift可以推断出这个常量或者变量的类型,请参考[类型安全和类型推断](#type_safety_and_type_inference)。在上面的例子中,没有给`welcomeMessage`赋初始值,所以变量`welcomeMessage`的类型是通过一个类型标注指定的,而不是通过初始值推断的。 +> 一般来说你很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值,Swift可以推断出这个常量或者变量的类型,请参考[类型安全和类型推断](#type_safety_and_type_inference)。在上面的例子中,没有给 `welcomeMessage` 赋初始值,所以变量 `welcomeMessage` 的类型是通过一个类型标注指定的,而不是通过初始值推断的。 ### 常量和变量的命名 @@ -129,7 +140,7 @@ let 🐶🐮 = "dogcow" 一旦你将常量或者变量声明为确定的类型,你就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,你也不能将常量与变量进行互转。 > 注意: -如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。 +> 如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。 你可以更改现有的变量值为其他同类型的值,在下面的例子中,`friendlyWelcome`的值从`"Hello!"`改为了`"Bonjour!"`: @@ -157,7 +168,7 @@ print(friendlyWelcome) // 输出 "Bonjour!" ``` -`print(_:separator:terminator:)`是一个用来输出一个或多个值到适当输出区的全局函数。如果你用 Xcode,`print(_:separator:terminator:)`将会输出内容到“console”面板上。`separator`和`terminator`参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给`terminator`参数--例如,`print(someValue, terminator:"")`。关于参数默认值的更多信息,请参考[默认参数值](./06_Functions.html#default_parameter_values)。 +`print(_:separator:terminator:)` 是一个用来输出一个或多个值到适当输出区的全局函数。如果你用 Xcode,`print(_:separator:terminator:)` 将会输出内容到“console”面板上。`separator` 和 `terminator` 参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 `terminator` 参数--例如,`print(someValue, terminator:"")` 。关于参数默认值的更多信息,请参考[默认参数值](./06_Functions.html#default_parameter_values)。 Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义: @@ -208,38 +219,39 @@ let cat = "🐱"; print(cat) ## 整数 -整数就是没有小数部分的数字,比如`42`和`-23`。整数可以是`有符号`(正、负、零)或者`无符号`(正、零)。 +整数就是没有小数部分的数字,比如 `42` 和 `-23` 。整数可以是 `有符号`(正、负、零)或者 `无符号`(正、零)。 -Swift 提供了8,16,32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是`UInt8`,32位有符号整数类型是`Int32`。就像 Swift 的其他类型一样,整数类型采用大写命名法。 +Swift 提供了8,16,32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是`UInt8`,32位有符号整数类型是 `Int32` 。就像 Swift 的其他类型一样,整数类型采用大写命名法。 ### 整数范围 -你可以访问不同整数类型的`min`和`max`属性来获取对应类型的最小值和最大值: +你可以访问不同整数类型的 `min` 和 `max` 属性来获取对应类型的最小值和最大值: ```swift let minValue = UInt8.min // minValue 为 0,是 UInt8 类型 let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型 -``` -`min`和`max`所传回值的类型,正是其所对的整数类型(如上例UInt8, 所传回的类型是UInt8),可用在表达式中相同类型值旁。 +``` + +`min` 和 `max` 所传回值的类型,正是其所对的整数类型(如上例UInt8, 所传回的类型是UInt8),可用在表达式中相同类型值旁。 ### Int 一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型`Int`,长度与当前平台的原生字长相同: -* 在32位平台上,`Int`和`Int32`长度相同。 -* 在64位平台上,`Int`和`Int64`长度相同。 +* 在32位平台上,`Int` 和 `Int32` 长度相同。 +* 在64位平台上,`Int` 和 `Int64` 长度相同。 -除非你需要特定长度的整数,一般来说使用`Int`就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,`Int`可以存储的整数范围也可以达到`-2,147,483,648`~`2,147,483,647`,大多数时候这已经足够大了。 +除非你需要特定长度的整数,一般来说使用 `Int` 就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,`Int` 可以存储的整数范围也可以达到 `-2,147,483,648` ~ `2,147,483,647` ,大多数时候这已经足够大了。 ### UInt -Swift 也提供了一个特殊的无符号类型`UInt`,长度与当前平台的原生字长相同: +Swift 也提供了一个特殊的无符号类型 `UInt`,长度与当前平台的原生字长相同: -* 在32位平台上,`UInt`和`UInt32`长度相同。 -* 在64位平台上,`UInt`和`UInt64`长度相同。 +* 在32位平台上,`UInt` 和 `UInt32` 长度相同。 +* 在64位平台上,`UInt` 和 `UInt64` 长度相同。 > 注意: 尽量不要使用`UInt`,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用`Int`,即使你要存储的值已知是非负的。统一使用`Int`可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考[类型安全和类型推断](#type_safety_and_type_inference)。 @@ -247,15 +259,15 @@ Swift 也提供了一个特殊的无符号类型`UInt`,长度与当前平台 ## 浮点数 -浮点数是有小数部分的数字,比如`3.14159`,`0.1`和`-273.15`。 +浮点数是有小数部分的数字,比如 `3.14159` ,`0.1` 和 `-273.15`。 -浮点类型比整数类型表示的范围更大,可以存储比`Int`类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型: +浮点类型比整数类型表示的范围更大,可以存储比 `Int` 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型: * `Double`表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。 * `Float`表示32位浮点数。精度要求不高的话可以使用此类型。 > 注意: -`Double`精确度很高,至少有15位数字,而`Float`最少只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围。 +`Double`精确度很高,至少有15位数字,而`Float`只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择 `Double`。 ## 类型安全和类型推断 @@ -268,32 +280,32 @@ Swift 是一个*类型安全(type safe)*的语言。类型安全的语言可 因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。 -当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如`42`和`3.14159`。) +当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如 `42` 和 `3.14159` 。) -例如,如果你给一个新常量赋值`42`并且没有标明类型,Swift 可以推断出常量类型是`Int`,因为你给它赋的初始值看起来像一个整数: +例如,如果你给一个新常量赋值 `42` 并且没有标明类型,Swift 可以推断出常量类型是 `Int` ,因为你给它赋的初始值看起来像一个整数: ```swift let meaningOfLife = 42 // meaningOfLife 会被推测为 Int 类型 ``` -同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是`Double`: +同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是 `Double`: ```swift let pi = 3.14159 // pi 会被推测为 Double 类型 ``` -当推断浮点数的类型时,Swift 总是会选择`Double`而不是`Float`。 +当推断浮点数的类型时,Swift 总是会选择 `Double` 而不是`Float`。 -如果表达式中同时出现了整数和浮点数,会被推断为`Double`类型: +如果表达式中同时出现了整数和浮点数,会被推断为 `Double` 类型: ```swift let anotherPi = 3 + 0.14159 // anotherPi 会被推测为 Double 类型 ``` -原始值`3`没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为`Double`类型。 +原始值 `3` 没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为 `Double` 类型。 ## 数值型字面量 @@ -314,13 +326,15 @@ let octalInteger = 0o21 // 八进制的17 let hexadecimalInteger = 0x11 // 十六进制的17 ``` -浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是`0x`)。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。浮点字面量还有一个可选的指数(exponent,在十进制浮点数中通过大写或者小写的`e`来指定,在十六进制浮点数中通过大写或者小写的`p`来指定。 +浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是 `0x` )。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 `e` 来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 `p` 来指定。 + +如果一个十进制数的指数为 `exp`,那这个数相当于基数和10^exp的乘积: -如果一个十进制数的指数为`exp`,那这个数相当于基数和10^exp的乘积: * `1.25e2` 表示 1.25 × 10^2,等于 `125.0`。 * `1.25e-2` 表示 1.25 × 10^-2,等于 `0.0125`。 -如果一个十六进制数的指数为`exp`,那这个数相当于基数和2^exp的乘积: +如果一个十六进制数的指数为`exp`,那这个数相当于基数和2^exp的乘积: + * `0xFp2` 表示 15 × 2^2,等于 `60.0`。 * `0xFp-2` 表示 15 × 2^-2,等于 `3.75`。 @@ -369,9 +383,9 @@ let one: UInt8 = 1 let twoThousandAndOne = twoThousand + UInt16(one) ``` -现在两个数字的类型都是`UInt16`,可以进行相加。目标常量`twoThousandAndOne`的类型被推断为`UInt16`,因为它是两个`UInt16`值的和。 +现在两个数字的类型都是 `UInt16`,可以进行相加。目标常量 `twoThousandAndOne` 的类型被推断为 `UInt16`,因为它是两个 `UInt16` 值的和。 -`SomeType(ofInitialValue)`是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,`UInt16`有一个构造器,可以接受一个`UInt8`类型的值,所以这个构造器可以用现有的`UInt8`来创建一个新的`UInt16`。注意,你并不能传入任意类型的值,只能传入`UInt16`内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考[扩展](./20_Extensions.html)。 +`SomeType(ofInitialValue)` 是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,`UInt16` 有一个构造器,可以接受一个`UInt8`类型的值,所以这个构造器可以用现有的 `UInt8` 来创建一个新的 `UInt16`。注意,你并不能传入任意类型的值,只能传入 `UInt16` 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考[扩展](./20_Extensions.html)。 ### 整数和浮点数转换 @@ -385,16 +399,16 @@ let pi = Double(three) + pointOneFourOneFiveNine // pi 等于 3.14159,所以被推测为 Double 类型 ``` -这个例子中,常量`three`的值被用来创建一个`Double`类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。 +这个例子中,常量 `three` 的值被用来创建一个 `Double` 类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。 -浮点数到整数的反向转换同样行,整数类型可以用`Double`或者`Float`类型来初始化: +浮点数到整数的反向转换同样行,整数类型可以用 `Double` 或者 `Float` 类型来初始化: ```swift let integerPi = Int(pi) // integerPi 等于 3,所以被推测为 Int 类型 ``` -当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说`4.75`会变成`4`,`-3.9`会变成`-3`。 +当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 `4.75` 会变成 `4`,`-3.9` 会变成 `-3`。 > 注意: 结合数字类常量和变量不同于结合数字类字面量。字面量`3`可以直接和字面量`0.14159`相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。 @@ -422,16 +436,16 @@ var maxAmplitudeFound = AudioSample.min ## 布尔值 -Swift 有一个基本的布尔(Boolean)类型,叫做`Bool`。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常量,`true`和`false`: +Swift 有一个基本的布尔(Boolean)类型,叫做`Bool`。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常量,`true` 和 `false`: ```swift let orangesAreOrange = true let turnipsAreDelicious = false ``` -`orangesAreOrange`和`turnipsAreDelicious`的类型会被推断为`Bool`,因为它们的初值是布尔字面量。就像之前提到的`Int`和`Double`一样,如果你创建变量的时候给它们赋值`true`或者`false`,那你不需要将常量或者变量声明为`Bool`类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。 +`orangesAreOrange` 和 `turnipsAreDelicious` 的类型会被推断为 `Bool`,因为它们的初值是布尔字面量。就像之前提到的 `Int` 和 `Double` 一样,如果你创建变量的时候给它们赋值 `true` 或者 `false`,那你不需要将常量或者变量声明为 `Bool` 类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。 -当你编写条件语句比如`if`语句的时候,布尔值非常有用: +当你编写条件语句比如 `if` 语句的时候,布尔值非常有用: ```swift if turnipsAreDelicious { @@ -444,7 +458,7 @@ if turnipsAreDelicious { 条件语句,例如`if`,请参考[控制流](./05_Control_Flow.html)。 -如果你在需要使用`Bool`类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误: +如果你在需要使用 `Bool` 类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误: ```swift let i = 1 @@ -462,7 +476,7 @@ if i == 1 { } ``` -`i == 1`的比较结果是`Bool`类型,所以第二个例子可以通过类型检查。类似`i == 1`这样的比较,请参考[基本操作符](./05_Control_Flow.html)。 +`i == 1` 的比较结果是 `Bool` 类型,所以第二个例子可以通过类型检查。类似 `i == 1` 这样的比较,请参考[基本操作符](./05_Control_Flow.html)。 和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。 @@ -471,16 +485,16 @@ if i == 1 { *元组(tuples)*把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。 -下面这个例子中,`(404, "Not Found")`是一个描述 *HTTP 状态码(HTTP status code)*的元组。HTTP 状态码是当你请求网页的时候 web 服务器返回的一个特殊值。如果你请求的网页不存在就会返回一个`404 Not Found`状态码。 +下面这个例子中,`(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)`的元组”。 +`(404, "Not Found")` 元组把一个 `Int` 值和一个 `String` 值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为 `(Int, String)` 的元组”。 -你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为`(Int, Int, Int)`或者`(String, Bool)`或者其他任何你想要的组合的元组。 +你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为 `(Int, Int, Int)` 或者 `(String, Bool)` 或者其他任何你想要的组合的元组。 你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了: @@ -524,7 +538,7 @@ print("The status message is \(http200Status.description)") // 输出 "The status message is OK" ``` -作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个`(Int, String)`元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考[函数参数与返回值](./06_Functions.html#Function_Parameters_and_Return_Values)。 +作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 `(Int, String)` 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考[函数参数与返回值](./06_Functions.html#Function_Parameters_and_Return_Values)。 > 注意: 元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。请参考[类和结构体](./09_Classes_and_Structures.html)。 @@ -541,11 +555,11 @@ print("The status message is \(http200Status.description)") * 没有值 > 注意: -C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回`nil`,`nil`表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如`NSNotFound`)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示_任意类型_的值缺失,并不需要一个特殊值。 +C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回`nil`,`nil`表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如`NSNotFound`)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示*任意类型*的值缺失,并不需要一个特殊值。 -来看一个例子。Swift 的`String`类型有一种构造器,作用是将一个`String`值转换成一个`Int`值。然而,并不是所有的字符串都可以转换成一个整数。字符串`"123"`可以被转换成数字`123`,但是字符串`"hello, world"`不行。 +来看一个例子。Swift 的 `Int` 类型有一种构造器,作用是将一个 `String` 值转换成一个 `Int` 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 `"123"` 可以被转换成数字 `123` ,但是字符串 `"hello, world"` 不行。 -下面的例子使用这种构造器来尝试将一个`String`转换成`Int`: +下面的例子使用这种构造器来尝试将一个 `String` 转换成 `Int`: ```swift let possibleNumber = "123" @@ -553,7 +567,7 @@ let convertedNumber = Int(possibleNumber) // convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int" ``` -因为该构造器可能会失败,所以它返回一个_可选类型_(optional)`Int`,而不是一个`Int`。一个可选的`Int`被写作`Int?`而不是`Int`。问号暗示包含的值是可选类型,也就是说可能包含`Int`值也可能*不包含值*。(不能包含其他任何值比如`Bool`值或者`String`值。只能是`Int`或者什么都没有。) +因为该构造器可能会失败,所以它返回一个*可选类型*(optional)`Int`,而不是一个 `Int`。一个可选的 `Int` 被写作 `Int?` 而不是 `Int`。问号暗示包含的值是可选类型,也就是说可能包含 `Int` 值也可能*不包含值*。(不能包含其他任何值比如 `Bool` 值或者 `String` 值。只能是 `Int` 或者什么都没有。) ### nil @@ -570,7 +584,7 @@ serverResponseCode = nil > 注意: `nil`不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。 -如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为`nil`: +如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 `nil`: ```swift var surveyAnswer: String? @@ -578,22 +592,23 @@ var surveyAnswer: String? ``` > 注意: -Swift 的`nil`和 Objective-C 中的`nil`并不一样。在 Objective-C 中,`nil`是一个指向不存在对象的指针。在 Swift 中,`nil`不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为`nil`,不只是对象类型。 +Swift 的 `nil` 和 Objective-C 中的 `nil` 并不一样。在 Objective-C 中,`nil` 是一个指向不存在对象的指针。在 Swift 中,`nil` 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 `nil`,不只是对象类型。 ### if 语句以及强制解析 -你可以使用`if`语句和`nil`比较来判断一个可选值是否包含值。你可以使用“相等”(`==`)或“不等”(`!=`)来执行比较。 +你可以使用 `if` 语句和 `nil` 比较来判断一个可选值是否包含值。你可以使用“相等”(`==`)或“不等”(`!=`)来执行比较。 -如果可选类型有值,它将不等于`nil`: +如果可选类型有值,它将不等于 `nil`: ```swift if convertedNumber != nil { print("convertedNumber contains some integer value.") } // 输出 "convertedNumber contains some integer value." -``` -当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(`!`)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的_强制解析(forced unwrapping)_: +``` + +当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(`!`)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的*强制解析(forced unwrapping)*: ```swift if convertedNumber != nil { @@ -602,17 +617,17 @@ if convertedNumber != nil { // 输出 "convertedNumber has an integer value of 123." ``` -更多关于`if`语句的内容,请参考[控制流](05_Control_Flow.html)。 +更多关于 `if` 语句的内容,请参考[控制流](05_Control_Flow.html)。 > 注意: -使用`!`来获取一个不存在的可选值会导致运行时错误。使用`!`来强制解析值之前,一定要确定可选包含一个非`nil`的值。 +使用 `!` 来获取一个不存在的可选值会导致运行时错误。使用 `!` 来强制解析值之前,一定要确定可选包含一个非 `nil` 的值。 ### 可选绑定 -使用*可选绑定(optional binding)*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在`if`和`while`语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if`和`while`语句,请参考[控制流](./05_Control_Flow.html)。 +使用*可选绑定(optional binding)*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 `if` 和 `while` 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if` 和 `while` 语句,请参考[控制流](./05_Control_Flow.html)。 -像下面这样在`if`语句中写一个可选绑定: +像下面这样在 `if` 语句中写一个可选绑定: ```swift if let constantName = someOptional { @@ -620,7 +635,7 @@ if let constantName = someOptional { } ``` -你可以像上面这样使用可选绑定来重写`possibleNumber`这个[例子](./01_The_Basics.html#optionals): +你可以像上面这样使用可选绑定来重写 `possibleNumber` 这个[例子](./01_The_Basics.html#optionals): ```swift if let actualNumber = Int(possibleNumber) { @@ -633,25 +648,37 @@ if let actualNumber = Int(possibleNumber) { 这段代码可以被理解为: -“如果`Int(possibleNumber)`返回的可选`Int`包含一个值,创建一个叫做`actualNumber`的新常量并将可选包含的值赋给它。” +“如果 `Int(possibleNumber)` 返回的可选 `Int` 包含一个值,创建一个叫做 `actualNumber` 的新常量并将可选包含的值赋给它。” -如果转换成功,`actualNumber`常量可以在`if`语句的第一个分支中使用。它已经被可选类型_包含的_值初始化过,所以不需要再使用`!`后缀来获取它的值。在这个例子中,`actualNumber`只被用来输出转换结果。 +如果转换成功,`actualNumber` 常量可以在 `if` 语句的第一个分支中使用。它已经被可选类型 *包含的* 值初始化过,所以不需要再使用 `!` 后缀来获取它的值。在这个例子中,`actualNumber` 只被用来输出转换结果。 -你可以在可选绑定中使用常量和变量。如果你想在`if`语句的第一个分支中操作`actualNumber`的值,你可以改成`if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。 +你可以在可选绑定中使用常量和变量。如果你想在`if`语句的第一个分支中操作 `actualNumber` 的值,你可以改成 `if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。 -你可以包含多个可选绑定在`if`语句中,并使用`where`子句做布尔值判断。 +你可以包含多个可选绑定或多个布尔条件在一个 `if` 语句中,只要使用逗号分开就行。如果所有可选绑定的值为 `nil` 或者所有布尔条件语句都为 `false`,这样整个 `if` 条件判断都是为 `false`,这时你就需要使用嵌套 `if` 条件语句来处理,如下所示: ```swift -if let firstNumber = Int("4"), secondNumber = Int("42") where firstNumber < secondNumber { - print("\(firstNumber) < \(secondNumber)") -} -// prints "4 < 42" -``` +if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 { + print("\(firstNumber) < \(secondNumber) < 100") +} +// Prints "4 < 42 < 100" + +if let firstNumber = Int("4") { + if let secondNumber = Int("42") { + if firstNumber < secondNumber && secondNumber < 100 { + print("\(firstNumber) < \(secondNumber) < 100") + } + } +} +// Prints "4 < 42 < 100" +``` + +> 注意: +> 在 `if` 条件语句中使用常量和变量来创建一个可选绑定,仅在 `if` 语句的句中(`body`)中才能获取到值。相反,在 `guard` 语句中使用常量和变量来创建一个可选绑定,仅在 `guard` 语句外且在语句后才能获取到值,请参考[控制流](./05_Control_Flow#early_exit.html)。 ### 隐式解析可选类型 -如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过`if`语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。 +如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过 `if` 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。 有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型_总会_有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。 @@ -659,7 +686,7 @@ if let firstNumber = Int("4"), secondNumber = Int("42") where firstNumber < seco 当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考[无主引用以及隐式解析可选属性](./16_Automatic_Reference_Counting.html#unowned_references_and_implicitly_unwrapped_optional_properties)。 -一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型`String`和隐式解析可选类型`String`之间的区别: +一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 `String` 和隐式解析可选类型 `String` 之间的区别: ```swift let possibleString: String? = "An optional string." @@ -672,7 +699,7 @@ let implicitString: String = assumedString // 不需要感叹号 你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。 > 注意: -如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。 +> 如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。 你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值: @@ -693,11 +720,11 @@ if let definiteString = assumedString { ``` > 注意: -如果一个变量之后可能变成`nil`的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是`nil`的话,请使用普通可选类型。 +> 如果一个变量之后可能变成`nil`的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是`nil`的话,请使用普通可选类型。 ## 错误处理 -你可以使用*错误处理(error handling)*来应对程序执行中可能会遇到的错误条件。 +你可以使用 *错误处理(error handling)* 来应对程序执行中可能会遇到的错误条件。 相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。 @@ -725,38 +752,38 @@ do { 这里有一个错误处理如何用来应对不同错误条件的例子。 ```swift -func makeASandwich() throws { - // ... -} - -do { - try makeASandwich() - eatASandwich() -} catch Error.OutOfCleanDishes { - washDishes() -} catch Error.MissingIngredients(let ingredients) { - buyGroceries(ingredients) +func makeASandwich() throws { + // ... +} + +do { + try makeASandwich() + eatASandwich() +} catch SandwichError.outOfCleanDishes { + washDishes() +} catch SandwichError.missingIngredients(let ingredients) { + buyGroceries(ingredients) } ``` -在此例中,`makeASandwich()`(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为`makeASandwich()`抛出错误,函数调用被包裹在`try`表达式中。将函数包裹在一个`do`语句中,任何被抛出的错误会被传播到提供的`catch`从句中。 +在此例中,`makeASandwich()`(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为 `makeASandwich()` 抛出错误,函数调用被包裹在 `try` 表达式中。将函数包裹在一个 `do` 语句中,任何被抛出的错误会被传播到提供的 `catch` 从句中。 -如果没有错误被抛出, `eatASandwich()`函数会被调用。如果一个匹配`Error.OutOfCleanDishes`的错误被抛出,`washDishes`函数会被调用。如果一个匹配`Error.MissingIngredients`的错误被抛出,`buyGroceries(_:)`函数会随着被`catch`所捕捉到的关联值`[String]`被调用。 +如果没有错误被抛出,`eatASandwich()` 函数会被调用。如果一个匹配 `SandwichError.outOfCleanDishes` 的错误被抛出,`washDishes()` 函数会被调用。如果一个匹配 `SandwichError.missingIngredients` 的错误被抛出,`buyGroceries(_:)` 函数会被调用,并且使用 `catch` 所捕捉到的关联值 `[String]` 作为参数。 抛出,捕捉,以及传播错误会在[错误处理](./18_Error_Handling.html)章节详细说明。 ## 断言 -可选类型可以让你判断值是否存在,你可以在代码中优雅地处理值缺失的情况。然而,在某些情况下,如果值缺失或者值并不满足特定的条件,你的代码可能没办法继续执行。这时,你可以在你的代码中触发一个*断言(assertion)*来结束代码运行并通过调试来找到值缺失的原因。 +可选类型可以让你判断值是否存在,你可以在代码中优雅地处理值缺失的情况。然而,在某些情况下,如果值缺失或者值并不满足特定的条件,你的代码可能没办法继续执行。这时,你可以在你的代码中触发一个 *断言(assertion)* 来结束代码运行并通过调试来找到值缺失的原因。 ### 使用断言进行调试 -断言会在运行时判断一个逻辑条件是否为`true`。从字面意思来说,断言“断言”一个条件是否为真。你可以使用断言来保证在运行其他代码之前,某些重要的条件已经被满足。如果条件判断为`true`,代码运行会继续进行;如果条件判断为`false`,代码执行结束,你的应用被终止。 +断言会在运行时判断一个逻辑条件是否为 `true`。从字面意思来说,断言“断言”一个条件是否为真。你可以使用断言来保证在运行其他代码之前,某些重要的条件已经被满足。如果条件判断为 `true`,代码运行会继续进行;如果条件判断为 `false`,代码执行结束,你的应用被终止。 如果你的代码在调试环境下触发了一个断言,比如你在 Xcode 中构建并运行一个应用,你可以清楚地看到不合法的状态发生在哪里并检查断言被触发时你的应用的状态。此外,断言允许你附加一条调试信息。 -你可以使用全局`assert(_:_file:line:)`函数来写一个断言。向这个函数传入一个结果为`true`或者`false`的表达式以及一条信息,当表达式的结果为`false`的时候这条信息会被显示: +你可以使用全局 `assert(_:_:file:line:)` 函数来写一个断言。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示: ```swift let age = -3 @@ -764,7 +791,7 @@ assert(age >= 0, "A person's age cannot be less than zero") // 因为 age < 0,所以断言会触发 ``` -在这个例子中,只有`age >= 0`为`true`的时候,即`age`的值非负的时候,代码才会继续执行。如果`age`的值是负数,就像代码中那样,`age >= 0`为`false`,断言被触发,终止应用。 +在这个例子中,只有 `age >= 0` 为 `true` 的时候,即 `age` 的值非负的时候,代码才会继续执行。如果 `age` 的值是负数,就像代码中那样,`age >= 0` 为 `false`,断言被触发,终止应用。 如果不需要断言信息,可以省略,就像这样: @@ -773,17 +800,17 @@ assert(age >= 0) ``` > 注意: -当代码使用优化编译的时候,断言将会被禁用,例如在 Xcode 中,使用默认的 target Release 配置选项来 build 时,断言会被禁用。 +> 当代码使用优化编译的时候,断言将会被禁用,例如在 Xcode 中,使用默认的 target Release 配置选项来 build 时,断言会被禁用。 ### 何时使用断言 当条件可能为假时使用断言,但是最终一定要_保证_条件为真,这样你的代码才能继续运行。断言的适用情景: -* 整数类型的下标索引被传入一个自定义下标脚本实现,但是下标索引值可能太小或者太大。 +* 整数类型的下标索引被传入一个自定义下标实现,但是下标索引值可能太小或者太大。 * 需要给函数传入一个值,但是非法的值可能导致函数不能正常执行。 -* 一个可选值现在是`nil`,但是后面的代码运行需要一个非`nil`值。 +* 一个可选值现在是 `nil`,但是后面的代码运行需要一个非 `nil` 值。 -请参考[下标脚本](./12_Subscripts.html)和[函数](./06_Functions.html)。 +请参考[下标](./12_Subscripts.html)和[函数](./06_Functions.html)。 > 注意: -断言可能导致你的应用终止运行,所以你应当仔细设计你的代码来让非法条件不会出现。然而,在你的应用发布之前,有时候非法条件可能出现,这时使用断言可以快速发现问题。 +> 断言可能导致你的应用终止运行,所以你应当仔细设计你的代码来让非法条件不会出现。然而,在你的应用发布之前,有时候非法条件可能出现,这时使用断言可以快速发现问题。 diff --git a/source/chapter2/02_Basic_Operators.md b/source/chapter2/02_Basic_Operators.md index 41d5aad3..cac08640 100755 --- a/source/chapter2/02_Basic_Operators.md +++ b/source/chapter2/02_Basic_Operators.md @@ -8,41 +8,47 @@ > 2.0 > 翻译+校对:[JackAlan](https://github.com/AlanMelody) +> 2.1 +> 校对:[shanks](http://codebuild.me) + +> 2.2 +> 翻译+校对:[Cee](https://github.com/Cee) 校对:[SketchK](https://github.com/SketchK),2016-05-11 + 本页包含内容: - [术语](#terminology) - [赋值运算符](#assignment_operator) - [算术运算符](#arithmetic_operators) -- [组合赋值运算符(Compound Assignment Operators)](#compound_assignment_operators) +- [组合赋值运算符](#compound_assignment_operators) - [比较运算符](#comparison_operators) -- [三目运算符(Ternary Conditional Operator)](#ternary_conditional_operator) +- [三目运算符](#ternary_conditional_operator) - [空合运算符](#nil_coalescing_operator) - [区间运算符](#range_operators) - [逻辑运算符](#logical_operators) -运算符是检查、改变、合并值的特殊符号或短语。例如,加号`+`将两个数相加(如`let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符`&&`(如`if enteredDoorCode && passedRetinaScan`),或让 i 值加1的便捷自增运算符`++i`等。 +运算符是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。 -Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误。如:赋值符(`=`)不返回值,以防止把想要判断相等运算符(`==`)的地方写成赋值符导致的错误。算术运算符(`+`,`-`,`*`,`/`,`%`等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](./24_Advanced_Operators.html#overflow_operators)。 +Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误。如:赋值符(`=`)不返回值,以防止把想要判断相等运算符(`==`)的地方写成赋值符导致的错误。算术运算符(`+`,`-`,`*`,`/`,`%`等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](../chapter2/25_Advanced_Operators.html#overflow_operators)。 -区别于 C 语言,在 Swift 中你可以对浮点数进行取余运算(`%`),Swift 还提供了 C 语言没有的表达两数之间的值的区间运算符(`a.. ## 术语 -运算符有一元、二元和三元运算符。 +运算符分为一元、二元和三元运算符。 -- 一元运算符对单一操作对象操作(如`-a`)。一元运算符分前置运算符和后置运算符,前置运算符需紧跟在操作对象之前(如`!b`),后置运算符需紧跟在操作对象之后(如`i++`)。 -- 二元运算符操作两个操作对象(如`2 + 3`),是中置的,因为它们出现在两个操作对象之间。 +- 一元运算符对单一操作对象操作(如 `-a`)。一元运算符分前置运算符和后置运算符,前置运算符需紧跟在操作对象之前(如 `!b`),后置运算符需紧跟在操作对象之后(如 `c!`)。 +- 二元运算符操作两个操作对象(如 `2 + 3`),是中置的,因为它们出现在两个操作对象之间。 - 三元运算符操作三个操作对象,和 C 语言一样,Swift 只有一个三元运算符,就是三目运算符(`a ? b : c`)。 -受运算符影响的值叫操作数,在表达式`1 + 2`中,加号`+`是二元运算符,它的两个操作数是值`1`和`2`。 +受运算符影响的值叫操作数,在表达式 `1 + 2` 中,加号 `+` 是二元运算符,它的两个操作数是值 `1` 和 `2`。 ## 赋值运算符 -赋值运算(`a = b`),表示用`b`的值来初始化或更新`a`的值: +赋值运算(`a = b`),表示用 `b` 的值来初始化或更新 `a` 的值: ```swift let b = 10 @@ -55,7 +61,7 @@ a = b ```swift let (x, y) = (1, 2) -// 现在 x 等于 1, y 等于 2 +// 现在 x 等于 1,y 等于 2 ``` 与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以以下代码是错误的: @@ -66,7 +72,7 @@ if x = y { } ``` -这个特性使你无法把(`==`)错写成(`=`),由于`if x = y`是错误代码,Swift帮你避免此类错误的的发生。 +这个特性使你无法把(`==`)错写成(`=`),由于 `if x = y` 是错误代码,Swift 能帮你避免此类错误发生。 ## 算术运算符 @@ -85,9 +91,9 @@ Swift 中所有数值类型都支持了基本的四则算术运算: 10.0 / 2.5 // 等于 4.0 ``` -与 C 语言和 Objective-C 不同的是,Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如`a &+ b`)。详情参见[溢出运算符](./24_Advanced_Operators.html#overflow_operators)。 +与 C 语言和 Objective-C 不同的是,Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如 `a &+ b`)。详情参见[溢出运算符](../chapter2/25_Advanced_Operators.html#overflow_operators)。 -加法运算符也可用于`String`的拼接: +加法运算符也可用于 `String` 的拼接: ```swift "hello, " + "world" // 等于 "hello, world" @@ -95,16 +101,16 @@ Swift 中所有数值类型都支持了基本的四则算术运算: ### 求余运算符 -求余运算(`a % b`)是计算`b`的多少倍刚刚好可以容入`a`,返回多出来的那部分(余数)。 +求余运算(`a % b`)是计算 `b` 的多少倍刚刚好可以容入`a`,返回多出来的那部分(余数)。 ->注意: -求余运算(`%`)在其他语言也叫取模运算。然而严格说来,我们看该运算符对负数的操作结果,"求余"比"取模"更合适些。 +> 注意: +求余运算(`%`)在其他语言也叫取模运算。然而严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。 -我们来谈谈取余是怎么回事,计算`9 % 4`,你先计算出`4`的多少倍会刚好可以容入`9`中: +我们来谈谈取余是怎么回事,计算 `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(用橙色标出) +你可以在 `9` 中放入两个 `4`,那余数是 1(用橙色标出)。 在 Swift 中可以表达为: @@ -112,13 +118,13 @@ Swift 中所有数值类型都支持了基本的四则算术运算: 9 % 4 // 等于 1 ``` -为了得到`a % b`的结果,`%`计算了以下等式,并输出`余数`作为结果: +为了得到 `a % b` 的结果,`%` 计算了以下等式,并输出`余数`作为结果: a = (b × 倍数) + 余数 -当`倍数`取最大值的时候,就会刚好可以容入`a`中。 +当`倍数`取最大值的时候,就会刚好可以容入 `a` 中。 -把`9`和`4`代入等式中,我们得`1`: +把 `9` 和 `4` 代入等式中,我们得 `1`: 9 = (4 × 2) + 1 @@ -128,13 +134,13 @@ Swift 中所有数值类型都支持了基本的四则算术运算: -9 % 4 // 等于 -1 ``` -把`-9`和`4`代入等式,`-2`是取到的最大整数: +把 `-9` 和 `4` 代入等式,`-2` 是取到的最大整数: -9 = (4 × -2) + -1 -余数是`-1`。 +余数是 `-1`。 -在对负数`b`求余时,`b`的符号会被忽略。这意味着 `a % b` 和 `a % -b`的结果是相同的。 +在对负数 `b` 求余时,`b` 的符号会被忽略。这意味着 `a % b` 和 `a % -b` 的结果是相同的。 ### 浮点数求余计算 @@ -144,46 +150,14 @@ Swift 中所有数值类型都支持了基本的四则算术运算: 8 % 2.5 // 等于 0.5 ``` -这个例子中,`8`除于`2.5`等于`3`余`0.5`,所以结果是一个`Double`值`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`的值。如果你只想修改`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 @@ -202,28 +176,29 @@ let minusSix = -6 let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6 ``` -虽然一元`+`什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。 +虽然一元 `+` 什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。 -## 复合赋值(Compound Assignment Operators) +## 组合赋值运算符 -如同 C 语言,Swift 也提供把其他运算符和赋值运算(`=`)组合的复合赋值运算符,组合加运算(`+=`)是其中一个例子: +如同 C 语言,Swift 也提供把其他运算符和赋值运算(`=`)组合的组合赋值运算符,组合加运算(`+=`)是其中一个例子: ```swift var a = 1 -a += 2 // a 现在是 3 +a += 2 +// a 现在是 3 ``` -表达式`a += 2`是`a = a + 2`的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。 +表达式 `a += 2` 是 `a = a + 2` 的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。 ->注意: +> 注意: 复合赋值运算没有返回值,`let b = a += 2`这类代码是错误。这不同于上面提到的自增和自减运算符。 在[表达式](../chapter3/04_Expressions.html)章节里有复合运算符的完整列表。 ‌ -## 比较运算符 +## 比较运算符(Comparison Operators) 所有标准 C 语言中的比较运算都可以在 Swift 中使用: @@ -235,7 +210,7 @@ a += 2 // a 现在是 3 - 小于等于(`a <= b`) > 注意: -Swift 也提供恒等`===`和不恒等`!==`这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在[类与结构](./09_Classes_and_Structures.html)。 +Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在[类与结构](../chapter2/09_Classes_and_Structures.html)。 每个比较运算都返回了一个标识表达式是否成立的布尔值: @@ -260,12 +235,25 @@ if name == "world" { // 输出 "hello, world", 因为 `name` 就是等于 "world" ``` -关于`if`语句,请看[控制流](./05_Control_Flow.html)。 +关于 `if` 语句,请看[控制流](../chapter2/05_Control_Flow.html)。 + +当元组中的值可以比较时,你也可以使用这些运算符来比较它们的大小。例如,因为 `Int` 和 `String` 类型的值可以比较,所以类型为 `(Int, String)` 的元组也可以被比较。相反,`Bool` 不能被比较,也意味着存有布尔类型的元组不能被比较。 + +比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如: + +```swift +(1, "zebra") < (2, "apple") // true,因为 1 小于 2 +(3, "apple") < (3, "bird") // true,因为 3 等于 3,但是 apple 小于 bird +(4, "dog") == (4, "dog") // true,因为 4 等于 4,dog 等于 dog +``` + +> 注意: +Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。 -## 三目运算符(Ternary Conditional Operator) +## 三目运算符(Ternary Conditional Operator) -三目运算符的特殊在于它是有三个操作数的运算符,它的原型是 `问题 ? 答案1 : 答案2`。它简洁地表达根据`问题`成立与否作出二选一的操作。如果`问题`成立,返回`答案1`的结果; 如果不成立,返回`答案2`的结果。 +三目运算符的特殊在于它是有三个操作数的运算符,它的形式是 `问题 ? 答案 1 : 答案 2`。它简洁地表达根据 `问题`成立与否作出二选一的操作。如果 `问题` 成立,返回 `答案 1` 的结果;反之返回 `答案 2` 的结果。 三目运算符是以下代码的缩写形式: @@ -277,7 +265,7 @@ if question { } ``` -这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出50点;如果没有表头,只需高出20点: +这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出 50 点;如果没有表头,只需高出 20 点: ```swift let contentHeight = 40 @@ -300,28 +288,25 @@ if hasHeader { // rowHeight 现在是 90 ``` -第一段代码例子使用了三目运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将`rowHeight`定义成变量,因为它的值无需在`if`语句中改变。 +第一段代码例子使用了三目运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变。 三目运算提供有效率且便捷的方式来表达二选一的选择。需要注意的事,过度使用三目运算符会使简洁的代码变的难懂。我们应避免在一个组合语句中使用多个三目运算符。 -## 空合运算符(Nil Coalescing Operator) +## 空合运算符(Nil Coalescing Operator) -空合运算符(`a ?? b`)将对可选类型`a`进行空判断,如果`a`包含一个值就进行解封,否则就返回一个默认值`b`.这个运算符有两个条件: +空合运算符(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解封,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。 -- 表达式`a`必须是Optional类型 -- 默认值`b`的类型必须要和`a`存储值的类型保持一致 - -空合运算符是对以下代码的简短表达方法 +空合运算符是对以下代码的简短表达方法: ```swift a != nil ? a! : b ``` -上述代码使用了三目运算符。当可选类型`a`的值不为空时,进行强制解封(`a!`)访问`a`中值,反之当`a`中值为空时,返回默认值b。无疑空合运算符(`??`)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。 +上述代码使用了三目运算符。当可选类型 `a` 的值不为空时,进行强制解封(`a!`),访问 `a` 中的值;反之返回默认值 `b`。无疑空合运算符(`??`)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。 > 注意: -如果`a`为非空值(`non-nil`),那么值`b`将不会被估值。这也就是所谓的短路求值。 +如果 `a` 为非空值(`non-nil`),那么值 `b` 将不会被计算。这也就是所谓的短路求值。 下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择: @@ -333,10 +318,10 @@ var colorNameToUse = userDefinedColorName ?? defaultColorName // userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red" ``` -`userDefinedColorName`变量被定义为一个可选`String`类型,默认值为`nil`。由于`userDefinedColorName`是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为`colorNameToUse`的变量赋予一个字符串类型初始值。 -由于`userDefinedColorName`值为空,因此表达式`userDefinedColorName ?? defaultColorName`返回`defaultColorName`的值,即`red`。 +`userDefinedColorName` 变量被定义为一个可选的 `String` 类型,默认值为 `nil`。由于 `userDefinedColorName` 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 `colorNameToUse` 的变量赋予一个字符串类型初始值。 +由于 `userDefinedColorName` 值为空,因此表达式 `userDefinedColorName ?? defaultColorName` 返回 `defaultColorName` 的值,即 `red`。 -另一种情况,分配一个非空值(`non-nil`)给`userDefinedColorName`,再次执行空合运算,运算结果为封包在`userDefaultColorName`中的值,而非默认值。 +另一种情况,分配一个非空值(`non-nil`)给 `userDefinedColorName`,再次执行空合运算,运算结果为封包在 `userDefaultColorName` 中的值,而非默认值。 ```swift userDefinedColorName = "green" @@ -345,14 +330,14 @@ colorNameToUse = userDefinedColorName ?? defaultColorName ``` -## 区间运算符 +## 区间运算符(Range Operators) Swift 提供了两个方便表达一个区间的值的运算符。 ### 闭区间运算符 -闭区间运算符(`a...b`)定义一个包含从`a`到`b`(包括`a`和`b`)的所有值的区间,`b`必须大于等于`a`。 +闭区间运算符(`a...b`)定义一个包含从 `a` 到 `b`(包括 `a` 和 `b`)的所有值的区间。`a` 的值不能超过 `b`。 ‌ -闭区间运算符在迭代一个区间的所有值时是非常有用的,如在`for-in`循环中: +闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 `for-in` 循环中: ```swift for index in 1...5 { @@ -365,14 +350,14 @@ for index in 1...5 { // 5 * 5 = 25 ``` -关于`for-in`,请看[控制流](./05_Control_Flow.html)。 +关于 `for-in`,请看[控制流](../chapter2/05_Control_Flow.html)。 ### 半开区间运算符 -半开区间(`a.. -## 逻辑运算 +## 逻辑运算(Logical Operators) 逻辑运算的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。 @@ -399,9 +384,9 @@ for i in 0..注意: -Swift 逻辑操作符`&&`和`||`是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。 +> 注意: +Swift 逻辑操作符 `&&` 和 `||` 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。 ### 使用括号来明确优先级 diff --git a/source/chapter2/03_Strings_and_Characters.md b/source/chapter2/03_Strings_and_Characters.md index 76ad2027..12c31586 100755 --- a/source/chapter2/03_Strings_and_Characters.md +++ b/source/chapter2/03_Strings_and_Characters.md @@ -8,6 +8,18 @@ > 2.0 > 翻译+校对:[DianQK](https://github.com/DianQK) +> 2.1 +> 翻译:[DianQK](https://github.com/DianQK) +> 校对:[shanks](http://codebuild.me), [Realank](https://github.com/Realank), + +> 2.2 +> 校对:[SketchK](https://github.com/SketchK) + +> 3.0 +> 校对:[CMB](https://github.com/chenmingbiao) +> +> 版本日期:2016-09-13 + 本页包含内容: - [字符串字面量](#string_literals) @@ -23,20 +35,20 @@ - [比较字符串](#comparing_strings) - [字符串的 Unicode 表示形式](#unicode_representations_of_strings) - `String`是例如"hello, world","albatross"这样的有序的`Character`(字符)类型的值的集合。通过`String`类型来表示。 -一个`String`的内容可以用变量的方式读取,它包括一个`Character`值的集合。 +一个`String`的内容可以用许多方式读取,它包括一个`Character`值的集合。 + 创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。 -字符串连接操作只需要简单地通过`+`符号将两个字符串相连即可。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你也可以在字符串内插过程中使用字符串插入常量、变量、字面量表达成更长的字符串,这样可以很容易的创建自定义的字符串值,进行展示、存储以及打印。 +字符串连接操作只需要简单地通过`+`符号将两个字符串相连即可。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你也可以在字符串内插过程中使用字符串插入常量、变量、字面量表达成更长的字符串,这样可以很容易的创建自定义的字符串值,进行展示、存储以及打印。 + 尽管语法简易,但`String`类型是一种快速、现代化的字符串实现。 -每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式(representations)。你也可以在常量、变量、字面量和表达式中进行字符串插值操作,这可以帮助你轻松创建用于展示、存储和打印的自定义字符串。 +每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式(representations)。 > 注意: > Swift 的`String`类型与 Foundation `NSString`类进行了无缝桥接。就像 [`AnyObject`类型](./19_Type_Casting.html#anyobject) 中提到的一样,在使用 Cocoa 中的 Foundation 框架时,您可以将创建的任何字符串的值转换成`NSString`,并调用任意的`NSString` API。您也可以在任意要求传入`NSString`实例作为参数的 API 中用`String`类型的值代替。 > 更多关于在 Foundation 和 Cocoa 中使用`String`的信息请查看 *[Using Swift with Cocoa and Objective-C (Swift 2.1)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)*。 - ## 字符串字面量(String Literals) @@ -64,7 +76,7 @@ var anotherEmptyString = String() // 初始化方法 // 两个字符串均为空并等价。 ``` -您可以通过检查其`Boolean`类型的`isEmpty`属性来判断该字符串是否为空: +您可以通过检查其`Bool`类型的`isEmpty`属性来判断该字符串是否为空: ```swift if emptyString.isEmpty { @@ -73,6 +85,7 @@ if emptyString.isEmpty { // 打印输出:"Nothing to see here" ``` + ## 字符串可变性 (String Mutability) @@ -91,6 +104,7 @@ constantString += " and another Highlander" > 注意: 在 Objective-C 和 Cocoa 中,您需要通过选择两个不同的类(`NSString`和`NSMutableString`)来指定字符串是否可以被修改。 + ## 字符串是值类型(Strings Are Value Types) @@ -105,6 +119,7 @@ Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字 在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。 + ## 使用字符(Working with Characters) @@ -121,7 +136,7 @@ for character in "Dog!🐶".characters { // 🐶 ``` -`for-in`循环在 [For Loops](./05_Control_Flow.html#for_loops) 中进行了详细描述。 +`for-in`循环在 [For 循环](./05_Control_Flow.html#for_loops) 中进行了详细描述。 另外,通过标明一个`Character`类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量: @@ -137,6 +152,7 @@ print(catString) // 打印输出:"Cat!🐱" ``` + ## 连接字符串和字符 (Concatenating Strings and Characters) @@ -199,6 +215,7 @@ Unicode 是一个国际标准,用于文本的编码和表示。 它使您可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。 Swift 的`String`和`Character`类型是完全兼容 Unicode 标准的。 + ### Unicode 标量(Unicode Scalars) @@ -244,7 +261,7 @@ let sparklingHeart = "\u{1F496}" // 💖, Unicode 标量 U+1F496 ```swift let eAcute: Character = "\u{E9}" // é let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́ -// eAcute 是 é, combinedEAcute 是 é +// eAcute 是 é, combinedEAcute 是 é ``` 可扩展的字符群集是一个灵活的方法,用许多复杂的脚本字符表示单一的`Character`值。 @@ -255,7 +272,7 @@ let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́ ```swift let precomposed: Character = "\u{D55C}" // 한 let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ᄒ, ᅡ, ᆫ -// precomposed 是 한, decomposed 是 한 +// precomposed 是 한, decomposed 是 한 ``` 可拓展的字符群集可以使包围记号(例如`COMBINING ENCLOSING CIRCLE`或者`U+20DD`)的标量包围其他 Unicode 标量,作为一个单一的`Character`值: @@ -265,7 +282,7 @@ let enclosedEAcute: Character = "\u{E9}\u{20DD}" // enclosedEAcute 是 é⃝ ``` -局部的指示符号的 Unicode 标量可以组合成一个单一的`Character`值,例如`REGIONAL INDICATOR SYMBOL LETTER U`(`U+1F1FA`)和`REGIONAL INDICATOR SYMBOL LETTER S`(`U+1F1F8`): +地域性指示符号的 Unicode 标量可以组合成一个单一的`Character`值,例如`REGIONAL INDICATOR SYMBOL LETTER U`(`U+1F1FA`)和`REGIONAL INDICATOR SYMBOL LETTER S`(`U+1F1F8`): ```swift @@ -286,7 +303,7 @@ print("unusualMenagerie has \(unusualMenagerie.characters.count) characters") 注意在 Swift 中,使用可拓展的字符群集作为`Character`值来连接或改变字符串时,并不一定会更改字符串的字符数量。 -例如,如果你用四个字符的单词`cafe`初始化一个新的字符串,然后添加一个`COMBINING ACTUE ACCENT`(`U+0301`)作为字符串的结尾。最终这个字符串的字符数量仍然是`4`,因为第四个字符是`é`,而不是`e`: +例如,如果你用四个字符的单词`cafe`初始化一个新的字符串,然后添加一个`COMBINING ACTUE ACCENT`(`U+0301`)作为字符串的结尾。最终这个字符串的字符数量仍然是`4`,因为第四个字符是`é`,而不是`e`: ```swift var word = "cafe" @@ -308,7 +325,7 @@ print("the number of characters in \(word) is \(word.characters.count)") ## 访问和修改字符串 (Accessing and Modifying a String) -你可以通字符串的属性和方法来访问和读取它,当然也可以用下标语法完成。 +你可以通过字符串的属性和方法来访问和修改它,当然也可以用下标语法完成。 ### 字符串索引 (String Indices) @@ -319,72 +336,70 @@ print("the number of characters in \(word) is \(word.characters.count)") 使用`startIndex`属性可以获取一个`String`的第一个`Character`的索引。使用`endIndex`属性可以获取最后一个`Character`的后一个位置的索引。因此,`endIndex`属性不能作为一个字符串的有效下标。如果`String`是空串,`startIndex`和`endIndex`是相等的。 -通过调用`String.Index`的`predecessor()`方法,可以立即得到前面一个索引,调用`successor()`方法可以立即得到后面一个索引。任何一个`String`的索引都可以通过锁链作用的这些方法来获取另一个索引,也可以调用`advancedBy(_:)`方法来获取。但如果尝试获取出界的字符串索引,就会抛出一个运行时错误。 +通过调用 `String` 的 `index(before:)` 或 `index(after:)` 方法,可以立即得到前面或后面的一个索引。您还可以通过调用 `index(_:offsetBy:)` 方法来获取对应偏移量的索引,这种方式可以避免多次调用 `index(before:)` 或 `index(after:)` 方法。 -你可以使用下标语法来访问`String`特定索引的`Character`。 +你可以使用下标语法来访问 `String` 特定索引的 `Character`。 ```swift let greeting = "Guten Tag!" greeting[greeting.startIndex] // G -greeting[greeting.endIndex.predecessor()] +greeting[greeting.index(before: greeting.endIndex)] // ! -greeting[greeting.startIndex.successor()] +greeting[greeting.index(after: greeting.startIndex)] // u -let index = greeting.startIndex.advancedBy(7) +let index = greeting.index(greeting.startIndex, offsetBy: 7) greeting[index] // a ``` -试图获取越界索引对应的`Character`,将引发一个运行时错误。 +试图获取越界索引对应的 `Character`,将引发一个运行时错误。 ```swift greeting[greeting.endIndex] // error -greeting.endIndex.successor() // error +greeting.index(after: endIndex) // error ``` -使用`characters`属性的`indices`属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。 +使用 `characters` 属性的 `indices` 属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。 ```swift for index in greeting.characters.indices { - print("\(greeting[index]) ", terminator: " ") + print("\(greeting[index]) ", terminator: "") } -// 打印输出 "G u t e n T a g !" +// 打印输出 "G u t e n T a g ! " ``` +> 注意: +> 您可以使用 `startIndex` 和 `endIndex` 属性或者 `index(before:)` 、`index(after:)` 和 `index(_:offsetBy:)` 方法在任意一个确认的并遵循 `Collection` 协议的类型里面,如上文所示是使用在 `String` 中,您也可以使用在 `Array`、`Dictionary` 和 `Set`中。 + ### 插入和删除 (Inserting and Removing) -调用`insert(_:atIndex:)`方法可以在一个字符串的指定索引插入一个字符。 +调用 `insert(_:atIndex:)` 方法可以在一个字符串的指定索引插入一个字符,调用 `insert(contentsOf:at:)` 方法可以在一个字符串的指定索引插入一个段字符串。 ```swift var welcome = "hello" -welcome.insert("!", atIndex: welcome.endIndex) -// welcome now 现在等于 "hello!" +welcome.insert("!", at: welcome.endIndex) +// welcome 变量现在等于 "hello!" + +welcome.insert(contentsOf:" there".characters, at: welcome.index(before: welcome.endIndex)) +// welcome 变量现在等于 "hello there!" ``` -调用`insertContentsOf(_:at:)`方法可以在一个字符串的指定索引插入一个字符串。 +调用 `remove(at:)` 方法可以在一个字符串的指定索引删除一个字符,调用 `removeSubrange(_:)` 方法可以在一个字符串的指定索引删除一个子字符串。 ```swift -welcome.insertContentsOf(" there".characters, at: welcome.endIndex.predecessor()) -// welcome 现在等于 "hello there!" -``` - -调用`removeAtIndex(_:)`方法可以在一个字符串的指定索引删除一个字符。 - -```swift -welcome.removeAtIndex(welcome.endIndex.predecessor()) +welcome.remove(at: welcome.index(before: welcome.endIndex)) // welcome 现在等于 "hello there" -``` - -调用`removeRange(_:)`方法可以在一个字符串的指定索引删除一个子字符串。 - -```swift -let range = welcome.endIndex.advancedBy(-6).. 注意: +> 您可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一个确认的并遵循 `RangeReplaceableCollection` 协议的类型里面,如上文所示是使用在 `String` 中,您也可以使用在 `Array`、`Dictionary` 和 `Set` 中。 + ## 比较字符串 (Comparing Strings) @@ -413,7 +428,7 @@ if quotation == sameQuotation { // "Voulez-vous un café?" 使用 LATIN SMALL LETTER E WITH ACUTE let eAcuteQuestion = "Voulez-vous un caf\u{E9}?" -// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E and COMBINING ACUTE ACCENT +// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E and COMBINING ACUTE ACCENT let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?" if eAcuteQuestion == combinedEAcuteQuestion { @@ -436,7 +451,7 @@ if latinCapitalLetterA != cyrillicCapitalLetterA { ``` > 注意: -> 在 Swift 中,字符串和字符并不区分区域。 +> 在 Swift 中,字符串和字符并不区分地域(not locale-sensitive)。 @@ -468,7 +483,7 @@ let romeoAndJuliet = [ var act1SceneCount = 0 for scene in romeoAndJuliet { if scene.hasPrefix("Act 1 ") { - ++act1SceneCount + act1SceneCount += 1 } } print("There are \(act1SceneCount) scenes in Act 1") @@ -482,9 +497,9 @@ var mansionCount = 0 var cellCount = 0 for scene in romeoAndJuliet { if scene.hasSuffix("Capulet's mansion") { - ++mansionCount + mansionCount += 1 } else if scene.hasSuffix("Friar Lawrence's cell") { - ++cellCount + cellCount += 1 } } print("\(mansionCount) mansion scenes; \(cellCount) cell scenes") @@ -498,7 +513,7 @@ print("\(mansionCount) mansion scenes; \(cellCount) cell scenes") ## 字符串的 Unicode 表示形式(Unicode Representations of Strings) -当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种编码格式编码。每一个字符串中的小块编码都被称为代码单元。这些包括 UTF-8 编码格式(编码字符串为8位的代码单元), UTF-16 编码格式(编码字符串位16位的代码单元),以及 UTF-32 编码格式(编码字符串32位的代码单元)。 +当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种`编码格式`(encoding forms)编码。每一个字符串中的小块编码都被称`代码单元`(code units)。这些包括 UTF-8 编码格式(编码字符串为8位的代码单元), UTF-16 编码格式(编码字符串位16位的代码单元),以及 UTF-32 编码格式(编码字符串32位的代码单元)。 Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式。 您可以利用`for-in`来对字符串进行遍历,从而以 Unicode 可扩展的字符群集的方式访问每一个`Character`值。 @@ -510,13 +525,12 @@ Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式 * UTF-16 代码单元集合 (利用字符串的`utf16`属性进行访问) * 21位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式 (利用字符串的`unicodeScalars`属性进行访问) -下面由`D``o``g``‼`(`DOUBLE EXCLAMATION MARK`, Unicode 标量 `U+203C`)和`�`(`DOG FACE`,Unicode 标量为`U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示: +下面由`D`,`o`,`g`,`‼`(`DOUBLE EXCLAMATION MARK`, Unicode 标量 `U+203C`)和`🐶`(`DOG FACE`,Unicode 标量为`U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示: ```swift let dogString = "Dog‼🐶" ``` - ### UTF-8 表示 @@ -629,7 +643,7 @@ print("") ### Unicode 标量表示 (Unicode Scalars Representation) 您可以通过遍历`String`值的`unicodeScalars`属性来访问它的 Unicode 标量表示。 -其为`UnicodeScalarView`类型的属性,`UnicodeScalarView`是`UnicodeScalar`的集合。 +其为`UnicodeScalarView`类型的属性,`UnicodeScalarView`是`UnicodeScalar`类型的值的集合。 `UnicodeScalar`是21位的 Unicode 代码点。 每一个`UnicodeScalar`拥有一个`value`属性,可以返回对应的21位数值,用`UInt32`来表示: @@ -645,7 +659,7 @@ print("") - + diff --git a/source/chapter2/04_Collection_Types.md b/source/chapter2/04_Collection_Types.md index 280a44a1..68875522 100755 --- a/source/chapter2/04_Collection_Types.md +++ b/source/chapter2/04_Collection_Types.md @@ -1,672 +1,679 @@ -# 集合类型 (Collection Types) ------------------ - -> 1.0 -> 翻译:[zqp](https://github.com/zqp) -> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai), [feiin](https://github.com/feiin) - -> 2.0 -> 翻译+校对:[JackAlan](https://github.com/AlanMelody) - -本页包含内容: - -- [集合的可变性(Mutability of Collections)](#mutability_of_collections) -- [数组(Arrays)](#arrays) -- [集合(Sets)](#sets) -- [字典(Dictionaries)](#dictionaries) - -Swift 语言提供`Arrays`、`Sets`和`Dictionaries`三种基本的集合类型用来存储集合数据。数组是有序数据的集。集合是无序无重复数据的集。字典是无序的键值对的集。 - -![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/CollectionTypes_intro_2x.png) - -Swift 语言中的`Arrays`、`Sets`和`Dictionaries`中存储的数据值类型必须明确。这意味着我们不能把不正确的数据类型插入其中。同时这也说明我们完全可以对取回值的类型非常自信。 - -> 注意: -Swift 的`Arrays`、`Sets`和`Dictionaries`类型被实现为泛型集合。更多关于泛型类型和集合,参见 [泛型](./23_Generics.html)章节。 - - -## 集合的可变性 - -如果创建一个`Arrays`、`Sets`或`Dictionaries`并且把它分配成一个变量,这个集合将会是可变的。这意味着我们可以在创建之后添加更多或移除已存在的数据项来改变这个集合的大小。如果我们把`Arrays`、`Sets`或`Dictionaries`分配成常量,那么它就是不可变的,它的大小不能被改变。 - -> 注意: -在我们不需要改变集合大小的时候创建不可变集合是很好的习惯。如此 Swift 编译器可以优化我们创建的集合。 - - -## 数组(Arrays) - -数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 - -> 注意: - Swift 的`Array`类型被桥接到`Foundation`中的`NSArray`类。 - 更多关于在`Foundation`和`Cocoa`中使用`Array`的信息,参见 [*Using Swift with Cocoa and Obejective-C*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。 - - -### 数组的简单语法 - -写 Swift 数组应该遵循像`Array`这样的形式,其中`Element`是这个数组中唯一允许存在的数据类型。我们也可以使用像`[Element]`这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。 - - -### 创建一个空数组 - -我们可以使用构造语法来创建一个由特定数据类型构成的空数组: - -```swift -var someInts = [Int]() -print("someInts is of type [Int] with \(someInts.count) items.") -// 打印 "someInts is of type [Int] with 0 items." -``` - -注意,通过构造函数的类型,`someInts`的值类型被推断为`[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 被推断为 [Double],等价于 [2.5, 2.5, 2.5] - -var sixDoubles = threeDoubles + anotherThreeDoubles -// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5] -``` - - -### 用字面量构造数组 - -我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值: - -`[value 1, value 2, value 3]`。 - -下面这个例子创建了一个叫做`shoppingList`并且存储`String`的数组: - -```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 -print("The shopping list contains \(shoppingList.count) items.") -// 输出 "The shopping list contains 2 items."(这个数组有2个项) -``` - -使用布尔值属性`isEmpty`作为检查`count`属性的值是否为 0 的捷径: - -```swift -if shoppingList.isEmpty { - print("The shopping list is empty.") -} else { - print("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 现在有四项了 -shoppingList += ["Chocolate Spread", "Cheese", "Butter"] -// shoppingList 现在有七项了 -``` - -可以直接使用下标语法来获取数组中的数据项,把我们需要的数据项的索引值放在直接放在数组名称的方括号中: - -```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 现在有6项 -``` - -> 注意: -不可以用下标访问的形式去在数组尾部添加新项。 - - -调用数组的`insert(_:atIndex:)`方法来在某个具体索引值之前添加数据项: - -```swift -shoppingList.insert("Maple Syrup", atIndex: 0) -// shoppingList 现在有7项 -// "Maple Syrup" 现在是这个列表中的第一项 -``` - -这次`insert(_:atIndex:)`方法调用把值为`"Maple Syrup"`的新数据项插入列表的最开始位置,并且使用`0`作为索引值。 - -类似的我们可以使用`removeAtIndex(_:)`方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它): - -```swift -let mapleSyrup = shoppingList.removeAtIndex(0) -// 索引值为0的数据项被移除 -// shoppingList 现在只有6项,而且不包括 Maple Syrup -// mapleSyrup 常量的值等于被移除数据项的值 "Maple Syrup" -``` -> 注意: -如果我们试着对索引越界的数据进行检索或者设置新值的操作,会引发一个运行期错误。我们可以使用索引值和数组的`count`属性进行比较来在使用某个索引之前先检验是否有效。除了当`count`等于 0 时(说明这是个空数组),最大索引值一直是`count - 1`,因为数组都是零起索引。 - -数据项被移除后数组中的空出项会被自动填补,所以现在索引值为`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 { - print(item) -} -// Six eggs -// Milk -// Flour -// Baking Powder -// Bananas -``` - -如果我们同时需要每个数据项的值和索引值,可以使用`enumerate()`方法来进行数组遍历。`enumerate()`返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历: - -```swift -for (index, value) in shoppingList.enumerate() { - print("Item \(String(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)。 - - -## 集合 - -集合(Set)用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。 - -> 注意: -> Swift的`Set`类型被桥接到`Fundation`中的`NSSet`类。 -> 关于使用`Fundation`和`Cocoa`中`Set`的知识,请看 [*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)。 - - -#### 集合类型的哈希值 - -一个类型为了存储在集合中,该类型必须是可哈希化的--也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是`Int`类型的,相等的对象哈希值必须相同,比如`a==b`,因此必须`a.hashValue == b.hashValue`。 - -Swift 的所有基本类型(比如`String`,`Int`,`Double`和`Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.html)有讲述)默认也是可哈希化的。 - -> 注意: -> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型符合 Swift 标准库中的`Hashable`协议。符合`Hashable`协议的类型需要提供一个类型为`Int`的可读属性`hashValue`。由类型的`hashValue`属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。 - -> 因为`Hashable`协议符合`Equatable`协议,所以符合该协议的类型也必须提供一个"是否相等"运算符(`==`)的实现。这个`Equatable`协议要求任何符合`==`实现的实例间都是一种相等的关系。也就是说,对于`a,b,c`三个值来说,`==`的实现必须满足下面三种情况: - -> * `a == a`(自反性) -> * `a == b`意味着`b == a`(对称性) -> * `a == b && b == c`意味着`a == c`(传递性) - -关于符合协议的更多信息,请看[协议](./22_Protocols.html)。 - - -### 集合类型语法 - -Swift 中的`Set`类型被写为`Set`,这里的`Element`表示`Set`中允许存储的类型,和数组不同的是,集合没有等价的简化形式。 - - -### 创建和构造一个空的集合 - -你可以通过构造器语法创建一个特定类型的空集合: - -```swift -var letters = Set() -print("letters is of type Set with \(letters.count) items.") -// 打印 "letters is of type Set with 0 items." -``` - -> 注意: -> 通过构造器,这里的`letters`变量的类型被推断为`Set`。 - -此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,我们可以通过一个空的数组字面量创建一个空的`Set`: - -```swift -letters.insert("a") -// letters 现在含有1个 Character 类型的值 -letters = [] -// letters 现在是一个空的 Set, 但是它依然是 Set 类型 -``` - - -### 用数组字面量创建集合 - -你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。 - -下面的例子创建一个称之为`favoriteGenres`的集合来存储`String`类型的值: - -```swift -var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] -// favoriteGenres 被构造成含有三个初始值的集合 -``` - -这个`favoriteGenres`变量被声明为“一个`String`值的集合”,写为`Set`。由于这个特定的集合含有指定`String`类型的值,所以它只允许存储`String`类型值。这里的`favoriteGenres`变量有三个`String`类型的初始值(`"Rock"`,`"Classical"`和`"Hip hop"`),并以数组字面量的方式出现。 - -> 注意: -> `favoriteGenres`被声明为一个变量(拥有`var`标示符)而不是一个常量(拥有`let`标示符),因为它里面的元素将会在下面的例子中被增加或者移除。 - -一个`Set`类型不能从数组字面量中被单独推断出来,因此`Set`类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个`Set`并且该数组字面量中的所有元素类型相同,那么你无须写出`Set`的具体类型。`favoriteGenres`的构造形式可以采用简化的方式代替: - -```swift -var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] -``` - -由于数组字面量中的所有元素类型相同,Swift 可以推断出`Set`作为`favoriteGenres`变量的正确类型。 - - -### 访问和修改一个集合 - -你可以通过`Set`的属性和方法来访问和修改一个`Set`。 - -为了找出一个`Set`中元素的数量,可以使用其只读属性`count`: - -```swift -print("I have \(favoriteGenres.count) favorite music genres.") -// 打印 "I have 3 favorite music genres." -``` - -使用布尔属性`isEmpty`作为一个缩写形式去检查`count`属性是否为`0`: - -```swift -if favoriteGenres.isEmpty { - print("As far as music goes, I'm not picky.") -} else { - print("I have particular music preferences.") -} -// 打印 "I have particular music preferences." -``` - -你可以通过调用`Set`的`insert(_:)`方法来添加一个新元素: - -```swift -favoriteGenres.insert("Jazz") -// favoriteGenres 现在包含4个元素 -``` - -你可以通过调用`Set`的`remove(_:)`方法去删除一个元素,如果该值是该`Set`的一个元素则删除该元素并且返回被删除的元素值,否则如果该`Set`不包含该值,则返回`nil`。另外,`Set`中的所有元素可以通过它的`removeAll()`方法删除。 - -```swift -if let removedGenre = favoriteGenres.remove("Rock") { - print("\(removedGenre)? I'm over it.") -} else { - print("I never much cared for that.") -} -// 打印 "Rock? I'm over it." -``` - -使用`contains(_:)`方法去检查`Set`中是否包含一个特定的值: - -```swift -if favoriteGenres.contains("Funk") { - print("I get up on the good foot.") -} else { - print("It's too funky in here.") -} -// 打印 "It's too funky in here." -``` - - -### 遍历一个集合 - -你可以在一个`for-in`循环中遍历一个`Set`中的所有值。 - -```swift -for genre in favoriteGenres { - print("\(genre)") -} -// Classical -// Jazz -// Hip hop -``` - -更多关于`for-in`循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。 - -Swift 的`Set`类型没有确定的顺序,为了按照特定顺序来遍历一个`Set`中的值可以使用`sort()`方法,它将根据提供的序列返回一个有序集合. - -```swift -for genre in favoriteGenres.sort() { - print("\(genre)") -} -// prints "Classical" -// prints "Hip hop" -// prints "Jazz -``` - - -### 完成集合操作 - -你可以高效地完成`Set`的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。 - - -#### 基本集合操作 - -下面的插图描述了两个集合-`a`和`b`-以及通过阴影部分的区域显示集合各种操作的结果。 - -![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setVennDiagram_2x.png) - -* 使用`intersect(_:)`方法根据两个集合中都包含的值创建的一个新的集合。 -* 使用`exclusiveOr(_:)`方法根据在一个集合中但不在两个集合中的值创建一个新的集合。 -* 使用`union(_:)`方法根据两个集合的值创建一个新的集合。 -* 使用`subtract(_:)`方法根据不在该集合中的值创建一个新的集合。 - -```swift -let oddDigits: Set = [1, 3, 5, 7, 9] -let evenDigits: Set = [0, 2, 4, 6, 8] -let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] - -oddDigits.union(evenDigits).sort() -// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -oddDigits.intersect(evenDigits).sort() -// [] -oddDigits.subtract(singleDigitPrimeNumbers).sort() -// [1, 9] -oddDigits.exclusiveOr(singleDigitPrimeNumbers).sort() -// [1, 2, 9] -``` - - -#### 集合成员关系和相等 - -下面的插图描述了三个集合-`a`,`b`和`c`,以及通过重叠区域表述集合间共享的元素。集合`a`是集合`b`的父集合,因为`a`包含了`b`中所有的元素,相反的,集合`b`是集合`a`的子集合,因为属于`b`的元素也被`a`包含。集合`b`和集合`c`彼此不关联,因为它们之间没有共同的元素。 - -![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setEulerDiagram_2x.png) - -* 使用“是否相等”运算符(`==`)来判断两个集合是否包含全部相同的值。 -* 使用`isSubsetOf(_:)`方法来判断一个集合中的值是否也被包含在另外一个集合中。 -* 使用`isSupersetOf(_:)`方法来判断一个集合中包含另一个集合中所有的值。 -* 使用`isStrictSubsetOf(_:)`或者`isStrictSupersetOf(_:)`方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。 -* 使用`isDisjointWith(_:)`方法来判断两个集合是否不含有相同的值。 - -```swift -let houseAnimals: Set = ["🐶", "🐱"] -let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] -let cityAnimals: Set = ["🐦", "🐭"] - -houseAnimals.isSubsetOf(farmAnimals) -// true -farmAnimals.isSupersetOf(houseAnimals) -// true -farmAnimals.isDisjointWith(cityAnimals) -// true -``` - - -## 字典 - -字典是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 - -> 注意: -> Swift 的`Dictionary`类型被桥接到`Foundation`的`NSDictionary`类。 -> 更多关于在`Foundation`和`Cocoa`中使用`Dictionary`类型的信息,参见 [*Using Swift with Cocoa and Obejective-C*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。 - - -## 字典类型快捷语法 - -Swift 的字典使用`Dictionary`定义,其中`Key`是字典中键的数据类型,`Value`是字典中对应于这些键所存储值的数据类型。 - -> 注意: -> 一个字典的`Key`类型必须遵循`Hashable`协议,就像`Set`的值类型。 - -我们也可以用`[Key: Value]`这样快捷的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。 - - -### 创建一个空字典 - -我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典: - -```swift -var namesOfIntegers = [Int: String]() -// namesOfIntegers 是一个空的 [Int: String] 字典 -``` - -这个例子创建了一个`[Int: String]`类型的空字典来储存整数的英语命名。它的键是`Int`型,值是`String`型。 - -如果上下文已经提供了类型信息,我们可以使用空字典字面量来创建一个空字典,记作`[:]`(中括号中放一个冒号): - -```swift -namesOfIntegers[16] = "sixteen" -// namesOfIntegers 现在包含一个键值对 -namesOfIntegers = [:] -// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典 -``` - - -## 用字典字面量创建字典 - -我们可以使用字典字面量来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作`Dictionary`集合的快捷途径。 - -一个键值对是一个`key`和一个`value`的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含、由逗号分割: - -```swift -[key 1: value 1, key 2: value 2, key 3: value 3] -``` - -下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称: - -```swift -var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] -``` - -`airports`字典被声明为一种`[String: String]`类型,这意味着这个字典的键和值都是`String`类型。 - -> 注意: -> `airports`字典被声明为变量(用`var`关键字)而不是常量(`let`关键字)因为后来更多的机场信息会被添加到这个示例字典中。 - -`airports`字典使用字典字面量初始化,包含两个键值对。第一对的键是`YYZ`,值是`Toronto Pearson`。第二对的键是`DUB`,值是`Dublin`。 - -这个字典语句包含了两个`String: String`类型的键值对。它们对应`airports`变量声明的类型(一个只有`String`键和`String`值的字典)所以这个字典字面量的任务是构造拥有两个初始数据项的`airport`字典。 - -和数组一样,我们在用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。 -`airports`字典也可以用这种简短方式定义: - -```swift -var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] -``` - -因为这个语句中所有的键和值都各自拥有相同的数据类型,Swift 可以推断出`Dictionary`是`airports`字典的正确类型。 - - -### 访问和修改字典 - -我们可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。 - -和数组一样,我们可以通过字典的只读属性`count`来获取某个字典的数据项数量: - -```swift -print("The dictionary of airports contains \(airports.count) items.") -// 打印 "The dictionary of airports contains 2 items."(这个字典有两个数据项) -``` - -使用布尔属性`isEmpty`来快捷地检查字典的`count`属性是否等于0: - -```swift -if airports.isEmpty { - print("The airports dictionary is empty.") -} else { - print("The airports dictionary is not empty.") -} -// 打印 "The airports dictionary is not empty." -``` - -我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值: - -```swift -airports["LHR"] = "London" -// airports 字典现在有三个数据项 -``` - -我们也可以使用下标语法来改变特定键对应的值: - -```swift -airports["LHR"] = "London Heathrow" -// "LHR"对应的值 被改为 "London Heathrow -``` - -作为另一种下标方法,字典的`updateValue(_:forKey:)`方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,`updateValue(_:forKey:)`方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和上面的下标方法不同的,`updateValue(_:forKey:)`这个方法返回更新值之前的原值。这样使得我们可以检查更新是否成功。 - -`updateValue(_:forKey:)`方法会返回对应值的类型的可选值。举例来说:对于存储`String`值的字典,这个函数会返回一个`String?`或者“可选 `String`”类型的值。 - -如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是`nil`。 - -```swift -if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") { - print("The old value for DUB was \(oldValue).") -} -// 输出 "The old value for DUB was Dublin." -``` - -我们也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回对应值的类型的可选值。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选值,否则将返回`nil`: - -```swift -if let airportName = airports["DUB"] { - print("The name of the airport is \(airportName).") -} else { - print("That airport is not in the airports dictionary.") -} -// 打印 "The name of the airport is Dublin Airport." -``` - -我们还可以使用下标语法来通过给某个键的对应值赋值为`nil`来从字典里移除一个键值对: - -```swift -airports["APL"] = "Apple Internation" -// "Apple Internation" 不是真的 APL 机场, 删除它 -airports["APL"] = nil -// APL 现在被移除了 -``` - -此外,`removeValueForKey(_:)`方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回`nil`: - -```swift -if let removedValue = airports.removeValueForKey("DUB") { - print("The removed airport's name is \(removedValue).") -} else { - print("The airports dictionary does not contain a value for DUB.") -} -// prints "The removed airport's name is Dublin Airport." -``` - - -### 字典遍历 - -我们可以使用`for-in`循环来遍历某个字典中的键值对。每一个字典中的数据项都以`(key, value)`元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组: - -```swift -for (airportCode, airportName) in airports { - print("\(airportCode): \(airportName)") -} -// YYZ: Toronto Pearson -// LHR: London Heathrow -``` - -更多关于`for-in`循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。 - -通过访问`keys`或者`values`属性,我们也可以遍历字典的键或者值: - -```swift -for airportCode in airports.keys { - print("Airport code: \(airportCode)") -} -// Airport code: YYZ -// Airport code: LHR - -for airportName in airports.values { - print("Airport name: \(airportName)") -} -// Airport name: Toronto Pearson -// Airport name: London Heathrow -``` - -如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受`Array`实例的 API 的参数,可以直接使用`keys`或者`values`属性构造一个新数组: - -```swift -let airportCodes = [String](airports.keys) -// airportCodes 是 ["YYZ", "LHR"] - -let airportNames = [String](airports.values) -// airportNames 是 ["Toronto Pearson", "London Heathrow"] -``` - -Swift 的字典类型是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的`keys`或`values`属性使用`sort()`方法。 +# 集合类型 (Collection Types) +----------------- + +> 1.0 +> 翻译:[zqp](https://github.com/zqp) +> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai), [feiin](https://github.com/feiin) + +> 2.0 +> 翻译+校对:[JackAlan](https://github.com/AlanMelody) + +> 2.1 +> 校对:[shanks](http://codebuild.me) + +> 2.2 +> 校对:[SketchK](https://github.com/SketchK) 2016-05-11 + + +本页包含内容: + +- [集合的可变性(Mutability of Collections)](#mutability_of_collections) +- [数组(Arrays)](#arrays) +- [集合(Sets)](#sets) +- [字典(Dictionaries)](#dictionaries) + +Swift 语言提供`Arrays`、`Sets`和`Dictionaries`三种基本的集合类型用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。 + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/CollectionTypes_intro_2x.png) + +Swift 语言中的`Arrays`、`Sets`和`Dictionaries`中存储的数据值类型必须明确。这意味着我们不能把不正确的数据类型插入其中。同时这也说明我们完全可以对取回值的类型非常自信。 + +> 注意: +Swift 的`Arrays`、`Sets`和`Dictionaries`类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./23_Generics.html)章节。 + + +## 集合的可变性 + +如果创建一个`Arrays`、`Sets`或`Dictionaries`并且把它分配成一个变量,这个集合将会是*可变的*。这意味着我们可以在创建之后添加更多或移除已存在的数据项,或者改变集合中的数据项。如果我们把`Arrays`、`Sets`或`Dictionaries`分配成常量,那么它就是*不可变的*,它的大小和内容都不能被改变。 + +> 注意: +在我们不需要改变集合的时候创建不可变集合是很好的实践。如此 Swift 编译器可以优化我们创建的集合。 + + +## 数组(Arrays) + +数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 + +> 注意: + Swift 的`Array`类型被桥接到`Foundation`中的`NSArray`类。 + 更多关于在`Foundation`和`Cocoa`中使用`Array`的信息,参见 [*Using Swift with Cocoa and Obejective-C*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。 + + +### 数组的简单语法 + +写 Swift 数组应该遵循像`Array`这样的形式,其中`Element`是这个数组中唯一允许存在的数据类型。我们也可以使用像`[Element]`这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。 + + +### 创建一个空数组 + +我们可以使用构造语法来创建一个由特定数据类型构成的空数组: + +```swift +var someInts = [Int]() +print("someInts is of type [Int] with \(someInts.count) items.") +// 打印 "someInts is of type [Int] with 0 items." +``` + +注意,通过构造函数的类型,`someInts`的值类型被推断为`[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 = [Double](count: 3, repeatedValue: 2.5) +// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5] + +var sixDoubles = threeDoubles + anotherThreeDoubles +// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5] +``` + + +### 用字面量构造数组 + +我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值: + +`[value 1, value 2, value 3]`。 + +下面这个例子创建了一个叫做`shoppingList`并且存储`String`的数组: + +```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 +print("The shopping list contains \(shoppingList.count) items.") +// 输出 "The shopping list contains 2 items."(这个数组有2个项) +``` + +使用布尔值属性`isEmpty`作为检查`count`属性的值是否为 0 的捷径: + +```swift +if shoppingList.isEmpty { + print("The shopping list is empty.") +} else { + print("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 现在有四项了 +shoppingList += ["Chocolate Spread", "Cheese", "Butter"] +// shoppingList 现在有七项了 +``` + +可以直接使用下标语法来获取数组中的数据项,把我们需要的数据项的索引值放在直接放在数组名称的方括号中: + +```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 现在有6项 +``` + +> 注意: +不可以用下标访问的形式去在数组尾部添加新项。 + + +调用数组的`insert(_:atIndex:)`方法来在某个具体索引值之前添加数据项: + +```swift +shoppingList.insert("Maple Syrup", atIndex: 0) +// shoppingList 现在有7项 +// "Maple Syrup" 现在是这个列表中的第一项 +``` + +这次`insert(_:atIndex:)`方法调用把值为`"Maple Syrup"`的新数据项插入列表的最开始位置,并且使用`0`作为索引值。 + +类似的我们可以使用`removeAtIndex(_:)`方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它): + +```swift +let mapleSyrup = shoppingList.removeAtIndex(0) +// 索引值为0的数据项被移除 +// shoppingList 现在只有6项,而且不包括 Maple Syrup +// mapleSyrup 常量的值等于被移除数据项的值 "Maple Syrup" +``` +> 注意: +如果我们试着对索引越界的数据进行检索或者设置新值的操作,会引发一个运行期错误。我们可以使用索引值和数组的`count`属性进行比较来在使用某个索引之前先检验是否有效。除了当`count`等于 0 时(说明这是个空数组),最大索引值一直是`count - 1`,因为数组都是零起索引。 + +数据项被移除后数组中的空出项会被自动填补,所以现在索引值为`0`的数据项的值再次等于`"Six eggs"`: + +```swift +firstItem = shoppingList[0] +// firstItem 现在等于 "Six eggs" +``` + +如果我们只想把数组中的最后一项移除,可以使用`removeLast()`方法而不是`removeAtIndex(_:)`方法来避免我们需要获取数组的`count`属性。就像后者一样,前者也会返回被移除的数据项: + +```swift +let apples = shoppingList.removeLast() +// 数组的最后一项被移除了 +// shoppingList 现在只有5项,不包括 Apples +// apples 常量的值现在等于 "Apples" 字符串 +``` + + +### 数组的遍历 + +我们可以使用`for-in`循环来遍历所有数组中的数据项: + +```swift +for item in shoppingList { + print(item) +} +// Six eggs +// Milk +// Flour +// Baking Powder +// Bananas +``` + +如果我们同时需要每个数据项的值和索引值,可以使用`enumerate()`方法来进行数组遍历。`enumerate()`返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历: + +```swift +for (index, value) in shoppingList.enumerate() { + print("Item \(String(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)。 + + +## 集合(Sets) + +*集合(Set)*用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。 + +> 注意: +> Swift的`Set`类型被桥接到`Foundation`中的`NSSet`类。 +> 关于使用`Foundation`和`Cocoa`中`Set`的知识,请看 [*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)。 + + +#### 集合类型的哈希值 + +一个类型为了存储在集合中,该类型必须是可哈希化的--也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是`Int`类型的,相等的对象哈希值必须相同,比如`a==b`,因此必须`a.hashValue == b.hashValue`。 + +Swift 的所有基本类型(比如`String`,`Int`,`Double`和`Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.html)有讲述)默认也是可哈希化的。 + +> 注意: +> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型符合 Swift 标准库中的`Hashable`协议。符合`Hashable`协议的类型需要提供一个类型为`Int`的可读属性`hashValue`。由类型的`hashValue`属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。 + +> 因为`Hashable`协议符合`Equatable`协议,所以符合该协议的类型也必须提供一个"是否相等"运算符(`==`)的实现。这个`Equatable`协议要求任何符合`==`实现的实例间都是一种相等的关系。也就是说,对于`a,b,c`三个值来说,`==`的实现必须满足下面三种情况: + +> * `a == a`(自反性) +> * `a == b`意味着`b == a`(对称性) +> * `a == b && b == c`意味着`a == c`(传递性) + +关于符合协议的更多信息,请看[协议](./22_Protocols.html)。 + + +### 集合类型语法 + +Swift 中的`Set`类型被写为`Set`,这里的`Element`表示`Set`中允许存储的类型,和数组不同的是,集合没有等价的简化形式。 + + +### 创建和构造一个空的集合 + +你可以通过构造器语法创建一个特定类型的空集合: + +```swift +var letters = Set() +print("letters is of type Set with \(letters.count) items.") +// 打印 "letters is of type Set with 0 items." +``` + +> 注意: +> 通过构造器,这里的`letters`变量的类型被推断为`Set`。 + +此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,我们可以通过一个空的数组字面量创建一个空的`Set`: + +```swift +letters.insert("a") +// letters 现在含有1个 Character 类型的值 +letters = [] +// letters 现在是一个空的 Set, 但是它依然是 Set 类型 +``` + + +### 用数组字面量创建集合 + +你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。 + +下面的例子创建一个称之为`favoriteGenres`的集合来存储`String`类型的值: + +```swift +var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] +// favoriteGenres 被构造成含有三个初始值的集合 +``` + +这个`favoriteGenres`变量被声明为“一个`String`值的集合”,写为`Set`。由于这个特定的集合含有指定`String`类型的值,所以它只允许存储`String`类型值。这里的`favoriteGenres`变量有三个`String`类型的初始值(`"Rock"`,`"Classical"`和`"Hip hop"`),并以数组字面量的方式出现。 + +> 注意: +> `favoriteGenres`被声明为一个变量(拥有`var`标示符)而不是一个常量(拥有`let`标示符),因为它里面的元素将会在下面的例子中被增加或者移除。 + +一个`Set`类型不能从数组字面量中被单独推断出来,因此`Set`类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个`Set`并且该数组字面量中的所有元素类型相同,那么你无须写出`Set`的具体类型。`favoriteGenres`的构造形式可以采用简化的方式代替: + +```swift +var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] +``` + +由于数组字面量中的所有元素类型相同,Swift 可以推断出`Set`作为`favoriteGenres`变量的正确类型。 + + +### 访问和修改一个集合 + +你可以通过`Set`的属性和方法来访问和修改一个`Set`。 + +为了找出一个`Set`中元素的数量,可以使用其只读属性`count`: + +```swift +print("I have \(favoriteGenres.count) favorite music genres.") +// 打印 "I have 3 favorite music genres." +``` + +使用布尔属性`isEmpty`作为一个缩写形式去检查`count`属性是否为`0`: + +```swift +if favoriteGenres.isEmpty { + print("As far as music goes, I'm not picky.") +} else { + print("I have particular music preferences.") +} +// 打印 "I have particular music preferences." +``` + +你可以通过调用`Set`的`insert(_:)`方法来添加一个新元素: + +```swift +favoriteGenres.insert("Jazz") +// favoriteGenres 现在包含4个元素 +``` + +你可以通过调用`Set`的`remove(_:)`方法去删除一个元素,如果该值是该`Set`的一个元素则删除该元素并且返回被删除的元素值,否则如果该`Set`不包含该值,则返回`nil`。另外,`Set`中的所有元素可以通过它的`removeAll()`方法删除。 + +```swift +if let removedGenre = favoriteGenres.remove("Rock") { + print("\(removedGenre)? I'm over it.") +} else { + print("I never much cared for that.") +} +// 打印 "Rock? I'm over it." +``` + +使用`contains(_:)`方法去检查`Set`中是否包含一个特定的值: + +```swift +if favoriteGenres.contains("Funk") { + print("I get up on the good foot.") +} else { + print("It's too funky in here.") +} +// 打印 "It's too funky in here." +``` + + +### 遍历一个集合 + +你可以在一个`for-in`循环中遍历一个`Set`中的所有值。 + +```swift +for genre in favoriteGenres { + print("\(genre)") +} +// Classical +// Jazz +// Hip hop +``` + +更多关于`for-in`循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。 + +Swift 的`Set`类型没有确定的顺序,为了按照特定顺序来遍历一个`Set`中的值可以使用`sort()`方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符'<'对元素进行比较的结果来确定. + +```swift +for genre in favoriteGenres.sort() { + print("\(genre)") +} +// prints "Classical" +// prints "Hip hop" +// prints "Jazz +``` + + +### 集合操作 + +你可以高效地完成`Set`的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。 + + +#### 基本集合操作 + +下面的插图描述了两个集合-`a`和`b`-以及通过阴影部分的区域显示集合各种操作的结果。 + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setVennDiagram_2x.png) + +* 使用`intersect(_:)`方法根据两个集合中都包含的值创建的一个新的集合。 +* 使用`exclusiveOr(_:)`方法根据在一个集合中但不在两个集合中的值创建一个新的集合。 +* 使用`union(_:)`方法根据两个集合的值创建一个新的集合。 +* 使用`subtract(_:)`方法根据不在该集合中的值创建一个新的集合。 + +```swift +let oddDigits: Set = [1, 3, 5, 7, 9] +let evenDigits: Set = [0, 2, 4, 6, 8] +let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] + +oddDigits.union(evenDigits).sort() +// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +oddDigits.intersect(evenDigits).sort() +// [] +oddDigits.subtract(singleDigitPrimeNumbers).sort() +// [1, 9] +oddDigits.exclusiveOr(singleDigitPrimeNumbers).sort() +// [1, 2, 9] +``` + + +#### 集合成员关系和相等 + +下面的插图描述了三个集合-`a`,`b`和`c`,以及通过重叠区域表述集合间共享的元素。集合`a`是集合`b`的父集合,因为`a`包含了`b`中所有的元素,相反的,集合`b`是集合`a`的子集合,因为属于`b`的元素也被`a`包含。集合`b`和集合`c`彼此不关联,因为它们之间没有共同的元素。 + +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setEulerDiagram_2x.png) + +* 使用“是否相等”运算符(`==`)来判断两个集合是否包含全部相同的值。 +* 使用`isSubsetOf(_:)`方法来判断一个集合中的值是否也被包含在另外一个集合中。 +* 使用`isSupersetOf(_:)`方法来判断一个集合中包含另一个集合中所有的值。 +* 使用`isStrictSubsetOf(_:)`或者`isStrictSupersetOf(_:)`方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。 +* 使用`isDisjointWith(_:)`方法来判断两个集合是否不含有相同的值(是否没有交集)。 + +```swift +let houseAnimals: Set = ["🐶", "🐱"] +let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] +let cityAnimals: Set = ["🐦", "🐭"] + +houseAnimals.isSubsetOf(farmAnimals) +// true +farmAnimals.isSupersetOf(houseAnimals) +// true +farmAnimals.isDisjointWith(cityAnimals) +// true +``` + + +## 字典 + +*字典*是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。 + +> 注意: +> Swift 的`Dictionary`类型被桥接到`Foundation`的`NSDictionary`类。 +> 更多关于在`Foundation`和`Cocoa`中使用`Dictionary`类型的信息,参见 [*Using Swift with Cocoa and Objective-C (Swift 2.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。 + + +## 字典类型快捷语法 + +Swift 的字典使用`Dictionary`定义,其中`Key`是字典中键的数据类型,`Value`是字典中对应于这些键所存储值的数据类型。 + +> 注意: +> 一个字典的`Key`类型必须遵循`Hashable`协议,就像`Set`的值类型。 + +我们也可以用`[Key: Value]`这样快捷的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。 + + +### 创建一个空字典 + +我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典: + +```swift +var namesOfIntegers = [Int: String]() +// namesOfIntegers 是一个空的 [Int: String] 字典 +``` + +这个例子创建了一个`[Int: String]`类型的空字典来储存整数的英语命名。它的键是`Int`型,值是`String`型。 + +如果上下文已经提供了类型信息,我们可以使用空字典字面量来创建一个空字典,记作`[:]`(中括号中放一个冒号): + +```swift +namesOfIntegers[16] = "sixteen" +// namesOfIntegers 现在包含一个键值对 +namesOfIntegers = [:] +// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典 +``` + + +## 用字典字面量创建字典 + +我们可以使用字典字面量来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作`Dictionary`集合的快捷途径。 + +一个键值对是一个`key`和一个`value`的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含、由逗号分割: + +```swift +[key 1: value 1, key 2: value 2, key 3: value 3] +``` + +下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称: + +```swift +var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] +``` + +`airports`字典被声明为一种`[String: String]`类型,这意味着这个字典的键和值都是`String`类型。 + +> 注意: +> `airports`字典被声明为变量(用`var`关键字)而不是常量(`let`关键字)因为后来更多的机场信息会被添加到这个示例字典中。 + +`airports`字典使用字典字面量初始化,包含两个键值对。第一对的键是`YYZ`,值是`Toronto Pearson`。第二对的键是`DUB`,值是`Dublin`。 + +这个字典语句包含了两个`String: String`类型的键值对。它们对应`airports`变量声明的类型(一个只有`String`键和`String`值的字典)所以这个字典字面量的任务是构造拥有两个初始数据项的`airport`字典。 + +和数组一样,我们在用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。 +`airports`字典也可以用这种简短方式定义: + +```swift +var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] +``` + +因为这个语句中所有的键和值都各自拥有相同的数据类型,Swift 可以推断出`Dictionary`是`airports`字典的正确类型。 + + +### 访问和修改字典 + +我们可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。 + +和数组一样,我们可以通过字典的只读属性`count`来获取某个字典的数据项数量: + +```swift +print("The dictionary of airports contains \(airports.count) items.") +// 打印 "The dictionary of airports contains 2 items."(这个字典有两个数据项) +``` + +使用布尔属性`isEmpty`来快捷地检查字典的`count`属性是否等于0: + +```swift +if airports.isEmpty { + print("The airports dictionary is empty.") +} else { + print("The airports dictionary is not empty.") +} +// 打印 "The airports dictionary is not empty." +``` + +我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值: + +```swift +airports["LHR"] = "London" +// airports 字典现在有三个数据项 +``` + +我们也可以使用下标语法来改变特定键对应的值: + +```swift +airports["LHR"] = "London Heathrow" +// "LHR"对应的值 被改为 "London Heathrow +``` + +作为另一种下标方法,字典的`updateValue(_:forKey:)`方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,`updateValue(_:forKey:)`方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和上面的下标方法不同的,`updateValue(_:forKey:)`这个方法返回更新值之前的原值。这样使得我们可以检查更新是否成功。 + +`updateValue(_:forKey:)`方法会返回对应值的类型的可选值。举例来说:对于存储`String`值的字典,这个函数会返回一个`String?`或者“可选 `String`”类型的值。 + +如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是`nil`。 + +```swift +if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") { + print("The old value for DUB was \(oldValue).") +} +// 输出 "The old value for DUB was Dublin." +``` + +我们也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回对应值的类型的可选值。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选值,否则将返回`nil`: + +```swift +if let airportName = airports["DUB"] { + print("The name of the airport is \(airportName).") +} else { + print("That airport is not in the airports dictionary.") +} +// 打印 "The name of the airport is Dublin Airport." +``` + +我们还可以使用下标语法来通过给某个键的对应值赋值为`nil`来从字典里移除一个键值对: + +```swift +airports["APL"] = "Apple Internation" +// "Apple Internation" 不是真的 APL 机场, 删除它 +airports["APL"] = nil +// APL 现在被移除了 +``` + +此外,`removeValueForKey(_:)`方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回`nil`: + +```swift +if let removedValue = airports.removeValueForKey("DUB") { + print("The removed airport's name is \(removedValue).") +} else { + print("The airports dictionary does not contain a value for DUB.") +} +// prints "The removed airport's name is Dublin Airport." +``` + + +### 字典遍历 + +我们可以使用`for-in`循环来遍历某个字典中的键值对。每一个字典中的数据项都以`(key, value)`元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组: + +```swift +for (airportCode, airportName) in airports { + print("\(airportCode): \(airportName)") +} +// YYZ: Toronto Pearson +// LHR: London Heathrow +``` + +更多关于`for-in`循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。 + +通过访问`keys`或者`values`属性,我们也可以遍历字典的键或者值: + +```swift +for airportCode in airports.keys { + print("Airport code: \(airportCode)") +} +// Airport code: YYZ +// Airport code: LHR + +for airportName in airports.values { + print("Airport name: \(airportName)") +} +// Airport name: Toronto Pearson +// Airport name: London Heathrow +``` + +如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受`Array`实例的 API 的参数,可以直接使用`keys`或者`values`属性构造一个新数组: + +```swift +let airportCodes = [String](airports.keys) +// airportCodes 是 ["YYZ", "LHR"] + +let airportNames = [String](airports.values) +// airportNames 是 ["Toronto Pearson", "London Heathrow"] +``` + +Swift 的字典类型是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的`keys`或`values`属性使用`sort()`方法。 diff --git a/source/chapter2/05_Control_Flow.md b/source/chapter2/05_Control_Flow.md index b1502fa7..665d366a 100755 --- a/source/chapter2/05_Control_Flow.md +++ b/source/chapter2/05_Control_Flow.md @@ -5,36 +5,40 @@ > 翻译:[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) -> 2.0,2.1 +> 2.0 > 翻译+校对:[JackAlan](https://github.com/AlanMelody) -> 定稿:[shanksyang](http//codebuild.me) + +> 2.1 +> 翻译:[Prayer](https://github.com/futantan) +> 校对:[shanks](http://codebuild.me) + +> 2.2 +> 翻译:[LinusLing](https://github.com/linusling) +> 校对:[SketchK](https://github.com/SketchK) + +> 3.0 +> 翻译:[Realank](https://github.com/realank) 2016-09-13 + + 本页包含内容: -- [For 循环](#for_loops) +- [For-In 循环](#for_in_loops) - [While 循环](#while_loops) - [条件语句](#conditional_statement) - [控制转移语句(Control Transfer Statements)](#control_transfer_statements) - [提前退出](#early_exit) -- [检测API可用性](#checking_api_availability) +- [检测 API 可用性](#checking_api_availability) -Swift提供了类似 C 语言的流程控制结构,包括可以多次执行任务的`for`和`while`循环,基于特定条件选择执行不同代码分支的`if`、`guard`和`switch`语句,还有控制流程跳转到其他代码的`break`和`continue`语句。 +Swift提供了多种流程控制结构,包括可以多次执行任务的`while`循环,基于特定条件选择执行不同代码分支的`if`、`guard`和`switch`语句,还有控制流程跳转到其他代码位置的`break`和`continue`语句。 -除了 C 语言里面传统的 for 循环,Swift 还增加了`for-in`循环,用来更简单地遍历数组(array),字典(dictionary),区间(range),字符串(string)和其他序列类型。 +Swift 还提供了`for-in`循环,用来更简单地遍历数组(array),字典(dictionary),区间(range),字符串(string)和其他序列类型。 -Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿至下一个 case,Swift 无需写`break`,所以不会发生这种贯穿的情况。case 还可以匹配更多的类型模式,包括区间匹配(range matching),元组(tuple)和特定类型的描述。`switch`的 case 语句中匹配的值可以是由 case 体内部临时的常量或者变量决定,也可以由`where`分句描述更复杂的匹配条件。 +Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿至下一个 case,Swift 无需写`break`,所以不会发生这种贯穿的情况。case 还可以匹配很多不同的模式,包括间隔匹配(interval match),元组(tuple)和转换到特定类型。`switch`语句的 case 中匹配的值可以绑定成临时常量或变量,在case体内使用,也可以用`where`来描述更复杂的匹配条件。 - -## For 循环 + +## For-In 循环 -Swift 提供两种`for`循环形式以来按照指定的次数多次执行一系列语句: - -* `for-in`循环对一个集合里面的每个元素执行一系列语句。 -* for 循环,用来重复执行一系列语句直到达成特定条件达成,一般通过在每次循环完成后增加计数器的值来实现。 - - -### For-In - -你可以使用`for-in`循环来遍历一个集合里面的所有元素,例如由数字表示的区间、数组中的元素、字符串中的字符。 +你可以使用`for-in`循环来遍历一个集合中的所有元素,例如数字范围、数组中的元素或者字符串中的字符。 下面的例子用来输出乘 5 乘法表前面一部分内容: @@ -49,11 +53,11 @@ for index in 1...5 { // 5 times 5 is 25 ``` -例子中用来进行遍历的元素是一组使用闭区间操作符(`...`)表示的从`1`到`5`的数字。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`print(_:separator:terminator:)`函数会再执行一次。整个过程会进行到闭区间结尾为止。 +例子中用来进行遍历的元素是使用闭区间操作符(`...`)表示的从`1`到`5`的数字区间。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表的结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`print(_:separator:terminator:)`函数会再执行一次。整个过程会进行到闭区间结尾为止。 上面的例子中,`index`是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index`在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用`let`关键字声明。 -如果你不需要知道区间序列内每一项的值,你可以使用下划线(`_`)替代变量名来忽略对值的访问: +如果你不需要区间序列内每一项的值,你可以使用下划线(`_`)替代变量名来忽略这个值: ```swift let base = 3 @@ -66,7 +70,7 @@ print("\(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`的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号`_`(替代循环中的变量)能够忽略具体的值,并且不提供循环遍历时对值的访问。 +这个例子计算 base 这个数的 power 次幂(本例中,是`3`的`10`次幂),从`1`(`3`的`0`次幂)开始做`3`的乘法, 进行`10`次,使用`1`到`10`的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号`_`(替代循环中的变量)能够忽略当前值,并且不提供循环遍历时对值的访问。 使用`for-in`遍历一个数组所有元素: @@ -95,55 +99,10 @@ for (animalName, legCount) in numberOfLegs { 字典元素的遍历顺序和插入顺序可能不同,字典的内容在内部是无序的,所以遍历元素时不能保证顺序。关于数组和字典,详情参见[集合类型](./04_Collection_Types.html)。 - -### For - -除了`for-in`循环,Swift 提供使用条件判断和递增方法的标准 C 样式`for`循环: - -```swift -for var index = 0; index < 3; ++index { - print("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`,则会执行大括号内部的代码。 -3. 执行所有语句之后,执行*递增表达式(increment expression)*。通常会增加或减少计数器的值,或者根据语句输出来修改某一个初始化的变量。当递增表达式运行完成后,重复执行第 2 步,条件表达式会再次执行。 - - -在初始化表达式中声明的常量和变量(比如`var index = 0`)只在`for`循环的生命周期里有效。如果想在循环结束后访问`index`的值,你必须要在循环生命周期开始前声明`index`。 - -```swift -var index: Int -for index = 0; index < 3; ++index { - print("index is \(index)") -} -// index is 0 -// index is 1 -// index is 2 -print("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`循环会一直运行一段语句直到条件变成`false`。这类循环适合使用在第一次迭代前,迭代次数未知的情况下。Swift 提供两种`while`循环形式: * `while`循环,每次在循环开始时计算条件是否符合; * `repeat-while`循环,每次在循环结束时计算条件是否符合。 @@ -151,22 +110,24 @@ print("The loop statements were executed \(index) times") ###While -`while`循环从计算单一条件开始。如果条件为`true`,会重复运行一系列语句,直到条件变为`false`。 +`while`循环从计算一个条件开始。如果条件为`true`,会重复运行一段语句,直到条件变为`false`。 -下面是一般情况下 `while` 循环格式: +下面是 `while` 循环的一般格式: -> while `condition` { -> `statements` -> } +``` +while condition { + statements +} +``` 下面的例子来玩一个叫做蛇和梯子的小游戏,也叫做滑道和梯子: -![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png) +![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png) 游戏的规则如下: * 游戏盘面包括 25 个方格,游戏目标是达到或者超过第 25 个方格; -* 每一轮,你通过掷一个 6 边的骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示; +* 每一轮,你通过掷一个六面体骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示; * 如果在某轮结束,你移动到了梯子的底部,可以顺着梯子爬上去; * 如果在某轮结束,你移动到了蛇的头部,你会顺着蛇的身体滑下去。 @@ -174,26 +135,27 @@ print("The loop statements were executed \(index) times") ```swift let finalSquare = 25 -var board = [Int](count: finalSquare + 1, repeatedValue: 0) +var board = [Int](repeating: 0, count: finalSquare + 1) ``` -一些方块被设置成有蛇或者梯子的指定值。梯子底部的方块是一个正值,使你可以向上移动,蛇头处的方块是一个负值,会让你向下移动: +一些方格被设置成特定的值来表示有蛇或者梯子。梯子底部的方格是一个正值,使你可以向上移动,蛇头处的方格是一个负值,会让你向下移动: ```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 补齐(这些风格上的调整都不是必须的,只是为了让代码看起来更加整洁)。 +3 号方格是梯子的底部,会让你向上移动到 11 号方格,我们使用`board[03]`等于`+08`(来表示`11`和`3`之间的差值)。使用一元正运算符(`+i`)是为了和一元负运算符(`-i`)对称,为了让盘面代码整齐,小于 10 的数字都使用 0 补齐(这些风格上的调整都不是必须的,只是为了让代码看起来更加整洁)。 -玩家由左下角编号为 0 的方格开始游戏。一般来说玩家第一次掷骰子后才会进入游戏盘面: +玩家由左下角空白处编号为 0 的方格开始游戏。玩家第一次掷骰子后才会进入游戏盘面: ```swift var square = 0 var diceRoll = 0 while square < finalSquare { // 掷骰子 - if ++diceRoll == 7 { diceRoll = 1 } + diceRoll += 1 + if diceRoll == 7 { diceRoll = 1 } // 根据点数移动 square += diceRoll if square < board.count { @@ -204,36 +166,39 @@ while square < finalSquare { print("Game over!") ``` -本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值使用前置自增操作符(`++i`)来自增 1 ,然后检测是否超出了最大值。`++diceRoll`调用完成_后_,返回值等于`diceRoll`自增后的值。任何时候如果`diceRoll`的值等于7时,就超过了骰子的最大值,会被重置为`1`。所以`diceRoll`的取值顺序会一直是`1`,`2`,`3`,`4`,`5`,`6`,`1`,`2`。 +本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值增加 1 ,然后检测是否超出了最大值。当`diceRoll`的值等于 7 时,就超过了骰子的最大值,会被重置为`1`。所以`diceRoll`的取值顺序会一直是 `1` ,`2`,`3`,`4`,`5`,`6`,`1`,`2` 等。 -掷完骰子后,玩家向前移动`diceRoll`个方格,如果玩家移动超过了第 25 个方格,这个时候游戏结束,相应地,代码会在`square`增加`board[square]`的值向前或向后移动(遇到了梯子或者蛇)之前,检测`square`的值是否小于`board`的`count`属性。 +掷完骰子后,玩家向前移动`diceRoll`个方格,如果玩家移动超过了第 25 个方格,这个时候游戏将会结束,为了应对这种情况,代码会首先判断`square`的值是否小于`board`的`count`属性,只有小于才会在`board[square]`上增加`square`,来向前或向后移动(遇到了梯子或者蛇)。 -如果没有这个检测(`square < board.count`),`board[square]`可能会越界访问`board`数组,导致错误。例如如果`square`等于`26`, 代码会去尝试访问`board[26]`,超过数组的长度。 +> 注意: +> 如果没有这个检测(`square < board.count`),`board[square]`可能会越界访问`board`数组,导致错误。如果`square`等于`26`, 代码会去尝试访问`board[26]`,超过数组的长度。 当本轮`while`循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为`false`,此时游戏结束。 -`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏的长度或者循环的次数,只有在达成指定条件时循环才会结束。 +`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏要跑多久,只有在达成指定条件时循环才会结束。 ###Repeat-While -`while`循环的另外一种形式是`repeat-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块,然后重复循环直到条件为`false`。 +`while`循环的另外一种形式是`repeat-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块。然后重复循环直到条件为`false`。 > 注意: -> Swift语言的`repeat-while`循环合其他语言中的`do-while`循环是类似的。 +> Swift语言的`repeat-while`循环和其他语言中的`do-while`循环是类似的。 -下面是一般情况下 `repeat-while`循环的格式: +下面是 `repeat-while`循环的一般格式: -> repeat { -> `statements` -> } while `condition` +```swift +repeat { + statements +} while condition +``` -还是蛇和梯子的游戏,使用`repeat-while`循环来替代`while`循环。`finalSquare`、`board`、`square`和`diceRoll`的值初始化同`while`循环一样: +还是蛇和梯子的游戏,使用`repeat-while`循环来替代`while`循环。`finalSquare`、`board`、`square`和`diceRoll`的值初始化同`while`循环时一样: ``` swift let finalSquare = 25 -var board = [Int](count: finalSquare + 1, repeatedValue: 0) +var board = [Int](repeating: 0, count: finalSquare + 1) 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 @@ -249,7 +214,8 @@ repeat { // 顺着梯子爬上去或者顺着蛇滑下去 square += board[square] // 掷骰子 - if ++diceRoll == 7 { diceRoll = 1 } + diceRoll += 1 + if diceRoll == 7 { diceRoll = 1 } // 根据点数移动 square += diceRoll } while square < finalSquare @@ -263,14 +229,14 @@ print("Game over!") ## 条件语句 -根据特定的条件执行特定的代码通常是十分有用的,例如:当错误发生时,你可能想运行额外的代码;或者,当输入的值太大或太小时,向用户显示一条消息等。要实现这些功能,你就需要使用*条件语句*。 +根据特定的条件执行特定的代码通常是十分有用的。当错误发生时,你可能想运行额外的代码;或者,当值太大或太小时,向用户显示一条消息。要实现这些功能,你就需要使用*条件语句*。 -Swift 提供两种类型的条件语句:`if`语句和`switch`语句。通常,当条件较为简单且可能的情况很少时,使用`if`语句。而`switch`语句更适用于条件较复杂、可能情况较多且需要用到模式匹配(pattern-matching)的情境。 +Swift 提供两种类型的条件语句:`if`语句和`switch`语句。通常,当条件较为简单且可能的情况很少时,使用`if`语句。而`switch`语句更适用于条件较复杂、有更多排列组合的时候。并且`switch`在需要用到模式匹配(pattern-matching)的情况下会更有用。 ### If -`if`语句最简单的形式就是只包含一个条件,当且仅当该条件为`true`时,才执行相关代码: +`if`语句最简单的形式就是只包含一个条件,只有该条件为`true`时,才执行相关代码: ```swift var temperatureInFahrenheit = 30 @@ -282,7 +248,7 @@ if temperatureInFahrenheit <= 32 { 上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行`if`块后面的代码。 -当然,`if`语句允许二选一,也就是当条件为`false`时,执行 *else 语句*: +当然,`if`语句允许二选一执行,叫做`else`从句。也就是当条件为`false`时,执行 *else 语句*: ```swift temperatureInFahrenheit = 40 @@ -294,9 +260,9 @@ if temperatureInFahrenheit <= 32 { // 输出 "It's not that cold. Wear a t-shirt." ``` -显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾——因此,`else`分支就被触发了。 +显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾。因此,`else`分支就被触发了。 -你可以把多个`if`语句链接在一起,像下面这样: +你可以把多个`if`语句链接在一起,来实现更多分支: ```swift temperatureInFahrenheit = 90 @@ -312,10 +278,10 @@ if temperatureInFahrenheit <= 32 { 在上面的例子中,额外的`if`语句用于判断是不是特别热。而最后的`else`语句被保留了下来,用于打印既不冷也不热时的消息。 -实际上,最后的`else`语句是可选的: +实际上,当不需要完整判断情况的时候,最后的`else`语句是可选的: ```swift -temperatureInFahrenheit = 90 +temperatureInFahrenheit = 72 if temperatureInFahrenheit <= 32 { print("It's very cold. Consider wearing a scarf.") } else if temperatureInFahrenheit >= 86 { @@ -332,46 +298,49 @@ if temperatureInFahrenheit <= 32 { `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` -> } +```swift +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* 构成,每个由`case`关键字开始。为了匹配某些更特定的值,Swift 提供了几种方法来进行更复杂的模式匹配,这些模式将在本节的稍后部分提到。 -`switch`语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支满足该要求,这个默认分支必须在`switch`语句的最后面。 +与`if`语句类似,每一个 case 都是代码执行的一条分支。`switch`语句会决定哪一条分支应该被执行,这个流程被称作根据给定的值*切换(switching)*。 + +`switch`语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支来涵盖其它所有没有对应的值,这个默认分支必须在`switch`语句的最后面。 下面的例子使用`switch`语句来匹配一个名为`someCharacter`的小写字符: + ```swift -let someCharacter: Character = "e" +let someCharacter: Character = "z" switch someCharacter { -case "a", "e", "i", "o", "u": - print("\(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": - print("\(someCharacter) is a consonant") +case "a": + print("The first letter of the alphabet") +case "z": + print("The last letter of the alphabet") default: - print("\(someCharacter) is not a vowel or a consonant") + print("Some other character") } -// 输出 "e is a vowel" +// Prints "The last letter of the alphabet" ``` -在这个例子中,第一个 case 分支用于匹配五个元音,第二个 case 分支用于匹配所有的辅音。 +在这个例子中,第一个 case 分支用于匹配第一个英文字母`a`,第二个 case 分支用于匹配最后一个字母`z`。 +因为`switch`语句必须有一个case分支用于覆盖所有可能的字符,而不仅仅是所有的英文字母,所以switch语句使用`default`分支来匹配除了`a`和`z`外的所有值,这个分支保证了swith语句的完备性。 -由于为其它可能的字符写 case 分支没有实际的意义,因此在这个例子中使用了默认分支来处理剩下的既不是元音也不是辅音的字符——这就保证了`switch`语句的完备性。 #### 不存在隐式的贯穿(No Implicit Fallthrough) -与 C 语言和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。 +与 C 和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。 > 注意: 虽然在Swift中`break`不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用`break`跳出,详情请参见[Switch 语句中的 break](#break_in_a_switch_statement)。 @@ -381,34 +350,39 @@ default: ```swift let anotherCharacter: Character = "a" switch anotherCharacter { -case "a": +case "a": // Invalid, the case has an empty body case "A": print("The letter A") default: print("Not the letter A") } -// this will report a compile-time error +// This will report a compile-time error. ``` -不像 C 语言里的`switch`语句,在 Swift 中,`switch`语句不会同时匹配`"a"`和`"A"`。相反的,上面的代码会引起编译期错误:`case "a": does not contain any executable statements`——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。 +不像 C 语言里的`switch`语句,在 Swift 中,`switch`语句不会一起匹配`"a"`和`"A"`。相反的,上面的代码会引起编译期错误:`case "a": 不包含任何可执行语句`——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。 -一个 case 也可以包含多个模式,用逗号把它们分开(如果太长了也可以分行写): - -> switch `some value to consider` { -> case `value 1`, -> `value 2`: -> `statements` -> } +为了让单个case同时匹配`a`和`A`,可以将这个两个值组合成一个复合匹配,并且用逗号分开: +```swift +let anotherCharacter: Character = "a" +switch anotherCharacter { +case "a", "A": + print("The letter A") +default: + print("Not the letter A") +} +// Prints "The letter A +``` +为了可读性,符合匹配可以写成多行形式,详情请参考[复合匹配(Compound Cases)](#compound_cases) > 注意: -如果想要贯穿至特定的 case 分支中,请使用`fallthrough`语句,详情请参考[贯穿(Fallthrough)](#fallthrough)。 +如果想要显式贯穿case分支,请使用`fallthrough`语句,详情请参考[贯穿(Fallthrough)](#fallthrough)。 #### 区间匹配 case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式: -``` +```swift let approximateCount = 62 let countedThings = "moons orbiting Saturn" var naturalCount: String @@ -430,10 +404,9 @@ print("There are \(naturalCount) \(countedThings).") // 输出 "There are dozens of moons orbiting Saturn." ``` -在上例中,`approximateCount`在一个`switch`声明中被估值。每一个`case`都与之进行比较。因为`approximateCount`落在了12到100的区间,所以`naturalCount`等于`"dozens of"`值,并且此后这段执行跳出了`switch`声明。 +在上例中,`approximateCount`在一个`switch`声明中被评估。每一个`case`都与之进行比较。因为`approximateCount`落在了 12 到 100 的区间,所以`naturalCount`等于`"dozens of"`值,并且此后的执行跳出了`switch`语句。 + -> 注意: -> 闭区间操作符(`...`)以及半开区间操作符(`..<`)功能被重载去返回`IntervalType`或`Range`。一个区间可以决定他是否包含特定的元素,就像当匹配一个`switch`声明的`case`一样。区间是一个连续值的集合,可以用`for-in`语句遍历它。 #### 元组(Tuple) @@ -459,17 +432,17 @@ default: // 输出 "(1, 1) is inside the box" ``` -![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphSimple_2x.png) +![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphSimple_2x.png) -在上面的例子中,`switch`语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在黄色y轴上,是否在一个以原点为中心的4x4的矩形里,或者在这个矩形外面。 +在上面的例子中,`switch`语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在橘黄色的y轴上,是否在一个以原点为中心的4x4的蓝色矩形里,或者在这个矩形外面。 -不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有_四个 case_。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配`case (0, 0)`,因此剩下的能够匹配(0, 0)的 case 分支都会被忽视掉。 +不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有_四个 case_。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配`case (0, 0)`,因此剩下的能够匹配的分支都会被忽视掉。 #### 值绑定(Value Bindings) -case 分支的模式允许将匹配的值绑定到一个临时的常量或变量,这些常量或变量在该 case 分支里就可以被引用了——这种行为被称为*值绑定*(value binding)。 +case 分支允许将匹配的值绑定到一个临时的常量或变量,并且在case分支体内使用 —— 这种行为被称为*值绑定*(value binding),因为匹配的值在case分支体内,与临时的常量或变量绑定。 下面的例子展示了如何在一个`(Int, Int)`类型的元组中使用值绑定来分类下图中的点(x, y): @@ -486,18 +459,16 @@ case let (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) +![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphMedium_2x.png) -在上面的例子中,`switch`语句会判断某个点是否在红色的x轴上,是否在黄色y轴上,或者不在坐标轴上。 +在上面的例子中,`switch`语句会判断某个点是否在红色的x轴上,是否在橘黄色的y轴上,或者不在坐标轴上。 这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`anotherPoint`的一个或两个值。第一个 case ——`case (let x, 0)`将匹配一个纵坐标为`0`的点,并把这个点的横坐标赋给临时的常量`x`。类似的,第二个 case ——`case (0, let y)`将匹配一个横坐标为`0`的点,并把这个点的纵坐标赋给临时的常量`y`。 -一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里引用。在这个例子中,它们用于简化`print(_:)`的书写。 +一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里使用。在这个例子中,它们用于打印给定点的类型。 请注意,这个`switch`语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)`声明了一个可以匹配余下所有值的元组。这使得`switch`语句已经完备了,因此不需要再书写默认分支。 -在上面的例子中,`x`和`y`是常量,这是因为没有必要在其对应的 case 分支中修改它们的值。然而,它们也可以是变量——程序将会创建临时变量,并用相应的值初始化它。修改这些变量只会影响其对应的 case 分支。 - #### Where @@ -518,18 +489,52 @@ case let (x, y): // 输出 "(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) +![image](https://developer.apple.com/library/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 都声明了常量`x`和`y`的占位符,用于临时获取元组`yetAnotherPoint`的两个值。这两个常量被用作`where`语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当`where`语句的条件为`true`时,匹配到的 case 分支才会被执行。 就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,`switch`语句就已经完备了,因此不需要再书写默认分支。 + +#### 复合匹配(Compound Cases) + +当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个case后面,并且用逗号隔开。当case后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写: + +```swift +let someCharacter: Character = "e" +switch someCharacter { +case "a", "e", "i", "o", "u": + print("\(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": + print("\(someCharacter) is a consonant") +default: + print("\(someCharacter) is not a vowel or a consonant") +} +``` + +这个switch语句中的第一个case,匹配了英语中的五个小写原因字母。相似的,第二个case匹配了英语中所有的小写辅音字母。最终,default分支匹配了其它所有字符。 +复合匹配同样可以包含值绑定。复合匹配里所有的匹配模式,都必须包含相同的值绑定。并且每一个绑定都必须获取到相同类型的值。这保证了,无论复合匹配中的哪个模式发生了匹配,分支体内的代码,都能获取到绑定的值,并且绑定的值都有一样的类型。 + +```swift +let stillAnotherPoint = (9, 0) +switch stillAnotherPoint { +case (let distance, 0), (0, let distance): + print("On an axis, \(distance) from the origin") +default: + print("Not on an axis") +} +``` + +上面的case有两个模式:`(let distance, 0)`匹配了在x轴上的值,`(0, let distance)`匹配了在y轴上的值。两个模式都绑定了`distance`,并且`distance`在两种模式下,都是整型——这意味着分支体内的代码,只要case匹配,都可以获取到`distance`值 + + ## 控制转移语句(Control Transfer Statements) -控制转移语句改变你代码的执行顺序,通过它你可以实现代码的跳转。Swift有四种控制转移语句。 +控制转移语句改变你代码的执行顺序,通过它可以实现代码的跳转。Swift 有五种控制转移语句: - `continue` - `break` @@ -537,15 +542,12 @@ case let (x, y): - `return` - `throw` -我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](./06_Functions.html)章节讨论,`throw`语句会在[错误抛出](./18_Error_Handling.html#throwing_errors) +我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](./06_Functions.html)章节讨论,`throw`语句会在[错误抛出](./18_Error_Handling.html#throwing_errors)章节讨论。 ### Continue -`continue`语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。就好像在说“本次循环迭代我已经执行完了”,但是并不会离开整个循环体。 - -> 注意: -> 在一个带有条件和递增的for循环体中,调用`continue`语句后,迭代增量仍然会被计算求值。循环体继续像往常一样工作,仅仅只是循环体中的执行代码会被跳过。 +`continue`语句告诉一个循环体立刻停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离开整个循环体。 下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句: @@ -564,7 +566,7 @@ print(puzzleOutput) // 输出 "grtmndsthnklk" ``` -在上面的代码中,只要匹配到元音字母或者空格字符,就调用`continue`语句,使本次循环迭代结束,从新开始下次循环迭代。这种行为使`switch`匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。 +在上面的代码中,只要匹配到元音字母或者空格字符,就调用`continue`语句,使本次循环结束,重新开始下次循环。这种行为使`switch`匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。 ### Break @@ -574,7 +576,7 @@ print(puzzleOutput) #### 循环语句中的 break -当在一个循环体中使用`break`时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环迭代的代码被执行,也不会再有下次的循环迭代产生。 +当在一个循环体中使用`break`时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环的代码被执行,也不会再有下次的循环产生。 #### Switch 语句中的 break @@ -584,7 +586,7 @@ print(puzzleOutput) 这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的`switch`需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上`break`语句。当那个分支被匹配到时,分支内的`break`语句立即结束`switch`代码块。 >注意: -当一个`switch`分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让`switch`分支达到被忽略的效果。你总是可以使用`break`来忽略某个分支。 +当一个`switch`分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让`switch`分支达到被忽略的效果。你应该使用`break`来忽略某个分支。 下面的例子通过`switch`来判断一个`Character`值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。 @@ -613,14 +615,14 @@ if let integerValue = possibleIntegerValue { 这个例子检查`numberSymbol`是否是拉丁,阿拉伯,中文或者泰语中的`1`到`4`之一。如果被匹配到,该`switch`分支语句给`Int?`类型变量`possibleIntegerValue`设置一个整数值。 -当`switch`代码块执行完后,接下来的代码通过使用可选绑定来判断`possibleIntegerValue`是否曾经被设置过值。因为是可选类型的缘故,`possibleIntegerValue`有一个隐式的初始值`nil`,所以仅仅当`possibleIntegerValue`曾被`switch`代码块的前四个分支中的某个设置过一个值时,可选的绑定将会被判定为成功。 +当`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 分支从而引发的错误。 +Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下,C 语言要求你显式地插入`break`语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。 如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用`fallthrough`关键字。下面的例子使用`fallthrough`来创建一个数字的描述语句。 @@ -638,29 +640,29 @@ print(description) // 输出 "The number 5 is a prime number, and also an integer." ``` -这个例子定义了一个`String`类型的变量`description`并且给它设置了一个初始值。函数使用`switch`逻辑来判断`integerToDescribe`变量的值。当`integerToDescribe`的值属于列表中的质数之一时,该函数添加一段文字在`description`后,来表明这个是数字是一个质数。然后它使用`fallthrough`关键字来“贯穿”到`default`分支中。`default`分支添加一段额外的文字在`description`的最后,至此`switch`代码块执行完了。 +这个例子定义了一个`String`类型的变量`description`并且给它设置了一个初始值。函数使用`switch`逻辑来判断`integerToDescribe`变量的值。当`integerToDescribe`的值属于列表中的质数之一时,该函数在`description`后添加一段文字,来表明这个数字是一个质数。然后它使用`fallthrough`关键字来“贯穿”到`default`分支中。`default`分支在`description`的最后添加一段额外的文字,至此`switch`代码块执行完了。 -如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到包含所有的`default`分支中。 +如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到`default`分支中。 -当`switch`代码块执行完后,使用`print`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。 +当`switch`代码块执行完后,使用`print(_:separator:terminator:)`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。 > 注意: -> `fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码执行继续连接到下一个 case 中的执行代码,这和 C 语言标准中的`switch`语句特性是一样的。 +> `fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码继续连接到下一个 case 中的代码,这和 C 语言标准中的`switch`语句特性是一样的。 ### 带标签的语句 -在 Swift 中,你可以在循环体和`switch`代码块中嵌套循环体和`switch`代码块来创造复杂的控制流结构。然而,循环体和`switch`代码块两者都可以使用`break`语句来提前结束整个方法体。因此,显示地指明`break`语句想要终止的是哪个循环体或者`switch`代码块,会很有用。类似地,如果你有许多嵌套的循环体,显示指明`continue`语句想要影响哪一个循环体也会非常有用。 +在 Swift 中,你可以在循环体和条件语句中嵌套循环体和条件语句来创造复杂的控制流结构。并且,循环体和条件语句都可以使用`break`语句来提前结束整个代码块。因此,显式地指明`break`语句想要终止的是哪个循环体或者条件语句,会很有用。类似地,如果你有许多嵌套的循环体,显式指明`continue`语句想要影响哪一个循环体也会非常有用。 -为了实现这个目的,你可以使用标签来标记一个循环体或者`switch`代码块,当使用`break`或者`continue`时,带上这个标签,可以控制该标签代表对象的中断或者执行。 +为了实现这个目的,你可以使用标签(*statement label*)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用`break`加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用`break`或者`continue`加标签,来结束或者继续这条被标记语句的执行。 -产生一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,并且该标签后面还需带着一个冒号。下面是一个`while`循环体的语法,同样的规则适用于所有的循环体和`switch`代码块。 +声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字(introducor keyword),并且该标签后面跟随一个冒号。下面是一个针对`while`循环体的标签语法,同样的规则适用于所有的循环体和条件语句。 > `label name`: while `condition` { > `statements` > } -下面的例子是在一个带有标签的`while`循环体中调用`break`和`continue`语句,该循环体是前面章节中*蛇和梯子*的改编版本。这次,游戏增加了一条额外的规则: +下面的例子是前面章节中*蛇和梯子*的适配版本,在此版本中,我们将使用一个带有标签的`while`循环体中调用`break`和`continue`语句。这次,游戏增加了一条额外的规则: - 为了获胜,你必须*刚好*落在第 25 个方块中。 @@ -668,35 +670,36 @@ print(description) 游戏的棋盘和之前一样: -![image](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png) +![image](https://developer.apple.com/library/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) +var board = [Int](repeating: 0, count: finalSquare + 1) 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`循环和`switch`语句来实现游戏的逻辑。`while`循环有一个标签名`gameLoop`,来表明它是游戏的主循环。 该`while`循环体的条件判断语句是`while square !=finalSquare`,这表明你必须刚好落在方格25中。 ```swift gameLoop: while square != finalSquare { - if ++diceRoll == 7 { diceRoll = 1 } + diceRoll += 1 + if diceRoll == 7 { diceRoll = 1 } switch square + diceRoll { case finalSquare: - // 到达最后一个方块,游戏结束 + // diceRoll will move us to the final square, so the game is over break gameLoop case let newSquare where newSquare > finalSquare: - // 超出最后一个方块,再掷一次骰子 + // diceRoll will move us beyond the final square, so roll again continue gameLoop default: - // 本次移动有效 + // this is a valid move, so find out its effect square += diceRoll square += board[square] } @@ -704,20 +707,21 @@ gameLoop: while square != finalSquare { print("Game over!") ``` -每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。 +每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`语句来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。 -- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop`语句跳转控制去执行`while`循环体后的第一行代码,游戏结束。 -- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop`语句结束本次`while`循环的迭代,开始下一次循环迭代。 -- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动骰子数个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。本次循环迭代结束,控制跳转到`while`循环体的条件判断语句处,再决定是否能够继续执行下次循环迭代。 +- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop`语句跳转控制去执行`while`循环体后的第一行代码,意味着游戏结束。 +- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop`语句结束本次`while`循环,开始下一次循环。 +- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动 `diceRoll` 个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。接着本次循环结束,控制跳转到`while`循环体的条件判断语句处,再决定是否需要继续执行下次循环。 >注意: -如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`代码块而不是`while`循环体。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。 +如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`语句而不是`while`循环。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。 同时请注意,当调用`continue gameLoop`去跳转到下一次循环迭代时,这里使用`gameLoop`标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以`continue`语句会影响到哪个循环体是没有歧义的。然而,`continue`语句使用`gameLoop`标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的`break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。 ## 提前退出 -像`if`语句一样,`guard`的执行取决于一个表达式的布尔值。我们可以使用`guard`语句来要求条件必须为真时,以执行`guard`语句后的代码。不同于`if`语句,一个`guard`语句总是有一个`else`分句,如果条件不为真则执行`else`分局中的代码。 +像`if`语句一样,`guard`的执行取决于一个表达式的布尔值。我们可以使用`guard`语句来要求条件必须为真时,以执行`guard`语句后的代码。不同于`if`语句,一个`guard`语句总是有一个`else`从句,如果条件不为真则执行`else`从句中的代码。 + ```swift func greet(person: [String: String]) { @@ -725,7 +729,6 @@ func greet(person: [String: String]) { return } print("Hello \(name)") - guard let location = person["location"] else { print("I hope the weather is nice near you.") return @@ -733,43 +736,45 @@ func greet(person: [String: String]) { print("I hope the weather is nice in \(location).") } greet(["name": "John"]) -// prints "Hello John!" -// prints "I hope the weather is nice near you." +// 输出 "Hello John!" +// 输出 "I hope the weather is nice near you." greet(["name": "Jane", "location": "Cupertino"]) -// prints "Hello Jane!" -// prints "I hope the weather is nice in Cupertino." +// 输出 "Hello Jane!" +// 输出 "I hope the weather is nice in Cupertino." ``` -如果`guard`语句的条件被满足,则在保护语句的封闭大括号结束后继续执行代码。任何使用了可选绑定作为条件的一部分并被分配了值的变量或常量对于剩下的保护语句出现的代码段是可用的。 +如果`guard`语句的条件被满足,则继续执行`guard`语句大括号后的代码。将变量或者常量的可选绑定作为`guard`语句的条件,都可以保护`guard`语句后面的代码。 -如果条件不被满足,在`else`分支上的代码就会被执行。这个分支必须转移控制以退出`guard`语句出现的代码段。它可以用控制转移语句如`return`,`break`或`continue`做这件事,或者它调用了一个不返回的方法或函数,例如`fatalError()`。 +如果条件不被满足,在`else`分支上的代码就会被执行。这个分支必须转移控制以退出`guard`语句出现的代码段。它可以用控制转移语句如`return`,`break`,`continue`或者`throw`做这件事,或者调用一个不返回的方法或函数,例如`fatalError()`。 -相比于可以实现同样功能的`if`语句,按需使用`guard`语句会提升我们代码的可靠性。 -它可以使你的代码连贯的被执行而不需要将它包在`else`块中,它可以使你处理违反要求的代码接近要求。 +相比于可以实现同样功能的`if`语句,按需使用`guard`语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在`else`块中,它可以使你在紧邻条件判断的地方,处理违规的情况。 ## 检测 API 可用性 -Swift 有内置支持去检查接口的可用性的,这可以确保我们不会不小心地使用对于当前部署目标不可用的 API。 +Swift内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的API。 -编译器使用 SDK 中的可用信息来验证在我们在可用部署目标指定项目的代码中所有的 API 调用。如果我们尝试使用一个不可用的 API,Swift 会在编译期报错。 +编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个不可用的 API,Swift 会在编译时报错。 -我们使用一个可用性条件在一个`if`或`guard`语句中去有条件的执行一段代码,这取决于我们想要使用的 API 是否在运行时是可用的。编译器使用从可用性条件语句中获取的信息,这时它会去验证在代码块中调用的 API 是否都可用。 +我们在`if`或`guard`语句中使用`可用性条件(availability condition)`去有条件的执行一段代码,来在运行时判断调用的API是否可用。编译器使用从可用性条件语句中获取的信息去验证,在这个代码块中调用的 API 是否可用。 ```swift -if #available(iOS 9, OSX 10.10, *) { - // 在 iOS 使用 iOS 9 APIs , 并且在 OS X 使用 OS X v10.10 APIs +if #available(iOS 10, macOS 10.12, *) { + // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API } else { - // 回滚至早前 iOS and OS X 的API + // 使用先前版本的 iOS 和 macOS 的 API } ``` -以上可用性条件指定在iOS,`if`段的代码仅仅在 iOS 9 及更高可运行;在 OS X,仅在 OS X v10.10 及更高可运行。最后一个参数,`*`,是必须的并且指定在任何其他平台上,`if`段的代码在最小可用部署目标指定项目中执行。 +以上可用性条件指定,在iOS中,`if`语句的代码块仅仅在 iOS 10 及更高的系统下运行;在 macOS中,仅在 macOS 10.12 及更高才会运行。最后一个参数,`*`,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if语句的代码块将会运行。 -在它普遍的形式中,可用性条件获取了平台名字和版本的清单。平台名字可以是`iOS`,`OSX`或`watchOS`。除了特定的主板本号像 iOS 8,我们可以指定较小的版本号像 iOS 8.3 以及 OS X v10.10.3。 +在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是`iOS`,`macOS`,`watchOS`和`tvOS`——请访问[声明属性](../chapter3/06_Attributes.html)来获取完整列表。除了指定像 iOS 8的主板本号,我们可以指定像iOS 8.3 以及 macOS 10.10.3的子版本号。 + +```swift +if #available(platform name version, ..., *) { + statements to execute if the APIs are available +} else { + fallback statements to execute if the APIs are unavailable +} +``` ->if #available(`platform name` `version`, `...`, *) { -> `statements to execute if the APIs are available` ->} else { -> `fallback statements to execute if the APIs are unavailable` ->} diff --git a/source/chapter2/06_Functions.md b/source/chapter2/06_Functions.md index 05d55cfb..81d3e15e 100755 --- a/source/chapter2/06_Functions.md +++ b/source/chapter2/06_Functions.md @@ -8,163 +8,167 @@ > 2.0 > 翻译+校对:[dreamkidd](https://github.com/dreamkidd) -本页包含内容: +> 2.1 +> 翻译:[DianQK](https://github.com/DianQK) +> 定稿:[shanks](http://codebuild.me) +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-12 + +> 3.0 +> 翻译: [crayygy](https://github.com/crayygy) 2016-09-12 + +本页包含内容: - [函数定义与调用(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 Argument Labels and Parameter Names) ](#Function_Argument_Labels_and_Parameter_Names) - [函数类型(Function Types)](#Function_Types) -- [函数嵌套(Nested Functions)](#Nested_Functions) +- [嵌套函数(Nested Functions)](#Nested_Functions) -函数是用来完成特定任务的独立的代码块。你给一个函数起一个合适的名字,用来标识函数做什么,并且当函数需要执行的时候,这个名字会被“调用”。 -Swift 统一的函数语法足够灵活,可以用来表示任何函数,包括从最简单的没有参数名字的 C 风格函数,到复杂的带局部和外部参数名的 Objective-C 风格函数。参数可以提供默认值,以简化函数调用。参数也可以既当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值可以被修改。 +`函数` 是一段完成特定任务的独立代码片段。你可以通过给函数命名来标识某个函数的功能,这个名字可以被用来在需要的时候"调用"这个函数来完成它的任务。 -在 Swift 中,每个函数都有一种类型,包括函数的参数值类型和返回值类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。 +Swift 统一的函数语法非常的灵活,可以用来表示任何函数,包括从最简单的没有参数名字的 C 风格函数,到复杂的带局部和外部参数名的 Objective-C 风格函数。参数可以提供默认值,以简化函数调用。参数也可以既当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值将被修改。 + +在 Swift 中,每个函数都有一个由函数的参数值类型和返回值类型组成的类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。 -## 函数的定义与调用(Defining and Calling Functions) +## 函数的定义与调用 (Defining and Calling Functions) -当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入(称为参数,parameters),也可以定义某种类型的值作为函数执行结束的输出(称为返回类型)。 +当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入(称为*参数*,*parameters*),也可以定义某种类型的值作为函数执行结束时的输出(称为 *返回* 类型,*return* type)。 -每个函数有个函数名,用来描述函数执行的任务。要使用一个函数时,你用函数名“调用”,并传给它匹配的输入值(称作实参,arguments)。一个函数的实参必须与函数参数表里参数的顺序一致。 +每个函数有个函数名,用来描述函数执行的任务。要使用一个函数时,用函数名来“调用”这个函数,并传给它匹配的输入值(称作 *实参* ,*arguments*)。函数的实参必须与函数参数表里参数的顺序一致。 + + +下面例子中的函数的名字是`sayHello(_:)`,之所以叫这个名字,是因为这个函数用一个人的名字当做输入,并返回向这个人问候的语句。为了完成这个任务,你需要定义一个输入参数——一个叫做 `personName` 的 `String` 值,和一个包含给这个人问候语的 `String` 类型的返回值: -在下面例子中的函数叫做`"sayHello(_:)"`,之所以叫这个名字,是因为这个函数用一个人的名字当做输入,并返回给这个人的问候语。为了完成这个任务,你定义一个输入参数-一个叫做 `personName` 的 `String` 值,和一个包含给这个人问候语的 `String` 类型的返回值: ```swift -func sayHello(personName: String) -> String { - let greeting = "Hello, " + personName + "!" +func greet(person: String) -> String { + let greeting = "Hello, " + person + "!" return greeting } ``` -所有的这些信息汇总起来成为函数的定义,并以 `func` 作为前缀。指定函数返回类型时,用返回箭头 `->`(一个连字符后跟一个右尖括号)后跟返回类型的名称的方式来表示。 +所有的这些信息汇总起来成为函数的*定义*,并以 `func` 作为前缀。指定函数返回类型时,用返回箭头 `->`(一个连字符后跟一个右尖括号)后跟返回类型的名称的方式来表示。 -该定义描述了函数做什么,它期望接收什么和执行结束时它返回的结果是什么类型。这样的定义使得函数可以在别的地方以一种清晰的方式被调用: +该定义描述了函数的功能,它期望接收什么作为参数和执行结束时它返回的结果是什么类型。这样的定义使得函数可以在别的地方以一种清晰的方式被调用: ```swift -print(sayHello("Anna")) -// prints "Hello, Anna!" -print(sayHello("Brian")) -// prints "Hello, Brian!" +print(greet(person: "Anna")) +// 打印 "Hello, Anna!" +print(greet(person: "Brian")) +// 打印 "Hello, Brian!" ``` -调用 `sayHello(_:)` 函数时,在圆括号中传给它一个 `String` 类型的实参。因为这个函数返回一个 `String` 类型的值,`sayHello` 可以被包含在 `print` 的调用中,用来输出这个函数的返回值,正如上面所示。 +调用 `sayHello(_:)` 函数时,在圆括号中传给它一个 `String` 类型的实参,例如 `sayHello("Anna")`。正如上面所示,因为这个函数返回一个 `String` 类型的值,所以`sayHello` 可以被包含在 `print(_:separator:terminator:)` 的调用中,用来输出这个函数的返回值。 -在 `sayHello(_:)` 的函数体中,先定义了一个新的名为 `greeting` 的 `String` 常量,同时赋值了给 `personName` 的一个简单问候消息。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 被调用,该函数结束它的执行并返回 `greeting` 的当前值。 +>注意 +`print(_:separator:terminator:)` 函数的第一个参数并没有设置一个标签,而其他的参数因为已经有了默认值,因此是可选的。关于这些函数语法上的变化详见下方关于 函数参数标签和参数名 以及 默认参数值。 + + +在 `sayHello(_:)` 的函数体中,先定义了一个新的名为 `greeting` 的 `String` 常量,同时,把对 `personName` 的问候消息赋值给了 `greeting` 。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 被调用,该函数结束它的执行并返回 `greeting` 的当前值。 你可以用不同的输入值多次调用 `sayHello(_:)`。上面的例子展示的是用`"Anna"`和`"Brian"`调用的结果,该函数分别返回了不同的结果。 为了简化这个函数的定义,可以将问候消息的创建和返回写成一句: ```swift -func sayHelloAgain(personName: String) -> String { - return "Hello again, " + personName + "!" +func greetAgain(person: String) -> String { + return "Hello again, " + person + "!" } -print(sayHelloAgain("Anna")) -// prints "Hello again, Anna!" +print(greetAgain(person: "Anna")) +// 打印 "Hello again, Anna!" ``` -## 函数参数与返回值(Function Parameters and Return Values) +## 函数参数与返回值 (Function Parameters and Return Values) -函数参数与返回值在Swift中极为灵活。你可以定义任何类型的函数,包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。 +函数参数与返回值在 Swift 中非常的灵活。你可以定义任何类型的函数,包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。 -### 多重输入参数(Multiple Input Parameters) + +### 无参数函数 (Functions Without Parameters) -函数可以有多个输入参数,写在圆括号中,用逗号分隔。 - -下面这个函数用一个半开区间的开始点和结束点,计算出这个范围内包含多少数字: - -```swift -func halfOpenRangeLength(start: Int, end: Int) -> Int { - return end - start -} -print(halfOpenRangeLength(1, 10)) -// prints "9" -``` - -### 无参函数(Functions Without Parameters) - -函数可以没有参数。下面这个函数就是一个无参函数,当被调用时,它返回固定的 `String` 消息: +函数可以没有参数。下面这个函数就是一个无参数函数,当被调用时,它返回固定的 `String` 消息: ```swift func sayHelloWorld() -> String { return "hello, world" } print(sayHelloWorld()) -// prints "hello, world" +// 打印 "hello, world" ``` 尽管这个函数没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。 -### 多参量函数 (Functions With Multiple Parameters) + +### 多参数函数 (Functions With Multiple Parameters) 函数可以有多种输入参数,这些参数被包含在函数的括号之中,以逗号分隔。 -这个函数取得一个人的名字和是否被招呼作为输入,并对那个人返回适当的问候语: +下面这个函数用一个人名和是否已经打过招呼作为输入,并返回对这个人的适当问候语: ```swift -func sayHello(personName: String, alreadyGreeted: Bool) -> String { +func greet(person: String, alreadyGreeted: Bool) -> String { if alreadyGreeted { - return sayHelloAgain(personName) + return greetAgain(person: person) } else { - return sayHello(personName) + return greet(person: person) } } -print(sayHello("Tim", alreadyGreeted: true)) -// prints "Hello again, Tim!" +print(greet(person: "Tim", alreadyGreeted: true)) +// 打印 "Hello again, Tim!" ``` -你通过在括号内传递一个`String`参数值和一个标识为`alreadyGreeted`的`Bool`值,使用逗号分隔来调用`sayHello(_:alreadyGreeted:)`函数。 - -当调用超过一个参数的函数时,第一个参数后的参数根据其对应的参数名称标记,函数参数命名在[函数参数名称(Function Parameter Names)](#Function_Parameter_Names)有更详细的描述。 +你可以通过在括号内使用逗号分隔来传递一个`String`参数值和一个标识为`alreadyGreeted`的`Bool`值,来调用`sayHello(_:alreadyGreeted:)`函数。注意这个函数和上面`greet(person:)`是不同的。虽然它们都有着同样的名字`greet`,但是`greet(person:alreadyGreeted:)`函数需要两个参数,而`greet(person:)`只需要一个参数。 -### 无返回值函数(Functions Without Return Values) +### 无返回值函数 (Functions Without Return Values) -函数可以没有返回值。下面是 `sayHello(_:)` 函数的另一个版本,叫 `sayGoodbye(_:)`,这个函数直接输出 `String` 值,而不是返回它: +函数可以没有返回值。下面是 `sayHello(_:)` 函数的另一个版本,叫 `sayGoodbye(_:)`,这个函数直接打印一个`String`值,而不是返回它: ```swift -func sayGoodbye(personName: String) { - print("Goodbye, \(personName)!") +func greet(person: String) { + print("Hello, \(person)!") } -sayGoodbye("Dave") -// prints "Goodbye, Dave!" +greet(person: "Dave") +// 打印 "Hello, Dave!" ``` 因为这个函数不需要返回值,所以这个函数的定义中没有返回箭头(->)和返回类型。 -> 注意: -> 严格上来说,虽然没有返回值被定义,`sayGoodbye(_:)` 函数依然返回了值。没有定义返回类型的函数会返回特殊的值,叫 `Void`。它其实是一个空的元组(tuple),没有任何元素,可以写成`()`。 +>注意 +严格上来说,虽然没有返回值被定义,`sayGoodbye(_:)` 函数依然返回了值。没有定义返回类型的函数会返回一个特殊的`Void`值。它其实是一个空的元组(tuple),没有任何元素,可以写成()。 + 被调用时,一个函数的返回值可以被忽略: ```swift -func printAndCount(stringToPrint: String) -> Int { - print(stringToPrint) - return stringToPrint.characters.count +func printAndCount(string: String) -> Int { + print(string) + return string.characters.count } -func printWithoutCounting(stringToPrint: String) { - printAndCount(stringToPrint) +func printWithoutCounting(string: String) { + let _ = printAndCount(string: string) } -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(string: "hello, world") +// 打印 "hello, world" 并且返回值 12 +printWithoutCounting(string: "hello, world") +// 打印 "hello, world" 但是没有返回任何值 ``` 第一个函数 `printAndCount(_:)`,输出一个字符串并返回 `Int` 类型的字符数。第二个函数 `printWithoutCounting`调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。 -> 注意: -> 返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,并且试图这样做,这将导致编译错误(compile-time error)。 +>注意 +返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,将导致编译时错误(compile-time error)。 -### 多重返回值函数(Functions with Multiple Return Values) + + +### 多重返回值函数 (Functions with Multiple Return Values) 你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。 -下面的这个例子中,定义了一个名为`minMax(_:)`的函数,作用是在一个`Int`数组中找出最小值与最大值。 +下例中定义了一个名为 `minMax(_:)` 的函数,作用是在一个 `Int` 类型的数组中找出最小值与最大值。 ```swift func minMax(array: [Int]) -> (min: Int, max: Int) { @@ -181,30 +185,33 @@ func minMax(array: [Int]) -> (min: Int, max: Int) { } ``` -`minMax(_:)`函数返回一个包含两个`Int`值的元组,这些值被标记为`min`和`max`,以便查询函数的返回值时他们可以被访问。 + `minMax(_:)` 函数返回一个包含两个 `Int` 值的元组,这些值被标记为 `min` 和 `max` ,以便查询函数的返回值时可以通过名字访问它们。 -`minMax(_:)`的函数体中,在开始的时候设置两个工作变量`currentMin`和`currentMax`作为数组中的第一个`Int`值。然后函数会遍历数组中剩余的值并检查该值是否比`currentMin`和`currentMax`更小或更大。最后数组中的最小值与最大值返回两个`Int`值最为一个元组。 +在 `minMax(_:)` 的函数体中,在开始的时候设置两个工作变量 `currentMin` 和 `currentMax` 的值为数组中的第一个数。然后函数会遍历数组中剩余的值并检查该值是否比 `currentMin` 和 `currentMax` 更小或更大。最后数组中的最小值与最大值作为一个包含两个 `Int` 值的元组返回。 -因为元组的成员值被命名为函数的返回类型的一部分​​,可以通过点语法来访问与取回发现的最小值与最小值: +因为元组的成员值已被命名,因此可以通过 `.` 语法来检索找到的最小值与最大值: ```swift -let bounds = minMax([8, -6, 2, 109, 3, 71]) +let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) print("min is \(bounds.min) and max is \(bounds.max)") -// prints "min is -6 and max is 109" +// 打印 "min is -6 and max is 109" ``` -需要注意的是,元组的成员不需要在函数中返回时命名,因为它们的名字已经在函数返回类型中有了定义。 +需要注意的是,元组的成员不需要在元组从函数中返回时命名,因为它们的名字已经在函数返回类型中指定了。 -可选元组返回类型(Optional Tuple Return Types) + +### 可选元组返回类型 (Optional Tuple Return Types) -如果函数返回的元组类型中有可能在整个元组中含有“没有值”,你可以使用*可选的(Optional)* 元组返回类型反映整个元组可以是`nil`的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如`(Int,Int)?`或`(String,Int,Bool)?` +如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用可选的( `optional` ) 元组返回类型反映整个元组可以是`nil`的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 `(Int, Int)?` 或 `(String, Int, Bool)?` -> 注意: -> 可选元组类型如`(Int,Int)?`与元组包含可选属性如`(Int?,Int?)`是不同的.可选的元组类型,整个数组是可选的,而不只是元组中的每个元素值。 +>注意 +可选元组类型如 `(Int, Int)?` 与元组包含可选类型如 `(Int?, Int?)` 是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。 -前面的`minMax(_:)`函数返回了一个包含两个`Int`值的元组。但是函数不会在数组中执行任何安全检查,如果`array`参数有一个空数组,如上定义的`minMax(_:)`在试图访问`array[0]`时会触发一个运行时错误。 -为了安全的处理这个"空数组"问题,写一个`minMax(_:)`函数使用可选元组返回类型,并且当数组为空时返回`nil`: +前面的 `minMax(_:)` 函数返回了一个包含两个 `Int` 值的元组。但是函数不会对传入的数组执行任何安全检查,如果 `array` 参数是一个空数组,如上定义的 `minMax(_:)` 在试图访问 `array[0]` 时会触发一个运行时错误(runtime error)。 + +为了安全地处理这个“空数组”问题,将 `minMax(_:)` 函数改写为使用可选元组返回类型,并且当数组为空时返回 `nil`: + ```swift func minMax(array: [Int]) -> (min: Int, max: Int)? { @@ -222,104 +229,97 @@ func minMax(array: [Int]) -> (min: Int, max: Int)? { } ``` -你可以选择性的绑定当`minMax(_:)`函数返回的是一个实际的元组值还是`nil` +你可以使用可选绑定来检查 `minMax(_:)` 函数返回的是一个存在的元组值还是 `nil`: ```swift -if let bounds = minMax([8, -6, 2, 109, 3, 71]) { +if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) { print("min is \(bounds.min) and max is \(bounds.max)") } -// prints "min is -6 and max is 109" +// 打印 "min is -6 and max is 109" ``` - -## 函数参数名称(Function Parameter Names) -函数参数都有一个*外部参数名(external parameter name)*和一个*本地参数名(local parameter name)*。外部参数名用来标记传递给函数调用的参数,本地参数名在实现函数的时候使用。 + +## 函数参数标签和参数名称 (Function Argument Labels and Parameter Names) + +每个函数参数都有一个参数标签( argument label )以及一个参数名称( parameter name )。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。 ```swift func someFunction(firstParameterName: Int, secondParameterName: Int) { - // function body goes here - // firstParameterName and secondParameterName refer to - // the argument values for the first and second parameters + // In the function body, firstParameterName and secondParameterName + // refer to the argument values for the first and second parameters. +} +someFunction(firstParameterName: 1, secondParameterName: 2) +``` + +所有的参数都必须有一个独一无二的名字。虽然多个参数拥有同样的参数标签是可能的,但是一个唯一的函数标签能够使你的代码更具可读性。 + + +### 参数标签 (Specifying Argument Labels) + +你可以在函数名称前指定它的参数标签,中间以空格分隔: + +```swift +func someFunction(argumentLabel parameterName: Int) { + // In the function body, parameterName refers to the argument value + // for that parameter. +} +``` + +这个版本的 `greet(person:)` 函数,接收一个人的名字和他的家乡,并且返回一句问候: + +```swift +func greet(person: String, from hometown: String) -> String { + return "Hello \(person)! Glad you could visit from \(hometown)." +} +print(greet(person: "Bill", from: "Cupertino")) +// Prints "Hello Bill! Glad you could visit from Cupertino." +``` + +参数标签的使用能够让一个函数在调用时更有表达力,更类似自然语言,并且仍保持了函数内部的可读性以及清晰的意图。 + + +### 忽略参数标签(Omitting Argument Labels) + +如果你不希望为某个参数添加一个标签,可以使用一个下划线(`_`)来代替一个明确的参数标签。 + +```swift +func someFunction(_ firstParameterName: Int, secondParameterName: Int) { + // In the function body, firstParameterName and secondParameterName + // refer to the argument values for the first and second parameters. } someFunction(1, secondParameterName: 2) ``` -一般情况下,第一个参数省略其外部参数名,第二个以后的参数使用其本地参数名作为自己的外部参数名。所有参数需要有不同的本地参数名,但可以共享相同的外部参数名。 - - -### 指定外部参数名(Specifying 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 -} -``` - -> 注意: -> 如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。 - -这个版本的`sayHello(_:)`函数,得到了两个人的名字,会同时返回对他俩的问候: - -```swift -func sayHello(to person: String, and anotherPerson: String) -> String { - return "Hello \(person) and \(anotherPerson)!" -} -print(sayHello(to: "Bill", and: "Ted")) -// prints "Hello Bill and Ted!" -``` - -为每个参数指定外部参数名,在你调用函数`sayHello(to:and:)`函数时时两个参数都必须被标记出来。 - -使用外部函数名可以使得函数可以用一句话表达清楚,并且使得函数体内部可读,能表达出函数的明确意图。 - -### 忽略外部参数名(Omitting External Parameter Names) - -如果你不想为第二个及后续的参数设置参数名,用一个下划线(`_`)代替一个明确地参数名。 - -```swift -func someFunction(firstParameterName: Int, _ secondParameterName: Int) { - // function body goes here - // firstParameterName and secondParameterName refer to - // the argument values for the first and second parameters -} -someFunction(1, 2) -``` - -> 注意: -> 因为第一个参数默认忽略其外部参数名称,明确写下划线是多余的。 +如果一个参数有一个标签,那么在调用的时候必须使用标签来标记这个参数。 -### 默认参数值(Default Parameter Values) +### 默认参数值 (Default Parameter Values) -你可以在函数体中为每个参数定义`默认值(Deafult Values)`。当默认值被定义后,调用这个函数时可以忽略这个参数。 +你可以在函数体中通过给参数赋值来为任意一个参数定义默认值(Deafult Values)。当默认值被定义后,调用这个函数时可以忽略这个参数。 ```swift -func someFunction(parameterWithDefault: Int = 12) { - // function body goes here - // if no arguments are passed to the function call, - // value of parameterWithDefault is 12 +func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) { + // If you omit the second argument when calling this function, then + // the value of parameterWithDefault is 12 inside the function body. } -someFunction(6) // parameterWithDefault is 6 -someFunction() // parameterWithDefault is 12 +someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault is 6 +someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12 ``` -> 注意: -> 将带有默认值的参数放在函数参数列表的最后。这样可以保证在函数调用时,非默认参数的顺序是一致的,同时使得相同的函数在不同情况下调用时显得更为清晰。 +将不带有默认值的参数放在函数参数列表的最前。一般来说,没有默认值的参数更加的重要,将不带默认值的参数放在最前保证在函数调用时,非默认参数的顺序是一致的,同时也使得相同的函数在不同情况下调用时显得更为清晰。 -### 可变参数(Variadic Parameters) + +### 可变参数 (Variadic Parameters) -一个`可变参数(variadic parameter)`可以接受零个或多个值。函数调用时,你可以用可变参数来传入不确定数量的输入参数。通过在变量类型名后面加入`(...)`的方式来定义可变参数。 +一个可变参数(variadic parameter)可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(`...`)的方式来定义可变参数。 -可变参数的传入值在函数体为此类型的一个数组。例如,一个叫做 `numbers` 的 `Double...` 型可变参数,在函数体内可以当做一个叫 `numbers` 的 `[Double]` 型的数组常量。 +可变参数的传入值在函数体中变为此类型的一个数组。例如,一个叫做 `numbers` 的 `Double...` 型可变参数,在函数体内可以当做一个叫 `numbers` 的 `[Double]` 型的数组常量。 -下面的这个函数用来计算一组任意长度数字的`算术平均数(arithmetic mean)`: +下面的这个函数用来计算一组任意长度数字的 *算术平均数(arithmetic mean)*: ```swift -func arithmeticMean(numbers: Double...) -> Double { +func arithmeticMean(_ numbers: Double...) -> Double { var total: Double = 0 for number in numbers { total += number @@ -332,75 +332,33 @@ arithmeticMean(3, 8.25, 18.75) // returns 10.0, which is the arithmetic mean of these three numbers ``` -> 注意: -> 一个函数最多只能有一个可变参数。 - -如果函数有一个或多个带默认值的参数,而且还有一个可变参数,那么把可变参数放在参数表的最后。 - -### 常量参数和变量参数(Constant and Variable Parameters) - -函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。 - -但是,有时候,如果函数中有传入参数的变量值副本将是很有用的。你可以通过指定一个或多个参数为变量参数,从而避免自己在函数中定义新的变量。变量参数不是常量,你可以在函数中把它当做新的可修改副本来使用。 - -通过在参数名前加关键字 `var` 来定义变量参数: - -```swift -func alignRight(var string: String, totalLength: Int, pad: Character) -> String { - let amountToPad = totalLength - string.characters.count - if amountToPad < 1 { - return string - } - let padString = String(pad) - for _ in 1...amountToPad { - string = padString + string - } - return string -} -let originalString = "hello" -let paddedString = alignRight(originalString, totalLength: 10, pad: "-") -// paddedString is equal to "-----hello" -// originalString is still equal to "hello" -``` - -这个例子中定义了一个新的叫做 `alignRight(_:totalLength:pad:)` 的函数,用来右对齐输入的字符串到一个长的输出字符串中。左侧空余的地方用指定的填充字符填充。这个例子中,字符串`"hello"`被转换成了`"-----hello"`。 - -`alignRight(_:totalLength:pad:)` 函数将参数 `string` 定义为变量参数。这意味着 `string` 现在可以作为一个局部变量,用传入的字符串值初始化,并且可以在函数体中进行操作。 - - 函数首先找出有多少字符需要被添加到左边的字符串以右对齐在整个字符串。这个值是存储在一个本地常数称为amountToPad。如果不需要填充(也就是说,如果amountToPad小于1),该函数返回字符串没有填充的输入值。 -   -  否则,该函数创建一个新的临时字符串常量称为padString,初始化填充字符,并将amountToPad padString副本添加到现有的左边的字符串。(一个字符串值不能被添加到一个字符值,所以padString常数用于确保双方+操作符的字符串值)。 - -该函数首先计算出多少个字符需要被添加到 `string` 的左边,以右对齐到总的字符串中。这个值存在局部常量 `amountToPad` 中。如果不需要填充(即,如果`amountToPad`小于`1`),该函数返回没有填充输入的`string`。 - -否则,该函数会建立一个临时的String常量称为`padString`,初始化`pad`字符,并将`amountToPad`作为`padString`的副本添加到现有的字符串左边。(一个`Character`后不能直接添加一个`String`值,所以`padString`经常用于确保`+`号两边都是`String`值。) - -> 注意: -> 对变量参数所进行的修改在函数调用结束后便消失了,并且对于函数体外是不可见的。变量参数仅仅存在于函数调用的生命周期中。 +>注意 +一个函数最多只能拥有一个可变参数。 ### 输入输出参数(In-Out Parameters) -变量参数,正如上面所述,仅仅能在函数体内被更改。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。 +函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误(compile-time error)。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。 -定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。 +定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个`输入输出参数`有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看`输入输出参数`一节。 -你只能将变量作为输入输出参数。你不能传入常量或者字面量(literal value),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数前加`&`符,表示这个值可以被函数修改。 +你只能传递变量给输入输出参数。你不能传入常量或者字面量(literal value),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 `&` 符,表示这个值可以被函数修改。 -> 注意: -> 输入输出参数不能有默认值,而且可变参数不能用 `inout` 标记。如果你用 `inout` 标记一个参数,这个参数不能被 `var` 或者 `let` 标记。 +>注意 +输入输出参数不能有默认值,而且可变参数不能用 `inout` 标记。 -下面是例子,`swapTwoInts(_:_:)` 函数,有两个分别叫做 `a` 和 `b` 的输入输出参数: + +下例中,`swapTwoInts(_:_:)` 函数有两个分别叫做 `a` 和 `b` 的输入输出参数: ```swift -func swapTwoInts(inout a: Int, inout _ b: Int) { +func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA } ``` -这个 `swapTwoInts(_:_:)` 函数仅仅交换 `a` 与 `b` 的值。该函数先将 `a` 的值存到一个暂时常量 `temporaryA` 中,然后将 `b` 的值赋给 `a`,最后将 `temporaryA` 幅值给 `b`。 +`swapTwoInts(_:_:)` 函数简单地交换 `a` 与 `b` 的值。该函数先将 `a` 的值存到一个临时常量 `temporaryA` 中,然后将 `b` 的值赋给 `a`,最后将 `temporaryA` 赋值给 `b`。 你可以用两个 `Int` 型的变量来调用 `swapTwoInts(_:_:)`。需要注意的是,`someInt` 和 `anotherInt` 在传入 `swapTwoInts(_:_:)` 函数前,都加了 `&` 的前缀: @@ -409,33 +367,34 @@ var someInt = 3 var anotherInt = 107 swapTwoInts(&someInt, &anotherInt) print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") -// prints "someInt is now 107, and anotherInt is now 3" +// Prints "someInt is now 107, and anotherInt is now 3" ``` 从上面这个例子中,我们可以看到 `someInt` 和 `anotherInt` 的原始值在 `swapTwoInts(_:_:)` 函数中被修改,尽管它们的定义在函数体外。 -> 注意: -> 输入输出参数和返回值是不一样的。上面的 `swapTwoInts` 函数并没有定义任何返回值,但仍然修改了 `someInt` 和 `anotherInt` 的值。输入输出参数是函数对函数体外产生影响的另一种方式。 +>注意 +输入输出参数和返回值是不一样的。上面的 `swapTwoInts` 函数并没有定义任何返回值,但仍然修改了 `someInt` 和 `anotherInt` 的值。输入输出参数是函数对函数体外产生影响的另一种方式。 + -## 函数类型(Function Types) +## 函数类型 (Function Types) -每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成。 +每个函数都有种特定的`函数类型`,函数的类型由函数的参数类型和返回类型组成。 例如: ```swift -func addTwoInts(a: Int, _ b: Int) -> Int { +func addTwoInts(_ a: Int, _ b: Int) -> Int { return a + b } -func multiplyTwoInts(a: Int, _ b: Int) -> Int { +func multiplyTwoInts(_ a: Int, _ b: Int) -> Int { return a * b } ``` -这个例子中定义了两个简单的数学函数:`addTwoInts` 和 `multiplyTwoInts`。这两个函数都传入两个 `Int` 类型, 返回一个合适的`Int`值。 +这个例子中定义了两个简单的数学函数:`addTwoInts` 和 `multiplyTwoInts`。这两个函数都接受两个 `Int` 值, 返回一个 `Int` 值。 -这两个函数的类型是 `(Int, Int) -> Int`,可以读作”这个函数类型,它有两个 `Int` 型的参数并返回一个 `Int` 型的值。”。 +这两个函数的类型是 `(Int, Int) -> Int`,可以解读为“这个函数类型有两个 `Int` 型的参数并返回一个 `Int` 型的值。”。 下面是另一个例子,一个没有参数,也没有返回值的函数: @@ -445,98 +404,103 @@ func printHelloWorld() { } ``` -这个函数的类型是:`() -> void`,或者叫“没有参数,并返回 `Void` 类型的函数”。 +这个函数的类型是:`() -> Void`,或者叫“没有参数,并返回 `Void` 类型的函数”。 -### 使用函数类型(Using Function Types) + +### 使用函数类型 (Using Function Types) -在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将函数赋值给它: +在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它: ```swift var mathFunction: (Int, Int) -> Int = addTwoInts ``` -这个可以读作: +这段代码可以被解读为: -“定义一个叫做 `mathFunction` 的变量,类型是‘一个有两个 `Int` 型的参数并返回一个 `Int` 型的值的函数’,并让这个新变量指向 `addTwoInts` 函数”。 +”定义一个叫做 `mathFunction` 的变量,类型是‘一个有两个 `Int` 型的参数并返回一个 `Int` 型的值的函数’,并让这个新变量指向 `addTwoInts` 函数”。 -`addTwoInts` 和 `mathFunction` 有同样的类型,所以这个赋值过程在 Swift 类型检查中是允许的。 +`addTwoInts` 和 `mathFunction` 有同样的类型,所以这个赋值过程在 Swift 类型检查(type-check)中是允许的。 现在,你可以用 `mathFunction` 来调用被赋值的函数了: ```swift print("Result: \(mathFunction(2, 3))") -// prints "Result: 5" +// Prints "Result: 5" ``` 有相同匹配类型的不同函数可以被赋值给同一个变量,就像非函数类型的变量一样: + ```swift mathFunction = multiplyTwoInts print("Result: \(mathFunction(2, 3))") -// prints "Result: 6" +// Prints "Result: 6" ``` 就像其他类型一样,当赋值一个函数给常量或变量时,你可以让 Swift 来推断其函数类型: ```swift let anotherMathFunction = addTwoInts -// anotherMathFunction is inferred to be of type (Int, Int) -> Int +// anotherMathFunction 被推断为 (Int, Int) -> Int 类型 ``` -### 函数类型作为参数类型(Function Types as Parameter Types) + +### 函数类型作为参数类型 (Function Types as Parameter Types) -你可以用`(Int, Int) -> Int`这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现交由给函数的调用者。 +你可以用 `(Int, Int) -> Int` 这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现留给函数的调用者来提供。 下面是另一个例子,正如上面的函数一样,同样是输出某种数学运算结果: ```swift -func printMathResult(mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) { +func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) { print("Result: \(mathFunction(a, b))") } printMathResult(addTwoInts, 3, 5) -// prints "Result: 8" +// 打印 "Result: 8" ``` -这个例子定义了 `printMathResult(_:_:_:)` 函数,它有三个参数:第一个参数叫 `mathFunction`,类型是`(Int, Int) -> Int`,你可以传入任何这种类型的函数;第二个和第三个参数叫 `a` 和 `b`,它们的类型都是 `Int`,这两个值作为已给的函数的输入值。 +这个例子定义了 `printMathResult(_:_:_:)` 函数,它有三个参数:第一个参数叫 `mathFunction`,类型是 `(Int, Int) -> Int`,你可以传入任何这种类型的函数;第二个和第三个参数叫 `a` 和 `b`,它们的类型都是 `Int`,这两个值作为已给出的函数的输入值。 -当 `printMathResult(_:_:_:)` 被调用时,它被传入 `addTwoInts` 函数和整数`3`和`5`。它用传入`3`和`5`调用 `addTwoInts`,并输出结果:`8`。 +当 `printMathResult(_:_:_:)` 被调用时,它被传入 `addTwoInts` 函数和整数 `3` 和 `5`。它用传入 `3` 和 `5` 调用 `addTwoInts`,并输出结果:`8`。 -`printMathResult(_:_:_:)` 函数的作用就是输出另一个合适类型的数学函数的调用结果。它不关心传入函数是如何实现的,它只关心这个传入的函数类型是正确的。这使得 `printMathResult(_:_:_:)` 可以以一种类型安全(type-safe)的方式来保证传入函数的调用是正确的。 +`printMathResult(_:_:_:)` 函数的作用就是输出另一个适当类型的数学函数的调用结果。它不关心传入函数是如何实现的,只关心传入的函数是不是一个正确的类型。这使得 `printMathResult(_:_:_:)` 能以一种类型安全(type-safe)的方式将一部分功能转给调用者实现。 -### 函数类型作为返回类型(Function Type as Return Types) + +### 函数类型作为返回类型 (Function Types as Return Types) -你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(`->`)后写一个完整的函数类型。 +你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(->)后写一个完整的函数类型。 -下面的这个例子中定义了两个简单函数,分别是 `stepForward` 和`stepBackward`。`stepForward` 函数返回一个比输入值大一的值。`stepBackward` 函数返回一个比输入值小一的值。这两个函数的类型都是 `(Int) -> Int`: +下面的这个例子中定义了两个简单函数,分别是 `stepForward` 和 `stepBackward`。`stepForward`函数返回一个比输入值大 `1` 的值。`stepBackward` 函数返回一个比输入值小 `1` 的值。这两个函数的类型都是 `(Int) -> Int`: ```swift -func stepForward(input: Int) -> Int { +func stepForward(_ input: Int) -> Int { return input + 1 } -func stepBackward(input: Int) -> Int { +func stepBackward(_ input: Int) -> Int { return input - 1 } ``` -下面这个叫做 `chooseStepFunction(_:)` 的函数,它的返回类型是 `(Int) -> Int` 的函数。`chooseStepFunction(_:)` 根据布尔值 `backwards` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数: +如下名为 `chooseStepFunction(_:)` 的函数,它的返回类型是 `(Int) -> Int` 类型的函数。`chooseStepFunction(_:)` 根据布尔值 `backwards` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数: ```swift -func chooseStepFunction(backwards: Bool) -> (Int) -> Int { - return backwards ? stepBackward : stepForward +func chooseStepFunction(backward: Bool) -> (Int) -> Int { + return backward ? stepBackward : stepForward } ``` -你现在可以用 `chooseStepFunction(_:)` 来获得一个函数,不管是那个方向: +你现在可以用 `chooseStepFunction(_:)` 来获得两个函数其中的一个: ```swift var currentValue = 3 -let moveNearerToZero = chooseStepFunction(currentValue > 0) -// moveNearerToZero now refers to the stepBackward() function +let moveNearerToZero = chooseStepFunction(backward: currentValue > 0) +// moveNearerToZero 现在指向 stepBackward() 函数。 ``` -上面这个例子中计算出从 `currentValue` 逐渐接近到`0`是需要向正数走还是向负数走。`currentValue` 的初始值是`3`,这意味着 `currentValue > 0` 是真的(`true`),这将使得 `chooseStepFunction(_:)` 返回 `stepBackward(_:)` 函数。一个指向返回的函数的引用保存在了 `moveNearerToZero` 常量中。 +上面这个例子中计算出从 `currentValue` 逐渐接近到0是需要向正数走还是向负数走。`currentValue` 的初始值是 `3`,这意味着 `currentValue > 0` 为真(true),这将使得 `chooseStepFunction(_:)` 返回 `stepBackward(_:)` 函数。一个指向返回的函数的引用保存在了 `moveNearerToZero` 常量中。 + +现在,moveNearerToZero 指向了正确的函数,它可以被用来数到零: -现在,`moveNearerToZero` 指向了正确的函数,它可以被用来数到`0`: ```swift print("Counting to zero:") @@ -550,26 +514,25 @@ print("zero!") // 2... // 1... // zero! - ``` -## 嵌套函数(Nested Functions) +## 嵌套函数 (Nested Functions) -这章中你所见到的所有函数都叫全局函数(global functions),它们定义在全局域中。你也可以把函数定义在别的函数体中,称作嵌套函数(nested functions)。 +到目前为止本章中你所见到的所有函数都叫`全局`函数(global functions),它们定义在`全局域`中。你也可以把函数定义在别的函数体中,称作 `嵌套函数`(nested functions)。 -默认情况下,嵌套函数是对外界不可见的,但是可以被他们封闭函数(enclosing function)来调用。一个封闭函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。 +默认情况下,嵌套函数是对外界不可见的,但是可以被它们的外围函数(enclosing function)调用。一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。 你可以用返回嵌套函数的方式重写 `chooseStepFunction(_:)` 函数: ```swift -func chooseStepFunction(backwards: Bool) -> (Int) -> Int { +func chooseStepFunction(backward: Bool) -> (Int) -> Int { func stepForward(input: Int) -> Int { return input + 1 } func stepBackward(input: Int) -> Int { return input - 1 } - return backwards ? stepBackward : stepForward + return backward ? stepBackward : stepForward } var currentValue = -4 -let moveNearerToZero = chooseStepFunction(currentValue > 0) +let moveNearerToZero = chooseStepFunction(backward: currentValue > 0) // moveNearerToZero now refers to the nested stepForward() function while currentValue != 0 { print("\(currentValue)... ") diff --git a/source/chapter2/07_Closures.md b/source/chapter2/07_Closures.md index ae7d5235..3fc7d74f 100755 --- a/source/chapter2/07_Closures.md +++ b/source/chapter2/07_Closures.md @@ -8,23 +8,33 @@ > 2.0 > 翻译+校对:[100mango](https://github.com/100mango) +> 2.1 +> 翻译:[100mango](https://github.com/100mango), [magicdict](https://github.com/magicdict) +> 校对:[shanks](http://codebuild.me) +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-12 +> +> 3.0 +> 翻译:[Lanford](https://github.com/LanfordCai) 2016-09-19 + 本页包含内容: - [闭包表达式(Closure Expressions)](#closure_expressions) - [尾随闭包(Trailing Closures)](#trailing_closures) - [值捕获(Capturing Values)](#capturing_values) - [闭包是引用类型(Closures Are Reference Types)](#closures_are_reference_types) +- [逃逸闭包(Escaping Closures) ](#escaping_closures) +- [自动闭包(Autoclosures)](#autoclosures) -闭包是自包含的函数代码块,可以在代码中被传递和使用。 -Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 匿名函数比较相似。 +*闭包*是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。 -闭包可以捕获和存储其所在上下文中任意常量和变量的引用。 -这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作。 +闭包可以捕获和存储其所在上下文中任意常量和变量的引用。*闭合、包裹*常量和变量,所谓闭包也。Swift 会为你管理在捕获过程中涉及到的所有内存操作。 -> 注意: -> 如果您不熟悉捕获(capturing)这个概念也不用担心,您可以在 [值捕获](#capturing_values) 章节对其进行详细了解。 +> 注意 +> 如果你不熟悉捕获(capturing)这个概念也不用担心,你可以在[值捕获](#capturing_values)章节对其进行详细了解。 -在[函数](./06_Functions.html) 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一: +在[函数](./06_Functions.html)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一: * 全局函数是一个有名字但不会捕获任何值的闭包 * 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包 @@ -33,7 +43,7 @@ Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他 Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下: * 利用上下文推断参数和返回值类型 -* 隐式返回单表达式闭包,即单表达式闭包可以省略`return`关键字 +* 隐式返回单表达式闭包,即单表达式闭包可以省略 `return` 关键字 * 参数名称缩写 * 尾随(Trailing)闭包语法 @@ -41,54 +51,43 @@ Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进 ## 闭包表达式(Closure Expressions) -[嵌套函数](./06_Functions.html#nested_function) 是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在您处理一些函数并需要将另外一些函数作为该函数的参数时。 +[嵌套函数](./06_Functions.html#nested_function)是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候编写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在你处理一些函数并需要将另外一些函数作为该函数的参数时。 -闭包表达式是一种利用简洁语法构建内联闭包的方式。 -闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。 -下面闭包表达式的例子通过使用几次迭代展示了`sort(_:)`方法定义和语法优化的方式。 -每一次迭代都用更简洁的方式描述了相同的功能。 +*闭包表达式*是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。下面闭包表达式的例子通过使用几次迭代展示了 `sorted(by:)` 方法定义和语法优化的方式。每一次迭代都用更简洁的方式描述了相同的功能。 -### sort 函数(The Sort Function) +### sorted 方法(The Sorted Method) -Swift 标准库提供了名为`sort`的函数,会根据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。 -一旦排序完成,`sort(_:)`方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被`sort(_:)`方法修改。 +Swift 标准库提供了名为 `sorted(by:)` 的方法,它会根据你所提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,`sorted(by:)` 方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被 `sorted(by:)` 方法修改。 -下面的闭包表达式示例使用`sort(_:)`方法对一个String类型的数组进行字母逆序排序,以下是初始数组值: +下面的闭包表达式示例使用 `sorted(by:)` 方法对一个 `String` 类型的数组进行字母逆序排序。以下是初始数组: ```swift let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] ``` -`sort(_:)`方法需要传入两个参数: +`sorted(by:)` 方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值*前面*,排序闭包函数需要返回`true`,反之返回`false`。 -* 已知类型的数组 -* 闭包函数,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回`true`,反之返回`false`。 +该例子对一个 `String` 类型的数组进行排序,因此排序闭包函数类型需为 `(String, String) -> Bool`。 -该例子对一个`String`类型的数组进行排序,因此排序闭包函数类型需为`(String, String) -> Bool`。 - -提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为`sort(_:)`方法的参数传入: +提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为 `sorted(by:)` 方法的参数传入: ```swift -func backwards(s1: String, s2: String) -> Bool { +func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2 } -var reversed = names.sort(backwards) -// reversed 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"] +var reversedNames = names.sorted(by: backward) +// reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"] ``` -如果第一个字符串 (`s1`) 大于第二个字符串 (`s2`),`backwards`函数返回`true`,表示在新的数组中`s1`应该出现在`s2`前。 -对于字符串中的字符来说,“大于” 表示 “按照字母顺序较晚出现”。 -这意味着字母`"B"`大于字母`"A"`,字符串`"Tom"`大于字符串`"Tim"`。 -其将进行字母逆序排序,`"Barry"`将会排在`"Alex"`之前。 +如果第一个字符串(`s1`)大于第二个字符串(`s2`),`backward(_:_:)` 函数会返回 `true`,表示在新的数组中 `s1` 应该出现在 `s2` 前。对于字符串中的字符来说,“大于”表示“按照字母顺序较晚出现”。这意味着字母 `"B"` 大于字母 `"A"` ,字符串 `"Tom"` 大于字符串 `"Tim"`。该闭包将进行字母逆序排序,`"Barry"` 将会排在 `"Alex"` 之前。 -然而,这是一个相当冗长的方式,本质上只是写了一个单表达式函数 (a > b)。 -在下面的例子中,利用闭合表达式语法可以更好的构造一个内联排序闭包。 +然而,以这种方式来编写一个实际上很简单的表达式(`a > b`),确实太过繁琐了。对于这个例子来说,利用闭包表达式语法可以更好地构造一个内联排序闭包。 ### 闭包表达式语法(Closure Expression Syntax) -闭包表达式语法有如下一般形式: +闭包表达式语法有如下的一般形式: ```swift { (parameters) -> returnType in @@ -96,94 +95,80 @@ var reversed = names.sort(backwards) } ``` -闭包表达式语法可以使用常量、变量和`inout`类型作为参数,不提供默认值。 -也可以在参数列表的最后使用可变参数。 -元组也可以作为参数和返回值。 +闭包表达式的参数可以是inout参数,但不能设定默认值。也可以使用具名的可变参数(译者注:但是如果可变参数不放在参数列表的最后一位的话,调用闭包的时时编译器将报错。可参考[这里](http://stackoverflow.com/questions/39548852/swift-3-0-closure-expression-what-if-the-variadic-parameters-not-at-the-last-pl))。元组也可以作为参数和返回值。 -下面的例子展示了之前`backwards`函数对应的闭包表达式版本的代码: +下面的例子展示了之前 `backward(_:_:)` 函数对应的闭包表达式版本的代码: ```swift -reversed = names.sort({ (s1: String, s2: String) -> Bool in +reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 }) ``` -需要注意的是内联闭包参数和返回值类型声明与`backwards`函数类型声明相同。 -在这两种方式中,都写成了`(s1: String, s2: String) -> Bool`。 -然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。 +需要注意的是内联闭包参数和返回值类型声明与 `backward(_:_:)` 函数类型声明相同。在这两种方式中,都写成了 `(s1: String, s2: String) -> Bool`。然而在内联闭包表达式中,函数和返回值类型都写在*大括号内*,而不是大括号外。 -闭包的函数体部分由关键字`in`引入。 -该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。 +闭包的函数体部分由关键字`in`引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。 -因为这个闭包的函数体部分如此短以至于可以将其改写成一行代码: +由于这个闭包的函数体部分如此短,以至于可以将其改写成一行代码: ```swift -reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } ) +reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } ) ``` -这说明`sort(_:)`方法的整体调用保持不变,一对圆括号仍然包裹住了函数中整个参数集合。而其中一个参数现在变成了内联闭包(相比于`backwards`版本的代码) +该例中 `sorted(by:)` 方法的整体调用保持不变,一对圆括号仍然包裹住了方法的整个参数。然而,参数现在变成了内联闭包。 ### 根据上下文推断类型(Inferring Type From Context) -因为排序闭包函数是作为`sort(_:)`方法的参数进行传入的,Swift可以推断其参数和返回值的类型。 -`sorted`期望第二个参数是类型为`(String, String) -> Bool`的函数,因此实际上`String`,`String`和`Bool`类型并不需要作为闭包表达式定义中的一部分。 -因为所有的类型都可以被正确推断,返回箭头 (`->`) 和围绕在参数周围的括号也可以被省略: +因为排序闭包函数是作为 `sorted(by:)` 方法的参数传入的,Swift 可以推断其参数和返回值的类型。`sorted(by:)` 方法被一个字符串数组调用,因此其参数必须是 `(String, String) -> Bool` 类型的函数。这意味着 `(String, String)` 和 `Bool` 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(`->`)和围绕在参数周围的括号也可以被省略: ```swift -reversed = names.sort( { s1, s2 in return s1 > s2 } ) +reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } ) ``` -实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。 +实际上,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,总是能够推断出闭包的参数和返回值类型。这意味着闭包作为函数或者方法的参数时,你几乎不需要利用完整格式构造内联闭包。 -然而您仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则可以采用完整格式的闭包。而在`sort(_:)`方法这个例子里,闭包的目的就是排序,读者能够推测除这个闭包是用于字符串处理的,因为这个闭包是为了处理字符串数组的排序。 +尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包。而在 `sorted(by:)` 方法这个例子里,显然闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。 -### 单表达式闭包隐式返回(Implicit Return From Single-Expression Clossures) +### 单表达式闭包隐式返回(Implicit Returns From Single-Expression Closures) -单行表达式闭包可以通过隐藏`return`关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为: +单行表达式闭包可以通过省略 `return` 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为: ```swift -reversed = names.sort( { s1, s2 in s1 > s2 } ) +reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } ) ``` -在这个例子中,`sort(_:)`方法的第二个参数函数类型明确了闭包必须返回一个`Bool`类型值。 -因为闭包函数体只包含了一个单一表达式 (`s1 > s2`),该表达式返回`Bool`类型值,因此这里没有歧义,`return`关键字可以省略。 +在这个例子中,`sorted(by:)` 方法的参数类型明确了闭包必须返回一个 `Bool` 类型值。因为闭包函数体只包含了一个单一表达式(`s1 > s2`),该表达式返回 `Bool` 类型值,因此这里没有歧义,`return` 关键字可以省略。 ### 参数名称缩写(Shorthand Argument Names) -Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过`$0`,`$1`,`$2`来顺序调用闭包的参数。 +Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 `$0`,`$1`,`$2` 来顺序调用闭包的参数,以此类推。 -如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。 -`in`关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成: +如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。`in`关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成: ```swift -reversed = names.sort( { $0 > $1 } ) +reversedNames = names.sorted(by: { $0 > $1 } ) ``` -在这个例子中,`$0`和`$1`表示闭包中第一个和第二个`String`类型的参数。 +在这个例子中,`$0`和`$1`表示闭包中第一个和第二个 `String` 类型的参数。 - -### 运算符函数(Operator Functions) + +### 运算符方法(Operator Methods) -实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。 -Swift 的`String`类型定义了关于大于号 (`>`) 的字符串实现,其作为一个函数接受两个`String`类型的参数并返回`Bool`类型的值。 -而这正好与`sort(_:)`方法的第二个参数需要的函数类型相符合。 -因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现: +实际上还有一种更简短的方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断出你想使用大于号的字符串函数实现: ```swift -reversed = names.sort(>) +reversedNames = names.sorted(by: >) ``` -更多关于运算符表达式的内容请查看 [运算符函数](./24_Advanced_Operators.html#operator_functions)。 +更多关于运算符方法的内容请查看[运算符方法](./25_Advanced_Operators.html#operator_methods)。 ## 尾随闭包(Trailing Closures) - -如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。 -尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。 +如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用*尾随闭包*来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签: ```swift func someFunctionThatTakesAClosure(closure: () -> Void) { @@ -191,33 +176,33 @@ func someFunctionThatTakesAClosure(closure: () -> Void) { } // 以下是不使用尾随闭包进行函数调用 -someFunctionThatTakesAClosure({ +someFunctionThatTakesAClosure(closure: { // 闭包主体部分 }) // 以下是使用尾随闭包进行函数调用 someFunctionThatTakesAClosure() { - // 闭包主体部分 + // 闭包主体部分 } ``` -> 注意: -> 如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把`()`省略掉。 - -在上例中作为`sorted`函数参数的字符串排序闭包可以改写为: +在[闭包表达式语法](#closure_expression_syntax)一节中作为 `sorted(by:)` 方法参数的字符串排序闭包可以改写为: ```swift -reversed = names.sort() { $0 > $1 } +reversedNames = names.sorted() { $0 > $1 } ``` -当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。 -举例来说,Swift 的`Array`类型有一个`map`方法,其获取一个闭包表达式作为其唯一参数。 -数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。 -具体的映射方式和返回值类型由闭包来指定。 +如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 `()` 省略掉: -当提供给数组闭包函数后,`map`方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。 +```swift +reversedNames = names.sorted { $0 > $1 } +``` -下例介绍了如何在`map`方法中使用尾随闭包将`Int`类型数组`[16,58,510]`转换为包含对应`String`类型的数组`["OneSix", "FiveEight", "FiveOneZero"]`: +当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。举例来说,Swift 的 `Array` 类型有一个 `map(_:)` 方法,这个方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。 + +当提供给数组的闭包应用于每个数组元素后,`map(_:)` 方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。 + +下例介绍了如何在 `map(_:)` 方法中使用尾随闭包将 `Int` 类型数组 `[16, 58, 510]` 转换为包含对应 `String` 类型的值的数组`["OneSix", "FiveEight", "FiveOneZero"]`: ```swift let digitNames = [ @@ -227,112 +212,90 @@ let digitNames = [ let numbers = [16, 58, 510] ``` -如上代码创建了一个数字位和它们名字映射的英文版本字典。 -同时定义了一个准备转换为字符串的整型数组。 +如上代码创建了一个整型数位和它们英文版本名字相映射的字典。同时还定义了一个准备转换为字符串数组的整型数组。 -您现在可以通过传递一个尾随闭包给`numbers`的`map`方法来创建对应的字符串版本数组。 -需要注意的是调用`numbers.map`不需要在`map`后面包含任何括号,因为其只需要传递闭包表达式这一个参数,并且该闭包表达式参数通过尾随方式进行撰写: +你现在可以通过传递一个尾随闭包给 `numbers` 数组的 `map(_:)` 方法来创建对应的字符串版本数组: ```swift let strings = numbers.map { - (var number) -> String in + (number) -> String in + var number = number var output = "" - while number > 0 { + repeat { output = digitNames[number % 10]! + output number /= 10 - } + } while number > 0 return output } // strings 常量被推断为字符串类型数组,即 [String] // 其值为 ["OneSix", "FiveEight", "FiveOneZero"] ``` -`map`在数组中为每一个元素调用了闭包表达式。 -您不需要指定闭包的输入参数`number`的类型,因为可以通过要映射的数组类型进行推断。 +`map(_:)` 为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数 `number` 的类型,因为可以通过要映射的数组类型进行推断。 -闭包`number`参数被声明为一个变量参数(变量的具体描述请参看[常量参数和变量参数](./06_Functions.html#constant_and_variable_parameters)),因此可以在闭包函数体内对其进行修改。闭包表达式制定了返回类型为`String`,以表明存储映射值的新数组类型为`String`。 +在该例中,局部变量 `number` 的值由闭包中的 `number` 参数获得,因此可以在闭包函数体内对其进行修改,(闭包或者函数的参数总是常量),闭包表达式指定了返回类型为 `String`,以表明存储映射值的新数组类型为 `String`。 -闭包表达式在每次被调用的时候创建了一个字符串并返回。 -其使用求余运算符 (number % 10) 计算最后一位数字并利用`digitNames`字典获取所映射的字符串。 +闭包表达式在每次被调用的时候创建了一个叫做 `output` 的字符串并返回。其使用求余运算符(`number % 10`)计算最后一位数字并利用 `digitNames` 字典获取所映射的字符串。这个闭包能够用于创建任意正整数的字符串表示。 -> 注意: -> 字典`digitNames`下标后跟着一个叹号 (!),因为字典下标返回一个可选值 (optional value),表明即使该 key 不存在也不会查找失败。 -> 在上例中,它保证了`number % 10`可以总是作为一个`digitNames`字典的有效下标 key。 -> 因此叹号可以用于强制解析 (force-unwrap) 存储在可选下标项中的`String`类型值。 +> 注意 +> 字典 `digitNames` 下标后跟着一个叹号(`!`),因为字典下标返回一个可选值(optional value),表明该键不存在时会查找失败。在上例中,由于可以确定 `number % 10` 总是 `digitNames` 字典的有效下标,因此叹号可以用于强制解包 (force-unwrap) 存储在下标的可选类型的返回值中的`String`类型的值。 -从`digitNames`字典中获取的字符串被添加到输出的前部,逆序建立了一个字符串版本的数字。 -(在表达式`number % 10`中,如果number为16,则返回6,58返回8,510返回0)。 +从 `digitNames` 字典中获取的字符串被添加到 `output` 的*前部*,逆序建立了一个字符串版本的数字。(在表达式 `number % 10` 中,如果 `number` 为 `16`,则返回 `6`,`58` 返回 `8`,`510` 返回 `0`。) -`number`变量之后除以10。 -因为其是整数,在计算过程中未除尽部分被忽略。 -因此 16变成了1,58变成了5,510变成了51。 +`number` 变量之后除以 `10`。因为其是整数,在计算过程中未除尽部分被忽略。因此 `16` 变成了 `1`,`58` 变成了 `5`,`510` 变成了 `51`。 -整个过程重复进行,直到`number /= 10`为0,这时闭包会将字符串输出,而`map`函数则会将字符串添加到所映射的数组中。 +整个过程重复进行,直到 `number /= 10` 为 `0`,这时闭包会将字符串 `output` 返回,而 `map(_:)` 方法则会将字符串添加到映射数组中。 -上例中尾随闭包语法在函数后整洁封装了具体的闭包功能,而不再需要将整个闭包包裹在`map`函数的括号内。 +在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 `map(_:)` 方法的括号内。 -## 捕获值(Capturing Values) +## 值捕获(Capturing Values) +闭包可以在其被定义的上下文中*捕获*常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。 -闭包可以在其定义的上下文中捕获常量或变量。 -即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。 +Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。 -Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。 -嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。 - -下例为一个叫做`makeIncrementor`的函数,其包含了一个叫做`incrementor`嵌套函数。 -嵌套函数`incrementor`从上下文中捕获了两个值,`runningTotal`和`amount`。 -之后`makeIncrementor`将`incrementor`作为闭包返回。 -每次调用`incrementor`时,其会以`amount`作为增量增加`runningTotal`的值。 +举个例子,这有一个叫做 `makeIncrementor` 的函数,其包含了一个叫做 `incrementor` 的嵌套函数。嵌套函数 `incrementor()` 从上下文中捕获了两个值,`runningTotal` 和 `amount`。捕获这些值之后,`makeIncrementor` 将 `incrementor` 作为闭包返回。每次调用 `incrementor` 时,其会以 `amount` 作为增量增加 `runningTotal` 的值。 ```swift -func makeIncrementor(forIncrement amount: Int) -> () -> Int { +func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 - func incrementor() -> Int { + func incrementer() -> Int { runningTotal += amount return runningTotal } - return incrementor + return incrementer } ``` -`makeIncrementor`返回类型为`() -> Int`。 -这意味着其返回的是一个函数,而不是一个简单类型值。 -该函数在每次调用时不接受参数只返回一个`Int`类型的值。 -关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.html#function_types_as_return_types)。 +`makeIncrementor` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.html#function_types_as_return_types)。 -`makeIncrementor`函数定义了一个整型变量`runningTotal`(初始为0) 用来存储当前跑步总数。 -该值通过`incrementor`返回。 +`makeIncrementer(forIncrement:)` 函数定义了一个初始值为 `0` 的整型变量 `runningTotal`,用来存储当前总计数值。该值为 `incrementor` 的返回值。 -`makeIncrementor`有一个`Int`类型的参数,其外部命名为`forIncrement`, 内部命名为`amount`,表示每次`incrementor`被调用时`runningTotal`将要增加的量。 +`makeIncrementer(forIncrement:)` 有一个 `Int` 类型的参数,其外部参数名为 `forIncrement`,内部参数名为 `amount`,该参数表示每次 `incrementor` 被调用时 `runningTotal` 将要增加的量。`makeIncrementer` 函数还定义了一个嵌套函数 `incrementor`,用来执行实际的增加操作。该函数简单地使 `runningTotal` 增加 `amount`,并将其返回。 -`incrementor`函数用来执行实际的增加操作。 -该函数简单地使`runningTotal`增加`amount`,并将其返回。 - -如果我们单独看这个函数,会发现看上去不同寻常: +如果我们单独考虑嵌套函数 `incrementer()`,会发现它有些不同寻常: ```swift -func incrementor() -> Int { +func incrementer() -> Int { runningTotal += amount return runningTotal } ``` -`incrementer`函数并没有任何参数,但是在函数体内访问了`runningTotal`和`amount`变量。这是因为其通过捕获在包含它的函数体内已经存在的`runningTotal`和`amount`变量的引用(reference)而实现。捕捉了变量引用,保证了`runningTotal`和`amount`变量在调用完`makeIncrementer`后不会消失,并且保证了在下一次执行`incrementer`函数时,`runningTotal`可以继续增加。 +`incrementer()` 函数并没有任何参数,但是在函数体内访问了 `runningTotal` 和 `amount` 变量。这是因为它从外围函数捕获了 `runningTotal` 和 `amount` 变量的*引用*。捕获引用保证了 `runningTotal` 和 `amount` 变量在调用完 `makeIncrementer` 后不会消失,并且保证了在下一次执行 `incrementer` 函数时,`runningTotal` 依旧存在。 -> 注意: -> 为了优化,Swift可能会捕捉和保存一份对值的拷贝,如果这个值是不可变或是在闭包外的。 -> Swift同样负责被捕捉的所有变量的内存管理,包括释放不被需要的变量。 +> 注意 +> 为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。 +> Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。 -下面代码为一个使用`makeIncrementor`的例子: +下面是一个使用 `makeIncrementor` 的例子: ```swift let incrementByTen = makeIncrementor(forIncrement: 10) ``` -该例子定义了一个叫做`incrementByTen`的常量,该常量指向一个每次调用会加10的`incrementor`函数。 -调用这个函数多次可以得到以下结果: +该例子定义了一个叫做 `incrementByTen` 的常量,该常量指向一个每次调用会将其 `runningTotal` 变量增加 `10` 的 `incrementor` 函数。调用这个函数多次可以得到以下结果: ```swift incrementByTen() @@ -343,34 +306,151 @@ incrementByTen() // 返回的值为30 ``` -如果您创建了另一个`incrementor`,其会有一个属于自己的独立的`runningTotal`变量的引用。 -下面的例子中,`incrementBySevne`捕获了一个新的`runningTotal`变量,该变量和`incrementByTen`中捕获的变量没有任何联系: +如果你创建了另一个 `incrementor`,它会有属于自己的引用,指向一个全新、独立的 `runningTotal` 变量: ```swift let incrementBySeven = makeIncrementor(forIncrement: 7) incrementBySeven() // 返回的值为7 +``` + +再次调用原来的 `incrementByTen` 会继续增加它自己的 `runningTotal` 变量,该变量和 `incrementBySeven` 中捕获的变量没有任何联系: + +```swift incrementByTen() // 返回的值为40 ``` -> 注意: -> 如果您将闭包赋值给一个类实例的属性,并且该闭包通过指向该实例或其成员来捕获了该实例,您将创建一个在闭包和实例间的强引用环。 -> Swift 使用捕获列表来打破这种强引用环。更多信息,请参考 [闭包引起的循环强引用](./16_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)。 +> 注意 +> 如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考[闭包引起的循环强引用](./16_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)。 ## 闭包是引用类型(Closures Are Reference Types) -上面的例子中,`incrementBySeven`和`incrementByTen`是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。 -这是因为函数和闭包都是引用类型。 +上面的例子中,`incrementBySeven` 和 `incrementByTen` 都是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是*引用类型*。 -无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。 -上面的例子中,`incrementByTen`指向闭包的引用是一个常量,而并非闭包内容本身。 +无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的*引用*。上面的例子中,指向闭包的引用 `incrementByTen` 是一个常量,而并非闭包内容本身。 -这也意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包: +这也意味着如果你将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包: ```swift let alsoIncrementByTen = incrementByTen alsoIncrementByTen() // 返回的值为50 ``` + + +## 逃逸闭包(Escaping Closures) + +当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中*逃逸*。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 `@escaping`,用来指明这个闭包是允许“逃逸”出这个函数的。 + +一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如: + +```swift +var completionHandlers: [() -> Void] = [] +func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { + completionHandlers.append(completionHandler) +} +``` + +`someFunctionWithEscapingClosure(_:)` 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你不将这个参数标记为 `@escaping`,就会得到一个编译错误。 + +将一个闭包标记为 `@escaping` 意味着你必须在闭包中显式地引用 `self`。比如说,在下面的代码中,传递到 `someFunctionWithEscapingClosure(_:)` 中的闭包是一个逃逸闭包,这意味着它需要显式地引用 `self`。相对的,传递到 `someFunctionWithNonescapingClosure(_:)` 中的闭包是一个非逃逸闭包,这意味着它可以隐式引用 `self`。 + + +```swift +func someFunctionWithNonescapingClosure(closure: () -> Void) { + closure() +} + +class SomeClass { + var x = 10 + func doSomething() { + someFunctionWithEscapingClosure { self.x = 100 } + someFunctionWithNonescapingClosure { x = 200 } + } +} + +let instance = SomeClass() +instance.doSomething() +print(instance.x) +// 打印出 "200" + +completionHandlers.first?() +print(instance.x) +// 打印出 "100" +``` + + +## 自动闭包(Autoclosures) + +*自动闭包*是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。 + +我们经常会*调用*采用自动闭包的函数,但是很少去*实现*这样的函数。举个例子来说,`assert(condition:message:file:line:)` 函数接受自动闭包作为它的 `condition` 参数和 `message` 参数;它的 `condition` 参数仅会在 debug 模式下被求值,它的 `message` 参数仅当 `condition` 参数为 `false` 时被计算求值。 + +自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。下面的代码展示了闭包如何延时求值。 + +```swift +var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] +print(customersInLine.count) +// 打印出 "5" + +let customerProvider = { customersInLine.remove(at: 0) } +print(customersInLine.count) +// 打印出 "5" + +print("Now serving \(customerProvider())!") +// Prints "Now serving Chris!" +print(customersInLine.count) +// 打印出 "4" +``` + +尽管在闭包的代码中,`customersInLine` 的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。请注意,`customerProvider` 的类型不是 `String`,而是 `() -> String`,一个没有参数且返回值为 `String` 的函数。 + +将闭包作为参数传递给函数时,你能获得同样的延时求值行为。 + +```swift +// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] +func serve(customer customerProvider: () -> String) { + print("Now serving \(customerProvider())!") +} +serve(customer: { customersInLine.remove(at: 0) } ) +// 打印出 "Now serving Alex!" +``` + +上面的 `serve(customer:)` 函数接受一个返回顾客名字的显式的闭包。下面这个版本的 `serve(customer:)` 完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为 `@autoclosure` 来接收一个自动闭包。现在你可以将该函数当作接受 `String` 类型参数(而非闭包)的函数来调用。`customerProvider` 参数将自动转化为一个闭包,因为该参数被标记了 `@autoclosure` 特性。 + +```swift +// customersInLine is ["Ewa", "Barry", "Daniella"] +func serve(customer customerProvider: @autoclosure () -> String) { + print("Now serving \(customerProvider())!") +} +serve(customer: customersInLine.remove(at: 0)) +// 打印出 "Now serving Ewa!" +``` + +> 注意 +> 过度使用 `autoclosures` 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。 + +如果你想让一个自动闭包可以“逃逸”,则应该同时使用 `@autoclosure` 和 `@escaping` 属性。`@escaping` 属性的讲解见上面的[逃逸闭包](#escaping_closures)。 + +```swift +// customersInLine is ["Barry", "Daniella"] +var customerProviders: [() -> String] = [] +func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) { + customerProviders.append(customerProvider) +} +collectCustomerProviders(customersInLine.remove(at: 0)) +collectCustomerProviders(customersInLine.remove(at: 0)) + +print("Collected \(customerProviders.count) closures.") +// 打印出 "Collected 2 closures." +for customerProvider in customerProviders { + print("Now serving \(customerProvider())!") +} +// 打印出 "Now serving Barry!" +// 打印出 "Now serving Daniella!" +``` + +在上面的代码中,`collectCustomerProviders(_:)` 函数并没有调用传入的 `customerProvider` 闭包,而是将闭包追加到了 `customerProviders` 数组中。这个数组定义在函数作用域范围外,这意味着数组内的闭包能够在函数返回之后被调用。因此,`customerProvider` 参数必须允许“逃逸”出函数作用域。 + diff --git a/source/chapter2/08_Enumerations.md b/source/chapter2/08_Enumerations.md index 81565015..7943563e 100755 --- a/source/chapter2/08_Enumerations.md +++ b/source/chapter2/08_Enumerations.md @@ -8,23 +8,30 @@ > 2.0 > 翻译+校对:[futantan](https://github.com/futantan) +> 2.1 +> 翻译:[Channe](https://github.com/Channe) +> 校对:[shanks](http://codebuild.me), + +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-13 + 本页内容包含: - [枚举语法(Enumeration Syntax)](#enumeration_syntax) -- [匹配枚举值与`Swith`语句(Matching Enumeration Values with a Switch Statement)](#matching_enumeration_values_with_a_switch_statement) -- [相关值(Associated Values)](#associated_values) +- [使用 Switch 语句匹配枚举值(Matching Enumeration Values with a Switch Statement)](#matching_enumeration_values_with_a_switch_statement) +- [关联值(Associated Values)](#associated_values) - [原始值(Raw Values)](#raw_values) - [递归枚举(Recursive Enumerations)](#recursive_enumerations) -**枚举**定义了一个通用类型的一组相关值,使你可以在你的代码中以一种安全的方式来使用这些值。 +*枚举*为一组相关的值定义了一个共同的类型,使你可以在你的代码中以类型安全的方式来使用这些值。 -如果你熟悉 C 语言,你就会知道,在 C 语言中枚举将枚举名和一个整型值相对应。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为“原始”值),则该值的类型可以是字符串,字符,或是一个整型值或浮点数。 +如果你熟悉 C 语言,你会知道在 C 语言中,枚举会为一组整型值分配相关联的名称。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为“原始”值),则该值的类型可以是字符串,字符,或是一个整型值或浮点数。 -此外,枚举成员可以指定任何类型的相关值存储到枚举成员值中,就像其他语言中的联合体(unions)和变体(variants)。你可以定义一组通用的相关成员作为枚举的一部分,每一组都有不同的一组与它相关的适当类型的数值。 +此外,枚举成员可以指定任意类型的关联值存储到枚举成员中,就像其他语言中的联合体(unions)和变体(variants)。每一个枚举成员都可以有适当类型的关联值。 -在 Swift 中,枚举类型是一等公民(first-class)。它们采用了很多传统上只被类(class)所支持的特征,例如计算型属性(computed properties),用于提供关于枚举当前值的附加信息,实例方法(instance methods),用于提供和枚举所代表的值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始的实现基础上扩展它们的功能;可以遵守协议(protocols)来提供标准的功能。 +在 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)。 +欲了解更多相关信息,请参见[属性(Properties)](./10_Properties.html),[方法(Methods)](./11_Methods.html),[构造过程(Initialization)](./14_Initialization.html),[扩展(Extensions)](./21_Extensions.html)和[协议(Protocols)](./22_Protocols.html)。 ## 枚举语法 @@ -33,50 +40,50 @@ ```swift enum SomeEnumeration { - // 枚举定义放在这里 + // 枚举定义放在这里 } ``` -下面是指南针四个方向的例子: +下面是用枚举表示指南针四个方向的例子: ```swift enum CompassPoint { - case North - case South - case East - case West + case North + case South + case East + case West } ``` -枚举中定义的值(如 `North`,`South`,`East`和`West`)是这个枚举的**成员值**(或**成员**)。`case`关键词表示一行新的成员值将被定义。 +枚举中定义的值(如 `North`,`South`,`East`和`West`)是这个枚举的*成员值*(或*成员*)。你使用`case`关键字来定义一个新的枚举成员值。 -> 注意: -> 和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的`CompassPoint`例子中,`North`,`South`,`East`和`West`不会隐式地赋值为`0`,`1`,`2`和`3`。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的`CompassPoint`类型。 +> 注意 +> 与 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的`CompassPoint`例子中,`North`,`South`,`East`和`West`不会被隐式地赋值为`0`,`1`,`2`和`3`。相反,这些枚举成员本身就是完备的值,这些值的类型是已经明确定义好的`CompassPoint`类型。 多个成员值可以出现在同一行上,用逗号隔开: ```swift enum Planet { - case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune + case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune } ``` -每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如`CompassPoint`和`Planet`)必须以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解: +每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如`CompassPoint`和`Planet`)应该以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解: ```swift var directionToHead = CompassPoint.West ``` -`directionToHead`的类型可以在它被`CompassPoint`的一个可能值初始化时推断出来。一旦`directionToHead`被声明为一个`CompassPoint`,你可以使用一个缩写语法(.)将其设置为另一个`CompassPoint`的值: +`directionToHead`的类型可以在它被`CompassPoint`的某个值初始化时推断出来。一旦`directionToHead`被声明为`CompassPoint`类型,你可以使用更简短的点语法将其设置为另一个`CompassPoint`的值: ```swift directionToHead = .East ``` -当`directionToHead`的类型已知时,再次为其赋值可以省略枚举名。使用显式类型的枚举值可以让代码具有更好的可读性。 +当`directionToHead`的类型已知时,再次为其赋值可以省略枚举类型名。在使用具有显式类型的枚举值时,这种写法让代码具有更好的可读性。 -## 匹配枚举值和`Switch`语句 +## 使用 Switch 语句匹配枚举值 你可以使用`switch`语句匹配单个枚举值: @@ -99,11 +106,11 @@ switch directionToHead { “判断`directionToHead`的值。当它等于`.North`,打印`“Lots of planets have a north”`。当它等于`.South`,打印`“Watch out for penguins”`。” -等等以此类推。 +……以此类推。 -正如在[控制流(Control Flow)](./05_Control_Flow.html)中介绍的那样,在判断一个枚举类型的值时,`switch`语句必须穷举所有情况。如果忽略了`.West`这种情况,上面那段代码将无法通过编译,因为它没有考虑到`CompassPoint`的全部成员。强制性全部穷举的要求确保了枚举成员不会被意外遗漏。 +正如在[控制流(Control Flow)](./05_Control_Flow.html)中介绍的那样,在判断一个枚举类型的值时,`switch`语句必须穷举所有情况。如果忽略了`.West`这种情况,上面那段代码将无法通过编译,因为它没有考虑到`CompassPoint`的全部成员。强制穷举确保了枚举成员不会被意外遗漏。 -当不需要匹配每个枚举成员的时候,你可以提供一个默认`default`分支来涵盖所有未明确被提出的枚举成员: +当不需要匹配每个枚举成员的时候,你可以提供一个`default`分支来涵盖所有未明确处理的枚举成员: ```swift let somePlanet = Planet.Earth @@ -117,54 +124,54 @@ default: ``` -## 相关值(Associated Values) +## 关联值(Associated Values) -上一小节的例子演示了如何定义(分类)枚举的成员。你可以为`Planet.Earth`设置一个常量或者变量,并且在赋值之后查看这个值。不管怎样,如果有时候能够把其他类型的**相关值**和成员值一起存储起来会很有用。这能让你存储成员值之外的自定义信息,并且当你每次在代码中使用该成员时允许这个信息产生变化。 +上一小节的例子演示了如何定义和分类枚举的成员。你可以为`Planet.Earth`设置一个常量或者变量,并在赋值之后查看这个值。然而,有时候能够把其他类型的*关联值*和成员值一起存储起来会很有用。这能让你连同成员值一起存储额外的自定义信息,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。 -你可以定义 Swift 的枚举存储任何类型的相关值,如果需要的话,每个成员的数据类型可以是各不相同的。枚举的这种特性跟其他语言中的可辨识联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。 +你可以定义 Swift 枚举来存储任意类型的关联值,如果需要的话,每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。 -例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有 UPC-A 格式的一维条形码,它使用数字 0 到 9。每一个条形码都有一个代表“数字系统”的数字,该数字后接 5 个代表“生产代码”的数字,接下来是5位“产品代码”。最后一个数字是“检查”位,用来验证代码是否被正确扫描: +例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用`0`到`9`的数字的 UPC-A 格式的一维条形码。每一个条形码都有一个代表“数字系统”的数字,该数字后接五位代表“厂商代码”的数字,接下来是五位代表“产品代码”的数字。最后一个数字是“检查”位,用来验证代码是否被正确扫描: -其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO 8859-1 字符,并且可以编码一个最多拥有 2953 个字符的字符串: +其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO 8859-1 字符,并且可以编码一个最多拥有 2,953 个字符的字符串: -对于库存跟踪系统来说,能够把 UPC-A 码作为四个整型值的元组,和把 QR 码作为一个任何长度的字符串存储起来是方便的。 +这便于库存跟踪系统用包含四个整型值的元组存储 UPC-A 码,以及用任意长度的字符串储存 QR 码。 -在 Swift 中,使用如下方式定义两种商品条码的枚举: +在 Swift 中,使用如下方式定义表示两种商品条形码的枚举: ```swift enum Barcode { - case UPCA(Int, Int, Int, Int) - case QRCode(String) + case UPCA(Int, Int, Int, Int) + case QRCode(String) } ``` 以上代码可以这么理解: -“定义一个名为`Barcode`的枚举类型,它可以是`UPCA`的一个相关值(`Int`,`Int`,`Int`,`Int`),或者是`QRCode`的一个字符串类型(`String`)相关值。” +“定义一个名为`Barcode`的枚举类型,它的一个成员值是具有`(Int,Int,Int,Int)`类型关联值的`UPCA`,另一个成员值是具有`String`类型关联值的`QRCode`。” -这个定义不提供任何`Int`或`String`的实际值,它只是定义了,当`Barcode`常量和变量等于`Barcode.UPCA`或`Barcode.QRCode`时,相关值的类型。 +这个定义不提供任何`Int`或`String`类型的关联值,它只是定义了,当`Barcode`常量和变量等于`Barcode.UPCA`或`Barcode.QRCode`时,可以存储的关联值的类型。 -然后可以使用任何一种条码类型创建新的条码,如: +然后可以使用任意一种条形码类型创建新的条形码,例如: ```swift var productBarcode = Barcode.UPCA(8, 85909, 51226, 3) ``` -以上例子创建了一个名为`productBarcode`的变量,并且赋给它一个`Barcode.UPCA`的相关元组值`(8, 85909, 51226, 3)`。 +上面的例子创建了一个名为`productBarcode`的变量,并将`Barcode.UPCA`赋值给它,关联的元组值为`(8, 85909, 51226, 3)`。 -同一个商品可以被分配给一个不同类型的条形码,如: +同一个商品可以被分配一个不同类型的条形码,例如: ```swift productBarcode = .QRCode("ABCDEFGHIJKLMNOP") ``` -这时,原始的`Barcode.UPCA`和其整数值被新的`Barcode.QRCode`和其字符串值所替代。条形码的常量和变量可以存储一个`.UPCA`或者一个`.QRCode`(连同它的相关值),但是在任何指定时间只能存储其中之一。 +这时,原始的`Barcode.UPCA`和其整数关联值被新的`Barcode.QRCode`和其字符串关联值所替代。`Barcode`类型的常量和变量可以存储一个`.UPCA`或者一个`.QRCode`(连同它们的关联值),但是在同一时间只能存储这两个值中的一个。 -像以前那样,不同的条形码类型可以使用一个 switch 语句来检查,然而这次相关值可以被提取作为 switch 语句的一部分。你可以在`switch`的 case 分支代码中提取每个相关值作为一个常量(用`let`前缀)或者作为一个变量(用`var`前缀)来使用: +像先前那样,可以使用一个 switch 语句来检查不同的条形码类型。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在`switch`的 case 分支代码中提取每个关联值作为一个常量(用`let`前缀)或者作为一个变量(用`var`前缀)来使用: ```swift switch productBarcode { @@ -176,7 +183,7 @@ case .QRCode(let productCode): // 输出 "QR code: ABCDEFGHIJKLMNOP." ``` -如果一个枚举成员的所有相关值被提取为常量,或者它们全部被提取为变量,为了简洁,你可以只放置一个`var`或者`let`标注在成员名称前: +如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个`let`或者`var`: ```swift switch productBarcode { @@ -191,9 +198,9 @@ case let .QRCode(productCode): ## 原始值(Raw Values) -在[相关值](#raw_values)小节的条形码例子中演示了一个枚举的成员如何声明它们存储不同类型的相关值。作为相关值的另一种选择,枚举成员可以被默认值(称为原始值)赋值,其中这些原始值具有相同的类型。 +在[关联值](#associated_values)小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。 -这里是一个枚举成员存储 ASCII 码的例子: +这是一个使用 ASCII 码作为原始值的枚举: ```swift enum ASCIIControlCharacter: Character { @@ -203,21 +210,22 @@ enum ASCIIControlCharacter: Character { } ``` -在这里,`ASCIIControlCharacter`的枚举类型的原始值类型被定义为字符型`Character`,并被设置了一些比较常见的 ASCII 控制字符。字符值的描述请详见[字符串和字符](./03_Strings_and_Characters.html)部分。 +枚举类型`ASCIIControlCharacter`的原始值类型被定义为`Character`,并设置了一些比较常见的 ASCII 控制字符。`Character`的描述详见[字符串和字符](./03_Strings_and_Characters.html)部分。 -原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。 +原始值可以是字符串,字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。 ->注意: ->原始值和相关值是不相同的。当你开始在你的代码中定义枚举的时候原始值是被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终是相同的。相关值是当你在创建一个基于枚举成员的新常量或变量时才会被设置,并且每次当你这么做得时候,它的值可以是不同的。 +> 注意 +> 原始值和关联值是不同的。原始值是在定义枚举时被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终不变。关联值是创建一个基于枚举成员的常量或变量时才设置的值,枚举成员的关联值可以变化。 + ### 原始值的隐式赋值(Implicitly Assigned Raw Values) -在使用原始值为整数或者字符串类型的枚举时,不需要显式的为每一个成员赋值,这时,Swift将会自动为你赋值。 +在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,Swift 将会自动为你赋值。 -例如,当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。 +例如,当使用整数作为原始值时,隐式赋值的值依次递增`1`。如果第一个枚举成员没有设置原始值,其原始值将为`0`。 -下面的枚举是对之前`Planet`这个枚举的一个细化,利用原始整型值来表示每个 planet 在太阳系中的顺序: +下面的枚举是对之前`Planet`这个枚举的一个细化,利用整型的原始值来表示每个行星在太阳系中的顺序: ```swift enum Planet: Int { @@ -225,11 +233,11 @@ enum Planet: Int { } ``` -在上面的例子中,`Plant.Mercury`赋了初值`1`,`Planet.Venus`会拥有隐式赋值`2`,依次类推。 +在上面的例子中,`Plant.Mercury`的显式原始值为`1`,`Planet.Venus`的隐式原始值为`2`,依次类推。 -当使用字符串作为枚举类型的初值时,每个枚举成员的隐式初值则为该成员的名称。 +当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称。 -下面的例子是`CompassPoint`枚举类型的精简版,使用字符串作为初值类型,隐式初始化为各个方向的名称: +下面的例子是`CompassPoint`枚举的细化,使用字符串类型的原始值来表示各个方向的名称: ```swift enum CompassPoint: String { @@ -237,7 +245,7 @@ enum CompassPoint: String { } ``` -上面例子中,`CompassPoint.South`拥有隐式初值`South`,依次类推。 +上面例子中,`CompassPoint.South`拥有隐式原始值`South`,依次类推。 使用枚举成员的`rawValue`属性可以访问该枚举成员的原始值: @@ -249,23 +257,24 @@ let sunsetDirection = CompassPoint.West.rawValue // sunsetDirection 值为 "West" ``` -### 使用原始值初始化枚举变量(Initializing from a Raw Value) + +### 使用原始值初始化枚举实例(Initializing from a Raw Value) -如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法将原始值类型作为参数,返回值是枚举成员或`nil`。你可以使用这种初始化方法来创建一个新的枚举变量。 +如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做`rawValue`的参数,参数类型即为原始值类型,返回值则是枚举成员或`nil`。你可以使用这个初始化方法来创建一个新的枚举实例。 -这个例子通过原始值`7`从而创建枚举成员: +这个例子利用原始值`7`创建了枚举成员`Uranus`: ```swift let possiblePlanet = Planet(rawValue: 7) // possiblePlanet 类型为 Planet? 值为 Planet.Uranus ``` -然而,并非所有可能的`Int`值都可以找到一个匹配的行星。正因为如此,构造函数可以返回一个**可选**的枚举成员。在上面的例子中,`possiblePlanet`是`Planet?`类型,或“可选的`Planet`”。 +然而,并非所有`Int`值都可以找到一个匹配的行星。因此,原始值构造器总是返回一个*可选*的枚举成员。在上面的例子中,`possiblePlanet`是`Planet?`类型,或者说“可选的`Planet`”。 ->注意: ->原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见[可失败构造器](../chapter3/05_Declarations#failable_initializers) +> 注意 +> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见[可失败构造器](../chapter3/05_Declarations.html#failable_initializers) -如果你试图寻找一个位置为9的行星,通过参数为`rawValue`构造函数返回的可选`Planet`值将是`nil`: +如果你试图寻找一个位置为`9`的行星,通过原始值构造器返回的可选`Planet`值将是`nil`: ```swift let positionToFind = 9 @@ -282,19 +291,15 @@ if let somePlanet = Planet(rawValue: positionToFind) { // 输出 "There isn't a planet at position 9 ``` -这个范例使用可选绑定(optional binding),通过原始值`9`试图访问一个行星。`if let somePlanet = Planet(rawValue: 9)`语句获得一个可选`Planet`,如果可选`Planet`可以被获得,把`somePlanet`设置成该可选`Planet`的内容。在这个范例中,无法检索到位置为`9`的行星,所以`else`分支被执行。 +这个例子使用了可选绑定(optional binding),试图通过原始值`9`来访问一个行星。`if let somePlanet = Planet(rawValue: 9)`语句创建了一个可选`Planet`,如果可选`Planet`的值存在,就会赋值给`somePlanet`。在这个例子中,无法检索到位置为`9`的行星,所以`else`分支被执行。 ## 递归枚举(Recursive Enumerations) -在对操作符进行描述的时候,使用枚举类型来对数据建模很方便,因为需要考虑的情况固定可枚举。操作符可以将两个由数字组成的算数表达式连接起来,例如,将`5`连接成复杂一些的表达式`5+4`。 -算术表达式的一个重要特性是,表达式可以嵌套使用。例如,表达式`(5 + 4) * 2`乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也许要支持这种嵌套————这表示枚举类型需要支持递归。 - -`递归枚举(recursive enumeration)`是一种枚举类型,表示它的枚举中,有一个或多个枚举成员拥有该枚举的其他成员作为相关值。使用递归枚举时,编译器会插入一个中间层。你可以在枚举成员前加上`indirect`来表示这成员可递归。 - -例如,下面的例子中,枚举类型存储了简单的算数表达式: +*递归枚举(recursive enumeration)*是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上`indirect`来表示该成员可递归。 +例如,下面的例子中,枚举类型存储了简单的算术表达式: ```swift enum ArithmeticExpression { @@ -304,7 +309,7 @@ enum ArithmeticExpression { } ``` -你也可以在枚举类型开头加上`indirect`关键字来表示它的所有成员都是可递归的: +你也可以在枚举类型开头加上`indirect`关键字来表明它的所有成员都是可递归的: ```swift indirect enum ArithmeticExpression { @@ -314,9 +319,16 @@ indirect enum ArithmeticExpression { } ``` -上面定义的枚举类型可以存储三种算数表达式:纯数字、两个表达式的相加、两个表达式相乘。`Addition` 和 `Multiplication`成员的相关值也是算数表达式————这些相关值使得嵌套表达式成为可能。 +上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。枚举成员`Addition`和`Multiplication`的关联值也是算术表达式——这些关联值使得嵌套表达式成为可能。例如,表达式`(5 + 4) * 2`,乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也需要支持这种嵌套——这意味着枚举类型需要支持递归。下面的代码展示了使用`ArithmeticExpression `这个递归枚举创建表达式`(5 + 4) * 2` -递归函数可以很直观地使用具有递归性质的数据结构。例如,下面是一个计算算术表达式的函数: +```swift +let five = ArithmeticExpression.Number(5) +let four = ArithmeticExpression.Number(4) +let sum = ArithmeticExpression.Addition(five, four) +let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2)) +``` + +要操作具有递归性质的数据结构,使用递归函数是一种直截了当的方式。例如,下面是一个对算术表达式求值的函数: ```swift func evaluate(expression: ArithmeticExpression) -> Int { @@ -329,12 +341,7 @@ func evaluate(expression: ArithmeticExpression) -> Int { return evaluate(left) * evaluate(right) } } - -// 计算 (5 + 4) * 2 -let five = ArithmeticExpression.Number(5) -let four = ArithmeticExpression.Number(4) -let sum = ArithmeticExpression.Addition(five, four) -let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2)) + print(evaluate(product)) // 输出 "18" ``` diff --git a/source/chapter2/09_Classes_and_Structures.md b/source/chapter2/09_Classes_and_Structures.md index 74257884..ac454f4d 100755 --- a/source/chapter2/09_Classes_and_Structures.md +++ b/source/chapter2/09_Classes_and_Structures.md @@ -7,6 +7,12 @@ > 2.0 > 翻译+校对:[SkyJean](https://github.com/SkyJean) +> 2.1 +> 校对:[shanks](http://codebuild.me),2015-10-29 + +> 2.2 +> 校对:[SketchK](https://github.com/SketchK) 2016-05-13 + 本页包含内容: - [类和结构体对比](#comparing_classes_and_structures) @@ -16,12 +22,12 @@ - [字符串(String)、数组(Array)、和字典(Dictionary)类型的赋值与复制行为](#assignment_and_copy_behavior_for_strings_arrays_and_dictionaries) -类和结构体是人们构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(常量、变量)和添加方法,从而扩展类和结构体的功能。 +*类*和*结构体*是人们构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(常量、变量)和添加方法,从而扩展类和结构体的功能。 与其他编程语言所不同的是,Swift 并不要求你为自定义类和结构去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其它代码的外部接口。 -> 注意: -通常一个`类`的实例被称为`对象`。然而在Swift 中,类和结构体的关系要比在其他语言中更加的密切,本章中所讨论的大部分功能都可以用在类和结构体上。因此,我们会主要使用`实例`而不是`对象`。 +> 注意 +> 通常一个`类`的实例被称为`对象`。然而在 Swift 中,类和结构体的关系要比在其他语言中更加的密切,本章中所讨论的大部分功能都可以用在类和结构体上。因此,我们会主要使用`实例`而不是`对象`。 ###类和结构体对比 @@ -30,26 +36,27 @@ Swift 中类和结构体有很多共同点。共同处在于: * 定义属性用于存储值 * 定义方法用于提供功能 -* 定义附属脚本用于访问值 +* 定义下标操作使得可以通过下标语法来访问实例所包含的值 * 定义构造器用于生成初始化值 * 通过扩展以增加默认实现的功能 * 实现协议以提供某种标准功能 -更多信息请参见 [属性](./10_Properties.html),[方法](./11_Methods.html),[下标脚本](./12_Subscripts.html),[初始过程](./14_Initialization.html),[扩展](./20_Extensions.html),和[协议](./21_Protocols.html)。 +更多信息请参见[属性](./10_Properties.html),[方法](./11_Methods.html),[下标](./12_Subscripts.html),[构造过程](./14_Initialization.html),[扩展](./21_Extensions.html),和[协议](./22_Protocols.html)。 与结构体相比,类还有如下的附加功能: * 继承允许一个类继承另一个类的特征 * 类型转换允许在运行时检查和解释一个类实例的类型 -* 解构器允许一个类实例释放任何其所被分配的资源 +* 析构器允许一个类实例释放任何其所被分配的资源 * 引用计数允许对一个类的多次引用 -更多信息请参见[继承](./13_Inheritance.html),[类型转换](./20_Type_Casting.html),[析构过程](./15_Deinitialization),和[自动引用计数](./16_Automatic_Reference_Counting)。 +更多信息请参见[继承](./13_Inheritance.html),[类型转换](./19_Type_Casting.html),[析构过程](./15_Deinitialization.html),和[自动引用计数](./16_Automatic_Reference_Counting.html)。 -> 注意: -结构体总是通过被复制的方式在代码中传递,因此请不要使用引用计数。 +> 注意 +> 结构体总是通过被复制的方式在代码中传递,不使用引用计数。 -### 定义 + +### 定义语法 类和结构体有着类似的定义方式。我们通过关键字`class`和`struct`来分别表示类和结构体,并在一对大括号中定义它们的具体内容: @@ -62,8 +69,8 @@ struct SomeStructure { } ``` -> 注意: -在你每次定义一个新类或者结构体的时候,实际上你是有效地定义了一个新的 Swift 类型。因此请使用 `UpperCamelCase` 这种方式来命名(如 `SomeClass` 和`SomeStructure`等),以便符合标准Swift 类型的大写命名风格(如`String`,`Int`和`Bool`)。相反的,请使用`lowerCamelCase`这种方式为属性和方法命名(如`framerate`和`incrementCount`),以便和类区分。 +> 注意 +> 在你每次定义一个新类或者结构体的时候,实际上你是定义了一个新的 Swift 类型。因此请使用`UpperCamelCase`这种方式来命名(如`SomeClass`和`SomeStructure`等),以便符合标准 Swift 类型的大写命名风格(如`String`,`Int`和`Bool`)。相反的,请使用`lowerCamelCase`这种方式为属性和方法命名(如`framerate`和`incrementCount`),以便和类型名区分。 以下是定义结构体和定义类的示例: @@ -80,10 +87,11 @@ class VideoMode { } ``` -在上面的示例中我们定义了一个名为`Resolution`的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为`width`和`height`的存储属性。存储属性是捆绑和存储在类或结构体中的常量或变量。当这两个属性被初始化为整数`0`的时候,它们会被推断为`Int`类型。 +在上面的示例中我们定义了一个名为`Resolution`的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为`width`和`height`的存储属性。存储属性是被捆绑和存储在类或结构体中的常量或变量。当这两个属性被初始化为整数`0`的时候,它们会被推断为`Int`类型。 -在上面的示例中我们还定义了一个名为`VideoMode`的类,用来描述一个视频显示器的特定模式。这个类包含了四个储存属性变量。第一个是`分辨率`,它被初始化为一个新的`Resolution`结构体的实例,具有`Resolution`的属性类型。新`VideoMode`实例同时还会初始化其它三个属性,它们分别是,初始值为`false`(意为“非隔行扫描视频”)的`interlaced`,回放帧率初始值为`0.0`的`frameRate`和值为可选`String`的`name`。`name`属性会被自动赋予一个默认值`nil`,意为“没有`name`值”,因为它是一个可选类型。 +在上面的示例中我们还定义了一个名为`VideoMode`的类,用来描述一个视频显示器的特定模式。这个类包含了四个变量存储属性。第一个是`分辨率`,它被初始化为一个新的`Resolution`结构体的实例,属性类型被推断为`Resolution`。新`VideoMode`实例同时还会初始化其它三个属性,它们分别是,初始值为`false`的`interlaced`,初始值为`0.0`的`frameRate`,以及值为可选`String`的`name`。`name`属性会被自动赋予一个默认值`nil`,意为“没有`name`值”,因为它是一个可选类型。 + ### 类和结构体实例 `Resolution`结构体和`VideoMode`类的定义仅描述了什么是`Resolution`和`VideoMode`。它们并没有描述一个特定的分辨率(resolution)或者视频模式(video mode)。为了描述一个特定的分辨率或者视频模式,我们需要生成一个它们的实例。 @@ -97,9 +105,10 @@ let someVideoMode = VideoMode() 结构体和类都使用构造器语法来生成新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如`Resolution()`或`VideoMode()`。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。[构造过程](./14_Initialization.html)章节会对类和结构体的初始化进行更详细的讨论。 + ### 属性访问 -通过使用*点语法*(*dot syntax*),你可以访问实例中所含有的属性。其语法规则是,实例名后面紧跟属性名,两者通过点号(.)连接: +通过使用*点语法*(*dot syntax*),你可以访问实例的属性。其语法规则是,实例名后面紧跟属性名,两者通过点号(`.`)连接: ```swift print("The width of someResolution is \(someResolution.width)") @@ -115,7 +124,7 @@ print("The width of someVideoMode is \(someVideoMode.resolution.width)") // 输出 "The width of someVideoMode is 0" ``` -你也可以使用点语法为属性变量赋值: +你也可以使用点语法为变量属性赋值: ```swift someVideoMode.resolution.width = 1280 @@ -123,10 +132,11 @@ print("The width of someVideoMode is now \(someVideoMode.resolution.width)") // 输出 "The width of someVideoMode is now 1280" ``` -> 注意: -与 Objective-C 语言不同的是,Swift 允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了`someVideoMode`中`resolution`属性的`width`这个子属性,以上操作并不需要重新设置`resolution`属性。 +> 注意 +> 与 Objective-C 语言不同的是,Swift 允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了`someVideoMode`中`resolution`属性的`width`这个子属性,以上操作并不需要重新为整个`resolution`属性设置新值。 -### 结构体类型的成员逐一构造器(Memberwise Initializers for Structure Types) + +### 结构体类型的成员逐一构造器(Memberwise Initializers for Structure Types) 所有结构体都有一个自动生成的*成员逐一构造器*,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中: @@ -139,24 +149,24 @@ let vga = Resolution(width:640, height: 480) ## 结构体和枚举是值类型 -*值类型*被赋予给一个变量、常量或者本身被传递给一个函数的时候,实际上操作的是其的*拷贝*。 +*值类型*被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被*拷贝*。 -在之前的章节中,我们已经大量使用了值类型。实际上,在 Swift 中,所有的基本类型:整数(Integer)、浮点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,并且都是以结构体的形式在后台所实现。 +在之前的章节中,我们已经大量使用了值类型。实际上,在 Swift 中,所有的基本类型:整数(Integer)、浮点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,并且在底层都是以结构体的形式所实现。 在 Swift 中,所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型属性,在代码中传递的时候都会被复制。 -请看下面这个示例,其使用了前一个示例中`Resolution`结构体: +请看下面这个示例,其使用了前一个示例中的`Resolution`结构体: ```swift let hd = Resolution(width: 1920, height: 1080) var cinema = hd ``` -在以上示例中,声明了一个名为`hd`的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的`Resolution`实例。 +在以上示例中,声明了一个名为`hd`的常量,其值为一个初始化为全高清视频分辨率(`1920` 像素宽,`1080` 像素高)的`Resolution`实例。 -然后示例中又声明了一个名为`cinema`的变量,其值为之前声明的`hd`。因为`Resolution`是一个结构体,所以`cinema`的值其实是`hd`的一个拷贝副本,而不是`hd`本身。尽管`hd`和`cinema`有着相同的宽(width)和高(height)属性,但是在后台中,它们是两个完全不同的实例。 +然后示例中又声明了一个名为`cinema`的变量,并将`hd`赋值给它。因为`Resolution`是一个结构体,所以`cinema`的值其实是`hd`的一个拷贝副本,而不是`hd`本身。尽管`hd`和`cinema`有着相同的宽(width)和高(height),但是在幕后它们是两个完全不同的实例。 -下面,为了符合数码影院放映的需求(2048 像素宽,1080 像素高),`cinema`的`width`属性需要作如下修改: +下面,为了符合数码影院放映的需求(`2048` 像素宽,`1080` 像素高),`cinema`的`width`属性需要作如下修改: ```swift cinema.width = 2048 @@ -172,11 +182,11 @@ print("cinema is now \(cinema.width) pixels wide") 然而,初始的`hd`实例中`width`属性还是`1920`: ```swift -print("hd is still \(hd.width ) pixels wide") +print("hd is still \(hd.width) pixels wide") // 输出 "hd is still 1920 pixels wide" ``` -在将`hd`赋予给`cinema`的时候,实际上是将`hd`中所存储的`值(values)`进行拷贝,然后将拷贝的数据存储到新的`cinema`实例中。结果就是两个完全独立的实例碰巧包含有相同的数值。由于两者相互独立,因此将`cinema`的`width`修改为`2048`并不会影响`hd`中的`width`的值。 +在将`hd`赋予给`cinema`的时候,实际上是将`hd`中所存储的值进行拷贝,然后将拷贝的数据存储到新的`cinema`实例中。结果就是两个完全独立的实例碰巧包含有相同的数值。由于两者相互独立,因此将`cinema`的`width`修改为`2048`并不会影响`hd`中的`width`的值。 枚举也遵循相同的行为准则: @@ -193,12 +203,12 @@ if rememberedDirection == .West { // 输出 "The remembered direction is still .West" ``` -上例中`rememberedDirection`被赋予了`currentDirection`的值(value),实际上它被赋予的是值(value)的一个拷贝。赋值过程结束后再修改`currentDirection`的值并不影响`rememberedDirection`所储存的原始值(value)的拷贝。 +上例中`rememberedDirection`被赋予了`currentDirection`的值,实际上它被赋予的是值的一个拷贝。赋值过程结束后再修改`currentDirection`的值并不影响`rememberedDirection`所储存的原始值的拷贝。 ## 类是引用类型 -与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,操作的是引用,其并不是拷贝。因此,引用的是已存在的实例本身而不是其拷贝。 +与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。 请看下面这个示例,其使用了之前定义的`VideoMode`类: @@ -210,9 +220,9 @@ tenEighty.name = "1080i" tenEighty.frameRate = 25.0 ``` -以上示例中,声明了一个名为`tenEighty`的常量,其引用了一个`VideoMode`类的新实例。在之前的示例中,这个视频模式(video mode)被赋予了HD分辨率(1920*1080)的一个拷贝(`hd`)。同时设置为交错(interlaced),命名为`“1080i”`。最后,其帧率是`25.0`帧每秒。 +以上示例中,声明了一个名为`tenEighty`的常量,其引用了一个`VideoMode`类的新实例。在之前的示例中,这个视频模式(video mode)被赋予了HD分辨率(`1920`*`1080`)的一个拷贝(即`hd`实例)。同时设置为`interlaced`,命名为`“1080i”`。最后,其帧率是`25.0`帧每秒。 -然后,`tenEighty` 被赋予名为`alsoTenEighty`的新常量,同时对`alsoTenEighty`的帧率进行修改: +然后,`tenEighty`被赋予名为`alsoTenEighty`的新常量,同时对`alsoTenEighty`的帧率进行修改: ```swift let alsoTenEighty = tenEighty @@ -221,25 +231,26 @@ alsoTenEighty.frameRate = 30.0 因为类是引用类型,所以`tenEight`和`alsoTenEight`实际上引用的是相同的`VideoMode`实例。换句话说,它们是同一个实例的两种叫法。 -下面,通过查看`tenEighty`的`frameRate`属性,我们会发现它正确的显示了基本`VideoMode`实例的新帧率,其值为`30.0`: +下面,通过查看`tenEighty`的`frameRate`属性,我们会发现它正确的显示了所引用的`VideoMode`实例的新帧率,其值为`30.0`: ```swift print("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`参数,而不改变常量的值。 +需要注意的是`tenEighty`和`alsoTenEighty`被声明为常量而不是变量。然而你依然可以改变`tenEighty.frameRate`和`alsoTenEighty.frameRate`,因为`tenEighty`和`alsoTenEighty`这两个常量的值并未改变。它们并不“存储”这个`VideoMode`实例,而仅仅是对`VideoMode`实例的引用。所以,改变的是被引用的`VideoMode`的`frameRate`属性,而不是引用`VideoMode`的常量的值。 + ### 恒等运算符 -因为类是引用类型,有可能有多个常量和变量在后台同时引用某一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。) +因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。) 如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的,Swift 内建了两个恒等运算符: -* 等价于 ( === ) -* 不等价于 ( !== ) +* 等价于(`===`) +* 不等价于(`!==`) -以下是运用这两个运算符检测两个常量或者变量是否引用同一个实例: +运用这两个运算符检测两个常量或者变量是否引用同一个实例: ```swift if tenEighty === alsoTenEighty { @@ -248,30 +259,31 @@ if tenEighty === alsoTenEighty { //输出 "tenEighty and alsoTenEighty refer to the same Resolution instance." ``` -请注意```“等价于"```(用三个等号表示,===) 与```“等于"```(用两个等号表示,==)的不同: +请注意,“等价于”(用三个等号表示,`===`)与“等于”(用两个等号表示,`==`)的不同: * “等价于”表示两个类类型(class type)的常量或者变量引用同一个类实例。 -* “等于”表示两个实例的值“相等”或“相同”,判定时要遵照类设计者定义定义的评判标准,因此相比于“相等”,这是一种更加合适的叫法。 +* “等于”表示两个实例的值“相等”或“相同”,判定时要遵照设计者定义的评判标准,因此相对于“相等”来说,这是一种更加合适的叫法。 -当你在定义你的自定义类和结构体的时候,你有义务来决定判定两个实例“相等”的标准。在章节[等价操作符](./24_Advanced_Operators.html#equivalence_operators)中将会详细介绍实现自定义“等于”和“不等于”运算符的流程。 +当你在定义你的自定义类和结构体的时候,你有义务来决定判定两个实例“相等”的标准。在章节[等价操作符](./25_Advanced_Operators.html#equivalence_operators)中将会详细介绍实现自定义“等于”和“不等于”运算符的流程。 + ### 指针 -如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。一个 Swift 常量或者变量引用一个引用类型的实例与 C 语言中的指针类似,不同的是并不直接指向内存中的某个地址,而且也不要求你使用星号(*)来表明你在创建一个引用。Swift 中这些引用与其它的常量或变量的定义方式相同。 +如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。一个引用某个引用类型实例的 Swift 常量或者变量,与 C 语言中的指针类似,但是并不直接指向某个内存地址,也不要求你使用星号(`*`)来表明你在创建一个引用。Swift 中的这些引用与其它的常量或变量的定义方式相同。 ## 类和结构体的选择 在你的代码中,你可以使用类和结构体来定义你的自定义数据类型。 -然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者适用不同的任务。当你在考虑一个工程项目的数据构造和功能的时候,你需要决定每个数据构造是定义成类还是结构体。 +然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者适用不同的任务。当你在考虑一个工程项目的数据结构和功能的时候,你需要决定每个数据结构是定义成类还是结构体。 按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体: -* 结构体的主要目的是用来封装少量相关简单数据值。 -* 有理由预计一个结构体实例在赋值或传递时,封装的数据将会被拷贝而不是被引用。 -* 任何在结构体中储存的值类型属性,也将会被拷贝,而不是被引用。 -* 结构体不需要去继承另一个已存在类型的属性或者行为。 +* 该数据结构的主要目的是用来封装少量相关简单数据值。 +* 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。 +* 该数据结构中储存的值类型属性,也应该被拷贝,而不是被引用。 +* 该数据结构不需要去继承另一个既有类型的属性或者行为。 举例来说,以下情境中适合使用结构体: @@ -284,11 +296,9 @@ if tenEighty === alsoTenEighty { ## 字符串(String)、数组(Array)、和字典(Dictionary)类型的赋值与复制行为 -Swift 中`字符串(String)`,`数组(Array)`和`字典(Dictionary)`类型均以结构体的形式实现。这意味着String,Array,Dictionary类型数据被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会发生拷贝行为(值传递方式)。 +Swift 中,许多基本类型,诸如`String`,`Array`和`Dictionary`类型均以结构体的形式实现。这意味着被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会被拷贝。 -Objective-C中`字符串(NSString)`,`数组(NSArray)`和`字典(NSDictionary)`类型均以类的形式实现,这与Swfit中以值传递方式是不同的。NSString,NSArray,NSDictionary在发生赋值或者传入函数(或方法)时,不会发生值拷贝,而是传递已存在实例的引用。 +Objective-C 中`NSString`,`NSArray`和`NSDictionary`类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或方法时,不会发生值拷贝,而是传递现有实例的引用。 - -> 注意: -以上是对于字符串、数组、字典和其它值的`拷贝`的描述。 -在你的代码中,拷贝好像是确实是在有拷贝行为的地方产生过。然而,在 Swift 的后台中,只有确有必要,`实际(actual)`拷贝才会被执行。Swift 管理所有的值拷贝以确保性能最优化的性能,所以你也没有必要去避免赋值以保证最优性能。(实际赋值由系统管理优化) +> 注意 +> 以上是对字符串、数组、字典的“拷贝”行为的描述。在你的代码中,拷贝行为看起来似乎总会发生。然而,Swift 在幕后只在绝对必要时才执行实际的拷贝。Swift 管理所有的值拷贝以确保性能最优化,所以你没必要去回避赋值来保证性能最优化。 diff --git a/source/chapter2/10_Properties.md b/source/chapter2/10_Properties.md index fb806128..ee844243 100755 --- a/source/chapter2/10_Properties.md +++ b/source/chapter2/10_Properties.md @@ -1,427 +1,438 @@ -# 属性 (Properties) +# 属性 (Properties) --- -> 1.0 -> 翻译:[shinyzhu](https://github.com/shinyzhu) -> 校对:[pp-prog](https://github.com/pp-prog) [yangsiy](https://github.com/yangsiy) - -> 2.0 -> 翻译+校对:[yangsiy](https://github.com/yangsiy) - -本页包含内容: - -- [存储属性(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`定义)。 - -可以在定义存储属性的时候指定默认值,请参考[默认属性值](./14_Initialization.html#default_property_values)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[在初始化阶段修改常量存储属性](./14_Initialization.html#assigning_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.append("Some data") -manager.data.append("Some more data") -// DataImporter 实例的 importer 属性还没有被创建 -``` - -`DataManager`类包含一个名为`data`的存储属性,初始值是一个空的字符串(`String`)数组。虽然没有写出全部代码,`DataManager`类的目的是管理和提供对这个字符串数组的访问。 - -`DataManager`的一个功能是从文件导入数据。该功能由`DataImporter`类提供,`DataImporter`完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。 - -`DataManager`也可能不从文件中导入数据就完成了管理数据的功能。所以当`DataManager`的实例被创建时,没必要创建一个`DataImporter`的实例,更明智的是当第一次用到`DataImporter`的时候才去创建它。 - -由于使用了`lazy`,`importer`属性只有在第一次被访问的时候才被创建。比如访问它的属性`fileName`时: - -```swift -print(manager.importer.fileName) -// DataImporter 实例的 importer 属性现在被创建了 -// 输出 "data.txt” -``` - -> 注意: -> 如果一个被标记为`lazy`的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。 - - -### 存储属性和实例变量 - -如果您有过 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) -print("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`的计算属性。一个矩形的中心点可以从原点(`origin`)和尺寸(`size`)算出,所以不需要将它以显式声明的`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 的计算属性就是*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。 - -> 注意: -> 必须使用`var`关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。`let`关键字只用来声明常量属性,表示初始化后再也无法修改的值。 - -只读计算属性的声明可以去掉`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) -print("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`提供一个只读计算属性来让外部用户直接获取体积是很有用的。 - - -## 属性观察器 - -*属性观察器*监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。 - -可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。属性重载请参考[重载](./13_Inheritance.html#overriding)。 - -> 注意: -> 不需要为非重载的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。 - -可以为属性添加如下的一个或全部观察器: - -- `willSet`在新的值被设置之前调用 -- `didSet`在新的值被设置之后立即调用 - -`willSet`观察器会将新的属性值作为常量参数传入,在`willSet`的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称`newValue`表示。 - -类似地,`didSet`观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名`oldValue`。 - -> 注意: -> 父类的属性在子类的构造器中被赋值时,它在父类中的`willSet`和`didSet`观察器会被调用。 -> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)和[类的构造器代理规则](./14_Initialization.html#initializer_delegation_for_class_types)。 - -这里是一个`willSet`和`didSet`的实际例子,其中定义了一个名为`StepCounter`的类,用来统计当人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。 - -```swift -class StepCounter { - var totalSteps: Int = 0 { - willSet(newTotalSteps) { - print("About to set totalSteps to \(newTotalSteps)") - } - didSet { - if totalSteps > oldValue { - print("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 语言中的静态变量)。 - -值类型的存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算属性一样只能定义成变量属性。 - -> 注意: -> 跟实例的存储属性不同,必须给存储型类型属性指定默认值,因为类型本身无法在初始化过程中使用构造器给类型属性赋值。 -> 存储型类型属性是延迟初始化的(lazily initialized),它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行初始化一次,并且不需要对其使用 `lazy` 修饰符。 - - -###类型属性语法 - -在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为全局(*global*)静态变量定义的。但是在 Swift 编程语言中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。 - -使用关键字`static`来定义类型属性。在为类(class)定义计算型类型属性时,可以使用关键字`class`来支持子类对父类的实现进行重写。下面的例子演示了存储型和计算型类型属性的语法: - -```swift -struct SomeStructure { - static var storedTypeProperty = "Some value." - static var computedTypeProperty: Int { - return 1 - } -} -enum SomeEnumeration { - static var storedTypeProperty = "Some value." - static var computedTypeProperty: Int { - return 6 - } -} -class SomeClass { - static var storedTypeProperty = "Some value." - static var computedTypeProperty: Int { - return 27 - } - class var overrideableComputedTypeProperty: Int { - return 107 - } -} -``` - -> 注意: -> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟实例计算属性的语法类似。 - - -###获取和设置类型属性的值 - -跟实例的属性一样,类型属性的访问也是通过点运算符来进行。但是,类型属性是通过类型本身来获取和设置,而不是通过实例。比如: - -```swift -print(SomeStructure.storedTypeProperty) -// 输出 "Some value." -SomeStructure.storedTypeProperty = "Another value." -print(SomeStructure.storedTypeProperty) -// 输出 "Another value.” -print(SomeEnumeration.computedTypeProperty) -// 输出 "6" -print(SomeClass.computedTypeProperty) -// 输出 "27" -``` - -下面的例子定义了一个结构体,使用两个存储型类型属性来表示多个声道的声音电平值,每个声道有一个 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 -print(leftChannel.currentLevel) -// 输出 "7" -print(AudioChannel.maxInputLevelForAllChannels) -// 输出 "7" -``` - -如果试图将右声道的电平设置成 11,则会将右声道的`currentLevel`修正到最大值 10,同时`maxInputLevelForAllChannels`的值也会更新到 10: - -```swift -rightChannel.currentLevel = 11 -print(rightChannel.currentLevel) -// 输出 "10" -print(AudioChannel.maxInputLevelForAllChannels) -// 输出 "10" -``` +> 1.0 +> 翻译:[shinyzhu](https://github.com/shinyzhu) +> 校对:[pp-prog](https://github.com/pp-prog) [yangsiy](https://github.com/yangsiy) + + +> 2.0 +> 翻译+校对:[yangsiy](https://github.com/yangsiy) + + +> 2.1 +> 翻译:[buginux](https://github.com/buginux) +> 校对:[shanks](http://codebuild.me),2015-10-29 + + +> 2.2 +> 翻译:[saitjr](https://github.com/saitjr),2016-04-11,[SketchK](https://github.com/SketchK) 2016-05-13 + + + +本页包含内容: + +- [存储属性(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` 定义)。 + +可以在定义存储属性的时候指定默认值,请参考[默认构造器](./14_Initialization.html#default_initializers)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[构造过程中常量属性的修改](./14_Initialization.html#assigning_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.append("Some data") +manager.data.append("Some more data") +// DataImporter 实例的 importer 属性还没有被创建 +``` + +`DataManager` 类包含一个名为 `data` 的存储属性,初始值是一个空的字符串(`String`)数组。这里没有给出全部代码,只需知道 `DataManager` 类的目的是管理和提供对这个字符串数组的访问即可。 + +`DataManager` 的一个功能是从文件导入数据。该功能由 `DataImporter` 类提供,`DataImporter` 完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。 + +`DataManager` 管理数据时也可能不从文件中导入数据。所以当 `DataManager` 的实例被创建时,没必要创建一个 `DataImporter` 的实例,更明智的做法是第一次用到 `DataImporter` 的时候才去创建它。 + +由于使用了 `lazy` ,`importer` 属性只有在第一次被访问的时候才被创建。比如访问它的属性 `fileName` 时: + +```swift +print(manager.importer.fileName) +// DataImporter 实例的 importer 属性现在被创建了 +// 输出 "data.txt” +``` + +> 注意 +> 如果一个被标记为 `lazy` 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。 + + +### 存储属性和实例变量 + +如果您有过 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) +print("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` 的计算属性。一个矩形的中心点可以从原点(`origin`)和大小(`size`)算出,所以不需要将它以显式声明的 `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 的计算属性就是*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。 + +> 注意 +> 必须使用 `var` 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。`let` 关键字只用来声明常量属性,表示初始化后再也无法修改的值。 + +只读计算属性的声明可以去掉 `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) +print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)") +// 输出 "the volume of fourByFiveByTwo is 40.0" +``` + +这个例子定义了一个名为 `Cuboid` 的结构体,表示三维空间的立方体,包含 `width`、`height` 和 `depth` 属性。结构体还有一个名为 `volume` 的只读计算属性用来返回立方体的体积。为 `volume` 提供 setter 毫无意义,因为无法确定如何修改 `width`、`height` 和 `depth` 三者的值来匹配新的 `volume`。然而,`Cuboid` 提供一个只读计算属性来让外部用户直接获取体积是很有用的。 + + +## 属性观察器 + +*属性观察器*监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。 + +可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。 属性重写请参考[重写](./13_Inheritance.html#overriding)。 + + +可以为属性添加如下的一个或全部观察器: + +- `willSet` 在新的值被设置之前调用 +- `didSet` 在新的值被设置之后立即调用 + +`willSet` 观察器会将新的属性值作为常量参数传入,在 `willSet` 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 `newValue` 表示。 + +同样,`didSet` 观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名 `oldValue`。如果在 `didSet` 方法中再次对该属性赋值,那么新值会覆盖旧的值。 + +> 注意 +> 父类的属性在子类的构造器中被赋值时,它在父类中的 `willSet` 和 `didSet` 观察器会被调用,随后才会调用子类的观察器。在父类初始化方法调用之前,子类给属性赋值时,观察器不会被调用。 +> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)和[类的构造器代理规则](./14_Initialization.html#initializer_delegation_for_class_types)。 + +下面是一个 `willSet` 和 `didSet` 实际运用的例子,其中定义了一个名为 `StepCounter` 的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。 + +```swift +class StepCounter { + var totalSteps: Int = 0 { + willSet(newTotalSteps) { + print("About to set totalSteps to \(newTotalSteps)") + } + didSet { + if totalSteps > oldValue { + print("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` 表示旧值的参数名。 + +>注意 +> +>如果将属性通过 in-out 方式传入函数,`willSet` 和 `didSet` 也会调用。这是因为 in-out 参数采用了拷入拷出模式:即在函数内部使用的是参数的 copy,函数结束后,又对参数重新赋值。关于 in-out 参数详细的介绍,请参考[输入输出参数](../chapter3/05_Declarations.html#in-out_parameters) + + +##全局变量和局部变量 + +计算属性和属性观察器所描述的功能也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。 + +前面章节提到的全局或局部变量都属于存储型变量,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。 + +另外,在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。 + +> 注意 +> 全局的常量或变量都是延迟计算的,跟[延迟存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记`lazy`修饰符。 +> 局部范围的常量或变量从不延迟计算。 + + +##类型属性 + +实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。 + +也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是*类型属性*。 + +类型属性用于定义某个类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。 + +存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。 + +> 注意 +> 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。 +> 存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 `lazy` 修饰符。 + + +###类型属性语法 + +在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为全局(*global*)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。 + +使用关键字 `static` 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 `class` 来支持子类对父类的实现进行重写。下面的例子演示了存储型和计算型类型属性的语法: + +```swift +struct SomeStructure { + static var storedTypeProperty = "Some value." + static var computedTypeProperty: Int { + return 1 + } +} +enum SomeEnumeration { + static var storedTypeProperty = "Some value." + static var computedTypeProperty: Int { + return 6 + } +} +class SomeClass { + static var storedTypeProperty = "Some value." + static var computedTypeProperty: Int { + return 27 + } + class var overrideableComputedTypeProperty: Int { + return 107 + } +} +``` + +> 注意 +> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。 + + +###获取和设置类型属性的值 + +跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过类型本身来访问,而不是通过实例。比如: + +```swift +print(SomeStructure.storedTypeProperty) +// 输出 "Some value." +SomeStructure.storedTypeProperty = "Another value." +print(SomeStructure.storedTypeProperty) +// 输出 "Another value.” +print(SomeEnumeration.computedTypeProperty) +// 输出 "6" +print(SomeClass.computedTypeProperty) +// 输出 "27" +``` + +下面的例子定义了一个结构体,使用两个存储型类型属性来表示两个声道的音量,每个声道具有 `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` 值大于静态类型属性 `maxInputLevelForAllChannels` 的值,属性观察器就将新值保存在 `maxInputLevelForAllChannels` 中。 + +> 注意 +> 在第一个检查过程中,`didSet` 属性观察器将 `currentLevel` 设置成了不同的值,但这不会造成属性观察器被再次调用。 + +可以使用结构体 `AudioChannel` 创建两个声道 `leftChannel` 和 `rightChannel`,用以表示立体声系统的音量: + +```swift +var leftChannel = AudioChannel() +var rightChannel = AudioChannel() +``` + +如果将左声道的 `currentLevel` 设置成 `7`,类型属性 `maxInputLevelForAllChannels` 也会更新成 `7`: + +```swift +leftChannel.currentLevel = 7 +print(leftChannel.currentLevel) +// 输出 "7" +print(AudioChannel.maxInputLevelForAllChannels) +// 输出 "7" +``` + +如果试图将右声道的 `currentLevel` 设置成 `11`,它会被修正到最大值 `10`,同时 `maxInputLevelForAllChannels` 的值也会更新到 `10`: + +```swift +rightChannel.currentLevel = 11 +print(rightChannel.currentLevel) +// 输出 "10" +print(AudioChannel.maxInputLevelForAllChannels) +// 输出 "10" +``` diff --git a/source/chapter2/11_Methods.md b/source/chapter2/11_Methods.md index 79cbd413..437d533c 100755 --- a/source/chapter2/11_Methods.md +++ b/source/chapter2/11_Methods.md @@ -8,6 +8,12 @@ > 2.0 > 翻译+校对:[DianQK](https://github.com/DianQK) +> 2.1 +> 翻译:[DianQK](https://github.com/DianQK),[Realank](https://github.com/Realank) 校对:[shanks](http://codebuild.me),2016-01-18 +> +> 2.2 +> 校对:[SketchK](https://github.com/SketchK) 2016-05-13 + 本页包含内容: - [实例方法(Instance Methods)](#instance_methods) @@ -15,7 +21,7 @@ **方法**是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。 -结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活的在你创建的类型(类/结构体/枚举)上定义方法。 +结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。 ## 实例方法 (Instance Methods) @@ -28,16 +34,16 @@ ```swift class Counter { - var count = 0 - func increment() { - ++count - } - func incrementBy(amount: Int) { - count += amount - } - func reset() { - count = 0 - } + var count = 0 + func increment() { + count += 1 + } + func incrementBy(amount: Int) { + count += amount + } + func reset() { + count = 0 + } } ``` @@ -51,37 +57,37 @@ class Counter { 和调用属性一样,用点语法(dot syntax)调用实例方法: ```swift - let counter = Counter() - // 初始计数值是0 - counter.increment() - // 计数值现在是1 - counter.incrementBy(5) - // 计数值现在是6 - counter.reset() - // 计数值现在是0 +let counter = Counter() +// 初始计数值是0 +counter.increment() +// 计数值现在是1 +counter.incrementBy(5) +// 计数值现在是6 +counter.reset() +// 计数值现在是0 ``` -### 方法的局部参数名称和外部参数名称(Local and External Parameter Names for Methods) +### 方法的局部参数名称和外部参数名称 (Local and External Parameter Names for Methods) 函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见[指定外部参数名](./06_Functions.html#specifying_external_parameter_names)。方法参数也一样(因为方法就是函数,只是这个函数与某个类型相关联了)。 Swift 中的方法和 Objective-C 中的方法极其相似。像在 Objective-C 中一样,Swift 中方法的名称通常用一个介词指向方法的第一个参数,比如:`with`,`for`,`by`等等。前面的`Counter`类的例子中`incrementBy(_:)`方法就是这样的。介词的使用让方法在被调用时能像一个句子一样被解读。 -具体来说,Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相适应,与你在写 Objective-C 的方法时很相似。这个约定还让表达式方法在调用时不需要再限定参数名称。 +具体来说,Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相适应,与你在写 Objective-C 的方法时很相似。这个约定还让富于表达性的方法在调用时不需要再限定参数名称。 看看下面这个`Counter`的另一个版本(它定义了一个更复杂的`incrementBy(_:)`方法): ```swift class Counter { - var count: Int = 0 - func incrementBy(amount: Int, numberOfTimes: Int) { - count += amount * numberOfTimes - } + var count: Int = 0 + func incrementBy(amount: Int, numberOfTimes: Int) { + count += amount * numberOfTimes + } } ``` -`incrementBy(_:numverOfTimes:)`方法有两个参数: `amount`和`numberOfTimes`。默认情况下,Swift 只把`amount`当作一个局部名称,但是把`numberOfTimes`即看作局部名称又看作外部名称。下面调用这个方法: +`incrementBy(_:numberOfTimes:)`方法有两个参数: `amount`和`numberOfTimes`。默认情况下,Swift 只把`amount`当作一个局部名称,但是把`numberOfTimes`即看作局部名称又看作外部名称。下面调用这个方法: ```swift let counter = Counter() @@ -90,17 +96,16 @@ counter.incrementBy(5, numberOfTimes: 3) ``` 你不必为第一个参数值再定义一个外部变量名:因为从函数名`incrementBy(_numberOfTimes:)`已经能很清楚地看出它的作用。但是第二个参数,就要被一个外部参数名称所限定,以便在方法被调用时明确它的作用。 -这种默认行为使上面代码意味着:在 Swift 中定义方法使用了与 Objective-C 同样的语法风格,并且方法将以自然表达式的方式被调用。 +上面描述的这种默认行为意味着在 Swift 中,定义方法使用了与 Objective-C 同样的语法风格,并且方法将以自然且富于表达性的方式被调用。 ### 修改方法的外部参数名称(Modifying External Parameter Name Behavior for Methods) -有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你可以自己添加一个显式的外部名称作为第一个参数的前缀来把这个局部名称当作外部名称使用。 +有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你自己可以为第一个参数添加一个显式的外部名称。 相反,如果你不想为方法的第二个及后续的参数提供一个外部名称,可以通过使用下划线(`_`)作为该参数的显式外部名称,这样做将覆盖默认行为。 - ### self 属性(The self Property) @@ -110,11 +115,11 @@ counter.incrementBy(5, numberOfTimes: 3) ```swift func increment() { - self.count++ + self.count += 1 } ``` -实际上,你不必在你的代码里面经常写`self`。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确的写`self`,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的`Counter`中已经示范了:`Counter`中的三个实例方法中都使用的是`count`(而不是`self.count`)。 +实际上,你不必在你的代码里面经常写`self`。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写`self`,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的`Counter`中已经示范了:`Counter`中的三个实例方法中都使用的是`count`(而不是`self.count`)。 使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用`self`属性来区分参数名称和属性名称。 @@ -122,14 +127,14 @@ func increment() { ```swift struct Point { - var x = 0.0, y = 0.0 - func isToTheRightOfX(x: Double) -> Bool { - return self.x > x - } + 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) { - print("This point is to the right of the line where x == 1.0") + print("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 ``` @@ -139,19 +144,19 @@ if somePoint.isToTheRightOfX(1.0) { ### 在实例方法中修改值类型(Modifying Value Types from Within Instance Methods) -结构体和枚举是**值类型**。一般情况下,值类型的属性不能在它的实例方法中被修改。 +结构体和枚举是**值类型**。默认情况下,值类型的属性不能在它的实例方法中被修改。 -但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择`变异(mutating)`这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中。方法还可以给它隐含的`self`属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。 +但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择`可变(mutating)`行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的`self`属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。 -要使用`变异`方法, 将关键字`mutating` 放到方法的`func`关键字之前就可以了: +要使用`可变`方法,将关键字`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 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) @@ -159,47 +164,47 @@ print("The point is now at (\(somePoint.x), \(somePoint.y))") // 打印输出: "The point is now at (3.0, 4.0)" ``` -上面的`Point`结构体定义了一个变异方法(mutating method)`moveByX(_:y:)`用来移动点。`moveByX`方法在被调用时修改了这个点,而不是返回一个新的点。方法定义时加上`mutating`关键字,这才让方法可以修改值类型的属性。 +上面的`Point`结构体定义了一个可变方法 `moveByX(_:y:)` 来移动`Point`实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了`mutating`关键字,从而允许修改属性。 -注意:不能在结构体类型常量上调用变异方法,因为常量的属性不能被改变,即使想改变的是常量的变量属性也不行,详情参见[存储属性和实例变量](./10_Properties.html#global_and_local_variables): +注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见[常量结构体的存储属性](./10_Properties.html#stored_properties_of_constant_structure_instances): ```swift let fixedPoint = Point(x: 3.0, y: 3.0) fixedPoint.moveByX(2.0, y: 3.0) -// 这里将会抛出一个错误 +// 这里将会报告一个错误 ``` -### 在变异方法中给self赋值(Assigning to self Within a Mutating Method) +### 在可变方法中给 self 赋值(Assigning to self Within a Mutating Method) -变异方法能够赋给隐含属性`self`一个全新的实例。上面`Point`的例子可以用下面的方式改写: +可变方法能够赋给隐含属性`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) - } + var x = 0.0, y = 0.0 + mutating func moveByX(deltaX: Double, y deltaY: Double) { + self = Point(x: x + deltaX, y: y + deltaY) + } } ``` -新版的变异方法`moveByX(_:y:)`创建了一个新的结构(它的 x 和 y 的值都被设定为目标值)。调用这个版本的方法和调用上个版本的最终结果是一样的。 +新版的可变方法`moveByX(_:y:)`创建了一个新的结构体实例,它的 x 和 y 的值都被设定为目标值。调用这个版本的方法和调用上个版本的最终结果是一样的。 -枚举的变异方法可以把`self`设置为相同的枚举类型中不同的成员: +枚举的可变方法可以把`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 + 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() @@ -208,34 +213,34 @@ ovenLight.next() // ovenLight 现在等于 .Off ``` -上面的例子中定义了一个三态开关的枚举。每次调用`next`方法时,开关在不同的电源状态(`Off`,`Low`,`High`)之前循环切换。 +上面的例子中定义了一个三态开关的枚举。每次调用`next()`方法时,开关在不同的电源状态(`Off`,`Low`,`High`)之间循环切换。 ## 类型方法 (Type Methods) -实例方法是被类型的某个实例调用的方法。你也可以定义类型本身调用的方法,这种方法就叫做**类型方法**。声明结构体和枚举的类型方法,在方法的`func`关键字之前加上关键字`static`。类可能会用关键字`class`来允许子类重写父类的实现方法。 +实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做**类型方法**(Type Methods)。在方法的`func`关键字之前加上关键字`static`,来指定类型方法。类还可以用关键字`class`来允许子类重写父类的方法实现。 -> 注意: -> 在 Objective-C 里面,你只能为 Objective-C 的类定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法:每一个类型方法都被它所支持的类型显式包含。 +> 注意 +> 在 Objective-C 中,你只能为 Objective-C 的类类型(classes)定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。 -类型方法和实例方法一样用点语法调用。但是,你是在类型层面上调用这个方法,而不是在实例层面上调用。下面是如何在`SomeClass`类上调用类型方法的例子: +类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。下面是如何在`SomeClass`类上调用类型方法的例子: ```swift class SomeClass { - static func someTypeMethod() { - // type method implementation goes here - } + class func someTypeMethod() { + // type method implementation goes here + } } SomeClass.someTypeMethod() ``` -在类型方法的方法体(body)中,`self`指向这个类型本身,而不是类型的某个实例。对于结构体和枚举来说,这意味着你可以用`self`来消除静态属性和静态方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。 +在类型方法的方法体(body)中,`self`指向这个类型本身,而不是类型的某个实例。这意味着你可以用`self`来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。 -一般来说,任何未限定的方法和属性名称,将会来自于本类中另外的类型级别的方法和属性。一个类型方法可以调用本类中另一个类型方法的名称,而无需在方法名称前面加上类型名称的前缀。同样,结构体和枚举的类型方法也能够直接通过静态属性的名称访问静态属性,而不需要类型名称前缀。 +一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前面加上类型名称。类似地,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称。 下面的例子定义了一个名为`LevelTracker`结构体。它监测玩家的游戏发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也可以存储多个玩家在同一设备上的游戏信息。 -游戏初始时,所有的游戏等级(除了等级 1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的所有玩家解锁。`LevelTracker`结构体用静态属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。 +游戏初始时,所有的游戏等级(除了等级 1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的所有玩家解锁。`LevelTracker`结构体用类型属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。 ```swift struct LevelTracker { @@ -258,11 +263,11 @@ struct LevelTracker { } ``` -`LevelTracker`监测玩家的已解锁的最高等级。这个值被存储在静态属性`highestUnlockedLevel`中。 +`LevelTracker`监测玩家已解锁的最高等级。这个值被存储在类型属性`highestUnlockedLevel`中。 -`LevelTracker`还定义了两个类型方法与`highestUnlockedLevel`配合工作。第一个类型方法是`unlockLevel`:一旦新等级被解锁,它会更新`highestUnlockedLevel`的值。第二个类型方法是`levelIsUnlocked`:如果某个给定的等级已经被解锁,它将返回`true`。(注意:尽管我们没有使用类似`LevelTracker.highestUnlockedLevel`的写法,这个类型方法还是能够访问静态属性`highestUnlockedLevel`) +`LevelTracker`还定义了两个类型方法与`highestUnlockedLevel`配合工作。第一个类型方法是`unlockLevel`,一旦新等级被解锁,它会更新`highestUnlockedLevel`的值。第二个类型方法是`levelIsUnlocked`,如果某个给定的等级已经被解锁,它将返回`true`。(注意,尽管我们没有使用类似`LevelTracker.highestUnlockedLevel`的写法,这个类型方法还是能够访问类型属性`highestUnlockedLevel`) -除了静态属性和类型方法,`LevelTracker`还监测每个玩家的进度。它用实例属性`currentLevel`来监测玩家当前的等级。 +除了类型属性和类型方法,`LevelTracker`还监测每个玩家的进度。它用实例属性`currentLevel`来监测每个玩家当前的等级。 为了便于管理`currentLevel`属性,`LevelTracker`定义了实例方法`advanceToLevel`。这个方法会在更新`currentLevel`之前检查所请求的新等级是否已经解锁。`advanceToLevel`方法返回布尔值以指示是否能够设置`currentLevel`。 @@ -270,19 +275,19 @@ struct 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 - } + 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`类创建一个新的`LevelTracker`实例来监测这个用户的进度。它提供了`completedLevel`方法,一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了`advanceToLevel`返回的布尔值,因为之前调用`LevelTracker.unlockLevel`时就知道了这个等级已经被解锁了)。 你还可以为一个新的玩家创建一个`Player`的实例,然后看这个玩家完成等级一时发生了什么: @@ -293,14 +298,14 @@ print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)") // 打印输出:highest unlocked level is now 2 ``` -如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么这次设置玩家当前等级的尝试将会失败: +如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么试图设置玩家当前等级将会失败: ```swift player = Player(name: "Beto") if player.tracker.advanceToLevel(6) { - print("player is now on level 6") + print("player is now on level 6") } else { - print("level 6 has not yet been unlocked") + print("level 6 has not yet been unlocked") } // 打印输出:level 6 has not yet been unlocked ``` diff --git a/source/chapter2/12_Subscripts.md b/source/chapter2/12_Subscripts.md index a09466c4..8c48dfa6 100755 --- a/source/chapter2/12_Subscripts.md +++ b/source/chapter2/12_Subscripts.md @@ -1,4 +1,4 @@ -# 下标脚本(Subscripts) +# 下标(Subscripts) ----------------- > 1.0 @@ -6,104 +6,109 @@ > 校对:[zq54zquan](https://github.com/zq54zquan) > 2.0 -> 翻译+校对:[shanksyang](https://github.com/shanksyang) +> 翻译+校对:[shanks](http://codebuild.me) + +> 2.1 +> 翻译+校对:[shanks](http://codebuild.me),[Realank](https://github.com/Realank) + +> 2.2 +> 校对:[SketchK](https://github.com/SketchK) 2016-05-13 + 本页包含内容: -- [下标脚本语法](#subscript_syntax) -- [下标脚本用法](#subscript_usage) -- [下标脚本选项](#subscript_options) +- [下标语法](#subscript_syntax) +- [下标用法](#subscript_usage) +- [下标选项](#subscript_options) -*下标脚本* 可以定义在类(Class)、结构体(structure)和枚举(enumeration)这些目标中,可以认为是访问集合(collection),列表(list)或序列(sequence的快捷方式,使用下标脚本的索引设置和获取值,不需要再调用实例的特定的赋值和访问方法。举例来说,用下标脚本访问一个数组(Array)实例中的元素可以这样写 `someArray[index]` ,访问字典(Dictionary)实例中的元素可以这样写 `someDictionary[key]`。 +*下标* (subscripts)可以定义在类(class)、结构体(structure)和枚举(enumeration)中,是访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个`Array`实例中的元素可以写作`someArray[index]`,访问`Dictionary`实例中的元素可以写作`someDictionary[key]`。 -对于同一个目标可以定义多个下标脚本,通过索引值类型的不同来进行重载,下标脚本不限于单个纬度,你可以定义多个入参的下标脚本满足自定义类型的需求。 - -> 译者:这里附属脚本重载在本小节中原文并没有任何演示 +一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。 -## 下标脚本语法 +## 下标语法 -下标脚本允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。语法类似于实例方法和计算型属性的混合。与定义实例方法类似,定义下标脚本使用`subscript`关键字,显式声明入参(一个或多个)和返回类型。与实例方法不同的是下标脚本可以设定为读写或只读。这种方式又有点像计算型属性的getter和setter: +下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存取。语法类似于实例方法语法和计算型属性语法的混合。与定义实例方法类似,定义下标使用`subscript`关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性: ```swift subscript(index: Int) -> Int { get { - // 返回与入参匹配的Int类型的值 + // 返回一个适当的 Int 类型的值 } set(newValue) { - // 执行赋值操作 + // 执行适当的赋值操作 } } ``` -`newValue`的类型必须和下标脚本定义的返回类型相同。与计算型属性相同的是set的入参声明`newValue`就算不写,在set代码块中依然可以使用默认的`newValue`这个变量来访问新赋的值。 +`newValue`的类型和下标的返回类型相同。如同计算型属性,可以不指定 setter 的参数(`newValue`)。如果不指定参数,setter 会提供一个名为`newValue`的默认参数。 -与只读计算型属性一样,可以直接将原本应该写在`get`代码块中的代码写在`subscript`中: +如同只读计算型属性,可以省略只读下标的`get`关键字: ```swift subscript(index: Int) -> Int { - // 返回与入参匹配的Int类型的值 + // 返回一个适当的 Int 类型的值 } ``` -下面代码演示了一个在`TimesTable`结构体中使用只读下标脚本的用法,该结构体用来展示传入整数的*n*倍。 +下面代码演示了只读下标的实现,这里定义了一个`TimesTable`结构体,用来表示传入整数的乘法表: ```swift struct TimesTable { let multiplier: Int subscript(index: Int) -> Int { - return multiplier * index + return multiplier * index } } let threeTimesTable = TimesTable(multiplier: 3) -print("3的6倍是\(threeTimesTable[6])") -// 输出 "3的6倍是18" +print("six times three is \(threeTimesTable[6])") +// 输出 "six times three is 18" ``` -在上例中,通过`TimesTable`结构体创建了一个用来表示索引值三倍的实例。数值`3`作为结构体`构造函数`入参初始化实例成员`multiplier`。 +在上例中,创建了一个`TimesTable`实例,用来表示整数`3`的乘法表。数值`3`被传递给结构体的构造函数,作为实例成员`multiplier`的值。 -你可以通过下标脚本来得到结果,比如`threeTimesTable[6]`。这条语句访问了`threeTimesTable`的第六个元素,返回`6`的`3`倍即`18`。 +你可以通过下标访问`threeTimesTable`实例,例如上面演示的`threeTimesTable[6]`。这条语句查询了`3`的乘法表中的第六个元素,返回`3`的`6`倍即`18`。 ->注意: -> `TimesTable`例子是基于一个固定的数学公式。它并不适合对`threeTimesTable[someIndex]`进行赋值操作,这也是为什么附属脚本只定义为只读的原因。 +> 注意 +> `TimesTable`例子基于一个固定的数学公式,对`threeTimesTable[someIndex]`进行赋值操作并不合适,因此下标定义为只读的。 -## 下标脚本用法 +## 下标用法 -根据使用场景不同下标脚本也具有不同的含义。通常下标脚本是用来访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以在你自己特定的类或结构体中自由的实现下标脚本来提供合适的功能。 +下标的确切含义取决于使用场景。下标通常作为访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以针对自己特定的类或结构体的功能来自由地以最恰当的方式实现下标。 -例如,Swift 的字典(Dictionary)实现了通过下标脚本来对其实例中存放的值进行存取操作。在下标脚本中使用和字典索引相同类型的值,并且把一个字典值类型的值赋值给这个下标脚本来为字典设值: +例如,Swift 的`Dictionary`类型实现下标用于对其实例中储存的值进行存取操作。为字典设值时,在下标中使用和字典的键类型相同的键,并把一个和字典的值类型相同的值赋给这个下标: ```swift var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] numberOfLegs["bird"] = 2 ``` -上例定义一个名为`numberOfLegs`的变量并用一个字典字面量初始化出了包含三对键值的字典实例。`numberOfLegs`的字典存放值类型推断为`[String:Int]`。字典实例创建完成之后通过下标脚本的方式将整型值`2`赋值到字典实例的索引为`bird`的位置中。 +上例定义一个名为`numberOfLegs`的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs`字典的类型被推断为`[String: Int]`。字典创建完成后,该例子通过下标将`String`类型的键`bird`和`Int`类型的值`2`添加到字典中。 -更多关于字典(Dictionary)下标脚本的信息请参考[读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary) +更多关于`Dictionary`下标的信息请参考[读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary) -> 注意: -> Swift 中字典的附属脚本实现中,在`get`部分返回值是`Int?`,上例中的`numberOfLegs`字典通过附属脚本返回的是一个`Int?`或者说“可选的int”,不是每个字典的索引都能得到一个整型值,对于没有设过值的索引的访问返回的结果就是`nil`;同样想要从字典实例中删除某个索引下的值也只需要给这个索引赋值为`nil`即可。 +> 注意 +> Swift 的`Dictionary`类型的下标接受并返回可选类型的值。上例中的`numberOfLegs`字典通过下标返回的是一个`Int?`或者说“可选的int”。`Dictionary`类型之所以如此实现下标,是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式,只需将键对应的值赋值为`nil`即可。 -## 下标脚本选项 +## 下标选项 -下标脚本允许任意数量的入参索引,并且每个入参类型也没有限制。下标脚本的返回值也可以是任何类型。下标脚本可以使用变量参数和可变参数,但使用写入读出(in-out)参数或给参数设置默认值都是不允许的。 +下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型。下标可以使用变量参数和可变参数,但不能使用输入输出参数,也不能给参数设置默认值。 -一个类或结构体可以根据自身需要提供多个下标脚本实现,在定义下标脚本时通过入参的类型进行区分,使用下标脚本时会自动匹配合适的下标脚本实现运行,这就是*下标脚本的重载*。 +一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适的下标,这就是*下标的重载*。 -一个下标脚本入参是最常见的情况,但只要有合适的场景也可以定义多个下标脚本入参。如下例定义了一个`Matrix`结构体,将呈现一个`Double`类型的二维矩阵。`Matrix`结构体的下标脚本需要两个整型参数: +虽然接受单一入参的下标是最常见的,但也可以根据情况定义接受多个入参的下标。例如下例定义了一个`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) + 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 @@ -121,7 +126,7 @@ struct Matrix { } ``` -`Matrix`提供了一个两个入参的构造方法,入参分别是`rows`和`columns`,创建了一个足够容纳`rows * columns`个数的`Double`类型数组。通过传入数组长度和初始值0.0到数组的一个构造器,将`Matrix`中每个元素初始值0.0。关于数组的构造方法和析构方法请参考[创建一个空数组](./04_Collection_Types.html#creating_an_empty_array)。 +`Matrix`提供了一个接受两个入参的构造方法,入参分别是`rows`和`columns`,创建了一个足够容纳`rows * columns`个`Double`类型的值的数组。通过传入数组长度和初始值`0.0`到数组的构造器,将矩阵中每个位置的值初始化为`0.0`。关于数组的这种构造方法请参考[创建一个空数组](./04_Collection_Types.html#creating_an_empty_array)。 你可以通过传入合适的`row`和`column`的数量来构造一个新的`Matrix`实例: @@ -129,32 +134,22 @@ struct Matrix { var matrix = Matrix(rows: 2, columns: 2) ``` -上例中创建了一个新的两行两列的`Matrix`实例。在阅读顺序从左上到右下的`Matrix`实例中的数组实例`grid`是矩阵二维数组的扁平化存储: +上例中创建了一个`Matrix`实例来表示两行两列的矩阵。该`Matrix`实例的`grid`数组按照从左上到右下的阅读顺序将矩阵扁平化存储: -```swift -// 示意图 -grid = [0.0, 0.0, 0.0, 0.0] +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/subscriptMatrix01_2x.png) - col0 col1 -row0 [0.0, 0.0, -row1 0.0, 0.0] -``` - -将值赋给带有`row`和`column`下标脚本的`matrix`实例表达式可以完成赋值操作,下标脚本入参使用逗号分割 +将`row`和`column`的值传入下标来为矩阵设值,下标的入参使用逗号分隔: ```swift matrix[0, 1] = 1.5 matrix[1, 0] = 3.2 ``` -上面两条语句分别`让matrix`的右上值为 1.5,坐下值为 3.2: +上面两条语句分别调用下标的 setter 将矩阵右上角位置(即`row`为`0`、`column`为`1`的位置)的值设置为`1.5`,将矩阵左下角位置(即`row`为`1`、`column`为`0`的位置)的值设置为`3.2`: -```swift -[0.0, 1.5, - 3.2, 0.0] -``` +![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/subscriptMatrix02_2x.png) -`Matrix`下标脚本的`getter`和`setter`中同时调用了下标脚本入参的`row`和`column`是否有效的判断。为了方便进行断言,`Matrix`包含了一个名为`indexIsValidForRow(_:column:)`的成员方法,用来确认入参的`row`或`column`值是否会造成数组越界: +`Matrix`下标的 getter 和 setter 中都含有断言,用来检查下标入参`row`和`column`的值是否有效。为了方便进行断言,`Matrix`包含了一个名为`indexIsValidForRow(_:column:)`的便利方法,用来检查入参`row`和`column`的值是否在矩阵范围内: ```swift func indexIsValidForRow(row: Int, column: Int) -> Bool { @@ -162,9 +157,9 @@ func indexIsValidForRow(row: Int, column: Int) -> Bool { } ``` -断言在下标脚本越界时触发: +断言在下标越界时触发: ```swift let someValue = matrix[2, 2] -// 断言将会触发,因为 [2, 2] 已经超过了matrix的最大长度 +// 断言将会触发,因为 [2, 2] 已经超过了 matrix 的范围 ``` diff --git a/source/chapter2/13_Inheritance.md b/source/chapter2/13_Inheritance.md index b4d94657..64028c3d 100755 --- a/source/chapter2/13_Inheritance.md +++ b/source/chapter2/13_Inheritance.md @@ -1,35 +1,38 @@ # 继承(Inheritance) -------------------- - +------------------- + > 1.0 > 翻译:[Hawstein](https://github.com/Hawstein) > 校对:[menlongsheng](https://github.com/menlongsheng) -> 2.0 -> 翻译+校对:[shanksyang](https://github.com/shanksyang) +> 2.0,2.1 +> 翻译+校对:[shanks](http://codebuild.me) +> +> 2.2 +> 校对:[SketchK](https://github.com/SketchK) 2016-05-13 本页包含内容: -- [定义一个基类(Base class)](#defining_a_base_class) +- [定义一个基类(Defining a Base Class)](#defining_a_base_class) - [子类生成(Subclassing)](#subclassing) - [重写(Overriding)](#overriding) -- [防止重写](#preventing_overrides) +- [防止重写(Preventing Overrides)](#preventing_overrides) 一个类可以*继承(inherit)*另一个类的方法(methods),属性(properties)和其它特性。当一个类继承其它类时,继承类叫*子类(subclass)*,被继承类叫*超类(或父类,superclass)*。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。 -在 Swift 中,类可以调用和访问超类的方法,属性和下标脚本(subscripts),并且可以重写(override)这些方法,属性和下标脚本来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。 +在 Swift 中,类可以调用和访问超类的方法,属性和下标(subscripts),并且可以重写(override)这些方法,属性和下标来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。 可以为类中继承来的属性添加属性观察器(property observers),这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性(stored property)还是计算型属性(computed property)。 -## 定义一个基类(Base class) +## 定义一个基类(Defining a Base Class) -不继承于其它类的类,称之为*基类(base calss)*。 +不继承于其它类的类,称之为*基类(base class)*。 -> 注意: +> 注意 Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。 -下面的例子定义了一个叫`Vehicle`的基类。这个基类声明了一个名为`currentSpeed `,默认值是0.0的存储属性(属性类型推断为`Double `)。`currentSpeed `属性的值被一个`String` 类型的只读计算型属性`description`使用,用来创建车辆的描述。 +下面的例子定义了一个叫`Vehicle`的基类。这个基类声明了一个名为`currentSpeed `,默认值是`0.0`的存储属性(属性类型推断为`Double`)。`currentSpeed`属性的值被一个`String`类型的只读计算型属性`description`使用,用来创建车辆的描述。 `Vehicle`基类也定义了一个名为`makeNoise`的方法。这个方法实际上不为`Vehicle`实例做任何事,但之后将会被`Vehicle`的子类定制: @@ -45,34 +48,35 @@ class Vehicle { } ``` -您可以用初始化语法创建一个`Vehicle `的新实例,即类名后面跟一个空括号: +您可以用初始化语法创建一个`Vehicle`的新实例,即类名后面跟一个空括号: + ```swift let someVehicle = Vehicle() ``` -现在已经创建了一个`Vehicle`的新实例,你可以访问它的`description`属性来打印车辆的当前速度。 +现在已经创建了一个`Vehicle`的新实例,你可以访问它的`description`属性来打印车辆的当前速度: ```swift print("Vehicle: \(someVehicle.description)") // Vehicle: traveling at 0.0 miles per hour ``` -`Vehicle`类定义了一个通用特性的车辆类,实际上没什么用处。为了让它变得更加有用,需要改进它能够描述一个更加具体的车辆类。 +`Vehicle`类定义了一个通用特性的车辆类,实际上没什么用处。为了让它变得更加有用,需要完善它从而能够描述一个更加具体类型的车辆。 ## 子类生成(Subclassing) -*子类生成(Subclassing)*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以优化或改变它。你还可以为子类添加新的特性。 +*子类生成(Subclassing)*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以进一步完善。你还可以为子类添加新的特性。 为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔: ```swift class SomeClass: SomeSuperclass { - // 类的定义 + // 这里是子类的定义 } ``` -下一个例子,定义一个叫`Bicycle`的子类,继承成父类`Vehicle` +下一个例子,定义一个叫`Bicycle`的子类,继承成父类`Vehicle`: ```swift class Bicycle: Vehicle { @@ -80,18 +84,18 @@ class Bicycle: Vehicle { } ``` -新的`Bicycle`类自动获得`Vehicle`类的所有特性,比如 `currentSpeed `和`description`属性,还有它的`makeNoise`方法。 +新的`Bicycle`类自动获得`Vehicle`类的所有特性,比如`currentSpeed`和`description`属性,还有它的`makeNoise()`方法。 除了它所继承的特性,`Bicycle`类还定义了一个默认值为`false`的存储型属性`hasBasket`(属性推断为`Bool`)。 -默认情况下,你创建任何新的`Bicycle`实例将不会有一个篮子,创建该实例之后,你可以为特定的`Bicycle`实例设置`hasBasket `属性为`ture`: +默认情况下,你创建任何新的`Bicycle`实例将不会有一个篮子(即`hasBasket`属性默认为`false`),创建该实例之后,你可以为特定的`Bicycle`实例设置`hasBasket`属性为`ture`: ```swift let bicycle = Bicycle() bicycle.hasBasket = true ``` -你还可以修改`Bicycle `实例所继承的`currentSpeed `属性,和查询实例所继承的`description `属性: +你还可以修改`Bicycle`实例所继承的`currentSpeed`属性,和查询实例所继承的`description`属性: ```swift bicycle.currentSpeed = 15.0 @@ -99,7 +103,7 @@ print("Bicycle: \(bicycle.description)") // Bicycle: traveling at 15.0 miles per hour ``` -子类还可以继续被其它类继承,下面的示例为`Bicycle `创建了一个名为`Tandem `(双人自行车)的子类: +子类还可以继续被其它类继承,下面的示例为`Bicycle`创建了一个名为`Tandem`(双人自行车)的子类: ```swift class Tandem: Bicycle { @@ -107,9 +111,9 @@ class Tandem: Bicycle { } ``` -`Tandem`从`Bicycle`继承了所有的属性与方法,这又使它同时继承了`Vehicle`的所有属性与方法。`Tandem`也增加了一个新的叫做`currentNumberOfPassengers`的存储型属性,默认值为0。 +`Tandem`从`Bicycle`继承了所有的属性与方法,这又使它同时继承了`Vehicle`的所有属性与方法。`Tandem`也增加了一个新的叫做`currentNumberOfPassengers`的存储型属性,默认值为`0`。 -如果你创建了一个`Tandem`的实例,你可以使用它所有的新属性和继承的属性,还能查询从`Vehicle`继承来的只读属性`description `: +如果你创建了一个`Tandem`的实例,你可以使用它所有的新属性和继承的属性,还能查询从`Vehicle`继承来的只读属性`description`: ```swift let tandem = Tandem() @@ -123,27 +127,27 @@ print("Tandem: \(tandem.description)") ## 重写(Overriding) -子类可以为继承来的实例方法(instance method),类方法(class method),实例属性(instance property),或下标脚本(subscript)提供自己定制的实现(implementation)。我们把这种行为叫*重写(overriding)*。 +子类可以为继承来的实例方法(instance method),类方法(class method),实例属性(instance property),或下标(subscript)提供自己定制的实现(implementation)。我们把这种行为叫*重写(overriding)*。 如果要重写某个特性,你需要在重写定义的前面加上`override`关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少`override`关键字的重写都会在编译时被诊断为错误。 `override`关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。 -### 访问超类的方法,属性及下标脚本 +### 访问超类的方法,属性及下标 -当你在子类中重写超类的方法,属性或下标脚本时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以优化已有实现的行为,或在一个继承来的变量中存储一个修改过的值。 +当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以完善已有实现的行为,或在一个继承来的变量中存储一个修改过的值。 -在合适的地方,你可以通过使用`super`前缀来访问超类版本的方法,属性或下标脚本: +在合适的地方,你可以通过使用`super`前缀来访问超类版本的方法,属性或下标: -* 在方法`someMethod`的重写实现中,可以通过`super.someMethod()`来调用超类版本的`someMethod`方法。 +* 在方法`someMethod()`的重写实现中,可以通过`super.someMethod()`来调用超类版本的`someMethod()`方法。 * 在属性`someProperty`的 getter 或 setter 的重写实现中,可以通过`super.someProperty`来访问超类版本的`someProperty`属性。 -* 在下标脚本的重写实现中,可以通过`super[someIndex]`来访问超类版本中的相同下标脚本。 +* 在下标的重写实现中,可以通过`super[someIndex]`来访问超类版本中的相同下标。 ### 重写方法 在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。 -下面的例子定义了`Vehicle`的一个新的子类,叫`Train `,它重写了从`Vehicle`类继承来的`makeNoise `方法: +下面的例子定义了`Vehicle`的一个新的子类,叫`Train`,它重写了从`Vehicle`类继承来的`makeNoise()`方法: ```swift class Train: Vehicle { @@ -153,28 +157,28 @@ class Train: Vehicle { } ``` -如果你创建一个`Train `的新实例,并调用了它的`makeNoise `方法,你就会发现`Train `版本的方法被调用: +如果你创建一个`Train`的新实例,并调用了它的`makeNoise()`方法,你就会发现`Train`版本的方法被调用: ```swift let train = Train() train.makeNoise() -// prints "Choo Choo" +// 打印 "Choo Choo" ``` ### 重写属性 -你可以重写继承来的实例属性或类属性,提供自己定制的getter和setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。 +你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。 -#### 重写属性的Getters和Setters +#### 重写属性的 Getters 和 Setters 你可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。 -你可以将一个继承来的只读属性重写为一个读写属性,只需要你在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。 +你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。 -> 注意: +> 注意 如果你在重写属性中提供了 setter,那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过`super.someProperty`来返回继承来的值,其中`someProperty`是你要重写的属性的名字。 -以下的例子定义了一个新类,叫`Car`,它是`Vehicle `的子类。这个类引入了一个新的存储型属性叫做`gear `,默认为整数1。`Car`类重写了继承自`Vehicle `的description属性,提供自定义的,包含当前档位的描述: +以下的例子定义了一个新类,叫`Car`,它是`Vehicle`的子类。这个类引入了一个新的存储型属性叫做`gear`,默认值为整数`1`。`Car`类重写了继承自`Vehicle`的`description`属性,提供包含当前档位的自定义描述: ```swift class Car: Vehicle { @@ -185,9 +189,9 @@ class Car: Vehicle { } ``` -重写的`description `属性,首先要调用`super.description`返回`Vehicle`类的`description`属性。之后,`Car `类版本的`description`在末尾增加了一些额外的文本来提供关于当前档位的信息。 +重写的`description`属性首先要调用`super.description`返回`Vehicle`类的`description`属性。之后,`Car`类版本的`description`在末尾增加了一些额外的文本来提供关于当前档位的信息。 -如果你创建了`Car `的实例并且设置了它的`gear`和`currentSpeed`属性,你可以看到它的`description`返回了`Car`中定义的`description`: +如果你创建了`Car`的实例并且设置了它的`gear`和`currentSpeed`属性,你可以看到它的`description`返回了`Car`中的自定义描述: ```swift let car = Car() @@ -197,12 +201,14 @@ print("Car: \(car.description)") // Car: traveling at 25.0 miles per hour in gear 3 ``` + #### 重写属性观察器(Property Observer) -你可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。 +你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。 -> 注意: -你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供`willSet`或`didSet`实现是不恰当。此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。 +> 注意 +你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供`willSet`或`didSet`实现是不恰当。 +此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。 下面的例子定义了一个新类叫`AutomaticCar`,它是`Car`的子类。`AutomaticCar`表示自动挡汽车,它可以根据当前的速度自动选择合适的挡位: @@ -216,7 +222,7 @@ class AutomaticCar: Car { } ``` -当你设置`AutomaticCar`的`currentSpeed `属性,属性的`didSet`观察器就会自动地设置`gear`属性,为新的速度选择一个合适的挡位。具体来说就是,属性观察器将新的速度值除以10,然后向下取得最接近的整数值,最后加1来得到档位`gear`的值。例如,速度为10.0时,挡位为1;速度为35.0时,挡位为4: +当你设置`AutomaticCar`的`currentSpeed`属性,属性的`didSet`观察器就会自动地设置`gear`属性,为新的速度选择一个合适的挡位。具体来说就是,属性观察器将新的速度值除以`10`,然后向下取得最接近的整数值,最后加`1`来得到档位`gear`的值。例如,速度为`35.0`时,挡位为`4`: ```swift let automatic = AutomaticCar() @@ -228,8 +234,8 @@ print("AutomaticCar: \(automatic.description)") ## 防止重写 -你可以通过把方法,属性或下标脚本标记为*`final`*来防止它们被重写,只需要在声明关键字前加上`final`特性即可。(例如:`final var`, `final func`, `final class func`, 以及 `final subscript`) +你可以通过把方法,属性或下标标记为*`final`*来防止它们被重写,只需要在声明关键字前加上`final`修饰符即可(例如:`final var`,`final func`,`final class func`,以及`final subscript`)。 -如果你重写了`final`方法,属性或下标脚本,在编译时会报错。在类扩展中的方法,属性或下标脚本也可以在扩展的定义里标记为 final。 +如果你重写了带有`final`标记的方法,属性或下标,在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final 的。 -你可以通过在关键字`class`前添加`final`特性(`final class`)来将整个类标记为 final 的,这样的类是不可被继承的,任何子类试图继承此类时,在编译时会报错。 +你可以通过在关键字`class`前添加`final`修饰符(`final class`)来将整个类标记为 final 的。这样的类是不可被继承的,试图继承这样的类会导致编译报错。 diff --git a/source/chapter2/14_Initialization.md b/source/chapter2/14_Initialization.md index 31a9c054..16f73f68 100755 --- a/source/chapter2/14_Initialization.md +++ b/source/chapter2/14_Initialization.md @@ -8,37 +8,46 @@ > 2.0 > 翻译+校对:[chenmingbiao](https://github.com/chenmingbiao) +> 2.1 +> 翻译:[Channe](https://github.com/Channe),[Realank](https://github.com/Realank) +> 校对:[shanks](http://codebuild.me),2016-1-23 + +> 2.2 +> 翻译:[pmst](https://github.com/colourful987) +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-14 + 本页包含内容: -- [存储型属性的初始赋值](#setting_initial_values_for_stored_properties) +- [存储属性的初始赋值](#setting_initial_values_for_stored_properties) - [自定义构造过程](#customizing_initialization) - [默认构造器](#default_initializers) - [值类型的构造器代理](#initializer_delegation_for_value_types) - [类的继承和构造过程](#class_inheritance_and_initialization) - [可失败构造器](#failable_initializers) - [必要构造器](#required_initializers) -- [通过闭包和函数来设置属性的默认值](#setting_a_default_property_value_with_a_closure_or_function) +- [通过闭包或函数设置属性的默认值](#setting_a_default_property_value_with_a_closure_or_function) -构造过程是使用类、结构体或枚举类型一个实例的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。 +构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。 通过定义构造器(`Initializers`)来实现构造过程,这些构造器可以看做是用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。 类的实例也可以通过定义析构器(`deinitializer`)在实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.html)。 -## 设置存储型属性的初始值 +## 存储属性的初始赋值 类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。 你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。以下小节将详细介绍这两种方法。 ->注意: +> 注意 当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者(`property observers`)。 + ### 构造器 -构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字`init`命名。 +构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字`init`命名: ```swift init() { @@ -60,14 +69,15 @@ print("The default temperature is \(f.temperature)° Fahrenheit") // 输出 "The default temperature is 32.0° Fahrenheit” ``` -这个结构体定义了一个不带参数的构造器`init`,并在里面将存储型属性`temperature`的值初始化为`32.0`(华摄氏度下水的冰点)。 +这个结构体定义了一个不带参数的构造器`init`,并在里面将存储型属性`temperature`的值初始化为`32.0`(华氏温度下水的冰点)。 + ### 默认属性值 如前所述,你可以在构造器中为存储型属性设置初始值。同样,你也可以在属性声明时为其设置默认值。 ->注意: -如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的效果是一样的,只不过使用默认值让属性的初始化和声明结合的更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性(后续章节将讲到)。 +> 注意 +如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的效果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性(后续章节将讲到)。 你可以使用更简单的方式在定义结构体`Fahrenheit`时为属性`temperature`设置默认值: @@ -80,13 +90,14 @@ struct Fahrenheit { ## 自定义构造过程 -你可以通过输入参数和可选属性类型来自定义构造过程,也可以在构造过程中修改常量属性。这些都将在后面章节中提到。 +你可以通过输入参数和可选类型的属性来自定义构造过程,也可以在构造过程中修改常量属性。这些都将在后面章节中提到。 + ### 构造参数 自定义`构造过程`时,可以在定义中提供构造参数,指定所需值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。 -下面例子中定义了一个包含摄氏度温度的结构体`Celsius`。它定义了两个不同的构造器:`init(fromFahrenheit:)`和`init(fromKelvin:)`,二者分别通过接受不同刻度表示的温度值来创建新的实例: +下面例子中定义了一个包含摄氏度温度的结构体`Celsius`。它定义了两个不同的构造器:`init(fromFahrenheit:)`和`init(fromKelvin:)`,二者分别通过接受不同温标下的温度值来创建新的实例: ```swift struct Celsius { @@ -106,15 +117,16 @@ let freezingPointOfWater = Celsius(fromKelvin: 273.15) 第一个构造器拥有一个构造参数,其外部名字为`fromFahrenheit`,内部名字为`fahrenheit`;第二个构造器也拥有一个构造参数,其外部名字为`fromKelvin`,内部名字为`kelvin`。这两个构造器都将唯一的参数值转换成摄氏温度值,并保存在属性`temperatureInCelsius`中。 + ### 参数的内部名称和外部名称 -跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。 +跟函数和方法参数相同,构造参数也拥有一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。 -然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名,就相当于在每个构造参数之前加了一个哈希符号。 +然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。因此在调用构造器时,主要通过构造器中的参数名和类型来确定应该被调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,Swift 会为构造器的每个参数自动生成一个跟内部名字相同的外部名。 -以下例子中定义了一个结构体`Color`,它包含了三个常量:`red`、`green`和`blue`。这些属性可以存储0.0到1.0之间的值,用来指示颜色中红、绿、蓝成分的含量。 +以下例子中定义了一个结构体`Color`,它包含了三个常量:`red`、`green`和`blue`。这些属性可以存储`0.0`到`1.0`之间的值,用来指示颜色中红、绿、蓝成分的含量。 -`Color`提供了一个构造器,其中包含三个`Double`类型的构造参数。`Color`也可以提供第二个构造器,它只包含`Double`类型名叫`white`的参数,它被用于给上述三个构造参数赋予同样的值。 +`Color`提供了一个构造器,其中包含三个`Double`类型的构造参数。`Color`也可以提供第二个构造器,它只包含名为`white`的`Double`类型的参数,它被用于给上述三个构造参数赋予同样的值。 ```swift struct Color { @@ -132,7 +144,7 @@ struct Color { } ``` -两种构造器都能用于创建一个新的`Color`实例,你需要为构造器每个外部参数传值。 +两种构造器都能用于创建一个新的`Color`实例,你需要为构造器每个外部参数传值: ```swift let magenta = Color(red: 1.0, green: 0.0, blue: 1.0) @@ -146,15 +158,16 @@ let veryGreen = Color(0.0, 1.0, 0.0) // 报编译时错误,需要外部名称 ``` + ### 不带外部名的构造器参数 -如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线(_)来显示描述它的外部名,以此重写上面所说的默认行为。 +如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线(`_`)来显式描述它的外部名,以此重写上面所说的默认行为。 -下面是之前`Celsius`例子的扩展,跟之前相比添加了一个带有`Double`类型参数名为`celsius`的构造器,其外部名用`_`代替。 +下面是之前`Celsius`例子的扩展,跟之前相比添加了一个带有`Double`类型参数的构造器,其外部名用`_`代替: ```swift -struct Celsius {I - var temperatureInCelsius: Double = 0.0 +struct Celsius { + var temperatureInCelsius: Double init(fromFahrenheit fahrenheit: Double) { temperatureInCelsius = (fahrenheit - 32.0) / 1.8 } @@ -169,11 +182,12 @@ let bodyTemperature = Celsius(37.0) // bodyTemperature.temperatureInCelsius 为 37.0 ``` -调用这种不需要外部参数名称的`Celsius(37.0)`构造器看起来十分简明的。因此适当使用这种`init(_ celsius: Double)`构造器可以提供`Double`类型的参数值而不需要加上外部名。 +调用`Celsius(37.0)`意图明确,不需要外部参数名称。因此适合使用`init(_ celsius: Double)`这样的构造器,从而可以通过提供`Double`类型的参数值调用构造器,而不需要加上外部名。 + ### 可选属性类型 -如果你定制的类型包含一个逻辑上允许取值为空的存储型属性--不管是因为它无法在初始化时赋值,还是因为它可以在之后某个时间点可以赋值为空--你都需要将它定义为可选类型`optional type`。可选类型的属性将自动初始化为空`nil`,表示这个属性是故意在初始化时设置为空的。 +如果你定制的类型包含一个逻辑上允许取值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时间点可以赋值为空——你都需要将它定义为`可选类型`(optional type)。可选类型的属性将自动初始化为`nil`,表示这个属性是有意在初始化时设置为空的。 下面例子中定义了类`SurveyQuestion`,它包含一个可选字符串属性`response`: @@ -194,17 +208,17 @@ cheeseQuestion.ask() cheeseQuestion.response = "Yes, I do like cheese." ``` -调查问题在问题提出之后,我们才能得到回答。所以我们将属性回答`response`声明为`String?`类型,或者说是可选字符串类型`optional String`。当`SurveyQuestion`实例化时,它将自动赋值为空`nil`,表明暂时还不存在此字符串。 +调查问题的答案在回答前是无法确定的,因此我们将属性`response`声明为`String?`类型,或者说是`可选字符串类型`(optional String)。当`SurveyQuestion`实例化时,它将自动赋值为`nil`,表明此字符串暂时还没有值。 ### 构造过程中常量属性的修改 -你可以在构造过程中的任意时间点修改常量属性的值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。 +你可以在构造过程中的任意时间点给常量属性指定一个值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。 ->注意: +> 注意 对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。 -你可以修改上面的`SurveyQuestion`示例,用常量属性替代变量属性`text`,表示问题内容`text`在`SurveyQuestion`的实例被创建之后不会再被修改。尽管`text`属性现在是常量,我们仍然可以在其类的构造器中设置它的值: +你可以修改上面的`SurveyQuestion`示例,用常量属性替代变量属性`text`,表示问题内容`text`在`SurveyQuestion`的实例被创建之后不会再被修改。尽管`text`属性现在是常量,我们仍然可以在类的构造器中设置它的值: ```swift class SurveyQuestion { @@ -226,9 +240,9 @@ beetsQuestion.response = "I also like beets. (But not with cheese.)" ## 默认构造器 -如果结构体和类的所有属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体和类创建一个默认构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。 +如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器(default initializers)。这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例。 -下面例子中创建了一个类`ShoppingListItem`,它封装了购物清单中的某一项的属性:名字(`name`)、数量(`quantity`)和购买状态 `purchase state`。 +下面例子中创建了一个类`ShoppingListItem`,它封装了购物清单中的某一物品的属性:名字(`name`)、数量(`quantity`)和购买状态 `purchase state`: ```swift class ShoppingListItem { @@ -244,13 +258,13 @@ var item = ShoppingListItem() ### 结构体的逐一成员构造器 -除上面提到的默认构造器,如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。 +除了上面提到的默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。 逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。 -下面例子中定义了一个结构体`Size`,它包含两个属性`width`和`height`。Swift 可以根据这两个属性的初始赋值`0.0`自动推导出它们的类型`Double`。 +下面例子中定义了一个结构体`Size`,它包含两个属性`width`和`height`。Swift 可以根据这两个属性的初始赋值`0.0`自动推导出它们的类型为`Double`。 -由于这两个存储型属性都有默认值,结构体`Size`自动获得了一个逐一成员构造器 `init(width:height:)`。 你可以用它来为`Size`创建新的实例: +结构体`Size`自动获得了一个逐一成员构造器`init(width:height:)`。你可以用它来为`Size`创建新的实例: ```swift struct Size { @@ -260,18 +274,19 @@ let twoByTwo = Size(width: 2.0, height: 2.0) ``` + ## 值类型的构造器代理 构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。 -构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.html)),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。 +构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.html)),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。 -对于值类型,你可以使用`self.init`在自定义的构造器中引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用`self.init`。 +对于值类型,你可以使用`self.init`在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用`self.init`。 -如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。 +如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制可以防止你为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用自动生成的构造器 ->注意: -假如你想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,我们建议你将自己定制的构造器写到扩展(`extension`)中,而不是跟值类型定义混在一起。想查看更多内容,请查看[扩展](./20_Extensions.html)章节。 +> 注意 +假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看[扩展](./21_Extensions.html)章节。 下面例子将定义一个结构体`Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体`Size`和`Point`,它们各自为其所有的属性提供了初始值`0.0`。 @@ -284,7 +299,7 @@ struct Point { } ``` -你可以通过以下三种方式为`Rect`创建实例--使用默认的0值来初始化`origin`和`size`属性;使用特定的`origin`和`size`实例来初始化;使用特定的`center`和`size`来初始化。在下面`Rect`结构体定义中,我们为这三种方式提供了三个自定义的构造器: +你可以通过以下三种方式为`Rect`创建实例——使用被初始化为默认值的`origin`和`size`属性来初始化;提供指定的`origin`和`size`实例来初始化;提供指定的`center`和`size`来初始化。在下面`Rect`结构体定义中,我们为这三种方式提供了三个自定义的构造器: ```swift struct Rect { @@ -303,11 +318,11 @@ struct Rect { } ``` -第一个`Rect`构造器`init()`,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器是一个空函数,使用一对大括号`{}`来描述,它没有执行任何定制的构造过程。调用这个构造器将返回一个`Rect`实例,它的`origin`和`size`属性都使用定义时的默认值`Point(x: 0.0, y: 0.0)`和`Size(width: 0.0, height: 0.0)`: +第一个`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) +// basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0) ``` 第二个`Rect`构造器`init(origin:size:)`,在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的。这个构造器只是简单地将`origin`和`size`的参数值赋给对应的存储型属性: @@ -315,39 +330,41 @@ let basicRect = Rect() ```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) +// originRect 的 origin 是 (2.0, 2.0),size 是 (5.0, 5.0) ``` -第三个`Rect`构造器`init(center:size:)`稍微复杂一点。它先通过`center`和`size`的值计算出`origin`的坐标。然后再调用(或代理给)`init(origin:size:)`构造器来将新的`origin`和`size`值赋值到对应的属性中: +第三个`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) +// centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0) ``` -构造器`init(center:size:)`可以自己将`origin`和`size`的新值赋值到对应的属性中。然而尽量利用现有的构造器和它所提供的功能来实现`init(center:size:)`的功能,是更方便、更清晰和更直观的方法。 +构造器`init(center:size:)`可以直接将`origin`和`size`的新值赋值到对应的属性中。然而,利用恰好提供了相关功能的现有构造器会更为方便,构造器`init(center:size:)`的意图也会更加清晰。 ->注意: -如果你想用另外一种不需要自己定义`init()`和`init(origin:size:)`的方式来实现这个例子,请参考[扩展](./20_Extensions.html)。 +> 注意 +如果你想用另外一种不需要自己定义`init()`和`init(origin:size:)`的方式来实现这个例子,请参考[扩展](./21_Extensions.html)。 ## 类的继承和构造过程 -类里面的所有存储型属性--包括所有继承自父类的属性--都必须在构造过程中设置初始值。 +类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。 -Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。 +Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。 + ### 指定构造器和便利构造器 -指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。 +*指定构造器*(designated initializers)是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。 -每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节[自动构造器的继承](#automatic_initializer_inheritance)。 +每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。 -便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。 +*便利构造器*(convenience initializers)是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。 你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。 + ### 指定构造器和便利构造器的语法 类的指定构造器的写法跟值类型简单构造器一样: @@ -371,14 +388,14 @@ convenience init(parameters) { 为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用: -#### 规则 1 +##### 规则 1 指定构造器必须调用其直接父类的的指定构造器。 -#### 规则 2 +##### 规则 2 便利构造器必须调用同一类中定义的其它构造器。 -#### 规则 3 -便利构造器必须最终以调用一个指定构造器结束。 +##### 规则 3 +便利构造器必须最终导致一个指定构造器被调用。 一个更方便记忆的方法是: @@ -387,109 +404,109 @@ convenience init(parameters) { 这些规则可以通过下面图例来说明: -![构造器代理图](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializerDelegation01_2x.png) +![构造器代理图](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/initializerDelegation01_2x.png) -如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器,而后者又调用了唯一的指定构造器。这满足了上面提到的规则2和3。这个父类没有自己的父类,所以规则1没有用到。 +如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器,而后者又调用了唯一的指定构造器。这满足了上面提到的规则 2 和 3。这个父类没有自己的父类,所以规则 1 没有用到。 -子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,因为它只能调用同一个类里的其他构造器。这满足了上面提到的规则2和3。而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则1。 +子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,因为它只能调用同一个类里的其他构造器。这满足了上面提到的规则 2 和 3。而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则 1。 -> 注意: -这些规则不会影响使用时,如何用类去创建实例。任何上图中展示的构造器都可以用来完整创建对应类的实例。这些规则只在实现类的定义时有影响。 +> 注意 +这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类定义如何实现。 -下面图例中展示了一种针对四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系。 +下面图例中展示了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系。 -![复杂构造器代理图](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializerDelegation02_2x.png) +![复杂构造器代理图](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/initializerDelegation02_2x.png) ### 两段式构造过程 -Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。 +Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性被引入它们的类指定一个初始值。当每个存储型属性的初始值被确定后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步定制它们的存储型属性。 -两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外地赋予不同的值。 +两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另外一个构造器意外地赋予不同的值。 -> 注意: -Swift的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值`0`或空值(比如说`0`或`nil`)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以`0`或`nil`作为合法默认值的情况。 +> 注意 +Swift 的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值`0`或空值(比如说`0`或`nil`)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以`0`或`nil`作为合法默认值的情况。 -Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能顺利完成: +Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能不出错地完成: -#### 安全检查 1 +##### 安全检查 1 指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。 如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。 -#### 安全检查 2 +##### 安全检查 2 指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。 -#### 安全检查 3 +##### 安全检查 3 便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。 -#### 安全检查 4 +##### 安全检查 4 -构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,`self`的值不能被引用。 +构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用`self`作为一个值。 -类实例在第一阶段结束以前并不是完全有效,仅能访问属性和调用方法,一旦完成第一阶段,该实例才会声明为有效实例。 +类实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,该实例才会成为有效实例,才能访问属性和调用方法。 以下是两段式构造过程中基于上述安全检查的构造流程展示: -#### 阶段 1 +##### 阶段 1 -- 某个指定构造器或便利构造器被调用; -- 完成新实例内存的分配,但此时内存还没有被初始化; -- 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化; -- 指定构造器将调用父类的构造器,完成父类属性的初始化; -- 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部; -- 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段1完成。 +- 某个指定构造器或便利构造器被调用。 +- 完成新实例内存的分配,但此时内存还没有被初始化。 +- 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。 +- 指定构造器将调用父类的构造器,完成父类属性的初始化。 +- 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。 +- 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。 -#### 阶段 2 +##### 阶段 2 - 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问`self`、修改它的属性并调用实例方法等等。 - 最终,任意构造器链中的便利构造器可以有机会定制实例和使用`self`。 -下图展示了在假定的子类和父类之间构造的阶段1: +下图展示了在假定的子类和父类之间的构造阶段 1: -![构造过程阶段1](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/twoPhaseInitialization01_2x.png) +![构建过程阶段1](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/twoPhaseInitialization01_2x.png) 在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。 -如安全检查1所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着造器链一直往上完成父类的构建过程。 +如安全检查 1 所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着构造器链一直往上完成父类的构造过程。 -父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要构建,也就无需继续向上做构建代理。 +父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要初始化,也就无需继续向上代理。 -一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,而阶段1也已完成。 +一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,阶段 1 完成。 -以下展示了相同构造过程的阶段2: +以下展示了相同构造过程的阶段 2: -![构建过程阶段2](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/twoPhaseInitialization02_2x.png) +![构建过程阶段2](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/twoPhaseInitialization02_2x.png) -父类中的指定构造器现在有机会进一步来定制实例(尽管它没有这种必要)。 +父类中的指定构造器现在有机会进一步来定制实例(尽管这不是必须的)。 -一旦父类中的指定构造器完成调用,子类的构指定构造器可以执行更多的定制操作(同样,它也没有这种必要)。 +一旦父类中的指定构造器完成调用,子类中的指定构造器可以执行更多的定制操作(这也不是必须的)。 最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的定制操作。 ### 构造器的继承和重写 -跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。 +跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例。 ->注意: -父类的构造器仅在确定和安全的情况下被继承。具体内容请参考后续章节[自动构造器的继承](#automatic_initializer_inheritance)。 +> 注意 +父类的构造器仅会在安全和适当的情况下被继承。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。 -假如你希望自定义的子类中能实现一个或多个跟父类相同的构造器,也许是为了完成一些定制的构造过程,你可以在你定制的子类中提供和重写与父类相同的构造器。 +假如你希望自定义的子类中能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现。 -当你写一个父类中带有指定构造器的子类构造器时,你需要重写这个指定的构造器。因此,你必须在定义子类构造器时带上`override`修饰符。即使你重写系统提供的默认构造器也需要带上`override`修饰符,具体内容请参考[默认构造器](#default_initializers)。 +当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因此,你必须在定义子类构造器时带上`override`修饰符。即使你重写的是系统自动提供的默认构造器,也需要带上`override`修饰符,具体内容请参考[默认构造器](#default_initializers)。 -无论是重写属性,方法或者是下标脚本,只要含有`override`修饰符就会去检查父类是否有相匹配的重写指定构造器和验证重写构造器参数。 +正如重写属性,方法或者是下标,`override`修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否正确。 ->注意: -当你重写一个父类指定构造器时,你需要写`override`修饰符,甚至你的子类构造器继承的是父类的便利构造器。 +> 注意 +当你重写一个父类的指定构造器时,你总是需要写`override`修饰符,即使你的子类将父类的指定构造器重写为了便利构造器。 -相反地,如果你写了一个和父类便利构造器相匹配的子类构造器,子类都不能直接调用父类的便利构造器,每个规则都在上文[构造器链](#initialization_chain)有所描述。 +相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文[类的构造器代理规则](#initializer_delegation_for_class_types)有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加`override`前缀。 -在下面的例子中定义了一个基础类叫`Vehicle`。基础类中声明了一个存储型属性`numberOfWheels`,它是值为`0`的`Int`类型属性。`numberOfWheels`属性用于创建名为`descrpiption`类型为`String`的计算型属性。 +在下面的例子中定义了一个叫`Vehicle`的基类。基类中声明了一个存储型属性`numberOfWheels`,它是值为`0`的`Int`类型的存储型属性。`numberOfWheels`属性用于创建名为`descrpiption`的`String`类型的计算型属性: ```swift class Vehicle { @@ -500,7 +517,7 @@ class Vehicle { } ``` -`Vehicle`类只为存储型属性提供默认值,而不自定义构造器。因此,它会自动生成一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。默认构造器通常在类中是指定构造器,它可以用于创建属性叫`numberOfWheels`值为`0`的`Vehicle`实例。 +`Vehicle`类只为存储型属性提供默认值,而不自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。自动获得的默认构造器总会是类中的指定构造器,它可以用于创建`numberOfWheels`为`0`的`Vehicle`实例: ```swift let vehicle = Vehicle() @@ -521,9 +538,9 @@ class Bicycle: Vehicle { 子类`Bicycle`定义了一个自定义指定构造器`init()`。这个指定构造器和父类的指定构造器相匹配,所以`Bicycle`中的指定构造器需要带上`override`修饰符。 -`Bicycle`的构造器`init()`一开始调用`super.init()`方法,这个方法的作用是调用`Bicycle`的父类`Vehicle`。这样可以确保`Bicycle`在修改属性之前它所继承的属性`numberOfWheels`能被`Vehicle`类初始化。在调用`super.init()`之后,原本的属性`numberOfWheels`被赋值为`2`。 +`Bicycle`的构造器`init()`以调用`super.init()`方法开始,这个方法的作用是调用`Bicycle`的父类`Vehicle`的默认构造器。这样可以确保`Bicycle`在修改属性之前,它所继承的属性`numberOfWheels`能被`Vehicle`类初始化。在调用`super.init()`之后,属性`numberOfWheels`的原值被新值`2`替换。 -如果你创建一个`Bicycle`实例,你可以调用继承的`description`计算型属性去查看属性`numberOfWheels`是否有改变。 +如果你创建一个`Bicycle`实例,你可以调用继承的`description`计算型属性去查看属性`numberOfWheels`是否有改变: ```swift let bicycle = Bicycle() @@ -531,34 +548,35 @@ print("Bicycle: \(bicycle.description)") // Bicycle: 2 wheel(s) ``` ->注意 -子类可以在初始化时修改继承变量属性,但是不能修改继承过来的常量属性。 +> 注意 +子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。 -### 自动构造器的继承 +### 构造器的自动继承 -如上所述,子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。在实践中,这意味着对于许多常见场景你不必重写父类的构造器,并且在尽可能安全的情况下以最小的代价来继承父类的构造器。 +如上所述,子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。在实践中,这意味着对于许多常见场景你不必重写父类的构造器,并且可以在安全的情况下以最小的代价继承父类的构造器。 -假设要为子类中引入的任意新属性提供默认值,请遵守以下2个规则: +假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则适用: -#### 规则 1 +##### 规则 1 如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。 -#### 规则 2 +##### 规则 2 -如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。 +如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器。 即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。 ->注意: -子类可以通过部分满足规则2的方式,使用子类便利构造器来实现父类的指定构造器。 +> 注意 +对于规则 2,子类可以将父类的指定构造器实现为便利构造器。 -### 指定构造器和便利构造器操作 + +### 指定构造器和便利构造器实践 -接下来的例子将在操作中展示指定构造器、便利构造器和自动构造器的继承。它定义了包含三个类`Food`、`RecipeIngredient`以及`ShoppingListItem`的类层次结构,并将演示它们的构造器是如何相互作用的。 +接下来的例子将在实践中展示指定构造器、便利构造器以及构造器的自动继承。这个例子定义了包含三个类`Food`、`RecipeIngredient`以及`ShoppingListItem`的类层次结构,并将演示它们的构造器是如何相互作用的。 -类层次中的基类是`Food`,它是一个简单的用来封装食物名字的类。`Food`类引入了一个叫做`name`的`String`类型属性,并且提供了两个构造器来创建`Food`实例: +类层次中的基类是`Food`,它是一个简单的用来封装食物名字的类。`Food`类引入了一个叫做`name`的`String`类型的属性,并且提供了两个构造器来创建`Food`实例: ```swift class Food { @@ -574,25 +592,25 @@ class Food { 下图中展示了`Food`的构造器链: -![Food构造器链](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializersExample01_2x.png) +![Food构造器链](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/initializersExample01_2x.png) -类没有提供一个默认的逐一成员构造器,所以`Food`类提供了一个接受单一参数`name`的指定构造器。这个构造器可以使用一个特定的名字来创建新的`Food`实例: +类类型没有默认的逐一成员构造器,所以`Food`类提供了一个接受单一参数`name`的指定构造器。这个构造器可以使用一个特定的名字来创建新的`Food`实例: ```swift let namedMeat = Food(name: "Bacon") // namedMeat 的名字是 "Bacon” ``` -`Food`类中的构造器`init(name: String)`被定义为一个指定构造器,因为它能确保所有新`Food`实例的中存储型属性都被初始化。`Food`类没有父类,所以`init(name: String)`构造器不需要调用`super.init()`来完成构造。 +`Food`类中的构造器`init(name: String)`被定义为一个指定构造器,因为它能确保`Food`实例的所有存储型属性都被初始化。`Food`类没有父类,所以`init(name: String)`构造器不需要调用`super.init()`来完成构造过程。 -`Food`类同样提供了一个没有参数的便利构造器 `init()`。这个`init()`构造器为新食物提供了一个默认的占位名字,通过代理调用同一类中定义的指定构造器`init(name: String)`并给参数`name`传值`[Unnamed]`来实现: +`Food`类同样提供了一个没有参数的便利构造器`init()`。这个`init()`构造器为新食物提供了一个默认的占位名字,通过横向代理到指定构造器`init(name: String)`并给参数`name`传值`[Unnamed]`来实现: ```swift let mysteryMeat = Food() // mysteryMeat 的名字是 [Unnamed] ``` -类层级中的第二个类是`Food`的子类`RecipeIngredient`。`RecipeIngredient`类构建了食谱中的一味调味剂。它引入了`Int`类型的数量属性`quantity`(以及从`Food`继承过来的`name`属性),并且定义了两个构造器来创建`RecipeIngredient`实例: +类层级中的第二个类是`Food`的子类`RecipeIngredient`。`RecipeIngredient`类用来表示食谱中的一项原料。它引入了`Int`类型的属性`quantity`(以及从`Food`继承过来的`name`属性),并且定义了两个构造器来创建`RecipeIngredient`实例: ```swift class RecipeIngredient: Food { @@ -609,15 +627,17 @@ class RecipeIngredient: Food { 下图中展示了`RecipeIngredient`类的构造器链: -![RecipeIngredient构造器](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializersExample02_2x.png) +![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, 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)`,它只通过`name`来创建`RecipeIngredient`的实例。这个便利构造器假设任意`RecipeIngredient`实例的`quantity`为`1`,所以不需要显式指明数量即可创建出实例。这个便利构造器的定义可以更加方便和快捷地创建实例,并且避免了创建多个`quantity`为`1`的`RecipeIngredient`实例时的代码重复。这个便利构造器只是简单地横向代理到类中的指定构造器,并为`quantity`参数传递`1`。 -注意,`RecipeIngredient`的便利构造器`init(name: String)`使用了跟`Food`中指定构造器`init(name: String)`相同的参数。因为这个便利构造器重写要父类的指定构造器`init(name: String)`,必须在前面使用使用`override`标识(参见[构造器的继承和重写](#initializer_inheritance_and_overriding))。 +注意,`RecipeIngredient`的便利构造器`init(name: String)`使用了跟`Food`中指定构造器`init(name: String)`相同的参数。由于这个便利构造器重写了父类的指定构造器`init(name: String)`,因此必须在前面使用`override`修饰符(参见[构造器的继承和重写](#initializer_inheritance_and_overriding))。 -在这个例子中,`RecipeIngredient`的父类是`Food`,它有一个便利构造器`init()`。这个构造器因此也被`RecipeIngredient`继承。这个继承的`init()`函数版本跟`Food`提供的版本是一样的,除了它是将任务代理给`RecipeIngredient`版本的`init(name: String)`而不是`Food`提供的版本。 +尽管`RecipeIngredient`将父类的指定构造器重写为了便利构造器,它依然提供了父类的所有指定构造器的实现。因此,`RecipeIngredient`会自动继承父类的所有便利构造器。 + +在这个例子中,`RecipeIngredient`的父类是`Food`,它有一个便利构造器`init()`。这个便利构造器会被`RecipeIngredient`继承。这个继承版本的`init()`在功能上跟`Food`提供的版本是一样的,只是它会代理到`RecipeIngredient`版本的`init(name: String)`而不是`Food`提供的版本。 所有的这三种构造器都可以用来创建新的`RecipeIngredient`实例: @@ -627,9 +647,9 @@ let oneBacon = RecipeIngredient(name: "Bacon") let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6) ``` -类层级中第三个也是最后一个类是`RecipeIngredient`的子类,叫做`ShoppingListItem`。这个类构建了购物单中出现的某一种调味料。 +类层级中第三个也是最后一个类是`RecipeIngredient`的子类,叫做`ShoppingListItem`。这个类构建了购物单中出现的某一种食谱原料。 -购物单中的每一项总是从`unpurchased`未购买状态开始的。为了展现这一事实,`ShoppingListItem`引入了一个布尔类型的属性`purchased`,它的默认值是`false`。`ShoppingListItem`还添加了一个计算型属性`description`,它提供了关于`ShoppingListItem`实例的一些文字描述: +购物单中的每一项总是从未购买状态开始的。为了呈现这一事实,`ShoppingListItem`引入了一个布尔类型的属性`purchased`,它的默认值是`false`。`ShoppingListItem`还添加了一个计算型属性`description`,它提供了关于`ShoppingListItem`实例的一些文字描述: ```swift class ShoppingListItem: RecipeIngredient { @@ -642,14 +662,14 @@ class ShoppingListItem: RecipeIngredient { } ``` -> 注意: -`ShoppingListItem`没有定义构造器来为`purchased`提供初始化值,这是因为任何添加到购物单的项的初始状态总是未购买。 +> 注意 +`ShoppingListItem`没有定义构造器来为`purchased`提供初始值,因为添加到购物单的物品的初始状态总是未购买。 由于它为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器,`ShoppingListItem`将自动继承所有父类中的指定构造器和便利构造器。 -下图种展示了所有三个类的构造器链: +下图展示了这三个类的构造器链: -![三类构造器图](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/initializersExample03_2x.png) +![三类构造器图](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/initializersExample03_2x.png) 你可以使用全部三个继承来的构造器来创建`ShoppingListItem`的新实例: @@ -669,24 +689,24 @@ for item in breakfastList { // 6 x eggs ✘ ``` -如上所述,例子中通过字面量方式创建了一个新数组`breakfastList`,它包含了三个新的`ShoppingListItem`实例,因此数组的类型也能自动推导为`ShoppingListItem[]`。在数组创建完之后,数组中第一个`ShoppingListItem`实例的名字从`[Unnamed]`修改为`Orange juice`,并标记为已购买。接下来通过遍历数组每个元素并打印它们的描述值,展示了所有项当前的默认状态都已按照预期完成了赋值。 +如上所述,例子中通过字面量方式创建了一个数组`breakfastList`,它包含了三个`ShoppingListItem`实例,因此数组的类型也能被自动推导为`[ShoppingListItem]`。在数组创建完之后,数组中第一个`ShoppingListItem`实例的名字从`[Unnamed]`更改为`Orange juice`,并标记为已购买。打印数组中每个元素的描述显示了它们都已按照预期被赋值。 ## 可失败构造器 -如果一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有用的。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。 +如果一个类、结构体或枚举类型的对象,在构造过程中有可能失败,则为其定义一个可失败构造器。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。 -为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在`init`关键字后面加添问号`(init?)`。 +为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在`init`关键字后面添加问号(`init?`)。 -> 注意: -可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其类型相同。 +> 注意 +可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。 -可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。你通过`return nil` 语句,来表明可失败构造器在何种情况下“失败”。 +可失败构造器会创建一个类型为自身类型的可选类型的对象。你通过`return nil`语句来表明可失败构造器在何种情况下应该“失败”。 -> 注意: -严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了能确保对象自身能被正确构建。所以即使你在表明可失败构造器,失败的这种情况下,用到了`return nil`。也不要在表明可失败构造器成功的这种情况下,使用关键字 `return`。 +> 注意 +严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用`return nil`表明可失败构造器构造失败,而不要用关键字`return`来表明构造成功。 -下例中,定义了一个名为`Animal`的结构体,其中有一个名为`species`的,`String`类型的常量属性。同时该结构体还定义了一个,带一个`String`类型参数`species`的,可失败构造器。这个可失败构造器,被用来检查传入的参数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。 +下例中,定义了一个名为`Animal`的结构体,其中有一个名为`species`的`String`类型的常量属性。同时该结构体还定义了一个接受一个名为`species`的`String`类型参数的可失败构造器。这个可失败构造器检查传入的参数是否为一个空字符串。如果为空字符串,则构造失败。否则,`species`属性被赋值,构造成功。 ```swift struct Animal { @@ -698,7 +718,7 @@ struct Animal { } ``` -你可以通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功。 +你可以通过该可失败构造器来构建一个`Animal`的实例,并检查构造过程是否成功: ```swift let someCreature = Animal(species: "Giraffe") @@ -710,7 +730,7 @@ if let giraffe = someCreature { // 打印 "An animal was initialized with a species of Giraffe" ``` -如果你给该可失败构造器传入一个空字符串作为其参数,则该可失败构造器失败。 +如果你给该可失败构造器传入一个空字符串作为其参数,则会导致构造失败: ```swift let anonymousCreature = Animal(species: "") @@ -722,14 +742,15 @@ if anonymousCreature == nil { // 打印 "The anonymous creature could not be initialized" ``` -> 注意: -空字符串(如`""`,而不是`"Giraffe"`)和一个值为`nil`的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们只所以让`Animal`的可失败构造器,构建对象失败,只是因为对于`Animal`这个类的`species`属性来说,它更适合有一个具体的值,而不是空字符串。 +> 注意 +空字符串(如`""`,而不是`"Giraffe"`)和一个值为`nil`的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们之所以让`Animal`的可失败构造器构造失败,只是因为对于`Animal`这个类的`species`属性来说,它更适合有一个具体的值,而不是空字符串。 -###枚举类型的可失败构造器 + +### 枚举类型的可失败构造器 -你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。还能在参数不满足枚举成员期望的条件时,构造失败。 +你可以通过一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的参数无法匹配任何枚举成员,则构造失败。 -下例中,定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员(`Kelvin`,`Celsius`,和 `Fahrenheit`)和一个被用来找到`Character`值所对应的枚举成员的可失败构造器: +下例中,定义了一个名为`TemperatureUnit`的枚举类型。其中包含了三个可能的枚举成员(`Kelvin`,`Celsius`,和`Fahrenheit`),以及一个根据`Character`值找出所对应的枚举成员的可失败构造器: ```swift enum TemperatureUnit { @@ -749,7 +770,7 @@ enum TemperatureUnit { } ``` -你可以通过给该可失败构造器传递合适的参数来获取这三个枚举成员中相匹配的其中一个枚举成员。当参数的值不能与任意一枚举成员相匹配时,该枚举类型的构建过程失败: +你可以利用该可失败构造器在三个枚举成员中获取一个相匹配的枚举成员,当参数的值不能与任何枚举成员相匹配时,则构造失败: ```swift let fahrenheitUnit = TemperatureUnit(symbol: "F") @@ -765,11 +786,12 @@ if unknownUnit == nil { // 打印 "This is not a defined temperature unit, so initialization failed." ``` -###带原始值的枚举类型的可失败构造器 + +### 带原始值的枚举类型的可失败构造器 -带原始值的枚举类型会自带一个可失败构造器`init?(rawValue:)`,该可失败构造器有一个名为`rawValue`的默认参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和枚举类型成员所带的原始值匹配,则该构造器构造一个带此原始值的枚举成员,否则构造失败。 +带原始值的枚举类型会自带一个可失败构造器`init?(rawValue:)`,该可失败构造器有一个名为`rawValue`的参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和某个枚举成员的原始值匹配,则该构造器会构造相应的枚举成员,否则构造失败。 -因此上面的 TemperatureUnit的例子可以重写为: +因此上面的`TemperatureUnit`的例子可以重写为: ```swift enum TemperatureUnit: Character { @@ -780,86 +802,59 @@ let fahrenheitUnit = TemperatureUnit(rawValue: "F") if fahrenheitUnit != nil { print("This is a defined temperature unit, so initialization succeeded.") } -// prints "This is a defined temperature unit, so initialization succeeded." +// 打印 "This is a defined temperature unit, so initialization succeeded." let unknownUnit = TemperatureUnit(rawValue: "X") if unknownUnit == nil { print("This is not a defined temperature unit, so initialization failed.") } -// prints "This is not a defined temperature unit, so initialization failed." +// 打印 "This is not a defined temperature unit, so initialization failed." ``` -###类的可失败构造器 + +### 构造失败的传递 -值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。比如在前面的例子中,结构体`Animal`的可失败构造器触发失败的行为,甚至发生在`species`属性的值被初始化以前。 +类,结构体,枚举的可失败构造器可以横向代理到类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。 -而对类而言,就没有那么幸运了。类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。 +无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。 + +> 注意 +可失败构造器也可以代理到其它的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。 + +下面这个例子,定义了一个名为`CartItem`的`Product`类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为`quantity`的常量存储型属性,并确保该属性的值至少为`1`: -下面例子展示了如何使用隐式解析可选类型来实现这个类的可失败构造器的要求: ```swift class Product { - let name: String! - init?(name: String) { - self.name = name + let name: String + init?(name: String) { if name.isEmpty { return nil } + self.name = name } } -``` -上面定义的`Product`类,其内部结构和之前`Animal`结构体很相似。`Product`类有一个不能为空字符串的`name`常量属性。为了强制满足这个要求,`Product`类使用了可失败构造器来确保这个属性的值在构造器成功时不为空。 -毕竟,`Product`是一个类而不是结构体,也就不能和`Animal`一样了。`Product`类的所有可失败构造器必须在自己失败前给`name`属性一个初始值。 - -上面的例子中,`Product`类的`name`属性被定义为隐式解析可选字符串类型(`String!`)。因为它是一个可选类型,所以在构造过程里的赋值前,`name`属性有个默认值`nil`。用默认值`nil`意味着`Product`类的所有属性都有一个合法的初始值。因而,在构造器中给`name`属性赋一个特定的值前,可失败构造器能够在传入一个空字符串时触发构造过程的失败。 - -因为`name`属性是一个常量,所以一旦`Product`类构造成功,`name`属性肯定有一个非`nil`的值。即使它被定义为隐式解析可选类型,也完全可以放心大胆地直接访问,而不用考虑`name`属性是否有值。 - -```swift -if let bowTie = Product(name: "bow tie") { - // 不需要检查 bowTie.name == nil - print("The product's name is \(bowTie.name)") -} -// 打印 "The product's name is bow tie" -``` - -###构造失败的传递 - -可失败构造器同样满足在[构造器链](#initialization_chain)中所描述的构造规则。其允许在同一类,结构体和枚举中横向代理其他的可失败构造器。类似的,子类的可失败构造器也能向上代理基类的可失败构造器。 - -无论是向上代理还是横向代理,如果你代理的可失败构造器,在构造过程中触发了构造失败的行为,整个构造过程都将被立即终止,接下来任何的构造代码都将不会被执行。 - ->注意: -可失败构造器也可以代理调用其它的非可失败构造器。通过这个方法,你可以为已有的构造过程加入构造失败的条件。 - -下面这个例子,定义了一个名为`CartItem`的`Product`类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为`quantity`的常量参数,用来表示该物品的数量至少为1: - -```swift class CartItem: Product { - let quantity: Int! + let quantity: Int init?(name: String, quantity: Int) { + if quantity < 1 { return nil } self.quantity = quantity super.init(name: name) - if quantity < 1 { return nil } - } } ``` -和`Product`类中的`name`属性相类似的,`CartItem`类中的`quantity`属性的类型也是一个隐式解析可选类型,只不过由(`String!`)变为了(`Int!`)。这样做都是为了确保在构造过程中,该属性在被赋予特定的值之前能有一个默认的初始值nil。 +`CartItem` 可失败构造器首先验证接收的 `quantity` 值是否大于等于 1 。倘若 `quantity` 值无效,则立即终止整个构造过程,返回失败结果,且不再执行余下代码。同样地,`Product` 的可失败构造器首先检查 `name` 值,假如 `name` 值为空字符串,则构造器立即执行失败。 -可失败构造器总是先向上代理调用基类,`Product`的构造器 `init(name:)`。这满足了可失败构造器在触发构造失败这个行为前必须总是执行构造代理调用这个条件。 - -如果由于`name`的值为空而导致基类的构造器在构造过程中失败。则整个`CartIem`类的构造过程都将失败,后面的子类的构造过程都将不会被执行。如果基类构建成功,则继续运行子类的构造器代码。 - -如果你构造了一个`CartItem`对象,并且该对象的`name`属性不为空以及`quantity`属性为1或者更多,则构造成功: +如果你通过传入一个非空字符串 `name` 以及一个值大于等于 1 的 `quantity` 来创建一个 `CartItem` 实例,那么构造方法能够成功被执行: ```swift if let twoSocks = CartItem(name: "sock", quantity: 2) { print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)") } -// 打印 "Item: sock, quantity: 2" +// 打印 "Item: sock, quantity: 2” ``` -如果你构造一个`CartItem`对象,其`quantity`的值`0`, 则`CartItem`的可失败构造器触发构造失败的行为: + +倘若你以一个值为 0 的 `quantity` 来创建一个 `CartItem` 实例,那么将导致 `CartItem` 构造器失败: ```swift if let zeroShirts = CartItem(name: "shirt", quantity: 0) { @@ -867,10 +862,10 @@ if let zeroShirts = CartItem(name: "shirt", quantity: 0) { } else { print("Unable to initialize zero shirts") } -// 打印 "Unable to initialize zero shirts" -``` +// 打印 "Unable to initialize zero shirts” +``` -类似的, 如果你构造一个`CartItem`对象,但其`name`的值为空, 则基类`Product`的可失败构造器将触发构造失败的行为,整个`CartItem`的构造行为同样为失败: +同样地,如果你尝试传入一个值为空字符串的 `name`来创建一个 `CartItem` 实例,那么将导致父类 `Product` 的构造过程失败: ```swift if let oneUnnamed = CartItem(name: "", quantity: 1) { @@ -878,34 +873,35 @@ if let oneUnnamed = CartItem(name: "", quantity: 1) { } else { print("Unable to initialize one unnamed product") } -// 打印 "Unable to initialize one unnamed product" +// 打印 "Unable to initialize one unnamed product” ``` -###重写一个可失败构造器 + +### 重写一个可失败构造器 -就如同其它构造器一样,你也可以用子类的可失败构造器重写基类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个基类的可失败构造器。这样做的好处是,即使基类的构造器为可失败构造器,但当子类的构造器在构造过程不可能失败时,我们也可以把它修改过来。 +如同其它的构造器,你可以在子类中重写父类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器。这使你可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败。 -注意当你用一个子类的非可失败构造器重写了一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。一个非可失败的构造器永远也不能代理调用一个可失败构造器。 +注意,当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。 ->注意: -你可以用一个非可失败构造器重写一个可失败构造器,但反过来却行不通。 +> 注意 +你可以用非可失败构造器重写可失败构造器,但反过来却不行。 -下例定义了一个名为`Document`的类,这个类中的`name`属性允许为`nil`和一个非空字符串,但不能是一个空字符串: +下例定义了一个名为`Document`的类,`name`属性的值必须为一个非空字符串或`nil`,但不能是一个空字符串: ```swift class Document { var name: String? - // 该构造器构建了一个name属性值为nil的document对象 + // 该构造器创建了一个 name 属性的值为 nil 的 document 实例 init() {} - // 该构造器构建了一个name属性值为非空字符串的document对象 + // 该构造器创建了一个 name 属性的值为非空字符串的 document 实例 init?(name: String) { - if name.isEmpty { return nil } self.name = name + if name.isEmpty { return nil } } } ``` -下面这个例子,定义了一个`Document`类的子类`AutomaticallyNamedDocument`。这个子类重写了父类的两个指定构造器,确保不论是通过没有 name 参数的构造器,还是通过传一个空字符串给`init(name:)`构造器,生成的实例中的`name`属性总有初始值`"[Untitled]"`。 +下面这个例子,定义了一个`Document`类的子类`AutomaticallyNamedDocument`。这个子类重写了父类的两个指定构造器,确保了无论是使用`init()`构造器,还是使用`init(name:)`构造器并为参数传递空字符串,生成的实例中的`name`属性总有初始`"[Untitled]"`: ```swift class AutomaticallyNamedDocument: Document { @@ -924,9 +920,9 @@ class AutomaticallyNamedDocument: Document { } ``` -`AutomaticallyNamedDocument`用一个非可失败构造器`init(name:)`,重写了父类的可失败构造器`init?(name:)`。因为子类用不同的方法处理了`name`属性的值为一个空字符串的这种情况。所以子类将不再需要一个可失败的构造器,用一个非可失败版本代替了父类的版本。 +`AutomaticallyNamedDocument`用一个非可失败构造器`init(name:)`重写了父类的可失败构造器`init?(name:)`。因为子类用另一种方式处理了空字符串的情况,所以不再需要一个可失败构造器,因此子类用一个非可失败构造器代替了父类的可失败构造器。 -你可以在构造器中调用父类的可失败构造器强制解包,以实现子类的非可失败构造器。比如,下面的`UntitledDocument `子类总有值为`"[Untitled]"`的 name 属性,它在构造过程中用了父类的可失败的构造器`init(name:)`。 +你可以在子类的非可失败构造器中使用强制解包来调用父类的可失败构造器。比如,下面的`UntitledDocument`子类的`name`属性的值总是`"[Untitled]"`,它在构造过程中使用了父类的可失败构造器`init?(name:)`: ```swift class UntitledDocument: Document { @@ -935,49 +931,50 @@ class UntitledDocument: Document { } } ``` -在这个例子中,如果在调用父类的构造器`init(name:)`时传给 name 的是空字符串,那么强制解绑操作会造成运行时错误。不过,因为这里是通过字符串常量来调用它,所以并不会发生运行时错误。 -###可失败构造器 init! +在这个例子中,如果在调用父类的可失败构造器`init?(name:)`时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过非空的字符串常量来调用它,所以并不会发生运行时错误。 -通常来说我们通过在`init`关键字后添加问号的方式(`init?`)来定义一个可失败构造器,但你也可以使用通过在`init`后面添加惊叹号的方式来定义一个可失败构造器`(init!)`,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。 + +### 可失败构造器 init! -你可以在 `init? `构造器中代理调用 `init!`构造器,反之亦然。 -你也可以用 `init?`重写 `init!`,反之亦然。 -你还可以用 `init`代理调用`init!`,但这会触发一个断言: `init!` 构造器是否会触发构造失败? +通常来说我们通过在`init`关键字后添加问号的方式(`init?`)来定义一个可失败构造器,但你也可以通过在`init`后面添加惊叹号的方式来定义一个可失败构造器(`init!`),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。 + +你可以在`init?`中代理到`init!`,反之亦然。你也可以用`init?`重写`init!`,反之亦然。你还可以用`init`代理到`init!`,不过,一旦`init!`构造失败,则会触发一个断言。 -##必要构造器 +## 必要构造器 -在类的构造器前添加 `required` 修饰符表明所有该类的子类都必须实现该构造器: +在类的构造器前添加`required`修饰符表明所有该类的子类都必须实现该构造器: ```swift class SomeClass { required init() { - // 在这里添加该必要构造器的实现代码 + // 构造器的实现代码 } } ``` -在子类重写父类的必要构造器时,必须在子类的构造器前也添加`required`修饰符,这是为了保证继承链上子类的构造器也是必要构造器。在重写父类的必要构造器时,不需要添加`override`修饰符: + +在子类重写父类的必要构造器时,必须在子类的构造器前也添加`required`修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加`override`修饰符: ```swift class SomeSubclass: SomeClass { required init() { - // 在这里添加子类必要构造器的实现代码 + // 构造器的实现代码 } } ``` ->注意: -如果子类继承的构造器能满足必要构造器的需求,则你无需显示的在子类中提供必要构造器的实现。 +> 注意 +如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。 -## 通过闭包和函数来设置属性的默认值 +## 通过闭包或函数设置属性的默认值 -如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。 +如果某个存储型属性的默认值需要一些定制或设置,你可以使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。 -这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。 +这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。 -下面列举了闭包如何提供默认值的代码概要: +下面介绍了如何用闭包为属性提供默认值: ```swift class SomeClass { @@ -985,49 +982,49 @@ class SomeClass { // 在这个闭包中给 someProperty 创建一个默认值 // someValue 必须和 SomeType 类型相同 return someValue - }() + }() } ``` -注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。 +注意闭包结尾的大括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。 ->注意: -如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的`self`属性,或者调用其它的实例方法。 +> 注意 +如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的`self`属性,或者调用任何实例方法。 下面例子中定义了一个结构体`Checkerboard`,它构建了西洋跳棋游戏的棋盘: -![西洋跳棋棋盘](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Art/checkersBoard_2x.png) +![西洋跳棋棋盘](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/chessBoard_2x.png) -西洋跳棋游戏在一副黑白格交替的 10x10 的棋盘中进行。为了呈现这副游戏棋盘,`Checkerboard`结构体定义了一个属性`boardColors`,它是一个包含 100 个布尔值的数组。数组中的某元素布尔值为`true`表示对应的是一个黑格,布尔值为`false`表示对应的是一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。 +西洋跳棋游戏在一副黑白格交替的`10x10`的棋盘中进行。为了呈现这副游戏棋盘,`Checkerboard`结构体定义了一个属性`boardColors`,它是一个包含`100`个`Bool`值的数组。在数组中,值为`true`的元素表示一个黑格,值为`false`的元素表示一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。 -`boardColor`数组是通过一个闭包来初始化和组装颜色值的: +`boardColor`数组是通过一个闭包来初始化并设置颜色值的: ```swift struct Checkerboard { let boardColors: [Bool] = { var temporaryBoard = [Bool]() var isBlack = false - for i in 1...10 { - for j in 1...10 { + for i in 1...8 { + for j in 1...8 { temporaryBoard.append(isBlack) isBlack = !isBlack } isBlack = !isBlack } return temporaryBoard - }() + }() func squareIsBlackAtRow(row: Int, column: Int) -> Bool { - return boardColors[(row * 10) + column] + return boardColors[(row * 8) + column] } } ``` -每当一个新的`Checkerboard`实例创建时,对应的赋值闭包会执行,一系列颜色值会被计算出来作为默认值赋值给`boardColors`。上面例子中描述的闭包将计算出棋盘中每个格子合适的颜色,将这些颜色值保存到一个临时数组`temporaryBoard`中,并在构建完成时将此数组作为闭包返回值返回。这个返回的值将保存到`boardColors`中,并可以通`squareIsBlackAtRow`这个工具函数来查询。 +每当一个新的`Checkerboard`实例被创建时,赋值闭包会被执行,`boardColors`的默认值会被计算出来并返回。上面例子中描述的闭包将计算出棋盘中每个格子对应的颜色,并将这些值保存到一个临时数组`temporaryBoard`中,最后在构建完成时将此数组作为闭包返回值返回。这个返回的数组会保存到`boardColors`中,并可以通过工具函数`squareIsBlackAtRow`来查询: ```swift let board = Checkerboard() print(board.squareIsBlackAtRow(0, column: 1)) -// 输出 "true" -print(board.squareIsBlackAtRow(9, column: 9)) -// 输出 "false" +// 打印 "true" +print(board.squareIsBlackAtRow(7, column: 7)) +// 打印 "false" ``` diff --git a/source/chapter2/15_Deinitialization.md b/source/chapter2/15_Deinitialization.md index d93844be..f55b48aa 100755 --- a/source/chapter2/15_Deinitialization.md +++ b/source/chapter2/15_Deinitialization.md @@ -8,12 +8,18 @@ > 2.0 > 翻译+校对:[chenmingbiao](https://github.com/chenmingbiao) +> 2.1 +> 校对:[shanks](http://codebuild.me),2015-10-31 +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-14 + 本页包含内容: - [析构过程原理](#how_deinitialization_works) -- [析构器操作](#deinitializers_in_action) +- [析构器实践](#deinitializers_in_action) -析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字`deinit`来标示,类似于构造器要用`init`来标示。 +*析构器*只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字`deinit`来标示,类似于构造器要用`init`来标示。 ##析构过程原理 @@ -28,22 +34,22 @@ deinit { } ``` -析构器是在实例释放发生前被自动调用。析构器是不允许被主动调用的。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。 +析构器是在实例释放发生前被自动调用。你不能主动调用析构器。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。 -因为直到实例的析构器被调用时,实例才会被释放,所以析构器可以访问所有请求实例的属性,并且根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。 +因为直到实例的析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性,并且可以根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。 -##析构器操作 +##析构器实践 -这是一个析构器操作的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是`Bank`和`Player`。`Bank`结构体管理一个虚拟货币的流通,在这个流通中我们设定`Bank`永远不可能拥有超过 10,000 的硬币,而且在游戏中有且只能有一个`Bank`存在,因此`Bank`结构体在实现时会带有静态属性和静态方法来存储和管理其当前的状态。 +这是一个析构器实践的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是`Bank`和`Player`。`Bank`类管理一种虚拟硬币,确保流通的硬币数量永远不可能超过 10,000。在游戏中有且只能有一个`Bank`存在,因此`Bank`用类来实现,并使用类型属性和类型方法来存储和管理其当前状态。 ```swift -struct Bank { +class Bank { static var coinsInBank = 10_000 - static func vendCoins(var numberOfCoinsToVend: Int) -> Int { - numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank) - coinsInBank -= numberOfCoinsToVend - return numberOfCoinsToVend + static func vendCoins(numberOfCoinsRequested: Int) -> Int { + let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank) + coinsInBank -= numberOfCoinsToVend + return numberOfCoinsToVend } static func receiveCoins(coins: Int) { coinsInBank += coins @@ -51,13 +57,13 @@ struct Bank { } ``` -`Bank`根据它的`coinsInBank`属性来跟踪当前它拥有的硬币数量。`Bank`还提供两个方法——`vendCoins(_:)`和`receiveCoins(_:)`,分别用来处理硬币的分发和收集。 +`Bank`使用`coinsInBank`属性来跟踪它当前拥有的硬币数量。`Bank`还提供了两个方法,`vendCoins(_:)`和`receiveCoins(_:)`,分别用来处理硬币的分发和收集。 -`vendCoins(_:)`方法在bank对象分发硬币之前检查是否有足够的硬币。如果没有足够多的硬币,`Bank`会返回一个比请求时要小的数字(如果没有硬币留在bank对象中就返回 0)。`vendCoins`方法声明`numberOfCoinsToVend`为一个变量参数,这样就可以在方法体的内部修改数字,而不需要定义一个新的变量。`vendCoins`方法返回一个整型值,表明了提供的硬币的实际数目。 +`vendCoins(_:)`方法在`Bank`对象分发硬币之前检查是否有足够的硬币。如果硬币不足,`Bank`对象会返回一个比请求时小的数字(如果`Bank`对象中没有硬币了就返回`0`)。`vendCoins`方法返回一个整型值,表示提供的硬币的实际数量。 -`receiveCoins`方法只是将bank对象的硬币存储和接收到的硬币数目相加,再保存回bank对象。 +`receiveCoins(_:)`方法只是将`Bank`对象接收到的硬币数目加回硬币存储中。 -`Player`类描述了游戏中的一个玩家。每一个 player 在任何时刻都有一定数量的硬币存储在他们的钱包中。这通过 player 的`coinsInPurse`属性来体现: +`Player`类描述了游戏中的一个玩家。每一个玩家在任意时间都有一定数量的硬币存储在他们的钱包中。这通过玩家的`coinsInPurse`属性来表示: ```swift class Player { @@ -74,22 +80,21 @@ class Player { } ``` +每个`Player`实例在初始化的过程中,都从`Bank`对象获取指定数量的硬币。如果没有足够的硬币可用,`Player`实例可能会收到比指定数量少的硬币. -每个`Player`实例构造时都会设定由硬币组成的启动额度值,这些硬币在bank对象初始化的过程中得到。如果在bank对象中没有足够的硬币可用,`Player`实例可能收到比指定数目少的硬币。 - -`Player`类定义了一个`winCoins(_:)`方法,该方法从bank对象获取一定数量的硬币,并把它们添加到玩家的钱包。`Player`类还实现了一个析构器,这个析构器在`Player`实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返回给bank对象: +`Player`类定义了一个`winCoins(_:)`方法,该方法从`Bank`对象获取一定数量的硬币,并把它们添加到玩家的钱包。`Player`类还实现了一个析构器,这个析构器在`Player`实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返还给`Bank`对象: ```swift var playerOne: Player? = Player(coins: 100) print("A new player has joined the game with \(playerOne!.coinsInPurse) coins") -// 输出 "A new player has joined the game with 100 coins" -print("There are now \(Bank.coinsInBank) coins left in the bank") -// 输出 "There are now 9900 coins left in the bank" +// 打印 "A new player has joined the game with 100 coins" +print("There are now \(Bank.coinsInBank) coins left in the bank") +// 打印 "There are now 9900 coins left in the bank" ``` -一个新的`Player`实例被创建时会设定有 100 个硬币(如果bank对象中硬币的数目足够)。这`个Player`实例存储在一个名为`playerOne`的可选`Player`变量中。这里使用一个可选变量,是因为玩家可以随时离开游戏。设置为可选使得你可以跟踪当前是否有玩家在游戏中。 +创建一个`Player`实例的时候,会向`Bank`对象请求 100 个硬币,如果有足够的硬币可用的话。这个`Player`实例存储在一个名为`playerOne`的可选类型的变量中。这里使用了一个可选类型的变量,因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前是否在游戏中。 -因为`playerOne`是可选的,所以用一个感叹号(`!`)作为修饰符,每当其`winCoins(_:)`方法被调用时,`coinsInPurse`属性就会被访问并打印出它的默认硬币数目。 +因为`playerOne`是可选的,所以访问其`coinsInPurse`属性来打印钱包中的硬币数量时,使用感叹号(`!`)来解包: ```swift playerOne!.winCoins(2_000) @@ -99,14 +104,14 @@ print("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 硬币。 +这里,玩家已经赢得了 2,000 枚硬币,所以玩家的钱包中现在有 2,100 枚硬币,而`Bank`对象只剩余 7,900 枚硬币。 ```swift playerOne = nil print("PlayerOne has left the game") -// 输出 "PlayerOne has left the game" +// 打印 "PlayerOne has left the game" print("The bank now has \(Bank.coinsInBank) coins") -// 输出 "The bank now has 10000 coins" +// 打印 "The bank now has 10000 coins" ``` -玩家现在已经离开了游戏。这表明是要将可选的`playerOne`变量设置为`nil`,意思是“不存在`Player`实例”。当这种情况发生的时候,`playerOne`变量对`Player`实例的引用被破坏了。没有其它属性或者变量引用`Player`实例,因此为了清空它占用的内存从而释放它。在这发生前,其析构器会被自动调用,从而使其硬币被返回到bank对象中。 +玩家现在已经离开了游戏。这通过将可选类型的`playerOne`变量设置为`nil`来表示,意味着“没有`Player`实例”。当这一切发生时,`playerOne`变量对`Player`实例的引用被破坏了。没有其它属性或者变量引用`Player`实例,因此该实例会被释放,以便回收内存。在这之前,该实例的析构器被自动调用,玩家的硬币被返还给银行。 diff --git a/source/chapter2/16_Automatic_Reference_Counting.md b/source/chapter2/16_Automatic_Reference_Counting.md index 55b76166..319ec3a3 100755 --- a/source/chapter2/16_Automatic_Reference_Counting.md +++ b/source/chapter2/16_Automatic_Reference_Counting.md @@ -8,6 +8,13 @@ > 2.0 > 翻译+校对:[Channe](https://github.com/Channe) +> 2.1 +> 翻译:[Channe](https://github.com/Channe) +> 校对:[shanks](http://codebuild.me),[Realank](https://github.com/Realank) ,2016-01-23 +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-14 + 本页包含内容: - [自动引用计数的工作机制](#how_arc_works) @@ -19,15 +26,15 @@ Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。 -然而,在少数情况下,ARC 为了能帮助你管理内存,需要更多的关于你的代码之间关系的信息。本章描述了这些情况,并且为你示范怎样启用 ARC 来管理你的应用程序的内存。 +然而在少数情况下,为了能帮助你管理内存,ARC 需要更多的,代码之间关系的信息。本章描述了这些情况,并且为你示范怎样才能使 ARC 来管理你的应用程序的所有内存。 -> 注意: +> 注意 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。 ## 自动引用计数的工作机制 -当你每次创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。内存中会包含实例的类型信息,以及这个实例所有相关属性的值。 +当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。 此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。 @@ -35,7 +42,7 @@ Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程 为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。 -为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢的保持住,只要强引用还在,实例是不允许被销毁的。 +为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。 ## 自动引用计数实践 @@ -72,7 +79,7 @@ reference1 = Person(name: "John Appleseed") // prints "John Appleseed is being initialized” ``` -应当注意到当你调用`Person`类的构造函数的时候,"John Appleseed is being initialized”会被打印出来。由此可以确定构造函数被执行。 +应当注意到当你调用`Person`类的构造函数的时候,`“John Appleseed is being initialized”`会被打印出来。由此可以确定构造函数被执行。 由于`Person`类的新实例被赋值给了`reference1`变量,所以`reference1`到`Person`类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证`Person`实例被保持在内存中不被销毁。 @@ -92,11 +99,11 @@ reference1 = nil reference2 = nil ``` -在你清楚地表明不再使用这个`Person`实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它。 +在你清楚地表明不再使用这个`Person`实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它: ```swift reference3 = nil -// prints "John Appleseed is being deinitialized" +// 打印 “John Appleseed is being deinitialized” ``` @@ -104,11 +111,11 @@ reference3 = nil 在上面的例子中,ARC 会跟踪你所新创建的`Person`实例的引用数量,并且会在`Person`实例不再被需要时销毁它。 -然而,我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。 +然而,我们可能会写出一个类实例的强引用数永远不能变成`0`的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。 你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。具体的过程在[解决类实例之间的循环强引用](#resolving_strong_reference_cycles_between_class_instances)中有描述。不管怎样,在你学习怎样解决循环强引用之前,很有必要了解一下它是怎样产生的。 -下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person`和`Apartment`,用来建模公寓和它其中的居民: +下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person`和`Apartment`,用来建模公寓和它其中的居民: ```swift class Person { @@ -130,7 +137,7 @@ class Apartment { 每一个`Person`实例有一个类型为`String`,名字为`name`的属性,并有一个可选的初始化为`nil`的`apartment`属性。`apartment`属性是可选的,因为一个人并不总是拥有公寓。 -类似的,每个`Apartment`实例有一个叫`number`,类型为`Int`的属性,并有一个可选的初始化为`nil`的`tenant`属性。`tenant`属性是可选的,因为一栋公寓并不总是有居民。 +类似的,每个`Apartment`实例有一个叫`unit`,类型为`String`的属性,并有一个可选的初始化为`nil`的`tenant`属性。`tenant`属性是可选的,因为一栋公寓并不总是有居民。 这两个类都定义了析构函数,用以在类实例被析构的时候输出信息。这让你能够知晓`Person`和`Apartment`的实例是否像预期的那样被销毁。 @@ -163,7 +170,7 @@ unit4A!.tenant = john ![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle02_2x.png) -不幸的是,这两个实例关联后会产生一个循环强引用。`Person`实例现在有了一个指向`Apartment`实例的强引用,而`Apartment`实例也有了一个指向`Person`实例的强引用。因此,当你断开`john`和`unit4A`变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁: +不幸的是,这两个实例关联后会产生一个循环强引用。`Person`实例现在有了一个指向`Apartment`实例的强引用,而`Apartment`实例也有了一个指向`Person`实例的强引用。因此,当你断开`john`和`unit4A`变量所持有的强引用时,引用计数并不会降为`0`,实例也不会被 ARC 销毁: ```swift john = nil @@ -187,13 +194,14 @@ Swift 提供了两种办法用来解决你在使用类的属性时所遇到的 对于生命周期中会变为`nil`的实例使用弱引用。相反地,对于初始化赋值后再也不会被赋值为`nil`的实例,使用无主引用。 + ### 弱引用 弱引用不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上`weak`关键字表明这是一个弱引用。 -在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以避免循环强引用。如果引用总是有值,则可以使用无主引用,在[无主引用](#2)中有描述。在上面`Apartment`的例子中,一个公寓的生命周期中,有时是没有“居民”的,因此适合使用弱引用来解决循环强引用。 +在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以避免循环强引用。如果引用总是有值,则可以使用无主引用,在[无主引用](#unowned_references)中有描述。在上面`Apartment`的例子中,一个公寓的生命周期中,有时是没有“居民”的,因此适合使用弱引用来解决循环强引用。 -> 注意: +> 注意 > 弱引用必须被声明为变量,表明其值能在运行时被修改。弱引用不能被声明为常量。 因为弱引用可以没有值,你必须将每一个弱引用声明为可选类型。在 Swift 中,推荐使用可选类型描述可能没有值的类型。 @@ -237,7 +245,7 @@ unit4A!.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`实例的强引用了: +`Person`实例依然保持对`Apartment`实例的强引用,但是`Apartment`实例只持有对`Person`实例的弱引用。这意味着当你断开`john`变量所保持的强引用时,再也没有指向`Person`实例的强引用了: ![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference02_2x.png) @@ -245,7 +253,7 @@ unit4A!.tenant = john ```swift john = nil -// prints "John Appleseed is being deinitialized" +// 打印 “John Appleseed is being deinitialized” ``` 唯一剩下的指向`Apartment`实例的强引用来自于变量`unit4A`。如果你断开这个强引用,再也没有指向`Apartment`实例的强引用了: @@ -256,27 +264,23 @@ john = nil ```swift unit4A = nil -// prints "Apartment 4A is being deinitialized" +// 打印 “Apartment 4A is being deinitialized” ``` 上面的两段代码展示了变量`john`和`unit4A`在被赋值为`nil`后,`Person`实例和`Apartment`实例的析构函数都打印出“销毁”的信息。这证明了引用循环被打破了。 - - - >注意: - 在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被删除,就会被立即销毁,这导致弱引用并不适合上面的用途。 + + > 注意 + 在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。 - + ### 无主引用 和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用是永远有值的。因此,无主引用总是被定义为非可选类型(non-optional type)。你可以在声明属性或者变量时,在前面加上关键字`unowned`表示这是一个无主引用。 由于无主引用是非可选类型,你不需要在使用它的时候将它展开。无主引用总是可以被直接访问。不过 ARC 无法在实例被销毁后将无主引用设为`nil`,因为非可选类型的变量不允许被赋值为`nil`。 -> 注意: ->如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,你必须确保引用始终指向一个未销毁的实例。 +> 注意 +> 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,你必须确保引用始终指向一个未销毁的实例。 > 还需要注意的是如果你试图访问实例已经被销毁的无主引用,Swift 确保程序会直接崩溃,而不会发生无法预期的行为。所以你应当避免这样的事情发生。 下面的例子定义了两个类,`Customer`和`CreditCard`,模拟了银行客户和客户的信用卡。这两个类中,每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用。 @@ -310,16 +314,16 @@ class CreditCard { } ``` -> 注意: -> `CreditCard`类的`number`属性被定义为`UInt64`类型而不是`Int`类型,以确保`number`属性的存储量在32位和64位系统上都能足够容纳16位的卡号。 +> 注意 +> `CreditCard`类的`number`属性被定义为`UInt64`类型而不是`Int`类型,以确保`number`属性的存储量在 32 位和 64 位系统上都能足够容纳 16 位的卡号。 -下面的代码片段定义了一个叫`john`的可选类型`Customer`变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为`nil`。 +下面的代码片段定义了一个叫`john`的可选类型`Customer`变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为`nil`: ```swift var john: Customer? ``` -现在你可以创建`Customer`类的实例,用它初始化`CreditCard`实例,并将新创建的`CreditCard`实例赋值为客户的`card`属性。 +现在你可以创建`Customer`类的实例,用它初始化`CreditCard`实例,并将新创建的`CreditCard`实例赋值为客户的`card`属性: ```swift john = Customer(name: "John Appleseed") @@ -340,13 +344,12 @@ john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!) ```swift john = nil -// prints "John Appleseed is being deinitialized" -// prints "Card #1234567890123456 is being deinitialized" +// 打印 “John Appleseed is being deinitialized” +// 打印 ”Card #1234567890123456 is being deinitialized” ``` 最后的代码展示了在`john`变量被设为`nil`后`Customer`实例和`CreditCard`实例的构造函数都打印出了“销毁”的信息。 - ###无主引用以及隐式解析可选属性 @@ -384,36 +387,36 @@ class City { } ``` -为了建立两个类的依赖关系,`City`的构造函数有一个`Country`实例的参数,并且将实例保存为`country`属性。 +为了建立两个类的依赖关系,`City`的构造函数接受一个`Country`实例作为参数,并且将实例保存到`country`属性。 -`Country`的构造函数调用了`City`的构造函数。然而,只有`Country`的实例完全初始化完后,`Country`的构造函数才能把`self`传给`City`的构造函数。(在[两段式构造过程](./14_Initialization.html#two_phase_initialization)中有具体描述) +`Country`的构造函数调用了`City`的构造函数。然而,只有`Country`的实例完全初始化后,`Country`的构造函数才能把`self`传给`City`的构造函数。(在[两段式构造过程](./14_Initialization.html#two_phase_initialization)中有具体描述) -为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将`Country`的`capitalCity`属性声明为隐式解析可选类型的属性。这表示像其他可选类型一样,`capitalCity`属性的默认值为`nil`,但是不需要展开它的值就能访问它。(在[隐式解析可选类型](./01_The_Basics.html#implicityly_unwrapped_optionals)中有描述) +为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将`Country`的`capitalCity`属性声明为隐式解析可选类型的属性。这意味着像其他可选类型一样,`capitalCity`属性的默认值为`nil`,但是不需要展开它的值就能访问它。(在[隐式解析可选类型](./01_The_Basics.html#implicityly_unwrapped_optionals)中有描述) -由于`capitalCity`默认值为`nil`,一旦`Country`的实例在构造函数中给`name`属性赋值后,整个初始化过程就完成了。这代表一旦`name`属性被赋值后,`Country`的构造函数就能引用并传递隐式的`self`。`Country`的构造函数在赋值`capitalCity`时,就能将`self`作为参数传递给`City`的构造函数。 +由于`capitalCity`默认值为`nil`,一旦`Country`的实例在构造函数中给`name`属性赋值后,整个初始化过程就完成了。这意味着一旦`name`属性被赋值后,`Country`的构造函数就能引用并传递隐式的`self`。`Country`的构造函数在赋值`capitalCity`时,就能将`self`作为参数传递给`City`的构造函数。 以上的意义在于你可以通过一条语句同时创建`Country`和`City`的实例,而不产生循环强引用,并且`capitalCity`的属性能被直接访问,而不需要通过感叹号来展开它的可选值: ```swift var country = Country(name: "Canada", capitalName: "Ottawa") print("\(country.name)'s capital city is called \(country.capitalCity.name)") -// prints "Canada's capital city is called Ottawa" +// 打印 “Canada's capital city is called Ottawa” ``` -在上面的例子中,使用隐式解析可选值的意义在于满足了两个类构造函数的需求。`capitalCity`属性在初始化完成后,能像非可选值一样使用和存取同时还避免了循环强引用。 +在上面的例子中,使用隐式解析可选值意味着满足了类的构造函数的两个构造阶段的要求。`capitalCity`属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。 ##闭包引起的循环强引用 前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。 -循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例。这个闭包体中可能访问了实例的某个属性,例如`self.someProperty`,或者闭包中调用了实例的某个方法,例如`self.someMethod`。这两种情况都导致了闭包 “捕获" `self`,从而产生了循环强引用。 +循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如`self.someProperty`,或者闭包中调用了实例的某个方法,例如`self.someMethod()`。这两种情况都导致了闭包“捕获”`self`,从而产生了循环强引用。 -循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你也把一个引用赋值给了这个闭包。实质上,这跟之前的问题是一样的-两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。 +循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你是将这个闭包的引用赋值给了属性。实质上,这跟之前的问题是一样的——两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。 -Swift 提供了一种优雅的方法来解决这个问题,称之为闭包捕获列表(closuer capture list)。同样的,在学习如何用闭包捕获列表破坏循环强引用之前,先来了解一下这里的循环强引用是如何产生的,这对我们很有帮助。 +Swift 提供了一种优雅的方法来解决这个问题,称之为`闭包捕获列表`(closure capture list)。同样的,在学习如何用闭包捕获列表打破循环强引用之前,先来了解一下这里的循环强引用是如何产生的,这对我们很有帮助。 -下面的例子为你展示了当一个闭包引用了`self`后是如何产生一个循环强引用的。例子中定义了一个叫`HTMLElement`的类,用一种简单的模型表示 HTML 中的一个单独的元素: +下面的例子为你展示了当一个闭包引用了`self`后是如何产生一个循环强引用的。例子中定义了一个叫`HTMLElement`的类,用一种简单的模型表示 HTML 文档中的一个单独的元素: ```swift class HTMLElement { @@ -441,51 +444,49 @@ class HTMLElement { } ``` -`HTMLElement`类定义了一个`name`属性来表示这个元素的名称,例如代表段落的"p",或者代表换行的"br"。`HTMLElement`还定义了一个可选属性`text`,用来设置和展现 HTML 元素的文本。 +`HTMLElement`类定义了一个`name`属性来表示这个元素的名称,例如代表段落的`“p”`,或者代表换行的`“br”`。`HTMLElement`还定义了一个可选属性`text`,用来设置 HTML 元素呈现的文本。 除了上面的两个属性,`HTMLElement`还定义了一个`lazy`属性`asHTML`。这个属性引用了一个将`name`和`text`组合成 HTML 字符串片段的闭包。该属性是`Void -> String`类型,或者可以理解为“一个没有参数,返回`String`的函数”。 -默认情况下,闭包赋值给了`asHTML`属性,这个闭包返回一个代表 HTML 标签的字符串。如果`text`值存在,该标签就包含可选值`text`;如果`text`不存在,该标签就不包含文本。对于段落元素,根据`text`是`"some text"`还是`nil`,闭包会返回"`

some text

`"或者"`

`"。 +默认情况下,闭包赋值给了`asHTML`属性,这个闭包返回一个代表 HTML 标签的字符串。如果`text`值存在,该标签就包含可选值`text`;如果`text`不存在,该标签就不包含文本。对于段落元素,根据`text`是`“some text”`还是`nil`,闭包会返回`"

some text

"`或者`"

"`。 -可以像实例方法那样去命名、使用`asHTML`属性。然而,由于`asHTML`是闭包而不是实例方法,如果你想改变特定元素的 HTML 处理的话,可以用自定义的闭包来取代默认值。 - - -例如,可以将一个闭包赋值给`asHTML`属性,这个闭包能在文本属性是 nil 时用默认文本,这是为了避免返回一个空的 `HTML` 标签: -```swift -let heading = HTMLElement(name: "h1") -let defaultText = "some default text" -heading.asHTML = { - return "<\(heading.name)>\(heading.text ?? defaultText)" -} -print(heading.asHTML()) -// prints "

some default text

" -``` +可以像实例方法那样去命名、使用`asHTML`属性。然而,由于`asHTML`是闭包而不是实例方法,如果你想改变特定 HTML 元素的处理方式的话,可以用自定义的闭包来取代默认值。 -> 注意: -`asHTML`声明为`lazy`属性,因为只有当元素确实需要处理为HTML输出的字符串时,才需要使用`asHTML`。也就是说,在默认的闭包中可以使用`self`,因为只有当初始化完成以及`self`确实存在后,才能访问`lazy`属性。 +例如,可以将一个闭包赋值给`asHTML`属性,这个闭包能在`text`属性是`nil`时使用默认文本,这是为了避免返回一个空的 HTML 标签: -`HTMLElement`类只提供一个构造函数,通过`name`和`text`(如果有的话)参数来初始化一个元素。该类也定义了一个析构函数,当`HTMLElement`实例被销毁时,打印一条消息。 +```swift +let heading = HTMLElement(name: "h1") +let defaultText = "some default text" +heading.asHTML = { + return "<\(heading.name)>\(heading.text ?? defaultText)" +} +print(heading.asHTML()) +// 打印 “

some default text

” +``` -下面的代码展示了如何用`HTMLElement`类创建实例并打印消息。 +> 注意 +`asHTML`声明为`lazy`属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用`asHTML`。也就是说,在默认的闭包中可以使用`self`,因为只有当初始化完成以及`self`确实存在后,才能访问`lazy`属性。 + +`HTMLElement`类只提供了一个构造函数,通过`name`和`text`(如果有的话)参数来初始化一个新元素。该类也定义了一个析构函数,当`HTMLElement`实例被销毁时,打印一条消息。 + +下面的代码展示了如何用`HTMLElement`类创建实例并打印消息: ```swift var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML()) -// prints"hello, world" +// 打印 “

hello, world

” ``` ->注意: -上面的`paragraph`变量定义为`可选HTMLElement`,因此我们可以赋值`nil`给它来演示循环强引用。 +> 注意 +上面的`paragraph`变量定义为可选类型的`HTMLElement`,因此我们可以赋值`nil`给它来演示循环强引用。 -不幸的是,上面写的`HTMLElement`类产生了类实例和`asHTML`默认值的闭包之间的循环强引用。循环强引用如下图所示: +不幸的是,上面写的`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#capturing_values))。 ->注意: +> 注意 虽然闭包多次使用了`self`,它只捕获`HTMLElement`实例的一个强引用。 如果设置`paragraph`变量为`nil`,打破它持有的`HTMLElement`实例的强引用,`HTMLElement`实例和它的闭包都不会被销毁,也是因为循环强引用: @@ -494,26 +495,27 @@ print(paragraph!.asHTML()) paragraph = nil ``` -注意`HTMLElementdeinitializer`中的消息并没有被打印,证明了`HTMLElement`实例并没有被销毁。 +注意,`HTMLElement`的析构函数中的消息并没有被打印,证明了`HTMLElement`实例并没有被销毁。 ##解决闭包引起的循环强引用 在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。 ->注意: +> 注意 Swift 有如下要求:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod()`(而不只是`someProperty`或`someMethod()`)。这提醒你可能会一不小心就捕获了`self`。 + ###定义捕获列表 -捕获列表中的每一项都由一对元素组成,一个元素是`weak`或`unowned`关键字,另一个元素是类实例的引用(如`self`)或初始化过的变量(如`delegate = self.delegate!`)。这些项在方括号中用逗号分开。 +捕获列表中的每一项都由一对元素组成,一个元素是`weak`或`unowned`关键字,另一个元素是类实例的引用(例如`self`)或初始化过的变量(如`delegate = self.delegate!`)。这些项在方括号中用逗号分开。 如果闭包有参数列表和返回类型,把捕获列表放在它们前面: ```swift lazy var someClosure: (Int, String) -> String = { [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in - // closure body goes here + // 这里是闭包的函数体 } ``` @@ -522,17 +524,18 @@ lazy var someClosure: (Int, String) -> String = { ```swift lazy var someClosure: Void -> String = { [unowned self, weak delegate = self.delegate!] in - // closure body goes here + // 这里是闭包的函数体 } ``` + ###弱引用和无主引用 -在闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。 +在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为`无主引用`。 -相反的,在被捕获的引用可能会变为`nil`时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为`nil`。这使我们可以在闭包体内检查它们是否存在。 +相反的,在被捕获的引用可能会变为`nil`时,将闭包内的捕获定义为`弱引用`。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为`nil`。这使我们可以在闭包体内检查它们是否存在。 ->注意: +> 注意 如果被捕获的引用绝对不会变为`nil`,应该用无主引用,而不是弱引用。 前面的`HTMLElement`例子中,无主引用是正确的解决循环强引用的方法。这样编写`HTMLElement`类来避免循环强引用: @@ -564,23 +567,25 @@ class HTMLElement { } ``` -上面的`HTMLElement`实现和之前的实现一致,除了在`asHTML`闭包中多了一个捕获列表。这里,捕获列表是`[unowned self]`,表示“用无主引用而不是强引用来捕获`self`”。 +上面的`HTMLElement`实现和之前的实现一致,除了在`asHTML`闭包中多了一个捕获列表。这里,捕获列表是`[unowned self]`,表示“将`self`捕获为无主引用而不是强引用”。 和之前一样,我们可以创建并打印`HTMLElement`实例: ```swift var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML()) -// prints "

hello, world

" +// 打印 “

hello, world

” ``` 使用捕获列表后引用关系如下图所示: ![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle02_2x.png) -这一次,闭包以无主引用的形式捕获`self`,并不会持有`HTMLElement`实例的强引用。如果将`paragraph`赋值为`nil`,`HTMLElement`实例将会被销毁,并能看到它的析构函数打印出的消息。 +这一次,闭包以无主引用的形式捕获`self`,并不会持有`HTMLElement`实例的强引用。如果将`paragraph`赋值为`nil`,`HTMLElement`实例将会被销毁,并能看到它的析构函数打印出的消息: ```swift paragraph = nil -// prints "p is being deinitialized" +// 打印 “p is being deinitialized” ``` + +你可以查看[捕获列表](../chapter3/04_Expressions.html)章节,获取更多关于捕获列表的信息。 diff --git a/source/chapter2/17_Optional_Chaining.md b/source/chapter2/17_Optional_Chaining.md index 43ee5426..3c5d2ac8 100755 --- a/source/chapter2/17_Optional_Chaining.md +++ b/source/chapter2/17_Optional_Chaining.md @@ -1,4 +1,4 @@ -# 可空链式调用(Optional Chaining) +# 可选链式调用(Optional Chaining) ----------------- @@ -9,23 +9,39 @@ > 2.0 > 翻译+校对:[lyojo](https://github.com/lyojo) +> 2.1 +> 校对:[shanks](http://codebuild.me),2015-10-31 +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-15 -可空链式调用(Optional Chaining)是一种可以请求和调用属性、方法及下标的过程,它的可空性体现于请求或调用的目标当前可能为空(nil)。如果可空的目标有值,那么调用就会成功;如果选择的目标为空(nil),那么这种调用将返回空(nil)。多个连续的调用可以被链接在一起形成一个调用链,如果其中任何一个节点为空(nil)将导致整个链调用失败。 +本页包含内容: -> -注意: -Swift 的可空链式调用和 Objective-C 中的消息为空有些相像,但是 Swift 可以使用在任意类型中,并且能够检查调用是否成功。 +- [使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping) +- [为可选链式调用定义模型类](#defining_model_classes_for_optional_chaining) +- [通过可选链式调用访问属性](#accessing_properties_through_optional_chaining) +- [通过可选链式调用调用方法](#calling_methods_through_optional_chaining) +- [通过可选链式调用访问下标](#accessing_subscripts_through_optional_chaining) +- [连接多层可选链式调用](#linking_multiple_levels_of_chaining) +- [在方法的可选返回值上进行可选链式调用](#chaining_on_methods_with_optional_return_values) + +可选链式调用(Optional Chaining)是一种可以在当前值可能为`nil`的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是`nil`,那么调用将返回`nil`。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为`nil`,整个调用链都会失败,即返回`nil`。 + +> 注意 +Swift 的可选链式调用和 Objective-C 中向`nil`发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。 -##使用可空链式调用来强制展开 -通过在想调用非空的属性、方法、或下标的可空值(optional value)后面放一个问号,可以定义一个可空链。这一点很像在可空值后面放一个叹号(!)来强制展开其中值。它们的主要的区别在于当可空值为空时可空链式只是调用失败,然而强制展开将会触发运行时错误。 +## 使用可选链式调用代替强制展开 -为了反映可空链式调用可以在空对象(nil)上调用,不论这个调用的属性、方法、下标等返回的值是不是可空值,它的返回结果都是一个可空值。你可以利用这个返回值来判断你的可空链式调用是否调用成功,如果调用有返回值则说明调用成功,返回`nil`则说明调用失败。 +通过在想调用的属性、方法、或下标的可选值(optional value)后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。 -特别地,可空链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可空类型值。当可空链式调用成功时,一个本应该返回`Int`的类型的结果将会返回`Int?`类型。 +为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回`nil`则说明调用失败。 -下面几段代码将解释可空链式调用和强制展开的不同。 -首先定义两个类`Person`和`Residence`。 +特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是`Int`类型,则会变为`Int?`类型。 + +下面几段代码将解释可选链式调用和强制展开的不同。 + +首先定义两个类`Person`和`Residence`: ```swift class Person { @@ -37,24 +53,24 @@ class Residence { } ``` -`Residence`有一个`Int`类型的属性`numberOfRooms`,其默认值为1。`Person`具有一个可空的`residence`属性,其类型为`Residence?`。 - -如果创建一个新的`Person`实例,因为它的`residence`属性是可空的,`john`属性将初始化为`nil`: +`Residence`有一个`Int`类型的属性`numberOfRooms`,其默认值为`1`。`Person`具有一个可选的`residence`属性,其类型为`Residence?`。 + +假如你创建了一个新的`Person`实例,它的`residence`属性由于是是可选型而将初始化为`nil`,在下面的代码中,`john`有一个值为`nil`的`residence`属性: ```swift let john = Person() ``` -如果使用叹号(!)强制展开获得这个`john`的`residence`属性中的`numberOfRooms`值,会触发运行时错误,因为这时没有可以展开的`residence`: +如果使用叹号(`!`)强制展开获得这个`john`的`residence`属性中的`numberOfRooms`值,会触发运行时错误,因为这时`residence`没有可以展开的值: ```swift let roomCount = john.residence!.numberOfRooms -// this triggers a runtime error +// 这会引发运行时错误 ``` -`john.residence`非空的时候,上面的调用成功,并且把`roomCount`设置为`Int`类型的房间数量。正如上面说到的,当`residence`为空的时候上面这段代码会触发运行时错误。 +`john.residence`为非`nil`值的时候,上面的调用会成功,并且把`roomCount`设置为`Int`类型的房间数量。正如上面提到的,当`residence`为`nil`的时候上面这段代码会触发运行时错误。 -可空链式调用提供了一种另一种访问`numberOfRooms`的方法,使用问号(?)来代替原来叹号(!)的位置: +可选链式调用提供了另一种访问`numberOfRooms`的方式,使用问号(`?`)来替代原来的叹号(`!`): ```swift if let roomCount = john.residence?.numberOfRooms { @@ -62,22 +78,22 @@ if let roomCount = john.residence?.numberOfRooms { } else { print("Unable to retrieve the number of rooms.") } -// prints "Unable to retrieve the number of rooms." +// 打印 “Unable to retrieve the number of rooms.” ``` -在`residence`后面添加问号之后,Swift就会在`residence`不为空的情况下访问`numberOfRooms`。 +在`residence`后面添加问号之后,Swift 就会在`residence`不为`nil`的情况下访问`numberOfRooms`。 -因为访问`numberOfRooms`有可能失败,可空链式调用会返回`Int?`类型,或称为“可空的Int”。如上例所示,当`residence`为`nil`的时候,可空的`Int`将会为`nil`,表明无法访问`numberOfRooms`。 +因为访问`numberOfRooms`有可能失败,可选链式调用会返回`Int?`类型,或称为“可选的 `Int`”。如上例所示,当`residence`为`nil`的时候,可选的`Int`将会为`nil`,表明无法访问`numberOfRooms`。访问成功时,可选的`Int`值会通过可选绑定展开,并赋值给非可选类型的`roomCount`常量。 -要注意的是,即使`numberOfRooms`是不可空的`Int`时,这一点也成立。只要是通过可空链式调用就意味着最后`numberOfRooms`返回一个`Int?`而不是`Int`。 +要注意的是,即使`numberOfRooms`是非可选的`Int`时,这一点也成立。只要使用可选链式调用就意味着`numberOfRooms`会返回一个`Int?`而不是`Int`。 -通过赋给`john.residence`一个`Residence`的实例变量: +可以将一个`Residence`的实例赋给`john.residence`,这样它就不再是`nil`了: ```swift john.residence = Residence() ``` -这样`john.residence`不为`nil`了。现在就可以正常访问`john.residence.numberOfRooms`,其值为默认的1,类型为`Int?`: +`john.residence`现在包含一个实际的`Residence`实例,而不再是`nil`。如果你试图使用先前的可选链式调用访问`numberOfRooms`,它现在将返回值为`1`的`Int?`类型的值: ```swift if let roomCount = john.residence?.numberOfRooms { @@ -85,15 +101,17 @@ if let roomCount = john.residence?.numberOfRooms { } else { print("Unable to retrieve the number of rooms.") } -// prints "John's residence has 1 room(s)." +// 打印 “John's residence has 1 room(s).” ``` -##为可空链式调用定义模型类 -通过使用可空链式调用可以调用多层属性,方法,和下标。这样可以通过各种模型向下访问各种子属性。并且判断能否访问子属性的属性,方法或下标。 + +## 为可选链式调用定义模型类 -下面这段代码定义了四个模型类,这些例子包括多层可空链式调用。为了方便说明,在`Person`和`Residence`的基础上增加了`Room`和`Address`,以及相关的属性,方法以及下标。 +通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标。 -Person类定义基本保持不变: +下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在`Person`和`Residence`的基础上增加了`Room`类和`Address`类,以及相关的属性、方法以及下标。 + +`Person`类的定义基本保持不变: ```swift class Person { @@ -101,7 +119,7 @@ class Person { } ``` -`Residence`类比之前复杂些,增加了一个`Room`类型的空数组`room`: +`Residence`类比之前复杂些,增加了一个名为`rooms`的变量属性,该属性被初始化为`[Room]`类型的空数组: ```swift class Residence { @@ -124,9 +142,15 @@ class Residence { } ``` -现在`Residence`有了一个存储`Room`类型的数组,`numberOfRooms`属性需要计算,而不是作为单纯的变量。计算后的`numberOfRooms`返回`rooms`数组的`count`属性值。现在的`Residence`还提供访问`rooms`数组的快捷方式, 通过可读写的下标来访问指定位置的数组元素。此外,还提供`printNumberOfRooms`方法,这个方法的作用就是输出这个房子中房间的数量。最后,`Residence`定义了一个可空属性`address`,其类型为`Address?`。`Address`类的定义在下面会说明。 +现在`Residence`有了一个存储`Room`实例的数组,`numberOfRooms`属性被实现为计算型属性,而不是存储型属性。`numberOfRooms`属性简单地返回`rooms`数组的`count`属性的值。 -类`Room`是一个简单类,只包含一个属性`name`,以及一个初始化函数: +`Residence`还提供了访问`rooms`数组的快捷方式,即提供可读写的下标来访问`rooms`数组中指定位置的元素。 + +此外,`Residence`还提供了`printNumberOfRooms()`方法,这个方法的作用是打印`numberOfRooms`的值。 + +最后,`Residence`还定义了一个可选属性`address`,其类型为`Address?`。`Address`类的定义在下面会说明。 + +`Room`类是一个简单类,其实例被存储在`rooms`数组中。该类只包含一个属性`name`,以及一个用于将该属性设置为适当的房间名的初始化函数: ```swift class Room { @@ -135,7 +159,7 @@ class Room { } ``` -最后一个类是`Address`,这个类有三个`String?`类型的可空属性。`buildingName`以及`buildingNumber`属性表示建筑的名称和号码,用来表示某个特定的建筑。第三个属性表示建筑所在街道的名称: +最后一个类是`Address`,这个类有三个`String?`类型的可选属性。`buildingName`以及`buildingNumber`属性分别表示某个大厦的名称和号码,第三个属性`street`表示大厦所在街道的名称: ```swift class Address { @@ -145,8 +169,8 @@ class Address { func buildingIdentifier() -> String? { if buildingName != nil { return buildingName - } else if buildingNumber != nil { - return buildingNumber + } else if buildingNumber != nil && street != nil { + return "\(buildingNumber) \(street)" } else { return nil } @@ -154,12 +178,14 @@ class Address { } ``` -类`Address`提供`buildingIdentifier()`方法,返回值为`String?`。 如果`buildingName`不为空则返回`buildingName`, 如果`buildingNumber`不为空则返回`buildingNumber`。如果这两个属性都为空则返回`nil`。 +`Address`类提供了`buildingIdentifier()`方法,返回值为`String?`。 如果`buildingName`有值则返回`buildingName`。或者,如果`buildingNumber`和`street`均有值则返回`buildingNumber`。否则,返回`nil`。 -##通过可空链式调用访问属性 -正如[使用可空链式调用来强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可空链式调用访问属性的可空值,并且判断访问是否成功。 + +## 通过可选链式调用访问属性 -下面的代码创建了一个`Person`实例,然后访问`numberOfRooms`属性: +正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。 + +下面的代码创建了一个`Person`实例,然后像之前一样,尝试访问`numberOfRooms`属性: ```swift let john = Person() @@ -168,12 +194,12 @@ if let roomCount = john.residence?.numberOfRooms { } else { print("Unable to retrieve the number of rooms.") } -// prints "Unable to retrieve the number of rooms." +// 打印 “Unable to retrieve the number of rooms.” ``` -因为`john.residence`为`nil`,所以毫无疑问这个可空链式调用失败。 +因为`john.residence`为`nil`,所以这个可选链式调用依旧会像先前一样失败。 -通过可空链式调用来设定属性值: +还可以通过可选链式调用来设置属性值: ```swift let someAddress = Address() @@ -182,11 +208,31 @@ someAddress.street = "Acacia Road" john.residence?.address = someAddress ``` -在这个例子中,通过`john.residence`来设定`address`属性也是不行的,因为`john.residence`为`nil`。 +在这个例子中,通过`john.residence`来设定`address`属性也会失败,因为`john.residence`当前为`nil`。 -##通过可空链式调用来调用方法 -可以通过可空链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。 -`Residence`中的`printNumberOfRooms()`方法输出当前的`numberOfRooms`值: +上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建`Address`实例,然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”,这使你能验证等号右侧的代码是否被执行。 + +```swift +func createAddress() -> Address { + print("Function was called.") + + let someAddress = Address() + someAddress.buildingNumber = "29" + someAddress.street = "Acacia Road" + + return someAddress +} +john.residence?.address = createAddress() +``` + +没有任何打印消息,可以看出`createAddress()`函数并未被执行。 + + +## 通过可选链式调用调用方法 + +可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。 + +`Residence`类中的`printNumberOfRooms()`方法打印当前的`numberOfRooms`值,如下所示: ```swift func printNumberOfRooms() { @@ -194,9 +240,9 @@ func printNumberOfRooms() { } ``` -这个方法没有返回值。但是没有返回值的方法隐式返回`Void`类型,如[无返回值函数](./06_Functions.html#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回()或者空的元组。 +这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型`Void`,如[无返回值函数](./06_Functions.html#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回`()`,或者说空的元组。 -如果在可空值上通过可空链式调用来调用这个方法,这个方法的返回类型为`Void?`,而不是`Void`,因为通过可空链式调用得到的返回值都是可空的。这样我们就可以使用`if`语句来判断能否成功调用`printNumberOfRooms()`方法,即使方法本身没有定义返回值。通过返回值是否为`nil`可以判断调用是否成功: +如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是`Void?`,而不是`Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用`if`语句来判断能否成功调用`printNumberOfRooms()`方法,即使方法本身没有定义返回值。通过判断返回值是否为`nil`可以判断调用是否成功: ```swift if john.residence?.printNumberOfRooms() != nil { @@ -204,10 +250,10 @@ if john.residence?.printNumberOfRooms() != nil { } else { print("It was not possible to print the number of rooms.") } -// prints "It was not possible to print the number of rooms." +// 打印 “It was not possible to print the number of rooms.” ``` -同样的,可以判断通过可空链式调用来给属性赋值是否成功。在上面的例子中,我们尝试给`john.residence`中的`address`属性赋值,即使`residence`为`nil`。通过可空链式调用给属性赋值会返回`Void?`,通过判断返回值是否为`nil`可以知道赋值是否成功: +同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的[通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)的例子中,我们尝试给`john.residence`中的`address`属性赋值,即使`residence`为`nil`。通过可选链式调用给属性赋值会返回`Void?`,通过判断返回值是否为`nil`就可以知道赋值是否成功: ```swift if (john.residence?.address = someAddress) != nil { @@ -215,17 +261,18 @@ if (john.residence?.address = someAddress) != nil { } else { print("It was not possible to set the address.") } -// prints "It was not possible to set the address." +// 打印 “It was not possible to set the address.” ``` + +## 通过可选链式调用访问下标 -##通过可空链式调用来访问下标 -通过可空链式调用,我们可以用下标来对可空值进行读取或写入,并且判断下标调用是否成功。 -> -注意: -当通过可空链式调用访问可空值的下标的时候,应该将问号放在下标方括号的前面而不是后面。可空链式调用的问号一般直接跟在可空表达式的后面。 +通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。 -下面这个例子用下标访问`john.residence`中`rooms`数组中第一个房间的名称,因为`john.residence`为`nil`,所以下标调用毫无疑问失败了: +> 注意 +通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。 + +下面这个例子用下标访问`john.residence`属性存储的`Residence`实例的`rooms`数组中的第一个房间的名称,因为`john.residence`为`nil`,所以下标调用失败了: ```swift if let firstRoomName = john.residence?[0].name { @@ -233,22 +280,20 @@ if let firstRoomName = john.residence?[0].name { } else { print("Unable to retrieve the first room name.") } -// prints "Unable to retrieve the first room name." +// 打印 “Unable to retrieve the first room name.” ``` +在这个例子中,问号直接放在`john.residence`的后面,并且在方括号的前面,因为`john.residence`是可选值。 -在这个例子中,问号直接放在`john.residence`的后面,并且在方括号的前面,因为`john.residence`是可空值。 - -类似的,可以通过下标,用可空链式调用来赋值: +类似的,可以通过下标,用可选链式调用来赋值: ```swift john.residence?[0] = Room(name: "Bathroom") ``` - 这次赋值同样会失败,因为`residence`目前是`nil`。 -如果你创建一个`Residence`实例,添加一些`Room`实例并赋值给`john.residence`,那就可以通过可选链和下标来访问数组中的元素: +如果你创建一个`Residence`实例,并为其`rooms`数组添加一些`Room`实例,然后将`Residence`实例赋值给`john.residence`,那就可以通过可选链和下标来访问数组中的元素: ```swift let johnsHouse = Residence() @@ -261,36 +306,40 @@ if let firstRoomName = john.residence?[0].name { } else { print("Unable to retrieve the first room name.") } -// prints "The first room name is Living Room." +// 打印 “The first room name is Living Room.” ``` -##访问可空类型的下标: -如果下标返回可空类型值,比如Swift中`Dictionary`的`key`下标。可以在下标的闭合括号后面放一个问号来链接下标的可空返回值: + +### 访问可选类型的下标 + +如果下标返回可选类型值,比如 Swift 中`Dictionary`类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用: ```swift var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]] testScores["Dave"]?[0] = 91 -testScores["Bev"]?[0]++ +testScores["Bev"]?[0] += 1 testScores["Brian"]?[0] = 72 -// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81] +// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81] ``` -上面的例子中定义了一个`testScores`数组,包含了两个键值对, 把`String`类型的`key`映射到一个整形数组。这个例子用可空链式调用把“Dave”数组中第一个元素设为91,把”Bev”数组的第一个元素+1,然后尝试把”Brian”数组中的第一个元素设为72。前两个调用是成功的,因为这两个`key`存在。但是key“Brian”在字典中不存在,所以第三个调用失败。 +上面的例子中定义了一个`testScores`数组,包含了两个键值对,把`String`类型的键映射到一个`Int`值的数组。这个例子用可选链式调用把`"Dave"`数组中第一个元素设为`91`,把`"Bev"`数组的第一个元素`+1`,然后尝试把`"Brian"`数组中的第一个元素设为`72`。前两个调用成功,因为`testScores`字典中包含`"Dave"`和`"Bev"`这两个键。但是`testScores`字典中没有`"Brian"`这个键,所以第三个调用失败。 -##多层链接 -可以通过多个链接多个可空链式调用来向下访问属性,方法以及下标。但是多层可空链式调用不会添加返回值的可空性。 + +## 连接多层可选链式调用 + +可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。 也就是说: -+ 如果你访问的值不是可空的,通过可空链式调用将会放回可空值。 -+ 如果你访问的值已经是可空的,通过可空链式调用不会变得“更”可空。 ++ 如果你访问的值不是可选的,可选链式调用将会返回可选值。 ++ 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。 因此: -+ 通过可空链式调用访问一个`Int`值,将会返回`Int?`,不过进行了多少次可空链式调用。 -+ 类似的,通过可空链式调用访问`Int?`值,并不会变得更加可空。 ++ 通过可选链式调用访问一个`Int`值,将会返回`Int?`,无论使用了多少层可选链式调用。 ++ 类似的,通过可选链式调用访问`Int?`值,依旧会返回`Int?`值,并不会返回`Int??`。 -下面的例子访问`john`中的`residence`中的`address`中的`street`属性。这里使用了两层可空链式调用,`residence`以及`address`,这两个都是可空值。 +下面的例子尝试访问`john`中的`residence`属性中的`address`属性中的`street`属性。这里使用了两层可选链式调用,`residence`以及`address`都是可选值: ```swift if let johnsStreet = john.residence?.address?.street { @@ -298,14 +347,14 @@ if let johnsStreet = john.residence?.address?.street { } else { print("Unable to retrieve the address.") } -// prints "Unable to retrieve the address." +// 打印 “Unable to retrieve the address.” ``` -`john.residence`包含`Residence`实例,但是`john.residence.address`为`nil`。因此,不能访问`john.residence?.address?.street`。 +`john.residence`现在包含一个有效的`Residence`实例。然而,`john.residence.address`的值当前为`nil`。因此,调用`john.residence?.address?.street`会失败。 -需要注意的是,上面的例子中,`street`的属性为`String?`。`john.residence?.address?.street`的返回值也依然是`String?`,即使已经进行了两次可空的链式调用。 +需要注意的是,上面的例子中,`street`的属性为`String?`。`john.residence?.address?.street`的返回值也依然是`String?`,即使已经使用了两层可选链式调用。 -如果把`john.residence.address`指向一个实例,并且为`address`中的`street`属性赋值,我们就能过通过可空链式调用来访问`street`属性。 +如果为`john.residence.address`赋值一个`Address`实例,并且为`address`中的`street`属性设置一个有效值,我们就能过通过可选链式调用来访问`street`属性: ```swift let johnsAddress = Address() @@ -318,24 +367,26 @@ if let johnsStreet = john.residence?.address?.street { } else { print("Unable to retrieve the address.") } -// prints "John's street name is Laurel Street." +// 打印 “John's street name is Laurel Street.” ``` -在上面的例子中,因为`john.residence`是一个可用的`Residence`实例,所以对`john.residence`的`address`属性赋值成功。 +在上面的例子中,因为`john.residence`包含一个有效的`Residence`实例,所以对`john.residence`的`address`属性赋值将会成功。 -##对返回可空值的函数进行链接 -上面的例子说明了如何通过可空链式调用来获取可空属性值。我们还可以通过可空链式调用来调用返回可空值的方法,并且可以继续对可空值进行链接。 + +## 在方法的可选返回值上进行可选链式调用 -在下面的例子中,通过可空链式调用来调用`Address`的`buildingIdentifier()`方法。这个方法返回`String?`类型。正如上面所说,通过可空链式调用的方法的最终返回值还是`String?`: +上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。 + +在下面的例子中,通过可选链式调用来调用`Address`的`buildingIdentifier()`方法。这个方法返回`String?`类型的值。如上所述,通过可选链式调用来调用该方法,最终的返回值依旧会是`String?`类型: ```swift if let buildingIdentifier = john.residence?.address?.buildingIdentifier() { print("John's building identifier is \(buildingIdentifier).") } -// prints "John's building identifier is The Larches." +// 打印 “John's building identifier is The Larches.” ``` -如果要进一步对方法的返回值进行可空链式调用,在方法`buildingIdentifier()`的圆括号后面加上问号: +如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可: ```swift if let beginsWithThe = @@ -346,9 +397,8 @@ if let beginsWithThe = print("John's building identifier does not begin with \"The\".") } } -// prints "John's building identifier begins with "The"." +// 打印 “John's building identifier begins with "The".” ``` -> -注意: -在上面的例子中在,在方法的圆括号后面加上问号是因为`buildingIdentifier()`的返回值是可空值,而不是方法本身是可空的。 +> 注意 +在上面的例子中,在方法的圆括号后面加上问号是因为你要在`buildingIdentifier()`方法的可选返回值上进行可选链式调用,而不是方法本身。 diff --git a/source/chapter2/18_Error_Handling.md b/source/chapter2/18_Error_Handling.md index 9fbd27ab..a501ca6f 100755 --- a/source/chapter2/18_Error_Handling.md +++ b/source/chapter2/18_Error_Handling.md @@ -3,63 +3,75 @@ > 2.1 > 翻译+校对:[lyojo](https://github.com/lyojo) [ray16897188](https://github.com/ray16897188) 2015-10-23 +> 校对:[shanks](http://codebuild.me) 2015-10-24 +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-15 -> 定稿:[shanks](http://codebuild.me) 2015-10-24 +本页包含内容: -本页包含内容: - -- [表示并抛出错误](#representing_and_throwing_errors) -- [处理错误](#handling_errors) +- [表示并抛出错误](#representing_and_throwing_errors) +- [处理错误](#handling_errors) - [指定清理操作](#specifying_cleanup_actions) -*错误处理(Error handling)*是响应错误以及从错误中恢复的过程。swift提供了在运行对可恢复错误抛出,捕获,传送和操作的高级支持。 +*错误处理(Error handling)*是响应错误以及从错误中恢复的过程。Swift 提供了在运行时对可恢复错误的抛出、捕获、传递和操作的一流支持。 -某些操作并不能总是保证执行所有代码都可以执行或总会产出有用的结果。可选类型用来表示值可能为空,但是当执行失败的时候,通常要去了解此次失败是由何引起,你的代码就可以做出与之相应的反应。 +某些操作无法保证总是执行完所有代码或总是生成有用的结果。可选类型可用来表示值缺失,但是当某个操作失败时,最好能得知失败的原因,从而可以作出相应的应对。 -举个例子,假如有个从磁盘上的某个文件读取以并做数据处理的任务,该任务会有多种可能失败的情形,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些错误情形可以让程序解决并处理一部分错误,然后把它解决不了的错误报告给用户。 +举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。 -> -注意: -Swift中的错误处理涉及到错误处理模式,这会用到Cocoa和Objective-C中的`NSError`。关于这个class的更多信息请参见:[ Using Swift with Cocoa and Objective-C (Swift 2.1)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)中的[错误处理](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10)。 +> 注意 +Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的`NSError`。关于这个类的更多信息请参见 [Using Swift with Cocoa and Objective-C (Swift 2.2)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中的[错误处理](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10)。 ##表示并抛出错误 -在Swift中,错误用遵循`ErrorType`协议类型的值来表示。这个空协议表示一种可以用做错误处理的类型。 -Swift的枚举类型尤为适合塑造一组相关的错误情形(error conditions),枚举的关联值(assiciated values)还可以提供额外信息,表示某些错误情形的性质。比如你可以这样表示在一个游戏中操作自动贩卖机会出现的错误情形: + +在 Swift 中,错误用符合`ErrorType`协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。 + +Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,你可以这样表示在一个游戏中操作自动贩卖机时可能会出现的错误状态: ```swift enum VendingMachineError: ErrorType { - case InvalidSelection //选择无效 - case InsufficientFunds(coinsNeeded: Int) //金额不足 - case OutOfStock //缺货 + case InvalidSelection //选择无效 + case InsufficientFunds(coinsNeeded: Int) //金额不足 + case OutOfStock //缺货 } ``` -抛出一个错误会让你对所发生的意外情况做出提示,表示正常的执行流程不能被执行下去。抛出错误使用`throws`关键字。比如下面的代码抛出一个错误,提示贩卖机还需要5个硬币: + +抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用`throw`关键字。例如,下面的代码抛出一个错误,提示贩卖机还需要`5`个硬币: ```swift throw VendingMachineError.InsufficientFunds(coinsNeeded: 5) ``` + ##处理错误 -某个错误被抛出时,那个地方的某部分代码必须要负责处理这个错误 - 比如纠正这个问题、尝试另外一种方式、或是给用户提示这个错误。 -Swift中有 4 种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用`do-catch`语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。每种方式在下面相应小节都有描述。 -当一个函数抛出一个错误时,你的程序流程会发生改变,所以关键是你要迅速的标识出代码中抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数,方法,或者构造器之前,加上`try`关键字 - 或者`try?`或者`try!`的变体。这些关键字在下面的小节中有具体讲解。 ->注意 ->Swift中的错误处理和其他语言中的用`try`,`catch`和`throw`的异常处理(exception handling)很像。和其他语言中(包括Objective-C)的异常处理不同的是,Swift中的错误处理并不涉及堆栈解退(Stack unwinding),这是一个计算昂贵(computationally expensive)的过程。就此而言,`throw`语句的性能特性是可以和`return`语句相当的。 -###用throwing函数传递错误 -用`throws`关键字标来识一个可抛出错误的函数,方法或是构造器。在函数声明中的参数列表之后加上`throws`。一个标识了`throws`的函数被称作*throwing函数*。如果这个函数还有返回值类型,`throws`关键词需要写在箭头(->)的前面。 +某个错误被抛出时,附近的某部分代码必须负责处理这个错误,例如纠正这个问题、尝试另外一种方式、或是向用户报告错误。 + +Swift 中有`4`种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用`do-catch`语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。每种方式在下面的小节中都有描述。 + +当一个函数抛出一个错误时,你的程序流程会发生改变,所以重要的是你能迅速识别代码中会抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数、方法或者构造器之前,加上`try`关键字,或者`try?`或`try!`这种变体。这些关键字在下面的小节中有具体讲解。 + +> 注意 +> Swift 中的错误处理和其他语言中用`try`,`catch`和`throw`进行异常处理很像。和其他语言中(包括 Objective-C )的异常处理不同的是,Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,`throw`语句的性能特性是可以和`return`语句相媲美的。 + + +### 用 throwing 函数传递错误 + +为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上`throws`关键字。一个标有`throws`关键字的函数被称作*throwing 函数*。如果这个函数指明了返回值类型,`throws`关键词需要写在箭头(`->`)的前面。 ```swift func canThrowErrors() throws -> String func cannotThrowErrors() -> String ``` -一个throwing函数从其内部抛出错误,并传递到该函数被调用时所在的区域中。 ->注意 -只有throwing函数可以传递错误。任何在某个非throwing函数内部抛出的错误只能在此函数内部处理 -下面的例子中`VendingMechine`类有一个`vend(itemNamed:)`方法,如果需要的物品不存在,缺货或者花费超过了已投入金额,该方法就会抛出一个相称的`VendingMachineError`。 +一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。 +> 注意 +只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。 + +下面的例子中,`VendingMechine`类有一个`vend(itemNamed:)`方法,如果请求的物品不存在、缺货或者投入金额小于物品价格,该方法就会抛出一个相应的`VendingMachineError`: ```swift struct Item { @@ -79,7 +91,7 @@ class VendingMachine { } func vend(itemNamed name: String) throws { - guard var item = inventory[name] else { + guard let item = inventory[name] else { throw VendingMachineError.InvalidSelection } @@ -92,16 +104,19 @@ class VendingMachine { } coinsDeposited -= item.price - --item.count - inventory[name] = item + + var newItem = item + newItem.count -= 1 + inventory[name] = newItem + dispenseSnack(name) } } ``` -`vend(itemNamed:)`方法的实现使用了`guard`语句,确保在购买某个零食所需的条件有任一不被满足时能够尽早退出此方法并抛出相匹配的错误。由于`throw`语句会立即将程序控制转移,所以某件物品只有在所有条件都满足时才会被售出。 +在`vend(itemNamed:)`方法的实现中使用了`guard`语句来提前退出方法,确保在购买某个物品所需的条件中,有任一条件不满足时,能提前退出方法并抛出相应的错误。由于`throw`语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。 -因为`vend(itemNamed:)`方法会传递出它抛出的任何错误,在你代码中调用它的地方必须要么直接处理这些错误 - 使用`do-catch`语句,`try?`或`try!`,要么继续将这些错误传递下去。例如下面例子中`buyFavoriteSnack(_:vendingMachine)`同样是一个 throwing 函数,任何由`vend(itemNamed:)`方法抛出的错误会一直被传递到`buyFavoriteSnack(_:vendingMachine)`函数被调用的那个地方。 +因为`vend(itemNamed:)`方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用`do-catch`语句,`try?`或`try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(_:vendingMachine:)`同样是一个 throwing 函数,任何由`vend(itemNamed:)`方法抛出的错误会一直被传递到`buyFavoriteSnack(_:vendingMachine:)`函数被调用的地方。 ```swift let favoriteSnacks = [ @@ -114,12 +129,27 @@ func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws { try vendingMachine.vend(itemNamed: snackName) } ``` -上例中`buyFavoriteSnack(_:vendingMachine)`函数会查找某人最喜欢的零食,并通过调用`vend(itemNamed:)`方法来尝试为他们买。因为`vend(itemNamed:)`方法能抛出错误,所以在调用的它时候在它前面加了`try`关键字。 + +上例中,`buyFavoriteSnack(_:vendingMachine:)`函数会查找某人最喜欢的零食,并通过调用`vend(itemNamed:)`方法来尝试为他们购买。因为`vend(itemNamed:)`方法能抛出错误,所以在调用的它时候在它前面加了`try`关键字。 + +throwing构造器能像throwing函数一样传递错误.例如下面代码中的`PurchasedSnack`构造器在构造过程中调用了throwing函数,并且通过传递到它的调用者来处理这些错误。 + +```swift +struct PurchasedSnack { + let name: String + init(name: String, vendingMachine: VendingMachine) throws { + try vendingMachine.vend(itemNamed: name) + self.name = name + } +} +``` ###用 Do-Catch 处理错误 -可以使用一个`do-catch`语句运行一段闭包代码来做错误处理。如果在`do`语句中的代码抛出了一个错误,则这个错误会与`catch`语句做匹配来决定哪条语句能处理它。 -下面是`do-catch`语句的通用形式: + +可以使用一个`do-catch`语句运行一段闭包代码来处理错误。如果在`do`子句中的代码抛出了一个错误,这个错误会与`catch`子句做匹配,从而决定哪条子句能处理它。 + +下面是`do-catch`语句的一般形式: ```swift do { @@ -131,9 +161,10 @@ do { statements } ``` -在`catch`后面写一个模式(pattern)来表示这个语句能处理什么样的错误。如果一条`catch`语句没带一个模式,那么这条语句可以和任何错误相匹配,并且把错误和一个名字为`name`的局部常量做绑定。关于模式匹配的更多信息请参考[Patterns](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html#//apple_ref/doc/uid/TP40014097-CH36-ID419)。 -`catch`语句不必将`do`语句中代码所抛出的每个可能的错误都处理。如果没有一条`catch`字句来处理错误,错误就会传播到周围的作用域。然而错误还是必须要被某个周围的作用域处理的 - 要么是一个外围的`do-catch`错误处理语句,要么是一个throwing函数的内部。举例来说,下面的代码处理了`VendingMachineError`枚举类型的全部3个枚举实例,但是所有其它的错误就必须由它周围作用域所处理: +在`catch`后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条`catch`子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为`error`的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。 + +`catch`子句不必将`do`子句中的代码所抛出的每一个可能的错误都作处理。如果所有`catch`子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的——要么是一个外围的`do-catch`错误处理语句,要么是一个 throwing 函数的内部。举例来说,下面的代码处理了`VendingMachineError`枚举类型的全部枚举值,但是所有其它的错误就必须由它周围的作用域处理: ```swift var vendingMachine = VendingMachine() @@ -147,12 +178,14 @@ do { } catch VendingMachineError.InsufficientFunds(let coinsNeeded) { print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.") } -// prints "Insufficient funds. Please insert an additional 2 coins." +// 打印 “Insufficient funds. Please insert an additional 2 coins.” ``` -上面的例子中`buyFavoriteSnack(_:vendingMachine:)`在一个`try`表达式中被调用,因为它能抛出一个错误。如果一个错误被抛出,相应的执行会被马上转移到`catch`语句中来,判断这个错误是否要被继续传递下去。如果没有错误抛出,`do`语句中余下的语句就会被执行。 + +上面的例子中,`buyFavoriteSnack(_:vendingMachine:)`函数在一个`try`表达式中调用,因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到`catch`子句中,并判断这个错误是否要被继续传递下去。如果没有错误抛出,`do`子句中余下的语句就会被执行。 ###将错误转换成可选值 -可以使用`try?`通过将其转换成一个可选值来处理错误。如果在评估`try?`表达式时一个错误被抛出,那么这个表达式的值就是`nil`。例如下面代码中的`x`和`y`有相同的值和特性: + +可以使用`try?`通过将错误转换成一个可选值来处理错误。如果在评估`try?`表达式时一个错误被抛出,那么表达式的值就是`nil`。例如,在下面的代码中,`x`和`y`有着相同的数值和等价的含义: ```swift func someThrowingFunction() throws -> Int { @@ -169,8 +202,9 @@ do { } ``` -如果`someThrowingFunction()`抛出一个错误,`x`和`y`的值是`nil`。否则`x`和`y`的值就是该函数的返回值。注意无论`someThrowingFunction()`的返回值类型是什么类型,`x`和`y`都是这个类型的可选类型。例子中此函数返回一个整型,所以`x`和`y`是整型的可选类型。 -如果你想对所有的错误都采用同样的方式来处理,用`try?`就可以让你写出简洁的错误处理代码。比如下面的代码用一些的方式来获取数据,如果所有的方法都失败则返回`nil`。 +如果`someThrowingFunction()`抛出一个错误,`x`和`y`的值是`nil`。否则`x`和`y`的值就是该函数的返回值。注意,无论`someThrowingFunction()`的返回值类型是什么类型,`x`和`y`都是这个类型的可选类型。例子中此函数返回一个整型,所以`x`和`y`是可选整型。 + +如果你想对所有的错误都采用同样的方式来处理,用`try?`就可以让你写出简洁的错误处理代码。例如,下面的代码用几种方式来获取数据,如果所有方式都失败了则返回`nil`: ```swift func fetchData() -> Data? { @@ -179,9 +213,12 @@ func fetchData() -> Data? { return nil } ``` -###使错误传递失效 -有时你知道某个 throwing 函数实际上在运行时是不会抛出错误的。在这种条件下,你可以在表达式前面写`try!`来使错误传递失效,并把调用包装在一个运行时断言(runtime assertion)中来断定不会有错误抛出。如果实际上确实抛出了错误,你就会得到一个运行时错误。 -例如下面的代码使用了`loadImage(_:)`函数,该函数从给定的路径下装载图片资源,如果图片不能够被载入则抛出一个错误。此种情况下因为图片是和应用绑定的,运行时不会有错误被抛出,所以是错误传递失效是没问题的。 + +### 禁用错误传递 + +有时你知道某个 throwing 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写`try!`来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。 + +例如,下面的代码使用了`loadImage(_:)`函数,该函数从给定的路径加载图片资源,如果图片无法载入则抛出一个错误。在这种情况下,因为图片是和应用绑定的,运行时不会有错误抛出,所以适合禁用错误传递: ```swift let photo = try! loadImage("./Resources/John Appleseed.jpg") @@ -189,24 +226,27 @@ let photo = try! loadImage("./Resources/John Appleseed.jpg") ##指定清理操作 -可以使用`defer`语句在代码执行到要离开当前的代码段之前去执行一套语句。该语句让你能够做一些应该被执行的必要清理工作,不管是以何种方式离开当前的代码段的 - 无论是由于抛出错误而离开,或是因为一条`return`或者`break`的类似语句。比如你可以用`defer`语句来保证文件描述符(file descriptors)已被关闭,并且手动分配的内存也被释放了。 -`defer`语句将代码的执行延迟到当前的作用域退出之前。该语句由`defer`关键字和要被延时执行的语句组成。延时执行的语句不会包含任何会将控制权移交到语句外面的代码,例如一条`break`或是`return`语句,或是抛出一个错误。延迟执行的操作是按照它们被指定的相反顺序执行 - 意思是第一条`defer`语句中的代码执行是在第二条`defer`语句中代码被执行之后,以此类推。 + +可以使用`defer`语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如`return`或者`break`的语句。例如,你可以用`defer`语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。 + +`defer`语句将代码的执行延迟到当前的作用域退出之前。该语句由`defer`关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如`break`或是`return`语句,或是抛出一个错误。延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条`defer`语句中的代码会在第二条`defer`语句中的代码被执行之后才执行,以此类推。 ```swift - func processFile(filename: String) throws { - if exists(filename) { - let file = open(filename) - defer { - close(file) - } - while let line = try file.readline() { - // 处理文件 - } - // 在这里,作用域的最后调用 close(file) - } - } +func processFile(filename: String) throws { + if exists(filename) { + let file = open(filename) + defer { + close(file) + } + while let line = try file.readline() { + // 处理文件。 + } + // close(file) 会在这里被调用,即作用域的最后。 + } +} ``` -上面的代码用了一条`defer`语句来确保`open(_:)`函数有一个相应的对`close(_:)`的调用。 ->注意 ->即使没有涉及到错误处理代码,你也可以用`defer`语句。 \ No newline at end of file +上面的代码使用一条`defer`语句来确保`open(_:)`函数有一个相应的对`close(_:)`函数的调用。 + +> 注意 +> 即使没有涉及到错误处理,你也可以使用`defer`语句。 diff --git a/source/chapter2/19_Nested_Types.md b/source/chapter2/19_Nested_Types.md deleted file mode 100644 index fe0bf822..00000000 --- a/source/chapter2/19_Nested_Types.md +++ /dev/null @@ -1,99 +0,0 @@ -> 1.0 -> 翻译:[Lin-H](https://github.com/Lin-H) -> 校对:[shinyzhu](https://github.com/shinyzhu) - -> 2.0 -> 翻译+校对:[SergioChan](https://github.com/SergioChan) - -# 嵌套类型 ------------------ - -本页包含内容: - -- [嵌套类型实例](#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.rawValue, second: nil) - } - } - } - - // BlackjackCard 的属性和方法 - let rank: Rank, suit: Suit - var description: String { - var output = "suit is \(suit.rawValue)," - 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`实例,并赋值给`values`。对于`J`,`Q`,`K`,`Ace`会使用特殊数值,对于数字面值的牌使用`Int`类型的值。 - -`BlackjackCard`结构体自身有两个属性—`rank`与`suit`,也同样定义了一个计算属性`description`,`description`属性用`rank`和`suit`的中内容来构建对这张扑克牌名字和数值的描述,并用可选类型`second`来检查是否存在第二个值,若存在,则在原有的描述中增加对第二数值的描述。 - -因为`BlackjackCard`是一个没有自定义构造函数的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#memberwise_initializers_for_structure_types)中知道结构体有默认的成员构造函数,所以你可以用默认的`initializer`去初始化新的常量`theAceOfSpades`: - -```swift -let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades) -print("theAceOfSpades: \(theAceOfSpades.description)") -// 打印出 "theAceOfSpades: suit is ♠, value is 1 or 11" -``` - -尽管`Rank`和`Suit`嵌套在`BlackjackCard`中,但仍可被引用,所以在初始化实例时能够通过枚举类型中的成员名称单独引用。在上面的例子中`description`属性能正确得输出对`Ace`牌有1和11两个值。 - - -##嵌套类型的引用 - -在外部对嵌套类型的引用,以被嵌套类型的名字为前缀,加上所要引用的属性名: - -```swift -let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue -// 红心的符号 为 "♡" -``` - -对于上面这个例子,这样可以使`Suit`, `Rank`, 和 `Values`的名字尽可能的短,因为它们的名字会自然的由定义它们的上下文来限定。 diff --git a/source/chapter2/19_Type_Casting.md b/source/chapter2/19_Type_Casting.md index 538a53ad..8eef424b 100644 --- a/source/chapter2/19_Type_Casting.md +++ b/source/chapter2/19_Type_Casting.md @@ -8,26 +8,32 @@ > 2.0 > 翻译+校对:[yangsiy](https://github.com/yangsiy) +> 2.1 +> 校对:[shanks](http://codebuild.me),2015-11-01 +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16 + 本页包含内容: - [定义一个类层次作为例子](#defining_a_class_hierarchy_for_type_casting) - [检查类型](#checking_type) - [向下转型(Downcasting)](#downcasting) -- [`Any`和`AnyObject`的类型转换](#type_casting_for_any_and_anyobject) +- [`Any` 和 `AnyObject` 的类型转换](#type_casting_for_any_and_anyobject) _类型转换_ 可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。 类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。 -你也可以用它来检查一个类是否实现了某个协议,就像在 [检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。 +你也可以用它来检查一个类型是否实现了某个协议,就像在[检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。 ## 定义一个类层次作为例子 -你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了几个这些类实例的数组,作为类型转换的例子。 +你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了这些类实例的数组,作为类型转换的例子。 -第一个代码片段定义了一个新的基础类 `MediaItem`。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 `String` 类型的 `name` 属性,和一个 `init name` 初始化器。(假定所有的媒体项都有个名称。) +第一个代码片段定义了一个新的基类 `MediaItem`。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 `String` 类型的 `name` 属性,和一个 `init(name:)` 初始化器。(假定所有的媒体项都有个名称。) ```swift class MediaItem { @@ -58,7 +64,7 @@ class Song: MediaItem { } ``` -最后一个代码段创建了一个数组常量 `library`,包含两个 `Movie` 实例和三个 `Song` 实例。`library` 的类型是在它被初始化时根据它数组中所包含的内容推断来的。Swift的类型检测器能够推理出 `Movie` 和 `Song` 有共同的父类 `MediaItem`,所以它推断出 `[MediaItem]` 类作为 `library` 的类型。 +最后一个代码段创建了一个数组常量 `library`,包含两个 `Movie` 实例和三个 `Song` 实例。`library` 的类型是在它被初始化时根据它数组中所包含的内容推断来的。Swift 的类型检测器能够推断出 `Movie` 和 `Song` 有共同的父类 `MediaItem`,所以它推断出 `[MediaItem]` 类作为 `library` 的类型: ```swift let library = [ @@ -68,7 +74,7 @@ let library = [ 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 的类型被推断为 [MediaItem] ``` 在幕后 `library` 里存储的媒体项依然是 `Movie` 和 `Song` 类型的。但是,若你迭代它,依次取出的实例会是 `MediaItem` 类型的,而不是 `Movie` 和 `Song` 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。 @@ -76,9 +82,9 @@ let library = [ ## 检查类型(Checking Type) -用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。 +用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。 -下面的例子定义了两个变量,`movieCount` 和 `songCount`,用来计算数组 `library` 中 `Movie` 和 `Song` 类型的实例数量。 +下面的例子定义了两个变量,`movieCount` 和 `songCount`,用来计算数组 `library` 中 `Movie` 和 `Song` 类型的实例数量: ```swift var movieCount = 0 @@ -86,37 +92,36 @@ var songCount = 0 for item in library { if item is Movie { - ++movieCount + movieCount += 1 } else if item is Song { - ++songCount + songCount += 1 } } print("Media library contains \(movieCount) movies and \(songCount) songs") -// prints "Media library contains 2 movies and 3 songs" +// 打印 “Media library contains 2 movies and 3 songs” ``` -示例迭代了数组 `library` 中的所有项。每一次,`for`-`in` 循环设置 +示例迭代了数组 `library` 中的所有项。每一次,`for-in` 循环设置 `item` 为数组中的下一个 `MediaItem`。 若当前 `MediaItem` 是一个 `Movie` 类型的实例,`item is Movie` 返回 -`true`,相反返回 `false`。同样的,`item is -Song` 检查item是否为 `Song` 类型的实例。在循环结束后,`movieCount` 和 `songCount` 的值就是被找到属于各自的类型的实例数量。 +`true`,否则返回 `false`。同样的,`item is Song` 检查 `item` 是否为 `Song` 类型的实例。在循环结束后,`movieCount` 和 `songCount` 的值就是被找到的属于各自类型的实例的数量。 ## 向下转型(Downcasting) -某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(`as?` 或 `as!`) +某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(`as?` 或 `as!`)。 -因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式(conditional form) `as?` 返回一个你试图向下转成的类型的可选值(optional value)。强制形式 `as!` 把试图向下转型和强制解包(force-unwraps)结果作为一个混合动作。 +因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式(conditional form)`as?` 返回一个你试图向下转成的类型的可选值(optional value)。强制形式 `as!` 把试图向下转型和强制解包(force-unwraps)转换结果结合为一个操作。 -当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。 +当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。 -只有你可以确定向下转型一定会成功时,才使用强制形式(`as!`)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。 +只有你可以确定向下转型一定会成功时,才使用强制形式(`as!`)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。 -下面的例子,迭代了 `library` 里的每一个 `MediaItem`,并打印出适当的描述。要这样做,`item` 需要真正作为 `Movie` 或 `Song` 的类型来使用,不仅仅是作为 `MediaItem`。为了能够在描述中使用 `Movie` 或 `Song` 的 `director` 或 `artist` 属性,这是必要的。 +下面的例子,迭代了 `library` 里的每一个 `MediaItem`,并打印出适当的描述。要这样做,`item` 需要真正作为 `Movie` 或 `Song` 的类型来使用,而不仅仅是作为 `MediaItem`。为了能够在描述中使用 `Movie` 或 `Song` 的 `director` 或 `artist` 属性,这是必要的。 -在这个示例中,数组中的每一个 `item` 可能是 `Movie` 或 `Song`。事前你不知道每个 `item` 的真实类型,所以这里使用条件形式的类型转换(`as?`)去检查循环里的每次下转。 +在这个示例中,数组中的每一个 `item` 可能是 `Movie` 或 `Song`。事前你不知道每个 `item` 的真实类型,所以这里使用条件形式的类型转换(`as?`)去检查循环里的每次下转: ```swift for item in library { @@ -136,37 +141,38 @@ for item in library { 示例首先试图将 `item` 下转为 `Movie`。因为 `item` 是一个 `MediaItem` 类型的实例,它可能是一个 `Movie`;同样,它也可能是一个 `Song`,或者仅仅是基类 -`MediaItem`。因为不确定,`as?`形式在试图下转时将返回一个可选值。`item as? Movie` 的返回值是 `Movie?` 或 “可选 `Movie`”类型。 +`MediaItem`。因为不确定,`as?` 形式在试图下转时将返回一个可选值。`item as? Movie` 的返回值是 `Movie?` 或者说“可选 `Movie`”。 当向下转型为 `Movie` 应用在两个 `Song` 实例时将会失败。为了处理这种情况,上面的例子使用了可选绑定(optional binding)来检查可选 `Movie` 真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“`if let movie = item as? Movie`”,可以这样解读: -“尝试将 `item` 转为 `Movie` 类型。若成功,设置一个新的临时常量 `movie` 来存储返回的可选 `Movie`” +“尝试将 `item` 转为 `Movie` 类型。若成功,设置一个新的临时常量 `movie` 来存储返回的可选 `Movie` 中的值” -若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director` 。相近的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。 +若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director`。相似的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。 -> 注意: -> 转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。 +> 注意 +> 转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。 -## `Any`和`AnyObject`的类型转换 +## `Any` 和 `AnyObject` 的类型转换 -Swift为不确定类型提供了两种特殊类型别名: +Swift 为不确定类型提供了两种特殊的类型别名: -* `AnyObject`可以代表任何class类型的实例。 -* `Any`可以表示任何类型,包括方法类型(function types)。 +* `AnyObject` 可以表示任何类类型的实例。 +* `Any` 可以表示任何类型,包括函数类型。 -> 注意: -> 只有当你明确的需要它的行为和功能时才使用`Any`和`AnyObject`。在你的代码里使用你期望的明确的类型总是更好的。 +> 注意 +> 只有当你确实需要它们的行为和功能时才使用 `Any` 和 `AnyObject`。在你的代码里使用你期望的明确类型总是更好的。 -### `AnyObject`类型 +### `AnyObject` 类型 -当在工作中使用 Cocoa APIs,我们一般会接收一个`[AnyObject]`类型的数组,或者说“一个任何对象类型的数组”。这是因为 Objective-C 没有明确的类型化数组。但是,你常常可以从 API 提供的信息中清晰地确定数组中对象的类型。 +当我们使用 Cocoa APIs 时,我们会接收到一个 `[AnyObject]` 类型的数组,或者说“一个任意类型对象的数组”。Objective-C现在支持明确的数组类型,但早期版本的Objective-C并没有这个功能。不管怎样,你都可以确信API提供的信息能够正确的表明数组中的元素类型。 -在这些情况下,你可以使用强制形式的类型转换(`as`)来下转在数组中的每一项到比 `AnyObject` 更明确的类型,不需要可选解析(optional unwrapping)。 -下面的示例定义了一个 `[AnyObject]` 类型的数组并填入三个`Movie`类型的实例: +在这些情况下,你可以使用强制形式的类型转换(`as!`)来下转数组中的每一项到比 `AnyObject` 更明确的类型,不需要可选解包(optional unwrapping)。 + +下面的示例定义了一个 `[AnyObject]` 类型的数组并填入三个 `Movie` 类型的实例: ```swift let someObjects: [AnyObject] = [ @@ -176,7 +182,7 @@ let someObjects: [AnyObject] = [ ] ``` -因为知道这个数组只包含 `Movie` 实例,你可以直接用(`as!`)下转并解包到不可选的`Movie`类型: +因为知道这个数组只包含 `Movie` 实例,你可以直接用(`as!`)下转并解包到非可选的 `Movie` 类型: ```swift for object in someObjects { @@ -188,7 +194,7 @@ for object in someObjects { // Movie: 'Alien', dir. Ridley Scott ``` -为了变为一个更短的形式,下转`someObjects`数组为`[Movie]`类型来代替下转数组中每一项的方式。 +为了变为一个更简短的形式,下转 `someObjects` 数组为 `[Movie]` 类型而不是下转数组中的每一项: ```swift for movie in someObjects as! [Movie] { @@ -199,9 +205,10 @@ for movie in someObjects as! [Movie] { // Movie: 'Alien', dir. Ridley Scott ``` -### `Any`类型 + +### `Any` 类型 -这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括方法类型和非 `class` 类型。它创建了一个可以存储`Any`类型的数组 `things`。 +这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括函数类型和非类类型。它创建了一个可以存储 `Any` 类型的数组 `things`: ```swift var things = [Any]() @@ -216,9 +223,9 @@ things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman")) things.append({ (name: String) -> String in "Hello, \(name)" }) ``` -`things` 数组包含两个 `Int` 值,2个 `Double` 值,1个 `String` 值,一个元组 `(Double, Double)` ,电影“Ghostbusters”,和一个获取 `String` 值并返回另一个 `String` 值的闭包表达式。 +`things` 数组包含两个 `Int` 值,两个 `Double` 值,一个 `String` 值,一个元组 `(Double, Double)`,一个`Movie`实例“Ghostbusters”,以及一个接受 `String` 值并返回另一个 `String` 值的闭包表达式。 -你可以在 `switch` 表达式的cases中使用 `is` 和 `as` 操作符来发觉只知道是 `Any` 或 `AnyObject` 的常量或变量的类型。下面的示例迭代 `things` 数组中的每一项的并用`switch`语句查找每一项的类型。这几种 `switch` 语句的情形绑定它们匹配的值到一个规定类型的常量,让它们的值可以被打印: +你可以在 `switch` 表达式的 `case` 中使用 `is` 和 `as` 操作符来找出只知道是 `Any` 或 `AnyObject` 类型的常量或变量的具体类型。下面的示例迭代 `things` 数组中的每一项,并用 `switch` 语句查找每一项的类型。有几个 `switch` 语句的 `case` 绑定它们匹配到的值到一个指定类型的常量,从而可以打印这些值: ```swift for thing in things { @@ -255,7 +262,3 @@ for thing in things { // a movie called 'Ghostbusters', dir. Ivan Reitman // Hello, Michael ``` - - -> 注意: -> 在一个switch语句的case中使用强制形式的类型转换操作符(as, 而不是 as?)来检查和转换到一个明确的类型。在 `switch` case 语句的内容中这种检查总是安全的。 diff --git a/source/chapter2/20_Nested_Types.md b/source/chapter2/20_Nested_Types.md index 18dfc46d..a4578df9 100755 --- a/source/chapter2/20_Nested_Types.md +++ b/source/chapter2/20_Nested_Types.md @@ -8,30 +8,36 @@ > 2.0 > 翻译+校对:[SergioChan](https://github.com/SergioChan) +> 2.1 +> 校对:[shanks](http://codebuild.me),2015-11-01 +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16 + 本页包含内容: -- [嵌套类型实例](#nested_types_in_action) -- [嵌套类型的引用](#referring_to_nested_types) +- [嵌套类型实践](#nested_types_in_action) +- [引用嵌套类型](#referring_to_nested_types) -枚举类型常被用于实现特定类或结构体的功能。也能够在有多种变量类型的环境中,方便地定义通用类或结构体来使用,为了实现这种功能,Swift允许你定义嵌套类型,可以在枚举类型、类和结构体中定义支持嵌套的类型。 +枚举常被用于为特定类或结构体实现某些功能。类似的,枚举可以方便的定义工具类或结构体,从而为某个复杂的类型所使用。为了实现这种功能,Swift 允许你定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结构体。 -要在一个类型中嵌套另一个类型,将需要嵌套的类型的定义写在被嵌套类型的区域{}内,而且可以根据需要定义多级嵌套。 +要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的`{}`内,而且可以根据需要定义多级嵌套。 -##嵌套类型实例 +## 嵌套类型实践 -下面这个例子定义了一个结构体`BlackjackCard`(二十一点),用来模拟`BlackjackCard`中的扑克牌点数。`BlackjackCard`结构体包含2个嵌套定义的枚举类型`Suit` 和 `Rank`。 +下面这个例子定义了一个结构体`BlackjackCard`(二十一点),用来模拟`BlackjackCard`中的扑克牌点数。`BlackjackCard`结构体包含两个嵌套定义的枚举类型`Suit`和`Rank`。 -在`BlackjackCard`规则中,`Ace`牌可以表示1或者11,`Ace`牌的这一特征用一个嵌套在枚举型`Rank`的结构体`Values`来表示。 +在`BlackjackCard`中,`Ace`牌可以表示`1`或者`11`,`Ace`牌的这一特征通过一个嵌套在`Rank`枚举中的结构体`Values`来表示: ```swift struct BlackjackCard { - // 嵌套定义枚举型Suit + // 嵌套的 Suit 枚举 enum Suit: Character { case Spades = "♠", Hearts = "♡", Diamonds = "♢", Clubs = "♣" } - // 嵌套定义枚举型Rank + // 嵌套的 Rank 枚举 enum Rank: Int { case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten case Jack, Queen, King, Ace @@ -63,37 +69,37 @@ struct BlackjackCard { } ``` -枚举型的`Suit`用来描述扑克牌的四种花色,并分别用一个`Character`类型的值代表花色符号。 +`Suit`枚举用来描述扑克牌的四种花色,并用一个`Character`类型的原始值表示花色符号。 -枚举型的`Rank`用来描述扑克牌从`Ace`~10,`J`,`Q`,`K`,13张牌,并分别用一个`Int`类型的值表示牌的面值。(这个`Int`类型的值不适用于`Ace`,`J`,`Q`,`K`的牌)。 +`Rank`枚举用来描述扑克牌从`Ace`~`10`,以及`J`、`Q`、`K`,这`13`种牌,并用一个`Int`类型的原始值表示牌的面值。(这个`Int`类型的原始值未用于`Ace`、`J`、`Q`、`K`这`4`种牌。) -如上文所提到的,枚举型`Rank`在自己内部定义了一个嵌套结构体`Values`。在这个结构体中,只有`Ace`有两个数值,其余牌都只有一个数值。结构体`Values`中定义的两个属性: +如上所述,`Rank`枚举在内部定义了一个嵌套结构体`Values`。结构体`Values`中定义了两个属性,用于反映只有`Ace`有两个数值,其余牌都只有一个数值: -- `first`为` Int` -- `second`为 `Int?` 或 “optional `Int`” +- `first`的类型为`Int` +- `second`的类型为`Int?`,或者说“optional `Int`” -`Rank`定义了一个计算属性`values`,它将会返回一个结构体`Values`的实例。这个计算属性会根据牌的面值,用适当的数值去初始化`Values`实例,并赋值给`values`。对于`J`,`Q`,`K`,`Ace`会使用特殊数值,对于数字面值的牌使用`Int`类型的值。 +`Rank`还定义了一个计算型属性`values`,它将会返回一个`Values`结构体的实例。这个计算型属性会根据牌的面值,用适当的数值去初始化`Values`实例。对于`J`、`Q`、`K`、`Ace`这四种牌,会使用特殊数值。对于数字面值的牌,使用枚举实例的原始值。 -`BlackjackCard`结构体自身有两个属性—`rank`与`suit`,也同样定义了一个计算属性`description`,`description`属性用`rank`和`suit`的中内容来构建对这张扑克牌名字和数值的描述,并用可选类型`second`来检查是否存在第二个值,若存在,则在原有的描述中增加对第二数值的描述。 +`BlackjackCard`结构体拥有两个属性——`rank`与`suit`。它也同样定义了一个计算型属性`description`,`description`属性用`rank`和`suit`中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型`second`是否有值,若有值,则在原有的描述中增加对`second`的描述。 -因为`BlackjackCard`是一个没有自定义构造函数的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#memberwise_initializers_for_structure_types)中知道结构体有默认的成员构造函数,所以你可以用默认的`initializer`去初始化新的常量`theAceOfSpades`: +因为`BlackjackCard`是一个没有自定义构造器的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#memberwise_initializers_for_structure_types)中可知,结构体有默认的成员构造器,所以你可以用默认的构造器去初始化新常量`theAceOfSpades`: ```swift let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades) print("theAceOfSpades: \(theAceOfSpades.description)") -// 打印出 "theAceOfSpades: suit is ♠, value is 1 or 11" +// 打印 “theAceOfSpades: suit is ♠, value is 1 or 11” ``` -尽管`Rank`和`Suit`嵌套在`BlackjackCard`中,但仍可被引用,所以在初始化实例时能够通过枚举类型中的成员名称单独引用。在上面的例子中`description`属性能正确得输出对`Ace`牌有1和11两个值。 +尽管`Rank`和`Suit`嵌套在`BlackjackCard`中,但它们的类型仍可从上下文中推断出来,所以在初始化实例时能够单独通过成员名称(`.Ace`和`.Spades`)引用枚举实例。在上面的例子中,`description`属性正确地反映了黑桃A牌具有`1`和`11`两个值。 -##嵌套类型的引用 +## 引用嵌套类型 -在外部对嵌套类型的引用,以被嵌套类型的名字为前缀,加上所要引用的属性名: +在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀: ```swift let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue -// 红心的符号 为 "♡" +// 红心符号为 “♡” ``` -对于上面这个例子,这样可以使`Suit`, `Rank`, 和 `Values`的名字尽可能的短,因为它们的名字会自然的由定义它们的上下文来限定。 +对于上面这个例子,这样可以使`Suit`、`Rank`和`Values`的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。 diff --git a/source/chapter2/20_Type_Casting.md b/source/chapter2/20_Type_Casting.md deleted file mode 100644 index 8ecbbfb7..00000000 --- a/source/chapter2/20_Type_Casting.md +++ /dev/null @@ -1,261 +0,0 @@ -> 1.0 -> 翻译:[xiehurricane](https://github.com/xiehurricane) -> 校对:[happyming](https://github.com/happyming) - -> 2.0 -> 翻译+校对:[yangsiy](https://github.com/yangsiy) - -# 类型转换(Type Casting) ------------------ - -本页包含内容: - -- [定义一个类层次作为例子](#defining_a_class_hierarchy_for_type_casting) -- [检查类型](#checking_type) -- [向下转型(Downcasting)](#downcasting) -- [`Any`和`AnyObject`的类型转换](#type_casting_for_any_and_anyobject) - - -_类型转换_可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。 - -类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。 - -你也可以用它来检查一个类是否实现了某个协议,就像在 [检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。 - - -## 定义一个类层次作为例子 - -你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了几个这些类实例的数组,作为类型转换的例子。 - -第一个代码片段定义了一个新的基础类 `MediaItem`。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 `String` 类型的 `name` 属性,和一个 `init name` 初始化器。(假定所有的媒体项都有个名称。) - -```swift -class MediaItem { - var name: String - init(name: String) { - self.name = name - } -} -``` - -下一个代码段定义了 `MediaItem` 的两个子类。第一个子类 `Movie` 封装了与电影相关的额外信息,在父类(或者说基类)的基础上增加了一个 `director`(导演)属性,和相应的初始化器。第二个子类 `Song`,在父类的基础上增加了一个 `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 - } -} - -print("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?` 或 `as!`) - -因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式(conditional form) `as?` 返回一个你试图向下转成的类型的可选值(optional value)。强制形式 `as!` 把试图向下转型和强制解包(force-unwraps)结果作为一个混合动作。 - -当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。 - -只有你可以确定向下转型一定会成功时,才使用强制形式(`as!`)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。 - -下面的例子,迭代了 `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 { - print("Movie: '\(movie.name)', dir. \(movie.director)") - } else if let song = item as? Song { - print("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?` 或 “可选 `Movie`”类型。 - -当向下转型为 `Movie` 应用在两个 `Song` -实例时将会失败。为了处理这种情况,上面的例子使用了可选绑定(optional binding)来检查可选 `Movie` 真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“`if let movie = item as? Movie`”,可以这样解读: - -“尝试将 `item` 转为 `Movie` 类型。若成功,设置一个新的临时常量 `movie` 来存储返回的可选 `Movie`” - -若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director` 。相近的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。 - -> 注意: -> 转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。 - - -## `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`类型: - -```swift -for object in someObjects { - let movie = object as! Movie - print("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] { - print("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.append({ (name: String) -> String in "Hello, \(name)" }) -``` - -`things` 数组包含两个 `Int` 值,2个 `Double` 值,1个 `String` 值,一个元组 `(Double, Double)` ,电影“Ghostbusters”,和一个获取 `String` 值并返回另一个 `String` 值的闭包表达式。 - -你可以在 `switch` 表达式的cases中使用 `is` 和 `as` 操作符来发觉只知道是 `Any` 或 `AnyObject` 的常量或变量的类型。下面的示例迭代 `things` 数组中的每一项的并用`switch`语句查找每一项的类型。这几种 `switch` 语句的情形绑定它们匹配的值到一个规定类型的常量,让它们的值可以被打印: - -```swift -for thing in things { - switch thing { - case 0 as Int: - print("zero as an Int") - case 0 as Double: - print("zero as a Double") - case let someInt as Int: - print("an integer value of \(someInt)") - case let someDouble as Double where someDouble > 0: - print("a positive double value of \(someDouble)") - case is Double: - print("some other double value that I don't want to print") - case let someString as String: - print("a string value of \"\(someString)\"") - case let (x, y) as (Double, Double): - print("an (x, y) point at \(x), \(y)") - case let movie as Movie: - print("a movie called '\(movie.name)', dir. \(movie.director)") - case let stringConverter as String -> String: - print(stringConverter("Michael")) - default: - print("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 -// Hello, Michael -``` - - -> 注意: -> 在一个switch语句的case中使用强制形式的类型转换操作符(as, 而不是 as?)来检查和转换到一个明确的类型。在 `switch` case 语句的内容中这种检查总是安全的。 diff --git a/source/chapter2/21_Extensions.md b/source/chapter2/21_Extensions.md index 47770030..1ed93023 100644 --- a/source/chapter2/21_Extensions.md +++ b/source/chapter2/21_Extensions.md @@ -6,7 +6,13 @@ > 校对:[Hawstein](https://github.com/Hawstein) > 2.0 -> 翻译+校对:[shanksyang](https://github.com/shanksyang) +> 翻译+校对:[shanks](http://codebuild.me) + +> 2.1 +> 校对:[shanks](http://codebuild.me) +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16 本页包含内容: @@ -17,33 +23,34 @@ - [下标](#subscripts) - [嵌套类型](#nested_types) -*扩展*就是向一个已有的类、结构体、枚举类型或者协议类型添加新功能(functionality)。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即*逆向建模*)。扩展和 Objective-C 中的分类(categories)类似。(不过与 Objective-C 不同的是,Swift 的扩展没有名字。) +*扩展* 就是为一个已有的类、结构体、枚举类型或者协议类型添加新功能。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即 *逆向建模* )。扩展和 Objective-C 中的分类类似。(与 Objective-C 不同的是,Swift 的扩展没有名字。) Swift 中的扩展可以: -- 添加计算型属性和计算型静态属性 +- 添加计算型属性和计算型类型属性 - 定义实例方法和类型方法 - 提供新的构造器 - 定义下标 - 定义和使用新的嵌套类型 - 使一个已有类型符合某个协议 -在 Swift 中,你甚至可以对一个协议(Protocol)进行扩展,提供协议需要的实现,或者添加额外的功能能够对合适的类型带来额外的好处。你可以从[协议扩展](./22_Protocols.html#protocol_extensions)获取更多的细节。 +在 Swift 中,你甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外的功能,从而可以让符合协议的类型拥有这些功能。你可以从[协议扩展](./22_Protocols.html#protocol_extensions)获取更多的细节。 + +> 注意 +扩展可以为一个类型添加新的功能,但是不能重写已有的功能。 ->注意: -扩展可以对一个类型添加新的功能,但是不能重写已有的功能。 - ## 扩展语法(Extension Syntax) -声明一个扩展使用关键字`extension`: +使用关键字 `extension` 来声明扩展: ```swift extension SomeType { - // 加到SomeType的新功能写到这里 + // 为 SomeType 添加的新功能写到这里 } ``` -一个扩展可以扩展一个已有类型,使其能够适配一个或多个协议(protocol)。当这种情况发生时,协议的名字应该完全按照类或结构体的名字的方式进行书写: + +可以通过扩展来扩展一个已有类型,使其采纳一个或多个协议。在这种情况下,无论是类还是结构体,协议名字的书写方式完全一样: ```swift extension SomeType: SomeProtocol, AnotherProctocol { @@ -51,15 +58,15 @@ extension SomeType: SomeProtocol, AnotherProctocol { } ``` -按照这种方式添加的协议遵循者(protocol conformance)被称之为[在扩展中添加协议遵循者](./22_Protocols.html#adding_protocol_conformance_with_an_extension) +通过这种方式添加协议一致性的详细描述请参阅[利用扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)。 ->注意: -如果你定义了一个扩展向一个已有类型添加新功能,那么这个新功能对该类型的所有已有实例中都是可用的,即使它们是在你的这个扩展的前面定义的。 +> 注意 +如果你通过扩展为一个已有类型添加新功能,那么新功能对该类型的所有已有实例都是可用的,即使它们是在这个扩展定义之前创建的。 ## 计算型属性(Computed Properties) -扩展可以向已有类型添加计算型实例属性和计算型类型属性。下面的例子向 Swift 的内建`Double`类型添加了5个计算型实例属性,从而提供与距离单位协作的基本支持: +扩展可以为已有类型添加计算型实例属性和计算型类型属性。下面的例子为 Swift 的内建 `Double` 类型添加了五个计算型实例属性,从而提供与距离单位协作的基本支持: ```swift extension Double { @@ -71,42 +78,41 @@ extension Double { } let oneInch = 25.4.mm print("One inch is \(oneInch) meters") -// 打印输出:"One inch is 0.0254 meters" +// 打印 “One inch is 0.0254 meters” let threeFeet = 3.ft print("Three feet is \(threeFeet) meters") -// 打印输出:"Three feet is 0.914399970739201 meters" +// 打印 “Three feet is 0.914399970739201 meters” ``` -这些计算属性表达的含义是把一个`Double`型的值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性仍可以接一个带有dot语法的浮点型字面值,而这恰恰是使用这些浮点型字面量实现距离转换的方式。 +这些计算型属性表达的含义是把一个 `Double` 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。 -在上述例子中,一个`Double`型的值`1.0`被用来表示“1米”。这就是为什么`m`计算型属性返回`self`——表达式`1.m`被认为是计算`1.0`的`Double`值。 +在上述例子中,`Double` 值 `1.0` 用来表示“1米”。这就是为什么计算型属性 `m` 返回 `self`,即表达式 `1.m` 被认为是计算 `Double` 值 `1.0`。 -其它单位则需要一些转换来表示在米下测量的值。1千米等于1,000米,所以`km`计算型属性要把值乘以`1_000.00`来转化成单位米下的数值。类似地,1米有3.28024英尺,所以`ft`计算型属性要把对应的`Double`值除以`3.28024`来实现英尺到米的单位换算。 +其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 `km` 要把值乘以 `1_000.00` 来实现千米到米的单位换算。类似地,一米有 3.28024 英尺,所以计算型属性 `ft` 要把对应的 `Double` 值除以 `3.28024` 来实现英尺到米的单位换算。 -这些属性是只读的计算型属性,所有从简考虑它们不用`get`关键字表示。它们的返回值是`Double`型,而且可以用于所有接受`Double`的数学计算中: +这些属性是只读的计算型属性,为了更简洁,省略了 `get` 关键字。它们的返回值是 `Double`,而且可以用于所有接受 `Double` 值的数学计算中: ```swift let aMarathon = 42.km + 195.m print("A marathon is \(aMarathon) meters long") -// 打印输出:"A marathon is 42195.0 meters long" +// 打印 “A marathon is 42195.0 meters long” ``` ->注意: -扩展可以添加新的计算属性,但是不可以添加存储属性,也不可以向已有属性添加属性观测器(property observers)。 +> 注意 +扩展可以添加新的计算型属性,但是不可以添加存储型属性,也不可以为已有属性添加属性观察器。 ## 构造器(Initializers) -扩展可以向已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。 +扩展可以为已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为其构造器参数,或者提供该类型的原始实现中未提供的额外初始化选项。 -扩展能向类中添加新的便利构造器,但是它们不能向类中添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供。 +扩展能为类添加新的便利构造器,但是它们不能为类添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供。 -> 注意: -如果你使用扩展向一个值类型添加一个构造器,在该值类型已经向所有的存储属性提供默认值,而且没有定义任何定制构造器(custom initializers)时,你可以在值类型的扩展构造器中调用默认构造器(default initializers)和逐一成员构造器(memberwise initializers)。 -> -正如在[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)中描述的,如果你已经把构造器写成值类型原始实现的一部分,上述规则不再适用。 +> 注意 +如果你使用扩展为一个值类型添加构造器,同时该值类型的原始实现中未定义任何定制的构造器且所有存储属性提供了默认值,那么我们就可以在扩展中的构造器里调用默认构造器和逐一成员构造器。 +正如在[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)中描述的,如果你把定制的构造器写在值类型的原始实现中,上述规则将不再适用。 -下面的例子定义了一个用于描述几何矩形的定制结构体`Rect`。这个例子同时定义了两个辅助结构体`Size`和`Point`,它们都把`0.0`作为所有属性的默认值: +下面的例子定义了一个用于描述几何矩形的结构体 `Rect`。这个例子同时定义了两个辅助结构体 `Size` 和 `Point`,它们都把 `0.0` 作为所有属性的默认值: ```swift struct Size { @@ -120,7 +126,7 @@ struct Rect { var size = Size() } ``` -因为结构体`Rect`提供了其所有属性的默认值,所以正如[默认构造器](./14_Initialization.html#default_initializers)中描述的,它可以自动接受一个默认构造器和一个逐一成员构造器。这些构造器可以用于构造新的`Rect`实例: +因为结构体 `Rect` 未提供定制的构造器,因此它会获得一个逐一成员构造器。又因为它为所有存储型属性提供了默认值,它又会获得一个默认构造器。详情请参阅[默认构造器](./14_Initialization.html#default_initializers)。这些构造器可以用于构造新的 `Rect` 实例: ```swift let defaultRect = Rect() @@ -128,7 +134,7 @@ let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0)) ``` -你可以提供一个额外的使用特殊中心点和大小的构造器来扩展`Rect`结构体: +你可以提供一个额外的接受指定中心点和大小的构造器来扩展 `Rect` 结构体: ```swift extension Rect { @@ -139,50 +145,50 @@ extension Rect { } } ``` -这个新的构造器首先根据提供的`center`和`size`值计算一个合适的原点。然后调用该结构体自动的逐一成员构造器`init(origin: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) +// centerRect 的原点是 (2.5, 2.5),大小是 (3.0, 3.0) ``` - ->注意: -如果你使用扩展提供了一个新的构造器,你依旧有责任保证构造过程能够让所有实例完全初始化。 +> 注意 +如果你使用扩展提供了一个新的构造器,你依旧有责任确保构造过程能够让实例完全初始化。 ## 方法(Methods) -扩展可以向已有类型添加新的实例方法和类型方法。下面的例子向`Int`类型添加一个名为`repetitions`的新实例方法: +扩展可以为已有类型添加新的实例方法和类型方法。下面的例子为 `Int` 类型添加了一个名为 `repetitions` 的实例方法: ```swift extension Int { - func repetitions(task: () -> ()) { - for i in 0.. Void) { + for _ in 0.. ()`类型的单参数(single argument),表明函数没有参数而且没有返回值。 +这个 `repetitions(:_)` 方法接受一个 `() -> Void` 类型的单参数,表示没有参数且没有返回值的函数。 -定义该扩展之后,你就可以对任意整数调用`repetitions`方法,实现的功能则是多次执行某任务: +定义该扩展之后,你就可以对任意整数调用 `repetitions(_:)` 方法,将闭包中的任务执行整数对应的次数: ```swift 3.repetitions({ print("Hello!") - }) +}) // Hello! // Hello! // Hello! ``` -可以使用 trailing 闭包使调用更加简洁: +可以使用尾随闭包让调用更加简洁: ```swift -3.repetitions{ +3.repetitions { print("Goodbye!") } // Goodbye! @@ -191,11 +197,11 @@ extension Int { ``` -### 修改实例方法(Mutating Instance Methods) +### 可变实例方法(Mutating Instance Methods) -通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改`self`或其属性的方法必须将该实例方法标注为`mutating`,正如来自原始实现的修改方法一样。 +通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改 `self` 或其属性的方法必须将该实例方法标注为 `mutating`,正如来自原始实现的可变方法一样。 -下面的例子向Swift的`Int`类型添加了一个新的名为`square`的修改方法,来实现一个原始值的平方计算: +下面的例子为 Swift 的 `Int` 类型添加了一个名为 `square` 的可变方法,用于计算原始值的平方值: ```swift extension Int { @@ -205,52 +211,51 @@ extension Int { } var someInt = 3 someInt.square() -// someInt 现在值是 9 +// someInt 的值现在是 9 ``` ## 下标(Subscripts) -扩展可以向一个已有类型添加新下标。这个例子向Swift内建类型`Int`添加了一个整型下标。该下标`[n]`返回十进制数字从右向左数的第n个数字 +扩展可以为已有类型添加新下标。这个例子为 Swift 内建类型 `Int` 添加了一个整型下标。该下标 `[n]` 返回十进制数字从右向左数的第 `n` 个数字: -- 123456789[0]返回9 -- 123456789[1]返回8 +- `123456789[0]` 返回 `9` +- `123456789[1]` 返回 `8` -...等等 +……以此类推。 ```swift extension Int { - subscript(var digitIndex: Int) -> Int { + subscript(digitIndex: Int) -> Int { var decimalBase = 1 - while digitIndex > 0 { - decimalBase *= 10 - --digitIndex - } - return (self / decimalBase) % 10 + for _ in 0.. ## 嵌套类型(Nested Types) -扩展可以向已有的类、结构体和枚举添加新的嵌套类型: +扩展可以为已有的类、结构体和枚举添加新的嵌套类型: ```swift extension Int { @@ -270,11 +275,11 @@ extension Int { } ``` -该例子向`Int`添加了新的嵌套枚举。这个名为`Kind`的枚举表示特定整数的类型。具体来说,就是表示整数是正数,零或者负数。 +该例子为 `Int` 添加了嵌套枚举。这个名为 `Kind` 的枚举表示特定整数的类型。具体来说,就是表示整数是正数、零或者负数。 -这个例子还向`Int`添加了一个新的计算实例属性,即`kind`,用来返回合适的`Kind`枚举成员。 +这个例子还为 `Int` 添加了一个计算型实例属性,即 `kind`,用来根据整数返回适当的 `Kind` 枚举成员。 -现在,这个嵌套枚举可以和一个`Int`值联合使用了: +现在,这个嵌套枚举可以和任意 `Int` 值一起使用了: ```swift @@ -282,20 +287,20 @@ func printIntegerKinds(numbers: [Int]) { for number in numbers { switch number.kind { case .Negative: - print("- ", appendNewline: false) + print("- ", terminator: "") case .Zero: - print("0 ", appendNewline: false) + print("0 ", terminator: "") case .Positive: - print("+ ", appendNewline: false) + print("+ ", terminator: "") } } print("") } printIntegerKinds([3, 19, -27, 0, -6, 0, 7]) -// prints "+ + - 0 - 0 +" +// 打印 “+ + - 0 - 0 + ” ``` -函数`printIntegerKinds`的输入是一个`Int`数组值并对其字符进行迭代。在每次迭代过程中,考虑当前字符的`kind`计算属性,并打印出合适的类别描述。 +函数 `printIntegerKinds(_:)` 接受一个 `Int` 数组,然后对该数组进行迭代。在每次迭代过程中,对当前整数的计算型属性 `kind` 的值进行评估,并打印出适当的描述。 ->注意: -由于已知`number.kind `是`Int.Kind`型,所以`Int.Kind`中的所有成员值都可以使用`switch`语句里的形式简写,比如使用 `. Negative`代替`Int.Kind.Negative`。 +> 注意 +由于已知 `number.kind` 是 `Int.Kind` 类型,因此在 `switch` 语句中,`Int.Kind` 中的所有成员值都可以使用简写形式,例如使用 `. Negative` 而不是 `Int.Kind.Negative`。 diff --git a/source/chapter2/22_Protocols.md b/source/chapter2/22_Protocols.md index 4ae60072..ad3a04ee 100644 --- a/source/chapter2/22_Protocols.md +++ b/source/chapter2/22_Protocols.md @@ -6,77 +6,82 @@ > 校对:[dabing1022](https://github.com/dabing1022) > 2.0 -> 翻译:[futantan](https://github.com/futantan) -> 校对:[小铁匠Linus](https://github.com/kevin833752) -> 定稿:[shanksyang](http://codebuild.me) +> 翻译+校对:[futantan](https://github.com/futantan) + +> 2.1 +> 翻译:[小铁匠Linus](https://github.com/kevin833752) +> 校对:[shanks](http://codebuild.me),2015-11-01 +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16 本页包含内容: -- [协议的语法(Protocol Syntax)](#protocol_syntax) -- [对属性的规定(Property Requirements)](#property_requirements) -- [对方法的规定(Method Requirements)](#method_requirements) -- [对Mutating方法的规定(Mutating Method Requirements)](#mutating_method_requirements) -- [对构造器的规定(Initializer Requirements)](#initializer_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) +- [协议语法(Protocol Syntax)](#protocol_syntax) +- [属性要求(Property Requirements)](#property_requirements) +- [方法要求(Method Requirements)](#method_requirements) +- [Mutating 方法要求(Mutating Method Requirements)](#mutating_method_requirements) +- [构造器要求(Initializer Requirements)](#initializer_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) -- [类专属协议(Class-Only Protocol)](#class_only_protocol) +- [类类型专属协议(Class-Only Protocol)](#class_only_protocol) - [协议合成(Protocol Composition)](#protocol_composition) -- [检验协议的一致性(Checking for Protocol Conformance)](#checking_for_protocol_conformance) -- [对可选协议的规定(Optional Protocol Requirements)](#optional_protocol_requirements) +- [检查协议一致性(Checking for Protocol Conformance)](#checking_for_protocol_conformance) +- [可选的协议要求(Optional Protocol Requirements)](#optional_protocol_requirements) - [协议扩展(Protocol Extensions)](#protocol_extensions) +协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以采纳协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型“符合”这个协议。 -`协议`定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为`遵循(conform)`这个协议。 +除了采纳协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样采纳协议的类型就能够使用这些功能。 -除了遵循协议的类型必须实现那些指定的规定以外,还可以对协议进行扩展,实现一些特殊的规定或者一些附加的功能,使得遵循的类型能够收益。 -## 协议的语法 +## 协议语法 -协议的定义方式与类,结构体,枚举的定义非常相似。 +协议的定义方式与类、结构体和枚举的定义非常相似: ```swift protocol SomeProtocol { - // 协议内容 + // 这里是协议的定义部分 } ``` -要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号`:`分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号`,`分隔。 +要让自定义类型采纳某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(`:`)分隔。采纳多个协议时,各协议之间用逗号(`,`)分隔: ```swift struct SomeStructure: FirstProtocol, AnotherProtocol { - // 结构体内容 + // 这里是结构体的定义部分 } ``` -如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。 +拥有父类的类在采纳协议时,应该将父类名放在协议名之前,以逗号分隔: ```swift class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol { - // 类的内容 + // 这里是类的定义部分 } ``` -## 对属性的规定 +## 属性要求 -协议可以规定其`遵循者`提供特定名称和类型的`实例属性(instance property)`或`类属性(type property)`,而不用指定是`存储型属性(stored property)`还是`计算型属性(calculate property)`。此外还必须指明是只读的还是可读可写的。 +协议可以要求采纳协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。 -如果协议规定属性是可读可写的,那么这个属性不能是常量或只读的计算属性。如果协议只要求属性是只读的(gettable),那个属性不仅可以是只读的,如果你代码需要的话,也可以是可写的。 +如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。 -协议中的通常用var来声明变量属性,在类型声明后加上`{ set get }`来表示属性是可读可写的,只读属性则用`{ get }`来表示。 +协议总是用 `var` 关键字来声明变量属性,在类型声明后加上 `{ set get }` 来表示属性是可读可写的,可读属性则用 `{ get }` 来表示: ```swift protocol SomeProtocol { - var mustBeSettable : Int { get set } + var mustBeSettable: Int { get set } var doesNotNeedToBeSettable: Int { get } } ``` -在协议中定义类属性(type property)时,总是使用`static`关键字作为前缀。当协议的遵循者是类时,可以使用`class`或`static`关键字来声明类属性: +在协议中定义类型属性时,总是使用 `static` 关键字作为前缀。当类类型采纳协议时,除了 `static` 关键字,还可以使用 `class` 关键字来声明类型属性: ```swift protocol AnotherProtocol { @@ -84,7 +89,7 @@ protocol AnotherProtocol { } ``` -如下所示,这是一个含有一个实例属性要求的协议: +如下所示,这是一个只含有一个实例属性要求的协议: ```swift protocol FullyNamed { @@ -92,23 +97,23 @@ protocol FullyNamed { } ``` -`FullyNamed`协议除了要求协议的遵循者提供全名属性外,对协议对遵循者的类型并没有特别的要求。这个协议表示,任何遵循`FullyNamed`协议的类型,都具有一个可读的`String`类型实例属性`fullName`。 +`FullyNamed` 协议除了要求采纳协议的类型提供 `fullName` 属性外,并没有其他特别的要求。这个协议表示,任何采纳 `FullyNamed` 的类型,都必须有一个可读的 `String` 类型的实例属性 `fullName`。 -下面是一个遵循`FullyNamed`协议的简单结构体: +下面是一个采纳 `FullyNamed` 协议的简单结构体: ```swift -struct Person: FullyNamed{ +struct Person: FullyNamed { var fullName: String } let john = Person(fullName: "John Appleseed") -//john.fullName 为 "John Appleseed" +// john.fullName 为 "John Appleseed" ``` -这个例子中定义了一个叫做`Person`的结构体,用来表示具有名字的人。从第一行代码中可以看出,它遵循了`FullyNamed`协议。 +这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它采纳了 `FullyNamed` 协议。 -`Person`结构体的每一个实例都有一个`String`类型的存储型属性`fullName`。这正好满足了`FullyNamed`协议的要求,也就意味着,`Person`结构体完整的`遵循`了协议。(如果协议要求未被完全满足,在编译时会报错) +`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地符合了协议。(如果协议要求未被完全满足,在编译时会报错。) -下面是一个更为复杂的类,它采用并遵循了`FullyNamed`协议: +下面是一个更为复杂的类,它采纳并符合了 `FullyNamed` 协议: ```swift class Starship: FullyNamed { @@ -126,14 +131,14 @@ var ncc1701 = Starship(name: "Enterprise", prefix: "USS") // ncc1701.fullName 是 "USS Enterprise" ``` -`Starship`类把`fullName`属性实现为只读的计算型属性。每一个`Starship`类的实例都有一个名为`name`的属性和一个名为`prefix`的可选属性。 当`prefix`存在时,将`prefix`插入到`name`之前来为Starship构建`fullName`,`prefix`不存在时,则将直接用`name`构建`fullName`。 +`Starship` 类把 `fullName` 属性实现为只读的计算型属性。每一个 `Starship` 类的实例都有一个名为 `name` 的非可选属性和一个名为 `prefix` 的可选属性。 当 `prefix` 存在时,计算型属性 `fullName` 会将 `prefix` 插入到 `name` 之前,从而为星际飞船构建一个全名。 -## 对方法的规定 +## 方法要求 -协议可以要求其遵循者实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是在协议的方法定义中,不支持参数默认值。 +协议可以要求采纳协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值。 -正如对属性的规定中所说的,在协议中定义类方法的时候,总是使用`static`关键字作为前缀。当协议的遵循者是类的时候,你可以在类的实现中使用`class`或者`static`来实现类方法: +正如属性要求中所述,在协议中定义类方法的时候,总是使用 `static` 关键字作为前缀。当类类型采纳协议时,除了 `static` 关键字,还可以使用 `class` 关键字作为前缀: ```swift protocol SomeProtocol { @@ -141,7 +146,7 @@ protocol SomeProtocol { } ``` -下面的例子定义了含有一个实例方法的协议: +下面的例子定义了一个只含有一个实例方法的协议: ```swift protocol RandomNumberGenerator { @@ -149,12 +154,11 @@ protocol RandomNumberGenerator { } ``` -`RandomNumberGenerator`协议要求其遵循者必须拥有一个名为`random`, 返回值类型为`Double`的实例方法。尽管这里并未指明,但是我们假设返回值在[0,1)区间内。 +`RandomNumberGenerator` 协议要求采纳协议的类型必须拥有一个名为 `random`, 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值在 `[0.0,1.0)` 区间内。 -`RandomNumberGenerator`协议并不在意每一个随机数是怎样生成的,它只强调这里有一个随机数生成器。 - -如下所示,下边的是一个遵循了`RandomNumberGenerator`协议的类。该类实现了一个叫做*线性同余生成器(linear congruential generator)*的伪随机数算法。 +`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。 +如下所示,下边是一个采纳并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器(linear congruential generator)* 的伪随机数算法。 ```swift class LinearCongruentialGenerator: RandomNumberGenerator { @@ -169,25 +173,24 @@ class LinearCongruentialGenerator: RandomNumberGenerator { } let generator = LinearCongruentialGenerator() print("Here's a random number: \(generator.random())") -// 输出 : "Here's a random number: 0.37464991998171" +// 打印 “Here's a random number: 0.37464991998171” print("And another one: \(generator.random())") -// 输出 : "And another one: 0.729023776863283" +// 打印 “And another one: 0.729023776863283” ``` -## 对Mutating方法的规定 +## Mutating 方法要求 -有时需要在方法中改变它的实例。例如,值类型(结构体,枚举)的实例方法中,将`mutating`关键字作为函数的前缀,写在`func`之前,表示可以在该方法中修改它所属的实例及其实例属性的值。这一过程在[在实例方法中修改值类型](./11_Methods.html#modifying_value_types_from_within_instance_methods)章节中有详细描述。 +有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在[在实例方法中修改值类型](./11_Methods.html#modifying_value_types_from_within_instance_methods)章节中有详细描述。 -如果你在协议中定义了一个方法旨在改变遵循该协议的实例,那么在协议定义时需要在方法前加`mutating`关键字。这使得结构和枚举遵循协议并满足此方法要求。 +如果你在协议中定义了一个实例方法,该方法会改变采纳该协议的类型的实例,那么在定义协议时需要在方法前加 `mutating` 关键字。这使得结构体和枚举能够采纳此协议并满足此方法要求。 +> 注意 +> 实现协议中的 `mutating` 方法时,若是类类型,则不用写 `mutating` 关键字。而对于结构体和枚举,则必须写 `mutating` 关键字。 ->注意: ->用类实现协议中的`mutating`方法时,不用写`mutating`关键字;用结构体,枚举实现协议中的`mutating`方法时,必须写`mutating`关键字。 +如下所示,`Togglable` 协议只要求实现一个名为 `toggle` 的实例方法。根据名称的暗示,`toggle()` 方法将改变实例属性,从而切换采纳该协议类型的实例的状态。 -如下所示,`Togglable`协议含有名为`toggle`的实例方法。根据名称推测,`toggle()`方法将通过改变实例属性,来切换遵循该协议的实例的状态。 - -`toggle()`方法在定义的时候,使用`mutating`关键字标记,这表明当它被调用时该方法将会改变协议遵循者实例的状态: +`toggle()` 方法在定义的时候,使用 `mutating` 关键字标记,这表明当它被调用时,该方法将会改变采纳协议的类型的实例: ```swift protocol Togglable { @@ -195,9 +198,9 @@ protocol Togglable { } ``` -当使用`枚举`或`结构体`来实现`Togglable`协议时,需要提供一个带有`mutating`前缀的`toggle`方法。 +当使用枚举或结构体来实现 `Togglable` 协议时,需要提供一个带有 `mutating` 前缀的 `toggle()` 方法。 -下面定义了一个名为`OnOffSwitch`的枚举类型。这个枚举类型在两种状态之间进行切换,用枚举成员`On`和`Off`表示。枚举类型的`toggle`方法被标记为`mutating`以满足`Togglable`协议的要求: +下面定义了一个名为 `OnOffSwitch` 的枚举。这个枚举在两种状态之间进行切换,用枚举成员 `On` 和 `Off` 表示。枚举的 `toggle()` 方法被标记为 `mutating`,以满足 `Togglable` 协议的要求: ```swift enum OnOffSwitch: Togglable { @@ -213,13 +216,13 @@ enum OnOffSwitch: Togglable { } var lightSwitch = OnOffSwitch.Off lightSwitch.toggle() -//lightSwitch 现在的值为 .On +// lightSwitch 现在的值为 .On ``` -## 对构造器的规定 +## 构造器要求 -协议可以要求它的遵循者实现指定的构造器。你可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体: +协议可以要求采纳协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体: ```swift protocol SomeProtocol { @@ -227,70 +230,68 @@ protocol SomeProtocol { } ``` -### 协议构造器规定在类中的实现 +### 构造器要求在类中的实现 -你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器(designated initializer)或者便利构造器(convenience initializer)。在这两种情况下,你都必须给构造器实现标上"required"修饰符: +你可以在采纳协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 `required` 修饰符: ```swift class SomeClass: SomeProtocol { required init(someParameter: Int) { - //构造器实现 + // 这里是构造器的实现部分 } } ``` -使用`required`修饰符可以保证:所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。 +使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。 -关于`required`构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。 +关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。 ->注意 ->如果类已经被标记为`final`,那么不需要在协议构造器的实现中使用`required`修饰符。因为final类不能有子类。关于`final`修饰符的更多内容,请参见[防止重写](./13_Inheritance.html#preventing_overrides)。 +> 注意 +> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见[防止重写](./13_Inheritance.html#preventing_overrides)。 -如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示`required`和`override`修饰符: +如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 `required` 和 `override` 修饰符: ```swift protocol SomeProtocol { init() } - class SomeSuperClass { init() { - // 构造器的实现 + // 这里是构造器的实现部分 } } - class SomeSubClass: SomeSuperClass, SomeProtocol { - // 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override" + // 因为采纳协议,需要加上 required + // 因为继承自父类,需要加上 override required override init() { - // 构造器实现 + // 这里是构造器的实现部分 } } ``` -### 可失败构造器的规定 +### 可失败构造器要求 -可以通过给协议`Protocols`中添加[可失败构造器](./14_Initialization.html#failable_initializers)来使遵循该协议的类型必须实现该可失败构造器。 - -如果在协议中定义一个可失败构造器,则在遵顼该协议的类型中必须添加同名同参数的可失败构造器或非可失败构造器。如果在协议中定义一个非可失败构造器,则在遵循该协议的类型中必须添加同名同参数的非可失败构造器或隐式解析类型的可失败构造器(`init!`)。 +协议还可以为采纳协议的类型定义可失败构造器要求,详见[可失败构造器](./14_Initialization.html#failable_initializers)。 +采纳协议的类型可以通过可失败构造器(`init?`)或非可失败构造器(`init`)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(`init`)或隐式解包可失败构造器(`init!`)来满足。 -## 协议类型 +## 协议作为类型 -尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。 +尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。 -协议可以像其他普通类型一样使用,使用场景: +协议可以像其他普通类型一样使用,使用场景如下: * 作为函数、方法或构造器中的参数类型或返回值类型 * 作为常量、变量或属性的类型 * 作为数组、字典或其他容器中的元素类型 > 注意 -> 协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法,例如(`FullyNamed`和`RandomNumberGenerator`) +> 协议是一种类型,因此协议类型的名称应与其他类型(例如 `Int`,`Double`,`String`)的写法相同,使用大写字母开头的驼峰式写法,例如(`FullyNamed` 和 `RandomNumberGenerator`)。 -如下所示,这个示例中将协议当做类型来使用: +下面是将协议作为类型使用的例子: ```swift class Dice { @@ -306,35 +307,34 @@ class Dice { } ``` -例子中定义了一个`Dice`类,用来代表桌游中的拥有N个面的骰子。`Dice`的实例含有`sides`和`generator`两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器。 +例子中定义了一个 `Dice` 类,用来代表桌游中拥有 N 个面的骰子。`Dice` 的实例含有 `sides` 和 `generator` 两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器,从而生成随机点数。 - `generator`属性的类型为`RandomNumberGenerator`,因此任何遵循了`RandomNumberGenerator`协议的类型的实例都可以赋值给`generator`,除此之外,无其他要求。 +`generator` 属性的类型为 `RandomNumberGenerator`,因此任何采纳了 `RandomNumberGenerator` 协议的类型的实例都可以赋值给 `generator`,除此之外并无其他要求。 -`Dice`类中也有一个构造器(initializer),用来进行初始化操作。构造器中含有一个名为`generator`,类型为`RandomNumberGenerator`的形参。在调用构造方法时创建`Dice`的实例时,可以传入任何遵循`RandomNumberGenerator`协议的实例给generator。 +`Dice` 类还有一个构造器,用来设置初始状态。构造器有一个名为 `generator`,类型为 `RandomNumberGenerator` 的形参。在调用构造方法创建 `Dice` 的实例时,可以传入任何采纳 `RandomNumberGenerator` 协议的实例给 `generator`。 -`Dice`类也提供了一个名为`roll`的实例方法用来模拟骰子的面值。它先使用`generator`的`random()`方法来创建一个[0,1)区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为generator遵循了`RandomNumberGenerator`协议,因而保证了`random`方法可以被调用。 +`Dice` 类提供了一个名为 `roll` 的实例方法,用来模拟骰子的面值。它先调用 `generator` 的 `random()` 方法来生成一个 `[0.0,1.0)` 区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为 `generator` 采纳了 `RandomNumberGenerator` 协议,可以确保它有个 `random()` 方法可供调用。 -下面的例子展示了如何使用`LinearCongruentialGenerator`的实例作为随机数生成器创建一个六面骰子: +下面的例子展示了如何使用 `LinearCongruentialGenerator` 的实例作为随机数生成器来创建一个六面骰子: ```swift var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator()) for _ in 1...5 { print("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 +// 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 ``` -## 委托(代理)模式 +## 委托(代理)模式 -委托是一种设计模式,它允许`类`或`结构体`将一些需要它们负责的功能`交由(或委托)`给其他的类型的实例。委托模式的实现很简单: 定义协议来封装那些需要被委托的函数和方法,使其`遵循者`拥有这些被委托的`函数和方法`。委托模式可以用来响应特定的动作或接收外部数据源提供的数据,而无需要知道外部数据源的类型信息。 +委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保采纳协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。 -下面的例子是两个基于骰子游戏的协议: +下面的例子定义了两个基于骰子游戏的协议: ```swift protocol DiceGame { @@ -349,9 +349,9 @@ protocol DiceGameDelegate { } ``` -`DiceGame`协议可以在任意含有骰子的游戏中实现。`DiceGameDelegate`协议可以用来追踪`DiceGame`的游戏过程。 +`DiceGame` 协议可以被任意涉及骰子的游戏采纳。`DiceGameDelegate` 协议可以被任意类型采纳,用来追踪 `DiceGame` 的游戏过程。 -如下所示,`SnakesAndLadders`是`Snakes and Ladders`([Control Flow](./05_Control_Flow.html)章节有该游戏的详细介绍)游戏的新版本。新版本使用`Dice`作为骰子,并且实现了`DiceGame`和`DiceGameDelegate`协议,后者用来记录游戏的过程: +如下所示,`SnakesAndLadders` 是 [控制流](./05_Control_Flow.html) 章节引入的蛇梯棋游戏的新版本。新版本使用 `Dice` 实例作为骰子,并且实现了 `DiceGame` 和 `DiceGameDelegate` 协议,后者用来记录游戏的过程: ```swift class SnakesAndLadders: DiceGame { @@ -370,15 +370,15 @@ class SnakesAndLadders: DiceGame { delegate?.gameDidStart(self) gameLoop: while square != finalSquare { let diceRoll = dice.roll() - delegate?.game(self,didStartNewTurnWithDiceRoll: diceRoll) + 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] + square += diceRoll + square += board[square] } } delegate?.gameDidEnd(self) @@ -386,17 +386,19 @@ class SnakesAndLadders: DiceGame { } ``` -这个版本的游戏封装到了`SnakesAndLadders`类中,该类遵循了`DiceGame`协议,并且提供了相应的可读的`dice`属性和`play`实例方法。(`dice`属性在构造之后就不再改变,且协议只要求`dice`为只读的,因此将`dice`声明为常量属性。) +关于这个蛇梯棋游戏的详细描述请参阅 [控制流](./05_Control_Flow.html) 章节中的 [Break](./05_Control_Flow.html#break) 部分。 -游戏使用`SnakesAndLadders`类的`构造器(initializer)`初始化游戏。所有的游戏逻辑被转移到了协议中的`play`方法,`play`方法使用协议规定的`dice`属性提供骰子摇出的值。 +这个版本的游戏封装到了 `SnakesAndLadders` 类中,该类采纳了 `DiceGame` 协议,并且提供了相应的可读的 `dice` 属性和 `play()` 方法。( `dice` 属性在构造之后就不再改变,且协议只要求 `dice` 为可读的,因此将 `dice` 声明为常量属性。) -注意:`delegate`并不是游戏的必备条件,因此`delegate`被定义为遵循`DiceGameDelegate`协议的可选属性。因为`delegate`是可选值,因此在初始化的时候被自动赋值为`nil`。随后,可以在游戏中为`delegate`设置适当的值。 +游戏使用 `SnakesAndLadders` 类的 `init()` 构造器来初始化游戏。所有的游戏逻辑被转移到了协议中的 `play()` 方法,`play()` 方法使用协议要求的 `dice` 属性提供骰子摇出的值。 -`DicegameDelegate`协议提供了三个方法用来追踪游戏过程。被放置于游戏的逻辑中,即`play()`方法内。分别在游戏开始时,新一轮开始时,游戏结束时被调用。 +注意,`delegate` 并不是游戏的必备条件,因此 `delegate` 被定义为 `DiceGameDelegate` 类型的可选属性。因为 `delegate` 是可选值,因此会被自动赋予初始值 `nil`。随后,可以在游戏中为 `delegate` 设置适当的值。 -因为`delegate`是一个遵循`DiceGameDelegate`的可选属性,因此在`play()`方法中使用了`可选链`来调用委托方法。 若`delegate`属性为`nil`, 则delegate所调用的方法失效,并不会产生错误。若`delegate`不为`nil`,则方法能够被调用 +`DicegameDelegate` 协议提供了三个方法用来追踪游戏过程。这三个方法被放置于游戏的逻辑中,即 `play()` 方法内。分别在游戏开始时,新一轮开始时,以及游戏结束时被调用。 -如下所示,`DiceGameTracker`遵循了`DiceGameDelegate`协议: +因为 `delegate` 是一个 `DiceGameDelegate` 类型的可选属性,因此在 `play()` 方法中通过可选链式调用来调用它的方法。若 `delegate` 属性为 `nil`,则调用方法会优雅地失败,并不会产生错误。若 `delegate` 不为 `nil`,则方法能够被调用,并传递 `SnakesAndLadders` 实例作为参数。 + +如下示例定义了 `DiceGameTracker` 类,它采纳了 `DiceGameDelegate` 协议: ```swift class DiceGameTracker: DiceGameDelegate { @@ -409,7 +411,7 @@ class DiceGameTracker: DiceGameDelegate { print("The game is using a \(game.dice.sides)-sided dice") } func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) { - ++numberOfTurns + numberOfTurns += 1 print("Rolled a \(diceRoll)") } func gameDidEnd(game: DiceGame) { @@ -418,37 +420,37 @@ class DiceGameTracker: DiceGameDelegate { } ``` -`DiceGameTracker`实现了`DiceGameDelegate`协议规定的三个方法,用来记录游戏已经进行的轮数。 当游戏开始时,`numberOfTurns`属性被赋值为0; 在每新一轮中递增; 游戏结束后,输出打印游戏的总轮数。 +`DiceGameTracker` 实现了 `DiceGameDelegate` 协议要求的三个方法,用来记录游戏已经进行的轮数。当游戏开始时,`numberOfTurns` 属性被赋值为 `0`,然后在每新一轮中递增,游戏结束后,打印游戏的总轮数。 -`gameDidStart`方法从`game`参数获取游戏信息并输出。`game`在方法中被当做`DiceGame`类型而不是`SnakeAndLadders`类型,所以方法中只能访问`DiceGame`协议中的成员。当然了,这些方法也可以在类型转换之后调用。在上例代码中,通过`is`操作符检查`game`是否为 `SnakesAndLadders`类型的实例,如果是,则打印出相应的内容。 +`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。 -无论当前进行的是何种游戏,`game`都遵循`DiceGame`协议以确保`game`含有`dice`属性,因此在`gameDidStart(_:)`方法中可以通过传入的`game`参数来访问`dice`属性,进而打印出`dice`的`sides`属性的值。 +无论当前进行的是何种游戏,由于 `game` 符合 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice` 的 `sides` 属性的值。 -`DiceGameTracker`的运行情况,如下所示: +`DiceGameTracker` 的运行情况如下所示: ```swift let tracker = DiceGameTracker() let game = SnakesAndLadders() game.delegate = tracker game.play() -// 开始一个新的Snakes and Ladders的游戏 -// 游戏使用 6 面的骰子 -// 翻转得到 3 -// 翻转得到 5 -// 翻转得到 4 -// 翻转得到 5 -// 游戏进行了 4 轮 +// 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)来扩充已存在类型(*译者注: 类,结构体,枚举等*)。扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。详情请在[扩展](./21_Extensions.html)章节中查看。 +即便无法修改源代码,依然可以通过扩展令已有类型采纳并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在[扩展](./21_Extensions.html)章节中查看。 > 注意 -> 通过扩展为已存在的类型遵循协议时,该类型的所有实例也会随之添加协议中的方法 +> 通过扩展令已有类型采纳并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。 -例如`TextRepresentable`协议,任何想要表示一些文本内容的类型都可以遵循该协议。这些想要表示的内容可以是类型本身的描述,也可以是当前内容的版本: +例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述: ```swift protocol TextRepresentable { @@ -456,7 +458,7 @@ protocol TextRepresentable { } ``` -可以通过扩展,为上一节中提到的`Dice`增加类遵循`TextRepresentable`协议的功能: +可以通过扩展,令先前提到的 `Dice` 类采纳并符合 `TextRepresentable` 协议: ```swift extension Dice: TextRepresentable { @@ -465,17 +467,18 @@ extension Dice: TextRepresentable { } } ``` -现在,通过扩展使得`Dice`类型遵循了一个新的协议,这和`Dice`类型在定义的时候声明为遵循`TextRepresentable`协议的效果相同。在扩展的时候,协议名称写在类型名之后,以冒号隔开,在大括号内写明新添加的协议内容。 -现在所有`Dice`的实例都遵循了`TextRepresentable`协议: +通过扩展采纳并符合协议,和在原始定义中采纳并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。 + +现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型: ```swift -let d12 = Dice(sides: 12,generator: LinearCongruentialGenerator()) -print(d12. textualDescription) -// 输出 "A 12-sided dice" +let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator()) +print(d12.textualDescription) +// 打印 “A 12-sided dice” ``` -同样`SnakesAndLadders`类也可以通过`扩展`的方式来遵循`TextRepresentable`协议: +同样,`SnakesAndLadders` 类也可以通过扩展采纳并符合 `TextRepresentable` 协议: ```swift extension SnakesAndLadders: TextRepresentable { @@ -484,13 +487,13 @@ extension SnakesAndLadders: TextRepresentable { } } print(game.textualDescription) -// 输出 "A game of Snakes and Ladders with 25 squares" +// 打印 “A game of Snakes and Ladders with 25 squares” ``` -## 通过扩展补充协议声明 +## 通过扩展采纳协议 -当一个类型已经实现了协议中的所有要求,却没有声明为遵循该协议时,可以通过扩展(空的扩展体)来补充协议声明: +当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展来采纳该协议: ```swift struct Hamster { @@ -502,53 +505,52 @@ struct Hamster { extension Hamster: TextRepresentable {} ``` -从现在起,`Hamster`的实例可以作为`TextRepresentable`类型使用: +从现在起,`Hamster` 的实例可以作为 `TextRepresentable` 类型使用: ```swift let simonTheHamster = Hamster(name: "Simon") let somethingTextRepresentable: TextRepresentable = simonTheHamster print(somethingTextRepresentable.textualDescription) -// 输出 "A hamster named Simon" +// 打印 “A hamster named Simon” ``` > 注意 -> 即使满足了协议的所有要求,类型也不会自动转变,因此你必须为它做出显式的协议声明。 +> 即使满足了协议的所有要求,类型也不会自动采纳协议,必须显式地采纳协议。 ## 协议类型的集合 -协议类型可以在数组或者字典这样的集合中使用,在[协议类型](./22_Protocols.html##protocols_as_types)提到了这样的用法。下面的例子创建了一个类型为`TextRepresentable`的数组: +协议类型可以在数组或者字典这样的集合中使用,在[协议类型](./22_Protocols.html##protocols_as_types)提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组: ```swift -let things: [TextRepresentable] = [game,d12,simonTheHamster] +let things: [TextRepresentable] = [game, d12, simonTheHamster] ``` -如下所示,`things`数组可以被直接遍历,并打印每个元素的文本表示: +如下所示,可以遍历 `things` 数组,并打印每个元素的文本表示: ```swift for thing in things { print(thing.textualDescription) } -// 输出: // A game of Snakes and Ladders with 25 squares // A 12-sided dice // A hamster named Simon ``` -`thing`被当做是`TextRepresentable`类型而不是`Dice`,`DiceGame`,`Hamster`等类型,即使真实的实例是它们中的一种类型。尽管如此,由于它是`TextRepresentable`类型,任何`TextRepresentable`都拥有一个`textualDescription`属性,所以每次循环访问`thing.textualDescription`是安全的。 +`thing` 是 `TextRepresentable` 类型而不是 `Dice`,`DiceGame`,`Hamster` 等类型,即使实例在幕后确实是这些类型中的一种。由于 `thing` 是 `TextRepresentable` 类型,任何 `TextRepresentable` 的实例都有一个 `textualDescription` 属性,所以在每次循环中可以安全地访问 `thing.textualDescription`。 ## 协议的继承 -协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔: +协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔: ```swift protocol InheritingProtocol: SomeProtocol, AnotherProtocol { - // 协议定义 + // 这里是协议的定义部分 } ``` -如下所示,`PrettyTextRepresentable`协议继承了`TextRepresentable`协议: +如下所示,`PrettyTextRepresentable` 协议继承了 `TextRepresentable` 协议: ```swift protocol PrettyTextRepresentable: TextRepresentable { @@ -556,9 +558,9 @@ protocol PrettyTextRepresentable: TextRepresentable { } ``` -例子中定义了一个新的协议`PrettyTextRepresentable`,它继承自`TextRepresentable`协议。任何遵循`PrettyTextRepresentable`协议的类型在满足该协议的要求时,也必须满足`TextRepresentable`协议的要求。在这个例子中,`PrettyTextRepresentable`协议要求其遵循者提供一个返回值为`String`类型的`prettyTextualDescription`属性。 +例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何采纳 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求采纳协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。 -如下所示,扩展`SnakesAndLadders`,让其遵循`PrettyTextRepresentable`协议: +如下所示,扩展 `SnakesAndLadders`,使其采纳并符合 `PrettyTextRepresentable` 协议: ```swift extension SnakesAndLadders: PrettyTextRepresentable { @@ -566,7 +568,7 @@ extension SnakesAndLadders: PrettyTextRepresentable { var output = textualDescription + ":\n" for index in 1...finalSquare { switch board[index] { - case let ladder where ladder > 0: + case let ladder where ladder > 0: output += "▲ " case let snake where snake < 0: output += "▼ " @@ -579,42 +581,42 @@ extension SnakesAndLadders: PrettyTextRepresentable { } ``` -上述扩展使得`SnakesAndLadders`遵循了`PrettyTextRepresentable`协议,并为每个`SnakesAndLadders`类型提供了协议要求的`prettyTextualDescription`属性。每个`PrettyTextRepresentable`类型同时也是`TextRepresentable`类型,所以在`prettyTextualDescription`的实现中,可以调用`textualDescription`属性。之后在每一行加上换行符,作为输出的开始。然后遍历数组中的元素,输出一个几何图形来表示遍历的结果: +上述扩展令 `SnakesAndLadders` 采纳了 `PrettyTextRepresentable` 协议,并提供了协议要求的 `prettyTextualDescription` 属性。每个 `PrettyTextRepresentable` 类型同时也是 `TextRepresentable` 类型,所以在 `prettyTextualDescription` 的实现中,可以访问 `textualDescription` 属性。然后,拼接上了冒号和换行符。接着,遍历数组中的元素,拼接一个几何图形来表示每个棋盘方格的内容: -* 当从数组中取出的元素的值大于0时,用`▲`表示 -* 当从数组中取出的元素的值小于0时,用`▼`表示 -* 当从数组中取出的元素的值等于0时,用`○`表示 +* 当从数组中取出的元素的值大于 `0` 时,用 `▲` 表示。 +* 当从数组中取出的元素的值小于 `0` 时,用 `▼` 表示。 +* 当从数组中取出的元素的值等于 `0` 时,用 `○` 表示。 -任意`SankesAndLadders`的实例都可以使用`prettyTextualDescription`属性。 +任意 `SankesAndLadders` 的实例都可以使用 `prettyTextualDescription` 属性来打印一个漂亮的文本描述: ```swift print(game.prettyTextualDescription) -// A game of Snakes and Ladders with 25 squares:TODO +// A game of Snakes and Ladders with 25 squares: // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○ ``` -## 类专属协议 -你可以在协议的继承列表中,通过添加`class`关键字,限制协议只能适配到类(class)类型。(结构体或枚举不能遵循该协议)。该`class`关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。 +## 类类型专属协议 + +你可以在协议的继承列表中,通过添加 `class` 关键字来限制协议只能被类类型采纳,而结构体或枚举不能采纳该协议。`class` 关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前: ```swift protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol { - // 协议定义 + // 这里是类类型专属协议的定义部分 } ``` -在以上例子中,协议`SomeClassOnlyProtocol`只能被类(class)类型适配。如果尝试让结构体或枚举类型适配该协议,则会出现编译错误。 - ->注意 ->当协议想要定义的行为,要求(或假设)它的遵循类型必须是引用语义而非值语义时,应该采用类专属协议。关于引用语义,值语义的更多内容,请查看[结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)和[类是引用类型](./09_Classes_and_Structures.html#classes_are_reference_types)。 +在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳该协议,则会导致编译错误。 +> 注意 +> 当协议定义的要求需要采纳协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看[结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)和[类是引用类型](./09_Classes_and_Structures.html#classes_are_reference_types)。 ## 协议合成 -有时候需要同时遵循多个协议。你可以将多个协议采用`protocol`这样的格式进行组合,称为`协议合成(protocol composition)`。你可以在`<>`中罗列任意多个你想要遵循的协议,以逗号分隔。 +有时候需要同时采纳多个协议,你可以将多个协议采用 `protocol` 这样的格式进行组合,称为 *协议合成(protocol composition)*。你可以在 `<>` 中罗列任意多个你想要采纳的协议,以逗号分隔。 -下面的例子中,将`Named`和`Aged`两个协议按照上述的语法组合成一个协议: +下面的例子中,将 `Named` 和 `Aged` 两个协议按照上述语法组合成一个协议,作为函数参数的类型: ```swift protocol Named { @@ -632,28 +634,28 @@ func wishHappyBirthday(celebrator: protocol) { } let birthdayPerson = Person(name: "Malcolm", age: 21) wishHappyBirthday(birthdayPerson) -// 输出 "Happy birthday Malcolm - you're 21! +// 打印 “Happy birthday Malcolm - you're 21!” ``` -`Named`协议包含`String`类型的`name`属性;`Aged`协议包含`Int`类型的`age`属性。`Person`结构体`遵循`了这两个协议。 +`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体采纳了这两个协议。 -`wishHappyBirthday`函数的形参`celebrator`的类型为`protocol`。可以传入任意`遵循`这两个协议的类型的实例。 +`wishHappyBirthday(_:)` 函数的参数 `celebrator` 的类型为 `protocol`。这意味着它不关心参数的具体类型,只要参数符合这两个协议即可。 -上面的例子创建了一个名为`birthdayPerson`的`Person`实例,作为参数传递给了`wishHappyBirthday(_:)`函数。因为`Person`同时遵循这两个协议,所以这个参数合法,函数将输出生日问候语。 +上面的例子创建了一个名为 `birthdayPerson` 的 `Person` 的实例,作为参数传递给了 `wishHappyBirthday(_:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。 > 注意 -> `协议合成`并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。 +> 协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。 -## 检验协议的一致性 +## 检查协议一致性 -你可以使用`is`和`as`操作符来检查是否遵循某一协议或强制转化为某一类型。检查和转化的语法和之前相同(*详情查看[类型转换](./20_Type_Casting.html)*): +你可以使用[类型转换](./20_Type_Casting.html)中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同: -* `is`操作符用来检查实例是否`遵循`了某个`协议`。 -* `as?`返回一个可选值,当实例`遵循`协议时,返回该协议类型;否则返回`nil`。 -* `as`用以强制向下转型,如果强转失败,会引起运行时错误。 +* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`。 +* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`。 +* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。 -下面的例子定义了一个`HasArea`的协议,要求有一个`Double`类型可读的`area`: +下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`: ```swift protocol HasArea { @@ -661,7 +663,7 @@ protocol HasArea { } ``` -如下所示,定义了`Circle`和`Country`类,它们都遵循了`HasArea`协议: +如下所示,`Circle` 类和 `Country` 类都采纳了 `HasArea` 协议: ```swift class Circle: HasArea { @@ -676,9 +678,9 @@ class Country: HasArea { } ``` -`Circle`类把`area`实现为基于`存储型属性`radius的`计算型属性`,`Country`类则把`area`实现为`存储型属性`。这两个类都`遵循`了`HasArea`协议。 +`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地符合了 `HasArea` 协议。 -如下所示,`Animal`是一个没有实现`HasArea`协议的类: +如下所示,`Animal` 是一个未采纳 `HasArea` 协议的类: ```swift class Animal { @@ -687,7 +689,7 @@ class Animal { } ``` -`Circle`,`Country`,`Animal`并没有一个相同的基类,然而,它们都是类,它们的实例都可以作为`AnyObject`类型的变量,存储在同一个数组中: +`Circle`,`Country`,`Animal` 并没有一个共同的基类,尽管如此,它们都是类,它们的实例都可以作为 `AnyObject` 类型的值,存储在同一个数组中: ```swift let objects: [AnyObject] = [ @@ -697,9 +699,9 @@ let objects: [AnyObject] = [ ] ``` -`objects`数组使用字面量初始化,数组包含一个`radius`为2的`Circle`的实例,一个保存了英国面积的`Country`实例和一个`legs`为4的`Animal`实例。 +`objects` 数组使用字面量初始化,数组包含一个 `radius` 为 `2` 的 `Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs` 为 `4` 的 `Animal` 实例。 -如下所示,`objects`数组可以被迭代,对迭代出的每一个元素进行检查,看它是否遵循了`HasArea`协议: +如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否符合 `HasArea` 协议: ```swift for object in objects { @@ -714,22 +716,23 @@ for object in objects { // Something that doesn't have an area ``` -当迭代出的元素遵循`HasArea`协议时,通过`as?`操作符将其`可选绑定(optional binding)`到`objectWithArea`常量上。`objectWithArea`是`HasArea`协议类型的实例,因此`area`属性是可以被访问和打印的。 +当迭代出的元素符合 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea` 是 `HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。 -`objects`数组中元素的类型并不会因为强转而丢失类型信息,它们仍然是`Circle`,`Country`,`Animal`类型。然而,当它们被赋值给`objectWithArea`常量时,则只被视为`HasArea`类型,因此只有`area`属性能够被访问。 +`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle`,`Country`,`Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。 -## 对可选协议的规定 -协议可以含有可选成员,其`遵循者`可以选择是否实现这些成员。在协议中使用`optional`关键字作为前缀来定义可选成员。当需要使用可选规定的方法或者属性时,他的类型自动会变成可选的。比如,一个定义为`(Int) -> String`的方法变成`((Int) -> String)?`。需要注意的是整个函数定义包裹在可选中,而不是放在函数的返回值后面。 +## 可选的协议要求 -可选协议在调用时使用`可选链`,因为协议的遵循者可能没有实现可选内容。像`someOptionalMethod?(someArgument)`这样,你可以在可选方法名称后加上`?`来检查该方法是否被实现。详细内容在[可空链式调用](./17_Optional_Chaining.html)章节中查看。 +协议可以定义可选要求,采纳协议的类型可以选择是否实现这些要求。在协议中使用 `optional` 关键字作为前缀来定义可选要求。使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值。 + +协议中的可选要求可通过可选链式调用来使用,因为采纳协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./17_Optional_Chaining.html)章节中查看。 > 注意 -> 可选协议只能在含有`@objc`前缀的协议中生效。 -> 这个前缀表示协议将暴露给Objective-C代码,详情参见[`Using Swift with Cocoa and Objective-C(Swift 2.1)`](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。即使你不打算和Objective-C有什么交互,如果你想要指明协议包含可选属性,那么还是要加上`@obj`前缀。 -> 还需要注意的是,`@objc`的协议只能由继承自 Objective-C 类的类或者其他的`@objc`类来遵循。它也不能被结构体和枚举遵循。 +> 可选的协议要求只能用在标记 `@objc` 特性的协议中。 +> 该特性表示协议将暴露给 Objective-C 代码,详情参见[`Using Swift with Cocoa and Objective-C(Swift 2.2)`](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。即使你不打算和 Objective-C 有什么交互,如果你想要指定可选的协议要求,那么还是要为协议加上 `@objc` 特性。 +> 还需要注意的是,标记 `@objc` 特性的协议只能被继承自 Objective-C 类的类或者 `@objc` 类采纳,其他类以及结构体和枚举均不能采纳这种协议。 -下面的例子定义了一个叫`Counter`的整数加法类,它使用外部的数据源来提供每次的增量。数据源是两个可选规定,在`CounterDataSource`协议中定义: +下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,包含两个可选要求: ```swift @objc protocol CounterDataSource { @@ -738,49 +741,49 @@ for object in objects { } ``` -`CounterDataSource`含有`incrementForCount(_:)`可选方法和`fiexdIncrement`可选属性,它们使用了不同的方法来从数据源中获取合适的增量值。 +`CounterDataSource` 协议定义了一个可选方法 `incrementForCount(_:)` 和一个可选属性 `fiexdIncrement`,它们使用了不同的方法来从数据源中获取适当的增量值。 > 注意 -> 严格来讲,`CounterDataSource`中的属性和方法都是可选的,因此可以在类中声明都不实现这些成员,尽管技术上允许这样做,不过最好不要这样写。 +> 严格来讲,`CounterDataSource` 协议中的方法和属性都是可选的,因此采纳协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。 -`Counter`类含有`CounterDataSource?`类型的可选属性`dataSource`,如下所示: +`Counter` 类含有 `CounterDataSource?` 类型的可选属性 `dataSource`,如下所示: ```swift -@objc class Counter { +class Counter { var count = 0 var dataSource: CounterDataSource? func increment() { if let amount = dataSource?.incrementForCount?(count) { count += amount - } else if let amount = dataSource?.fixedIncrement? { + } else if let amount = dataSource?.fixedIncrement { count += amount } } } ``` -类`Counter`使用`count`来存储当前的值。该类同时定义了一个`increment`方法,每次调用该方法的时候,将会增加`count`的值。 +`Counter` 类使用变量属性 `count` 来存储当前值。该类还定义了一个 `increment()` 方法,每次调用该方法的时候,将会增加 `count` 的值。 -`increment()`方法首先试图使用`incrementForCount(_:)`方法来得到每次的增量。`increment()`方法使用可选链来尝试调用`incrementForCount(_:)`,并将当前的`count`值作为参数传入。 +`increment()` 方法首先试图使用 `incrementForCount(_:)` 方法来得到每次的增量。`increment()` 方法使用可选链式调用来尝试调用 `incrementForCount(_:)`,并将当前的 `count` 值作为参数传入。 -这里使用了两种可选链方法。首先,由于`dataSource`可能为`nil`,因此在`dataSource`后边加上了`?`标记来表明只在`dataSource`非空时才去调用`incrementForCount(_:)`方法。其次,即使`dataSource`存在,也无法保证其是否实现了`incrementForCount(_:)`方法,因为这个方法是可选的。在这里,有可能未被实现的`incrementForCount(_:)`方法同样使用可选链进行调用。只有在`incrementForCount(_:)`存在的情况下才能调用`incrementForCount(_:)`-也就是说,它是`nil`的时候。这就是为什么要在`incrementForCount(_:)`方法后边也加有`?`标记的原因。 +这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `incrementForCount(_:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `incrementForCount(_:)` 方法,因为这个方法是可选的。因此,`incrementForCount(_:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `incrementForCount(_:)` 方法后边也加上了 `?`。 -调用`incrementForCount(_:)`方法在上述两种情形都有可能失败,所以返回值为*可选*`Int`类型。虽然在`CounterDataSource`中,`incrementForCount`被定义为一个非可选`Int`(non-optional),但是这里我们仍然需要返回*可选*`Int`类型。想获得更多的关于如何使用多可选链的操作的信息,请查阅[多层链接](./17_Optional_Chaining) +调用 `incrementForCount(_:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`incrementForCount(_:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型,即 `Int?` 而不是 `Int??`。关于这一点的更多信息,请查阅[连接多层可选链式调用](./17_Optional_Chaining) -在调用`incrementForCount(_:)`方法后,`Int`型`可选值`通过`可选绑定(optional binding)`自动拆包并赋值给常量`amount`。如果可选值确实包含一个数值,这表示`delegate`和方法都存在,之后便将`amount`加到`count`上,增加操作完成。 +在调用 `incrementForCount(_:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成。 -如果没有从`incrementForCount(_:)`获取到值,可能是`dataSource`为nil,或者它并没有实现`incrementForCount(_:)`方法——那么`increment()`方法将试图从数据源的`fixedIncrement`属性中获取增量。`fixedIncrement`也是一个可选型,所以在属性名的后面添加`?`来试图取回可选属性的值。和之前一样,返回值为可选型,即使在`CounterDataSource`中定义的是一个非可选的`Int`类型的`fixedIncrement`属性。 +如果没有从 `incrementForCount(_:)` 方法获取到值,可能由于 `dataSource` 为 `nil`,或者它并没有实现 `incrementForCount(_:)` 方法,那么 `increment()` 方法将试图从数据源的 `fixedIncrement` 属性中获取增量。`fixedIncrement` 是一个可选属性,因此属性值是一个 `Int?` 值,即使该属性在 `CounterDataSource` 协议中的类型是非可选的 `Int`。 -`ThreeSource`实现了`CounterDataSource`协议,它实现来可选属性`fixedIncrement`,设置值为`3`: +下面的例子展示了 `CounterDataSource` 的简单实现。`ThreeSource` 类采纳了 `CounterDataSource` 协议,它实现了可选属性 `fixedIncrement`,每次会返回 `3`: ```swift -@objc class ThreeSource: CounterDataSource { - let fixedIncrement = 3 +class ThreeSource: NSObject, CounterDataSource { + let fixedIncrement = 3 } ``` -可以使用`ThreeSource`的实例作为`Counter`实例的数据源: +可以使用 `ThreeSource` 的实例作为 `Counter` 实例的数据源: ```swift var counter = Counter() @@ -795,13 +798,13 @@ for _ in 1...4 { // 12 ``` -上述代码新建了一个`Counter`实例;将它的数据源设置为`TreeSource`实例;调用`increment()`4次。和你预想的一样,每次在调用的时候,`count`的值增加3. +上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `ThreeSource` 的实例,然后调用 `increment()` 方法四次。和预期一样,每次调用都会将 `count` 的值增加 `3`. -下面是一个更为复杂的数据源`TowardsZeroSource`,它将使得最后的值变为0: +下面是一个更为复杂的数据源 `TowardsZeroSource`,它将使得最后的值变为 `0`: ```swift -class TowardsZeroSource: CounterDataSource { -func incrementForCount(count: Int) -> Int { +@objc class TowardsZeroSource: NSObject, CounterDataSource { + func incrementForCount(count: Int) -> Int { if count == 0 { return 0 } else if count < 0 { @@ -813,11 +816,9 @@ func incrementForCount(count: Int) -> Int { } ``` -`TowardsZeroSource`实现了`CounterDataSource`协议中的`incrementForCount(_:)`方法,以`count`参数为依据,计算出每次的增量。如果`count`已经为0,方法返回0,这表示之后不会再有增量。 +`TowardsZeroSource` 实现了 `CounterDataSource` 协议中的 `incrementForCount(_:)` 方法,以 `count` 参数为依据,计算出每次的增量。如果 `count` 已经为 `0`,此方法返回 `0`,以此表明之后不应再有增量操作发生。 -你可以配合使用`TowardsZeroSource`实例和`Counter`实例来从`-4`增加到`0`.一旦增加到`0`,数值便不会再有变动。 - -在下面的例子中,将从`-4`增加到`0`。一旦结果为`0`,便不在增加: +你可以使用 `TowardsZeroSource` 实例将 `Counter` 实例来从 `-4` 增加到 `0`。一旦增加到 `0`,数值便不会再有变动: ```swift counter.count = -4 @@ -836,9 +837,9 @@ for _ in 1...5 { ## 协议扩展 -使用扩展协议的方式可以为遵循者提供方法或属性的实现。通过这种方式,可以让你无需在每个遵循者中都实现一次,无需使用全局函数,你可以通过扩展协议的方式进行定义。 +协议可以通过扩展来为采纳协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个采纳协议的类型中都重复同样的实现,也无需使用全局函数。 -例如,可以扩展`RandomNumberGenerator`协议,让其提供`randomBool()`方法。该方法使用`random()`方法返回一个随机的`Bool`值: +例如,可以扩展 `RandomNumberGenerator` 协议来提供 `randomBool()` 方法。该方法使用协议中定义的 `random()` 方法来返回一个随机的 `Bool` 值: ```swift extension RandomNumberGenerator { @@ -848,24 +849,25 @@ extension RandomNumberGenerator { } ``` -通过扩展协议,所有协议的遵循者,在不用任何修改的情况下,都自动得到了这个扩展所增加的方法。 +通过协议扩展,所有采纳协议的类型,都能自动获得这个扩展所增加的方法实现,无需任何额外修改: ```swift let generator = LinearCongruentialGenerator() print("Here's a random number: \(generator.random())") -// 输出 "Here's a random number: 0.37464991998171" +// 打印 “Here's a random number: 0.37464991998171” print("And here's a random Boolean: \(generator.randomBool())") -// 输出 "And here's a random Boolean: true" +// 打印 “And here's a random Boolean: true” ``` + ### 提供默认实现 -可以通过协议扩展的方式来为协议规定的属性和方法提供默认的实现。如果协议的遵循者对规定的属性和方法提供了自己的实现,那么遵循者提供的实现将被使用。 +可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果采纳协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。 > 注意 -> 通过扩展协议提供的协议实现和可选协议规定有区别。虽然协议遵循者无需自己实现,通过扩展提供的默认实现,可以不是用可选链调用。 +> 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,采纳协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。 -例如,`PrettyTextRepresentable`协议,继承自`TextRepresentable`协议,可以为其提供一个默认的`prettyTextualDescription`属性,来简化访问`textualDescription`属性: +例如,`PrettyTextRepresentable` 协议继承自 `TextRepresentable` 协议,可以为其提供一个默认的 `prettyTextualDescription` 属性,只是简单地返回 `textualDescription` 属性的值: ```swift extension PrettyTextRepresentable { @@ -875,14 +877,15 @@ extension PrettyTextRepresentable { } ``` + ### 为协议扩展添加限制条件 -在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议遵循者,才能获得协议扩展提供的属性和方法。这些限制写在协议名之后,使用`where`关键字来描述限制情况。([Where语句](./23_Generics.html#where_clauses))。: +在扩展协议的时候,可以指定一些限制条件,只有采纳协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[Where子句](./23_Generics.html#where_clauses)中所描述的。 -例如,你可以扩展`CollectionType`协议,但是只适用于元素遵循`TextRepresentable`的情况: +例如,你可以扩展 `CollectionType` 协议,但是只适用于集合中的元素采纳了 `TextRepresentable` 协议的情况: ```swift -extension CollectionType where Generator.Element : TextRepresentable { +extension CollectionType where Generator.Element: TextRepresentable { var textualDescription: String { let itemsAsText = self.map { $0.textualDescription } return "[" + itemsAsText.joinWithSeparator(", ") + "]" @@ -890,9 +893,9 @@ extension CollectionType where Generator.Element : TextRepresentable { } ``` -`textualDescription`属性将每个元素的文本描述以逗号分隔的方式连接起来。 +`textualDescription` 属性返回整个集合的文本描述,它将集合中的每个元素的文本描述以逗号分隔的方式连接起来,包在一对方括号中。 -现在我们来看`Hamster`,它遵循`TextRepresentable`协议: +现在我们来看看先前的 `Hamster` 结构体,它符合 `TextRepresentable` 协议,同时这里还有个装有 `Hamster` 的实例的数组: ```swift let murrayTheHamster = Hamster(name: "Murray") @@ -901,12 +904,12 @@ let mauriceTheHamster = Hamster(name: "Maurice") let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster] ``` -因为`Array`遵循`CollectionType`协议,数组的元素又遵循`TextRepresentable`协议,所以数组可以使用`textualDescription`属性得到数组内容的文本表示: +因为 `Array` 符合 `CollectionType` 协议,而数组中的元素又符合 `TextRepresentable` 协议,所以数组可以使用 `textualDescription` 属性得到数组内容的文本表示: ```swift print(hamsters.textualDescription) -// 输出 "(A hamster named Murray, A hamster named Morgan, A hamster named Maurice)" +// 打印 “[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]” ``` > 注意 -> 如果有多个协议扩展,而一个协议的遵循者又同时满足它们的限制,那么将会使用所满足限制最多的那个扩展。 +> 如果多个协议扩展都为同一个协议要求提供了默认实现,而采纳协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。 diff --git a/source/chapter2/23_Generics.md b/source/chapter2/23_Generics.md index 360458e0..03633c0b 100644 --- a/source/chapter2/23_Generics.md +++ b/source/chapter2/23_Generics.md @@ -9,6 +9,13 @@ > 2.0 > 翻译+校对: [SergioChan](https://github.com/SergioChan) +> 2.1 +> 校对:[shanks](http://codebuild.me),2015-11-01 + +> 2.2:翻译+校对:[Lanford](https://github.com/LanfordCai),2016-04-08 [SketchK](https://github.com/SketchK) 2016-05-16 + +> 3.0:翻译+校对:[chenmingjia](https://github.com/chenmingjia),2016-09-12 + 本页包含内容: - [泛型所解决的问题](#the_problem_that_generics_solve) @@ -19,39 +26,38 @@ - [扩展一个泛型类型](#extending_a_generic_type) - [类型约束](#type_constraints) - [关联类型](#associated_types) -- [`Where`语句](#where_clauses) +- [Where 子句](#where_clauses) -*泛型代码*可以让你写出根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型。它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图。 +泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。 -泛型是 Swift 强大特征中的其中一个,许多 Swift 标准库是通过泛型代码构建出来的。事实上,泛型的使用贯穿了整本语言手册,只是你没有发现而已。例如,Swift 的数组和字典类型都是泛型集。你可以创建一个`Int`数组,也可创建一个`String`数组,或者甚至于可以是任何其他 Swift 的类型数据数组。同样的,你也可以创建存储任何指定类型的字典(dictionary),而且这些类型可以是没有限制的。 +泛型是 Swift 最强大的特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上,泛型的使用贯穿了整本语言手册,只是你可能没有发现而已。例如,Swift 的 `Array` 和 `Dictionary` 都是泛型集合。你可以创建一个 `Int` 数组,也可创建一个 `String` 数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典。 ## 泛型所解决的问题 -这里是一个标准的,非泛型函数`swapTwoInts`,用来交换两个Int值: +下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值: ```swift func swapTwoInts(inout a: Int, inout _ b: Int) { - let temporaryA = a - a = b - b = temporaryA + let temporaryA = a + a = b + b = temporaryA } ``` -这个函数使用写入读出(in-out)参数来交换`a`和`b`的值,请参考[输入输出参数](./06_Functions.html#in_out_parameters)。 +这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,请参考[输入输出参数](./06_Functions.html#in_out_parameters)。 -`swapTwoInts(_:_:)`函数可以交换`b`的原始值到`a`,也可以交换a的原始值到`b`,你可以调用这个函数交换两个`Int`变量值: +`swapTwoInts(_:_:)` 函数交换 `b` 的原始值到 `a`,并交换 `a` 的原始值到 `b`。你可以调用这个函数交换两个 `Int` 变量的值: ```swift var someInt = 3 var anotherInt = 107 swapTwoInts(&someInt, &anotherInt) print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") -// 输出 "someInt is now 107, and anotherInt is now 3" +// 打印 “someInt is now 107, and anotherInt is now 3” ``` - -`swapTwoInts(_:_:)`函数是非常有用的,但是它只能交换`Int`值,如果你想要交换两个`String`或者`Double`,就不得不写更多的函数,如 `swapTwoStrings`和`swapTwoDoubles(_:_:)`,如同如下所示: +诚然,`swapTwoInts(_:_:)` 函数挺有用,但是它只能交换 `Int` 值,如果你想要交换两个 `String` 值或者 `Double`值,就不得不写更多的函数,例如 `swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)`,如下所示: ```swift func swapTwoStrings(inout a: String, inout _ b: String) { @@ -67,17 +73,17 @@ func swapTwoDoubles(inout a: Double, inout _ b: Double) { } ``` -你可能注意到 `swapTwoInts`、 `swapTwoStrings`和`swapTwoDoubles(_:_:)`函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是`Int`、`String`和`Double`。 +你可能注意到 `swapTwoInts(_:_:)`、`swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 的函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是 `Int`、`String` 和 `Double`。 -但实际应用中通常需要一个用处更强大并且尽可能的考虑到更多的灵活性单个函数,可以用来交换两个任何类型值,很幸运的是,泛型代码帮你解决了这种问题。(一个这种泛型函数后面已经定义好了。) +在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。) ->注意: -在所有三个函数中,`a`和`b`的类型是一样的。如果`a`和`b`不是相同的类型,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个`String`类型的变量和一个`Double`类型的变量互相交换值。如果一定要做,Swift 将报编译错误。 +> 注意 +在上面三个函数中,`a` 和 `b` 类型相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。 ## 泛型函数 -`泛型函数`可以工作于任何类型,这里是一个上面`swapTwoInts(_:_:)`函数的泛型版本,用于交换两个值: +泛型函数可以适用于任何类型,下面的 `swapTwoValues(_:_:)` 函数是上面三个函数的泛型版本: ```swift func swapTwoValues(inout a: T, inout _ b: T) { @@ -87,81 +93,74 @@ func swapTwoValues(inout a: T, inout _ b: T) { } ``` -`swapTwoValues(_:_:)`函数主体和`swapTwoInts(_:_:)`函数是一样的,它只在第一行稍微有那么一点点不同于`swapTwoInts`,如下所示: +`swapTwoValues(_:_:)` 的函数主体和 `swapTwoInts(_:_:)` 函数是一样的,它们只在第一行有点不同,如下所示: ```swift func swapTwoInts(inout a: Int, inout _ b: Int) func swapTwoValues(inout a: T, inout _ b: T) ``` +这个函数的泛型版本使用了占位类型名(在这里用字母 `T` 来表示)来代替实际类型名(例如 `Int`、`String` 或 `Double`)。占位类型名没有指明 `T` 必须是什么类型,但是它指明了 `a` 和 `b` 必须是同一类型 `T`,无论 `T` 代表什么类型。只有 `swapTwoValues(_:_:)` 函数在调用时,才能根据所传入的实际类型决定 `T` 所代表的类型。 -这个函数的泛型版本使用了占位类型名字(通常此情况下用字母`T`来表示)来代替实际类型名(如`Int`、`String`或`Double`)。占位类型名没有提示`T`必须是什么类型,但是它提示了`a`和`b`必须是同一类型`T`,而不管`T`表示什么类型。只有`swapTwoValues(_:_:)`函数在每次调用时所传入的实际类型才能决定`T`所代表的类型。 +另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(``)。这个尖括号告诉 Swift 那个 `T` 是 `swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。 -另外一个不同之处在于这个泛型函数名后面跟着的占位类型名字(T)是用尖括号括起来的(``)。这个尖括号告诉 Swift 那个`T`是`swapTwoValues(_:_:)`函数所定义的一个类型。因为`T`是一个占位命名类型,Swift 不会去查找命名为T的实际类型。 +`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T` 所代表的类型都会由传入的值的类型推断出来。 -`swapTwoValues(_:_:)`函数除了要求传入的两个任何类型值是同一类型外,也可以作为`swapTwoInts`函数被调用。每次`swapTwoValues`被调用,T所代表的类型值都会传给函数。 - -在下面的两个例子中,`T`分别代表`Int`和`String`: +在下面的两个例子中,`T` 分别代表 `Int` 和 `String`: ```swift var someInt = 3 var anotherInt = 107 swapTwoValues(&someInt, &anotherInt) -// someInt 现在等于 107, anotherInt 现在等于 3 -``` +// someInt is now 107, and anotherInt is now 3 -```swift var someString = "hello" var anotherString = "world" swapTwoValues(&someString, &anotherString) -// someString 现在等于 "world", anotherString 现在等于 "hello" +// someString is now "world", and anotherString is now "hello" ``` - ->注意 -上面定义的函数`swapTwoValues(_:_:)`是受`swap`函数启发而实现的。`swap`函数存在于 Swift 标准库,并可以在其它类中任意使用。如果你在自己代码中需要类似`swapTwoValues(_:_:)`函数的功能,你可以使用已存在的交换函数`swap(_:_:)`函数。 +> 注意 +上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。 ## 类型参数 -在上面的`swapTwoValues`例子中,占位类型`T`是一种类型参数的示例。类型参数指定并命名为一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(如``)。 +在上面的 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是类型参数的一个例子。类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 ``)。 -一旦一个类型参数被指定,那么其可以被使用来定义一个函数的参数类型(如`swapTwoValues(_:_:)`函数中的参数`a`和`b`),或作为一个函数返回类型,或用作函数主体中的注释类型。在这种情况下,被类型参数所代表的占位类型不管函数任何时候被调用,都会被实际类型所替换(在上面`swapTwoValues`例子中,当函数第一次被调用时,`T`被`Int`替换,第二次调用时,被`String`替换。)。 +一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a` 和 `b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T` 被 `Int` 替换,第二次调用时,被 `String` 替换。) -你可支持多个类型参数,命名在尖括号中,用逗号分开。 +你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。 ## 命名类型参数 -在简单的情况下,泛型函数或泛型类型需要指定一个占位类型(如上面的`swapTwoValues`泛型函数,或一个存储单一类型的泛型集,如数组),通常用一单个字母`T`来命名类型参数。不过,你可以使用任何有效的标识符来作为类型参数名。 +在大多数情况下,类型参数具有一个描述性名字,例如 `Dictionary` 中的 `Key` 和 `Value`,以及 `Array` 中的 `Element`,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 `T`、`U`、`V`,正如上面演示的 `swapTwoValues(_:_:)` 函数中的 `T` 一样。 -如果你使用多个参数定义更复杂的泛型函数或泛型类型,那么使用更多的描述类型参数是非常有用的。例如,Swift 字典(Dictionary)类型有两个类型参数,一个是键,另外一个是值。如果你自己写字典,你或许会定义这两个类型参数为`Key`和`Value`,用来记住它们在你的泛型代码中的作用。 - ->注意 -请始终使用大写字母开头的驼峰式命名法(例如`T`和`Key`)来给类型参数命名,以表明它们是类型的占位符,而非类型值。 +> 注意 +请始终使用大写字母开头的驼峰命名法(例如 `T` 和 `MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。 ## 泛型类型 +除了泛型函数,Swift 还允许你定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,类似于 `Array` 和 `Dictionary`。 -通常在泛型函数中,Swift 允许你定义你自己的泛型类型。这些自定义类、结构体和枚举作用于任何类型,如同`Array`和`Dictionary`的用法。 +这部分内容将向你展示如何编写一个名为 `Stack` (栈)的泛型集合类型。栈是一系列值的有序集合,和 `Array` 类似,但它相比 Swift 的 `Array` 类型有更多的操作限制。数组允许在数组的任意位置插入新元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。 -这部分向你展示如何写一个泛型集类型--`Stack`(栈)。一个栈是一系列值域的集合,和`Array`(数组)类似,但其是一个比 Swift 的`Array`类型更多限制的集合。一个数组可以允许其里面任何位置的插入/删除操作,而栈,只允许在集合的末端添加新的项(如同*push*一个新值进栈)。同样的一个栈也只能从末端移除项(如同*pop*一个值出栈)。 +> 注意 +栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。 ->注意 -栈的概念已被`UINavigationController`类使用来模拟试图控制器的导航结构。你通过调用`UINavigationController`的`pushViewController(_:animated:)`方法来为导航栈添加(add)新的试图控制器;而通过`popViewControllerAnimated(_:)`的方法来从导航栈中移除(pop)某个试图控制器。每当你需要一个严格的`后进先出`方式来管理集合,堆栈都是最实用的模型。 - -下图展示了一个栈的压栈(push)/出栈(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. 移除掉一个值后,现在栈又重新只有三个值。 +1. 现在有三个值在栈中。 +2. 第四个值被压入到栈的顶部。 +3. 现在有四个值在栈中,最近入栈的那个值在顶部。 +4. 栈中最顶部的那个值被移除,或称之为出栈。 +5. 移除掉一个值后,现在栈又只有三个值了。 -这里展示了如何写一个非泛型版本的栈,`Int`值型的栈: +下面展示了如何编写一个非泛型版本的栈,以 `Int` 型的栈为例: ```swift struct IntStack { @@ -175,37 +174,35 @@ struct IntStack { } ``` -这个结构体在栈中使用一个`Array`性质的`items`存储值。`Stack`提供两个方法:`push`和`pop`,从栈中压进一个值和移除一个值。这些方法标记为可变的,因为它们需要修改(或*转换*)结构体的`items`数组。 +这个结构体在栈中使用一个名为 `items` 的 `Array` 属性来存储值。`Stack` 提供了两个方法:`push(_:)` 和 `pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。 -上面所展现的`IntStack`类型只能用于`Int`值,不过,其对于定义一个泛型`Stack`类(可以处理*任何*类型值的栈)是非常有用的。 - -这里是一个相同代码的泛型版本: +上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。 +下面是相同代码的泛型版本: ```swift -struct Stack { - var items = [T]() - mutating func push(item: T) { +struct Stack { + var items = [Element]() + mutating func push(item: Element) { items.append(item) } - mutating func pop() -> T { + mutating func pop() -> Element { return items.removeLast() } } ``` +注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(``)。 -注意到`Stack`的泛型版本基本上和非泛型版本相同,但是泛型版本的占位类型参数为T代替了实际`Int`类型。这种类型参数包含在一对尖括号里(``),紧随在结构体名字后面。 +`Element` 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 `Element` 来引用。在这个例子中,`Element` 在如下三个地方被用作占位符: -`T`定义了一个名为“某种类型T”的节点提供给后来用。这种将来类型可以在结构体的定义里任何地方表示为“T”。在这种情况下,`T`在如下三个地方被用作节点: +- 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。 +- 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。 +- 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。 -- 创建一个名为`items`的属性,使用空的T类型值数组对其进行初始化; -- 指定一个包含一个参数名为`item`的`push(_:)`方法,该参数必须是T类型; -- 指定一个`pop`方法的返回值,该返回值将是一个T类型值。 +由于 `Stack` 是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,就像 `Array` 和 `Dictionary` 那样。 -由于`Stack`是泛型类型,所以在 Swift 中其可以用来创建任何有效类型的栈,这种方式如同`Array`和`Dictionary`。 - -你可以通过在尖括号里写出栈中需要存储的数据类型来创建并初始化一个`Stack`实例。比如,要创建一个`strings`的栈,你可以写成`Stack()`: +你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack()`: ```swift var stackOfStrings = Stack() @@ -213,78 +210,80 @@ stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") stackOfStrings.push("cuatro") -// 现在栈已经有4个string了 +// 栈中现在有 4 个字符串 ``` -下图将展示`stackOfStrings`如何`push`这四个值进栈的过程: +下图展示了 `stackOfStrings` 如何将这四个值入栈: ![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png) -从栈中`pop`并移除值"cuatro": +移除并返回栈顶部的值 `"cuatro"`,即将其出栈: ```swift let fromTheTop = stackOfStrings.pop() -// fromTheTop 等于 "cuatro", 现在栈中还有3个string +// fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串 ``` -下图展示了如何从栈中pop一个值的过程: +下图展示了 `stackOfStrings` 如何将顶部的值出栈: ![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png) ## 扩展一个泛型类型 -当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。 +当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。 -下面的例子扩展了泛型`Stack`类型,为其添加了一个名为`topItem`的只读计算属性,它将会返回当前栈顶端的元素而不会将其从栈中移除。 +下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除: ```swift extension Stack { - var topItem: T? { - return items.isEmpty ? nil : items[items.count - 1] + var topItem: Element? { + return items.isEmpty ? nil : items[items.count - 1] } } ``` -`topItem`属性会返回一个`T`类型的可选值。当栈为空的时候,`topItem`将会返回`nil`;当栈不为空的时候,`topItem`会返回`items`数组中的最后一个元素。 +`topItem` 属性会返回一个 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。 -注意这里的扩展并没有定义一个类型参数列表。相反的,`Stack`类型已有的类型参数名称,`T`,被用在扩展中当做`topItem`计算属性的可选类型。 +注意,这个扩展并没有定义一个类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。 -`topItem`计算属性现在可以被用来返回任意`Stack`实例的顶端元素而无需移除它: +计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素且不移除它: ```swift if let topItem = stackOfStrings.topItem { - print("The top item on the stack is \(topItem).") + print("The top item on the stack is \(topItem).") } -// 输出 "The top item on the stack is tres." +// 打印 “The top item on the stack is tres.” ``` -##类型约束 +## 类型约束 -`swapTwoValues(_:_:)`函数和`Stack`类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。 +`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。 -例如,Swift 的`Dictionary`类型对作用于其键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键类型必须是*可哈希*,也就是说,必须有一种方法可以使其被唯一的表示。`Dictionary`之所以需要其键是可哈希是为了以便于其检查其是否已经包含某个特定键的值。如无此需求,`Dictionary`既不会告诉是否插入或者替换了某个特定键的值,也不能查找到已经存储在字典里面的给定键值。 +例如,Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary` 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。 -这个需求强制加上一个类型约束作用于`Dictionary`的键上,当然其键类型必须遵循`Hashable`协议(Swift 标准库中定义的一个特定协议)。所有的 Swift 基本类型(如`String`,`Int`, `Double`和 `Bool`)默认都是可哈希。 +为了实现这个要求,一个类型约束被强制加到 `Dictionary` 的键类型上,要求其键类型必须符合 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。 -当你创建自定义泛型类型时,你可以定义你自己的类型约束,当然,这些约束要支持泛型编程的强力特征中的多数。抽象概念如`可哈希`具有的类型特征是根据它们概念特征来界定的,而不是它们的直接类型特征。 +当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。 + ### 类型约束语法 -你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同): +你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型时的语法与之相同): ```swift func someFunction(someT: T, someU: U) { - // 这里是函数主体 + // 这里是泛型函数的函数体部分 } ``` -上面这个假定函数有两个类型参数。第一个类型参数`T`,有一个需要`T`必须是`SomeClass`子类的类型约束;第二个类型参数`U`,有一个需要`U`必须遵循`SomeProtocol`协议的类型约束。 +上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须符合 `SomeProtocol` 协议的类型约束。 -### 类型约束行为 + +### 类型约束实践 -这里有个名为`findStringIndex`的非泛型函数,该函数功能是去查找包含一给定`String`值的数组。若查找到匹配的字符串,`findStringIndex(_:_:)`函数返回该字符串在数组中的索引值(`Int`),反之则返回`nil`: +这里有个名为 `findStringIndex` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findStringIndex(_:_:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`: ```swift func findStringIndex(array: [String], _ valueToFind: String) -> Int? { @@ -297,20 +296,19 @@ func findStringIndex(array: [String], _ valueToFind: String) -> Int? { } ``` - -`findStringIndex(_:_:)`函数可以作用于查找一字符串数组中的某个字符串: +`findStringIndex(_:_:)` 函数可以用于查找字符串数组中的某个字符串: ```swift let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] if let foundIndex = findStringIndex(strings, "llama") { print("The index of llama is \(foundIndex)") } -// 输出 "The index of llama is 2" +// 打印 “The index of llama is 2” ``` -如果只是针对字符串而言查找在数组中的某个值的索引,用处不是很大,不过,你可以写出相同功能的泛型函数`findIndex`,用某个类型`T`值替换掉提到的字符串。 +如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 `T` 替换 `String` 类型来写出具有相同功能的泛型函数 `findIndex(_:_:)`。 -这里展示如何写一个你或许期望的`findStringIndex`的泛型版本`findIndex`。请注意这个函数仍然返回`Int`,是不是有点迷惑呢,而不是泛型类型?那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数不会编译,原因在例子后面会说明: +下面展示了 `findStringIndex(_:_:)` 函数的泛型版本 `findIndex(_:_:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因会在例子后面说明: ```swift func findIndex(array: [T], _ valueToFind: T) -> Int? { @@ -323,11 +321,11 @@ func findIndex(array: [T], _ valueToFind: T) -> Int? { } ``` -上面所写的函数不会编译。这个问题的位置在等式的检查上,`“if value == valueToFind”`。不是所有的 Swift 中的类型都可以用等式符(==)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么 Swift 没法猜到对于这个类或结构体而言“等于”的意思。正因如此,这部分代码不能可能保证工作于每个可能的类型`T`,当你试图编译这部分代码时估计会出现相应的错误。 +上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。比如说,如果你创建一个自定义的类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型 `T`,当你试图编译这部分代码时会出现相应的错误。 -不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个`Equatable`协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的 Swift 标准类型自动支持`Equatable`协议。 +不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何遵循该协议的类型必须实现等式符(`==`)及不等符(`!=`),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。 -任何`Equatable`类型都可以安全的使用在`findIndex(_:_:)`函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个`Equatable`类型约束作为类型参数定义的一部分: +任何 `Equatable` 类型都可以安全地使用在 `findIndex(_:_:)` 函数中,因为其保证支持等式操作符。为了说明这个事实,当你定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分: ```swift func findIndex(array: [T], _ valueToFind: T) -> Int? { @@ -340,55 +338,55 @@ func findIndex(array: [T], _ valueToFind: T) -> Int? { } ``` +`findIndex(_:_:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T` ”。 -`findIndex`中这个单个类型参数写做:`T: Equatable`,也就意味着“任何T类型都遵循`Equatable`协议”。 - -`findIndex(_:_:)`函数现在则可以成功的编译过,并且作用于任何遵循`Equatable`的类型,如`Double`或`String`: +`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 +// doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中 let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea") -// stringIndex is an optional Int containing a value of 2 +// stringIndex 类型为 Int?,其值为 2 ``` -##关联类型(Associated Types) +## 关联类型 -当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型作为协议的一部分,给定了类型的一个占位名(或别名)。作用于关联类型上实际类型在协议被实现前是不需要指定的。关联类型被指定为`typealias`关键字。 +定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。 -### 关联类型行为 + +### 关联类型实践 -这里是一个`Container`协议的例子,定义了一个`ItemType`关联类型: +下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `ItemType`: ```swift protocol Container { - typealias ItemType + associatedtype ItemType mutating func append(item: ItemType) var count: Int { get } subscript(i: Int) -> ItemType { get } } ``` -`Container`协议定义了三个任何容器必须支持的兼容要求: +`Container` 协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能: -- 必须可以通过`append(_:)`方法添加一个新元素到容器里; -- 必须可以通过使用`count`属性获取容器里元素的数量,并返回一个`Int`值; -- 必须可以通过容器的`Int`索引值下标可以检索到每一个元素。 +- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。 +- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。 +- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。 -这个协议没有指定容器里的元素是如何存储的或何种类型是允许的。这个协议只指定三个任何遵循`Container`类型所必须支持的功能点。一个遵循的类型在满足这三个条件的情况下也可以提供其他额外的功能。 +这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵从 `Container` 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。 -任何遵循`Container`协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的元素可以加进容器里,必须明确可以通过其下标返回元素类型。 +任何遵从 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。 -为了定义这三个条件,`Container`协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。`Container`协议需要指定任何通过`append(_:)`方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。 +为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。 -为了达到此目的,`Container`协议声明了一个`ItemType`的关联类型,写作`typealias ItemType`。这个协议不会定义`ItemType`是什么的别名,这个信息将由任何遵循协议的类型来提供。尽管如此,`ItemType`别名提供了一种识别`Container`中元素类型的方法,并且用于`append(_:)`方法和`subscript`方法的类型定义,以便保证任何`Container`期望的行为能够被执行。 +为了达到这个目的,`Container` 协议声明了一个关联类型 `ItemType`,写作 `associatedtype ItemType`。这个协议无法定义 `ItemType` 是什么类型的别名,这个信息将留给遵从协议的类型来提供。尽管如此,`ItemType` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行。 -这里是一个早前`IntStack`类型的非泛型版本,遵循`Container`协议: +下面是先前的非泛型的 `IntStack` 类型,这一版本采纳并符合了 `Container` 协议: ```swift struct IntStack: Container { - // IntStack的原始实现 + // IntStack 的原始实现部分 var items = [Int]() mutating func push(item: Int) { items.append(item) @@ -396,13 +394,13 @@ struct IntStack: Container { mutating func pop() -> Int { return items.removeLast() } - // 遵循Container协议的实现 + // Container 协议的实现部分 typealias ItemType = Int mutating func append(item: Int) { self.push(item) } var count: Int { - return items.count + return items.count } subscript(i: Int) -> Int { return items[i] @@ -410,118 +408,114 @@ struct IntStack: Container { } ``` +`IntStack` 结构体实现了 `Container` 协议的三个要求,其原有功能也不会和这些要求相冲突。 -`IntStack`类型实现了`Container`协议的所有三个要求,在`IntStack`类型的每个包含部分的功能都满足这些要求。 +此外,`IntStack` 在实现 `Container` 的要求时,指定 `ItemType` 为 `Int` 类型,即 `typealias ItemType = Int`,从而将 `Container` 协议中抽象的 `ItemType` 类型转换为具体的 `Int` 类型。 -此外,`IntStack`指定了`Container`的实现,适用的`ItemType`被用作`Int`类型。对于这个`Container`协议实现而言,定义 `typealias ItemType = Int`,将抽象的`ItemType`类型转换为具体的`Int`类型。 +由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `ItemType` 为 `Int`。因为 `IntStack` 符合 `Container` 协议的所有要求,Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `ItemType` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias ItemType = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `ItemType` 应该是哪种类型。 -感谢Swift类型参考,你不用在`IntStack`定义部分声明一个具体的`Int`的`ItemType`。由于`IntStack`遵循`Container`协议的所有要求,只要通过简单的查找`append(_:)`方法的`item`参数类型和下标返回的类型,Swift就可以推断出合适的`ItemType`来使用。确实,如果上面的代码中你删除了 `typealias ItemType = Int`这一行,一切仍旧可以工作,因为它清楚的知道`ItemType`使用的是何种类型。 - -你也可以生成遵循`Container`协议的泛型`Stack`类型: +你也可以让泛型 `Stack` 结构体遵从 `Container` 协议: ```swift -struct Stack: Container { - // original Stack implementation - var items = [T]() - mutating func push(item: T) { +struct Stack: Container { + // Stack 的原始实现部分 + var items = [Element]() + mutating func push(item: Element) { items.append(item) } - mutating func pop() -> T { + mutating func pop() -> Element { return items.removeLast() } - // conformance to the Container protocol - mutating func append(item: T) { + // Container 协议的实现部分 + mutating func append(item: Element) { self.push(item) } var count: Int { - return items.count + return items.count } - subscript(i: Int) -> T { + subscript(i: Int) -> Element { return items[i] } } ``` -这个时候,占位类型参数`T`被用作`append(_:)`方法的`item`参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的`ItemType`的`T`的合适类型。 +这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `ItemType` 的类型。 + +### 通过扩展一个存在的类型来指定关联类型 -### 扩展一个存在的类型为一指定关联类型 +[通过扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。 -在[在扩展中添加协议成员](./21_Protocols.html#adding_protocol_conformance_with_an_extension)中有描述扩展一个存在的类型添加遵循一个协议。这个类型包含一个关联类型的协议。 - -Swift的`Array`已经提供`append(_:)`方法,一个`count`属性和通过下标来查找一个自己的元素。这三个功能都达到`Container`协议的要求。也就意味着你可以扩展`Array`去遵循`Container`协议,只要通过简单声明`Array`适用于该协议而已。如何实践这样一个空扩展,在[通过扩展补充协议声明](./21_Protocols.html#declaring_protocol_adoption_with_an_extension)中有描述这样一个实现一个空扩展的行为: +Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引值的下标用以检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 采纳该协议就可以扩展 `Array`,使其遵从 `Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展采纳协议](./22_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述: ```swift extension Array: Container {} ``` -如同上面的泛型`Stack`类型一样,`Array`的`append(_:)`方法和下标保证`Swift`可以推断出`ItemType`所使用的适用的类型。定义了这个扩展后,你可以将任何`Array`当作`Container`来使用。 +如同上面的泛型 `Stack` 结构体一样,`Array` 的 `append(_:)` 方法和下标确保了 Swift 可以推断出 `ItemType` 的类型。定义了这个扩展后,你可以将任意 `Array` 当作 `Container` 来使用。 -## Where 语句 +## Where 子句 -[类型约束](#type_constraints)能够确保类型符合泛型函数或类的定义约束。 +[类型约束](#type_constraints)让你能够为泛型函数或泛型类型的类型参数定义一些强制要求。 -对关联类型定义约束是非常有用的。你可以在参数列表中通过*where*语句定义参数的约束。一个`where`语句能够使一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可以写一个`where`语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。 +为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。 -下面的例子定义了一个名为`allItemsMatch`的泛型函数,用来检查两个`Container`实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回一个为`true`的`Boolean`值,反之则为`false`。 +下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`。 -被检查的两个`Container`可以不是相同类型的容器(虽然它们可以是),但它们确实拥有相同类型的元素。这个需求通过一个类型约束和`where`语句结合来表示: +被检查的两个 `Container` 可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 `where` 子句来表示: ```swift -func allItemsMatch< - C1: Container, C2: Container - where C1.ItemType == C2.ItemType, C1.ItemType: Equatable> - (someContainer: C1, anotherContainer: C2) -> Bool { - - // 检查两个Container的元素个数是否相同 +func allItemsMatch + (_ someContainer: C1, _ anotherContainer: C2) -> Bool + where C1.ItemType == C2.ItemType, C1.ItemType: Equatable { + + // 检查两个容器含有相同数量的元素 if someContainer.count != anotherContainer.count { return false } - - // 检查两个Container相应位置的元素彼此是否相等 + + // 检查每一对元素是否相等 for i in 0..() @@ -536,16 +530,7 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) { } else { print("Not all items match.") } -// 输出 "All items match." +// 打印 “All items match.” ``` - 上面的例子创建一个`Stack`单例来存储`String`,然后压了三个字符串进栈。这个例子也创建了一个`Array`单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组是不同的类型,但它们都遵循`Container`协议,而且它们都包含同样的类型值。因此你可以调用`allItemsMatch(_:_:)`函数,用这两个容器作为它的参数。在上面的例子中,`allItemsMatch(_:_:)`函数正确的显示了这两个容器的所有元素都是相互匹配的。 - - [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 +上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。 diff --git a/source/chapter2/24_Access_Control.md b/source/chapter2/24_Access_Control.md index 0e13cec3..243599ca 100644 --- a/source/chapter2/24_Access_Control.md +++ b/source/chapter2/24_Access_Control.md @@ -8,96 +8,111 @@ > 2.0 > 翻译+校对:[mmoaay](https://github.com/mmoaay) +> 2.1 +> 翻译:[Prayer](https://github.com/futantan) +> 校对:[shanks](http://codebuild.me),2015-11-01 +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-17 + 本页内容包括: - [模块和源文件](#modules_and_source_files) - [访问级别](#access_levels) - - [访问级别的使用原则](#guiding_principle_of_access_levels) + - [访问级别基本原则](#guiding_principle_of_access_levels) - [默认访问级别](#default_access_levels) - - [单目标应用程序的访问级别](#access_levels_for_single-target_apps) - - [Framework的访问级别](#access_levels_for_frameworks) - - [单元测试目标的访问级别](#access_levels_for_unit_test_targets) + - [单 target 应用程序的访问级别](#access_levels_for_single-target_apps) + - [框架的访问级别](#access_levels_for_frameworks) + - [单元测试 target 的访问级别](#access_levels_for_unit_test_targets) - [访问控制语法](#access_control_syntax) - [自定义类型](#custom_types) - [元组类型](#tuple_types) - [函数类型](#function_types) - [枚举类型](#enumeration_types) - - [原始值和关联值](#raw_values_and_associated_values) - [嵌套类型](#nested_types) - [子类](#subclassing) - [常量、变量、属性、下标](#constants_variables_properties_subscripts) - [Getter和Setter](#getters_and_setters) -- [初始化](#initializers) - - [默认初始化方法](#default_initializers) - - [结构体的默认成员初始化方法](#default_memberwise_initializers_for_structure_types) +- [构造器](#initializers) + - [默认构造器](#default_initializers) + - [结构体默认的成员逐一构造器](#default_memberwise_initializers_for_structure_types) - [协议](#protocols) - [协议继承](#protocol_inheritance) - [协议一致性](#protocol_conformance) - [扩展](#extensions) - - [协议的扩展](#adding_protocol_conformance_with_an_extension) + - [通过扩展添加协议一致性](#adding_protocol_conformance_with_an_extension) - [泛型](#generics) - [类型别名](#type_aliases) -*访问控制*可以限定其他源文件或模块中代码对你代码的访问级别。这个特性可以让我们隐藏功能实现的一些细节,并且可以明确的申明我们提供给其他人的接口中哪些部分是他们可以访问和使用的。 +访问控制可以限定其他源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。 -你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。 +你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。 -在提供了不同访问级别的同时,Swift还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单目标应用程序,我们完全可以不用申明代码的显式访问级别。 +Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单一 target 的应用程序,我们完全可以不用显式申明代码的访问级别。 -> 注意:简单起见,代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会以“实体”代替。 +> 注意 +为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。 ## 模块和源文件 Swift 中的访问控制模型基于模块和源文件这两个概念。 -*模块*指的是以独立单元构建和发布的`Framework`或`Application`。在Swift 中的一个模块可以使用`import`关键字引入另外一个模块。 +模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 `import` 关键字导入另外一个模块。 -在 Swift 中,Xcode的每个构建目标(比如`Framework`或`app bundle`)都被当作模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的`Framework`,这个`Framework`在 Swift 中就被称为模块。当它被引入到某个 app 工程或者另外一个`Framework`时,它里面的一切(属性、函数等)仍然属于这个独立的模块。 +在 Swift 中,Xcode 的每个 target(例如框架或应用程序)都被当作独立的模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。 -*源文件*指的是 Swift 中的`Swift File`,就是编写 Swift 源代码的文件,它通常属于一个模块。尽管一般我们将不同的`类` 分别定义在不同的源文件中,但是同一个源文件可以包含多个`类`和`函数` 的定义。 +源文件就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。 ## 访问级别 Swift 为代码中的实体提供了三种不同的访问级别。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。 -- `public`:可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。通常情况下,`Framework` 中的某个接口是可以被任何人使用时,你可以将其设置为`public`级别。 -- `internal`:可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。通常情况下,某个接口或`Framework`作为内部结构使用时,你可以将其设置为`internal`级别。 -- `private`:只能在当前源文件中使用的实体,称为私有实体。使用`private`级别,可以用作隐藏某些功能的实现细节。 +- `public`:可以访问同一模块源文件中的任何实体,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,框架中的某个接口可以被任何人使用时,你可以将其设置为 `public` 级别。 +- `internal`:可以访问同一模块源文件中的任何实体,但是不能从模块外访问该模块源文件中的实体。通常情况下,某个接口只在应用程序或框架内部使用时,你可以将其设置为 `internal` 级别。 +- `private`:限制实体只能在所在的源文件内部使用。使用 `private` 级别可以隐藏某些功能的实现细节。 -`public`为最高级访问级别,`private`为最低级访问级别。 +`public` 为最高(限制最少)访问级别,`private` 为最低(限制最多)访问级别。 -> 注意:Swift中的`private`访问和其他语言中的`private`访问不太一样,它的范围限于整个源文件,而不是声明。这就意味着,一个`类` 可以访问定义该`类` 的源文件中定义的所有`private`实体,但是如果一个`类` 的扩展是定义在独立的源文件中,那么就不能访问这个`类` 的`private`成员。 +> 注意 +Swift 中的 `private` 访问级别不同于其他语言,它的范围限于源文件,而不是声明范围内。这就意味着,一个类型可以访问其所在源文件中的所有 `private` 实体,但是如果它的扩展定义在其他源文件中,那么它的扩展就不能访问它在这个源文件中定义的 `private` 实体。 -### 访问级别的使用原则 -Swift 中的访问级别遵循一个使用原则:访问级别统一性。 -比如说: +### 访问级别基本原则 -- 一个`public`访问级别的变量,不能将它的类型定义为`internal`和`private`。因为变量可以被任何人访问,但是定义它的类型不可以,所以这样就会出现错误。 -- 函数的访问级别不能高于它的参数、返回类型的访问级别。因为如果函数定义为`public`而参数或者返回类型定义为`internal`或`private`,就会出现函数可以被任何人访问,但是它的参数和返回类型确不可以,同样会出现错误。 +Swift 中的访问级别遵循一个基本原则:不可以在某个实体中定义访问级别更高的实体。 + +例如: + +- 一个 `public` 访问级别的变量,其类型的访问级别不能是 `internal` 或 `private`。因为无法保证变量的类型在使用变量的地方也具有访问权限。 +- 函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为如果函数定义为 `public` 而参数类型或者返回类型定义为 `internal` 或 `private`,就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以。 ### 默认访问级别 -如果你不为代码中的所有实体定义显式访问级别,那么它们默认为`internal`级别。在大多数情况下,我们不需要设置实体的显式访问级别。因为我们一般都是在开发一个`app bundle`。 + +如果你不为代码中的实体显式指定访问级别,那么它们默认为 `internal` 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。 -### 单目标应用程序的访问级别 -当你编写一个单目标应用程序时,该应用的所有功能都是为该应用服务,不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别`internal`即可。但是如果你愿意,你也可以使用`private`级别,用于隐藏一些功能的实现细节。 +### 单 target 应用程序的访问级别 + +当你编写一个单 target 应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 `internal` 即可。但是,你也可以使用 `private` 级别,用于隐藏一些功能的实现细节。 -### Framework的访问级别 -当你开发`Framework`时,就需要把一些对外的接口定义为`public`级别,以便其他人导入该`Framework`后可以正常使用其功能。这些被你定义为`public`的接口,就是这个`Framework`的API。 +### 框架的访问级别 -> 注意:`Framework`的内部实现细节依然可以使用默认的`internal`级别,或者也可以定义为`private`级别。只有当你想把它作为 API 的一部分的时候,才将其定义为`public`级别。 +当你开发框架时,就需要把一些对外的接口定义为 `public` 级别,以便使用者导入该框架后可以正常使用其功能。这些被你定义为 `public` 的接口,就是这个框架的 API。 + +> 注意 +框架依然会使用默认的 `internal` 级别,也可以指定为 `private` 级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定 `public` 级别。 -### 单元测试目标的访问级别 +### 单元测试 target 的访问级别 -当你的app有单元测试目标时,为了方便测试,测试模块需要能访问到你app中的代码。默认情况下只有`public`级别的实体才可以被其他模块访问。然而,如果在引入一个生产模块时使用`@testable`注解,然后用带测试的方式编译这个生产模块,单元测试目标就可以访问所有`internal`级别的实体。 +当你的应用程序包含单元测试 target 时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有 `public` 级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用 `@testable` 特性,然后在允许测试的编译设置(`Build Options -> Enable Testability`)下编译这个应用程序模块,单元测试 target 就可以访问应用程序模块中所有 `internal` 级别的实体。 ## 访问控制语法 -通过修饰符`public`、`internal`、`private`来声明实体的访问级别: + +通过修饰符 `public`、`internal`、`private` 来声明实体的访问级别: ```swift public class SomePublicClass {} @@ -109,72 +124,78 @@ internal let someInternalConstant = 0 private func somePrivateFunction() {} ``` -除非有特殊的说明,否则实体都使用默认的访问级别`internal`,可以查阅**[默认访问级别](#default_access_levels)**这一节。这意味着在不使用修饰符声明显式访问级别的情况下,`SomeInternalClass`和`someInternalConstant`仍然拥有隐式的访问级别`internal`: +除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅[默认访问级别](#default_access_levels)这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass` 和 `someInternalConstant` 仍然拥有隐式的访问级别 `internal`: ```swift -class SomeInternalClass {} // 隐式访问级别 internal -var someInternalConstant = 0 // 隐式访问级别 internal +class SomeInternalClass {} // 隐式访问级别 internal +var someInternalConstant = 0 // 隐式访问级别 internal ``` ## 自定义类型 -如果想为一个自定义类型申明显式访问级别,那么要明确一点。那就是你要确保新类型的访问级别和它实际的作用域相匹配。比如说,如果你定义了一个`private`类,那这个类就只能在定义它的源文件中当作属性类型、函数参数或者返回类型使用。 -类的访问级别也可以影响到类成员(属性、函数、初始化方法等)的默认访问级别。如果你将类申明为`private`类,那么该类的所有成员的默认访问级别也会成为`private`。如果你将类申明为`public`或者`internal`类(或者不明确的申明访问级别,而使用默认的`internal`访问级别),那么该类的所有成员的访问级别是`internal`。 +如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 `private` 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。 -> 注意:上面提到,一个`public`类的所有成员的访问级别默认为`internal`级别,而不是`public`级别。如果你想将某个成员申明为`public`级别,那么你必须使用修饰符明确的声明该成员。这样做的好处是,在你定义公共接口API的时候,可以明确的选择哪些属性或方法是需要公开的,哪些是内部使用的,可以避免将内部使用的属性方法公开成公共API的错误。 +一个类型的访问级别也会影响到类型成员(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 级别,那么该类型的所有成员的默认访问级别也会变成 `private`。如果你将类型指定为 `public` 或者 `internal` 级别(或者不明确指定访问级别,而使用默认的 `internal` 访问级别),那么该类型的所有成员的默认访问级别将是 `internal`。 + +> 注意 +上面提到,一个 `public` 类型的所有成员的访问级别默认为 `internal` 级别,而不是 `public` 级别。如果你想将某个成员指定为 `public` 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。 ```swift public class SomePublicClass { // 显式的 public 类 - public var somePublicProperty = 0 // 显式的 public 类成员 - var someInternalProperty = 0 // 隐式的 internal 类成员 - private func somePrivateMethod() {} // 显式的 private 类成员 + public var somePublicProperty = 0 // 显式的 public 类成员 + var someInternalProperty = 0 // 隐式的 internal 类成员 + private func somePrivateMethod() {} // 显式的 private 类成员 } class SomeInternalClass { // 隐式的 internal 类 - var someInternalProperty = 0 // 隐式的 internal 类成员 - private func somePrivateMethod() {} // 显式的 private 类成员 + var someInternalProperty = 0 // 隐式的 internal 类成员 + private func somePrivateMethod() {} // 显式的 private 类成员 } private class SomePrivateClass { // 显式的 private 类 - var somePrivateProperty = 0 // 隐式的 private 类成员 - func somePrivateMethod() {} // 隐式的 private 类成员 + var somePrivateProperty = 0 // 隐式的 private 类成员 + func somePrivateMethod() {} // 隐式的 private 类成员 } ``` ### 元组类型 -元组的访问级别使用是所有类型的访问级别使用中最为严谨的。比如说,如果你构建一个包含两种不同类型元素的元组,其中一个元素类型的访问级别为`internal`,另一个为`private`级别,那么这个元组的访问级别为`private`。也就是说元组的访问级别与元组中访问级别最低的类型一致。 -> 注意:元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推导出的,而不是明确的申明。 +元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal` 级别,另一个类型为 `private` 级别,那么这个元组的访问级别为 `private`。 + +> 注意 +元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。 ### 函数类型 -函数的访问级别需要根据该函数的参数类型和返回类型的访问级别得出。如果根据参数类型和返回类型得出的函数访问级别不符合默认上下文,那么就需要明确地申明该函数的访问级别。 -下面的例子定义了一个名为`someFunction`全局函数,并且没有明确地申明其访问级别。也许你会认为该函数应该拥有默认的访问级别`internal`,但事实并非如此。事实上,如果按下面这种写法,代码是无法编译通过的: +函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。 + +下面的例子定义了一个名为 `someFunction` 的全局函数,并且没有明确地指定其访问级别。也许你会认为该函数应该拥有默认的访问级别 `internal`,但事实并非如此。事实上,如果按下面这种写法,代码将无法通过编译: ```swift func someFunction() -> (SomeInternalClass, SomePrivateClass) { - // function implementation goes here + // 此处是函数实现部分 } ``` -我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅**[自定义类型](#custom_types)**)。其中一个类的访问级别是`internal`,另一个的访问级别是`private`,所以根据元组访问级别的原则,该元组的访问级别是`private`(元组的访问级别与元组中访问级别最低的类型一致)。 +我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅[自定义类型](#custom_types))。其中一个类的访问级别是 `internal`,另一个的访问级别是 `private`,所以根据元组访问级别的原则,该元组的访问级别是 `private`(元组的访问级别与元组中访问级别最低的类型一致)。 -因为该函数返回类型的访问级别是`private`,所以你必须使用`private`修饰符,明确的声明该函数: +因为该函数返回类型的访问级别是 `private`,所以你必须使用 `private` 修饰符,明确指定该函数的访问级别: ```swift private func someFunction() -> (SomeInternalClass, SomePrivateClass) { - // function implementation goes here + // 此处是函数实现部分 } ``` -将该函数申明为`public`或`internal`,或者使用默认的访问级别`internal`都是错误的,因为如果把该函数当做`public`或`internal`级别来使用的话,是无法得到`private`级别的返回值的。 +将该函数指定为 `public` 或 `internal`,或者使用默认的访问级别 `internal` 都是错误的,因为如果把该函数当做 `public` 或 `internal` 级别来使用的话,可能会无法访问 `private` 级别的返回值。 ### 枚举类型 -枚举中成员的访问级别继承自该枚举,你不能为枚举中的成员单独申明不同的访问级别。 -比如下面的例子,枚举`CompassPoint`被明确的申明为`public`级别,那么它的成员`North`,`South`,`East`,`West`的访问级别同样也是`public`: +枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。 + +比如下面的例子,枚举 `CompassPoint` 被明确指定为 `public` 级别,那么它的成员 `North`、`South`、`East`、`West` 的访问级别同样也是 `public`: ```swift public enum CompassPoint { @@ -186,20 +207,23 @@ public enum CompassPoint { ``` -### 原始值和关联值 -枚举定义中的任何原始值或关联值的类型都必须有一个访问级别,这个级别至少要不低于枚举的访问级别。比如说,你不能在一个`internal`访问级别的枚举中定义`private`级别的原始值类型。 +#### 原始值和关联值 + +枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 `internal` 访问级别的枚举中定义 `private` 级别的原始值类型。 ### 嵌套类型 -如果在`private`级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有`private`访问级别。如果在`public`或者`internal`级别的类型中定义嵌套类型,那么该嵌套类型自动拥有`internal`访问级别。如果想让嵌套类型拥有`public`访问级别,那么需要明确地申明该嵌套类型的访问级别。 + +如果在 `private` 级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有 `private` 访问级别。如果在 `public` 或者 `internal` 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 `internal` 访问级别。如果想让嵌套类型拥有 `public` 访问级别,那么需要明确指定该嵌套类型的访问级别。 ## 子类 -子类的访问级别不得高于父类的访问级别。比如说,父类的访问级别是`internal`,子类的访问级别就不能申明为`public`。 -此外,在满足子类不高于父类访问级别以及遵循各访问级别作用域(即模块或源文件)的前提下,你可以重写任意类成员(方法、属性、初始化方法、下标索引等)。 +子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 `internal`,子类的访问级别就不能是 `public`。 -如果我们无法直接访问某个类中的属性或函数等,那么可以继承该类,从而可以更容易的访问到该类的类成员。下面的例子中,类`A`的访问级别是`public`,它包含一个函数`someMethod`,访问级别为`private`。类`B`继承类`A`,并且访问级别申明为`internal`,但是在类`B`中重写了类`A`中访问级别为`private`的方法`someMethod`,并重新申明为`internal`级别。通过这种方式,我们就可以访问到某类中`private`级别的类成员,并且可以重新申明其访问级别,以便其他人使用: +此外,你可以在符合当前访问级别的条件下重写任意类成员(方法、属性、构造器、下标等)。 + +可以通过重写为继承来的类成员提供更高的访问级别。下面的例子中,类 `A` 的访问级别是 `public`,它包含一个方法 `someMethod()`,访问级别为 `private`。类 `B` 继承自类 `A`,访问级别为 `internal`,但是在类 `B` 中重写了类 `A` 中访问级别为 `private` 的方法 `someMethod()`,并重新指定为 `internal` 级别。通过这种方式,我们就可以将某类中 `private` 级别的类成员重新指定为更高的访问级别,以便其他人使用: ```swift public class A { @@ -211,7 +235,7 @@ internal class B: A { } ``` -只要满足子类不高于父类访问级别以及遵循各访问级别作用域的前提下(即`private`的作用域在同一个源文件中,`internal`的作用域在同一个模块下),我们甚至可以在子类中,用子类成员访问父类成员,哪怕父类成员的访问级别比子类成员的要低: +我们甚至可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(也就是说,在同一源文件中访问父类 `private` 级别的成员,在同一模块内访问父类 `internal` 级别的成员): ```swift public class A { @@ -219,51 +243,53 @@ public class A { } internal class B: A { - override internal func someMethod() { - super.someMethod() - } + override internal func someMethod() { + super.someMethod() + } } ``` -因为父类`A`和子类`B`定义在同一个源文件中,所以在类`B`中可以在重写的`someMethod`方法中调用`super.someMethod()`。 +因为父类 `A` 和子类 `B` 定义在同一个源文件中,所以在子类 `B` 可以在重写的 `someMethod()` 方法中调用 `super.someMethod()`。 ## 常量、变量、属性、下标 -常量、变量、属性不能拥有比它们的类型更高的访问级别。比如说,你定义一个`public`级别的属性,但是它的类型是`private`级别的,这是编译器所不允许的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。 -如果常量、变量、属性、下标索引的定义类型是`private`级别的,那么它们必须要明确的申明访问级别为`private`: +常量、变量、属性不能拥有比它们的类型更高的访问级别。例如,你不能定义一个 `public` 级别的属性,但是它的类型却是 `private` 级别的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。 + +如果常量、变量、属性、下标的类型是 `private` 级别的,那么它们必须明确指定访问级别为 `private`: ```swift private var privateInstance = SomePrivateClass() ``` -### Getter和Setter -常量、变量、属性、下标索引的`Getters`和`Setters`的访问级别继承自它们所属成员的访问级别。 +### Getter 和 Setter -`Setter`的访问级别可以低于对应的`Getter`的访问级别,这样就可以控制变量、属性或下标索引的读写权限。在`var`或`subscript`定义作用域之前,你可以通过`private(set)`或`internal(set)`先为它们的写权限申明一个较低的访问级别。 +常量、变量、属性、下标的 `Getters` 和 `Setters` 的访问级别和它们所属类型的访问级别相同。 -> 注意:这个规定适用于用作存储的属性或用作计算的属性。即使你不明确地申明存储属性的`Getter`、`Setter`,Swift也会隐式的为其创建`Getter`和`Setter`,用于对该属性进行读取操作。使用`private(set)`和`internal(set)`可以改变Swift隐式创建的`Setter`的访问级别。这对计算属性也同样适用。 +`Setter` 的访问级别可以低于对应的 `Getter` 的访问级别,这样就可以控制变量、属性或下标的读写权限。在 `var` 或 `subscript` 关键字之前,你可以通过 `private(set)` 或 `internal(set)` 为它们的写入权限指定更低的访问级别。 -下面的例子中定义了一个名为`TrackedString`的结构体,它记录了`value`属性被修改的次数: +> 注意 +这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 `Getter` 和 `Setter`,Swift 也会隐式地为其创建 `Getter` 和 `Setter`,用于访问该属性的后备存储。使用 `private(set)` 和 `internal(set)` 可以改变 `Setter` 的访问级别,这对计算型属性也同样适用。 + +下面的例子中定义了一个名为 `TrackedString` 的结构体,它记录了 `value` 属性被修改的次数: ```swift struct TrackedString { - private(set) var numberOfEdits = 0 - var value: String = "" { - didSet { - numberOfEdits++ - } - } + private(set) var numberOfEdits = 0 + var value: String = "" { + didSet { + numberOfEdits += 1 + } + } } ``` +`TrackedString` 结构体定义了一个用于存储 `String` 值的属性 `value`,并将初始值设为 `""`(一个空字符串)。该结构体还定义了另一个用于存储 `Int` 值的属性 `numberOfEdits`,它用于记录属性 `value` 被修改的次数。这个功能通过属性 `value` 的 `didSet` 观察器实现,每当给 `value` 赋新值时就会调用 `didSet` 方法,然后将 `numberOfEdits` 的值加一。 -`TrackedString`结构体定义了一个用于存储`String`类型的属性`value`,并将初始化值设为`""`(即一个空字符串)。该结构体同时也定义了另一个用于存储`Int`类型的属性名`numberOfEdits`,它用于记录属性`value`被修改的次数。这个功能的实现通过属性`value`的`didSet`方法实现,每当给`value`赋新值时就会调用`didSet`方法,然后将`numberOfEdits`的值加一。 +结构体 `TrackedString` 和它的属性 `value` 均没有显式指定访问级别,所以它们都拥有默认的访问级别 `internal`。但是该结构体的 `numberOfEdits` 属性使用了 `private(set)` 修饰符,这意味着 `numberOfEdits` 属性只能在定义该结构体的源文件中赋值。`numberOfEdits` 属性的 `Getter` 依然是默认的访问级别 `internal`,但是 `Setter` 的访问级别是 `private`,这表示该属性只有在当前的源文件中是可读写的,而在当前源文件所属的模块中只是一个可读的属性。 -结构体`TrackedString`和它的属性`value`均没有申明显式访问级别,所以它们都拥有默认的访问级别`internal`。但是该结构体的`numberOfEdits`属性使用`private(set)`修饰符进行申明,这意味着`numberOfEdits`属性只能在定义该结构体的源文件中赋值。`numberOfEdits`属性的`Getter`依然是默认的访问级别`internal`,但是`Setter`的访问级别是`private`,这表示该属性只有在当前的源文件中是可读写的,而在当前源文件所属的模块中它只是一个可读的属性。 - -如果你实例化`TrackedString`结构体,并且多次对`value`属性的值进行修改,你就会看到`numberOfEdits`的值会随着修改次数进行变化: +如果你实例化 `TrackedString` 结构体,并多次对 `value` 属性的值进行修改,你就会看到 `numberOfEdits` 的值会随着修改次数而变化: ```swift var stringToEdit = TrackedString() @@ -271,19 +297,19 @@ stringToEdit.value = "This string will be tracked." stringToEdit.value += " This edit will increment numberOfEdits." stringToEdit.value += " So will this one." print("The number of edits is \(stringToEdit.numberOfEdits)") -// prints "The number of edits is 3" +// 打印 “The number of edits is 3” ``` -虽然你可以在其他的源文件中实例化该结构体并且获取到`numberOfEdits`属性的值,但是你不能对其进行赋值。这样就能很好的告诉使用者,你只管使用,而不需要知道其实现细节。 +虽然你可以在其他的源文件中实例化该结构体并且获取到 `numberOfEdits` 属性的值,但是你不能对其进行赋值。这一限制保护了该记录功能的实现细节,同时还提供了方便的访问方式。 -如果有必要你可以为`Getter`和`Setter`方法设定显式访问级别。下面的例子就是明确申明了`public`访问级别的`TrackedString`结构体。结构体的成员(包括`numberOfEdits`属性)拥有默认的访问级别`internal`。你可以结合`public`和`private(set)`修饰符把结构体中的`numberOfEdits`属性`Getter`方法的访问级别设置为`public`,而`Setter`方法的访问级别设置为`private`: +你可以在必要时为 `Getter` 和 `Setter` 显式指定访问级别。下面的例子将 `TrackedString` 结构体明确指定为了 `public` 访问级别。结构体的成员(包括 `numberOfEdits` 属性)拥有默认的访问级别 `internal`。你可以结合 `public` 和 `private(set)` 修饰符把结构体中的 `numberOfEdits` 属性的 `Getter` 的访问级别设置为 `public`,而 `Setter` 的访问级别设置为 `private`: ```swift public struct TrackedString { public private(set) var numberOfEdits = 0 public var value: String = "" { didSet { - numberOfEdits++ + numberOfEdits += 1 } } public init() {} @@ -291,61 +317,74 @@ public struct TrackedString { ``` -## 初始化 -我们可以给自定义的初始化方法申明访问级别,但是要不高于它所属类的访问级别。但[必要构造器](./14_Initialization.html#required_initializers)例外,它的访问级别必须和所属类的访问级别相同。 +## 构造器 -如同函数或方法参数,初始化方法参数的访问级别也不能低于初始化方法的访问级别。 +自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是[必要构造器](./14_Initialization.html#required_initializers),它的访问级别必须和所属类型的访问级别相同。 + +如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别。 -### 默认初始化方法 -Swift为结构体、类都提供了一个默认的无参初始化方法,用于给它们的所有属性提供赋值操作,但不会给出具体值。默认初始化方法可以参阅[默认构造器](./14_Initialization.html#default_initializers)。默认初始化方法的访问级别与所属类型的访问级别相同。 +### 默认构造器 -> 注意:如果一个类型被申明为`public`级别,那么默认的初始化方法的访问级别为`internal`。如果你想让无参的初始化方法在其他模块中可以被使用,那么你必须提供一个具有`public`访问级别的无参初始化方法。 +如[默认构造器](./14_Initialization.html#default_initializers)所述,Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。 + +默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 `public`。如果一个类型被指定为 `public` 级别,那么默认构造器的访问级别将为 `internal`。如果你希望一个 `public` 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 `public` 访问级别的无参数构造器。 -### 结构体的默认成员初始化方法 -如果结构体中的任一存储属性的访问级别为`private`,那么它的默认成员初始化方法访问级别就是`private`。尽管如此,结构体的初始化方法的访问级别依然是`internal`。 +### 结构体默认的成员逐一构造器 -如果你想在其他模块中使用该结构体的默认成员初始化方法,那么你需要提供一个访问级别为`public`的默认成员初始化方法。 +如果结构体中任意存储型属性的访问级别为 `private`,那么该结构体默认的成员逐一构造器的访问级别就是 `private`。否则,这种构造器的访问级别依然是 `internal`。 + +如同前面提到的默认构造器,如果你希望一个 `public` 级别的结构体也能在其他模块中使用其默认的成员逐一构造器,你依然只能自己提供一个 `public` 访问级别的成员逐一构造器。 ## 协议 -如果想为一个协议明确的申明访问级别,那么需要注意一点,就是你要确保该协议只在你申明的访问级别作用域中使用。 -协议中的每一个必须要实现的函数都具有和该协议相同的访问级别。这样才能确保该协议的使用者可以实现它所提供的函数。 +如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被采纳。 -> 注意:如果你定义了一个`public`访问级别的协议,那么实现该协议提供的必要函数也会是`public`的访问级别。这一点不同于其他类型,比如,`public`访问级别的其他类型,他们成员的访问级别为`internal`。 +协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意采纳者都将可用。 + +> 注意 +如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,当类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`。 ### 协议继承 -如果定义了一个新的协议,并且该协议继承了一个已知的协议,那么新协议拥有的访问级别最高也只和被继承协议的访问级别相同。比如说,你不能定义一个`public`的协议而去继承一个`internal`的协议。 + +如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。 ### 协议一致性 -类可以采用比自身访问级别低的协议。比如说,你可以定义一个`public`级别的类,可以让它在其他模块中使用,同时它也可以采用一个`internal`级别的协议,并且只能在定义了该协议的模块中使用。 -采用了协议的类的访问级别取它本身和所采用协议中最低的访问级别。也就是说如果一个类是`public`级别,采用的协议是`internal`级别,那么采用了这个协议后,该类的访问级别也是`internal`。 +一个类型可以采纳比自身访问级别低的协议。例如,你可以定义一个 `public` 级别的类型,它可以在其他模块中使用,同时它也可以采纳一个 `internal` 级别的协议,但是只能在该协议所在的模块中作为符合该协议的类型使用。 -如果你采用了协议,那么实现了协议所必须的方法后,该方法的访问级别遵循协议的访问级别。比如说,一个`public`级别的类,采用了`internal`级别的协议,那么该类实现协议的方法至少也得是`internal`。 +采纳了协议的类型的访问级别取它本身和所采纳协议两者间最低的访问级别。也就是说如果一个类型是 `public` 级别,采纳的协议是 `internal` 级别,那么采纳了这个协议后,该类型作为符合协议的类型时,其访问级别也是 `internal`。 -> 注意:Swift和Objective-C一样,协议的一致性保证了一个类不可能在同一个程序中用不同的方法采用同一个协议。 +如果你采纳了协议,那么实现了协议的所有要求后,你必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 `public` 级别的类型,采纳了 `internal` 级别的协议,那么协议的实现至少也得是 `internal` 级别。 + +> 注意 +Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。 ## 扩展 -你可以在条件允许的情况下对类、结构体、枚举进行扩展。扩展成员应该具有和原始类成员一致的访问级别。比如你扩展了一个公共类型,那么你新加的成员应该具有和原始成员一样的默认的`internal`访问级别。 -或者,你可以明确申明扩展的访问级别(比如使用`private extension`)给该扩展内所有成员申明一个新的默认访问级别。这个新的默认访问级别仍然可以被单独成员所申明的访问级别所覆盖。 +你可以在访问级别允许的情况下对类、结构体、枚举进行扩展。扩展成员具有和原始类型成员一致的访问级别。例如,你扩展了一个 `public` 或者 `internal` 类型,扩展中的成员具有默认的 `internal` 访问级别,和原始类型中的成员一致 。如果你扩展了一个 `private` 类型,扩展成员则拥有默认的 `private` 访问级别。 + +或者,你可以明确指定扩展的访问级别(例如,`private extension`),从而给该扩展中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。 -### 协议的扩展 -如果一个扩展采用了某个协议,那么你就不能对该扩展使用访问级别修饰符来申明了。该扩展中实现协议的方法都会遵循该协议的访问级别。 +### 通过扩展添加协议一致性 + +如果你通过扩展来采纳协议,那么你就不能显式指定该扩展的访问级别了。协议拥有相应的访问级别,并会为该扩展中所有协议要求的实现提供默认的访问级别。 ## 泛型 -泛型类型或泛型函数的访问级别取泛型类型、函数本身、泛型类型参数三者中的最低访问级别。 + +泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。 ## 类型别名 -任何你定义的类型别名都会被当作不同的类型,以便于进行访问控制。一个类型别名的访问级别不可高于原类型的访问级别。比如说,一个`private`级别的类型别名可以设定给一个`public`、`internal`、`private`的类型,但是一个`public`级别的类型别名只能设定给一个`public`级别的类型,不能设定给`internal`或`private` 级别的类型。 -> 注意:这条规则也适用于为满足协议一致性而给相关类型命名别名的情况。 +你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,`private` 级别的类型别名可以作为 `public`、`internal`、`private` 类型的别名,但是 `public` 级别的类型别名只能作为 `public` 类型的别名,不能作为 `internal` 或 `private` 类型的别名。 + +> 注意 +这条规则也适用于为满足协议一致性而将类型别名用于关联类型的情况。 diff --git a/source/chapter2/25_Advanced_Operators.md b/source/chapter2/25_Advanced_Operators.md index e556b045..ee0757f6 100644 --- a/source/chapter2/25_Advanced_Operators.md +++ b/source/chapter2/25_Advanced_Operators.md @@ -8,241 +8,253 @@ > 2.0 > 翻译+校对:[buginux](https://github.com/buginux) +> 2.1 +> 校对:[shanks](http://codebuild.me),2015-11-01 +> +> 2.2 +> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-17 + 本页内容包括: - [位运算符](#bitwise_operators) - [溢出运算符](#overflow_operators) -- [优先级和结合性(Precedence and Associativity)](#precedence_and_associativity) -- [运算符函数(Operator Functions)](#operator_functions) +- [优先级和结合性](#precedence_and_associativity) +- [运算符函数](#operator_functions) - [自定义运算符](#custom_operators) -除了在之前介绍过的[基本运算符](./02_Basic_Operators.html),Swift 中还有许多可以对数值进行复杂操作的高级运算符。这些高级运算符包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。 +除了在之前介绍过的[基本运算符](./02_Basic_Operators.html),Swift 中还有许多可以对数值进行复杂运算的高级运算符。这些高级运算符包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。 -与C语言中的算术运算符不同,Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。 +与 C 语言中的算术运算符不同,Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。 -在定义自有的结构体、类和枚举时,最好也同时为它们提供标准Swift运算符的实现。Swift简化了运算符的自定义实现,也使判断不同类型所对应的行为更为简单。 +自定义结构体、类和枚举时,如果也为它们提供标准 Swift 运算符的实现,将会非常有用。在 Swift 中自定义运算符非常简单,运算符也会针对不同类型使用对应实现。 -我们不用被预定义的运算符所限制。在 Swift 当中可以自由地定义中缀、前缀、后缀和赋值运算符,以及相应的优先级与结合性。这些运算符在代码中可以像预设的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义的运算符。 +我们不用被预定义的运算符所限制。在 Swift 中可以自由地定义中缀、前缀、后缀和赋值运算符,以及相应的优先级与结合性。这些运算符在代码中可以像预定义的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义的运算符。 ## 位运算符 -位运算符(`Bitwise operators`)可以操作一个数据结构中每个独立的位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。 +位运算符可以操作数据结构中每个独立的比特位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。 -Swift 支持C语言中的全部位运算符,具体如下: +Swift 支持 C 语言中的全部位运算符,接下来会一一介绍。 -### 按位取反运算符(`bitwise NOT operator`) + +### 按位取反运算符 + +按位取反运算符(`~`)可以对一个数值的全部比特位进行取反: -按位取反运算符(`~`) 可以对一个数值的全部位进行取反: ![Art/bitwiseNOT_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseNOT_2x.png) -按位取反操作符是一个前置运算符,需要直接放在操作数的之前,并且它们之间不能添加任何空格。 +按位取反运算符是一个前缀运算符,需要直接放在运算的数之前,并且它们之间不能添加任何空格: -``` +```swift let initialBits: UInt8 = 0b00001111 -let invertedBits = ~initialBits // 等于 0b11110000 +let invertedBits = ~initialBits // 等于 0b11110000 ``` -`UInt8` 类型的整数有 8 个比特位,可以存储 0 ~ 255之间的任意整数。这个例子初始化了一个 `UInt8` 类型的整数,并赋值为二进制的 `00001111`,它的前 4 位都为`0`,后 4 位都为`1`。这个值等价于十进制的 `15` 。 +`UInt8` 类型的整数有 8 个比特位,可以存储 `0 ~ 255` 之间的任意整数。这个例子初始化了一个 `UInt8` 类型的整数,并赋值为二进制的 `00001111`,它的前 4 位都为 `0`,后 4 位都为 `1`。这个值等价于十进制的 `15`。 接着使用按位取反运算符创建了一个名为 `invertedBits` 的常量,这个常量的值与全部位取反后的 `initialBits` 相等。即所有的 `0` 都变成了 `1`,同时所有的 `1` 都变成 `0`。`invertedBits` 的二进制值为 `11110000`,等价于无符号十进制数的 `240`。 -### 按位与运算符(Bitwise AND Operator) + +### 按位与运算符 -按位与运算符(`&`)可以对两个数的比特位进行合并。它返回一个新的数,只有当两个操作数的对应位*都*为 `1` 的时候,该数的对应位才为 `1`。 +按位与运算符(`&`)可以对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位都为 `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") +![Art/bitwiseAND_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseAND_2x.png) -在下面的示例当中,`firstSixBits` 和 `lastSixBits` 中间 4 个位的值都为 1 。按位与运算符对它们进行了运算,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`: +在下面的示例当中,`firstSixBits` 和 `lastSixBits` 中间 4 个位的值都为 `1`。按位与运算符对它们进行了运算,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`: -``` +```swift let firstSixBits: UInt8 = 0b11111100 let lastSixBits: UInt8 = 0b00111111 -let middleFourBits = firstSixBits & lastSixBits // 等于 00111100 +let middleFourBits = firstSixBits & lastSixBits // 等于 00111100 ``` -### 按位或运算符(Bitwise OR Operator) + +### 按位或运算符 -按位或运算符(`|`)可以对两个数的比特位进行比较。它返回一个新的数,只要两个操作数的对应位中有*任意*一个为 `1` 时,该数的对应位就为 `1`。 +按位或运算符(`|`)可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有任意一个为 `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`: +在下面的示例中,`someBits` 和 `moreBits` 不同的位会被设置为 `1`。接位或运算符对它们进行了运算,得到二进制数值 `11111110`,等价于无符号十进制数的 `254`: -``` +```swift let someBits: UInt8 = 0b10110010 let moreBits: UInt8 = 0b01011110 -let combinedbits = someBits | moreBits // 等于 11111110 +let combinedbits = someBits | moreBits // 等于 11111110 ``` -### 按位异或运算符(Bitwise XOR Opoerator) + +### 按位异或运算符 -按位异或运算符(`^`)可以对两个数的比特位进行比较。它返回一个新的数,当两个操作数的对应位不相同时,该数的对应位就为 `1`: +按位异或运算符(`^`)可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 `1`: ![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` 而对方设置为 `0` 的位。 按位异或运算符将这两个位都设置为 `1`,同时将其它位都设置为 `0`: +在下面的示例当中,`firstBits` 和 `otherBits` 都有一个自己的位为 `1` 而对方的对应位为 `0` 的位。 按位异或运算符将新数的这两个位都设置为 `1`,同时将其它位都设置为 `0`: -``` +```swift let firstBits: UInt8 = 0b00010100 let otherBits: UInt8 = 0b00000101 -let outputBits = firstBits ^ otherBits // 等于 00010001 +let outputBits = firstBits ^ otherBits // 等于 00010001 ``` -### 按位左移/右移运算符 + +### 按位左移、右移运算符 -按位左移运算符(`<<`)和按位右移运算符(`>>`)可以对一个数进行指定位数的左移和右移,但是需要遵守下面定义的规则。 +按位左移运算符(`<<`)和按位右移运算符(`>>`)可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。 对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2,同样地,将一个整数右移一位,等价于将这个数除以 2。 -#### 无符号整型的移位操作 + +#### 无符号整数的移位运算 -对无符号整型进行移位的规则如下: +对无符号整数进行移位的规则如下: -1. 已经存在的比特位按指定的位数进行左移和右移。 -2. 任何移动超出整型存储边界的位都会被丢弃。 -3. 用 0 来填充移动后产生的空白位。 +1. 已经存在的位按指定的位数进行左移和右移。 +2. 任何因移动而超出整型存储范围的位都会被丢弃。 +3. 用 `0` 来填充移位后产生的空白位。 -这种方法称为逻辑移位(`logical shift`)。 +这种方法称为逻辑移位。 -以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 1 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 1 位) 的结果。蓝色的部分是被移位的,灰色的部分是被抛弃的,橙色的部分则是被填充进来的。 +以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 `1` 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 `1` 位)的结果。蓝色的部分是被移位的,灰色的部分是被抛弃的,橙色的部分则是被填充进来的: ![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 中的移位操作: +下面的代码演示了 Swift 中的移位运算: -``` -let shiftBits: UInt8 = 4 // 即二进制的00000100 -shiftBits << 1 // 00001000 -shiftBits << 2 // 00010000 -shiftBits << 5 // 10000000 -shiftBits << 6 // 00000000 -shiftBits >> 2 // 00000001 +```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 +let redComponent = (pink & 0xFF0000) >> 16 // redComponent 是 0xCC,即 204 +let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 是 0x66, 即 102 +let blueComponent = pink & 0x0000FF // blueComponent 是 0x99,即 153 ``` -这个示例使用了一个命名为 `pink` 的 `UInt32` 型常量来存储层叠样式表(`CSS`)中粉色的颜色值。该 `CSS` 的十六进制颜色值 `#CC6699`, 在 Swift 中表示为 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。 +这个示例使用了一个命名为 `pink` 的 `UInt32` 型常量来存储 CSS 中粉色的颜色值。该 CSS 的十六进制颜色值 `#CC6699`,在 Swift 中表示为 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。 -红色部分是通过对 `0xCC6699` 和 `0xFF0000` 进行按位与运算后得到的。`0xFF0000` 中的 `0` 部分作为*掩码*,掩盖了 `OxCC6699` 中的第二和第三个字节,使得数值中的 `6699` 被忽略,只留下 `0xCC0000`。 +红色部分是通过对 `0xCC6699` 和 `0xFF0000` 进行按位与运算后得到的。`0xFF0000` 中的 `0` 部分“掩盖”了 `OxCC6699` 中的第二、第三个字节,使得数值中的 `6699` 被忽略,只留下 `0xCC0000`。 -然后,再将这个数按向右移动 16 位(`>> 16`)。十六进制中每两个字符表示 8 个比特位,所以移动 16 位后 `0xCC0000` 就变为 `0x0000CC`。这个数和`0xCC`是等同的,也就是十进制数值的 `204`。 +然后,再将这个数按向右移动 16 位(`>> 16`)。十六进制中每两个字符表示 8 个比特位,所以移动 16 位后 `0xCC0000` 就变为 `0x0000CC`。这个数和`0xCC`是等同的,也就是十进制数值的 `204`。 同样的,绿色部分通过对 `0xCC6699` 和 `0x00FF00` 进行按位与运算得到 `0x006600`。然后将这个数向右移动 8 位,得到 `0x66`,也就是十进制数值的 `102`。 -最后,蓝色部分通过对 `0xCC6699` 和 `0x0000FF` 进行按位与运算得到 `0x000099`。并且不需要进行向右移位,所以结果为 `0x99` ,也就是十进制数值的 `153`。 +最后,蓝色部分通过对 `0xCC6699` 和 `0x0000FF` 进行按位与运算得到 `0x000099`。这里不需要再向右移位,所以结果为 `0x99` ,也就是十进制数值的 `153`。 -#### 有符号整型的移位操作 + +#### 有符号整数的移位运算 -对比无符号整型来说,有符整型的移位操作相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 位有符号整数的,但是其中的原理对任何位数的有符号整数都是通用的。) +对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 比特位的有符号整数的,但是其中的原理对任何位数的有符号整数都是通用的。) -有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。 +有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。 -其余的比特位(通常被称为数值位)存储了这个数的真实值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4` 的 `Int8` 型整数的二进制位表现形式: +其余的比特位(通常被称为数值位)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4` 的 `Int8` 型整数的二进制位表现形式: ![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。 +负数的存储方式略有不同。它存储的值的绝对值等于 `2` 的 `n` 次方减去它的实际值(也就是数值位表示的值),这里的 `n` 为数值位的比特位数。一个 8 比特位的数有 7 个比特位是数值位,所以是 `2` 的 `7` 次方,即 `128`。 这是值为 `-4` 的 `Int8` 型整数的二进制位表现形式: ![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`) 的二进制表示。 +这次的符号位为 `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") -负数的表示通常被称为二进制补码(`two's complement`)表示法。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。 +负数的表示通常被称为二进制补码表示。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。 -首先,如果想对 `-1` 和 `-4` 进行加法操作,我们只需要将这两个数的全部 8 个比特位进行相加,并且将计算结果中超出 8 位的数值丢弃: +首先,如果想对 `-1` 和 `-4` 进行加法运算,我们只需要将这两个数的全部 8 个比特位进行相加,并且将计算结果中超出 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") -其次,使用二进制补码可以使负数的按位左移和右移操作得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2,每向右一位就将自身的数值除以 2。要达到此目的,对有符号整数的右移有一个额外的规则: +其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2,每向右一位就将自身的数值除以 2。要达到此目的,对有符号整数的右移有一个额外的规则: -* 当对正整数进行按位右移操作时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用*符号位*进行填充,而不是用 0。 +* 当对整数进行按位右移运算时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是用 `0`。 ![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") -这个行为可以确保有符号整数的符号位不会因为右移操作而改变,这通常被称为算术移位(`arithmetic shift`)。 +这个行为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为算术移位。 -由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 0。在移位的过程中保持符号位不变,意味着负整数在接近 `0` 的过程中会一直保持为负。 +由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 `0`。在移位的过程中保持符号位不变,意味着负整数在接近 `0` 的过程中会一直保持为负。 ## 溢出运算符 -在默认情况下,当向一个整数赋超过它容量的值时,Swift 默认会报错,而不是生成一个无效的数。这个行为给我们操作过大或着过小的数的时候提供了额外的安全性。 +在默认情况下,当向一个整数赋予超过它容量的值时,Swift 默认会报错,而不是生成一个无效的数。这个行为为我们在运算过大或着过小的数的时候提供了额外的安全性。 例如,`Int16` 型整数能容纳的有符号整数范围是 `-32768` 到 `32767`,当为一个 `Int16` 型变量赋的值超过这个范围时,系统就会报错: -``` +```swift var potentialOverflow = Int16.max -// potentialOverflow 的值是 32767, 这是 Int16 能容纳的最大整数 - +// potentialOverflow 的值是 32767,这是 Int16 能容纳的最大整数 potentialOverflow += 1 // 这里会报错 ``` 为过大或者过小的数值提供错误处理,能让我们在处理边界值时更加灵活。 -然而,也可以选择让系统在数值溢出的时候采取截断操作,而非报错。可以使用 Swift 提供的三个溢出操作符(`overflow operators`)来让系统支持整数溢出运算。这些操作符都是以 `&` 开头的: +然而,也可以选择让系统在数值溢出的时候采取截断处理,而非报错。可以使用 Swift 提供的三个溢出运算符来让系统支持整数溢出运算。这些运算符都是以 `&` 开头的: * 溢出加法 `&+` * 溢出减法 `&-` * 溢出乘法 `&*` + ### 数值溢出 数值有可能出现上溢或者下溢。 -这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么: -``` +这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么: + +```swift var unsignedOverflow = UInt8.max // unsignedOverflow 等于 UInt8 所能容纳的最大整数 255 - unsignedOverflow = unsignedOverflow &+ 1 // 此时 unsignedOverflow 等于 0 ``` -`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`255`,以二进制表示即 `11111111`)。然后使用了溢出加法运算符(`&+`)对其进行加 1 操作。这使得它的二进制表示正好超出 `UInt8` 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,留在 `UInt8` 边界内的值是 `00000000`,也就是十进制数值的 0。 +`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`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") -同样地,当我们对一个无符号整数使用溢出减法(`&-`)进行下溢运算时也会产生类似的现象: +同样地,当我们对一个无符号整数使用溢出减法(`&-`)进行下溢运算时也会产生类似的现象: -``` +```swift var unsignedOverflow = UInt8.min // unsignedOverflow 等于 UInt8 所能容纳的最小整数 0 - unsignedOverflow = unsignedOverflow &- 1 // 此时 unsignedOverflow 等于 255 ``` -`UInt8` 型整数能容纳的最小值是 0,以二进制表示即 `00000000`。当使用溢出减法运算符对其进行减 1 操作时,数值会产生下溢并被截断为 `11111111`, 也就是十进制数值的 255。 +`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/overflowAddition_2x.png") -溢出也会发生在有符号整型数值上。在对有符号整型数值进行溢出加法或溢出减法运算时,符号位也需要参与计算,正如[按位左移/右移运算符](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-ID34)所描述的。 +溢出也会发生在有符号整型数值上。在对有符号整型数值进行溢出加法或溢出减法运算时,符号位也需要参与计算,正如[按位左移、右移运算符](#bitwise_left_and_right_shift_operators)所描述的。 -``` +```swift var signedOverflow = Int8.min // signedOverflow 等于 Int8 所能容纳的最小整数 -128 - signedOverflow = signedOverflow &- 1 // 此时 signedOverflow 等于 127 ``` -`Int8` 型整数能容纳的最小值是 -128,以二进制表示即 `10000000`。当使用溢出减法操作符对其进行减 1 操作时,符号位被翻转,得到二进制数值 `01111111`,也就是十进制数值的 `127`,这个值也是 `Int8` 型整数所能容纳的最大值。 +`Int8` 型整数能容纳的最小值是 `-128`,以二进制表示即 `10000000`。当使用溢出减法运算符对其进行减 `1` 运算时,符号位被翻转,得到二进制数值 `01111111`,也就是十进制数值的 `127`,这个值也是 `Int8` 型整数所能容纳的最大值。 ![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") @@ -251,92 +263,91 @@ signedOverflow = signedOverflow &- 1 ## 优先级和结合性 -运算符的优先级(`precedence`)使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。 +运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。 -结合性(`associativity`)定义了相同优先级的运算符是如何结合(或关联)的 —— 是与左边结合为一组,还是与右边结合为一组。可以将这意思理解为“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”。 +结合性定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组。可以将这意思理解为“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”。 -在复合表达式的运算顺序中,运算符的优先级和结合性是非常重要的。举例来说,为什么下面这个表达式的运算结果是 `4`? +在复合表达式的运算顺序中,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个表达式的运算结果会是 `17`。 ```swift -2 + 3 * 4 % 5 -// 结果是 4 +2 + 3 % 4 * 5 +// 结果是 17 ``` -如果严格地从左到右进行运算,则运算的过程是这样的: +如果完全从左到右进行运算,则运算的过程是这样的: - 2 + 3 = 5 -- 5 * 4 = 20 -- 20 % 5 = 0 +- 5 % 4 = 1 +- 1 * 5 = 5 -但是正确答案是 `4` 而不是 `0`。优先级高的运算符要先于优先级低的运算符进行计算。与C语言类似,在 Swift 当中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。 +但是正确答案是 `17` 而不是 `5`。优先级高的运算符要先于优先级低的运算符进行计算。与 C 语言类似,在 Swift 中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。 而乘法与取余的优先级相同。这时为了得到正确的运算顺序,还需要考虑结合性。乘法与取余运算都是左结合的。可以将这考虑成为这两部分表达式都隐式地加上了括号: ```swift -2 + ((3 * 4) % 5) +2 + ((3 % 4) * 5) ``` -`(3 * 4) = 12`,所以表达式相当于: - +`(3 % 4)` 等于 `3`,所以表达式相当于: ```swift -2 + (12 % 5) +2 + (3 * 5) ``` -`12 % 5 = 2`,所以表达式相当于: +`3 * 5` 等于 `15`,所以表达式相当于: ```swift -2 + 2 +2 + 15 ``` -此时可以容易地看出计算的结果为 `4`。 +因此计算结果为 `17`。 -如果想查看完整的 Swift 运算符优先级和结合性规则,请参考[表达式](../chapter3/04_Expressions.html)。 +如果想查看完整的 Swift 运算符优先级和结合性规则,请参考[表达式](../chapter3/04_Expressions.html)。如果想查看 Swift 标准库提供所有的运算符,请查看 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。 -> 注意: -> 对于C语言和 Objective-C 来说,Swift 的运算符优先级和结合性规则是更加简洁和可预测的。但是,这也意味着它们于那些基于C的语言不是完全一致的。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然是按照你所想的那样去执行。 +> 注意 +> 相对 C 语言和 Objective-C 来说,Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致的。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合你的预期。 ## 运算符函数 -类和结构可以为现有的操作符提供自定义的实现,这通常被称为运算符重载(`overloading`)。 +类和结构体可以为现有的运算符提供自定义的实现,这通常被称为运算符重载。 -下面的例子展示了如何为自定义的结构实现加法操作符(`+`)。算术加法运算符是一个两目运算符(`binary operator`),因为它可以对两个目标进行操作,同时它还是中缀(`infix`)运算符,因为它出现在两个目标中间。 +下面的例子展示了如何为自定义的结构体实现加法运算符(`+`)。算术加法运算符是一个双目运算符,因为它可以对两个值进行运算,同时它还是中缀运算符,因为它出现在两个值中间。 -例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量`(x, y)`,紧接着定义了一个可以对两个 `Vector2D` 结构体进行相加的运算符函数(`operator function`): +例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以对两个 `Vector2D` 结构体进行相加的运算符函数: ```swift struct Vector2D { var x = 0.0, y = 0.0 } - func + (left: Vector2D, right: Vector2D) -> Vector2D { return Vector2D(x: left.x + right.x, y: left.y + right.y) } ``` -该运算符函数被定义为一个全局函数,并且函数的名字与它要进行重载的 `+` 名字一致。因为算术加法运算符是双目运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的输入参数,同时有一个 `Vector2D` 类型的返回值。 +该运算符函数被定义为一个全局函数,并且函数的名字与它要进行重载的 `+` 名字一致。因为算术加法运算符是双目运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的参数,同时有一个 `Vector2D` 类型的返回值。 -在这个实现中,输入参数分别被命名为 `left` 和 `right`,代表在 `+` 运算符左边和右边的两个 `Vector2D` 对象。函数返回了一个新的 `Vector2D` 的对象,这个对象的 `x` 和 `y` 分别等于两个参数对象的 `x` 和 `y` 的值之和。 +在这个实现中,输入参数分别被命名为 `left` 和 `right`,代表在 `+` 运算符左边和右边的两个 `Vector2D` 实例。函数返回了一个新的 `Vector2D` 实例,这个实例的 `x` 和 `y` 分别等于作为参数的两个实例的 `x` 和 `y` 的值之和。 -这个函数被定义成全局的,而不是 `Vector2D` 结构的成员方法,所以任意两个 `Vector2D` 对象都可以使用这个中缀运算符: +这个函数被定义成全局的,而不是 `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) +// 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") + ### 前缀和后缀运算符 -上个例子演示了一个双目中缀运算符的自定义实现。类与结构体也能提供标准单目运算符(`unary operators`)的实现。单目运算符只有一个操作目标。当运算符出现在操作目标之前时,它就是前缀(`prefix`)的(比如 `-a`),而当它出现在操作目标之后时,它就是后缀(`postfix`)的(比如 `i++`)。 +上个例子演示了一个双目中缀运算符的自定义实现。类与结构体也能提供标准单目运算符的实现。单目运算符只运算一个值。当运算符出现在值之前时,它就是前缀的(例如 `-a`),而当它出现在值之后时,它就是后缀的(例如 `b!`)。 -要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 `func` 关键字之前指定 `prefix` 或者 `postfix` 限定符: +要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 `func` 关键字之前指定 `prefix` 或者 `postfix` 修饰符: ```swift prefix func - (vector: Vector2D) -> Vector2D { @@ -344,22 +355,21 @@ prefix func - (vector: Vector2D) -> Vector2D { } ``` -这段代码为 `Vector2D` 类型实现了单目减运算符(`-a`)。由于单目减运算符是前缀运算符,所以这个函数需要加上 `prefix` 限定符。 +这段代码为 `Vector2D` 类型实现了单目负号运算符。由于该运算符是前缀运算符,所以这个函数需要加上 `prefix` 修饰符。 -对于简单数值,单目减运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,单目减运算将其 `x` 和 `y` 属性的正负性都进行了改变。 +对于简单数值,单目负号运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,该运算将其 `x` 和 `y` 属性的正负性都进行了改变: ```swift let positive = Vector2D(x: 3.0, y: 4.0) let negative = -positive // negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例 - let alsoPositive = -negative // alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例 ``` - + ### 复合赋值运算符 -复合赋值运算符(`Compound assignment operators`)将赋值运算符(`=`)与其它运算符进行结合。比如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接被修改。 +复合赋值运算符将赋值运算符(`=`)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接被修改。 ```swift func += (inout left: Vector2D, right: Vector2D) { @@ -376,33 +386,16 @@ original += vectorToAdd // original 的值现在为 (4.0, 6.0) ``` -还可以将赋值与 `prefix` 或 `postfix` 限定符结合起来,下面的代码为 `Vector2D` 实例实现了前缀自增运算符(`++a`): -```swift -prefix func ++ (inout vector: Vector2D) -> Vector2D { - vector += Vector2D(x: 1.0, y: 1.0) - return vector -} -``` - -这个前缀自增运算符使用了前面定义的加法赋值操作。它对 `Vector2D` 的 `x` 和 `y` 属性都进行了加 `1` 操作,再将结果返回: - -```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` 进行重载。 +> 注意 +> 不能对默认的赋值运算符(`=`)进行重载。只有组合赋值运算符可以被重载。同样地,也无法对三目条件运算符 (`a ? b : c`) 进行重载。 -### 等价操作符 +### 等价运算符 -自定义的类和结构体没有对等价操作符(`equivalence operators`)进行默认实现,等价操作符通常被称为“相等”操作符(`==`)与“不等”操作符(`!=`)。对于自定义类型,Swift 无法判断其是否“相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。 +自定义的类和结构体没有对等价运算符进行默认实现,等价运算符通常被称为“相等”运算符(`==`)与“不等”运算符(`!=`)。对于自定义类型,Swift 无法判断其是否“相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。 -为了使用等价操作符来对自定义的类型进行判等操作,需要为其提供自定义实现,实现的方法与其它中缀运算符一样: +为了使用等价运算符能对自定义的类型进行判等运算,需要为其提供自定义实现,实现的方法与其它中缀运算符一样: ```swift func == (left: Vector2D, right: Vector2D) -> Bool { @@ -413,9 +406,9 @@ func != (left: Vector2D, right: Vector2D) -> Bool { } ``` -上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 对象是否有相等。对于 `Vector2D` 类型来说,“相等”意味“两个实例的 `x`属性 和 `y` 属性都相等”,这也是代码中用来进行判等的逻辑。示例里同时也实现了“不等”操作符(`!=`),它简单地将“相等”操作符进行取反后返回。 +上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 实例是否相等。对于 `Vector2D` 类型来说,“相等”意味着“两个实例的 `x` 属性和 `y` 属性都相等”,这也是代码中用来进行判等的逻辑。示例里同时也实现了“不等”运算符(`!=`),它简单地将“相等”运算符的结果进行取反后返回。 -现在我们可以使用这两个运算符来判断两个 `Vector2D` 对象是否相等。 +现在我们可以使用这两个运算符来判断两个 `Vector2D` 实例是否相等: ```swift let twoThree = Vector2D(x: 2.0, y: 3.0) @@ -423,21 +416,21 @@ let anotherTwoThree = Vector2D(x: 2.0, y: 3.0) if twoThree == anotherTwoThree { print("These two vectors are equivalent.") } -// prints "These two vectors are equivalent." +// 打印 “These two vectors are equivalent.” ``` -### 自定义运算符 +## 自定义运算符 -除了实现标准运算符,在 Swift 当中还可以声明和实现自定义运算符(`custom operators`)。可以用来自定义运算符的字符列表请参考[操作符](../chapter3/02_Lexical_Structure.html#operators) +除了实现标准运算符,在 Swift 中还可以声明和实现自定义运算符。可以用来自定义运算符的字符列表请参考[运算符](../chapter3/02_Lexical_Structure.html#operators)。 -新的运算符要在全局作用域内,使用 `operator` 关键字进行声明,同时还要指定 `prefix`、`infix` 或者 `postfix` 限定符: +新的运算符要使用 `operator` 关键字在全局作用域内进行定义,同时还要指定 `prefix`、`infix` 或者 `postfix` 修饰符: ```swift prefix operator +++ {} ``` -上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有意义,因为我们针对 `Vector2D` 的实例来定义它的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法操作符来让矩阵对自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y`属性的值翻倍: +上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有意义,因此我们针对 `Vector2D` 的实例来定义它的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法运算符来让矩阵对自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y` 属性的值翻倍: ```swift prefix func +++ (inout vector: Vector2D) -> Vector2D { @@ -446,7 +439,7 @@ prefix func +++ (inout vector: Vector2D) -> Vector2D { } ``` -`Vector2D` 的 `+++` 的实现和 `++` 的实现很相似, 唯一不同的是前者对自身进行相加, 而后者是与另一个值为 `(1.0, 1.0)` 的向量相加. +`Vector2D` 的 `+++` 的实现和 `++` 的实现很相似,唯一不同的是前者对自身进行相加,而后者是与另一个值为 `(1.0, 1.0)` 的向量相加。 ```swift var toBeDoubled = Vector2D(x: 1.0, y: 4.0) @@ -455,15 +448,16 @@ let afterDoubling = +++toBeDoubled // afterDoubling 现在的值也为 (2.0, 8.0) ``` + ### 自定义中缀运算符的优先级和结合性 -自定义的中缀(`infix`)运算符也可以指定优先级(`precedence`)和结合性(`associativity`)。[优先级和结合性](#PrecedenceandAssociativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。 +自定义的中缀运算符也可以指定优先级和结合性。[优先级和结合性](#precedence_and_associativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。 -结合性(`associativity`)可取的值有` left`,`right` 和 `none`。当左结合运算符跟其他相同优先级的左结合运算符写在一起时,会跟左边的操作数进行结合。同理,当右结合运算符跟其他相同优先级的右结合运算符写在一起时,会跟右边的操作数进行结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。 +结合性可取的值有` left`,`right` 和 `none`。当左结合运算符跟其他相同优先级的左结合运算符写在一起时,会跟左边的值进行结合。同理,当右结合运算符跟其他相同优先级的右结合运算符写在一起时,会跟右边的值进行结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。 -结合性(`associativity`)的默认值是 `none`,优先级(`precedence`)如果没有指定,则默认为 `100`。 +结合性的默认值是 `none`,优先级的默认值 `100`。 -以下例子定义了一个新的中缀运算符 `+-`,此操作符是左结合的,并且它的优先级为 `140`: +以下例子定义了一个新的中缀运算符 `+-`,此运算符的结合性为 `left`,并且它的优先级为 `140`: ```swift infix operator +- { associativity left precedence 140 } @@ -473,10 +467,10 @@ func +- (left: Vector2D, right: Vector2D) -> Vector2D { let firstVector = Vector2D(x: 1.0, y: 2.0) let secondVector = Vector2D(x: 3.0, y: 4.0) let plusMinusVector = firstVector +- secondVector -// plusMinusVector 是一个 Vector2D 类型,并且它的值为 (4.0, -2.0) +// plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0) ``` -这个运算符把两个向量的 `x` 值相加,同时用第一个向量的 `y` 值减去第二个向量的 `y` 值。因为它本质上是属于“加型”运算符,所以将它的结合性和优先级被设置为(`left` 和 `140`),这与 `+` 和 `-` 等默认的中缀加型操作符是相同的。完整的 Swift 操作符默认结合性与优先级请参考[表达式](../chapter3/04_Expressions.html)。 +这个运算符把两个向量的 `x` 值相加,同时用第一个向量的 `y` 值减去第二个向量的 `y` 值。因为它本质上是属于“相加型”运算符,所以将它的结合性和优先级被分别设置为 `left` 和 `140`,这与 `+` 和 `-` 等默认的中缀“相加型”运算符是相同的。关于 Swift 标准库提供的运算符的结合性与优先级,请参考 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。 -> 注意: -> 当定义前缀与后缀操作符的时候,我们并没有指定优先级。然而,如果对同一个操作数同时使用前缀与后缀操作符,则后缀操作符会先被执行。 +> 注意 +> 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。 diff --git a/source/chapter3/01_About_the_Language_Reference.md b/source/chapter3/01_About_the_Language_Reference.md index 0a6e6bfd..db6da062 100755 --- a/source/chapter3/01_About_the_Language_Reference.md +++ b/source/chapter3/01_About_the_Language_Reference.md @@ -12,34 +12,29 @@ - [如何阅读语法](#how_to_read_the_grammar) -本书的这一节描述了Swift编程语言的形式语法。这里描述的语法是为了帮助您更详细的了解该语言,而不是让您直接实现一个解析器或编译器。 +本书的这一节描述了 Swift 编程语言的形式语法。这里描述的语法是为了帮助您更详细地了解该语言,而不是让您直接实现一个解析器或编译器。 -Swift语言相对小一点,这是由于在Swift代码中几乎所有常见的类型、函数以及运算符都已经在Swift标准库中定义了。虽然这些类型、函数和运算符并不是Swift语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。 +Swift 语言相对较小,这是由于 Swift 代码中的几乎所有常见类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。 ## 如何阅读语法 -用来描述Swift编程语言形式语法的标记遵循下面几个约定: - -- 箭头(→)用来标记语法产式,可以理解为“可以包含”。 -- *斜体*文字用来表示句法分类,并出现在一个语法产式规则两侧。 -- 义词和标点符号由粗体固定宽度的文本标记,而且只出现在一个语法产式规则的右侧。 -- 选择性的语法产式由竖线(|)分隔。当可选用的语法产式太多时,为了阅读方便,它们将被拆分为多行语法产式规则。 -- 少数情况下,常规字体文字用来描述语法产式规则的右边。 -- 可选的句法分类和文字用尾标`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)­}­ +用来描述 Swift 编程语言形式语法的符号遵循下面几个约定: +- 箭头(`→`)用来标记语法产式,可以理解为“可由……构成”。 +- 斜体文字用来表示句法类型,并出现在一个语法产式规则两侧。 +- 关键字和标点符号由固定宽度的粗体文本表示,只出现在一个语法产式规则的右侧。 +- 可供选择的语法产式由竖线(`|`)分隔。当可选用的语法产式太多时,为了阅读方便,它们将被拆分为多行语法产式规则。 +- 少数情况下,语法产式规则的右侧会有用于描述的常规字体文字。 +- 可选的句法类型和字面值用尾标 `opt` 来标记。 +举个例子,getter-setter 的语法块的定义如下: +> getter-setter 方法块语法 +> *getter-setter 方法块* → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)可选 } | { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) } +这个定义表明,一个 getter-setter 方法块可以由一个 getter 子句后跟一个可选的 setter 子句构成,然后用大括号括起来,或者由一个 setter 子句后跟一个 getter 子句构成,然后用大括号括起来。下面的两个语法产式等价于上述的语法产式,并明确指出了如何取舍: +> getter-setter 方法块语法 +> getter-setter 方法块 → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)可选 } +> getter-setter 方法块 → { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) } diff --git a/source/chapter3/02_Lexical_Structure.md b/source/chapter3/02_Lexical_Structure.md index a8799711..3f7ee53b 100755 --- a/source/chapter3/02_Lexical_Structure.md +++ b/source/chapter3/02_Lexical_Structure.md @@ -5,265 +5,343 @@ > 翻译:[superkam](https://github.com/superkam) > 校对:[numbbbbb](https://github.com/numbbbbb) + + > 2.0 > 翻译+校对:[buginux](https://github.com/buginux) + +> 2.1 +> 翻译:[mmoaay](https://github.com/mmoaay) + + + +> 2.2 +> 翻译+校对:[星夜暮晨](https://github.com/semperidem),2016-04-06 + + 本页包含内容: -- [空白与注释(*Whitespace and Comments*)](#whitespace_and_comments) -- [标识符(*Identifiers*)](#identifiers) -- [关键字(*Keywords*)](#keywords) -- [字面量(*Literals*)](#literals) -- [运算符(*Operators*)](#operators) +- [空白与注释](#whitespace_and_comments) +- [标识符](#identifiers) +- [关键字和标点符号](#keywords) +- [字面量](#literals) + - [整数字面量](#integer_literals) + - [浮点数字面量](#floating_point_literals) + - [字符串字面量](#string_literals) +- [运算符](#operators) -Swift 的“词法结构(*lexical structure*)”描述了能构成该语言中合法标记(*tokens*)的字符序列。这些合法标记组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法标记由一个标识符、关键字、标点符号、文字或运算符组成。 +Swift 的*“词法结构 (lexical structure)”* 描述了能构成该语言中合法符号 (token) 的字符序列。这些合法符号组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符 (identifier)、关键字 (keyword)、标点符号 (punctuation)、字面量 (literal) 或运算符 (operator) 组成。 -通常情况下,标记是在随后将介绍的语法约束下,由 Swift 源文件的输入文本中提取可能的最长子串生成。这种方法称为“最长匹配项(*longest match*)”,或者“最大适合”(*maximal munch*)。 +通常情况下,通过考虑输入文本当中可能的最长子串,并且在随后将介绍的语法约束之下,根据随后将介绍的语法约束生成的,根据 Swift 源文件当中的字符来生成相应的“符号”。这种方法称为*“最长匹配 (longest match)”*,或者*“最大适合(maximal munch)”*。 ## 空白与注释 -空白(*whitespace*)有两个用途:分隔源文件中的标记和帮助区分运算符属于前缀还是后缀(参见 [运算符](#operators)),在其他情况下则会被忽略。以下的字符会被当作空白:空格(*space*)(U+0020)、换行符(*line feed*)(U+000A)、回车符(*carriage return*)(U+000D)、水平制表符(*horizontal tab*)(U+0009)、垂直制表符(*vertical tab*)(U+000B)、换页符(*form feed*)(U+000C)以及空(*null*)(U+0000)。 +空白 (whitespace) 有两个用途:分隔源文件中的符号以及帮助区分运算符属于前缀还是后缀(参见 [运算符](#operators)),在其他情况下空白则会被忽略。以下的字符会被当作空白:空格(U+0020)、换行符(U+000A)、回车符(U+000D)、水平制表符(U+0009)、垂直制表符(U+000B)、换页符(U+000C)以及空字符(U+0000)。 -注释(*comments*)被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符(*line feed*)(U+000A)或者回车符(*carriage return*)(U+000D)。多行注释由 `/*` 开始,以 `*/` 结束。注释允许嵌套,但注释标记必须匹配。 +注释被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符(U+000A)或者回车符(U+000D)。多行注释由 `/*` 开始,以 `*/` 结束。注释允许嵌套,但注释标记必须匹配。 -就像 [标记格式引用(Markup Formatting Reference)](https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497) 所说的那样,注释可以包含附加的格式和标记。 +正如 [*Markup Formatting Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497) 所述,注释可以包含附加的格式和标记。 ## 标识符 -标识符(*identifiers*)可以由以下的字符开始:大写或小写的字母 `A` 到 `Z`、下划线 `_`、基本多文种平面(*Basic Multilingual Plane*)中的 Unicode 非组合字符以及基本多文种平面以外的非专用区(*Private Use Area*)字符。首字符之后,允许使用数字和 Unicode 字符组合。 +*标识符(identifier)* 可以由以下的字符开始:大写或小写的字母 `A` 到 `Z`、下划线 (`_`)、基本多文种平面 (Basic Multilingual Plane) 中非字符数字组合的 Unicode 字符以及基本多文种平面以外的非个人专用区字符。在首字符之后,允许使用数字和组合 Unicode 字符。 -使用保留字(*reserved word*)作为标识符,需要在其前后增加反引号 \`。例如,`class` 不是合法的标识符,但可以使用 \`class\`。反引号不属于标识符的一部分,\`x\` 和 `x` 表示同一标识符。 +使用保留字作为标识符,需要在其前后增加反引号 (`` ` ``)。例如,`class` 不是合法的标识符,但可以使用 `` `class` ``。反引号不属于标识符的一部分,`` `x` `` 和 `x` 表示同一标识符。 -闭包(*closure*)中如果没有明确指定参数名称,参数将被隐式命名为 `$0`、`$1`、`$2`等等。 这些命名在闭包作用域范围内是合法的标识符。 +闭包中如果没有明确指定参数名称,参数将被隐式命名为 `$0`、`$1`、`$2` 等等。这些命名在闭包作用域范围内是合法的标识符。 + +> 标识符语法 -> 标识符语法 -> *标识符* → [*头部标识符*](#identifier_head) [*标识符字符组*](#identifier_characters)可选 -> *标识符* → \`[*头部标识符*](#identifier_head) [*标识符字符组*](#identifier_characters)可选\` -> *标识符* → [*隐式参数名*](#implicit_parameter_name) -> *标识符列表* → [*标识符*](#identifier) | [*标识符*](#identifier) **,** [*标识符列表*](#identifier_list) - +> *标识符* → [*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)可选 +> *标识符* → \`[*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)可选\` +> *标识符* → [*隐式参数名*](#implicit-parameter-name) + + +> *标识符列表* → [*标识符*](#identifier) | [*标识符*](#identifier) **,** [*标识符列表*](#identifier-list) + + > *头部标识符* → 大写或小写字母 A - Z -> *头部标识符* → U+00A8, U+00AA, U+00AD, U+00AF, U+00B2–U+00B5, or U+00B7–U+00BA -> *头部标识符* → U+00BC–U+00BE, U+00C0–U+00D6, U+00D8–U+00F6, or U+00F8–U+00FF -> *头部标识符* → U+0100–U+02FF, U+0370–U+167F, U+1681–U+180D, or U+180F–U+1DBF +> *头部标识符* → _ +> *头部标识符* → U+00A8,U+00AA,U+00AD,U+00AF,U+00B2–U+00B5,或者 U+00B7–U+00BA +> *头部标识符* → U+00BC–U+00BE,U+00C0–U+00D6,U+00D8–U+00F6,或者 U+00F8–U+00FF +> *头部标识符* → U+0100–U+02FF,U+0370–U+167F,U+1681–U+180D,或者 U+180F–U+1DBF > *头部标识符* → U+1E00–U+1FFF -> *头部标识符* → U+200B–U+200D, U+202A–U+202E, U+203F–U+2040, U+2054, or U+2060–U+206F -> *头部标识符* → U+2070–U+20CF, U+2100–U+218F, U+2460–U+24FF, or U+2776–U+2793 -> *头部标识符* → U+2C00–U+2DFF or U+2E80–U+2FFF -> *头部标识符* → U+3004–U+3007, U+3021–U+302F, U+3031–U+303F, or U+3040–U+D7FF -> *头部标识符* → U+F900–U+FD3D, U+FD40–U+FDCF, U+FDF0–U+FE1F, or U+FE30–U+FE44 +> *头部标识符* → U+200B–U+200D,U+202A–U+202E,U+203F–U+2040,U+2054,或者 U+2060–U+206F +> *头部标识符* → U+2070–U+20CF,U+2100–U+218F,U+2460–U+24FF,或者 U+2776–U+2793 +> *头部标识符* → U+2C00–U+2DFF 或者 U+2E80–U+2FFF +> *头部标识符* → U+3004–U+3007,U+3021–U+302F,U+3031–U+303F,或者 U+3040–U+D7FF +> *头部标识符* → U+F900–U+FD3D,U+FD40–U+FDCF,U+FDF0–U+FE1F,或者 U+FE30–U+FE44 > *头部标识符* → U+FE47–U+FFFD -> *头部标识符* → U+10000–U+1FFFD, U+20000–U+2FFFD, U+30000–U+3FFFD, or U+40000–U+4FFFD -> *头部标识符* → U+50000–U+5FFFD, U+60000–U+6FFFD, U+70000–U+7FFFD, or U+80000–U+8FFFD -> *头部标识符* → U+90000–U+9FFFD, U+A0000–U+AFFFD, U+B0000–U+BFFFD, or U+C0000–U+CFFFD -> *头部标识符* → U+D0000–U+DFFFD or U+E0000–U+EFFFD +> *头部标识符* → U+10000–U+1FFFD,U+20000–U+2FFFD,U+30000–U+3FFFD,或者 U+40000–U+4FFFD +> *头部标识符* → U+50000–U+5FFFD,U+60000–U+6FFFD,U+70000–U+7FFFD,或者 U+80000–U+8FFFD +> *头部标识符* → U+90000–U+9FFFD,U+A0000–U+AFFFD,U+B0000–U+BFFFD,或者 U+C0000–U+CFFFD +> *头部标识符* → U+D0000–U+DFFFD 或者 U+E0000–U+EFFFD + + > *标识符字符* → 数值 0 - 9 -> *标识符字符* → U+0300–U+036F, U+1DC0–U+1DFF, U+20D0–U+20FF, or U+FE20–U+FE2F -> *标识符字符* → [*头部标识符*](#identifier_head) - -> *标识符字符组* → [*标识符字符*](#identifier_character) [*标识符字符列表*](#identifier_characters)可选 - -> *隐式参数名* → **$** [*十进制数字列表*](#decimal_digits) +> *标识符字符* → U+0300–U+036F,U+1DC0–U+1DFF,U+20D0–U+20FF,或者 U+FE20–U+FE2F +> *标识符字符* → [*头部标识符*](#identifier-head) +> +> *标识符字符组* → [*标识符字符*](#identifier-character) [*标识符字符组*](#identifier-characters)可选 + + +> *隐式参数名* → **$** [*十进制数字列表*](#decimal-digits) -## 关键字和符号 +## 关键字和标点符号 -下面这些被保留的关键字(*keywords*)不允许用作标识符,除非被反引号转义,具体描述请参考 [标识符](#identifiers)。 +下面这些被保留的关键字不允许用作标识符,除非使用反引号转义,具体描述请参考 [标识符](#identifiers)。除了 `inout`、`var` 以及 `let` 之外的关键字可以用作某个函数声明或者函数调用当中的外部参数名,不用添加反引号转义。 -* **用在声明中的关键字:** *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*,这些关键字在特定上下文之外可以被用于标识符。 - -以下标记被当作保留符号,不能用于自定义操作符:`(`、`)`、`{`、`}`、`[`、`]`、`.`、`,`、`:`、`;`、`=`、`@`、`#`、`&(作为前缀操作符)`、`->`、`` `、`?` 和 `!(作为后缀操作符)`。 +* 用在声明中的关键字: `associatedtype`、`class`、`deinit`、`enum`、`extension`、`func`、`import`、`init`、`inout`、`internal`、`let`、`operator`、`private`、`protocol`、`public`、`static`、`struct`、`subscript`、`typealias` 以及 `var`。 +* 用在语句中的关键字:`break`、`case`、`continue`、`default`、`defer`、`do`、`else`、`fallthrough`、`for`、`guard`、`if`、`in`、`repeat`、`return`、`switch`、`where` 以及 `while`。 +* 用在表达式和类型中的关键字:`as`、`catch`、`dynamicType`、`false`、`is`、`nil`、`rethrows`、`super`、`self`、`Self`、`throw`、`throws`、`true`、`try`、`#column`、`#file`、`#function` 以及 `#line`。 +* 用在模式中的关键字:`_`。 +* 以井字号 (`#`) 开头的关键字:`#available`、`#column`、`#else#elseif`、`#endif`、`#file`、`#function`、`#if`、`#line` 以及 `#selector`。 +* 特定上下文中被保留的关键字: `associativity`、`convenience`、`dynamic`、`didSet`、`final`、`get`、`infix`、`indirect`、`lazy`、`left`、`mutating`、`none`、`nonmutating`、`optional`、`override`、`postfix`、`precedence`、`prefix`、`Protocol`、`required`、`right`、`set`、`Type`、`unowned`、`weak` 以及 `willSet`。这些关键字在特定上下文之外可以被用做标识符。 + +以下符号被当作保留符号,不能用于自定义运算符: `(`、`)`、`{`、`}`、`[`、`]`、`.`、`,`、`:`、`;`、`=`、`@`、`#`、`&`(作为前缀运算符)、`->`、`` ` ``、`?`、`!`(作为后缀运算符)。 ## 字面量 -字面量是用来表示源码中某种特定类型的值,比如一个数字或字符串。 +*字面量 (literal)* 用来表示源码中某种特定类型的值,比如一个数字或字符串。 下面是字面量的一些示例: ```swift -42 // 整型字面量 -3.14159 // 浮点型字面量 -"Hello, world!" // 字符串型字面量 -true // 布尔型字面量 +42 // 整数字面量 +3.14159 // 浮点数字面量 +"Hello, world!" // 字符串字面量 +true // 布尔值字面量 ``` -字面量本身并不包含类型信息。事实上,一个字面量会被解析为拥有无限的精度,然后 Swift 的类型推导会尝试去推导出这个字面量的类型。比如,在 `let x: Int8 = 42` 这个声明中,Swift 使用了显式类型标注(`: Int8`)来推导出 `42` 这个整型字面量的类型是 `Int8`。如果没有可用的类型信息, Swift 则会从标准库中定义的字面量类型中推导出一个默认的类型。整型字面量的默认类型是 `Int`,浮点型字面量的默认类型是 `Double`,字符串型字面量的默认类型是 `String`,布尔型字面量的默认类型是 `Bool`。比如,在 `let str = "Hello, world"` 这个声明中,字符串 `"Hello, world"`的默认推导类型就是 `String`。 +字面量本身并不包含类型信息。事实上,一个字面量会被解析为拥有无限的精度,然后 Swift 的类型推导会尝试去推导出这个字面量的类型。比如,在 `let x: Int8 = 42` 这个声明中,Swift 使用了显式类型标注(`: Int8`)来推导出 `42` 这个整数字面量的类型是 `Int8`。如果没有可用的类型信息, Swift 则会从标准库中定义的字面量类型中推导出一个默认的类型。整数字面量的默认类型是 `Int`,浮点数字面量的默认类型是 `Double`,字符串字面量的默认类型是 `String`,布尔值字面量的默认类型是 `Bool`。比如,在 `let str = "Hello, world"` 这个声明中,字符串 `"Hello, world"` 的默认推导类型就是 `String`。 -当为一个字面量值指定了类型标注的时候,这个注解的类型必须能通过这个字面量值实例化后得到。也就是说,这个类型必须遵守这些 Swift 标准库协议中的一个:整型字面量的`IntegerLiteralConvertible`协议、符点型字面量的`FloatingPointLiteralConvertible`协议、字符串字面量的`StringLiteralConvertible`协议以及布尔型字面量的`BooleanLiteralConvertible`协议。比如,`Int8` 遵守了 `IntegerLiteralConvertible`协议,因此它能在 `let x: Int8 = 42` 这个声明中作为整型字面量 `42` 的类型标注。 +当为一个字面量值指定了类型标注的时候,这个标注的类型必须能通过这个字面量值实例化。也就是说,这个类型必须符合这些 Swift 标准库协议中的一个:整数字面量的 `IntegerLiteralConvertible` 协议、浮点数字面量的 `FloatingPointLiteralConvertible` 协议、字符串字面量的 `StringLiteralConvertible` 协议以及布尔值字面量的 `BooleanLiteralConvertible` 协议。比如,`Int8` 符合 `IntegerLiteralConvertible` 协议,因此它能在 `let x: Int8 = 42` 这个声明中作为整数字面量 `42` 的类型标注。 > 字面量语法 -> *字面量* → [*数字型字面量*](#numeric_literal) | [*字符串型字面量*](#string_literal) | [*布尔型字面量*](#boolean_literal) | [*nil型字面量*](#nil_literal) - -> *数字型字面量* → -可选[*整型字面量*](#integer_literal) | -可选[*符点型字面量*](#floating_point_literal) -> *布尔型字面量* → **true** | **false** -> *nil型字面量* → **nil** +> *字面量* → [*数值字面量*](#numeric-literal) | [*字符串字面量*](#string-literal) | [*布尔值字面量*](#boolean-literal) | [*nil 字面量*](#nil-literal) -### 整型字面量 + +> *数值字面量* → **-**可选 [*整数字面量*](#integer-literal) | **-**可选 [*浮点数字面量*](#floating-point-literal) +> +> *布尔值字面量* → **true** | **false** +> +> *nil 字面量* → **nil** -整型字面量(*integer literals*)表示未指定精度整型数的值。整型字面量默认用十进制表示,可以加前缀来指定其他的进制,二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。 + +### 整数字面量 -十进制字面量包含数字 `0` 至 `9`。二进制字面量只包含 `0` 或 `1`,八进制字面量包含数字 `0` 至 `7`,十六进制字面量包含数字 `0` 至 `9` 以及字母 `A` 至 `F` (大小写均可)。 +*整数字面量 (Integer Literals)* 表示未指定精度整数的值。整数字面量默认用十进制表示,可以加前缀来指定其他的进制。二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。 -负整数的字面量在整型字面量前加减号 `-`,比如 `-42`。 +十进制字面量包含数字 `0` 至 `9`。二进制字面量只包含 `0` 或 `1`,八进制字面量包含数字 `0` 至 `7`,十六进制字面量包含数字 `0` 至 `9` 以及字母 `A` 至 `F`(大小写均可)。 -整型字面面可以使用下划线 `_` 来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。 +负整数的字面量在整数字面量前加负号 `-`,比如 `-42`。 -除非特别指定,整型字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数类型](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID323)。 +整型字面面可以使用下划线 (`_`) 来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,这同样也会被系统所忽略,并不会影响字面量的值。 -> 整型字面量语法 -> *整型字面量* → [*二进制字面量*](#binary_literal) -> *整型字面量* → [*八进制字面量*](#octal_literal) -> *整型字面量* → [*十进制字面量*](#decimal_literal) -> *整型字面量* → [*十六进制字面量*](#hexadecimal_literal) - -> *二进制字面量* → **0b** [*二进制数字*](#binary_digit) [*二进制字面量字符组*](#binary_literal_characters)可选 +除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../chapter2/01_The_Basics.html#integers)。 + +> 整数字面量语法 + + +> *整数字面量* → [*二进制字面量*](#binary-literal) +> *整数字面量* → [*八进制字面量*](#octal-literal) +> *整数字面量* → [*十进制字面量*](#decimal-literal) +> *整数字面量* → [*十六进制字面量*](#hexadecimal-literal) + + +> *二进制字面量* → **0b** [*二进制数字*](#binary-digit) [*二进制字面量字符组*](#binary-literal-characters)可选 +> > *二进制数字* → 数值 0 到 1 -> *二进制字面量字符* → [*二进制数字*](#binary_digit) | _ -> *二进制字面量字符组* → [*二进制字面量字符*](#binary_literal_character) [*二进制字面量字符组*](#binary_literal_characters)可选 - -> *八进制字面量* → **0o** [*八进字数字*](#octal_digit) [*八进制字符列表*](#octal_literal_characters)可选 +> +> *二进制字面量字符* → [*二进制数字*](#binary-digit) | _ +> +> *二进制字面量字符组* → [*二进制字面量字符*](#binary-literal-character) [*二进制字面量字符组*](#binary-literal-characters)可选 + + +> *八进制字面量* → **0o** [*八进字数字*](#octal-digit) [*八进制字符组*](#octal-literal-characters)可选 +> > *八进字数字* → 数值 0 到 7 -> *八进制字符* → [*八进字数字*](#octal_digit) | _ -> *八进制字符组* → [*八进制字符*](#octal_literal_character) [*八进制字符列表*](#octal_literal_characters)可选 - -> *十进制字面量* → [*十进制数字*](#decimal_digit) [*十进制字符组*](#decimal_literal_characters)可选 +> +> *八进制字符* → [*八进字数字*](#octal-digit) | _ +> +> *八进制字符组* → [*八进制字符*](#octal-literal-character) [*八进制字符组*](#octal-literal-characters)可选 + + +> *十进制字面量* → [*十进制数字*](#decimal-digit) [*十进制字符组*](#decimal-literal-characters)可选 +> > *十进制数字* → 数值 0 到 9 -> *十进制数字列表* → [*十进制数字*](#decimal_digit) [*十进制数字列表*](#decimal_digits)可选 -> *十进制字符* → [*十进制数字*](#decimal_digit) | _ -> *十进制字符列表* → [*十进制字符*](#decimal_literal_character) [*十进制字符列表*](#decimal_literal_characters)可选 - -> *十六进制字面量* → **0x** [*十六进制数字*](#hexadecimal_digit) [*十六进制字面量字符列表*](#hexadecimal_literal_characters)可选 +> +> *十进制数字组* → [*十进制数字*](#decimal-digit) [*十进制数字组*](#decimal-digits)可选 +> +> *十进制字符* → [*十进制数字*](#decimal-digit) | _ +> +> *十进制字符组* → [*十进制字符*](#decimal-literal-character) [*十进制字符组*](#decimal-literal-characters)可选 + + +> *十六进制字面量* → **0x** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)可选 +> > *十六进制数字* → 数值 0 到 9, 字母 a 到 f, 或 A 到 F -> *十六进制字符* → [*十六进制数字*](#hexadecimal_digit) | _ -> *十六进制字面量字符列表* → [*十六进制字符*](#hexadecimal_literal_character) [*十六进制字面量字符列表*](#hexadecimal_literal_characters)可选 +> +> *十六进制字符* → [*十六进制数字*](#hexadecimal-digit) | _ +> +> *十六进制字面量字符组* → [*十六进制字符*](#hexadecimal-literal-character) [*十六进制字面量字符组*](#hexadecimal-literal-characters)可选 -### 浮点型字面量 + +### 浮点数字面量 -浮点型字面量(*floating-point literals*)表示未指定精度浮点数的值。 +*浮点数字面量 (Floating-point literals)* 表示未指定精度浮点数的值。 -浮点型字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀 `0x`)。 +浮点数字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀 `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`。 +十进制浮点数字面量由十进制数字串后跟小数部分或指数部分(或两者皆有)组成。十进制小数部分由小数点 (`.`) 后跟十进制数字串组成。指数部分由大写或小写字母 `e` 为前缀后跟十进制数字串组成,这串数字表示 `e` 之前的数量乘以 10 的几次方。例如:`1.25e2` 表示 1.25 x 10²,也就是 `125.0`;同样,`1.25e-2` 表示 1.25 x 10¯²,也就是 `0.0125`。 -十六进制浮点型字面量(*hexadecimal floating-point literals*)由前缀 `0x` 后跟可选的十六进制小数部分以及十六进制指数部分组成。十六进制小数部分由小数点后跟十六进制数字串组成。指数部分由大写或小写字母 `p` 为前缀后跟十进制数字串组成,这串数字表示 `p` 之前的数量乘以 2 的几次方。例如:`0xFp2` 表示 `15 ⨉ 2^2`,也就是 `60`;同样,`0xFp-2` 表示 `15 ⨉ 2^-2`,也就是 `3.75`。 +十六进制浮点数字面量由前缀 `0x` 后跟可选的十六进制小数部分以及十六进制指数部分组成。十六进制小数部分由小数点后跟十六进制数字串组成。指数部分由大写或小写字母 `p` 为前缀后跟十进制数字串组成,这串数字表示 `p` 之前的数量乘以 2 的几次方。例如:`0xFp2` 表示 15 x 2²,也就是 `60`;同样,`0xFp-2` 表示 15 x 2¯²,也就是 `3.75`。 -负的浮点型字面量由一元运算符减号 `-` 和浮点型字面量组成,例如 `-42.5`。 +负数的浮点数字面量由负号 (`-`) 和浮点数字面量组成,例如 `-42.5`。 -浮点型字面量允许使用下划线 `_` 来增强数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。 +浮点数字面量允许使用下划线 (`_`) 来增强数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。 -除非特别指定,浮点型字面量的默认推导类型为 Swift 标准库类型中的 `Double`,表示64位浮点数。Swift 标准库也定义了 `Float` 类型,表示32位浮点数。 +除非特别指定,浮点数字面量的默认推导类型为 Swift 标准库类型中的 `Double`,表示 64 位浮点数。Swift 标准库也定义了 `Float` 类型,表示 32 位浮点数。 -> 浮点型字面量语法 -> *浮点数字面量* → [*十进制字面量*](#decimal_literal) [*十进制分数*](#decimal_fraction)可选 [*十进制指数*](#decimal_exponent)可选 -> *浮点数字面量* → [*十六进制字面量*](#hexadecimal_literal) [*十六进制分数*](#hexadecimal_fraction)可选 [*十六进制指数*](#hexadecimal_exponent) - -> *十进制分数* → **.** [*十进制字面量*](#decimal_literal) -> *十进制指数* → [*浮点数e*](#floating_point_e) [*正负号*](#sign)可选 [*十进制字面量*](#decimal_literal) - -> *十六进制分数* → **.** [*十六进制数字*](#hexadecimal_digit) [*十六进制字面量字符列表*](#hexadecimal_literal_characters)可选 -> *十六进制指数* → [*浮点数p*](#floating_point_p) [*正负号*](#sign)可选 [*十进制字面量*](#decimal_literal) - -> *浮点数e* → **e** | **E** -> *浮点数p* → **p** | **P** +> 浮点数字面量语法 + + +> *浮点数字面量* → [*十进制字面量*](#decimal-literal) [*十进制分数*](#decimal-fraction)可选 [*十进制指数*](#decimal-exponent)可选 +> *浮点数字面量* → [*十六进制字面量*](#hexadecimal-literal) [*十六进制分数*](#hexadecimal-fraction)可选 [*十六进制指数*](#hexadecimal-exponent) + + +> *十进制分数* → **.** [*十进制字面量*](#decimal-literal) +> +> *十进制指数* → [*十进制指数 e*](#floating-point-e) [*正负号*](#sign)可选 [*十进制字面量*](#decimal-literal) + + +> *十六进制分数* → **.** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)可选 +> +> *十六进制指数* → [*十六进制指数 p*](#floating-point-p) [*正负号*](#sign)可选 [*十进制字面量*](#decimal-literal) + + +> *十进制指数 e* → **e** | **E** +> +> *十六进制指数 p* → **p** | **P** +> > *正负号* → **+** | **-** + +### 字符串字面量 -### 字符串型字面量 +字符串字面量由被包在双引号中的一串字符组成,形式如下: -字符串型字面量(*string literal*)由被包在双引号中的一串字符组成,形式如下: +> "`字符`" -``` -"characters" -``` - -字符串型字面量中不能包含未转义的双引号 (`"`)、未转义的反斜线(`\`)、回车符(*carriage return*)或换行符(*line feed*)。 +字符串字面量中不能包含未转义的双引号(`"`)、未转义的反斜线(`\`)、回车符、换行符。 可以在字符串字面量中使用的转义特殊符号如下: -* 空字符(Null Character)`\0` -* 反斜线(Backslash)`\\` -* 水平制表符(Horizontal Tab)`\t` -* 换行符(Line Feed)`\n` -* 回车符(Carriage Return)`\r` -* 双引号(Double Quote)`\"` -* 单引号(Single Quote)`\'` -* Unicode标量 `\u{n}`,n为一到八位的十六进制数字 +* 空字符 `\0` +* 反斜线 `\\` +* 水平制表符 `\t` +* 换行符 `\n` +* 回车符 `\r` +* 双引号 `\"` +* 单引号 `\'` +* Unicode 标量 `\u{`n`}`,n 为一到八位的十六进制数字 -字符串字面量允许在反斜杠小括号 `\()` 中插入表达式的值。插入表达式(*interpolated expression*)不能包含未转义的双引号 `"`、未转义的反斜线 `\`、回车符或者换行符。表达式结果的类型必须在 *String* 类中有对应的初始化方法。 +字符串字面量允许在反斜杠 (`\`) 后的括号 `()` 中插入表达式的值。插入表达式可以包含字符串字面量,但不能包含未转义的双引号 (`"`)、未转义的反斜线 (`\`)、回车符以及换行符。 例如,以下所有字符串字面量的值都是相同的: -``` +```swift "1 2 3" +"1 2 \("3")" "1 2 \(3)" "1 2 \(1 + 2)" let x = 3; "1 2 \(x)" ``` -字符串字面量的默认推导类型为 `String`。组成字符串的字符默认推导类型为 `Character`。更多有关 `String` 和 `Character` 的信息请参照 [字符串和字符](../chapter2/03_Strings_and_Characters.html) 以及[字符串结构参考](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/doc/uid/TP40015181)。 +字符串字面量的默认推导类型为 `String`。更多有关 `String` 类型的信息请参考 [字符串和字符](../chapter2/03_Strings_and_Characters.html) 以及 [*String Structure Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/doc/uid/TP40015181)。 -用 `+` 操作符连接的字符型字面量是在编译时进行连接的。比如下面的 `textA` 和 `textB` 时完全一样的—— `textA` 没有任何运行时的连接操作。 +用 `+` 操作符连接的字符型字面量是在编译时进行连接的。比如下面的 `textA` 和 `textB` 是完全一样的,`textA` 没有任何运行时的连接操作。 -``` +```swift let textA = "Hello " + "world" let textB = "Hello world" ``` -> 字符型字面量语法 -> *字符串字面量* → **"** [*引用文本*](#quoted_text)可选 **"** - -> *引用文本* → [*引用文本条目*](#quoted_text_item) [*引用文本*](#quoted_text) 可选 -> *引用文本条目* → [*转义字符*](#escaped_character) -> *引用文本条目* → **\(** [*表达式*](./04_Expressions.html) **)** -> *引用文本条目* → **除了"­, \­, U+000A, 或者 U+000D的所有Unicode的字符** -> *转义字符* → **\0** | **\\** | **\t** | **\n** | **\r** | **\"** | **\'** -> *转义字符* → **\u {** [*unicode标量数字*](#unicode_scalar_digits) **}** -> *unicode标量数字* → 一到八位的十六进制数字 +> 字符串字面量语法 + + +> *字符串字面量* → [*静态字符串字面量*](#static-string-literal) | [*插值字符串字面量*](#interpolated-string-literal) + + +> *静态字符串字面量* → **"**[*引用文本*](#quoted-text)可选**"** +> +> *引用文本* → [*引用文本项*](#quoted-text-item) [*引用文本*](#quoted-text)可选 +> +> *引用文本项* → [*转义字符*](#escaped-character) +> *引用文本项* → 除了 **"**、**\\**、U+000A、U+000D 以外的所有 Unicode 字符 + + +> *插值字符串字面量* → **"**[*插值文本*](#interpolated-text)可选**"** +> +> *插值文本* → [*插值文本项*](#interpolated-text-item) [*插值文本*](#interpolated-text)可选 +> +> *插值文本项* → **\\****(**[*表达式*](./04_Expressions.html)**)** | [*引用文本项*](#quoted-text-item) + + +> *转义字符* → **\\****0** | **\\****\\** | **\t** | **\n** | **\r** | **\\"** | **\\'** +> *转义字符* → **\u {** [*unicode 标量数字*](#unicode-scalar-digits) **}** +> +> *unicode 标量数字* → 一到八位的十六进制数字 ## 运算符 Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基础运算符](../chapter2/02_Basic_Operators.html) 和 [高级运算符](../chapter2/25_Advanced_Operators.html) 中进行了阐述。这一小节将描述哪些字符能用于自定义运算符。 -自定义运算符可以由以下其中之一的 ASCII 字符 `/`、`=`、 `-`、`+`、`!`、`*`、`%`、`<`、`>`、`&`、`|`、`^`、`?` 以及 `~`, 或者后面语法中规定的任一个 Unicode 字符开始。在第一个字符之后,允许使用组合型 Unicode 字符。也可以使用两个或者多个的点号来自定义运算符(比如, `....`)。虽然可以自定义包含问号`?`的运算符,但是这个运算符不能只包含单独的一个问号。 +自定义运算符可以由以下其中之一的 ASCII 字符 `/`、`=`、 `-`、`+`、`!`、`*`、`%`、`<`、`>`、`&`、`|`、`^`、`?` 以及 `~`,或者后面语法中规定的任一个 Unicode 字符(其中包含了*数学运算符*、*零散符号(Miscellaneous Symbols)* 以及印刷符号 (Dingbats) 之类的 Unicode 块)开始。在第一个字符之后,允许使用组合型 Unicode 字符。 - 注意: - 以下这些标记 =, ->, //, /*, */, ., <(前缀运算符), &, and ?, ?(中缀运算符), >(后缀运算符), ! 以及 ? 是被系统保留的。这些标记不能被重载,也不能用于自定义操作符。 +您也可以以点号 (`.`) 开头来定义自定义运算符。这些运算符可以包含额外的点,例如 `.+.`。如果某个运算符不是以点号开头的,那么它就无法再包含另外的点号了。例如,`+.+` 就会被看作为一个 `+` 运算符后面跟着一个 `.+` 运算符。 -运算符两侧的空白被用来区分该运算符是否为前缀运算符(*prefix operator*)、后缀运算符(*postfix operator*)或二元运算符(*binary operator*)。规则总结如下: +虽然您可以用问号 `?` 来自定义运算符,但是这个运算符不能只包含单独的一个问号。此外,虽然运算符可以包含一个惊叹号 `!`,但是前缀运算符不能够以问号或者惊叹号开头。 -* 如果运算符两侧都有空白或两侧都无空白,将被看作二元运算符。例如:`a+b` 和 `a + b` 中的运算符 `+` 被看作二元运算符。 -* 如果运算符只有左侧空白,将被看作前缀一元运算符。例如 `a ++b` 中的 `++` 被看作前缀一元运算符。 -* 如果运算符只有右侧空白,将被看作后缀一元运算符。例如 `a++ b` 中的 `++` 被看作后缀一元运算符。 -* 如果运算符左侧没有空白并紧跟 `.`,将被看作后缀一元运算符。例如 `a++.b` 中的 `++` 被看作后缀一元运算符(即上式被视为 `a++ .b` 而非 `a ++ .b`)。 +> 注意 +> 以下这些标记 `=`、`->`、`//`、`/*`、`*/`、`.`、`<`(前缀运算符)、`&`、`?`、`?`(中缀运算符)、`>`(后缀运算符)、`!` 、`?` 是被系统保留的。这些符号不能被重载,也不能用于自定义运算符。 -鉴于这些规则,运算符前的字符 `(`、`[` 和 `{` ;运算符后的字符 `)`、`]` 和 `}` 以及字符 `,`、`;` 和 `:` 都被视为空白。 +运算符两侧的空白被用来区分该运算符是否为前缀运算符、后缀运算符或二元运算符。规则总结如下: -以上规则需注意一点,如果预定义运算符 `!` 或 `?` 左侧没有空白,则不管右侧是否有空白都将被看作后缀运算符。如果将 `?` 用作可选链(*optional-chaining*)操作符,左侧必须无空白。如果用于条件运算符 `? :`,必须两侧都有空白。 +* 如果运算符两侧都有空白或两侧都无空白,将被看作二元运算符。例如:`a+++b` 和 `a +++ b` 当中的 `+++` 运算符会被看作二元运算符。 +* 如果运算符只有左侧空白,将被看作一元前缀运算符。例如 `a +++b` 中的 `+++` 运算符会被看做是一元前缀运算符。 +* 如果运算符只有右侧空白,将被看作一元后缀运算符。例如 `a+++ b` 中的 `+++` 运算符会被看作是一元后缀运算符。 +* 如果运算符左侧没有空白并紧跟 `.`,将被看作一元后缀运算符。例如 `a+++.b` 中的 `+++` 运算符会被视为一元后缀运算符(即上式被视为 `a+++ .b` 而不是 `a +++ .b`)。 -在某些特定的构造中 ,以 `<` 或 `>` 开头的运算符会被分离成两个或多个标记,剩余部分以同样的方式会被再次分离。因此,在 `Dictionary>` 中没有必要添加空白来消除闭合字符 `>` 的歧义。在这个例子中, 闭合字符 `>` 不会被视为单独的标记,因而不会被误解析为 `>>` 运算符的一部分。 +鉴于这些规则,运算符前的字符 `(`、`[` 和 `{`,运算符后的字符 `)`、`]` 和 `}`,以及字符 `,`、`;` 和 `:` 都被视为空白。 -要学习如何自定义运算符,请参考 [自定义操作符](../chapter2/25_Advanced_Operators.html#custom_operators) 和 [运算符声明](./05_Declarations.html#operator_declaration)。要学习如何重载运算符,请参考 [运算符方法](../chapter2/25_Advanced_Operators.html#operator_functions)。 +以上规则需注意一点,如果预定义运算符 `!` 或 `?` 左侧没有空白,则不管右侧是否有空白都将被看作后缀运算符。如果将 `?` 用作可选链式调用运算符,左侧必须无空白。如果用于条件运算符 `? :`,必须两侧都有空白。 -> 运算符语法语法 -> *运算符* → [*头部运算符*](#operator_head) [*运算符字符组*](#operator_characters)可选 -> *运算符* → [*头部点运算符*](#dot_operator_head) [*点运算符字符组*](#dot_operator_characters)可选 - -> *头部运算符* → **/** | **=** | **+** | **!** |**\*** | **%** |**<** | **>** |**&** | **|** |**/** | **~** | **?** | +在某些特定的设计中 ,以 `<` 或 `>` 开头的运算符会被分离成两个或多个符号,剩余部分可能会以同样的方式被再次分离。因此,在 `Dictionary>` 中没有必要添加空白来消除闭合字符 `>` 的歧义。在这个例子中, 闭合字符 `>` 不会被视为单独的符号,因而不会被错误解析为 `>>` 运算符。 + +要学习如何自定义运算符,请参考 [自定义运算符](../chapter2/25_Advanced_Operators.html#custom_operators) 和 [运算符声明](05_Declarations.html#operator_declaration)。要学习如何重载运算符,请参考 [运算符函数](../chapter2/25_Advanced_Operators.html#operator_functions)。 + +> 运算符语法 + + +> *运算符* → [*头部运算符*](#operator-head) [*运算符字符组*](#operator-characters)可选 +> *运算符* → [*头部点运算符*](#dot-operator-head) [*点运算符字符组*](#dot-operator-characters)可选 + + +> *头部运算符* → **/** | **=** | **-** | **+** | **!** | __*__ | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **?** > *头部运算符* → U+00A1–U+00A7 -> *头部运算符* → U+00A9 or U+00AB -> *头部运算符* → U+00AC or U+00AE -> *头部运算符* → U+00B0–U+00B1, U+00B6, U+00BB, U+00BF, U+00D7, or U+00F7 -> *头部运算符* → U+2016–U+2017 or U+2020–U+2027 +> *头部运算符* → U+00A9 或 U+00AB +> *头部运算符* → U+00AC 或 U+00AE +> *头部运算符* → U+00B0–U+00B1,U+00B6,U+00BB,U+00BF,U+00D7,或 U+00F7 +> *头部运算符* → U+2016–U+2017 或 U+2020–U+2027 > *头部运算符* → U+2030–U+203E > *头部运算符* → U+2041–U+2053 > *头部运算符* → U+2055–U+205E @@ -273,21 +351,28 @@ Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基 > *头部运算符* → U+2E00–U+2E7F > *头部运算符* → U+3001–U+3003 > *头部运算符* → U+3008–U+3030 - -> *运算符字符* → [*头部运算符*](#operator_head) + + +> *运算符字符* → [*头部运算符*](#operator-head) > *运算符字符* → U+0300–U+036F > *运算符字符* → U+1DC0–U+1DFF > *运算符字符* → U+20D0–U+20FF > *运算符字符* → U+FE00–U+FE0F > *运算符字符* → U+FE20–U+FE2F -> *运算符字符* → U+E0100–U+E01EF - -> *运算符字符组* → [*运算符字符*](#operator_character) [*运算符字符组*] (#operator_characters)可选 - +> *运算符字符* → U+E0100–U+E01EF +> +> *运算符字符组* → [*运算符字符*](#operator-character) [*运算符字符组*](#operator-characters)可选 + + > *头部点运算符* → **..** -> *头部点运算符字符* → . | [*运算符字符*](#operator_character) -> *头部点运算符字符组* → [*点运算符字符*](#dot_operator_character) [*点运算符字符组*](#dot_operator_characters)可选 - +> +> *点运算符字符* → **.** | [*运算符字符*](#operator-character) +> +> *点运算符字符组* → [*点运算符字符*](#dot-operator-character) [*点运算符字符组*](#dot-operator-characters)可选 + + > *二元运算符* → [*运算符*](#operator) -> *前置运算符* → [*运算符*](#operator) -> *后置运算符* → [*运算符*](#operator) +> +> *前缀运算符* → [*运算符*](#operator) +> +> *后缀运算符* → [*运算符*](#operator) diff --git a/source/chapter3/03_Types.md b/source/chapter3/03_Types.md index 87cadba5..8f90eb25 100644 --- a/source/chapter3/03_Types.md +++ b/source/chapter3/03_Types.md @@ -8,237 +8,266 @@ > 2.0 > 翻译+校对:[EudeMorgen](https://github.com/EudeMorgen) +> 2.1 +> 翻译:[mmoaay](https://github.com/mmoaay) + 本页包含内容: -- [类型注解(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) +- [类型注解](#type_annotation) +- [类型标识符](#type_identifier) +- [元组类型](#tuple_type) +- [函数类型](#function_type) +- [数组类型](#array_type) +- [字典类型](#dictionary_type) +- [可选类型](#optional_type) +- [隐式解析可选类型](#implicitly_unwrapped_optional_type) +- [协议合成类型](#protocol_composition_type) +- [元类型](#metatype_type) +- [类型继承子句](#type_inheritance_clause) +- [类型推断](#type_inference) -Swift 语言存在两种类型:命名型类型和复合型类型。命名型类型是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义的类MyClass的实例拥有类型MyClass。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。 +Swift 语言存在两种类型:命名型类型和复合型类型。命名型类型是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义的类 MyClass 的实例拥有类型 MyClass。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。 -那些通常被其它语言认为是基本或初级的数据型类型(Data types)——比如表示数字、字符和字符串的类型——实际上就是命名型类型,这些类型在Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照“扩展和扩展声明”章节里讨论的那样,声明一个扩展来增加它们的行为以迎合你程序的需求。 +那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../chapter2/21_Extensions.html) 和 [扩展声明](05_Declarations.html#extension_declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。 -*复合型类型*是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型(Int, (Int, Int))包含两个元素:第一个是命名型类型Int,第二个是另一个复合型类型(Int, Int). +复合型类型是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`。 本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。 > 类型语法 -> *类型* → [*数组类型*](#array_type) | [*字典类型*](../chapter3/03_Types.html#dictionary_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) + +> *类型* → [*数组类型*](#array-type) | [*字典类型*](#dictionary-type) | [*函数类型*](#function-type) | [*类型标识*](#type-identifier) | [*元组类型*](#tuple-type) | [*可选类型*](#optional-type) | [*隐式解析可选类型*](#implicitly-unwrapped-optional-type) | [*协议合成类型*](#protocol-composition-type) | [*元型类型*](#metatype-type) -##类型注解 +## 类型注解 -类型注解显式地指定一个变量或表达式的值。类型注解始于冒号`:`终于类型,比如下面两个例子: +类型注解显式地指定一个变量或表达式的值。类型注解始于冒号 `:` 终于类型,比如下面两个例子: ```swift let someTuple: (Double, Double) = (3.14159, 2.71828) -func someFunction(a: Int){ /* ... */ } +func someFunction(a: Int) { /* ... */ } ``` -在第一个例子中,表达式`someTuple`的类型被指定为`(Double, Double)`。在第二个例子中,函数`someFunction`的参数`a`的类型被指定为`Int`。 +在第一个例子中,表达式 `someTuple` 的类型被指定为 `(Double, Double)`。在第二个例子中,函数 `someFunction` 的参数 `a` 的类型被指定为 `Int`。 -类型注解可以在类型之前包含一个类型特性(type attributes)的可选列表。 +类型注解可以在类型之前包含一个类型特性的可选列表。 > 类型注解语法 -> *类型注解* → **:** [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type) + +> *类型注解* → **:** [*特性列表*](06_Attributes.html#attributes)可选 [*类型*](#type) -##类型标识符 +## 类型标识符 -类型标识符引用命名型类型或者是命名型/复合型类型的别名。 +类型标识符引用命名型类型,还可引用命名型或复合型类型的别名。 -大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符`Int`引用命名型类型`Int`,同样,类型标识符`Dictionary`引用命名型类型`Dictionary`。 +大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 `Int` 引用命名型类型 `Int`,同样,类型标识符 `Dictionary` 引用命名型类型 `Dictionary`。 -在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型/复合型类型的类型别名。比如,在下面的例子中,类型标识符使用`Point`来引用元组`(Int, Int)`: +在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型或复合型类型的类型别名。比如,在下面的例子中,类型标识符使用 `Point` 来引用元组 `(Int, Int)`: ```swift typealias Point = (Int, Int) let origin: Point = (0, 0) ``` -情况二,类型标识符使用dot(`.`)语法来表示在其它模块(modules)或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在`ExampleModule`模块中声明的命名型类型`MyType`: +情况二,类型标识符使用点语法(`.`)来表示在其它模块或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在 `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) +> 类型标识符语法 + +> *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)可选 | [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)可选 **.** [*类型标识符*](#type-identifier) + +> *类型名称* → [*标识符*](02_Lexical_Structure.html#identifier) -##元组类型 +## 元组类型 -元组类型使用逗号隔开并使用括号括起来的0个或多个类型组成的列表。 +元组类型是使用括号括起来的零个或多个类型,类型间用逗号隔开。 -你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号`(:)`组成。“函数和多返回值”章节里有一个展示上述特性的例子。 +你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../chapter2/06_Functions.html#functions_with_multiple_return_values) 章节里有一个展示上述特性的例子。 -`void`是空元组类型`()`的别名。如果括号内只有一个元素,那么该类型就是括号内元素的类型。比如,`(Int)`的类型是`Int`而不是`(Int)`。所以,只有当元组类型包含的元素个数在两个及以上时才可以命名元组元素。 +`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) + +> *元组类型* → **(** [*元组类型主体*](#tuple-type-body)可选 **)** + +> *元组类型主体* → [*元组类型元素列表*](#tuple-type-element-list) **...**可选 + +> *元组类型元素列表* → [*元组类型元素*](#tuple-type-element) | [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list) + +> *元组类型元素* → [*特性列表*](06_Attributes.html#attributes)可选 **inout**可选 [*类型*](#type) | **inout**可选 [*元素名*](#element-name) [*类型注解*](#type-annotation) + +> *元素名* → [*标识符*](02_Lexical_Structure.html#identifier) -##函数类型 +## 函数类型 -函数类型表示一个函数、方法或闭包的类型,它由一个参数类型和返回值类型组成,中间用箭头`->`隔开: +函数类型表示一个函数、方法或闭包的类型,它由参数类型和返回值类型组成,中间用箭头(`->`)隔开: - `parameter type` -> `return type` +> `参数类型` -> `返回值类型` -由于 *参数类型* 和 *返回值类型* 可以是元组类型,所以函数类型支持多参数与多返回值的函数与方法。。 +由于参数类型和返回值类型可以是元组类型,所以函数类型支持多参数与多返回值的函数与方法。 -对于参数类型是空元组类型`()`以及返回值类型为表达式类型的函数类型,你可以对其参数声明使用`autoclosure`(见声明属性章节)。一个自动闭包函数捕获特定表达式上的隐式闭包而非表达式本身。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到在函数体中有使用它的值。以自动闭包函数类型做为参数的例子详见 [自动闭包(Autoclosures)](TODO:添加链接) 。 +你可以对函数参数使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被使用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.html#autoclosures) 。 -函数类型可以拥有一个可变长参数作为参数类型中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点`(...)`组成,如`Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即`Int...`就是`[Int]`。关于使用可变长参数的例子,见章节[Variadic Parameters](TODO:添加链接)。 +函数类型可以拥有一个可变长参数作为参数类型中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.html#variadic_parameters)。 -为了指定一个`in-out`参数,可以在参数类型前加`inout`前缀。但是你不可以对可变长参数或返回值类型使用`inout`。关于In-Out参数的讨论见章节[In-Out参数部分](TODO:添加链接)。 +为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.html#in_out_parameters)。 -柯里化函数(Curried fuction)的函数类型从右向左递归地组成一组。例如,函数类型`Int -> Int -> Int`可以被理解为`Int -> (Int -> Int)`——也就是说,一个函数的参数为`Int`类型,其返回类型是一个参数类型为`Int`返回类型为`Int`的函数类型。关于柯里化函数的讨论见章节[Curried Fuctions](TODO:添加链接)。 +柯里化函数的函数类型从右向左进行组合。例如,函数类型 `Int -> Int -> Int` 可以理解为 `Int -> (Int -> Int)`,也就是说,该函数类型的参数为 `Int` 类型,其返回类型是一个参数类型为 `Int`,返回类型为 `Int` 的函数类型。关于柯里化函数的讨论见章节 [柯里化函数](05_Declarations.html#curried_functions)。 -函数类型若要抛出错误就必须使用`throws`关键字来标记,若要重抛错误则必须使用`rethrows`关键字来标记。`throws`关键字是函数类型的一部分,不抛出函数(nonthrowing function)是抛出函数(throwing function)函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。对于柯里化函数,`throws`关键字只应用于最里层的函数。抛出和重抛函数(rethrowing function)的相关描述见章节[抛出函数与方法](TODO:添加链接)和[重抛函数与方法](TODO:添加链接)。 +函数类型若要抛出错误就必须使用 `throws` 关键字来标记,若要重抛错误则必须使用 `rethrows` 关键字来标记。`throws` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。对于柯里化函数,`throws` 关键字只应用于最里层的函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](05_Declarations.html#throwing_functions_and_methods) 和 [重抛函数与方法](05_Declarations.html#rethrowing_functions_and_methods)。 > 函数类型语法 -> *函数类型* → [*类型*](../chapter3/03_Types.html#type) _抛出_ _可选_ **->** [*类型*](../chapter3/03_Types.html#type) -> *函数类型* → [*类型*](../chapter3/03_Types.html#type)_重抛_ **->** [*类型*](../chapter3/03_Types.html#type) + +> *函数类型* → [*类型*](#type) **throws**可选 **->** [*类型*](#type) +> *函数类型* → [*类型*](#type) **rethrows**可选 **->** [*类型*](#type) -##数组类型 +## 数组类型 + +Swift 语言为标准库中定义的 `Array` 类型提供了如下语法糖: + +> [`类型`] -Swift语言中使用[`type`]来简化标准库中定义`Array`类型的操作。 换句话说,下面两个声明是等价的: ```swift -let someArray: [String] = ["Alex", "Brian", "Dave"] let someArray: Array = ["Alex", "Brian", "Dave"] +let someArray: [String] = ["Alex", "Brian", "Dave"] ``` -上面两种情况下,常量`someArray`都被声明为字符串数组。数组的元素也可以通过`[]`获取访问:`someArray[0]`是指第0个元素`“Alex”`。 -你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组。 +上面两种情况下,常量 `someArray` 都被声明为字符串数组。数组的元素也可以通过下标访问:`someArray[0]` 是指第 0 个元素 `"Alex"`。 + +你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组: ```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](TODO:添加链接)。 +访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的例子中,`array3D[0]` 是 `[[1, 2], [3, 4]]`,`array3D[0][1]` 是 `[3, 4]`,`array3D[0][1][1]` 则是 `4`。 + +关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.html#arrays)。 > 数组类型语法 -> *数组类型* → [*类型*](../chapter3/03_Types.html#type) + +> *数组类型* → **[** [*类型*](#type) **]** -##字典类型 +## 字典类型 + +Swift 语言为标准库中定义的 `Dictionary` 类型提供了如下语法糖: + +> [`键类型` : `值类型`] -Swift语言中使用[`key type: value type`]来简化标准库中定义`Dictionary`类型的操作。 换句话说,下面两个声明是等价的: ```swift let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39] let someDictionary: Dictionary = ["Alex": 31, "Paul": 39] ``` -上面两种情况,常量`someDictionary`被声明为一个字典,其中键为String类型,值为Int类型。 -字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]`返回键`Alex`对应的值。如果键在字典中不存在的话,则这个下标返回`nil`。 +上面两种情况,常量 `someDictionary` 被声明为一个字典,其中键为 `String` 类型,值为 `Int` 类型。 -字典中键的类型必须遵循Swift标准库中的可哈希协议。 +字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。如果键在字典中不存在的话,则这个下标返回 `nil`。 -关于Swift标准库中`Dictionary`类型的更多细节可查看章节[Dictionaries](TODO:添加链接)。 +字典中键的类型必须符合 Swift 标准库中的 `Hashable` 协议。 + +关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../chapter2/04_Collection_Types.html#dictionaries)。 > 字典类型语法 -> *字典类型* → **[**[*类型*](../chapter3/03_Types.html#type) **:** [*类型*](../chapter3/03_Types.html#type) **]** + +> *字典类型* → **[** [*类型*](#type) **:** [*类型*](#type) **]** -##可选类型 +## 可选类型 -Swift定义后缀`?`来作为标准库中的定义的命名型类型`Optional`的简写。换句话说,下面两个声明是等价的: +Swift 定义后缀 `?` 来作为标准库中的定义的命名型类型 `Optional` 的语法糖。换句话说,下面两个声明是等价的: ```swift var optionalInteger: Int? var optionalInteger: Optional ``` -在上述两种情况下,变量`optionalInteger`都被声明为可选整型类型。注意在类型和`?`之间没有空格。 -类型`Optional`是一个枚举,有两种形式,`None`和`Some(T)`,又来代表可能出现或可能不出现的值。任意类型都可以被显式的声明(或隐式的转换)为可选类型。当声明一个可选类型时,确保使用括号给`?`提供合适的作用范围。比如说,声明一个整型的可选数组,应写作`(Int[])?`,写成`Int[]?`的话则会出错。 +在上述两种情况下,变量 `optionalInteger` 都被声明为可选整型类型。注意在类型和 `?` 之间没有空格。 -如果你在声明或定义可选变量或特性的时候没有提供初始值,它的值则会自动赋成缺省值`nil`。 +类型 `Optional` 是一个枚举,有两个成员,`None` 和 `Some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明或定义可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`。 -如果一个可选类型的实例包含一个值,那么你就可以使用后缀操作符`!`来获取该值,正如下面描述的: +如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 `!` 来获取该值,正如下面描述的: ```swift optionalInteger = 42 -optionalInteger! // 42 +optionalInteger! // 42 ``` -使用`!`操作符获取值为`nil`的可选项会导致运行错误(runtime error)。 -你也可以使用可选链和可选绑定来选择性的执行可选表达式上的操作。如果值为`nil`,不会执行任何操作因此也就没有运行错误产生。 +使用 `!` 运算符解包值为 `nil` 的可选值会导致运行错误。 -更多细节以及更多如何使用可选类型的例子,见章节[Optionals](TODO:添加链接)。 +你也可以使用可选链式调用和可选绑定来选择性地在可选表达式上执行操作。如果值为 `nil`,不会执行任何操作,因此也就没有运行错误产生。 + +更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../chapter2/01_The_Basics.html#optionals)。 > 可选类型语法 -> *可选类型* → [*类型*](../chapter3/03_Types.html#type) **?** + +> *可选类型* → [*类型*](#type) **?** -##隐式解析可选类型 +## 隐式解析可选类型 -Swift语言定义后缀`!`作为标准库中命名类型`ImplicitlyUnwrappedOptional`的简写。换句话说,下面两个声明等价: +Swift 语言定义后缀 `!` 作为标准库中命名类型 `ImplicitlyUnwrappedOptional` 的语法糖。换句话说,下面两个声明等价: ```swift var implicitlyUnwrappedString: String! var implicitlyUnwrappedString: ImplicitlyUnwrappedOptional ``` -上述两种情况下,变量`implicitlyUnwrappedString`被声明为一个隐式解析可选类型的字符串。注意类型与`!`之间没有空格。 -你可以在使用可选类型的地方同样使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选特性,反之亦然。 +上述两种情况下,变量 `implicitlyUnwrappedString` 被声明为一个隐式解析可选类型的字符串。注意类型与 `!` 之间没有空格。 -有了可选,你在声明隐式解析可选变量或特性的时候就不用指定初始值,因为它有缺省值`nil`。 +你可以在使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。 -由于隐式解析可选的值会在使用时自动解析,所以没必要使用操作符`!`来解析它。也就是说,如果你使用值为`nil`的隐式解析可选,就会导致运行错误。 +正如可选类型一样,你在声明隐式解析可选类型的变量或属性的时候也不用指定初始值,因为它有默认值 `nil`。 -使用可选链会选择性的执行隐式解析可选表达式上的某一个操作。如果值为`nil`,就不会执行任何操作,因此也不会产生运行错误。 +由于隐式解析可选类型的值会在使用时自动解析,所以没必要使用操作符 `!` 来解析它。也就是说,如果你使用值为 `nil` 的隐式解析可选类型,就会导致运行错误。 -关于隐式解析可选的更多细节,见章节[Implicitly Unwrapped Optionals](TODO:添加链接)。 +可以使用可选链式调用来在隐式解析可选表达式上选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。 -> 隐式解析可选类型(Implicitly Unwrapped Optional Type)语法 -> *隐式解析可选类型* → [*类型*](../chapter3/03_Types.html#type) **!** +关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../chapter2/01_The_Basics.html#implicityly_unwrapped_optionals)。 + +> 隐式解析可选类型语法 + +> *隐式解析可选类型* → [*类型*](#type) **!** -##协议合成类型 +## 协议合成类型 -协议合成类型是一种遵循具体协议列表中每个协议的类型。协议合成类型可能会用在类型注解和泛型参数中。 +协议合成类型是一种符合协议列表中每个指定协议的类型。协议合成类型可能会用在类型注解和泛型参数中。 协议合成类型的形式如下: -```swift -protocol -``` +> protocol<`Protocol 1`, `Procotol 2`> -协议合成类型允许你指定一个值,其类型遵循多个协议的条件且不需要定义一个新的命名型协议来继承其它想要遵循的各个协议。比如,协议合成类型`protocol`等效于一个从`Protocol A`,`Protocol B`, `Protocol C`继承而来的新协议`Protocol D`,很显然这样做有效率的多,甚至不需引入一个新名字。 +协议合成类型允许你指定一个值,其类型符合多个协议的要求且不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `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) + +> *协议合成类型* → **protocol** **<** [*协议标识符列表*](#protocol-identifier-list)可选 **>** + +> *协议标识符列表* → [*协议标识符*](#protocol-identifier) | [*协议标识符*](#protocol-identifier) **,** [*协议标识符列表*](#protocol-identifier-list) + +> *协议标识符* → [*类型标识符*](#type-identifier) -##元类型 +## 元类型 -元类型是指所有类型的类型,包括类、结构体、枚举和协议。 +元类型是指类型的类型,包括类类型、结构体类型、枚举类型和协议类型。 -类、结构体或枚举类型的元类型是相应的类型名紧跟`.Type`。协议类型的元类型——并不是运行时遵循该协议的具体类型——是该协议名字紧跟`.Protocol`。比如,类`SomeClass`的元类型就是`SomeClass.Type`,协议`SomeProtocol`的元类型就是`SomeProtocal.Protocol`。 +类、结构体或枚举类型的元类型是相应的类型名紧跟 `.Type`。协议类型的元类型——并不是运行时符合该协议的具体类型——而是该协议名字紧跟 `.Protocol`。比如,类 `SomeClass` 的元类型就是 `SomeClass.Type`,协议 `SomeProtocol` 的元类型就是 `SomeProtocal.Protocol`。 -你可以使用后缀`self`表达式来获取类型。比如,`SomeClass.self`返回`SomeClass`本身,而不是`SomeClass`的一个实例。同样,`SomeProtocol.self`返回`SomeProtocol`本身,而不是运行时遵循`SomeProtocol`的某个类型的实例。还可以对类型的实例使用`dynamicType`表达式来获取该实例在运行阶段的类型,如下所示: +你可以使用后缀 `self` 表达式来获取类型。比如,`SomeClass.self` 返回 `SomeClass` 本身,而不是 `SomeClass` 的一个实例。同样,`SomeProtocol.self` 返回 `SomeProtocol` 本身,而不是运行时符合 `SomeProtocol` 的某个类型的实例。还可以对类型的实例使用 `dynamicType` 表达式来获取该实例在运行阶段的类型,如下所示: ```swift class SomeBaseClass { @@ -252,26 +281,26 @@ class SomeSubClass: SomeBaseClass { } } let someInstance: SomeBaseClass = SomeSubClass() -// someInstance is of type SomeBaseClass at compile time, but -// someInstance is of type SomeSubClass at runtime +// someInstance 在编译期是 SomeBaseClass 类型, +// 但是在运行期则是 SomeSubClass 类型 someInstance.dynamicType.printClassName() -// prints "SomeSubClass +// 打印 “SomeSubClass” ``` -可以使用恒等运算符(`===`和 `!==`)来测试一个实例的运行时类型和它的编译时类型是否一致。 +可以使用恒等运算符(`===` 和 `!==`)来测试一个实例的运行时类型和它的编译时类型是否一致。 -``` -if someInstance.dynamicType === someInstance.self { +```swift +if someInstance.dynamicType === SomeBaseClass.self { print("The dynamic type of someInstance is SomeBaseCass") } else { print("The dynamic type of someInstance isn't SomeBaseClass") } -// prints "The dynamic type of someInstance isn't SomeBaseClass" +// 打印 “The dynamic type of someInstance isn't SomeBaseClass” ``` -可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,必须使用 `required` 关键字标记被调用的构造器,或者使用 `final` 关键字标记整个类。 +可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,被调用的构造器必须使用 `required` 关键字标记,或者整个类使用 `final` 关键字标记。 -``` +```swift class AnotherSubClass: SomeBaseClass { let string: String required init(string: String) { @@ -285,39 +314,43 @@ let metatype: AnotherSubClass.Type = AnotherSubClass.self let anotherInstance = metatype.init(string: "some string") ``` -> 元(Metatype)类型语法 -> *元类型* → [*类型*](../chapter3/03_Types.html#type) **.** **Type** | [*类型*](../chapter3/03_Types.html#type) **.** **Protocol** +> 元类型语法 + +> *元类型* → [*类型*](#type) **.** **Type** | [*类型*](#type) **.** **Protocol** -##类型继承子句 +## 类型继承子句 -类型继承子句被用来指定一个命名型类型继承的哪个类、遵循的哪些协议。类型继承子句也用来指定一个类需要遵循的协议。类型继承子句开始于冒号`:`,其后是类所需遵循的协议或者类型标识符列表或者两者均有。 +类型继承子句被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句也用来指定一个类类型专属协议。类型继承子句开始于冒号 `:`,其后是类的超类或者一系列类型标识符。 -类可以继承单个超类,遵循任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要遵循的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,见章节Inheritance。 +类可以继承单个超类,采纳任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要采纳的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../chapter2/13_Inheritance.html)。 -其它命名型类型可能只继承或遵循一个协议列表。协议类型可能继承于其它任意数量的协议。当一个协议类型继承于其它协议时,其它协议的条件集合会被整合在一起,然后其它从当前协议继承的任意类型必须遵循所有这些条件。正如在协议声明中所讨论的那样,可以把类的关键字放到类型继承子句中的首位,这样就可以用一个类的条件来标记一个协议声明。 +其它命名型类型可能只继承或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件。正如在 [协议声明](05_Declarations.html#protocol_declaration) 中所讨论的那样,可以把 `class` 关键字放到协议类型的类型继承子句的首位,这样就可以声明一个类类型专属协议。 -枚举定义中的类型继承子句可以是一个协议列表,或是指定原始值的枚举——一个单独的指定原始值类型的命名型类型。使用类型继承子句来指定原始值类型的枚举定义的例子,见章节Raw Values。 +枚举定义中的类型继承子句可以是一系列协议,或是枚举的原始值类型的命名型类型。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../chapter2/08_Enumerations.html#raw_values)。 > 类型继承子句语法 -> *类型继承子句* → **:** [*类需求*](../chapter3/03_Types.html#class_requirement) **,** [*类型继承列表*](../chapter3/03_Types.html#type_inheritance_list) -> *类型继承子句* → **:** [*类需求*](../chapter3/03_Types.html#class_requirement) -> *类型继承子句* → **:** [*类型继承列表*](../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) -> *类需求* → **类** + +> *类型继承子句* → **:** [*类要求*](#class-requirement) **,** [*类型继承列表*](#type-inheritance-list) +> *类型继承子句* → **:** [*类要求*](#class-requirement) +> *类型继承子句* → **:** [*类型继承列表*](#type-inheritance-list) + +> *类型继承列表* → [*类型标识符*](#type-identifier) | [*类型标识符*](#type-identifier) **,** [*类型继承列表*](#type-inheritance-list) + +> *类要求* → **class** -##类型推断 +## 类型推断 -Swift广泛的使用类型推断,从而允许你可以忽略代码中很多变量和表达式的类型或部分类型。比如,对于`var x: Int = 0`,你可以完全忽略类型而简写成`var x = 0`——编译器会正确的推断出`x`的类型`Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以忽略类型的一部分。比如,如果你写了`let dict: Dictionary = ["A": 1]`,编译提也能推断出`dict`的类型是`Dictionary`。 +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`)。 +在上面的两个例子中,类型信息从表达式树的叶子节点传向根节点。也就是说,`var x: Int = 0` 中 `x` 的类型首先根据 `0` 的类型进行推断,然后将该类型信息传递到根节点(变量 `x`)。 -在Swift中,类型信息也可以反方向流动——从根节点传向叶子节点。在下面的例子中,常量`eFloat`上的显式类型注解(`:Float`)导致数字字面量`2.71828`的类型是`Float`而非`Double`。 +在 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. +let e = 2.71828 // e 的类型会被推断为 Double +let eFloat: Float = 2.71828 // eFloat 的类型为 Float ``` -Swift中的类型推断在单独的表达式或语句水平上进行。这意味着所有用于推断类型的信息必须可以从表达式或其某个子表达式的类型检查中获取。 +Swift 中的类型推断在单独的表达式或语句上进行。这意味着所有用于类型推断的信息必须可以从表达式或其某个子表达式的类型检查中获取到。 diff --git a/source/chapter3/04_Expressions.md b/source/chapter3/04_Expressions.md index 801fde0c..26258390 100644 --- a/source/chapter3/04_Expressions.md +++ b/source/chapter3/04_Expressions.md @@ -8,240 +8,306 @@ > 2.0 > 翻译+校对:[EudeMorgen](https://github.com/EudeMorgen) +> 2.1 +> 翻译:[mmoaay](https://github.com/mmoaay) + +> 2.2 +> 校对:[175](https://github.com/Brian175) + +> 3.0 +> 翻译+校对:[chenmingjia](https://github.com/chenmingjia) + 本页包含内容: -- [前缀表达式(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) +- [前缀表达式](#prefix_expressions) + - [try 运算符](#try_operator) +- [二元表达式](#binary_expressions) + - [赋值表达式](#assignment_operator) + - [三元条件运算符](#ternary_conditional_operator) + - [类型转换运算符](#type-casting_operators) +- [基本表达式](#primary_expressions) + - [字面量表达式](#literal_expression) + - [self 表达式](#self_expression) + - [超类表达式](#superclass_expression) + - [闭包表达式](#closure_expression) + - [隐式成员表达式](#implicit_member_expression) + - [圆括号表达式](#parenthesized_expression) + - [通配符表达式](#wildcard_expression) + - [选择器表达式](#selector_expression) +- [后缀表达式](#postfix_expressions) + - [函数调用表达式](#function_call_expression) + - [构造器表达式](#initializer_expression) + - [显式成员表达式](#explicit_member_expression) + - [后缀 self 表达式](#postfix_self_expression) + - [dynamicType 表达式](#dynamic_type_expression) + - [下标表达式](#subscript_expression) + - [强制取值表达式](#forced-Value_expression) + - [可选链表达式](#optional-chaining_expression) -Swift 中存在四种表达式: 前缀(prefix)表达式,二元(binary)表达式,主要(primary)表达式和后缀(postfix)表达式。表达式可以返回一个值,以及运行某些逻辑(causes a side effect)。 +Swift 中存在四种表达式:前缀表达式,二元表达式,基本表达式和后缀表达式。表达式在返回一个值的同时还可以引发副作用。 -前缀表达式和二元表达式就是对某些表达式使用各种运算符(operators)。 主要表达式是最短小的表达式,它提供了获取(变量的)值的一种途径。 后缀表达式则允许你建立复杂的表达式,例如配合函数调用和成员访问。 每种表达式都在下面有详细论述。 +通过前缀表达式和二元表达式可以对简单表达式使用各种运算符。基本表达式从概念上讲是最简单的一种表达式,它是一种访问值的方式。后缀表达式则允许你建立复杂的表达式,例如函数调用和成员访问。每种表达式都在下面有详细论述。 > 表达式语法 -> *表达式* → [*试算子(try operator)*](../chapter3/04_Expressions.html#*) _可选_ | [*前置表达式*](../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) + +> *表达式* → [*try运算符*](#try-operator)可选 [*前缀表达式*](#prefix-expression) [*二元表达式列表*](#binary-expressions)可选 + +> *表达式列表* → [*表达式*](#expression) | [*表达式*](#expression) **,** [*表达式列表*](#expression-list) -## 前缀表达式(Prefix Expressions) +## 前缀表达式 -前缀表达式由可选的前缀符号和表达式组成。(这个前缀符号只能接收一个参数) +前缀表达式由可选的前缀运算符和表达式组成。前缀运算符只接收一个参数,表达式则紧随其后。 -对于这些操作符的使用,请参见: [Basic Operators](TODO:添加链接) 和 [Advanced Operators](TODO:添加链接) +关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/25_Advanced_Operators.md)。 -对于 Swift 标准库提供的操作符的使用,请参见[Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。 +关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Swift Standard Library Operators Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。 -作为对上面标准库运算符的补充,你也可以对 某个函数的参数使用 '&'运算符。 更多信息,请参见: [In-Out parameters](TODO:添加链接). +除了标准库运算符,你也可以对某个变量使用 `&` 运算符,从而将其传递给函数的输入输出参数。更多信息,请参阅 [输入输出参数](../chapter2/06_Functions.html#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) +> 前缀表达式语法 + +> *前缀表达式* → [*前缀运算符*](02_Lexical_Structure.md#prefix-operator)可选 [*后缀表达式*](#postfix-expression) +> *前缀表达式* → [*输入输出表达式*](#in-out-expression) + +> *输入输出表达式* → **&** [*标识符*](02_Lexical_Structure.md#identifier) -## try 操作符(try operator) -try表达式由紧跟在可能会出错的表达式后面的`try`操作符组成,形式如下: - `try expression` -强制的try表示由紧跟在可能会出错的表达式后面的`try!`操作符组成,出错时会产生一个运行时错误,形式如下: - `try! expression` +### try 运算符 -当在二进制运算符左边的表达式被标记上 `try`、`try?` 或者 `try!` 时,这个操作对整个二进制表达式都产生作用。也就是说,你可以使用圆括号来明确操作符的应用范围。 +try 表达式由 `try` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下: -``` -sum = try someThrowingFunction() + anotherThrowingFunction() // try 对两个方法调用都产生作用 -sum = try (someThrowingFunction() + anotherThrowingFunction()) // try 对两个方法调用都产生作用 -sum = (try someThrowingFunction()) + anotherThrowingFunction() // Error: try 只对第一个方法调用产生作用 +> try `可抛出错误的表达式` + +可选的 try 表达式由 `try?` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下: + +> try? `可抛出错误的表达式` + +如果可抛出错误的表达式没有抛出错误,整个表达式返回的可选值将包含可抛出错误的表达式的返回值,否则,该可选值为 `nil`。 + +强制的 try 表达式由 `try!` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下: + +> try! `可抛出错误的表达式` + +如果可抛出错误的表达式抛出了错误,将会引发运行时错误。 + +在二进制运算符左侧的表达式被标记上 `try`、`try?` 或者 `try!` 时,这个运算符对整个二进制表达式都产生作用。也就是说,你可以使用括号来明确运算符的作用范围。 + +```swift +sum = try someThrowingFunction() + anotherThrowingFunction() // try 对两个函数调用都产生作用 +sum = try (someThrowingFunction() + anotherThrowingFunction()) // try 对两个函数调用都产生作用 +sum = (try someThrowingFunction()) + anotherThrowingFunction() // 错误:try 只对第一个函数调用产生作用 ``` -`try` 表达式不能出现在二进制操作符的的右边,除非二进制操作符是赋值操作符或者 `try` 表达式是被圆括号括起来的。 +`try` 表达式不能出现在二进制运算符的的右侧,除非二进制运算符是赋值运算符或者 `try` 表达式是被圆括号括起来的。 -关于`try`、`try?` 和 `try!` 更多的例子和信息请参见:[Error Handling](TODO:添加链接) - -> try表达式语法 -> *try 操作符* → [*try*](LexicalStructure.html#try_operator) | try­? | *try!* +关于 `try`、`try?` 和 `try!` 的更多信息,以及该如何使用的例子,请参阅 [错误处理](../chapter2/18_Error_Handling.html)。 +> try 表达式语法 + +> *try 运算符* → **try** | **try?** | **try!** -## 二元表达式(Binary Expressions) +## 二元表达式 -二元表达式由 "左边参数" + "二元运算符" + "右边参数" 组成, 它有如下的形式: +二元表达式由中缀运算符和左右参数表达式组成。形式如下: -> `left-hand argument` `operator` `right-hand argument` +> `左侧参数` `二元运算符` `右侧参数` -关于这些运算符(operators)的更多信息,请参见:[Basic Operators](TODO:添加链接)和 [Advanced Operators](TODO:添加链接)。 +关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/25_Advanced_Operators.md)。 + +关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Swift Standard Library Operators Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。 > 注意 -> 在解析时, 一个二元表达式表示为一个一级数组(a flat list), 这个数组(List)根据运算符的先后顺序,被转换成了一个tree. 例如: 2 + 3 * 5 首先被认为是: 2, + , `` 3``, *, 5. 随后它被转换成 tree (2 + (3 * 5)) +> 在解析时,一个二元表达式将作为一个扁平列表表示,然后根据运算符的优先级,再进一步进行组合。例如,`2 + 3 * 5` 首先被看作具有五个元素的列表,即 `2`、`+`、`3`、`*`、`5`,随后根据运算符优先级组合为 `(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) _可选_ -> *赋值操作符* - +> *二元表达式* → [*二元运算符*](02_Lexical_Structure.md#binary-operator) [*前缀表达式*](#prefix-expression) +> *二元表达式* → [*赋值运算符*](#assignment-operator) [*try运算符*](#try-operator)可选 [*前缀表达式*](#prefix-expression) +> *二元表达式* → [*条件运算符*](#conditional-operator) [*try运算符*](#try-operator)可选 [*前缀表达式*](#prefix-expression) +> *二元表达式* → [*类型转换运算符*](#type-casting-operator) + +> *二元表达式列表* → [*二元表达式*](#binary-expression) [*二元表达式列表*](#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 +// a 为 "test",b 为 12,c 为 3,9.45 会被忽略 ``` 赋值运算符不返回任何值。 > 赋值运算符语法 + > *赋值运算符* → **=** -## 三元条件运算符(Ternary Conditional Operator) +### 三元条件运算符 -三元条件运算符 是根据条件来获取值。 形式如下: +三元条件运算符会根据条件来对两个给定表达式中的一个进行求值,形式如下: -> `condition` ? `expression used if true` : `expression used if false` +> `条件` ? `表达式(条件为真则使用)` : `表达式(条件为假则使用)` -如果 `condition` 是true, 那么返回 第一个表达式的值(此时不会调用第二个表达式), 否则返回第二个表达式的值(此时不会调用第一个表达式)。 +如果条件为真,那么对第一个表达式进行求值并返回结果。否则,对第二个表达式进行求值并返回结果。未使用的表达式不会进行求值。 -想看三元条件运算符的例子,请参见: Ternary Conditional Operator. +关于使用三元条件运算符的例子,请参阅 [三元条件运算符](../chapter2/02_Basic_Operators.md#ternary_conditional_operator)。 > 三元条件运算符语法 -> *三元条件运算符* → **?** [*表达式*](../chapter3/04_Expressions.html#expression) **:** + +> *三元条件运算符* → **?** [try运算符](#try-operator)可选 [*表达式*](#expression) **:** -## 类型转换运算符(Type-Casting Operators) +### 类型转换运算符 -有4种类型转换运算符: `is`,`as`,`? `和`!`. 它们有如下的形式: +有 4 种类型转换运算符:`is`、`as`、`as? `和`as!`。它们有如下的形式: -> `expression` is `type` -> `expression` as `type` -> `expression` is? `type` -> `expression` as! `type` +> `表达式` is `类型` +`表达式` as `类型` +`表达式` as? `类型` +`表达式` as! `类型` -`is`运算符在程序运行时检查表达式能否向下转化为指定的类型,如果可以在返回`ture`,如果不行,则返回`false`。 +`is` 运算符在运行时检查表达式能否向下转化为指定的类型,如果可以则返回 `ture`,否则返回 `false`。 + +`as` 运算符在编译时执行向上转换和桥接。向上转换可将表达式转换成超类的实例而无需使用任何中间变量。以下表达式是等价的: -`as`运算符在程序编译时执行类型转化,且总是成功,比如进行向上转换(upcast)和桥接(bridging)。向上转换指把表达式转换成类型的超类的一个是实例而不使用中间的变量。以下表达式是等价的: ```swift func f(any: Any) { print("Function for Any") } func f(int: Int) { print("Function for Int") } let x = 10 f(x) -// prints "Function for Int" +// 打印 “Function for Int” let y: Any = x f(y) -// prints "Function for Any" +// 打印 “Function for Any” f(x as Any) -// prints "Function for Any" +// 打印 “Function for Any” ``` -桥接运算可以让你把一个Swift标准库中的类型的表达式作为一个与之相关的基础类(比如NSString)来使用,而不需要新建一个实例。关于桥接的更多实例参见Using Swift with Cocoa and Objective-C中的Cocoa Data Types。 -`as?`操作符为带条件的类型转换。`as?`操作符返回可选的转换类型。在运行时,如果转换成功,表达式的值会被覆盖掉再返回,如果转换不成功的话,则返回`nil`。如果条件转换中的条件的真值一开始就已经确定真伪了,则在编译时会报错。 +桥接可将 Swift 标准库中的类型(例如 `String`)作为一个与之相关的 Foundation 类型(例如 `NSString`)来使用,而不需要新建一个实例。关于桥接的更多信息,请参阅 [*Using Swift with Cocoa and Objective-C (Swift2.2)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中的 [Working with Cocoa Data Types](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)。 -`a!`操作符表示强制转换,其返回指定的类型,而不是可选的类型。如果转换失败,则会出现运行时错误。表达式`x as T` 效果等同于`(x as? T)!`。 +`as?` 运算符有条件地执行类型转换,返回目标类型的可选值。在运行时,如果转换成功,返回的可选值将包含转换后的值,否则返回 `nil`。如果在编译时就能确定转换一定会成功或是失败,则会导致编译报错。 -关于类型转换的更多内容和例子,请参见: [Type Casting](TODO:添加链接). +`as!` 运算符执行强制类型转换,返回目标类型的非可选值。如果转换失败,则会导致运行时错误。表达式 `x as! T` 效果等同于 `(x as? T)!`。 -> 类型转换运算符(type-casting-operator)语法 -> *类型转换运算符* → **is** [*类型*](../chapter3/03_Types.html#type) -> *类型转换运算符* → **as** [*类型*](../chapter3/03_Types.html#type) -> *类型转换运算符* → **is** **?** [*类型*](../chapter3/03_Types.html#type) -> *类型转换运算符* → **as** **!** [*类型*](../chapter3/03_Types.html#type) +关于类型转换的更多内容和例子,请参阅 [类型转换](../chapter2/19_Type_Casting.md)。 + + +> 类型转换运算符语法 +> *类型转换运算符* → **is** [*类型*](03_Types.md#type) +> *类型转换运算符* → **as** [*类型*](03_Types.md#type) +> *类型转换运算符* → **as** **?** [*类型*](03_Types.md#type) +> *类型转换运算符* → **as** **!** [*类型*](03_Types.md#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) +> 基本表达式语法 + +> *基本表达式* → [*标识符*](02_Lexical_Structure.md#identifier) [*泛型实参子句*](08_Generic_Parameters_and_Arguments.md#generic-argument-clause)可选 +> *基本表达式* → [*字面量表达式*](#literal-expression) +> *基本表达式* → [*self表达式*](#self-expression) +> *基本表达式* → [*超类表达式*](#superclass-expression) +> *基本表达式* → [*闭包表达式*](#closure-expression) +> *基本表达式* → [*圆括号表达式*](#parenthesized-expression) +> *基本表达式* → [*隐式成员表达式*](#implicit-member-expression) +> *基本表达式* → [*通配符表达式*](#wildcard-expression) +> *基本表达式* → [*选择器表达式*](#selector-expression) -### 字符型表达式(Literal Expression) + +### 字面量表达式 -由这些内容组成:普通的字符(string, number) , 一个字符的字典或者数组,或者下面列表中的特殊字符。 +字面量表达式可由普通字面量(例如字符串或者数字),字典或者数组字面量,或者下面列表中的特殊字面量组成: -字符(Literal) | 类型(Type) | 值(Value) -------------- | ---------- | ---------- -/__FILE__ | String | 所在的文件名 -/__LINE__ | Int | 所在的行数 -/__COLUMN__ | Int | 所在的列数 -/__FUNCTION__ | String | 所在的function 的名字 +字面量 | 类型 | 值 +:------------- | :---------- | :---------- +`#file` | `String` | 所在的文件名 +`#line` | `Int` | 所在的行数 +`#column` | `Int` | 所在的列数 +`#function` | `String` | 所在的声明的名字 -在某个函数(function)中,`__FUNCTION__` 会返回当前函数的名字。 在某个方法(method)中,它会返回当前方法的名字。 在某个property 的getter/setter中会返回这个属性的名字。 在特殊的成员如init/subscript中 会返回这个关键字的名字,在某个文件的顶端(the top level of a file),它返回的是当前module的名字。 +`#line`除了上述含义外,还有另一种含义。当它出现在单独一行时,会被理解成行控制语句,请参阅[线路控制语句](../chapter3/10_Statements.md#线路控制语句)。 + +对于 `#function`,在函数中会返回当前函数的名字,在方法中会返回当前方法的名字,在属性的存取器中会返回属性的名字,在特殊的成员如 `init` 或 `subscript` 中会返回这个关键字的名字,在某个文件中会返回当前模块的名字。 + +`#function` 作为函数或者方法的默认参数值时,该字面量的值取决于函数或方法的调用环境。 -当作为函数或者方法时,字符型表达式的值在被调用时初始化。 ```swift -func logFunctionName(string: String = __FUNCTION__) { +func logFunctionName(string: String = #function) { print(string) } func myFunction() { - logFunctionName() // Prints "myFunction()". + logFunctionName() } - -myFunction() -namedArgs(1, withJay: 2) +myFunction() // 打印 “myFunction()” ``` -一个`array literal`,是一个有序的值的集合。 它的形式是: +数组字面量是值的有序集合,形式如下: -> [`value 1`, `value 2`, `...`] +> [`值 1`, `值 2`, `...`] + +数组中的最后一个表达式可以紧跟一个逗号。数组字面量的类型是 `[T]`,这个 `T` 就是数组中元素的类型。如果数组中包含多种类型,`T` 则是跟这些类型最近的的公共父类型。空数组字面量由一组方括号定义,可用来创建特定类型的空数组。 -数组中的最后一个表达式可以紧跟一个逗号(','). []表示空数组 。 array literal的type是 T[], 这个T就是数组中元素的type. 如果该数组中有多种type, T则是跟这些type的公共`supertype`最接近的type.空的`array literal`由一组方括号定义,可用来创建特定类型的空数组。 ```swift var emptyArray: [Double] = [] ``` -一个`dictionary literal` 是一个包含无序的键值对(key-value pairs)的集合,它的形式是: +字典字面量是一个包含无序键值对的集合,形式如下: -> [`key 1`: `value 1`, `key 2`: `value 2`, `...`] +> [`键 1` : `值 1`, `键 2` : `值 2`, `...`] + +字典中的最后一个表达式可以紧跟一个逗号。字典字面量的类型是 `[Key : Value]`,`Key` 表示键的类型,`Value` 表示值的类型。如果字典中包含多种类型,那么 `Key` 表示的类型则为所有键最接近的公共父类型,`Value` 与之相似。一个空的字典字面量由方括号中加一个冒号组成(`[:]`),从而与空数组字面量区分开,可以使用空字典字面量来创建特定类型的字典。 -dictionary 的最后一个表达式可以是一个逗号(','). [:] 表示一个空的dictionary. 它的type是 Dictionary (这里KeyType表示 key的type, ValueType表示 value的type) 如果这个dictionary 中包含多种 types, 那么KeyType, Value 则对应着它们的公共supertype最接近的type( closest common supertype).一个空的dictionary literal由方括号中加一个冒号组成,以此来与空array literal区分开,可以使用空的dictionary literal来创建特定类型的键值对。 ```swift -var emptyDictionary: [String: Double]=[:] +var emptyDictionary: [String : Double] = [:] ``` + > 字面量表达式语法 -> *字面量表达式* → [*字面量*](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) + +> *字面量表达式* → [*字面量*](02_Lexical_Structure.md#literal) +> *字面量表达式* → [*数组字面量*](#array-literal) | [*字典字面量*](#dictionary-literal) +> *字面量表达式* → **#file** | **#line** | **#column** | **#function** -self表达式是对 当前type 或者当前instance的引用。它的形式如下: + +> *数组字面量* → **[** [*数组字面量项列表*](#array-literal-items)可选 **]** + +> *数组字面量项列表* → [*数组字面量项*](#array-literal-item) **,**可选 | [*数组字面量项*](#array-literal-item) **,** [*数组字面量项列表*](#array-literal-items) + +> *数组字面量项* → [*表达式*](#expression) + + +> *字典字面量* → **[** [*字典字面量项列表*](#dictionary-literal-items) **]** | **[** **:** **]** + +> *字典字面量项列表* → [*字典字面量项*](#dictionary-literal-item) **,**可选 | [*字典字面量项*](#dictionary-literal-item) **,** [*字典字面量项列表*](#dictionary-literal-items) + +> *字典字面量项* → [*表达式*](#expression) **:** [*表达式*](#expression) + + +### self 表达式 + +`self` 表达式是对当前类型或者当前实例的显式引用,它有如下形式: > self -> self.`member name` -> self[`subscript index`] -> self(`initializer arguments`) -> self.init(`initializer arguments`) +> self.`成员名称` +> self[`下标索引`] +> self(`构造器参数`) +> self.init(`构造器参数`) -如果在 initializer, subscript, instance method中,self等同于当前type的instance. 在一个静态方法(static method), 类方法(class method)中, self等同于当前的type. +如果在构造器、下标、实例方法中,`self` 引用的是当前类型的实例。在一个类型方法中,`self` 引用的是当前的类型。 -当访问 member(成员变量时), self 用来区分重名变量(例如函数的参数). 例如, -(下面的 self.greeting 指的是 var greeting: String, 而不是 init(greeting: String) ) +当访问成员时,`self` 可用来区分重名变量,例如函数的参数: ```swift class SomeClass { @@ -252,7 +318,7 @@ class SomeClass { } ``` -在mutating 方法中, 你可以使用self 对 该instance进行赋值。 +在 `mutating` 方法中,你可以对 `self` 重新赋值: ```swift struct Point { @@ -263,49 +329,60 @@ struct Point { } ``` -> Self 表达式语法 -> *self表达式* → **self** -> *self表达式* → **self** **.** [*标识符*](LexicalStructure.html#identifier) -> *self表达式* → **self** **[** [*表达式*](../chapter3/04_Expressions.html#expression) **]** -> *self表达式* → **self** **.** **init** +> self 表达式语法 + +> *self 表达式* → **self** | [*self 方法表达式*](#self-method-expression) | [*self 下标表达式*](#self-subscript-expression) | [*self 构造器表达式*](#self-initializer-expression) +> + +> *self 方法表达式* → **self** **.** [*标识符*](02_Lexical_Structure.md#identifier) + +> *self 下标表达式* → **self** **[** [*表达式*](#expression) **]** + +> *self 构造器表达式* → **self** **.** **init** -### 超类表达式(Superclass Expression) + +### 超类表达式 -超类表达式可以使我们在某个class中访问它的超类. 它有如下形式: +超类表达式可以使我们在某个类中访问它的超类,它有如下形式: -> super.`member name` -> super[`subscript index`] -> super.init(`initializer arguments`) +> super.`成员名称` +> super[`下标索引`] +> super.init(`构造器参数`) -形式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) **]** +> 超类表达式语法 + +> *超类表达式* → [*超类方法表达式*](#superclass-method-expression) | [*超类下标表达式*](#superclass-subscript-expression) | [*超类构造器表达式*](#superclass-initializer-expression) + +> *超类方法表达式* → **super** **.** [*标识符*](02_Lexical_Structure.md#identifier) + +> *超类下标表达式* → **super** **[** [*表达式*](#expression) **]** + > *超类构造器表达式* → **super** **.** **init** -### 闭包表达式(Closure Expression) + +### 闭包表达式 -闭包(closure) 表达式可以建立一个闭包(在其他语言中也叫 lambda, 或者 匿名函数(anonymous function)). 跟函数(function)的声明一样, 闭包(closure)包含了可执行的代码(跟方法主体(statement)类似) 以及接收(capture)的参数。 它的形式如下: +闭包表达式会创建一个闭包,在其他语言中也叫 *lambda* 或匿名函数。跟函数一样,闭包包含了待执行的代码,不同的是闭包还会捕获所在环境中的常量和变量。它的形式如下: ```swift -{ (parameters) -> return type in - statements +{ (parameters) -> return type in + statements } ``` -闭包的参数声明形式跟方法中的声明一样, 请参见:[Function Declaration](TODO:添加链接). +闭包的参数声明形式跟函数一样,请参阅 [函数声明](05_Declarations.md#function_declaration)。 -闭包还有几种特殊的形式, 让使用更加简洁: +闭包还有几种特殊的形式,能让闭包使用起来更加简洁: -- 闭包可以省略 它的参数的type 和返回值的type. 如果省略了参数和参数类型,就也要省略 'in'关键字。 如果被省略的type 无法被编译器获知(inferred) ,那么就会抛出编译错误。 -- 闭包可以省略参数,转而在方法体(statement)中使用 $0, $1, $2 来引用出现的第一个,第二个,第三个参数。 -- 如果闭包中只包含了一个表达式,那么该表达式就会自动成为该闭包的返回值。 在执行 'type inference '时,该表达式也会返回。 +- 闭包可以省略它的参数和返回值的类型。如果省略了参数名和所有的类型,也要省略 `in` 关键字。如果被省略的类型无法被编译器推断,那么就会导致编译错误。 +- 闭包可以省略参数名,参数会被隐式命名为 `$` 加上其索引位置,例如 `$0`、`$1`、`$2` 分别表示第一个、第二个、第三个参数,以此类推。 +- 如果闭包中只包含一个表达式,那么该表达式的结果就会被视为闭包的返回值。表达式结果的类型也会被推断为闭包的返回类型。 -下面几个 闭包表达式是 等价的: +下面几个闭包表达式是等价的: ```swift myFunction { @@ -323,46 +400,95 @@ myFunction { return $0 + $1 } myFunction { $0 + $1 } ``` -关于 向闭包中传递参数的内容,参见: [Function Call Expression](TODO:添加链接). +关于如何将闭包作为参数来传递的内容,请参阅 [函数调用表达式](#function_call_expression)。 -### 参数列表(Capture Lists) +#### 捕获列表 -闭包表达式可以通过一个参数列表(capture list) 来显式指定它需要的参数。 参数列表由中括号 [] 括起来,里面的参数由逗号','分隔。一旦使用了参数列表,就必须使用'in'关键字(在任何情况下都得这样做,包括忽略参数的名字,type, 返回值时等等)。 +默认情况下,闭包会捕获附近作用域中的常量和变量,并使用强引用指向它们。你可以通过一个捕获列表来显式指定它的捕获行为。 -在闭包的参数列表( capture list)中, 参数可以声明为 'weak' 或者 'unowned' . +捕获列表在参数列表之前,由中括号括起来,里面是由逗号分隔的一系列表达式。一旦使用了捕获列表,就必须使用 `in` 关键字,即使省略了参数名、参数类型和返回类型。 + +捕获列表中的项会在闭包创建时被初始化。每一项都会用闭包附近作用域中的同名常量或者变量的值初始化。例如下面的代码示例中,捕获列表包含 `a` 而不包含 `b`,这将导致这两个变量具有不同的行为。 ```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 +var a = 0 +var b = 0 +let closure = { [a] in + print(a, b) +} + +a = 10 +b = 10 +closure() +// 打印 “0 10” ``` -在参数列表中,也可以使用任意表达式来赋值. 该表达式会在 闭包被执行时赋值,然后按照不同的力度来获取(这句话请慎重理解)。(captured with the specified strength. ) 例如: +在示例中,变量 `b` 只有一个,然而,变量 `a` 有两个,一个在闭包外,一个在闭包内。闭包内的变量 `a` 会在闭包创建时用闭包外的变量 `a` 的值来初始化,除此之外它们并无其他联系。这意味着在闭包创建后,改变某个 `a` 的值都不会对另一个 `a` 的值造成任何影响。与此相反,闭包内外都是同一个变量 `b`,因此在闭包外改变其值,闭包内的值也会受影响。 + +如果闭包捕获的值具有引用语义则有所不同。例如,下面示例中,有两个变量 `x`,一个在闭包外,一个在闭包内,由于它们的值是引用语义,虽然这是两个不同的变量,它们却都引用着同一实例。 ```swift -// Weak capture of "self.parent" as "parent" +class SimpleClass { + var value: Int = 0 +} +var x = SimpleClass() +var y = SimpleClass() +let closure = { [x] in + print(x.value, y.value) +} + +x.value = 10 +y.value = 10 +closure() +// 打印 “10 10” +``` + +如果捕获列表中的值是类类型,你可以使用 `weak` 或者 `unowned` 来修饰它,闭包会分别用弱引用和无主引用来捕获该值。 + +```swift +myFunction { print(self.title) } // 以强引用捕获 +myFunction { [weak self] in print(self!.title) } // 以弱引用捕获 +myFunction { [unowned self] in print(self.title) } // 以无主引用捕获 +``` + +在捕获列表中,也可以将任意表达式的值绑定到一个常量上。该表达式会在闭包被创建时进行求值,闭包会按照指定的引用类型来捕获表达式的值。例如: + +```swift +// 以弱引用捕获 self.parent 并赋值给 parent myFunction { [weak parent = self.parent] in print(parent!.title) } ``` -关于闭包表达式的更多信息和例子,请参见: [Closure Expressions](TODO:添加链接),关于更多参数列表的信息和例子,请参见: [Resolving Strong Reference Cycles for Closures](TODO:添加链接)。 +关于闭包表达式的更多信息和例子,请参阅 [闭包表达式](../chapter2/07_Closures.md#closure_expressions)。关于捕获列表的更多信息和例子,请参阅 [解决闭包引起的循环强引用](../chapter2/16_Automatic_Reference_Counting.md#resolving_strong_reference_cycles_for_closures)。 > 闭包表达式语法 -> *闭包表达式* → **{** [*闭包签名(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) + +> *闭包表达式* → **{** [*闭包签名*](#closure-signature)可选 [*语句*](10_Statements.md#statements) **}** -在可以判断出类型(type)的上下文(context)中,隐式成员表达式是访问某个type的member( 例如 class method, enumeration case) 的简洁方法。 它的形式是: + +> *闭包签名* → [*参数子句*](05_Declarations.md#parameter-clause) [*函数结果*](05_Declarations.md#function-result)可选 **in** +> *闭包签名* → [*标识符列表*](02_Lexical_Structure.md#identifier-list) [*函数结果*](05_Declarations.md#function-result)可选 **in** +> *闭包签名* → [*捕获列表*](#capture-list) [*参数子句*](05_Declarations.md#parameter-clause) [*函数结果*](05_Declarations.md#function-result)可选 **in** +> *闭包签名* → [*捕获列表*](#capture-list) [*标识符列表*](02_Lexical_Structure.md#identifier-list) [*函数结果*](05_Declarations.md#function-result)可选 **in** +> *闭包签名* → [*捕获列表*](#capture-list) **in** -> .`member name` + +> *捕获列表* → **[** [*捕获列表项列表*](#capture-list-items) **]** + +> *捕获列表项列表* → [*捕获列表项*](#capture-list-item) | [*捕获列表项*](#capture-list-item) **,** [*捕获列表项列表*](#capture-list-items) + +> *捕获列表项* → [*捕获说明符*](#capture-specifier)可选 [*表达式*](#expression) + +> *捕获说明符* → **weak** | **unowned** | **unowned(safe)** | **unowned(unsafe)** -例子: + +### 隐式成员表达式 + +若类型可被推断出来,可以使用隐式成员表达式来访问某个类型的成员(例如某个枚举成员或某个类型方法),形式如下: + +> .`成员名称` + +例如: ```swift var x = MyEnumeration.SomeValue @@ -370,282 +496,390 @@ x = .AnotherValue ``` > 隐式成员表达式语法 -> *隐式成员表达式* → **.** [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) + +> *隐式成员表达式* → **.** [*标识符*](02_Lexical_Structure.md#identifier) -### 圆括号表达式(Parenthesized Expression) + +### 圆括号表达式 -圆括号表达式由多个子表达式和逗号','组成。 每个子表达式前面可以有 identifier x: 这样的可选前缀。形式如下: +圆括号表达式由圆括号和其中多个逗号分隔的子表达式组成。每个子表达式前面可以有一个标识符,用冒号隔开。圆括号表达式形式如下: ->(`identifier 1`: `expression 1`, `identifier 2`: `expression 2`, `...`) +> (`标识符 1` : `表达式 1`, `标识符 2` : `表达式 2`, `...`) -圆括号表达式用来建立tuples , 然后把它做为参数传递给 function. 如果某个圆括号表达式中只有一个 子表达式,那么它的type就是 子表达式的type。例如: (1)的 type是Int, 而不是(Int) +使用圆括号表达式来创建元组,然后将其作为参数传递给函数。如果某个圆括号表达式中只有一个子表达式,那么它的类型就是子表达式的类型。例如,表达式 `(1)` 的类型是 `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) +> 圆括号表达式语法 + +> *圆括号表达式* → **(** [*表达式元素列表*](#expression-element-list)可选 **)** + +> *表达式元素列表* → [*表达式元素*](#expression-element) | [*表达式元素*](#expression-element) **,** [*表达式元素列表*](#expression-element-list) + +> *表达式元素* → [*表达式*](#expression) | [*标识符*](02_Lexical_Structure.md#identifier) **:** [*表达式*](#expression) -### 通配符表达式(Wildcard Expression) + +### 通配符表达式 -通配符表达式用来忽略传递进来的某个参数。例如:下面的代码中,10被传递给x, 20被忽略(译注:好奇葩的语法。。。) +通配符表达式可以在赋值过程中显式忽略某个值。例如下面的代码中,`10` 被赋值给 `x`,而 `20` 则被忽略: ```swift (x, _) = (10, 20) -// x is 10, 20 is ignored +// x 为 10,20 被忽略 ``` > 通配符表达式语法 + > *通配符表达式* → **_** - -## 后缀表达式(Postfix Expressions) + +### 选择器表达式 -后缀表达式就是在某个表达式的后面加上 操作符。 严格的讲,每个主要表达式(primary expression)都是一个后缀表达式 +选择器表达式可以让你通过选择器来引用在Objective-C中方法(method)和属性(property)的setter和getter方法。 -Swift 标准库提供了下列后缀表达式: +> \#selector(方法名) +\#selector(getter: 属性名) +\#selector(setter: 属性名) -- ++ 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`) - -如果该function 的声明中指定了参数的名字,那么在调用的时候也必须得写出来. 例如: - -> `function name`(`argument name 1`: `argument value 1`, `argument name 2`: `argument value 2`) - -可以在 函数调用表达式的尾部(最后一个参数之后)加上 一个闭包(closure) , 该闭包会被目标函数理解并执行。它具有如下两种写法: +方法名和属性名必须是存在于 Objective-C 运行时中的方法和属性的引用。选择器表达式的返回值是一个 Selector 类型的实例。例如: ```swift -// someFunction takes an integer and a closure as its arguments -someFunction(x, {$0 == 13}+ +class SomeClass: NSObject { + let property: String + @objc(doSomethingWithInt:) + func doSomething(_ x: Int) { } + + init(property: String) { + self.property = property + } +} +let selectorForMethod = #selector(SomeClass.doSomething(_:)) +let selectorForPropertyGetter = #selector(getter: SomeClass.property) +``` +当为属性的getter创建选择器时,属性名可以是变量属性或者常量属性的引用。但是当为属性的setter创建选择器时,属性名只可以是对变量属性的引用。 + +方法名称可以包含圆括号来进行分组,并使用as 操作符来区分具有相同方法名但类型不同的方法, 例如: + +```swift +extension SomeClass { + @objc(doSomethingWithString:) + func doSomething(_ x: String) { } +} +let anotherSelector = #selector(SomeClass.doSomething(_:) as (SomeClass) -> (String) -> Void) +``` + +由于选择器是在编译时创建的,因此编译器可以检查方法或者属性是否存在,以及是否在运行时暴露给了 Objective-C 。 + +> 注意 +> 虽然方法名或者属性名是个表达式,但是它不会被求值。 + +更多关于如何在 Swift 代码中使用选择器来与 Objective-C API 进行交互的信息,请参阅 [Using Swift with Cocoa and Objective-C (Swift 3)](https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[Objective-C Selectors](https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-ID59)部分。 + +> 选择器表达式语法 + +> *选择器表达式* → __#selector__ **(** [*表达式*](#expression) **)** +> *选择器表达式* → __#selector__ **(** [*getter:表达式*](#expression) **)** +> *选择器表达式* → __#selector__ **(** [*setter:表达式*](#expression) **)** + + + +## 后缀表达式 + +后缀表达式就是在某个表达式的后面运用后缀运算符或其他后缀语法。从语法构成上来看,基本表达式也是后缀表达式。 + +关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/25_Advanced_Operators.md)。 + +关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Swift Standard Library Operators Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。 + +> 后缀表达式语法 + +> *后缀表达式* → [*基本表达式*](#primary-expression) +> *后缀表达式* → [*后缀表达式*](#postfix-expression) [*后缀运算符*](02_Lexical_Structure.md#postfix-operator) +> *后缀表达式* → [*函数调用表达式*](#function-call-expression) +> *后缀表达式* → [*构造器表达式*](#initializer-expression) +> *后缀表达式* → [*显式成员表达式*](#explicit-member-expression) +> *后缀表达式* → [*后缀 self 表达式*](#postfix-self-expression) +> *后缀表达式* → [*dynamicType 表达式*](#dynamic-type-expression) +> *后缀表达式* → [*下标表达式*](#subscript-expression) +> *后缀表达式* → [*强制取值表达式*](#forced-value-expression) +> *后缀表达式* → [*可选链表达式*](#optional-chaining-expression) + + +### 函数调用表达式 + +函数调用表达式由函数名和参数列表组成,形式如下: + +> `函数名`(`参数 1`, `参数 2`) + +函数名可以是值为函数类型的任意表达式。 + +如果函数声明中指定了参数的名字,那么在调用的时候也必须得写出来。这种函数调用表达式具有以下形式: + +> `函数名`(`参数名 1`: `参数 1`, `参数名 2`: `参数 2`) + +如果函数的最后一个参数是函数类型,可以在函数调用表达式的尾部(右圆括号之后)加上一个闭包,该闭包会作为函数的最后一个参数。如下两种写法是等价的: + +```swift +// someFunction 接受整数和闭包参数 +someFunction(x, f: {$0 == 13}) someFunction(x) {$0 == 13} ``` 如果闭包是该函数的唯一参数,那么圆括号可以省略。 ```swift -// someFunction takes a closure as its only argument +// someFunction 只接受一个闭包参数 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) + +> *函数调用表达式* → [*后缀表达式*](#postfix-expression) [*圆括号表达式*](#parenthesized-expression) +> *函数调用表达式* → [*后缀表达式*](#postfix-expression) [*圆括号表达式*](#parenthesized-expression)可选 [*尾随闭包*](#trailing-closure) + +> *尾随闭包* → [*闭包表达式*](#closure-expression) -### 初始化函数表达式(Initializer Expression) + +### 构造器表达式 -Initializer表达式用来给某个Type初始化。 它的形式如下: +构造器表达式用于访问某个类型的构造器,形式如下: -> `expression`.init(`initializer arguments`) - -初始化函数表达式在调用函数时用来初始某个Type。 也可以使用初始化函数表达式来委托调用(delegate to )到superclass的initializers. +> `表达式`.init(`构造器参数`) +你可以在函数调用表达式中使用构造器表达式来初始化某个类型的新实例。也可以使用构造器表达式来代理给超类构造器。 ```swift class SomeSubClass: SomeSuperClass { - init() { - // subclass initialization goes here + override init() { + // 此处为子类构造过程 super.init() } } ``` -和函数类似, 初始化表达式可以用作数值。 举例来说: +和函数类似,构造器表达式可以作为一个值。 例如: ```swift -// Type annotation is required because String has multiple initializers. +// 类型注解是必须的,因为 String 类型有多种构造器 let initializer: Int -> String = String.init let oneTwoThree = [1, 2, 3].map(initializer).reduce("", combine: +) print(oneTwoThree) -// prints "123" +// 打印 “123” ``` -如果要用名字来指定某个type, 可以不用初始化函数表达式直接使用type的initializer。在其他情况下, 你必须使用初始化函数表达式。 - +如果通过名字来指定某个类型,可以不用构造器表达式而直接使用类型的构造器。在其他情况下,你必须使用构造器表达式。 ```swift -let s1 = SomeType.init(data: 3) // Valid -let s2 = SomeType(data: 1) // Also valid +let s1 = SomeType.init(data: 3) // 有效 +let s2 = SomeType(data: 1) // 有效 -let s4 = someValue.dynamicType(data: 5) // Error -let s3 = someValue.dynamicType.init(data: 7) // Valid +let s4 = someValue.dynamicType(data: 5) // 错误 +let s3 = someValue.dynamicType.init(data: 7) // 有效 ``` - - > 构造器表达式语法 -> *构造器表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **.** **init** + +> *构造器表达式* → [*后缀表达式*](#postfix-expression) **.** **init** +> *构造器表达式* → [*后缀表达式*](#postfix-expression) **.** **init** **(** [*参数名称*](#argument-names) **)** -### 显式成员表达式(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 +let y = c.someProperty // 访问成员 ``` -对于tuple, 要根据它们出现的顺序(0, 1, 2...)来使用: +元组的成员会隐式地根据表示它们出现顺序的整数来命名,以 0 开始,例如: ```swift var t = (10, 20, 30) t.0 = t.1 -// Now t is (20, 20, 30) +// 现在元组 t 为 (20, 20, 30) ``` -对于某个module的member的调用,只能调用在top-level声明中的member. +对于模块的成员来说,只能直接访问顶级声明中的成员。 + +为了区分只有参数名有所不同的方法或构造器,在圆括号中写出参数名,参数名后紧跟一个冒号,对于没有参数名的参数,使用下划线代替参数名。而对于重载方法,则需使用类型标注进行区分。例如: + +```swift +class SomeClass { + func someMethod(x: Int, y: Int) {} + func someMethod(x: Int, z: Int) {} + func overloadedMethod(x: Int, y: Int) {} + func overloadedMethod(x: Int, y: Bool) {} +} +let instance = SomeClass() + +let a = instance.someMethod // 有歧义 +let b = instance.someMethod(_:y:) // 无歧义 + +let d = instance.overloadedMethod // 有歧义 +let d = instance.overloadedMethod(_:y:) // 有歧义 +let d: (Int, Bool) -> Void = instance.overloadedMethod(_:y:) // 无歧义 +``` + +如果点号(`.`)出现在行首,它会被视为显式成员表达式的一部分,而不是隐式成员表达式的一部分。例如如下代码所展示的被分为多行的链式方法调用: + +```swift +let x = [10, 3, 20, 15, 4] + .sort() + .filter { $0 > 5 } + .map { $0 * 100 } +``` > 显式成员表达式语法 -> *显示成员表达式* → [*后置表达式*](../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) _可选_ + +> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*十进制数字*](02_Lexical_Structure.md#decimal-digit) +> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*标识符*](02_Lexical_Structure.md#identifier) [*泛型实参子句*](08_Generic_Parameters_and_Arguments.md#generic-argument-clause)可选
+> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*标识符*](02_Lexical_Structure.md#identifier) **(** [*参数名称*](#argument-names) **)** +> + +> *参数名称* → [*参数名*](#argument-name) [*参数名称*](#argument-names)可选
+ +> *参数名* → [*标识符*](02_Lexical_Structure.md#identifier) **:** -### 后缀self表达式(Postfix Self Expression) + +### 后缀 self 表达式 -后缀表达式由 某个表达式 + '.self' 组成. 形式如下: +后缀 `self` 表达式由某个表达式或类型名紧跟 `.self` 组成,其形式如下: -> `expression`.self -> `type`.self +> `表达式`.self +> `类型`.self -形式1 表示会返回 expression 的值。例如: x.self 返回 x +第一种形式返回表达式的值。例如:`x.self` 返回 `x`。 -形式2:返回对应的type。我们可以用它来动态的获取某个instance的type。 +第二种形式返回相应的类型。我们可以用它来获取某个实例的类型作为一个值来使用。例如,`SomeClass.self` 会返回 `SomeClass` 类型本身,你可以将其传递给相应函数或者方法作为参数。 -> 后置Self 表达式语法 -> *后置self表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **.** **self** +> 后缀 self 表达式语法 + +> *后缀 self 表达式* → [*后缀表达式*](#postfix-expression) **.** **self** -### dynamic表达式(Dynamic Type Expression) + +### dynamicType 表达式 -(因为dynamicType是一个独有的方法,所以这里保留了英文单词,未作翻译, --- 类似与self expression) +dynamicType 表达式由类似[函数调用表达式(Function Call Expression)](#function-call-expression)的特殊语法表达式组成,形式如下: -dynamicType 表达式由 某个表达式 + '.dynamicType' 组成。 +> type(of:`表达式`) -> `expression`.dynamicType - -上面的形式中, expression 不能是某type的名字(当然了,如果我都知道它的名字了还需要动态来获取它吗)。动态类型表达式会返回"运行时"某个instance的type, 具体请看下面的列子: +上述形式中的表达式不能是类型名。type(of:) 表达式会返回某个实例在运行时的类型,具体请看下面的例子: ```swift class SomeBaseClass { class func printClassName() { - println("SomeBaseClass") + print("SomeBaseClass") } } class SomeSubClass: SomeBaseClass { override class func printClassName() { - println("SomeSubClass") + print("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" +// someInstance 在编译时的静态类型为 SomeBaseClass, +// 在运行时的动态类型为 SomeSubClass +type(of: someInstance).printClassName() +// 打印 “SomeSubClass” ``` > 动态类型表达式语法 -> *动态类型表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **.** **dynamicType** + +> *动态类型表达式* → type(of:表达式) **.** **dynamicType** -### 下标脚本表达式(Subscript Expression) + +### 下标表达式 -下标脚本表达式提供了通过下标脚本访问getter/setter 的方法。它的形式是: +可通过下标表达式访问相应的下标,形式如下: -> `expression`[`index expressions`] +> `表达式`[`索引表达式`] -可以通过下标脚本表达式通过getter获取某个值,或者通过setter赋予某个值. +要获取下标表达式的值,可将索引表达式作为下标表达式的参数来调用下标 getter。下标 setter 的调用方式与之一样。 -关于subscript的声明,请参见: Protocol Subscript Declaration. +关于下标的声明,请参阅 [协议下标声明](05_Declarations.md#protocol_subscript_declaration)。 -> 附属脚本表达式语法 -> *附属脚本表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **[** [*表达式列表*](../chapter3/04_Expressions.html#expression_list) **]** +> 下标表达式语法 + +> *下标表达式* → [*后缀表达式*](#postfix-expression) **[** [*表达式列表*](#expression-list) **]** -### 强制取值表达式(Forced-Value Expression) + +### 强制取值表达式 -强制取值表达式用来获取某个目标表达式的值(该目标表达式的值必须不是nil )。它的形式如下: +当你确定可选值不是 `nil` 时,可以使用强制取值表达式来强制解包,形式如下: -> `expression`! +> `表达式`! + +如果该表达式的值不是 `nil`,则返回解包后的值。否则,抛出运行时错误。 + +返回的值可以被修改,无论是修改值本身,还是修改值的成员。例如: -如果该表达式的值不是nil, 则返回对应的值。 否则,抛出运行时错误(runtime error)。 -返回的值可能会被需改,可以是被赋值了,也可以是出现异常造成的。比如: ```swift var x: Int? = 0 x!++ -// x is now 1 +// x 现在是 1 var someDictionary = ["a": [1, 2, 3], "b": [10, 20]] someDictionary["a"]![0] = 100 -// someDictionary is now [b: [10, 20], a: [100, 2, 3]] +// someDictionary 现在是 [b: [10, 20], a: [100, 2, 3]] ``` -> 强制取值(Forced Value)语法 -> *强制取值(Forced Value)表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **!** +> 强制取值语法 + +> *强制取值表达式* → [*后缀表达式*](#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) +如果某个后缀表达式包含可选链表达式,那么它的执行过程会比较特殊。如果该可选链表达式的值是 `nil`,整个后缀表达式会直接返回 `nil`。如果该可选链表达式的值不是 `nil`,则返回可选链表达式解包后的值,并将该值用于后缀表达式中剩余的表达式。在这两种情况下,整个后缀表达式的值都会是可选类型。 -如果某个"后缀表达式"的"子表达式"中包含了"可选链表达式",那么只有最外层的表达式返回的才是一个optional type. 例如,在下面的例子中, 如果c 不是nil, 那么 c?.property.performAction() 这句代码在执行时,就会先获得c 的property方法,然后调用 performAction()方法。 然后对于 "c?.property.performAction()" 这个整体,它的返回值是一个optional type. +如果某个后缀表达式中包含了可选链表达式,那么只有最外层的表达式会返回一个可选类型。例如,在下面的例子中,如果 `c` 不是 `nil`,那么它的值会被解包,然后通过 `.property` 访问它的属性,接着进一步通过 `.performAction()` 调用相应方法。整个 `c?.property.performAction()` 表达式返回一个可选类型的值,而不是多重可选类型。 ```swift var c: SomeClass? var result: Bool? = c?.property.performAction() ``` -如果不使用可选链表达式,那么 上面例子的代码跟下面例子等价: +上面的例子跟下面的不使用可选链表达式的例子等价: ```swift +var result: Bool? = nil if let unwrappedC = c { result = unwrappedC.property.performAction() } ``` -后缀'?' 返回目标表达式的值可能会被修改,可能是由于出现了赋值,也有可能是出现异常而产生的修改。如果可选链表达式为`nil`,则表达式右边的复制操作不会被执行。比如: + +可选链表达式解包后的值可以被修改,无论是修改值本身,还是修改值的成员。如果可选链表达式的值为 `nil`,则表达式右侧的赋值操作不会被执行。例如: + ```swift func someFunctionWithSideEffects() -> Int { - return 42 // No actual side effects. + // 译者注:为了能看出此函数是否被执行,加上了一句打印 + print("someFunctionWithSideEffects") + return 42 } var someDictionary = ["a": [1, 2, 3], "b": [10, 20]] - + someDictionary["not here"]?[0] = someFunctionWithSideEffects() -// someFunctionWithSideEffects is not evaluated -// someDictionary is still [b: [10, 20], a: [1, 2, 3]] - +// someFunctionWithSideEffects 不会被执行 +// someDictionary 依然是 ["b": [10, 20], "a": [1, 2, 3]] + someDictionary["a"]?[0] = someFunctionWithSideEffects() -// someFunctionWithSideEffects is evaluated and returns 42 -// someDictionary is now [b: [10, 20], a: [42, 2, 3]] +// someFunctionWithSideEffects 被执行并返回 42 +// someDictionary 现在是 ["b": [10, 20], "a": [42, 2, 3]] ``` - > 可选链表达式语法 -> *可选链表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **?** + +> *可选链表达式* → [*后缀表达式*](#postfix-expression) **?** diff --git a/source/chapter3/05_Declarations.md b/source/chapter3/05_Declarations.md index 5ad0a93a..3e8f602a 100755 --- a/source/chapter3/05_Declarations.md +++ b/source/chapter3/05_Declarations.md @@ -1,1087 +1,1247 @@ - -# 声明(Declarations) ------------------ - -> 1.0 -> 翻译:[marsprince](https://github.com/marsprince) [Lenhoon](https://github.com/marsprince)[(微博)](http://www.weibo.com/lenhoon) -> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai) - -> 2.0 -> 翻译+校对:[Lenhoon](https://github.com/Lenhoon), + +# 声明(Declarations) +----------------- + +> 1.0 +> 翻译:[marsprince](https://github.com/marsprince) [Lenhoon](https://github.com/marsprince)[(微博)](http://www.weibo.com/lenhoon) +> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai) + +> 2.0 +> 翻译+校对:[Lenhoon](https://github.com/Lenhoon), > [BridgeQ](https://github.com/WXGBridgeQ) > 2.1 -> 翻译+校队:[shanks](http://codebuild.me) - -本页包含内容: - -- [顶级代码](#top-level_code) -- [代码块](#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) -- [声明修饰符](#declaration_modifiers) - -一条*声明(declaration)*可以在程序里引入新的名字或者构造。举例来说,可以使用声明来引入函数和方法,变量和常量,或者来定义新的命名好的枚举,结构,类和协议类型。可以使用一条声明来延长一个已经存在的命名好的类型的行为。或者在程序里引入在其它地方声明的符号。 - -在Swift中,大多数声明在某种意义上讲也是执行或同时声明它们的初始化定义。这意味着,因为协议和它们的成员不匹配,大多数协议成员需要单独的声明。为了方便起见,也因为这些区别在Swift里不是很重要,*声明语句(declaration)*同时包含了声明和定义。 - -> 声明语法 -> *声明* → [*导入声明*](#grammer_of_an_import_declaration) -> *声明* → [*常量声明*](#grammer_of_a_constant_declaration) -> *声明* → [*变量声明*](#grammer_of_a_variable_declaration) -> *声明* → [*类型别名声明*](#grammer_of_a_type_alias_declaration) -> *声明* → [*函数声明*](#grammer_of_a_function_declaration) -> *声明* → [*枚举声明*](#grammer_of_a_enumeration_declaration) -> *声明* → [*结构体声明*](#grammer_of_a_structure_declaration) -> *声明* → [*类声明*](#grammer_of_a_class_declaration) -> *声明* → [*协议声明*](#grammer_of_a_protocol_declaration) -> *声明* → [*构造器声明*](#grammer_of_an_initializer_declaration) -> *声明* → [*析构器声明*](#grammer_of_a_deinitializer_declaration) -> *声明* → [*扩展声明*](#grammer_of_an_extension_declaration) -> *声明* → [*附属脚本声明*](#grammer_of_a_subscript_declaration) -> *声明* → [*运算符声明*](#grammer_of_an_operator_declaration) -> *声明(Declarations)列表* → [*声明*](#declaration) [*声明(Declarations)列表*](#declarations) _可选_ - - -##顶级代码 - -Swift 的源文件中的顶级代码由零个或多个语句,声明和表达式组成。默认情况下,在一个源文件的顶层声明的变量,常量和其他命名的声明语句可以被同一模块部分里的每一个源文件中的代码访问。可以通过使用一个访问级别修饰符来标记这个声明,从而重写这个默认行为,[访问控制级别(Access Control Levels)](#access_control_levels)中有所介绍。 - -> 顶级(Top Level) 声明语法 -> *顶级声明* → [*多条语句(Statements)*](../chapter3/04_Expressions.html) _可选_ - - -##代码块 - -*代码块*用来将一些声明和控制结构的语句组织在一起。它有如下的形式: - -> { -> `statements` -> } - -代码块中的*语句(statements)*包括声明,表达式和各种其他类型的语句,它们按照在源码中的出现顺序被依次执行。 - -> 代码块语法 -> *代码块* → **{** [*多条语句(Statements)*](../chapter3/04_Expressions.html) _可选_ **}** - - -##引入声明 - -可以使用在其他文件中声明的内容*引入声明(import declaration)*。引入语句的基本形式是引入整个代码模块;它由`import`关键字开始,后面 -紧跟一个模块名: - -> import `module` - -可以提供更多的细节来限制引入的符号,如声明一个特殊的子模块或者在一个模块或子模块中做特殊的声明。(待改进) -当使用了这些细节后,在当前的程序汇总只有引入的符号是可用的(并不是声明的整个模块)。 - -> import `import kind` `module`.`symbol name` -> import `module`.`submodule` - - -> 导入(Import)声明语法 -> *导入声明* → [*特性(attributes)列表*](../chapter3/06_Attributes.html) _可选_ **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) | [*运算符*](../chapter3/02_Lexical_Structure.html) - - -##常量声明 - -*常量声明(constant declaration)*可以在程序里命名一个常量。常量以关键词`let`来声明,遵循如下的格式: - -> let `constant name`: `type` = `expression` - -当常量的值被给定后,常量就将*常量名称(constant name)*和*表达式(expression)*初始值不变的结合在了一起,而且不能更改。 - -这意味着如果常量以类的形式被初始化,类本身的内容是可以改变的,但是常量和类之间的结合关系是不能改变的。 - -当一个常量被声明为全局变量,它必须被给定一个初始值。当一个常量在类或者结构体中被声明时,它被认为是一个*常量属性(constant property)*。常量并不是可计算的属性,因此不包含getters和setters。 - -如果*常量名(constant name)*是一个元组形式,元组中的每一项初始化*表达式(expression)*中都要有对应的值。 - -```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." -``` - -当*常量名称(constant name)*的类型可以被推断出时,类型标注*(:type)*在常量声明中是一个可选项,它可以用来描述在[类型推断(Type Inference)](./03_Types.html#type_inference)中找到的类型。 - -声明一个常量类型属性要使用关键字`static`声明修饰符。类型属性在[类型属性(Type Properties)](../chapter2/10_Properties.html#type_properties)中有介绍。 - -如果还想获得更多关于常量的信息或者想在使用中获得帮助,请查看[常量和变量](../chapter2/01_The_Basics.html#constants_and_variables)和[存储属性(Stored Properties)](../chapter2/10_Properties.html#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) - - -##变量声明 - -*变量声明(variable declaration)*可以在程序里声明一个变量,它以关键字`var`来声明。 - -变量声明有几种不同的形式声明不同种类的命名值和计算型值,如存储和计算变量和属性,存储变量和属性监视,和静态变量属性。所使用的声明形式取决于变量所声明的范围和打算声明的变量类型。 - ->注意: ->也可以在协议声明的上下文声明属性,详情参见[协议属性声明(Protocal Property Declaration)](./05_Declarations.html#protocol_declaration)。 - -可以重载一个子类中的属性,通过使用'override'声明修饰符来标记子类的属性声明,[重写(Overriding)](../chapter2/13_Inheritance.html#overriding)中有所介绍。 - - -###存储型变量和存储型属性 - -下面的形式声明了一个存储型变量或存储型变量属性 - -> var `variable name`: `type` = `expression` - -可以在全局,函数内,或者在类和结构体的声明(context)中使用这种形式来声明一个变量。当变量以这种形式 -在全局或者一个函数内被声明时,它代表一个*存储型变量(stored variable)*。当它在类或者结构体中被声明时,它代表一个*存储型变量属性(stored variable property)*。 - -初始化的*表达式(expression)*不可以在协议的声明中出现,在其他情况下,初始化*表达式(expression)*是可选的(optional),如果没有初始化*表达式(expression)*,那么变量定义时必须显示包括类型标注(*:type*) - -对于常量的定义,如果*变量名字(variable name)*是一个元组(tuple),元组中每一项的名称都要和初始化*表达式(expression)*中的相应值一致。 - -正如名字一样,存储型变量的值或存储型变量属性存储在内存中。 - - -###计算型变量和计算型属性 - -如下形式声明一个一个存储型变量或存储型属性: - -> var `variable name`: `type` { -> get { -> `statements` -> } -> set(`setter name`) { -> `statements` -> } -> } - -可以在全局,函数体内或者类,结构体,枚举,扩展声明的上下文中使用这种形式的声明。当变量以这种形式在全局或者一个函数内被声明时,它代表一个*计算型变量(computed variable)*。当它在类,结构体,枚举,扩展声明的上下文中中被声明时,它代表一个*计算型变量(computed variable)*。 - -getter用来读取变量值,setter用来写入变量值。setter子句是可选择的,只有getter是必需的,可以将这些语句 -都省略,只是简单的直接返回请求值,正如在[只读计算属性(Read-Only Computed Properties)](../chapter2/10_Properties.html#computed_properties)中描述的那样。但是如果提供了一个setter语句,也必需提供一个getter语句。 - -setter的名字和圆括号内的语句是可选的。如果写了一个setter名,它就会作为setter的参数被使用。如果不写setter名,setter的初始名为'newValue',正如在[便捷 setter 声明(Shorthand Setter Declaration)](../chapter2/10_Properties.html#shorthand_setter_declaration)中提到的那样。 - -不像存储型变量和存储型属性那样,计算型属性和计算型变量的值不存储在内存中。 - -获得更多信息,查看更多关于计算型属性的例子,请查看[计算属性(Computed Properties)](../chapter2/10_Properties.html#computed_properties)一节。 - - -###存储型变量监视器和属性监视器 - -可以用`willset`和`didset`监视器来声明一个存储型变量或属性。一个包含监视器的存储型变量或属性按如下的形式声明: - -> var `variable name`: `type` = expression { -> willSet(setter name) { -> `statements` -> } -> didSet(`setter name`) { -> `statements` -> } -> } - -可以在全局,函数体内或者类,结构体,枚举,扩展声明的上下文中使用这种形式的声明。当变量以这种形式在全局或者一个函数内被声明时,监视器代表一个*存储型变量监视器(stored variable observers)*;当它在类,结构体,枚举,扩展声明的上下文中被声明时,监视器代表*属性监视器(property observers)*。 - -可以为适合的监视器添加任何存储型属性。也可以通过重写子类属性的方式为适合的监视器添加任何继承的属性 -(无论是存储型还是计算型的),参见[重写属性监视器(Overriding Property Observers)](../chapter2/13_Inheritance.html#overriding)。 - -初始化*表达式(expression)*在一个类中或者结构体的声明中是可选的,但是在其他地方是必需的。当类型可以从初始化*表达式(expression)*中推断而来,那么这个*类型(type)*标注是可选的。 - -当变量或属性的值被改变时,`willset`和`didset`监视器提供了一个监视方法(适当的回应)。 -监视器不会在变量或属性第一次初始化时运行,它们只有在值被外部初始化语句改变时才会被运行。 - -`willset`监视器只有在变量或属性值被改变之前运行。新的值作为一个常量经过过`willset`监视器,因此不可以在`willset`语句中改变它。`didset`监视器在变量或属性值被改变后立即运行。和`willset`监视器相反,为了以防止仍然需要获得旧的数据,旧变量值或者属性会经过`didset`监视器。这意味着,如果在变量或属性自身的`didiset`监视器语句中设置了一个值,设置的新值会取代刚刚在`willset`监视器中经过的那个值。 - -在`willset`和`didset`语句中,*setter名(setter name)*和圆括号的语句是可选的。如果写了一个setter名,它就会作为`willset`和`didset`的参数被使用。如果不写setter名, -`willset`监视器初始名为`newvalue`,`didset`监视器初始名为`oldvalue`。 - -当提供一个`willset`语句时,`didset`语句是可选的。同样的,在提供了一个`didset`语句时,`willset`语句是可选的。 - -获得更多信息,查看如何使用属性监视器的例子,请查看[属性监视器(Property Observers)](../chapter2/10_Properties.html#property_observers)一节。 -声明修饰符 - - -###类型变量属性 - -声明一个类型变量属性,要用`static`声明修饰符标记该声明。类可能需要`class`声明修饰符去标记类的类型计算型属性从而允许子类可以重写超类的实现。类型属性在[类型属性(Type Properties)](../chapter2/10_Properties.html#type_properties)章节讨论。 - ->>注意 ->> ->在一个类声明中,关键字`static`与用声明修饰符`class`和`final`去标记一个声明的效果相同 - - -> 变量声明语法 -> *变量声明* → [*变量声明头(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/05_Declarations.html#initializer) [*willSet-didSet代码块*](../chapter3/05_Declarations.html#willSet_didSet_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) - - -##类型的别名声明 - -*类型别名声明(type alias declaration)*可以在程序里为一个已存在的类型声明一个别名。类型的别名声明语句使用关键字`typealias`声明,遵循如下的形式: - -> `typealias name` = `existing type` - -当声明一个类型的别名后,可以在程序的任何地方使用别*名(name)*来代替*已存在的类型(existing type)*。已存在的类型可以是已经被命名的类型或者是混合类型。类型的别名不产生新的类型,它只是简单的和已存在的类型做名称替换。 - -查看更多[协议关联类型声明(Protocol Associated Type Declaration)](./05_Declarations.html#protocol_associated_type_declaration). - - -> 类型别名声明语法 -> *类型别名声明* → [*类型别名头(Head)*](../chapter3/05_Declarations.html#typealias_head) [*类型别名赋值*](../chapter3/05_Declarations.html#typealias_assignment) -> *类型别名头(Head)* → [*属性列表*](../chapter3/06_Attributes.html) _可选_ [*访问级别修饰符*](../chapter3/05_Declarations.html#declaration_modifiers) _可选_ **typealias** [*类型别名名称*](../chapter3/05_Declarations.html#typealias_name) -> *类型别名名称* → [*标识符*](LexicalStructure.html#identifier) -> *类型别名赋值* → **=** [*类型*](../chapter3/03_Types.html#type) - - -##函数声明 - -使用*函数声明(function declaration)*在程序里引入新的函数或者方法。一个函数被声明在类的上下文,结构体,枚举,或者协议中,从而作为*方法(method)*被引用。 -函数声明使用关键字`func`,遵循如下的形式: - -> func `function name`(`parameters`) -> `return type` { -> `statements` -> } - -如果函数返回`Void`类型,返回类型可以被忽略,如下所示: - -> func `function name`(`parameters`) { -> `statements` -> } - -每个参数的类型都要标明,它们不能被推断出来。虽然函数的参数默认是常量,也可以使用参数名前使用`let`来强调这一行为。在这些参数前面添加`var`使它们成为变量,作用域内任何对变量的改变只在函数体内有效,或者用`inout`使的这些改变可以在调用域内生效。更多关于in-out参数的讨论,参见[In-Out参数(In-Out Parameters)](#) - -函数可以使用元组类型作为返回值来返回多个变量。 - -函数定义可以出现在另一个函数声明内。这种函数被称作nested函数。更多关于*嵌套函数(Nested Functions)*的讨论,参见[嵌套函数(Nested Functions)](../chapter2/06_Functions.html#Nested_Functions)。 - - -###参数名 - -函数的参数是一个以逗号分隔的列表 。函数调用是的变量顺序必须和函数声明时的参数顺序一致。 -最简单的参数列表有着如下的形式: - -> `parameter name`: `parameter type` - -一个参数有一个内部名称,这个内部名称可以在函数体内被使用。同样也可以作为外部名称,当调用方法时这个外部名称被作为实参的标签来使用。默认情况下,第一个参数的外部名称省略不写,第二个和其之后的参数使用它们的内部名称作为它们的外部名称。 - -```swift -func f(x: Int, y: Int) -> Int{ return x + y} -f(1, y: 2) // y是有标记的,x没有 -``` - -可以按如下的一种形式,重写参数名被使用的默认过程: - -> `external parameter name` `local parameter name`: `parameter type` -> _ `local parameter name`: `parameter type` - -在内部参数名前的名称赋予这个参数一个外部名称,这个名称可以和内部参数的名称不同。外部参数名在函数被调用时必须被使用。对应的参数在方法或函数被调用时必须有外部名 。 - -内部参数名前的强调字符下划线(_)使参数在函数被调用时没有名称。在函数或方法调用时,与其对应的语句必须没有名字。 - -```swift -func f(x x: Int, withY y: Int, _z: Int) -> Int{ - return x + y + z } -f(x: 1, withY: 2, 3) // x和y是有标记的,z没有 -``` +> 翻译:[mmoaay](https://github.com/mmoaay), [shanks](http://codebuild.me) +> 校对:[shanks](http://codebuild.me) - - - -###特殊类型的参数 - -参数可以被忽略,参数的值的数量可变,并且还可以提供默认值,使用形式如下: - -> _ : `parameter type`. -> `parameter name`: `parameter type`... -> `parameter name`: `parameter type` = `default argument value` - -以下划线(_)命名的参数是明确忽略的,在函数体内不能被访问。 - -一个以基础类型名的参数,如果紧跟着三个点(`...`),被理解为是可变参数。一个函数至多可以拥有一个可变参数,且必须是最后一个参数。可变参数被作为该基本类型名的数组来看待。举例来讲,可变参数`Int...`被看做是`[Int]`。查看可变参数的使用例子,详见[可变参数(Variadic Parameters)](../chapter2/06_Functions.html#Function_Parameters_and_Return_Values)一节。 - -在参数的类型后面有一个以等号(`=`)连接的表达式,这样的参数被看做有着给定表达式的初始值。当函数被调用时,给定的表达式被求值。如果参数在函数调用时被省略了,就会使用初始值。 - -```swift -func f(x: Int = 42) -> Int { return x} -f() // 有效的,使用默认值 -f(7) // 有效的,提供了值,没有提供值的名称 -f(x: 7) //无效的,值和值的名称都提供了 -``` - -###特殊方法 - -枚举或结构体的方法来修改`self`属性,必须以`mutating`声明修饰符标记。 - -子类方法重写超类中的方法必须以`override`声明修饰符标记。重写一个方法不使用`override`修饰符,或者使用了`override`修饰符却并没有重写超类方法都会产生一个编译时错误。 - -枚举或者结构体中的类型方法而不是实例方法,要以`static`声明修饰符标记,而对于类中的类型方法,要使用`class`声明修饰符标记。 - - -###柯里化函数(Curried Functions) - -可以重写一个带有多个参数的函数使它等同于一个只有一个参数并且返回一个函数的函数,这个返回函数携带下一个参数并且返回另外一个函数,一直持续到再没有剩余的参数,此时要返回的函数返回原来的多参函数要返回的原始值。这个重写的函数被称为*柯里化函数(curried function)*。例如,可以为`addTwoInts(a:b:)`重写一个等价的`addTwoIntsCurried(a:)(b:)`的函数。 - -```swift -func addTwoInts(a: Int, b: Int) -> Int { - return a + b -} - -func addTwoIntsCurried(a: Int) -> (Int -> Int) { - func addTheOtherInt(b: Int) -> Int { - return a + b - } - return addTheOtherInt -} -``` - -这个`addTwoInts(a:b:)`函数带有两个整型值并且返回他们的和。`addTwoIntsCurried(a:)(b:)`函数带有一个整型值,并且返回另外一个带有第二个整型值的函数并使其和第一个整型值相加(这个内嵌的函数从包含它的函数中捕获第一个整型参数的值)。 - -在Swift中,可以通过以下语法非常简明的写一个柯里化函数: - -> func `function name`(`parameter`)(`parameter`) -> `return type` { -> `statements` -> } - -举例来说,下面的两个声明是等价的: - -```swift -func addTwoIntsCurried(a a: Int)(b: Int) -> Int { - return a + b -} - -func addTwoIntsCurried(a a: Int) -> (Int -> Int) - { - func addTheOtherInt(b: Int) -> Int { - return a + b - } - return addTheOtherInt -} -``` - -为了像使用非柯里化函数一样的方式使用`addTwoIntsCurried(a:)(b:)`函数,必须用第一个整型参数调用`addTwoIntsCurried(a:)(b:)`,紧接着用第二个整型参数调用其返回的函数: - -```swift -addTwoInts(a: 4, b: 5) -//返回值为9 -addTwoIntsCurried(a: 4)(b: 5) -//返回值为9 -``` - -虽然在每次调用一个非柯里化函数时必须提供所有的参数,可以使用函数的柯里化形式把参数分配在多次函数调用中,称之为“*偏函数应用(partial function application)*”,例如可以为`addTwoIntsCurried(a:)(b:)`函数使用参数`1`然后把返回的结果赋值给常量`plusOne`: - -```swift -let plusOne = addTwoIntsCurried(a: 1) -// plusOne 是类型为 Int -> Int的函数 -``` - -因为`plusOne`是函数`addTwoIntsCurried(a:)(b:)`绑定参数为`1`时结果,所以可以调用`plusOne`并且传入一个整型使其和`1`相加。 - -```swift -plusOne(10) -// 返回值为11 -``` - - -###抛出异常函数和抛出异常方法(Throwing Functions and Methods) - -可以抛出一个错误的函数或方法必需使用`throws`关键字标记。这些函数和方法被称为*抛出异常函数(throwing functions)*和*抛出异常方法(throwing methods)*。它们有着下面的形式: - -> func `function name`(`parameters`) throws -> -> `return type` { -> `statements` -> } - -调用一个抛出异常函数或抛出异常方法必需用一个`try`或者`try!`表达式来封装(也就是说,在一个范围内使用一个`try`或者`try!`运算符)。 - -`throws`关键字是函数的类型的一部分,不抛出异常的函数是抛出异常函数的一个子类型。所以,可以在使用抛出异常函数的地方使用不抛出异常函数。对于柯里化函数,`throws`关键字仅运用于最内层的函数。 - -不能重写一个仅基于是否能抛出错误的函数。也就是说,可以重载一个基于函数*参数(parameter)*能否抛出一个错误的函数。 - -一个抛出异常的方法不能重写一个不能抛出异常的方法,而且一个异常抛出方法不能满足一个协议对于不抛出异常方法的需求。也就是说,一个不抛出异常的方法可以重写一个抛出异常的方法,而且一个不抛出异常的方法可以满足一个协议对于抛出异常的需求。 - - -###重抛出异常函数和重抛出异常方法(Rethrowing Functions and Methods) - -一个函数或方法可以使用`rethrows`关键字来声明,从而表明仅当这个函数或方法的一个函数参数抛出错误时这个函数或方法才抛出错误。这些函数和方法被称为*重抛出异常函数(rethrowing functions)*和*重抛出异常方法(rethrowing methods)*。重抛出异常函数或方法必需有至少一个抛出异常函数参数。 - -``` - func functionWithCallback(callback: () throws -> Int) rethrows { - try callback() - } -``` -一个抛出异常函数方法不能重写一个重抛出异常函数方法,一个抛出异常方法不能满足一个协议对于重抛出异常方法的需求。也就是说,一个重抛出异常方法可以重写一个抛出异常方法,而且一个重抛出异常方法可以满足一个协议对于抛出异常方法的需求。 - - -> 函数声明语法 -> *函数声明* → [*函数头*](../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) **throws** [*函数结果*](../chapter3/05_Declarations.html#function_result) _可选_ -> *函数签名(signature)* → [*parameter-clauses*](../chapter3/05_Declarations.html#parameter_clauses) **rethrows** [*函数结果*](../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) - - -##枚举声明 - -在程序里使用*枚举声明(enumeration)*来引入一个枚举类型。 - -枚举声明有两种基本的形式,使用关键字`enum`来声明。枚举声明体使用从零开始的变量——叫做*枚举用例(enumeration cases)*,和任意数量的声明,包括计算型属性,实例方法,类型方法,构造器,类型别名,甚至其他枚举,结构体,和类。枚举声明不能包含析构器或者协议声明。 - -枚举类型可以采用任何数量的协议,但是这些协议不能从类,结构体和其他的枚举继承。 - -不像类或者结构体。枚举类型并不提供隐式的初始构造器,所有构造器必须显式的声明。构造器可以委托枚举中的其他构造器,但是构造过程仅当构造器将一个枚举用例指定给`self`才全部完成。 - -和结构体类似但是和类不同,枚举是值类型:枚举实例在赋予变量或常量时,或者被函数调用时被复制。 -更多关于值类型的信息,参见结构体和枚举都是[值类型(Structures and Enumerations Are Value Types)](../chapter2/09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)一节。 - -可以扩展枚举类型,正如在[扩展声明(Extension Declaration)](./05_Declarations.html#extension_declaration)中讨论的一样。 - - -###任意用例类型的枚举 - -如下的形式声明了一个包含任意类型枚举用例的枚举变量 - -> enum `enumeration name`: `adopted protocols`{ -> case `enumeration case 1` -> case `enumeration case 2`(`associated value types`) -> } - -这种形式的枚举声明在其他语言中有时被叫做*可识别联合(discrinminated)*。 - -这种形式中,每一个用例块由关键字`case`开始,后面紧接着一个或多个以逗号分隔的枚举用例。每一个用例名必须是独一无二的。每一个用例也可以指定它所存储的指定类型的值,这些类型在*关联值类型(associated values types)*的元组里被指定,立即书写在用例名后。 - -枚举用例也可以指定函数作为其存储的值,从而通过特定的关联值创建一个枚举实例。和真正的函数一样,你可以获取一个枚举用例的引用,然后在后续代码中调用它。 - -```swift -enum Number { - case Integer(Int) - case Real(Double) -} -let f = Number.Integer -// f is a function of type (Int) -> Number -// f 是一个传入 Int 返回 Number 类型的函数 - -// Apply f to create an array of Number instances with integer values -// 利用函数 f 把一个整数数组转成 Number 数组 -let evenInts: [Number] = [0, 2, 4, 6].map(f) -``` - -获得更多关于关联值类型的信息和例子,请查看[关联值(Associated Values)](../chapter2/08_Enumerations.html#associated_values)一节。 +> 2.2 +> 翻译:[星夜暮晨](https://github.com/SemperIdem) - - -### 间接的枚举 - -枚举有一个递归结构,就是说,枚举有着枚举类型自身实例的关联值的用例。然而,枚举类型的实例有值语义,意味着它们在内存中有着固定的位置。为了支持递归,编译器必需插入一个间接层。 - -为间接使用特殊的枚举用例,使用`indirect`声明修饰符标记。 - -> enum Tree { -> case Empty -> indirect case Node(value: T, left: Tree, right:Tree) -> } - -为了间接的使用一个枚举的所有用例,使用`indirect`修饰符标记整个枚举-当枚举有许多用例且每个用例都需要使用`indirect`修饰符标记的时候这将非常便利。 - -一个被`indirect`修饰符标记的枚举用例必需有一个关联值。一个使用`indirect`修饰符标记的枚举包含有着关联值的用例和没有关联值的用例的混合。就是说,它不能包含任何也使用`indirect`修饰符标记的用例。 - - - -###使用原始值类型用例的枚举(Enumerations with Cases of a Raw-Value Type) - -以下的形式声明了一个包含相同基础类型的枚举用例的枚举: - -> enum `enumeration name`: `raw value type`, `adopted protocols`{ -> case `enumeration case 1` = `raw value 1` -> case `enumeration case 2` = `raw value 2` -> } - -在这种形式中,每一个用例块由`case`关键字开始,后面紧接着一个或多个以逗号分隔的枚举用例。和第一种形式的枚举用例不同,这种形式的枚举用例包含一个同类型的基础值,叫做*原始值(raw value)*。这些值的类型在*原始值类型(raw-value type)*中被指定,必须表示一个整数,浮点数,字符串,或者一个字符。特别是*原始值类型(raw-value type)*必需遵守`Equatable`类型的协议和下列形式中的一种字面量构造协议(literal-convertible protocols):整型字面量有`IntergerLiteralConvertible`,浮点行字面量有`FloatingPointLiteralConvertible`,包含任意数量字符的字符串型字面量有`StringLiteralConvertible`,仅包含一个单一字符的字符串型字面量有`ExtendedGraphemeClusterLiteralConvertible`。每一个用例必须有唯一的名字,必须有一个唯一的初始值。 - -如果初始值类型被指定为`Int`,则不必为用例显式的指定值,它们会隐式的被标为值`0,1,2`等。每一个没有被赋值的`Int`类型时间会隐式的赋予一个初始值,它们是自动递增的。 - -```Swift -num ExampleEnum: Int { - case A, B, C = 5, D -} -``` - -在上面的例子中,`ExampleEnum.A`的值是`0`,`ExampleEnum.B`的值是`1`。因为`ExampleEnum.C`的值被显式的设定为`5`,因此`ExampleEnum.D`的值会自动增长为`6`。 - -如果原始值类型被指定为`String`类型,你不用明确的为用例指定值,每一个没有指定的用例会隐式地用与用例名字相同的字符串指定。 - -> enum WeekendDay: String { -> case Saturday, Sunday -> } - -在上面这个例子中,`WeekendDay.Saturday`的原始值是`"Saturday"`,`WeekendDay.Sunday`的原始值是`"Sunday"`。 - -拥有多种用例的原始值类型的枚举含蓄地遵循定义在Swift标准库中的`RawRepresentable`协议。所以,它们拥有一个原始值(`rawValue`)属性和一个有着`init?(rawValue: RawValue)`签名的可失败构造器(a failable initializer)。可以使用原始值属性去取的枚举用例的原始值,就像在`ExampleEnum.B.rawValue`中一样。如果有一个用例符合,也可以使用原始值去找到一个符合的用例,通过调用枚举的可失败构造器,如`ExampleEnum(rawValue: 5)`,这个可失败构造器返回一个可选的用例。想得到更多的信息和关于原始值类型查看更多信息和获取初始值类型用例的信息,参阅初始值[原始值(Raw Values)](../chapter2/08_Enumerations.html#raw_values)。 - - -###获得枚举用例 - -使用点(.)来引用枚举类型的用例,如`EnumerationType.EnumerationCase`。当枚举类型可以上下文推断出时,可以省略它(.仍然需要),参照枚举语法[(Enumeration Syntax)](../chapter2/08_Enumerations.html#enumeration_syntax)和[显式成员表达(Implicit Member Expression)](./04_Expressions.html#primary_expressions)。 - -使用`switch`语句来检验枚举用例的值,正如使用[switch语句匹配枚举值(Matching Enumeration Values with a Switch Statement)](../chapter2/08_Enumerations.html#matching_enumeration_values_with_a_switch_statement)一节描述的那样。枚举类型是模式匹配(pattern-matched)的,和其相反的是`switch`语句case块中枚举用例匹配,在[枚举用例类型(Enumeration Case Pattern)](./07_Patterns.html#enumeration_case_pattern)中有描述。 - - -> 枚举声明语法 -> *枚举声明* → [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [*访问级别修饰符*](TODO) _可选_ [*联合式枚举*](TODO) -> *枚举声明* → [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [*访问级别修饰符*](TODO) _可选_ [*原始值式枚举*](../chapter3/05_Declarations.html#raw_value_style_enum) -> *联合式枚举* → **indirect** _可选_ **enum** [*枚举名*](../chapter3/05_Declarations.html#enum_name) [*泛型参数子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [类型继承子句](TODO)_可选_ **{** [*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) _可选_ **indirect** _可选_ **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) -> *原始值式枚举* → **enum** [*枚举名*](../chapter3/05_Declarations.html#enum_name) [*泛型参数子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [*类型继承子句*](TODO) **{** [*原始值式枚举成员列表*](../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) _可选_ -> *原始值赋值* → **=** [*原始值字面量*](TODO) -> *原始值字面量* → [数字型字面量](TODO)|[字符串型字面量](TODO)|[布尔型字面量](TODO) - - -##结构体声明 - -使用*结构体声明(strucre declaration)*可以在程序里引入一个结构体类型。结构体声明使用`struct`关键字,遵循如下的形式: - -> struct `structure name`: `adopted protocols` { -> `declarations` -> } - -结构体内包含零或多个声明*声明(declarations)*。这些*声明(declarations)*可以包括存储型和计算型属性,类型属性,实例方法,类型方法,构造器,下标脚本,类型别名,甚至其他结构体,类,和枚举声明。结构体声明不能包含析构器或者协议声明。详细讨论和包含多种结构体声明的实例,参见[类和结构体(Classes and Structures)](../chapter2/09_Classes_and_Structures.html)一节。 - -结构体可以包含任意数量的协议,但是不能继承自类,枚举或者其他结构体。 - -有三种方法可以创建一个声明过的结构体实例: - -* 调用结构体内声明的构造器,参照[构造器(Initializers)](../chapter2/14_Initialization.html#setting_initial_values_for_stored_properties)一节。 - -* 如果没有声明构造器,调用结构体的逐个构造器,详情参见[Memberwise Initializers for Structure Types](../chapter2/14_Initialization.html#memberwise_initializers_for_structure_types)。 - -* 如果没有声明析构器,结构体的所有属性都有初始值,调用结构体的默认构造器,详情参见[默认构造器(Default Initializers)](../chapter2/14_Initialization.html#default_initializers)。 - -结构体的构造过程参见[构造过程(Initiaization)](../chapter2/14_Initialization.html)一节。 - -结构体实例属性可以用点(.)来获得,详情参见[属性访问(Accessing Properties)](../chapter2/09_Classes_and_Structures.html#comparing_classes_and_structures)一节。 - -结构体是值类型;结构体的实例在被赋予变量或常量,被函数调用时被复制。获得关于值类型更多信息,参见 -[结构体和枚举是值类型(Structures and Enumerations Are Value Types)](../chapter2/09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)一节。 - -可以使用扩展声明来扩展结构体类型的行为,参见[扩展声明(Extension Declaration)](../chapter3/05_Declarations.html#extension_declaration)。 - - -> 结构体声明语法 -> *结构体声明* → [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [访问级别修饰符](TODO )_可选_ **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 declaration)*来引入一个类。类声明使用关键字`class`,遵循如下的形式: - -> class `class name`: `superclass`, `adopted protocols` { -> `declarations` -> } - -一个类内包含零或多个*声明(declarations)*。这些*声明(declarations)*可以包括存储型和计算型属性,实例方法,类型方法,构造器,单独的析构器,下标脚本,类型别名,甚至其他结构体,类,和枚举声明。类声明不能包含协议声明。详细讨论和包含多种类声明的实例,参见[类和结构体(Classes and Structures)](../chapter2/09_Classes_and_Structures.html)一节。 - -一个类只能继承一个父类,*超类(superclass)*,但是可以包含任意数量的协议。*超类(superclass)*第一次出现在*类名(class name)*和冒号后面,其后跟着*采用的协议(adopted protocols)*。泛型类可以继承其它类型类和非泛型类,但是非泛型类只能继承其它的非泛型类。当在冒号后面写泛型超类的名称时,必须写那个泛型类的全名,包括它的泛型参数子句。 - -正如在[初始化声明(Initializer Declaration)](./chapter3/05_Declarations.html#initializer_declaration)谈及的那样,类可以有指定构造器和方便构造器。类的指定构造器必须初始化类所有的已声明的属性,它必须在超类构造器调用前被执行。 - -类可以重写属性,方法,下表脚本和它的超类构造器。重写的属性,方法,下标脚本,和指定构造器必须以`override`声明修饰符标记。 - -为了要求子类去实现超类的构造器,使用`required`声明修饰符去标记超类的构造器。在子类实现父类构造器时也必须使用`required`声明修饰符去标记。 - -虽然*超类(superclass)*的属性和方法声明可以被当前类继承,但是*超类(superclass)*声明的指定构造器却不能。这意味着,如果当前类重写了超类的所有指定构造器,它就继承了超类的方便构造器。Swift的类并不是继承自一个全局基础类。 - -有两种方法来创建已声明的类的实例: - -* 调用类的一个构造器,参见[构造器(Initializers)](../chapter2/14_Initialization.html)。 - -* 如果没有声明构造器,而且类的所有属性都被赋予了初始值,调用类的默认构造器,参见[默认构造器(Default Initializers)](../chapter2/14_Initialization.html#default_initializers)。 - -类实例属性可以用点(.)来获得,详情参见[属性访问(Accessing Properties)](../chapter2/09_Classes_and_Structures.html#comparing_classes_and_structures)一节。 - -类是引用类型;当被赋予常量或变量,函数调用时,类的实例是被引用,而不是复制。获得更多关于引用类型的信息,[结构体和枚举都是值类型(Structures and Enumerations Are Value Types)](../chapter2/09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)一节。 - -可以使用扩展声明来扩展类的行为,参见[扩展声明(Extension Declaration)](./05_Declarations.html#extension_declaration)。 - - -> 类声明语法 -> *类声明* → [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [访问级别修饰符](TODO)**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 declaration)*为程序引入一个命名了的协议类型。协议声明在一个全局访问的区域使用 `protocol` 关键词来进行声明并有下面这样的形式: - -> protocol `protocol name`: `inherited protocols` { -> `protocol member declarations` -> } - -协议的主体包含零或多个*协议成员声明(protocol member declarations)*,这些成员描述了任何采用该协议必须满足的一致性要求。特别的,一个协议可以声明必须实现某些属性、方法、初始化程序及下标脚本的一致性类型。协议也可以声明专用种类的类型别名,叫做*关联类型(associated types)*,它可以指定协议的不同声明之间的关系。协议声明不包括类,结构体,枚举或者其它协议的声明。协议成员声明会在下面的详情里进行讨论。 - -协议类型可以从很多其它协议那继承。当一个协议类型从其它协议那继承的时候,来自其它协议的所有要求就集合了,而且从当前协议继承的任何类型必须符合所有的这些要求。对于如何使用协议继承的例子,查看[协议继承(Protocol Inheritance)](../chapter2/21_Protocols.html#protocol_inheritance) - -> 注意: -也可以使用协议合成类型集合多个协议的一致性要求,详情参见[协议合成类型(Protocol Composition Type)](../chapter3/03_Types.html#protocol_composition_type)和[协议合成(Protocol Composition)](../chapter2/21_Protocols.html#protocol_composition) - -可以通过采用在类型的扩展声明中的协议来为之前声明的类型添加协议一致性。在扩展中必须实现所有采用协议的要求。如果该类型已经实现了所有的要求,可以让这个扩展声明的主题留空。 - -默认地,符合某一个协议的类型必须实现所有声明在协议中的属性、方法和下标脚本。也就是说,可以用`optional`声明修饰符标注这些协议成员声明以指定它们的一致性类型实现是可选的。`optional`修饰符仅仅可以用于使用`objc`属性标记过的协议。这样的结果就是仅仅类类型可以采用并符合包含可选成员要求的协议。更多关于如何使用`optional`属性的信息及如何访问可选协议成员的指导——比如当不能肯定是否一致性的类型实现了它们——参见[可选协议要求(Optional Protocol Requirements)](../chapter2/21_Protocols.html#optional_protocol_requirements) - -为了限制协议的采用仅仅针对类类型,需要强制使用`class`来标记协议,通过将`class`关键在写在冒号后面的*继承协议列表(inherited protocols)*的第一个位置。例如,下面的协议形式只能被类类型采用: - -```swift -protocol SomeProtocol:class{ - /* Protocol member go here */ -} -``` - -任意继承自需要标记有`class`协议的协议都可以智能地仅能被类类型采用。 - ->注意: ->如果协议已经用`object`属性标记了,`class`条件就隐性地应用于该协议;没有必要再明确地使用`class`条件来标记该协议了。 - -协议是命名的类型,因此它们可以以另一个命名类型出现在代码的所有地方,就像[协议类型(Protocol as Types)](../chapter2/21_Protocols.html#protocols_as_types)里讨论的那样。然而不能构造一个协议的实例,因为协议实际上不提供它们指定的要求的实现。 - -可以使用协议来声明一个类的代理的方法或者应该实现的结构,就像[委托(代理)模式(Delegation)](../chapter2/21_Protocols.html#delegation)描述的那样。 - - -> 协议(Protocol)声明语法 -> *协议声明* → [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [*访问级别修饰符*](TODO) _可选_ **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) _可选_ - - -###协议属性声明 - -协议声明了一致性类型必须在协议声明的主体里通过引入一个*协议属性声明(protocol property declaraion)*来实现一个属性。协议属性声明有一种特殊的类型声明形式: - -> var `property name`: `type` { get set } - -同其它协议成员声明一样,这些属性声明仅仅针对符合该协议的类型声明了`getter`和`setter`要求。结果就是不需要在协议里它被声明的地方实现`getter`和`setter`。 - -`getter`和`setter`要求可以通过一致性类型以各种方式满足。如果属性声明包含`get`和`set`关键词,一致性类型就可以用可读写(实现了`getter`和`setter`)的存储型变量属性或计算型属性,但是属性不能以常量属性或只读计算型属性实现。如果属性声明仅仅包含`get`关键词的话,它可以作为任意类型的属性被实现。比如说实现了协议的属性要求的一致性类型,参见[属性要求(Property Requirements)](../chapter2/21_Protocols.html#property_requirements) - -更多参见[变量声明(Variabel Declaration)](../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) - - -###协议方法声明 - -协议声明了一致性类型必须在协议声明的主体里通过引入一个协议方法声明来实现一个方法。协议方法声明和函数方法声明有着相同的形式,包含如下两条规则:它们不包括函数体,不能在类的声明内为它们的参数提供初始值.举例来说,符合的类型执行协议必需的方法。参见[必需方法(Method Requirements)](../chapter2/22_Protocols.html#method_requirements)一节。 - -使用`static`声明修饰符可以在协议声明中声明一个类或必需的静态方法。执行这些方法的类用修饰符`class`声明。相反的,执行这些方法的结构体必须以`static`声明修饰符声明。如果想使用扩展方法,在扩展类时使用`class`修饰符,在扩展结构体时使用`static`修饰符。 - -更多请参阅[函数声明(Function Declaration)](../chapter3/05_Declarations.html#function_declaration)。 - - -> 协议方法声明语法 -> *协议方法声明* → [*函数头*](../chapter3/05_Declarations.html#function_head) [*函数名*](../chapter3/05_Declarations.html#function_name) [*泛型参数子句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [*函数签名(Signature)*](../chapter3/05_Declarations.html#function_signature) - - -###协议构造器声明 - -协议声明了一致性类型必须在协议声明的主体里通过引入一个协议构造器声明来实现一个构造器。协议构造器声明 -除了不包含构造器体外,和构造器声明有着相同的形式。 - -一个一致性类型可以通过实现一个非可失败构造器或者`init!`可失败构造器去满足一个非可失败协议构造器的需求。一个一致性类型通过实现任意类型的构造器可以满足一个可失败协议构造器的需求。 - -当一个类去实现一个构造器去满足一个协议的构造器的需求,如果这个类还没有用`final`声明修饰符标记,这个构造器必需使用`required`声明修饰符去标记。 - -更多请参阅[构造器声明(Initializer Declaration)](../chapter3/05_Declarations.html#initializer_declaration)。 - - -> 协议构造器声明语法 -> *协议构造器声明* → [*构造器头(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关键字,一致的类型必须*至少(at least)*包含一个getter语句,可以选择是否包含setter语句。 - -更多参阅[下标脚本声明(Subscript Declaration)](../chapter3/05_Declarations.html#subscript_declaration)。 - - -> 协议附属脚本声明语法 -> *协议附属脚本声明* → [*附属脚本头(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`指代和协议一致的可能的类型。获得更多信息和例子,查看[关联类型(Associated Types)](../chapter2/23_Generics.html#associated_types)一节或[类型别名声明(Type Alias Declaration)](../chapter3/05_Declarations.html#type_alias_declaration)一节。 - - -> 协议关联类型声明语法 -> *协议关联类型声明* → [*类型别名头(Head)*](../chapter3/05_Declarations.html#typealias_head) [*类型继承子句*](../chapter3/03_Types.html#type_inheritance_clause) _可选_ [*类型别名赋值*](../chapter3/05_Declarations.html#typealias_assignment) _可选_ - - -##构造器声明 - -*构造器(initializer)*声明会为程序内的类,结构体或枚举引入构造器。构造器使用关键字`init`来声明,遵循两条基本形式。 - -结构体,枚举,类可以有任意数量的构造器,但是类的构造器的规则和行为是不一样的。不像结构体和枚举那样,类有两种结构体,designed initializers 和convenience initializers,参见[构造过程(Initialization)](../chapter2/14_Initialization.html)一节。 - -如下的形式声明了结构体,枚举和类的指定构造器: - -> init(`parameters`) { -> `statements` -> } - -类的指定构造器将类的所有属性直接初始化。如果类有超类,它不能调用该类的其他构造器,它只能调用超类的一个指定构造器。如果该类从它的超类处继承了任何属性,这些属性在当前类内被赋值或修饰时,必须调用一个超类的指定构造器。 - -指定构造器可以在类声明的上下文中声明,因此它不能用扩展声明的方法加入一个类中。 - -结构体和枚举的构造器可以调用其他的已声明的构造器,委托其中一个或所有的构造器进行初始化过程。 - -以`convenience`声明修饰符来标记构造器声明来声明一个类的便利构造器: - -> convenience init(`parameters`) { -> `statements` -> } - -便利构造器可以将初始化过程委托给另一个便利构造器或类的一个指定构造器。这意味着,类的初始化过程必须 -以一个将所有类属性完全初始化的指定构造器的调用作为结束。便利构造器不能调用超类的构造器。 - -可以使用required声明修饰符,将便利构造器和指定构造器标记为每个子类的构造器都必须实现的。一个子类的关于这个构造器的实现也必须使用`required`声明修饰符标记。 - -默认情况下,声明在超类的构造器没有被子类继承。也就是说,如果一个子类使用默认的值去构造它所有的存储属性,而且没有定义任何自己的构造器,它将继承超类的构造器。如果子类重写所有超类的指定构造器,子类继承超类的便利构造器。 - -和方法,属性和下表脚本一样,需要使用`override`声明修饰符标记重写了的制定构造器。 - ->注意 ->如果使用`required`声明修饰符去标记一个构造器,当在子类中重写必要构造器时,也不要用`override`修饰符去标记构造器。 - -查看更多关于不同声明方法的构造器的例子,参阅[构造过程(Initialization)](../chapter2/14_Initialization.html)一节。 - - -###可失败构造器(Failable Initializers) - -*可失败构造器*是一种可以生成可选实例或者是一类构造器声明的隐式解析可选实例(an implicitly unwrapped optional instance)类型。所以,构造区通过返回`nil`来指明构造过程失败。 - -声明可以生成可选实例的可失败构造器,在构造器声明的`init`关键字后加追加一个问号(`init?`)。声明可生成隐式解析可选实例的可失败构造器,在构造器声明后追加一个叹号(`init!`)。使用`init?`可失败构造器生成结构体的一个可选实例的例子如下。 - -```swift -struct SomeStruct { - let string: String - //生成一个'SomeStruct'的可选实例 - init?(input: String) { - if input.isEmpty { - // 弃用'self' 返回 'nil' - } - string = input - } -} -``` - -除非必需处理结果的可选性,可以使用调用非可失败构造器的方式调用`init?`可失败构造器。 - -```swift -if let actualInstance = SomeStruct(input: "Hello") { - //'SomeStruct'实例相关 -} else { - //'SomeStruct'实例构造过程失败,构造器返回'nil' -} -``` - -在实现构造体的任何时间,结构体或者枚举的可失败构造器可以返回`nil`。然而,类的可失败构造器,仅在类的所有存储属性被构造之后且`self.init`或`super.init`被调用之后才返回`nil`(就是说,构造器的委托被执行)。 - -可失败构造器可以委托任何种类的构造器。非可失败可以委托其它非可失败构造器或者`init!`可失败构造器。 - -构造过程的失败由构造器的委托产生。特别的,如果可失败构造器代理一个构造器失败且返回`nil`,那么之后被委托的构造器也会失败且隐式的返回`nil`。如果非可失败构造器代理`init!`可失败构造器失败了且返回`nil`,那么后出现一个运行时错误(如同使用`!`操作符去解析一个有着`nil`值的可选项)。 - -可失败指定构造器可以在子类中任何一种指定构造器重写。非可失败指定构造器在子类中仅能通过非可失败构造器被重写。 - -得到更多的信息并且了解更多关于可失败构造器的例子,请参阅[可失败构造器(Failable Initializer)](。。/chapter2/14_Initialization.html#failable_initializers) - - -> 构造器声明语法 -> *构造器声明* → [*构造器头(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) _可选_ [*声明修饰符列表(modifiers)*](TODO) _可选_ **init** -> *构造器头(Head)* → [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修饰符列表(modifiers)*](TODO) _可选_ **init ?** -> *构造器头(Head)* → [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修饰符列表(modifiers)*](TODO) _可选_ **init !** -> *构造器主体* → [*代码块*](../chapter3/05_Declarations.html#code_block) - - -##析构声明 - -*析构声明(deinitializer declaration)*为类声明了一个析构器。析构器没有参数,遵循如下的格式: - -> deinit { -> `statements` -> } - -当类没有任何语句时将要被释放时,析构器会自动的被调用。析构器在类的声明体内只能被声明一次——但是不能在 -类的扩展声明内,每个类最多只能有一个。 - -子类继承了它的超类的析构器,在子类将要被释放时隐式的调用。子类在所有析构器被执行完毕前不会被释放。 - -析构器不会被直接调用。 - -查看例子和如何在类的声明中使用析构器,参见[析构过程Deinitialization](../chapter2/15_Deinitialization.html)一节。 - - -> 析构器声明语法 -> *析构器声明* → [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ **deinit** [*代码块*](../chapter3/05_Declarations.html#code_block) - - -##扩展声明 - -*扩展声明(extension declaration)*用于扩展一个现存的类,结构体,枚举的行为。扩展声明使用关键字`extension`,遵循如下的规则: - -> extension `type name`: `adopted protocols` { -> `declarations` -> } - -一个扩展声明体包括零个或多个*声明语句(declarations)*。这些*声明语句(declarations)*可以包括计算型属性,计算型类型属性,实例方法,类型方法,构造器,下标脚本声明,甚至其他结构体,类,和枚举声明。扩展声明不能包含析构器,协议声明,存储型属性,属性监测器或其他的扩展属性。详细讨论和查看包含多种扩展声明的实例,参见[扩展(Extensions)](../chapter2/21_Extensions.html)一节。 - -扩展声明可以向现存的类,结构体,枚举内添加*一致的协议(adopted protocols)*。扩展声明不能向一个类中添加继承的类,因此在*类型名称*的冒号后面仅能指定一个协议列表。 - -属性,方法,现存类型的构造器不能被它们类型的扩展所重写。 - -扩展声明可以包含构造器声明,这意味着,如果扩展的类型在其他模块中定义,构造器声明必须委托另一个在 -那个模块里声明的构造器来恰当的初始化。 - - -> 扩展(Extension)声明语法 -> *扩展声明* → [访问级别修饰符](TODO) _可选_ **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) _可选_ **}** - - -##下标脚本声明 - -*下标脚本(subscript)*声明用于向特定类型添加附属脚本支持,通常为访问集合,列表和序列的元素时提供语法便利。附属脚本声明使用关键字`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 name)*和封闭的括号是可选的。如果使用了setter名称,它会被当做传给setter的变量的名称。如果不使用setter名称,那么传给setter的变量的名称默认是`value`。*setter名称(setter name)*的类型必须与*返回类型(return type)*的类型相同。 - -可以在下标脚本声明的类型中,可以重载下标脚本,只要*参数列表(parameters)*或*返回类型(return type)*与先前的不同即可。也可以重写继承自超类的下标脚本声明。此时,必须使用`override`声明修饰符声明那个被重写的下标脚本。(待定) - -同样可以在协议声明的上下文中声明下标脚本,[协议下标脚本声明(Protocol Subscript Declaration)](../chapter3/05_Declarations.html#protocol_subscript_declaration)中有所描述。 - -更多关于下标脚本和下标脚本声明的例子,请参考[下标脚本(Subscripts)](../chapter2/12_Subscripts.html)。 - - -> 附属脚本声明语法 -> *附属脚本声明* → [*附属脚本头(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) _可选_ [*声明修饰符列表(declaration-modifiers)*](TODO) _可选_ **subscript** [*参数子句*](../chapter3/05_Declarations.html#parameter_clause) -> *附属脚本结果(Result)* → **->** [*特性(Attributes)列表*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type) - - -##运算符声明(translated by 林) - -*运算符声明(operator declaration)*会向程序中引入中缀、前缀或后缀运算符,它使用关键字`operator`声明。 - -可以声明三种不同的缀性:中缀、前缀和后缀。操作符的*缀性(fixity)*描述了操作符与它的操作数的相对位置。 - -运算符声明有三种基本形式,每种缀性各一种。运算符的缀性通过在`operator`关键字之前添加声明修饰符`infix`,`prefix`或`postfix`来指定。每种形式中,运算符的名字只能包含[运算符(Operators)](../chapter3/02_Lexical_Structure.html#operators)中定义的运算符字符。 - -下面的这种形式声明了一个新的中缀运算符: -> infix operator `operator name` { -> precedence `precedence level` -> associativity `associativity` -> } - -*中缀运算符(infix operator)*是二元运算符,它可以被置于两个操作数之间,比如表达式`1 + 2` 中的加法运算符(`+`)。 - -中缀运算符可以可选地指定优先级,结合性,或两者同时指定。 - -运算符的*优先级(precedence)*可以指定在没有括号包围的情况下,运算符与它的操作数如何紧密绑定的。可以使用上下文关键字`precedence`并*优先级(precedence level)*一起来指定一个运算符的优先级。*优先级(precedence level)*可以是0到255之间的任何一个数字(十进制整数);与十进制整数字面量不同的是,它不可以包含任何下划线字符。尽管优先级是一个特定的数字,但它仅用作与另一个运算符比较(大小)。也就是说,一个操作数可以同时被两个运算符使用时,例如`2 + 3 * 5`,优先级更高的运算符将优先与操作数绑定。 - -运算符的*结合性(associativit)*可以指定在没有括号包围的情况下,优先级相同的运算符以何种顺序被分组的。可以使用上下文关键字`associativity`并_结合性(associativity)_一起来指定一个运算符的结合性,其中_结合性_可以说是上下文关键字`left`,`right`或`none`中的任何一个。左结合运算符以从左到右的形式分组。例如,减法运算符(`-`)具有左结合性,因此`4 - 5 - 6`被以`(4 - 5) - 6`的形式分组,其结果为`-7`。 -右结合运算符以从右到左的形式分组,对于设置为`none`的非结合运算符,它们不以任何形式分组。具有相同优先级的非结合运算符,不可以互相邻接。例如,表达式`1 < 2 < 3`非法的。 - -声明时不指定任何优先级或结合性的中缀运算符,它们的优先级会被初始化为100,结合性被初始化为`none`。 - -下面的这种形式声明了一个新的前缀运算符: -> prefix operator `operator name`{} - -紧跟在操作数前边的*前缀运算符(prefix operator)*是一元运算符,例如表达式`++i`中的前缀递增运算符(`++`)。 - -前缀运算符的声明中不指定优先级。前缀运算符是非结合的。 - -下面的这种形式声明了一个新的后缀运算符: - -> postfix operator `operator name`{} - -紧跟在操作数后边的*后缀运算符(postfix operator)*是一元运算符,例如表达式`i++`中的前缀递增运算符(`++`)。 - -和前缀运算符一样,后缀运算符的声明中不指定优先级。后缀运算符是非结合的。 - -声明了一个新的运算符以后,需要声明一个跟这个运算符同名的函数来实现这个运算符。如果在实现一个前缀或者后缀操作符,也必须使用相符的`prefix`或者`postfix`声明修饰符标记函数声明。如果实现中缀操作符,不需要使用`infix`声明修饰符标记函数声明。如何实现一个新的运算符,请参考[Custom Operators](../chapter2/25_Advanced_Operators.html#custom_operators)。 - - -> 运算符声明语法 -> *运算符声明* → [*前置运算符声明*](../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** - - - -## 声明修饰符 - -*声明修饰符(Declaration modifiers)*是关键字或者说是上下文相关的关键字,它可以修改一个声明的行为或者含义。可以在一个声明的特性和引进该声明的关键字之间,指定一个声明修饰符,并写下它的关键字或上下文相关的关键字。 - -`dynamic` -可以将该修饰符用于任何可以出现在Objective-C中的类成员上。当将`dynamic`修饰符用于一个成员声明上时,对该成员的访问总是由Objective-C的实时系统动态地安排,而永远不会由编译器内联或去虚拟化。 -因为当一个声明被标识`dynamic`修饰符时,会由Objective-C的实时系统动态地安排,所以他们是被隐式的标识了`objc`特性的。 - -`final` - -该修饰符用于修饰一个类或类中的属性,方法,以及下标成员。如果用它修饰一个类,那么这个类则不能被继承。如果用它修饰类中的属性,方法或下标,则表示在子类中,它们不能被重写。 - -`lazy` - -该修饰符用于修饰类或结构体中的存储型变量属性,表示该属性的初始值最多只被计算和存储一次,且发生在第一次访问它时。如何使用`lazy`特性的一个例子,请见:[惰性存储型属性(Lazy Stored Properties)](../chapter2/10_Properties.html#lazy_stored_properties)。 - -`optional` - -该修饰符用于修饰一个类或类中的属性,方法,以及下标成员,表示遵循类型没有被要求实现这些成员。 -只能将`optional`修饰符用于被`objc`标识的协议。这样一来,只有类类型可以适配或遵循拥有可选成员需求的协议。关于如何使用`optional`修饰符,以及如何访问可选协议成员的指导(比如,不确定遵循类型是否已经实现了这些可选成员),可以参见[对可选协议的规定(Optional Protocol Requirements)](../chapter2/22_Protocols.html#optional_protocol_requirements)一章 - -`required` - -该修饰符用于修饰一个类的特定构造器或便捷构造器,表示该类所有的子类都需要实现该构造器。在子类实现该构造器时,同样必须使用`required`修饰符修饰该构造器。 - - -`weak` - -`weak`修饰符用于修饰一个变量或一个存储型变量属性,表示该变量或属性通过一个弱引用指向存储其值的对象。该变量或属性的类型必须是一个可选类类型。通过`weak`修饰符可避免强引用循环。关于`weak`修饰符的例子和更多信息,可以参见[弱引用(Weak References)](../chapter2/16_Automatic_Reference_Counting.html#resolving_strong_reference_cycles_between_class_instances)一章 - - -### 访问控制级别 - -Swift提供了三个级别的权限控制:`public`, `internal`, 和 `private`。可以给声明标识以下访问级别修饰符中的一个以指定声明的权限级别。访问控制在[访问控制(Access Control)](../chapter2/24_Access_Control.html)一章有详细说明。 - -`public` - -修饰符用于修饰声明时,表示该声明可被同一个模块中的代码访问。被`public`权限级别修饰符修饰的声明,还可被其他模块的代码访问,只要该模块注入了该声明所在的模块。 - -`internal` - -修饰符用于修饰声明时,表示该声明只能被同一模块中的代码访问。默认的,绝大多数声明会被隐式的标识上`internal`权限级别修饰符 - - -`private` - -修饰符用于修饰声明时,表示该声明只能被同一源文件中的代码访问。 - - -以上的任意一个权限级别修饰符都可以有选择的带上一个参数,该参数由关键字`set`和一对括号组成(比如,`private(set)`)。当想要指明一个变量或下标脚注的setter的访问级别要低于或等于该变量或下标脚注的实际访问级别时,使用这种格式的权限级别修饰符,就像[Getters and Setters](../chapter2/24_Access_Control.html#getters_and_setters)一章中讨论的一样。 - - ->声明修饰符的语法 - ->声明修饰符 → **class**­ | **convenience­**| **dynamic**­ | **final**­ | **infix**­ | **lazy­** | **mutating**­ | **nonmutating**­ | **optional**­ | **override**­ | **postfix**|­ **prefix­** | **required­** | **static**­ | **unowned­** | **unowned­(­safe­)**­ | **unowned­(­unsafe­)­** | **weak**­ - ->声明修饰符 → 权限级别修饰符­ -> ->访问级别修饰符 → **internal**­ | **internal­(­set­)­** -> ->访问级别修饰符 → **private­** | **private­(­set­)­** -> ->访问级别修饰符 → **public­** | **public­(­set­)­** -> ->访问级别修饰符 → [访问级别修饰符(access-level-modeifier)](#) [访问级别修饰符列表(access-level-modeifiers)](#) _可选_ -­ +本页包含内容: + +- [顶级代码](#top-level_code) +- [代码块](#code_blocks) +- [导入声明](#import_declaration) +- [常量声明](#constant_declaration) +- [变量声明](#variable_declaration) + - [存储型变量和存储型变量属性](#stored_variables_and_stored_variable_properties) + - [计算型变量和计算型属性](#computed_variables_and_computed_properties) + - [存储型变量和属性的观察器](#stored_variable_observers_and_property_observers) + - [类型变量属性](#type_variable_properties) +- [类型别名声明](#type_alias_declaration) +- [函数声明](#function_declaration) + - [参数名](#parameter_names) + - [输入输出参数](#in-out_parameters) + - [特殊参数](#special_kinds_of_parameters) + - [特殊方法](#special_kinds_of_methods) + - [抛出错误的函数和方法](#throwing_functions_and_methods) + - [重抛错误的函数和方法](#rethrowing_functions_and_methods) +- [枚举声明](#enumeration_declaration) + - [任意类型的枚举用例](#enumerations_with_cases_of_any_type) + - [递归枚举](#enumerations_with_indirection) + - [拥有原始值的枚举用例](#enumerations_with_cases_of_a_raw-value_type) + - [访问枚举用例](#accessing_enumeration_cases) +- [结构体声明](#structure_declaration) +- [类声明](#class_declaration) +- [协议声明](#protocol_declaration) + - [协议属性声明](#protocol_property_declaration) + - [协议方法声明](#protocol_method_declaration) + - [协议构造器声明](#protocol_initializer_declaration) + - [协议下标声明](#protocol_subscript_declaration) + - [协议关联类型声明](#protocol_associated_type_declaration) +- [构造器声明](#initializer_declaration) + - [可失败构造器](#failable_initializers) +- [析构器声明](#deinitializer_declaration) +- [扩展声明](#extension_declaration) +- [下标声明](#subscript_declaration) +- [运算符声明](#operator_declaration) +- [声明修饰符](#declaration_modifiers) + - [访问控制级别](#access_control_levels) + +*声明 (declaration)* 用以向程序里引入新的名字或者结构。举例来说,可以使用声明来引入函数和方法,变量和常量,或者定义新的具有命名的枚举、结构、类和协议类型。还可以使用声明来扩展一个既有的具有命名的类型的行为,或者在程序里引入在其它地方声明的符号。 + +在 Swift 中,大多数声明在某种意义上讲也是定义,因为声明往往伴随着实现或初始化。由于协议并不提供实现,大多数协议成员仅仅只是声明而已。为了方便起见,也是因为这些区别在 Swift 中并不是很重要,“声明”这个术语同时包含了声明和定义两种含义。 + +> 声明语法 +> +> *声明* → [*导入声明*](#import-declaration) +> *声明* → [*常量声明*](#constant-declaration) +> *声明* → [*变量声明*](#variable-declaration) +> *声明* → [*类型别名声明*](#typealias-declaration) +> *声明* → [*函数声明*](#function-declaration) +> *声明* → [*枚举声明*](#enum-declaration) +> *声明* → [*结构体声明*](#struct-declaration) +> *声明* → [*类声明*](#class-declaration) +> *声明* → [*协议声明*](#protocol-declaration) +> *声明* → [*构造器声明*](#initializer-declaration) +> *声明* → [*析构器声明*](#deinitializer-declaration) +> *声明* → [*扩展声明*](#extension-declaration) +> *声明* → [*下标声明*](#subscript-declaration) +> *声明* → [*运算符声明*](#operator-declaration) +> +> *多条声明* → [*声明*](#declaration) [*多条声明*](#declarations)可选 + + +## 顶级代码 + +Swift 的源文件中的顶级代码 (top-level code) 由零个或多个语句、声明和表达式组成。默认情况下,在一个源文件的顶层声明的变量,常量和其他具有命名的声明可以被同模块中的每一个源文件中的代码访问。可以使用一个访问级别修饰符来标记声明来覆盖这种默认行为,请参阅 [访问控制级别](#access_control_levels)。 + +> 顶级声明语法 +> *顶级声明* → [*多条语句*](10_Statements.md#statements)可选 + + +## 代码块 + +*代码块 (code block)* 可以将一些声明和控制结构组织在一起。它有如下的形式: + +```swift +{ + 语句 +} +``` +代码块中的“语句”包括声明、表达式和各种其他类型的语句,它们按照在源码中的出现顺序被依次执行。 + +> 代码块语法 +> +> *代码块* → **{** [*多条语句*](10_Statements.md#statements)可选 **}** + + +## 导入声明 + +*导入声明 (import declaration)* 让你可以使用在其他文件中声明的内容。导入语句的基本形式是导入整个模块,它由 `import` 关键字和紧随其后的模块名组成: + +```swift +import 模块 +``` + +可以对导入操作提供更细致的控制,如指定一个特殊的子模块或者指定一个模块或子模块中的某个声明。提供了这些限制后,在当前作用域中,只有被导入的符号是可用的,而不是整个模块中的所有声明。 + +```swift +import 导入类型 模块.符号名 +import 模块.子模块 +``` + + +> 导入声明语法 +> +> *导入声明* → [*特性列表*](06_Attributes.md#attributes)可选 **import** [*导入类型*](#import-kind)可选 [*导入路径*](#import-path) +> +> *导入类型* → **typealias** | **struct** | **class** | **enum** | **protocol** | **var** | **func** +> +> *导入路径* → [*导入路径标识符*](#import-path-identifier) | [*导入路径标识符*](#import-path-identifier) **.** [*导入路径*](#import-path) +> +> *导入路径标识符* → [*标识符*](02_Lexical_Structure.md#identifier) | [*运算符*](02_Lexical_Structure.md#operator) + + +## 常量声明 + +*常量声明 (constant declaration)* 可以在程序中引入一个具有命名的常量。常量以关键字 `let` 来声明,遵循如下格式: + +```swift +let 常量名称: 类型 = 表达式 +``` + +常量声明在“常量名称”和用于初始化的“表达式”的值之间定义了一种不可变的绑定关系;当常量的值被设定之后,它就无法被更改。这意味着,如果常量以类对象来初始化,对象本身的内容是可以改变的,但是常量和该对象之间的绑定关系是不能改变的。 + +当一个常量被声明为全局常量时,它必须拥有一个初始值。在类或者结构中声明一个常量时,它将作为*常量属性 (constant property)*。常量声明不能是计算型属性,因此也没有存取方法。 + +如果常量名称是元组形式,元组中每一项的名称都会和初始化表达式中对应的值进行绑定。 + +```swift +let (firstNumber, secondNumber) = (10, 42) +``` + +在上例中,`firstNumber` 是一个值为 `10` 的常量,`secnodeName` 是一个值为 `42` 的常量。所有常量都可以独立地使用: + +```swift +print("The first number is \(firstNumber).") +// 打印 “The first number is 10.” +print("The second number is \(secondNumber).") +// 打印 “The second number is 42.” +``` + +当常量名称的类型 (`:` 类型) 可以被推断出时,类型标注在常量声明中是可选的,正如 [类型推断](03_Types.md#type_inference) 中所描述的。 + +声明一个常量类型属性要使用 `static` 声明修饰符。类型属性在 [类型属性](../chapter2/10_Properties.md#type_properties)中有介绍。 + +如果还想获得更多关于常量的信息或者想在使用中获得帮助,请参阅 [常量和变量](../chapter2/01_The_Basics.md#constants_and_variables) 和 [存储属性](../chapter2/10_Properties.md#stored_properties)。 + + +> 常量声明语法 +> +> *常量声明* → [*特性列表*](06_Attributes.md#attributes)可选 [*声明修饰符列表*](#declaration-modifiers)可选 **let** [*模式构造器列表*](pattern-initializer-list) +> +> *模式构造器列表* → [*模式构造器*](#pattern-initializer) | [*模式构造器*](#pattern-initializer) **,** [*模式构造器列表*](#pattern-initializer-list) +> +> *模式构造器* → [*模式*](07_Patterns.md#pattern) [*构造器*](#initializer)可选 +> +> *构造器* → **=** [*表达式*](04_Expressions.md#expression) + + +## 变量声明 + +*变量声明 (variable declaration)* 可以在程序中引入一个具有命名的变量,它以关键字 `var` 来声明。 + +变量声明有几种不同的形式,可以声明不同种类的命名值和可变值,如存储型和计算型变量和属性,属性观察器,以及静态变量属性。所使用的声明形式取决于变量声明的适用范围和打算声明的变量类型。 + +> 注意 +> 也可以在协议声明中声明属性,详情请参阅 [协议属性声明](#protocol_property_declaration)。 + +可以在子类中重写继承来的变量属性,使用 `override` 声明修饰符标记属性的声明即可,详情请参阅 [重写](../chapter2/13_Inheritance.md#overriding)。 + + +### 存储型变量和存储型变量属性 + +使用如下形式声明一个存储型变量或存储型变量属性: + +```swift +var 变量名称: 类型 = 表达式 +``` + +可以在全局范围,函数内部,或者在类和结构的声明中使用这种形式来声明一个变量。当变量以这种形式在全局范围或者函数内部被声明时,它代表一个存储型变量。当它在类或者结构中被声明时,它代表一个*存储型变量属性 (stored variable property)*。 + +用于初始化的表达式不可以在协议的声明中出现,在其他情况下,该表达式是可选的。如果没有初始化表达式,那么变量声明必须包含类型标注 (`:` *type*)。 + +如同常量声明,如果变量名称是元组形式,元组中每一项的名称都会和初始化表达式中对应的值进行绑定。 + +正如名字所示,存储型变量和存储型变量属性的值会存储在内存中。 + + +### 计算型变量和计算型属性 + +使用如下形式声明一个计算型变量或计算型属性: + +```swift +var 变量名称: 类型 { + get { + 语句 + } + set(setter 名称) { + 语句 + } +} +``` + +可以在全局范围、函数内部,以及类、结构、枚举、扩展的声明中使用这种形式的声明。当变量以这种形式在全局范围或者函数内部被声明时,它表示一个计算型变量。当它在类、结构、枚举、扩展声明的上下文中被声明时,它表示一个*计算型属性 (computed property)*。 + +getter 用来读取变量值,setter 用来写入变量值。setter 子句是可选的,getter 子句是必须的。不过也可以将这些子句都省略,直接返回请求的值,正如在 [只读计算型属性](../chapter2/10_Properties.md#computed_properties) 中描述的那样。但是如果提供了一个 setter 子句,就必须也提供一个 getter 子句。 + +setter 的圆括号以及 setter 名称是可选的。如果提供了 setter 名称,它就会作为 setter 的参数名称使用。如果不提供 setter 名称,setter 的参数的默认名称为 `newValue`,正如在 [便捷 setter 声明](../chapter2/10_Properties.md#shorthand_setter_declaration) 中描述的那样。 + +与存储型变量和存储型属性不同,计算型变量和计算型属性的值不存储在内存中。 + +要获得更多关于计算型属性的信息和例子,请参阅 [计算型属性](../chapter2/10_Properties.md#computed_properties)。 + + +### 存储型变量和属性的观察器 + +可以在声明存储型变量或属性时提供 `willSet` 和 `didSet` 观察器。一个包含观察器的存储型变量或属性以如下形式声明: + +```swift +var 变量名称: 类型 = 表达式 { + willSet(setter 名称) { + 语句 + } + didSet(setter 名称) { + 语句 + } +} +``` + +可以在全局范围、函数内部,或者类、结构的声明中使用这种形式的声明。当变量以这种形式在全局范围或者函数内部被声明时,观察器表示一个存储型变量观察器。当它在类和结构的声明中被声明时,观察器表示一个属性观察器。 + +可以为任何存储型属性添加观察器。也可以通过重写父类属性的方式为任何继承的属性(无论是存储型还是计算型的)添加观察器 +,正如 [重写属性观察器](../chapter2/13_Inheritance.md#overriding_property_observers) 中所描述的。 + +用于初始化的表达式在类或者结构的声明中是可选的,但是在其他声明中则是必须的。如果可以从初始化表达式中推断出类型信息,那么可以不提供类型标注。 + +当变量或属性的值被改变时,`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` 则是可选的。 + +要获得更多信息以及查看如何使用属性观察器的例子,请参阅 [属性观察器](../chapter2/10_Properties.md#property_observers)。 + + +### 类型变量属性 + +要声明一个类型变量属性,用 `static` 声明修饰符标记该声明。类可以改用 `class` 声明修饰符标记类的类型计算型属性从而允许子类重写超类的实现。类型属性在 [类型属性](../chapter2/10_Properties.md#type_properties) 章节有详细讨论。 + +> 注意 +> 在一个类声明中,使用关键字 `static` 与同时使用 `class` 和 `final` 去标记一个声明的效果相同。 + + +> 变量声明语法 + + +> *变量声明* → [*变量声明头*](#variable-declaration-head) [*模式构造器列表*](#pattern-initializer-list) +> *变量声明* → [*变量声明头*](#variable-declaration-head) [*变量名称*](#variable-name) [*类型标注*](03_Types.md#type-annotation) [*代码块*](#code-block) +> *变量声明* → [*变量声明头*](#variable-declaration-head) [*变量名称*](#variable-name) [*类型标注*](03_Types.md#type-annotation) [*getter-setter代码块*](#getter-setter-block) +> *变量声明* → [*变量声明头*](#variable-declaration-head) [*变量名称*](#variable-name) [*类型标注*](03_Types.md#type-annotation) [*getter-setter关键字代码块*](#getter-setter-keyword-block) +> *变量声明* → [*变量声明头*](#variable-declaration-head) [*变量名称*](#variable-name) [*构造器*](#initializer) [*willSet-didSet代码块*](#willSet-didSet-block) +> *变量声明* → [*变量声明头*](#variable-declaration-head) [*变量名称*](#variable-name) [*类型标注*](03_Types.md#type-annotation) [*构造器*](#initializer)可选 [*willSet-didSet代码块*](#willSet-didSet-block) + + +> *变量声明头* → [*特性列表*](06_Attributes.md#attributes)可选 [*声明修饰符列表*](#declaration-modifiers)可选 **var** +> +> *变量名称* → [*标识符*](02_Lexical_Structure.md#identifier) + + +> *getter-setter 代码块* → [*代码块*](#code-block) +> *getter-setter 代码块* → **{** [*getter子句*](#getter-clause) [*setter子句*](#setter-clause)可选 **}** +> *getter-setter 代码块* → **{** [*setter子句*](#setter-clause) [*getter子句*](#getter-clause) **}** +> +> *getter 子句* → [*特性列表*](06_Attributes.md#attributes)可选 **get** [*代码块*](#code-block) +> +> *setter 子句* → [*特性列表*](06_Attributes.md#attributes)可选 **set** [*setter名称*](#setter-name)可选 [*代码块*](#code-block) +> +> *setter 名称* → **(** [*标识符*](02_Lexical_Structure.md#identifier) **)** + + +> *getter-setter 关键字代码块* → **{** [*getter关键字子句*](#getter-keyword-clause) [*setter关键字子句*](#setter-keyword-clause)可选 **}** +> *getter-setter 关键字代码块* → **{** [*setter关键字子句*](#setter-keyword-clause) [*getter关键字子句*](#getter-keyword-clause) **}** +> +> *getter 关键字子句* → [*特性列表*](06_Attributes.md#attributes)可选 **get** +> +> *setter 关键字子句* → [*特性列表*](06_Attributes.md#attributes)可选 **set** + + +> *willSet-didSet 代码块* → **{** [*willSet子句*](#willSet-clause) [*didSet子句*](#didSet-clause)可选 **}** +> *willSet-didSet 代码块* → **{** [*didSet子句*](#didSet-clause) [*willSet子句*](#willSet-clause)可选 **}** +> +> *willSet 子句* → [*特性列表*](06_Attributes.md#attributes)可选 **willSet** [*setter名称*](#setter-name)可选 [*代码块*](#code-block) +> +> *didSet 子句* → [*特性列表*](06_Attributes.md#attributes)可选 **didSet** [*setter名称*](#setter-name)可选 [*代码块*](#code-block) + + +## 类型别名声明 + +*类型别名 (type alias)* 声明可以在程序中为一个既有类型声明一个别名。类型别名声明语句使用关键字 `typealias` 声明,遵循如下的形式: + +```swift +typealias 类型别名 = 现存类型 +``` + +当声明一个类型的别名后,可以在程序的任何地方使用“别名”来代替现有类型。现有类型可以是具有命名的类型或者混合类型。类型别名不产生新的类型,它只是使用别名来引用现有类型。 + +另请参阅 [协议关联类型声明](#protocol_associated_type_declaration)。 + + +> 类型别名声明语法 +> +> *类型别名声明* → [*类型别名头*](#typealias-head) [*类型别名赋值*](#typealias-assignment) +> +> *类型别名头* → [*特性列表*](06_Attributes.md#attributes)可选 [*访问级别修饰符*](#access-level-modifier)可选 **typealias** [*类型别名名称*](#typealias-name) +> +> *类型别名名称* → [*标识符*](02_Lexical_Structure.md#identifier) +> +> *类型别名赋值* → **=** [*类型*](03_Types.md#type) + + +## 函数声明 + +使用*函数声明 (function declaration)* 在程序中引入新的函数或者方法。在类、结构体、枚举,或者协议中声明的函数会作为方法。函数声明使用关键字 `func`,遵循如下的形式: + +```swift +func 函数名称(参数列表) -> 返回类型 { + 语句 +} +``` + +如果函数返回 `Void` 类型,返回类型可以省略,如下所示: + +```swift +func 函数名称(参数列表) { + 语句 +} +``` + +每个参数的类型都要标明,因为它们不能被推断出来。虽然函数的参数默认是常量,也可以在参数名前添加 `let` 来强调这一行为。如果您在某个参数名前面加上了 `inout`,那么这个参数就可以在这个函数作用域当中被修改。更多关于 `inout` 参数的讨论,请参阅 [输入输出参数](#in-out_parameters)。 + +函数可以使用元组类型作为返回类型来返回多个值。 + +函数定义可以出现在另一个函数声明内。这种函数被称作*嵌套函数 (nested function)*。更多关于嵌套函数的讨论,请参阅 [嵌套函数](../chapter2/06_Functions.md#Nested_Functions)。 + + +### 参数名 + +函数的参数列表由一个或多个函数参数组成,参数间以逗号分隔。函数调用时的参数顺序必须和函数声明时的参数顺序一致。最简单的参数列表有着如下的形式: + +`参数名称`: `参数类型` + +一个参数有一个内部名称,这个内部名称可以在函数体内被使用。还有一个外部名称,当调用函数时这个外部名称被作为实参的标签来使用。默认情况下,第一个参数的外部名称会被省略,第二个和之后的参数使用它们的内部名称作为它们的外部名称。例如: + +```swift +func f(x: Int, y: Int) -> Int { return x + y } +f(1, y: 2) // 参数 y 有标签,参数 x 则没有 +``` + +可以按照如下两种形式之一,重写参数名称的默认行为: + +`外部参数名称` `内部参数名称`: `参数类型` +_ `内部参数名称`: `参数类型` + +在内部参数名称前的名称会作为这个参数的外部名称,这个名称可以和内部参数的名称不同。外部参数名称在函数被调用时必须被使用,即对应的参数在方法或函数被调用时必须有外部名。 + +内部参数名称前的下划线(`_`)可使该参数在函数被调用时没有名称。在函数或方法调用时,对应的参数必须没有名字。 + +```swift +func f(x x: Int, withY y: Int, _ z: Int) -> Int { return x + y + z } +f(x: 1, withY: 2, 3) // 参数 x 和 y 是有标签的,参数 z 则没有 +``` + + +### 输入输出参数 + +输入输出参数被传递时遵循如下规则: + +1. 函数调用时,参数的值被拷贝。 +2. 函数体内部,拷贝后的值被修改。 +3. 函数返回后,拷贝后的值被赋值给原参数。 + +这种行为被称为*拷入拷出 (copy-in copy-out)* 或*值结果调用 (call by value result)*。例如,当一个计算型属性或者一个具有属性观察器的属性被用作函数的输入输出参数时,其 getter 会在函数调用时被调用,而其 setter 会在函数返回时被调用。 + +作为一种优化手段,当参数值存储在内存中的物理地址时,在函数体内部和外部均会使用同一内存位置。这种优化行为被称为*引用调用 (call by reference)*,它满足了拷入拷出模式的所有要求,且消除了复制带来的开销。在代码中,要规范使用拷入拷出模式,不要依赖于引用调用。 + +不要使用传递给输入输出参数的值,即使原始值在当前作用域中依然可用。当函数返回时,你对原始值所做的更改会被拷贝的值所覆盖。不要依赖于引用调用的优化机制来试图避免这种覆盖。 + +不能将同一个值传递给多个输入输出参数,因为这种情况下的拷贝与覆盖行为的顺序是不确定的,因此原始值的最终值也将无法确定。例如: + +```swift +var x = 10 +func f(inout a: Int, inout _ b: Int) { + a += 1 + b += 10 +} +f(&x, &x) // 编译报错 error: inout arguments are not allowed to alias each other +``` + +如果嵌套函数在外层函数返回后才调用,嵌套函数对输入输出参数造成的任何改变将不会影响到原始值。例如: + +```swift +func outer(inout a: Int) -> () -> Void { + func inner() { + a += 1 + } + return inner +} + +var x = 10 +let f = outer(&x) +f() +print(x) +// 打印 “10” +``` + +调用嵌套函数 `inner()` 对 `a` 递增后,`x` 的值并未发生改变,因为 `inner()` 在外层函数 `outer()` 返回后才被调用。若要改变 `x` 的值,必须在 `outer()` 返回前调用 `inner()`。 + +关于输入输出参数的详细讨论,请参阅 [输入输出参数](../chapter2/06_Functions.md#in_out_parameters)。 + + +### 特殊参数 + +参数可以被忽略,数量可以不固定,还可以为其提供默认值,使用形式如下: + +```swift +_ : 参数类型 +参数名称: 参数类型... +参数名称: 参数类型 = 默认参数值 +``` + +以下划线(`_`)命名的参数会被显式忽略,无法在函数体内使用。 + +一个参数的基本类型名称如果紧跟着三个点(`...`),会被视为可变参数。一个函数至多可以拥有一个可变参数,且必须是最后一个参数。可变参数会作为包含该参数类型元素的数组处理。举例来讲,可变参数 `Int...` 会作为 `[Int]` 来处理。关于使用可变参数的例子,请参阅 [可变参数](../chapter2/06_Functions.md#variadic_parameters)。 + +如果在参数类型后面有一个以等号(`=`)连接的表达式,该参数会拥有默认值,即给定表达式的值。当函数被调用时,给定的表达式会被求值。如果参数在函数调用时被省略了,就会使用其默认值。 + +```swift +func f(x: Int = 42) -> Int { return x } +f() // 有效,使用默认值 +f(7) // 有效,提供了值 +f(x: 7) // 无效,该参数没有外部名称 +``` + + +### 特殊方法 + +枚举或结构体的方法如果会修改 `self`,则必须以 `mutating` 声明修饰符标记。 + +子类重写超类中的方法必须以 `override` 声明修饰符标记。重写方法时不使用 `override` 修饰符,或者被 `override` 修饰符修饰的方法并未对超类方法构成重写,都会导致编译错误。 + +枚举或者结构体中的类型方法,要以 `static` 声明修饰符标记,而对于类中的类型方法,除了使用 `static`,还可使用 `class` 声明修饰符标记。 + + +### 抛出错误的函数和方法 + +可以抛出错误的函数或方法必须使用 `throws` 关键字标记。这类函数和方法被称为抛出函数和抛出方法。它们有着下面的形式: + +```swift +func 函数名称(参数列表) throws -> 返回类型 { + 语句 +} +``` + +抛出函数或抛出方法的调用必须包裹在 `try` 或者 `try!` 表达式中(也就是说,在作用域内使用 `try` 或者 `try!` 运算符)。 + +`throws` 关键字是函数的类型的一部分,非抛出函数是抛出函数的子类型。所以,可以在使用抛出函数的地方使用非抛出函数。 + +不能仅基于函数能否抛出错误来进行函数重载。也就是说,可以基于函数的函数类型的参数能否抛出错误来进行函数重载。 + +抛出方法不能重写非抛出方法,而且抛出方法不能满足协议对于非抛出方法的要求。也就是说,非抛出方法可以重写抛出方法,而且非抛出方法可以满足协议对于抛出方法的要求。 + + +### 重抛错误的函数和方法 + +函数或方法可以使用 `rethrows` 关键字来声明,从而表明仅当该函数或方法的一个函数类型的参数抛出错误时,该函数或方法才抛出错误。这类函数和方法被称为重抛函数和重抛方法。重新抛出错误的函数或方法必须至少有一个参数的类型为抛出函数。 + +```swift +func functionWithCallback(callback: () throws -> Int) rethrows { + try callback() +} +``` + +重抛函数或者方法不能够从自身直接抛出任何错误,这意味着它不能够包含 `throw` 语句。它只能够传递作为参数的抛出函数所抛出的错误。例如,在 `do-catch` 代码块中调用抛出函数,并在 `catch` 子句中抛出其它错误都是不允许的。 + +抛出方法不能重写重抛方法,而且抛出方法不能满足协议对于重抛方法的要求。也就是说,重抛方法可以重写抛出方法,而且重抛方法可以满足协议对于抛出方法的要求。 + + +> 函数声明语法 + + +> *函数声明* → [*函数头*](#function-head) [*函数名*](#function-name) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [*函数签名*](#function-signature) [*函数体*](#function-body)可选 + + +> *函数头* → [*特性列表*](06_Attributes.md#attributes)可选 [*声明修饰符列表*](#declaration-modifiers)可选 **func** +> +> *函数名* → [*标识符*](02_Lexical_Structure.md#identifier) | [*运算符*](02_Lexical_Structure.md#operator) + + +> *函数签名* → [*参数子句列表*](#parameter-clauses) **throws**可选 [*函数结果*](#function-result)可选 +> *函数签名* → [*参数子句列表*](#parameter-clauses) **rethrows** [*函数结果*](#function-result)可选 +> +> *函数结果* → **->** [*特性列表*](06_Attributes.md#attributes)可选 [*类型*](03_Types.md#type) +> +> *函数体* → [*代码块*](#code-block) + + +> *参数子句列表* → [*参数子句*](#parameter-clause) [*参数子句列表*](#parameter-clauses)可选 +> +> *参数子句* → **(** **)** | **(** [*参数列表*](#parameter-list) **)** +> +> *参数列表* → [*参数*](#parameter) | [*参数*](#parameter) **,** [*参数列表*](#parameter-list) +> +> *参数* → **let**可选 [*外部参数名*](#external-parameter-name)可选 [*内部参数名*](#local-parameter-name) [*类型标注*](03_Types.md#type-annotation) [*默认参数子句*](#default-argument-clause)可选 +> *参数* → **inout** [*外部参数名*](#external-parameter-name)可选 [*内部参数名*](#local-parameter-name) [*类型标注*](03_Types.md#type-annotation) +> *参数* → [*外部参数名*](#external-parameter-name)可选 [*内部参数名*](#local-parameter-name) [*类型标注*](03_Types.md#type-annotation) **...** +> +> *外部参数名* → [*标识符*](02_Lexical_Structure.md#identifier) | **_** +> +> *内部参数名* → [*标识符*](02_Lexical_Structure.md#identifier) | **_** +> +> *默认参数子句* → **=** [*表达式*](04_Expressions.md#expression) + + +## 枚举声明 + +在程序中使用*枚举声明 (enumeration declaration)* 来引入一个枚举类型。 + +枚举声明有两种基本形式,使用关键字 `enum` 来声明。枚举声明体包含零个或多个值,称为枚举用例,还可包含任意数量的声明,包括计算型属性、实例方法、类型方法、构造器、类型别名,甚至其他枚举、结构体和类。枚举声明不能包含析构器或者协议声明。 + +枚举类型可以采纳任意数量的协议,但是枚举不能从类、结构体和其他枚举继承。 + +不同于类或者结构体,枚举类型并不隐式提供默认构造器,所有构造器必须显式声明。一个构造器可以委托给枚举中的其他构造器,但是构造过程仅当构造器将一个枚举用例赋值给 `self` 后才算完成。 + +和结构体类似但是和类不同,枚举是值类型。枚举实例在被赋值到变量或常量时,或者传递给函数作为参数时会被复制。更多关于值类型的信息,请参阅 [结构体和枚举是值类型](../chapter2/09_Classes_and_Structures.md#structures_and_enumerations_are_value_types)。 + +可以扩展枚举类型,正如在 [扩展声明](#extension_declaration) 中讨论的一样。 + + +### 任意类型的枚举用例 + +如下的形式声明了一个包含任意类型枚举用例的枚举变量: + +```swift +enum 枚举名称: 采纳的协议 { + case 枚举用例1 + case 枚举用例2(关联值类型) +} +``` + +这种形式的枚举声明在其他语言中有时被叫做可识别联合。 + +在这种形式中,每个用例块由关键字 `case` 开始,后面紧接一个或多个以逗号分隔的枚举用例。每个用例名必须是独一无二的。每个用例也可以指定它所存储的指定类型的值,这些类型在关联值类型的元组中被指定,紧跟用例名之后。 + +具有关联值的枚举用例可以像函数一样使用,通过指定的关联值创建枚举实例。和真正的函数一样,你可以获取枚举用例的引用,然后在后续代码中调用它。 + +```swift +enum Number { + case Integer(Int) + case Real(Double) +} + +// f 的类型为 (Int) -> Number +let f = Number.Integer + +// 利用 f 把一个整数数组转成 Number 数组 +let evenInts: [Number] = [0, 2, 4, 6].map(f) +``` + +要获得更多关于具有关联值的枚举用例的信息和例子,请参阅 [关联值](../chapter2/08_Enumerations.md#associated_values)。 + + +#### 递归枚举 + +枚举类型可以具有递归结构,就是说,枚举用例的关联值类型可以是枚举类型自身。然而,枚举类型的实例具有值语义,这意味着它们在内存中有固定布局。为了支持递归,编译器必须插入一个间接层。 + +要让某个枚举用例支持递归,使用 `indirect` 声明修饰符标记该用例。 + +```swift +enum Tree { + case Empty + indirect case Node(value: T, left: Tree, right:Tree) +} +``` + +要让一个枚举类型的所有用例都支持递归,使用 `indirect` 修饰符标记整个枚举类型,当枚举有多个用例且每个用例都需要使用 `indirect` 修饰符标记的时候这将非常便利。 + +被 `indirect` 修饰符标记的枚举用例必须有一个关联值。使用 `indirect` 修饰符标记的枚举类型可以既包含有关联值的用例,同时还可包含没有关联值的用例。但是,它不能再单独使用 `indirect` 修饰符来标记某个用例。 + + +### 拥有原始值的枚举用例 + +以下形式声明了一种枚举类型,其中各个枚举用例的类型均为同一种基本类型: + +```swift +enum 枚举名称: 原始值类型, 采纳的协议 { + case 枚举用例1 = 原始值1 + case 枚举用例2 = 原始值2 +} +``` + +在这种形式中,每一个用例块由 `case` 关键字开始,后面紧跟一个或多个以逗号分隔的枚举用例。和第一种形式的枚举用例不同,这种形式的枚举用例包含一个基础值,叫做原始值,各个枚举用例的原始值的类型必须相同。这些原始值的类型通过原始值类型指定,必须表示一个整数、浮点数、字符串或者字符。原始值类型必须符合 `Equatable` 协议和下列字面量转换协议中的一种:整型字面量需符合 `IntergerLiteralConvertible` 协议,浮点型字面量需符合 `FloatingPointLiteralConvertible` 协议,包含任意数量字符的字符串型字面量需符合 `StringLiteralConvertible` 协议,仅包含一个单一字符的字符串型字面量需符合 `ExtendedGraphemeClusterLiteralConvertible` 协议。每一个用例的名字和原始值必须唯一。 + +如果原始值类型被指定为 `Int`,则不必为用例显式地指定原始值,它们会隐式地被赋值 `0`、`1`、`2` 等。每个未被赋值的 `Int` 类型的用例会被隐式地赋值,其值为上一个用例的原始值加 `1`。 + +```Swift +enum ExampleEnum: Int { + case A, B, C = 5, D +} +``` + +在上面的例子中,`ExampleEnum.A` 的原始值是 `0`,`ExampleEnum.B` 的原始值是 `1`。因为 `ExampleEnum.C` 的原始值被显式地设定为 `5`,因此 `ExampleEnum.D` 的原始值会自动增长为 `6`。 + +如果原始值类型被指定为 `String` 类型,你不用明确地为用例指定原始值,每个没有指定原始值的用例会隐式地将用例名字作为原始值。 + +```swift +enum WeekendDay: String { + case Saturday, Sunday +} +``` + +在上面这个例子中,`WeekendDay.Saturday` 的原始值是 `"Saturday"`,`WeekendDay.Sunday` 的原始值是 `"Sunday"`。 + +枚举用例具有原始值的枚举类型隐式地符合定义在 Swift 标准库中的 `RawRepresentable` 协议。所以,它们拥有一个 `rawValue` 属性和一个可失败构造器 `init?(rawValue: RawValue)`。可以使用 `rawValue` 属性去获取枚举用例的原始值,例如 `ExampleEnum.B.rawValue`。还可以根据原始值来创建一个相对应的枚举用例,只需调用枚举的可失败构造器,例如 `ExampleEnum(rawValue: 5)`,这个可失败构造器返回一个可选类型的用例。要获得更多关于具有原始值的枚举用例的信息和例子,请参阅 [原始值](../chapter2/08_Enumerations.md#raw_values)。 + + +### 访问枚举用例 + +使用点语法(`.`)来引用枚举类型的枚举用例,例如 `EnumerationType.EnumerationCase`。当枚举类型可以由上下文推断而出时,可以省略它(但是 `.` 仍然需要),正如 [枚举语法](../chapter2/08_Enumerations.md#enumeration_syntax) 和 [显式成员表达式](04_Expressions.md#explicit_member_expression) 所述。 + +可以使用 `switch` 语句来检验枚举用例的值,正如 [使用 switch 语句匹配枚举值](../chapter2/08_Enumerations.md#matching_enumeration_values_with_a_switch_statement) 所述。枚举类型是模式匹配的,依靠 `switch` 语句 `case` 块中的枚举用例模式,正如 [枚举用例模式](07_Patterns.md#enumeration_case_pattern) 所述。 + + +> 枚举声明语法 + + +> *枚举声明* → [*特性列表*](06_Attributes.md#attributes)可选 [*访问级别修饰符*](#access-level-modifier)可选 [*联合风格枚举*](#union-style-enum) +> *枚举声明* → [*特性列表*](06_Attributes.md#attributes)可选 [*访问级别修饰符*](#access-level-modifier) 可选 [*原始值风格枚举*](#raw-value-style-enum) + + +> *联合风格枚举* → **indirect**可选 **enum** [*枚举名称*](#enum-name) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [类型继承子句](03_Types.md#type-inheritance-clause)可选 **{** [*多个联合风格枚举成员*](#union-style-enum-members)可选 **}** +> +> *多个联合风格枚举成员* → [*联合风格枚举成员*](#union-style-enum-member) [*多个联合风格枚举成员*](#union-style-enum-members)可选 +> +> *联合风格枚举成员* → [*声明*](#declaration) | [*联合风格枚举用例子句*](#union-style-enum-case-clause) +> +> *联合风格枚举用例子句* → [*特性列表*](06_Attributes.md#attributes)可选 **indirect**可选 **case** [*联合风格枚举用例列表*](#union-style-enum-case-list) +> +> *联合风格枚举用例列表* → [*联合风格枚举用例*](#union-style-enum-case) | [*联合风格枚举用例*](#union-style-enum-case) **,** [*联合风格枚举用例列表*](#union-style-enum-case-list) +> +> *联合风格枚举用例* → [*枚举用例名称*](#enum-case-name) [*元组类型*](03_Types.md#tuple-type)可选 +> +> *枚举名称* → [*标识符*](02_Lexical_Structure.md#identifier) +> +> *枚举用例名称* → [*标识符*](02_Lexical_Structure.md#identifier) + + +> *原始值风格枚举* → **enum** [*枚举名称*](#enum-name) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [类型继承子句](03_Types.md#type-inheritance-clause) **{** [*多个原始值风格枚举成员*](#raw-value-style-enum-members) **}** +> +> *多个原始值风格枚举成员* → [*原始值风格枚举成员*](#raw-value-style-enum-member) [*多个原始值风格枚举成员*](#raw-value-style-enum-members)可选 +> +> *原始值风格枚举成员* → [*声明*](#declaration) | [*原始值风格枚举用例子句*](#raw-value-style-enum-case-clause) +> +> *原始值风格枚举用例子句* → [*特性列表*](06_Attributes.md#attributes)可选 **case** [*原始值风格枚举用例列表*](#raw-value-style-enum-case-list) +> +> *原始值风格枚举用例列表* → [*原始值风格枚举用例*](#raw-value-style-enum-case) | [*原始值风格枚举用例*](#raw-value-style-enum-case) **,** [*原始值风格枚举用例列表*](#raw-value-style-enum-case-list) +> +> *原始值风格枚举用例* → [*枚举用例名称*](#enum-case-name) [*原始值赋值*](#raw-value-assignment)可选 +> +> *原始值赋值* → **=** [*原始值字面量*](#raw-value-literal) +> +> *原始值字面量* → [数字型字面量](02_Lexical_Structure.md#numeric-literal) | [字符串型字面量](02_Lexical_Structure.md#static-string-literal) | [布尔型字面量](02_Lexical_Structure.md#boolean-literal) + + +## 结构体声明 + +使用*结构体声明 (structure declaration)* 可以在程序中引入一个结构体类型。结构体声明使用 `struct` 关键字,遵循如下的形式: + +```swift +struct 结构体名称: 采纳的协议 { + 多条声明 +} +``` + +结构体内可包含零个或多个声明。这些声明可以包括存储型和计算型属性、类型属性、实例方法、类型方法、构造器、下标、类型别名,甚至其他结构体、类、和枚举声明。结构体声明不能包含析构器或者协议声明。关于结构体的详细讨论和示例,请参阅 [类和结构体](../chapter2/09_Classes_and_Structures.md)。 + +结构体可以采纳任意数量的协议,但是不能继承自类、枚举或者其他结构体。 + +有三种方法可以创建一个已声明的结构体实例: + +* 调用结构体内声明的构造器,正如 [构造器](../chapter2/14_Initialization.md#initializers) 所述。 + +* 如果没有声明构造器,调用结构体的成员逐一构造器,正如 [结构体类型的成员逐一构造器](../chapter2/14_Initialization.md#memberwise_initializers_for_structure_types) 所述。 + +* 如果没有声明构造器,而且结构体的所有属性都有初始值,调用结构体的默认构造器,正如 [默认构造器](../chapter2/14_Initialization.md#default_initializers) 所述。 + +结构体的构造过程请参阅 [构造过程](../chapter2/14_Initialization.md)。 + +结构体实例的属性可以用点语法(`.`)来访问,正如 [访问属性](../chapter2/09_Classes_and_Structures.md#accessing_properties) 所述。 + +结构体是值类型。结构体的实例在被赋予变量或常量,或传递给函数作为参数时会被复制。关于值类型的更多信息,请参阅 +[结构体和枚举是值类型](../chapter2/09_Classes_and_Structures.md#structures_and_enumerations_are_value_types)。 + +可以使用扩展声明来扩展结构体类型的行为,请参阅 [扩展声明](#extension_declaration)。 + + +> 结构体声明语法 +> +> *结构体声明* → [*特性列表*](06_Attributes.md#attributes)可选 [*访问级别修饰符*](#access-level-modifier) 可选 **struct** [*结构体名称*](#struct-name) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [类型继承子句](03_Types.md#type-inheritance-clause)可选 [*结构体主体*](#struct-body) +> +> *结构体名称* → [*标识符*](02_Lexical_Structure.md#identifier) +> +> *结构体主体* → **{** [*多条声明*](#declarations)可选 **}** + + +## 类声明 + +可以在程序中使用*类声明 (class declaration)* 来引入一个类。类声明使用关键字 `class`,遵循如下的形式: + +```swift +class 类名: 超类, 采纳的协议 { + 多条声明 +} +``` + +类内可以包含零个或多个声明。这些声明可以包括存储型和计算型属性、实例方法、类型方法、构造器、唯一的析构器、下标、类型别名,甚至其他结构体、类和枚举声明。类声明不能包含协议声明。关于类的详细讨论和示例,请参阅 [类和结构体](../chapter2/09_Classes_and_Structures.md)。 + +一个类只能继承自一个超类,但是可以采纳任意数量的协议。超类紧跟在类名和冒号后面,其后跟着采纳的协议。泛型类可以继承自其它泛型类和非泛型类,但是非泛型类只能继承自其它非泛型类。当在冒号后面写泛型超类的名称时,必须写上泛型类的全名,包括它的泛型形参子句。 + +正如 [构造器声明](#initializer_declaration) 所讨论的,类可以有指定构造器和便利构造器。类的指定构造器必须初始化类中声明的所有属性,并且必须在调用超类构造器之前。 + +类可以重写属性、方法、下标以及构造器。重写的属性、方法、下标和指定构造器必须以 `override` 声明修饰符标记。 + +为了要求子类去实现超类的构造器,使用 `required` 声明修饰符标记超类的构造器。子类实现超类构造器时也必须使用 `required` 声明修饰符。 + +虽然超类属性和方法声明可以被当前类继承,但是超类声明的指定构造器却不能。即便如此,如果当前类重写了超类的所有指定构造器,它就会继承超类的所有便利构造器。Swift 的类并不继承自一个通用基础类。 + +有两种方法来创建已声明的类的实例: + +* 调用类中声明的构造器,请参阅 [构造器](../chapter2/14_Initialization.md#initializers)。 + +* 如果没有声明构造器,而且类的所有属性都被赋予了初始值,调用类的默认构造器,请参阅 [默认构造器](../chapter2/14_Initialization.md#default_initializers)。 + +类实例属性可以用点语法(`.`)来访问,请参阅 [访问属性](../chapter2/09_Classes_and_Structures.md#accessing_properties)。 + +类是引用类型。当被赋予常量或变量,或者传递给函数作为参数时,类的实例会被引用,而不是被复制。关于引用类型的更多信息,请参阅 [结构体和枚举是值类型](../chapter2/09_Classes_and_Structures.md#structures_and_enumerations_are_value_types)。 + +可以使用扩展声明来扩展类的行为,请参阅 [扩展声明](#extension_declaration)。 + + +> 类声明语法 +> +> *类声明* → [*特性列表*](06_Attributes.md#attributes)可选 [访问级别修饰符](#access-level-modifier)可选 **class** [*类名*](#class-name) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [*类型继承子句*](03_Types.md#type-inheritance-clause)可选 [*类主体*](#class-body) +> +> *类名* → [*标识符*](02_Lexical_Structure.md#identifier) +> +> *类主体* → **{** [*多条声明*](#declarations)可选 **}** + + +## 协议声明 + +*协议声明 (protocol declaration)* 可以为程序引入一个命名的协议类型。协议声明只能在全局区域使用 `protocol` 关键字来进行声明,并遵循如下形式: + +```swift +protocol 协议名称: 继承的协议 { + 协议成员声明 +} +``` + +协议的主体包含零个或多个协议成员声明,这些成员描述了任何采纳该协议的类型必须满足的一致性要求。一个协议可以声明采纳者必须实现的某些属性、方法、构造器以及下标。协议也可以声明各种各样的类型别名,叫做关联类型,它可以指定协议的不同声明之间的关系。协议声明不能包括类、结构体、枚举或者其它协议的声明。协议成员声明会在后面进行讨论。 + +协议类型可以继承自任意数量的其它协议。当一个协议类型继承自其它协议的时候,来自其它协议的所有要求会聚合在一起,而且采纳当前协议的类型必须符合所有的这些要求。关于如何使用协议继承的例子,请参阅 [协议继承](../chapter2/22_Protocols.md#protocol_inheritance)。 + +> 注意 +> 也可以使用协议合成类型来聚合多个协议的一致性要求,请参阅 [协议合成类型](03_Types.md#protocol_composition_type) 和 [协议合成](../chapter2/22_Protocols.md#protocol_composition)。 + +可以通过类型的扩展声明来采纳协议,从而为之前声明的类型添加协议一致性。在扩展中,必须实现所有采纳协议的要求。如果该类型已经实现了所有的要求,可以让这个扩展声明的主体留空。 + +默认地,符合某个协议的类型必须实现所有在协议中声明的属性、方法和下标。即便如此,可以用 `optional` 声明修饰符标注协议成员声明,以指定它们的实现是可选的。`optional` 修饰符仅仅可以用于使用 `objc` 特性标记过的协议。因此,仅仅类类型可以采用并符合包含可选成员要求的协议。更多关于如何使用 `optional` 声明修饰符的信息,以及如何访问可选协议成员的指导——例如不能确定采纳协议的类型是否实现了它们时——请参阅 [可选协议要求](../chapter2/22_Protocols.md#optional_protocol_requirements) + +为了限制协议只能被类类型采纳,需要使用 `class` 关键字来标记协议,将 `class` 关键在写在冒号后面的继承的协议列表的首位。例如,下面的协议只能被类类型采纳: + +```swift +protocol SomeProtocol: class { + /* 这里是协议成员 */ +} +``` + +任何继承自标记有 `class` 关键字的协议的协议也仅能被类类型采纳。 + +> 注意 +> 如果协议已经用 `objc` 特性标记了,`class` 要求就隐式地应用于该协议,无需显式使用 `class` 关键字。 + +协议类型是命名的类型,因此它们可以像其他命名类型一样使用,正如 [协议作为类型](../chapter2/22_Protocols.md#protocols_as_types) 所讨论的。然而,不能构造一个协议的实例,因为协议实际上不提供它们指定的要求的实现。 + +可以使用协议来声明作为代理的类或者结构体应该实现的方法,正如 [委托(代理)模式](../chapter2/22_Protocols.md#delegation) 中所述。 + + +> 协议声明语法 + + +> *协议声明* → [*特性列表*](06_Attributes.md#attributes)可选 [访问级别修饰符](#access-level-modifier)可选 **protocol** [*协议名称*](#protocol-name) [*类型继承子句*](03_Types.html#type-inheritance-clause)可选 [*协议主体*](#protocol-body) +> +> *协议名称* → [*标识符*](02_Lexical_Structure.md#identifier) +> +> *协议主体* → **{** [*协议成员声明列表*](#protocol-member-declarations)可选 **}** + + +> *协议成员声明* → [*协议属性声明*](#protocol-property-declaration) +> *协议成员声明* → [*协议方法声明*](#protocol-method-declaration) +> *协议成员声明* → [*协议构造器声明*](#protocol-initializer-declaration) +> *协议成员声明* → [*协议下标声明*](#protocol-subscript-declaration) +> *协议成员声明* → [*协议关联类型声明*](#protocol-associated-type-declaration) +> +> *协议成员声明列表* → [*协议成员声明*](#protocol-member-declaration) [*协议成员声明列表*](#protocol-member-declarations)可选 + + +### 协议属性声明 + +协议可以通过在协议声明主体中引入一个协议属性声明,来声明符合的类型必须实现的属性。协议属性声明有一种特殊的变量声明形式: + +```swift +var 属性名: 类型 { get set } +``` + +同其它协议成员声明一样,这些属性声明仅仅针对符合该协议的类型声明了 getter 和 setter 要求,你不能在协议中直接实现 getter 和 setter。 + +符合类型可以通过多种方式满足 getter 和 setter 要求。如果属性声明包含 `get` 和 `set` 关键字,符合类型就可以用存储型变量属性或可读可写的计算型属性来满足此要求,但是属性不能以常量属性或只读计算型属性实现。如果属性声明仅仅包含 `get` 关键字的话,它可以作为任意类型的属性被实现。关于如何实现协议中的属性要求的例子,请参阅 [属性要求](../chapter2/22_Protocols.md#property_requirements) + +另请参阅 [变量声明](#variable_declaration)。 + + +> 协议属性声明语法 +> +> *协议属性声明* → [*变量声明头*](#variable-declaration-head) [*变量名称*](#variable-name) [*类型标注*](03_Types.md#type-annotation) [*getter-setter关键字代码块*](#getter-setter-keyword-block) + + +### 协议方法声明 + +协议可以通过在协议声明主体中引入一个协议方法声明,来声明符合的类型必须实现的方法。协议方法声明和函数方法声明有着相同的形式,但有两项例外:它们不包括函数体,也不能包含默认参数。关于如何实现协议中的方法要求的例子,请参阅 [方法要求](../chapter2/22_Protocols.md#method_requirements)。 + +使用 `static` 声明修饰符可以在协议声明中声明一个类型方法。结构体实现这些方法时使用 `static` 声明修饰符,类在实现这些方法时,除了使用 `static` 声明修饰符,还可以选择使用 `class` 声明修饰符。通过扩展实现时亦是如此。 + +另请参阅 [函数声明](#function_declaration)。 + + +> 协议方法声明语法 +> +> *协议方法声明* → [*函数头*](#function-head) [*函数名*](#function-name) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [*函数签名*](#function-signature) + + +### 协议构造器声明 + +协议可以通过在协议声明主体中引入一个协议构造器声明,来声明符合的类型必须实现的构造器。协议构造器声明 +除了不包含实现主体外,和构造器声明有着相同的形式。 + +符合类型可以通过实现一个非可失败构造器或者 `init!` 可失败构造器来满足一个非可失败协议构造器的要求,可以通过实现任意类型的构造器来满足一个可失败协议构造器的要求。 + +类在实现一个构造器去满足一个协议的构造器要求时,如果这个类还没有用 `final` 声明修饰符标记,这个构造器必须用 `required` 声明修饰符标记。 + +另请参阅 [构造器声明](#initializer_declaration)。 + + +> 协议构造器声明语法 +> +> *协议构造器声明* → [*构造器头*](#initializer-head) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [*参数子句*](#parameter-clause) **throws**可选 +> *协议构造器声明* → [*构造器头*](#initializer-head) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [*参数子句*](#parameter-clause) **rethrows** + + +### 协议下标声明 + +协议可以通过在协议声明主体中引入一个协议下标声明,来声明符合的类型必须实现的下标。协议下标声明有一个特殊的下标声明形式: + +```swift +subscript (参数列表) -> 返回类型 { get set } +``` + +下标声明只为符合类型声明了 getter 和 setter 要求。如果下标声明包含 `get` 和 `set` 关键字,符合类型也必须实现 getter 和 setter 子句。如果下标声明只包含 `get` 关键字,符合类型必须实现 getter 子句,可以选择是否实现 setter 子句。 + +另请参阅 [下标声明](#subscript_declaration)。 + + +> 协议下标声明语法 +> +> *协议下标声明* → [*下标头*](#subscript-head) [*下标结果*](#subscript-result) [*getter-setter关键字代码块*](#getter-setter-keyword-block) + + +### 协议关联类型声明 + +使用关键字 `associatedtype` 来声明协议关联类型。关联类型为作为协议声明的一部分,为某种类型提供了一个别名。关联类型和泛型参数子句中的类型参数很相似,但是它们和 `Self` 一样,用于协议中。`Self` 指代采纳协议的类型。要获得更多信息和例子,请参阅 [关联类型](../chapter2/23_Generics.md#associated_types)。 + +另请参阅 [类型别名声明](#type_alias_declaration)。 + + +> 协议关联类型声明语法 +> +> *协议关联类型声明* → [*类型别名头*](#typealias-head) [*类型继承子句*](03_Types.md#type-inheritance-clause)可选 [*类型别名赋值*](#typealias-assignment)可选 + + +## 构造器声明 + +构造器声明会为程序中的类、结构体或枚举引入构造器。构造器使用关键字 `init` 来声明,有两种基本形式。 + +结构体、枚举、类可以有任意数量的构造器,但是类的构造器具有不同的规则和行为。不同于结构体和枚举,类有两种构造器,即指定构造器和便利构造器,请参阅 [构造过程](../chapter2/14_Initialization.md)。 + +采用如下形式声明结构体和枚举的构造器,以及类的指定构造器: + +```swift +init(参数列表) { + 构造语句 +} +``` + +类的指定构造器直接将类的所有属性初始化。它不能调用类中的其他构造器,如果类有超类,则必须调用超类的一个指定构造器。如果该类从它的超类继承了属性,必须在调用超类的指定构造器后才能修改这些属性。 + +指定构造器只能在类声明中声明,不能在扩展声明中声明。 + +结构体和枚举的构造器可以调用其他已声明的构造器,从而委托其他构造器来进行部分或者全部构造过程。 + +要为类声明一个便利构造器,用 `convenience` 声明修饰符来标记构造器声明: + +```swift +convenience init(参数列表) { + 构造语句 +} +``` + +便利构造器可以将构造过程委托给另一个便利构造器或一个指定构造器。但是,类的构造过程必须以一个将类中所有属性完全初始化的指定构造器的调用作为结束。便利构造器不能调用超类的构造器。 + +可以使用 `required` 声明修饰符,将便利构造器和指定构造器标记为每个子类都必须实现的构造器。这种构造器的子类实现也必须使用 `required` 声明修饰符标记。 + +默认情况下,超类中的构造器不会被子类继承。但是,如果子类的所有存储型属性都有默认值,而且子类自身没有定义任何构造器,它将继承超类的构造器。如果子类重写了超类的所有指定构造器,子类将继承超类的所有便利构造器。 + +和方法、属性和下标一样,需要使用 `override` 声明修饰符标记重写的指定构造器。 + +> 注意 +> 如果使用 `required` 声明修饰符标记一个构造器,在子类中重写这种构造器时,无需使用 `override` 修饰符。 + +就像函数和方法,构造器也可以抛出或者重抛错误,你可以在构造器参数列表的圆括号之后使用 `throws` 或 `rethrows` 关键字来表明相应的抛出行为。 + +关于在不同类型中声明构造器的例子,请参阅 [构造过程](../chapter2/14_Initialization.md)。 + + +### 可失败构造器 + +可失败构造器可以生成所属类型的可选实例或者隐式解包可选实例,因此,这种构造器通过返回 `nil` 来指明构造过程失败。 + +声明生成可选实例的可失败构造器时,在构造器声明的 `init` 关键字后加追加一个问号(`init?`)。声明生成隐式解包可选实例的可失败构造器时,在构造器声明后追加一个叹号(`init!`)。使用 `init?` 可失败构造器生成结构体的一个可选实例的例子如下。 + +```swift +struct SomeStruct { + let string: String + //生成一个 SomeStruct 的可选实例 + init?(input: String) { + if input.isEmpty { + // 丢弃 self,并返回 nil + return nil + } + string = input + } +} +``` + +调用 `init?` 可失败构造器和调用非可失败构造器的方式相同,不过你需要处理可选类型的返回值。 + +```swift +if let actualInstance = SomeStruct(input: "Hello") { + // 利用 SomeStruct 实例做些事情 +} else { + // SomeStruct 实例的构造过程失败,构造器返回了 nil +} +``` + +可失败构造器可以在构造器实现中的任意位置返回 `nil`。 + +可失败构造器可以委托任意种类的构造器。非可失败可以委托其它非可失败构造器或者 `init!` 可失败构造器。非可失败构造器可以委托超类的 `init?` 可失败指定构造器,但是需要使用强制解包,例如 `super.init()!`。 + +构造过程失败通过构造器委托来传递。具体来说,如果可失败构造器委托的可失败构造器构造过程失败并返回 `nil`,那么该可失败构造器也会构造失败并隐式地返回 `nil`。如果非可失败构造器委托的 `init!` 可失败构造器构造失败并返回了 `nil`,那么会发生运行时错误(如同使用 `!` 操作符去强制解包一个值为 `nil` 的可选值)。 + +子类可以用任意种类的指定构造器重写超类的可失败指定构造器,但是只能用非可失败指定构造器重写超类的非可失败指定构造器。 + +更多关于可失败构造器的信息和例子,请参阅 [可失败构造器](../chapter2/14_Initialization.md#failable_initializers)。 + + +> 构造器声明语法 +> +> *构造器声明* → [*构造器头*](#initializer-head) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [*参数子句*](#parameter-clause) **throws**可选 [*构造器主体*](#initializer-body) +> *构造器声明* → [*构造器头*](#initializer-head) [*泛型形参子句*](08_Generic_Parameters_and_Arguments.md#generic-parameter-clause)可选 [*参数子句*](#parameter-clause) **rethrows**可选 [*构造器主体*](#initializer-body) +> +> *构造器头* → [*特性列表*](06_Attributes.md#attributes)可选 [*声明修饰符列表*](#declaration-modifiers)可选 **init** +> *构造器头* → [*特性列表*](06_Attributes.md#attributes)可选 [*声明修饰符列表*](#declaration-modifiers)可选 **init** **?** +> *构造器头* → [*特性列表*](06_Attributes.md#attributes)可选 [*声明修饰符列表*](#declaration-modifiers)可选 **init** **!** +> +> *构造器主体* → [*代码块*](#code-block) + + +## 析构器声明 + +*析构器声明 (deinitializer declaration)* 可以为类声明一个析构器。析构器没有参数,遵循如下格式: + +```swift +deinit { + 语句 +} +``` + +当没有任何强引用引用着类的对象,对象即将被释放时,析构器会被自动调用。析构器只能在类主体的声明中声明,不能在类的扩展声明中声明,并且每个类最多只能有一个析构器。 + +子类会继承超类的析构器,并会在子类对象将要被释放时隐式调用。继承链上的所有析构器全部调用完毕后子类对象才会被释放。 + +析构器不能直接调用。 + +关于如何在类声明中使用析构器的例子,请参阅 [析构过程](../chapter2/15_Deinitialization.md)。 + + +> 析构器声明语法 +> +> *析构器声明* → [*特性列表*](06_Attributes.md#attributes)可选 **deinit** [*代码块*](#code-block) + + +## 扩展声明 + +*扩展声明 (extension declaration)* 可以扩展一个现存的类、结构体和枚举类型的行为。扩展声明使用关键字 `extension`,遵循如下格式: + +```swift +extension 类型名称: 采纳的协议 { + 声明语句 +} +``` + +扩展声明体可包含零个或多个声明语句。这些声明语句可以包括计算型属性、计算型类型属性、实例方法、类型方法、构造器、下标声明,甚至其他结构体、类和枚举声明。扩展声明不能包含析构器、协议声明、存储型属性、属性观察器或其他扩展声明。关于扩展声明的详细讨论,以及各种扩展声明的例子,请参阅 [扩展](../chapter2/21_Extensions.md)。 + +扩展声明可以为现存的类、结构体、枚举添加协议一致性,但是不能为类添加超类,因此在扩展声明的类型名称的冒号后面仅能指定一个协议列表。 + +现存类型的属性、方法、构造器不能在扩展中被重写。 + +扩展声明可以包含构造器声明。这意味着,如果被扩展的类型在其他模块中定义,构造器声明必须委托另一个在那个模块中声明的构造器,以确保该类型能被正确地初始化。 + + +> 扩展声明语法 +> +> *扩展声明* → [访问级别修饰符](#access-level-modifier)可选 **extension** [*类型标识符*](03_Types.md#type-identifier) [*类型继承子句*](03_Types.md#type-inheritance-clause)可选 [*扩展主体*](#extension-body) +> +> *扩展主体* → **{** [*多条声明*](#declarations)可选 **}** + + +## 下标声明 + +*下标声明 (subscript declaration)* 用于为特定类型的对象添加下标支持,通常也用于为访问集合、列表和序列中的元素提供语法便利。下标声明使用关键字 `subscript`,形式如下: + +```swift +subscript (参数列表) -> 返回类型 { + get { + 语句 + } + set(setter 名称) { + 语句 + } +} +``` + +下标声明只能出现在类、结构体、枚举、扩展和协议的声明中。 + +参数列表指定一个或多个用于在相关类型的下标表达式中访问元素的索引(例如,表达式 `object[i]` 中的 `i`)。索引可以是任意类型,但是必须包含类型标注。返回类型指定了被访问的元素的类型。 + +和计算型属性一样,下标声明支持对元素的读写操作。getter 用于读取值,setter 用于写入值。setter 子句是可选的,当仅需要一个 getter 子句时,可以将二者都忽略,直接返回请求的值即可。但是,如果提供了 setter 子句,就必须提供 getter 子句。 + +圆括号以及其中的 setter 名称是可选的。如果提供了 setter 名称,它会作为 setter 的参数名称。如果不提供 setter 名称,那么 setter 的参数名称默认是 `value`。setter 名称的类型必须与返回类型相同。 + +可以重载下标,只要参数列表或返回类型不同即可。还可以重写继承自超类的下标,此时必须使用 `override` 声明修饰符声明被重写的下标。 + +同样可以在协议声明中声明下标,正如 [协议下标声明](#protocol_subscript_declaration) 中所述。 + +更多关于下标的信息和例子,请参阅 [下标](../chapter2/12_Subscripts.md)。 + + +> 下标声明语法 +> +> *下标声明* → [*下标头*](#subscript-head) [*下标结果*](#subscript-result) [*代码块*](#code-block) +> *下标声明* → [*下标头*](#subscript-head) [*下标结果*](#subscript-result) [*getter-setter代码块*](#getter-setter-block) +> *下标声明* → [*下标头*](#subscript-head) [*下标结果*](#subscript-result) [*getter-setter关键字代码块*](#getter-setter-keyword-block) +> +> *下标头* → [*特性列表*](06_Attributes.md#attributes)可选 [*声明修饰符列表*](#declaration-modifiers)可选 **subscript** [*参数子句*](#parameter-clause) +> +> *下标结果* → **->** [*特性列表*](06_Attributes.md#attributes)可选 [*类型*](03_Types.md#type) + + +## 运算符声明 + +*运算符声明 (operator declaration)* 会向程序中引入中缀、前缀或后缀运算符,使用关键字 `operator` 来声明。 + +可以声明三种不同的缀性:中缀、前缀和后缀。运算符的缀性指定了运算符与其运算对象的相对位置。 + +运算符声明有三种基本形式,每种缀性各一种。运算符的缀性通过在 `operator` 关键字之前添加声明修饰符 `infix`,`prefix` 或 `postfix` 来指定。每种形式中,运算符的名字只能包含 [运算符](02_Lexical_Structure.md#operators) 中定义的运算符字符。 + +下面的形式声明了一个新的中缀运算符: + +```swift +infix operator 运算符名称 { + precedence 优先级 + associativity 结合性 +} +``` + +中缀运算符是二元运算符,置于两个运算对象之间,例如加法运算符(`+`)位于表达式 `1 + 2` 的中间。 + +中缀运算符可以选择指定优先级或结合性,或者两者同时指定。 + +运算符的优先级可以指定在没有括号包围的情况下,运算符与其运算对象如何结合。可以使用上下文相关的关键字 `precedence` 以及紧随其后的优先级数字来指定一个运算符的优先级。优先级可以是 `0` 到 `255` 之间的任何十进制整数。与十进制整数字面量不同的是,它不可以包含任何下划线字符。尽管优先级是一个特定的数字,但它仅用作与另一个运算符的优先级比较大小。也就是说,当两个运算符为同一个运算对象竞争时,例如 `2 + 3 * 5`,优先级更高的运算符将优先参与运算。 + +运算符的结合性可以指定在没有括号包围的情况下,多个优先级相同的运算符将如何组合。可以使用上下文相关的关键字 `associativity` 以及紧随其后的结合性关键字来指定运算符的结合性,结合性关键字也是上下文相关的,包括 `left`、`right` 和 `none`。左结合运算符以从左到右的顺序进行组合。例如,减法运算符(`-`)具有左结合性,因此 `4 - 5 - 6` 以 `(4 - 5) - 6` 的形式组合,其结果为 `-7`。右结合运算符以从右到左的顺序组合,而设置为 `none` 的运算符不进行组合。具有相同优先级的非结合运算符,不可以互相邻接。例如,表达式 `1 < 2 < 3` 是非法的。 + +声明时不指定任何优先级或结合性的中缀运算符,优先级为 `100`,结合性为 `none`。 + +下面的形式声明了一个新的前缀运算符: + +```swift +prefix operator 运算符名称 {} +``` + +出现在运算对象前边的前缀运算符是一元运算符,例如表达式 `!a` 中的前缀非运算符(`!`)。 + +前缀运算符的声明中不指定优先级,而且前缀运算符是非结合的。 + +下面的形式声明了一个新的后缀运算符: + +```swift +postfix operator 运算符名称 {} +``` + +紧跟在运算对象后边的后缀运算符是一元运算符,例如表达式 `a!` 中的后缀强制解包运算符(`!`)。 + +和前缀运算符一样,后缀运算符的声明中不指定优先级,而且后缀运算符是非结合的。 + +声明了一个新的运算符以后,需要实现一个跟这个运算符同名的函数来实现这个运算符。如果是实现一个前缀或者后缀运算符,也必须使用相符的 `prefix` 或者 `postfix` 声明修饰符标记函数声明。如果是实现中缀运算符,则不需要使用 `infix` 声明修饰符标记函数声明。关于如何实现一个新的运算符的例子,请参阅 [自定义运算符](../chapter2/25_Advanced_Operators.md#custom_operators)。 + + +> 运算符声明语法 + + +> *运算符声明* → [*前缀运算符声明*](#prefix-operator-declaration) | [*后缀运算符声明*](#postfix-operator-declaration) | [*中缀运算符声明*](#infix-operator-declaration) + + +> *前缀运算符声明* → **prefix** **运算符** [*运算符*](02_Lexical_Structure.md#operator) **{** **}** +> +> *后缀运算符声明* → **postfix** **运算符** [*运算符*](02_Lexical_Structure.md#operator) **{** **}** +> +> *中缀运算符声明* → **infix** **运算符** [*运算符*](02_Lexical_Structure.md#operator) **{** [*中缀运算符属性*](#infix-operator-attributes)可选 **}** + + +> *中缀运算符属性* → [*优先级子句*](#precedence-clause)可选 [*结和性子句*](#associativity-clause)可选 +> +> *优先级子句* → **precedence** [*优先级水平*](#precedence-level) +> +> *优先级水平* → 十进制整数 0 到 255,包含 0 和 255 +> +> *结和性子句* → **associativity** [*结和性*](#associativity) +> +> *结和性* → **left** | **right** | **none** + + +## 声明修饰符 + +声明修饰符都是关键字或上下文相关的关键字,可以修改一个声明的行为或者含义。可以在声明的特性(如果存在)和引入该声明的关键字之间,利用声明修饰符的关键字或上下文相关的关键字指定一个声明修饰符。 + +`dynamic` + +该修饰符用于修饰任何兼容 Objective-C 的类的成员。访问被 `dynamic` 修饰符标记的类成员将总是由 Objective-C 运行时系统进行动态派发,而不会由编译器进行内联或消虚拟化。 + +因为被标记 `dynamic` 修饰符的类成员会由 Objective-C 运行时系统进行动态派发,所以它们会被隐式标记 `objc` 特性。 + +`final` + +该修饰符用于修饰类或类中的属性、方法以及下标。如果用它修饰一个类,那么这个类不能被继承。如果用它修饰类中的属性、方法或下标,那么它们不能在子类中被重写。 + +`lazy` + +该修饰符用于修饰类或结构体中的存储型变量属性,表示该属性的初始值最多只被计算和存储一次,且发生在它被第一次访问时。关于如何使用 `lazy` 修饰符的例子,请参阅 [惰性存储型属性](../chapter2/10_Properties.md#lazy_stored_properties)。 + +`optional` + +该修饰符用于修饰协议中的属性、方法以及下标成员,表示符合类型可以不实现这些成员要求。 + +只能将 `optional` 修饰符用于被 `objc` 特性标记的协议。这样一来,就只有类类型可以采纳并符合拥有可选成员要求的协议。关于如何使用 `optional` 修饰符,以及如何访问可选协议成员(比如,不确定符合类型是否已经实现了这些可选成员)的信息,请参阅 [可选协议要求](../chapter2/22_Protocols.md#optional_protocol_requirements)。 + +`required` + +该修饰符用于修饰类的指定构造器或便利构造器,表示该类所有的子类都必须实现该构造器。在子类实现该构造器时,必须同样使用 `required` 修饰符修饰该构造器。 + +`weak` + +该修饰符用于修饰变量或存储型变量属性,表示该变量或属性持有其存储的对象的弱引用。这种变量或属性的类型必须是可选的类类型。使用 `weak` 修饰符可避免强引用循环。关于 `weak` 修饰符的更多信息和例子,请参阅 [弱引用](../chapter2/16_Automatic_Reference_Counting.md#resolving_strong_reference_cycles_between_class_instances)。 + + +### 访问控制级别 + +Swift 提供了三个级别的访问控制:`public`、`internal` 和 `private`。可以使用以下任意一种访问级别修饰符来指定声明的访问级别。访问控制在 [访问控制](../chapter2/24_Access_Control.md) 中有详细讨论。 + +`public` + +该修饰符表示声明可被同模块的代码访问,只要其他模块导入了声明所在的模块,也可以进行访问。 + +`internal` + +该修饰符表示声明只能被同模块的代码访问。默认情况下,绝大多数声明会被隐式标记 `internal` 访问级别修饰符。 + +`private` + +该修饰符表示声明只能被所在源文件的代码访问。 + +以上访问级别修饰符都可以选择带上一个参数,该参数由一对圆括号和其中的 `set` 关键字组成(例如,`private(set)`)。使用这种形式的访问级别修饰符来限制某个属性或下标的 setter 的访问级别低于其本身的访问级别,正如 [Getter 和 Setter](../chapter2/24_Access_Control.md#getters_and_setters) 中所讨论的。 + + +> 声明修饰符的语法 + + +> *声明修饰符* → **class** | **convenience**| **dynamic** | **final** | **infix** | **lazy** | **mutating** | **nonmutating** | **optional** | **override** | **postfix** | **prefix** | **required** | **static** | **unowned** | **unowned ( safe )** | **unowned ( unsafe )** | **weak** +> 声明修饰符 → [*访问级别修饰符*](#access-level-modifier) +> +> *声明修饰符列表* → [*声明修饰符*](#declaration-modifier) [*声明修饰符列表*](#declaration-modifiers)可选 + + +>访问级别修饰符 → **internal** | **internal ( set )** +>访问级别修饰符 → **private** | **private ( set )** +>访问级别修饰符 → **public** | **public ( set )** diff --git a/source/chapter3/06_Attributes.md b/source/chapter3/06_Attributes.md index 21f5b989..bf4af714 100755 --- a/source/chapter3/06_Attributes.md +++ b/source/chapter3/06_Attributes.md @@ -6,218 +6,228 @@ > 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai) > 2.0 -> 翻译+校对:[KYawn](https://github.com/KYawn),[小铁匠Linus](https://github.com/kevin833752) +> 翻译+校对:[KYawn](https://github.com/KYawn) + +> 2.1 +> 翻译:[小铁匠Linus](https://github.com/kevin833752) 本页内容包括: - [声明特性](#declaration_attributes) +- [Interface Builder 使用的声明特性](#declaration_attributes_used_by_interface_builder) - [类型特性](#type_attributes) -特性提供了关于声明和类型的更多信息。在Swift中有两类特性,用于修饰声明的以及用于修饰类型的。 +特性提供了有关声明和类型的更多信息。在Swift中有两种特性,分别用于修饰声明和类型。 -通过以下方式指定一个特性:符号`@`后面跟特性名,如果包含参数,则把参数带上: +您可以通过以下方式指定一个特性:符号`@`后跟特性的名称和特性接收的任何参数: -> @`attribute name` -> @`attribute name`(`attribute arguments`) +> @ `特性名` -有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰一个特定的声明的。这些特性的参数写在小括号内,它们的格式由它们所属的特性来定义。 +> @ `特性名`(`特性参数`) + +有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰某个特定的声明的。这些特性的参数写在圆括号内,它们的格式由它们所属的特性来定义。 -## 声明特性 - -声明特性只能应用于声明。然而,你也可以将`noreturn`特性应用于函数或方法类型。 - -`autoclosure` - -这个特性通过把表达式自动封装成无参数的闭包来延迟表达式的计算。它可以声明返回表达式自身类型的没有参数的方法类型,也可以用于函数参数的声明。含有`autoclosure`特性的声明同时也具有`noescape`的特性,除非传递可选参数`escaping`.关于怎样使用`autoclosure`特性的例子,参见[函数类型](./03_Types.html#function_type). +##声明特性 +声明特性只能应用于声明。 `available` -将`available`特性用于声明时,意味着该声明的生命周期会依赖于特定的平台和操作系统版本。 +将 `available` 特性用于声明时,表示该声明的生命周期与特定的平台和操作系统版本有关。 -`available`特性经常与参数列表一同出现,该参数列表至少有两个参数,参数之间由逗号分隔。这些参数由以下这些平台名字中的一个起头: +`available` 特性经常与参数列表一同出现,该参数列表至少有两个特性参数,参数之间由逗号分隔。这些参数由以下这些平台名字中的一个起头: -- `iOS` -- `iOSApplicationExtension` -- `OSX` -- `OSXApplicationExtension` -- `watchOS` +- iOS +- iOSApplicationExtension +- macOS +- macOSApplicationExtension +- watchOS +- watchOSApplicationExtension +- tvOS +- tvOSApplicationExtension -当然,你也可以用一个星号(*)来表示,该声明在上面提到的所有平台上都是有效的。 +当然,你也可以用一个星号(*)来表示上面提到的所有平台。 +其余的参数,可以按照任何顺序出现,并且可以添加关于声明生命周期的附加信息,包括重要事件。 -剩下的参数,可以以任何顺序出现,并且可以添加关于声明生命周期的附加信息,包括重要的里程碑。 +- `unavailable`参数表示该声明在指定的平台上是无效的。 +- `introduced` 参数表示指定平台从哪一版本开始引入该声明。格式如下: -- `unavailable`参数表示:该声明在特定的平台上是无效的 +`introduced`=`版本号` -- `introduced`参数表示:该声明第一次被引入时所在平台的版本。格式如下: -

`introduced=version number`

这里的`version number`由一个正的十进制整数或浮点数构成。 +*版本号*由一个或多个正整数构成,由句点分隔的。 -- `deprecated`参数表示:该声明第一次被建议弃用时所在平台的版本。格式如下: -

`deprecated=version number`

这里的`version number`由一个正的十进制整数或浮点数构成。 +- `deprecated`参数表示指定平台从哪一版本开始弃用该声明。格式如下: -- `obsoleted`参数表示:该声明第一次被弃用时所在平台的版本。当一个声明被弃用时,它就从此平台中被移除,不能再被使用。格式如下: -

`obsoleted=version number`

这里的`version number`由一个正的十进制整数或浮点数构成。 +`deprecated`=`版本号` -- `message`参数用来提供文本信息。当使用建议弃用或者被弃用的声明时,编译器会抛出错误或警告信息。格式如下: -

`message=message`

这里的`message`由一个字符串文字构成。 +可选的*版本号*由一个或多个正整数构成,由句点分隔的。省略版本号表示该声明目前已弃用,当弃用出现时无需给出任何有关信息。如果你省略了版本号,冒号(:)也可省略。 -- `renamed`参数用来提供文本信息,用以表示被重命名的声明的新名字。当使用这个重命名的声明遇到错误时,编译器会显示出该新名字。格式如下: -

`renamed=new name`

这里的`new name`由一个字符串文字构成。 +- `obsoleted` 参数表示指定平台从哪一版本开始废弃该声明。当一个声明被废弃后,它就从平台中移除,不能再被使用。格式如下: -你可以将`renamed`参数和`unavailable`参数以及类型别名声明组合使用,以向用户表示:在你的代码中,一个声明已经被重命名。当一个声明的名字在一个框架或者库的不同发布版本间发生变化时,这会相当有用。 +`obsoleted`=`版本号` + +*版本号*由一个或多个正整数构成,由句点分隔的。 + +- `message` 参数用来提供文本信息。当使用被弃用或者被废弃的声明时,编译器会抛出警告或错误信息。格式如下: + +`message`=`信息内容` + +信息内容由一个字符串构成。 + +- `renamed` 参数用来提供文本信息,用以表示被重命名的声明的新名字。当使用声明的旧名字时,编译器会报错提示新名字。格式如下: + +`renamed`=`新名字` + +新名字由一个字符串构成。 + +你可以将`renamed` 参数和 `unavailable` 参数以及类型别名声明组合使用,以此向用户表示某个声明已经被重命名。当某个声明的名字在一个框架或者库的不同发布版本间发生变化时,这会相当有用。 ```swift -// First release +// 首发版本 protocol MyProtocol { - // protocol definition -} -``` - -```swift -// Subsequent release renames MyProtocol -protocol MyRenamedProtocol { - // protocol definition +// 这里是协议定义 } - -@available(*, unavailable, renamed="MyRenamedProtocol") +``` + +```swift +// 后续版本重命名了 MyProtocol +protocol MyRenamedProtocol { +// 这里是协议定义 +} +@available(*, unavailable, renamed:"MyRenamedProtocol") typealias MyProtocol = MyRenamedProtocol ``` -你可以在一个单独的声明上使用多个`available`特性,以详细说明该声明在不同平台上的有效性。编译器只有在当前的目标平台和`available`特性中指定的平台匹配时,才会使用`available`特性。 +你可以在某个声明上使用多个 `available` 特性,以指定该声明在不同平台上的可用性。编译器只有在当前目标平台和 `available` 特性中指定的平台匹配时,才会使用 `available` 特性。 -如果`available`特性除了平台名称参数外,只指定了一个`introduced `参数,那么可以使用以下简写语法代替: +如果 `available` 特性除了平台名称参数外,只指定了一个 `introduced` 参数,那么可以使用以下简写语法代替: -@available(`platform name` `version number`, *) +@available(平台名称 版本号,*) -`available`特性的简写语法可以简明地表达出多个平台的可用性。尽管这两种形式在功能上是相同的,但请尽可能地使用简明语法形式。 +`available` 特性的简写语法可以简明地表达出声明在多个平台上的可用性。尽管这两种形式在功能上是相同的,但请尽可能地使用简写语法形式。 ```swift -@available(iOS 8.0, OSX 10.10, *) +@available(iOS 10.0, macOS 10.12, *) class MyClass { - // class definition +// 这里是类定义 } ``` +`discardableResult` + +该特性用于的函数或方法声明,以抑制编译器中 函数或方法的返回值被调而没有使用其结果的警告。 + +`GKInspectable` + +应用此属性,暴露一个自定义GameplayKit组件属性给SpriteKit编辑器UI。 + `objc` -该特性用于修饰任何可以在Objective-C中表示的声明。比如,非嵌套类、协议、非泛型枚举(仅限整型值类型)、类和协议的属性和方法(包括`getter`和`setter`)、构造器、析构器以及下标。`objc`特性告诉编译器这个声明可以在Objective-C代码中使用。 +该特性用于修饰任何可以在 Objective-C 中表示的声明。比如,非嵌套类、协议、非泛型枚举(仅限原始值为整型的枚举)、类和协议中的属性和方法(包括存取方法)、构造器、析构器以及下标运算符。`objc` 特性告诉编译器这个声明可以在 Objective-C 代码中使用。 -标有`objc`特性的类必须继承自Objective-C中定义的类。如果你将`objc`特性应用于一个类或协议,它也会隐式地应用于那个类的成员或协议。对于标记了`objc`特性的类,编译器会隐式地为它的子类添加`objc`特性。标记了`objc`特性的协议不能继承没有标记`objc`的协议。 +标有 `objc` 特性的类必须继承自 Objective-C 中定义的类。如果你将 `objc` 特性应用于一个类或协议,它也会隐式地应用于类或协议中兼容 Objective-C 的成员。对于标记了 `objc` 特性的类,编译器会隐式地为它的子类添加 `objc` 特性。标记了 `objc` 特性的协议不能继承没有标记 `objc` 的协议。 -如果你将`objc`特性应用于枚举,每一个枚举的`case`都会以枚举名称和`case`名称组合的方式暴露在Objective-C代码中。例如:一个名为`Venus`的`case`在`Planet`枚举中,这个`case`暴露在Objective-C代码中时叫做`PlanetVenus`。 +如果你将 `objc` 特性应用于枚举,每一个枚举用例都会以枚举名称和用例名称组合的方式暴露在 Objective-C 代码中。例如,在 `Planet` 枚举中有一个名为 `Venus` 的用例,该用例暴露在 Objective-C 代码中时叫做 `PlanetVenus`。 -`objc`特性有一个可选的参数,由标记符组成。当你想把`objc`所修饰的实体以一个不同的名字暴露给Objective-C时,你就可以使用这个特性参数。你可以使用这个参数来命名类,协议,方法,getters,setters,以及构造器。下面的例子把`ExampleClass`中`enabled`属性的getter暴露给Objective-C,名字是`isEnabled`,而不是它原来的属性名。 +`objc` 特性有一个可选的参数,由标识符构成。当你想把 objc 所修饰的实体以一个不同的名字暴露给 Objective-C 时,你就可以使用这个特性参数。你可以使用这个参数来命名类、枚举类型、枚举用例、协议、方法、存取方法以及构造器。下面的例子把 `ExampleClass` 中的 `enabled` 属性的取值方法暴露给 Objective-C,名字是 `isEnabled`,而不是它原来的属性名。 ```swift @objc class ExampleClass: NSObject { - var enabled: Bool { - @objc(isEnabled) get { - // Return the appropriate value - } - } +var enabled: Bool { +@objc(isEnabled) get { +// 返回适当的值 } +} } ``` - -`noescape` - -在函数或者方法声明上使用该特性,它表示参数将不会被存储用作后续的计算,其用来确保不会超出函数调用的生命周期。对于其属性或方法来说,使用`noescape`声明属性的函数类型不需要显式的使用`self.`。 - `nonobjc` -该特性用于方法、属性、下标、或构造器的声明,这些声明本是可以在Objective-C代码中表示的。使用`nonobjc`特性告诉编译器这个声明不能在Objective-C代码中使用。 +该特性用于方法、属性、下标、或构造器的声明,这些声明本可以在 Objective-C 代码中使用,而使用 `nonobjc` 特性则告诉编译器这个声明不能在 Objective-C 代码中使用。 -可以使用`nonobjc`特性解决标有`objc`的类中桥接方法的循环问题,该特性还允许标有`objc`的类的构造器和方法进行重载(overload)。 +可以使用 `nonobjc` 特性解决标有 `objc` 的类中桥接方法的循环问题,该特性还允许对标有 `objc` 的类中的构造器和方法进行重载。 -标有`nonobjc`特性的方法不能重写(override)一个标有`objc`特性的方法。然而,标有`objc`特性的方法可以重写标有`nonobjc`特性的方法。同样,标有`nonobjc`特性的方法不能满足一个需要标有`@objc`特性的方法的协议。 - -`noreturn` - -该特性用于修饰函数或方法声明,表明该函数或方法的对应类型,`T`,是`@noreturn T`。你可以用这个特性修饰函数或方法的类型,这样一来,函数或方法就不会返回到它的调用者中去。 - -对于一个没有用`noreturn`特性标记的函数或方法,你可以将它重写为用该特性标记的。相反,对于一个已经用`noreturn`特性标记的函数或方法,你则不可以将它重写为没使用该特性标记的。当你在一个comforming类型中实现一个协议方法时,该规则同样适用。 +标有 `nonobjc` 特性的方法不能重写标有 `objc` 特性的方法。然而,标有 `objc` 特性的方法可以重写标有 `nonobjc` 特性的方法。同样,标有 `nonobjc` 特性的方法不能满足标有 `@objc` 特性的协议中的方法要求。 `NSApplicationMain` -在类上使用该特性表示该类是应用程序委托类,使用该特性与调用`NSApplicationMain(_:_:)`函数并且把该类的名字作为委托类的名字传递给函数的效果相同。 +在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `NSApplicationMain`(\_:_:) 函数并且把该类的名字作为委托类的名字传递给函数的效果相同。 -如果你不想使用这个特性,可以提供一个`main.swift`文件,并且提供一个`main()`函数去调用`NSApplicationMain(_:_:)`函数。比如,如果你的应用程序使用一个派生于`NSApplication`的自定义子类作为主要类,你可以调用`NSApplicationMain`函数而不是使用该特性。 +如果你不想使用这个特性,可以提供一个 main.swift 文件,并在代码**顶层**调用`NSApplicationMain`(\_:_:) 函数,如下所示: +```swift +import AppKit +NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) +``` `NSCopying` -该特性用于修饰一个类的存储型变量属性。该特性将使属性的setter与属性值的一个副本合成,这个值由`copyWithZone(_:)`方法返回,而不是属性本身的值。该属性的类型必需遵循`NSCopying`协议。 +该特性用于修饰一个类的存储型变量属性。该特性将使属性的设值方法使用传入值的副本进行赋值,这个值由传入值的 `copyWithZone`(\_:) 方法返回。该属性的类型必需符合 `NSCopying` 协议。 -`NSCopying`特性的原理与Objective-C中的`copy`特性相似。 +`NSCopying` 特性的行为与 Objective-C 中的 `copy` 特性相似。 `NSManaged` -该特性用于修饰`NSManagedObject`子类中的实例方法或存储型变量属性,表明属性的存储和实现由Core Data在运行时基于相关实体描述动态提供。对于标记了`NSManaged`特性的属性,Core Data也会在运行时提供存储。 +该特性用于修饰 `NSManagedObject` 子类中的实例方法或存储型变量属性,表明它们的实现由 `Core Data` 在运行时基于相关实体描述动态提供。对于标记了 `NSManaged` 特性的属性,`Core Data` 也会在运行时为其提供存储。应用这个特性也意味着`objc`特性。 `testable` -该特性用于`import`声明可以测试的编译模块,它能访问任何标有`internal`权限标识符的实体,这和将它声明为`public`权限标识符有同样的效果。 +在导入允许测试的编译模块时,该特性用于修饰 `import` 声明,这样就能访问被导入模块中的任何标有 `internal` 访问级别修饰符的实体,犹如它们被标记了 `public` 访问级别修饰符。测试也可以访问使用`internal`或者`public`访问级别修饰符标记的类和类成员,就像它们是`open`访问修饰符声明的。 `UIApplicationMain` -在类上使用该特性表示该类是应用程序委托类,使用该特性与调用`UIApplicationMain(_:_:)`函数并且把该类的名字作为委托类的名字传递给函数的效果相同。 +在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `UIApplicationMain`函数并且把该类的名字作为委托类的名字传递给函数的效果相同。 -如果你不想使用这个特性,可以提供一个`main.swift`文件,并且提供一个`main`函数去调用`UIApplicationMain(_:_:)`函数。比如,如果你的应用程序使用一个派生于`UIApplication`的自定义子类作为主要类,你可以调用`UIApplicationMain`函数而不是使用该特性。 +如果你不想使用这个特性,可以提供一个 main.swift 文件,并在代码顶层调用 `UIApplicationMain`(\_:\_:\_:) 函数。比如,如果你的应用程序使用一个继承于 UIApplication 的自定义子类作为主要类,你可以调用 `UIApplicationMain`(\_:\_:\_:) 函数而不是使用该特性。 -`warn_unused_result` + +###Interface Builder 使用的声明特性 +`Interface Builder` 特性是 `Interface Builder` 用来与 Xcode 同步的声明特性。`Swift` 提供了以下的 `Interface Builder` 特性:`IBAction`,`IBOutlet`,`IBDesignable`,以及`IBInspectable` 。这些特性与 Objective-C 中对应的特性在概念上是相同的。 -该特性应用于方法或函数声明,当方法或函数被调用,但其结果未被使用时,该特性会让编译器会产生警告。 +`IBOutlet` 和 `IBInspectable` 用于修饰一个类的属性声明,`IBAction` 特性用于修饰一个类的方法声明,`IBDesignable` 用于修饰类的声明。 -你可以使用这个特性提供一个警告信息,这个警告信息是关于不正确地使用未变异的方法,这个方法也有一个对应的变异方法。 - -`warn_unused_result`特性会有选择地采用下面两个参数之一。 - -- `message`参数用来提供警告信息。在当方法或函数被调用,但其结果未被使用时,会显示警告信息。格式如下: -

`message=message`

这里的`message`由一个字符串文字构成。 - -- `mutable_variant`参数用于提供变异方法的名称,如果未变异方法以一个可变的值被调用而且其结果并未被使用时,应该使用此变异方法。格式如下(方法名有字符串构成):

`mutable_variant=method name`

-比如,Swift标准库同时提供了变异方法`sortInPlace()`和未变异方法`sort()`集合,它们的元素生成器符合`Comparable`协议。如果你调用了`sort()`方法,而没有使用它的结果,其实很有可能,你是打算使用变异方法`sortInPlace()`。 - -### Interface Builder使用的声明特性 - -Interface Builder特性是Interface Builder用来与Xcode同步的声明特性。Swift提供了以下的Interface Builder特性:`IBAction`,`IBDesignable`,`IBInspectable`,以及`IBOutlet`。这些特性与Objective-C中对应的特性在概念上是相同的。 - -`IBOutlet`和`IBInspectable`用于修饰一个类的属性声明;`IBAction`特性用于修饰一个类的方法声明;`IBDesignable`用于修饰类的声明。 +`IBAction` 和 `IBOutlet` 特性都意味着`objc`特性。 -## 类型特性 +##类型特性 +类型特性只能用于修饰类型。 -类型特性只能用于修饰类型。然而,你也可以用`noreturn`特性去修饰函数或方法声明。 +`autoclosure` + +这个特性通过把表达式自动封装成无参数的闭包来延迟表达式的计算。它可以修饰类型为返回表达式结果类型的无参数函数类型的函数参数。关于如何使用 autoclosure 特性的例子,请参阅 [自动闭包](http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html/) 和 [函数类型](http://wiki.jikexueyuan.com/project/swift/chapter3/03_Types.html)。 `convention` +该特性用于修饰函数类型,它指出了函数调用的约定。 -该特性用于函数的类型,它指出函数调用的约定。 +convention 特性总是与下面的参数之一一起出现。 -`convention`特性总是与下面的参数之一一起出现。 +- `swift` 参数用于表示一个 Swift 函数引用。这是 Swift 中函数值的标准调用约定。 -- `swift`参数用于表明一个Swift函数引用。这是Swift中标准的函数值调用约定。 +- `block` 参数用于表示一个 Objective-C 兼容的块引用。函数值会作为一个块对象的引用,块是一种 `id` 兼容的 Objective-C 对象,其中嵌入了调用函数。调用函数使用 C 的调用约定。 -- `block`参数用于表明一个Objective-C兼容的块引用。函数值表示为一个块对象的引用,这是一个`id-`兼容的Objective-C对象,对象中嵌入了调用函数。调用函数使用C的调用约定。 +- `c` 参数用于表示一个 C 函数引用。函数值没有上下文,不具备捕获功能,同样使用 C 的调用约定。 -- `c`参数用于表明一个C函数引用。函数值没有上下文,这个函数也使用C的调用约定。 +使用 C 函数调用约定的函数也可用作使用 Objective-C 块调用约定的函数,同时使用 Objective-C 块调用约定的函数也可用作使用 Swift 函数调用约定的函数。然而,只有非泛型的全局函数、局部函数以及未捕获任何局部变量的闭包,才可以被用作使用 C 函数调用约定的函数。 -使用C函数调用约定的函数也可用作使用Objective-C块调用约定的函数,同时使用Objective-C块调用约定的函数也可用作使用Swift函数调用约定的函数。然而,只有非泛型的全局函数和本地函数或者不使用任何本地变量的闭包可以被用作使用C函数调用约定的函数。 +`escaping` +在函数或者方法声明上使用该特性,它表示参数将不会被存储以供延迟执行,这将确保参数不会超出函数调用的生命周期。在使用 `escaping` 声明特性的函数类型中访问属性和方法时不需要显式地使用 `self.`。关于如何使用 `escaping` 特性的例子,请参阅 [逃逸闭包](http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html)。 -`noreturn` +>特性语法 -该特性用于修饰函数或方法的类型,表明该函数或方法不会返回到它的调用者中去。你也可以用它标记函数或方法的声明,表示函数或方法的相应类型,`T`,是`@noreturn T`。 +> *特性 *→ @ 特性名 特性参数子句可选 -> 特性语法 -> *特性* → **@** [*特性名*](#attribute_name) [*特性参数子句*](#attribute_argument_clause) (可选) -> *特性名* → [*标识符*](02_Lexical_Structure.html#identifiers) -> *特性参数子句* → **(** [*平衡令牌列表*](#balanced_tokens) (可选) **)** -> *特性(Attributes)列表* → [*特色*](#attribute) [*特性(Attributes)列表*](#attributes) (可选) -> *平衡令牌列表* → [*平衡令牌*](#balanced_token) [*平衡令牌列表*](#balanced_tokens) (可选) -> *平衡令牌* → **(** [*平衡令牌列表*](#balanced_tokens) (可选) **)** -> *平衡令牌* → **[** [*平衡令牌列表*](#balanced_tokens) (可选) **]** -> *平衡令牌* → **{** [*平衡令牌列表*](#balanced_tokens) (可选) **}** -> *平衡令牌* → **任意标识符, 关键字, 字面量或运算符** -> *平衡令牌* → **任意标点除了(, ), [, ], {, 或 }** +> *特性名* → 标识符 +> *特性参数子句* → ( 均衡令牌列表可选 ) +> *特性列表* → 特性 特性列表可选 +> *均衡令牌列表* → 均衡令牌 均衡令牌列表可选 +> *均衡令牌* → ( 均衡令牌列表可选 ) + +> *均衡令牌* → [ 均衡令牌列表可选 ] + +> *均衡令牌* → { 均衡令牌列表可选} + +> *均衡令牌* → 任意标识符,关键字,字面量或运算符 + +> *均衡令牌* → 任意标点除了 (,),[,],{,或 } diff --git a/source/chapter3/07_Patterns.md b/source/chapter3/07_Patterns.md index d9aa54c2..058c338e 100755 --- a/source/chapter3/07_Patterns.md +++ b/source/chapter3/07_Patterns.md @@ -1,13 +1,15 @@ # 模式(Patterns) ------------------ - +----------------- + > 1.0 > 翻译:[honghaoz](https://github.com/honghaoz) > 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai) > 2.0 -> 翻译+校对:[ray16897188](https://github.com/ray16897188), -> [BridgeQ](https://github.com/WXGBridgeQ) +> 翻译+校对:[ray16897188](https://github.com/ray16897188), + +> 2.1 +> 翻译:[BridgeQ](https://github.com/WXGBridgeQ) 本页内容包括: @@ -17,97 +19,101 @@ - [元组模式(Tuple Pattern)](#tuple_pattern) - [枚举用例模式(Enumeration Case Pattern)](#enumeration_case_pattern) - [可选模式(Optional Pattern)](#optional_pattern) -- [类型转换模式(Type-Casting Pattern)](#type-casting_pattern) +- [类型转换模式(Type-Casting Pattern)](#type-casting_patterns) - [表达式模式(Expression Pattern)](#expression_pattern) -模式(pattern)代表了单个值或者复合值的结构。例如,元组`(1, 2)`的结构是逗号分隔的,包含两个元素的列表。因为模式代表一种值的结构,而不是特定的某个值,你可以把模式和各种同类型的值匹配起来。比如,`(x, y)`可以匹配元组`(1, 2)`,以及任何含两个元素的元组。除了将模式与一个值匹配外,你可以从复合值中提取出部分或全部,然后分别把各个部分和一个常量或变量绑定起来。 +模式代表单个值或者复合值的结构。例如,元组 `(1, 2)` 的结构是由逗号分隔的,包含两个元素的列表。因为模式代表一种值的结构,而不是特定的某个值,你可以利用模式来匹配各种各样的值。比如,`(x, y)` 可以匹配元组 `(1, 2)`,以及任何含两个元素的元组。除了利用模式匹配一个值以外,你可以从复合值中提取出部分或全部值,然后分别把各个部分的值和一个常量或变量绑定起来。 -swift语言中模式有2个基本的分类:一类能成功和任何值的类型相匹配,另一类在运行时(runtime)和某特定值匹配时可能会失败。 +Swift 中的模式分为两类:一种能成功匹配任何类型的值,另一种在运行时匹配某个特定值时可能会失败。 -第一类模式用于解构简单变量,常量和可选绑定中的值。此类模式包括通配符模式(wildcard patterns),标识符模式(identifier patterns),以及任何包含了它们的值绑定模式(value binding patterns)或者元祖模式(tuple patterns)。你可以为这类模式指定一个类型标注(type annotation)从而限制它们只能匹配某种特定类型的值。 +第一类模式用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式、标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型标注,从而限制它们只能匹配某种特定类型的值。 -第二类模式用于全模式匹配,这种情况下你用来相比较的值在运行时可能还不存在。此类模式包括枚举用例模式(enumeration case patterns),可选模式(optional patterns),表达式模式(expression patterns)和类型转换模式(type-casting patterns)。你在`switch`语句的case标签中,`do`语句的`catch`从句中,或者在`if, while, guard`和`for-in`语句的case条件句中使用这类模式。 +第二类模式用于全模式匹配,这种情况下你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式、可选模式、表达式模式和类型转换模式。你在 `switch` 语句的 `case` 标签中,`do` 语句的 `catch` 子句中,或者在 `if`、`while`、`guard` 和 `for-in` 语句的 `case` 条件句中使用这类模式。 -> 模式(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) _可选_ -> *模式* → [*枚举用例模式*](../chapter3/07_Patterns.html#enum_case_pattern) -> *模式* → [*可选模式*](../chapter3/07_Patterns.html#optional_pattern) -> *模式* → [*类型转换模式*](../chapter3/07_Patterns.html#type_casting_pattern) -> *模式* → [*表达式模式*](../chapter3/07_Patterns.html#expression_pattern) +> 模式语法 + +> *模式* → [*通配符模式*](#wildcard_pattern) [*类型标注*](03_Types.md#type-annotation)可选 +> *模式* → [*标识符模式*](#identifier_pattern) [*类型标注*](03_Types.md#type-annotation)可选 +> *模式* → [*值绑定模式*](#value-binding-pattern) +> *模式* → [*元组模式*](#tuple-pattern) [*类型标注*](03_Types.md#type-annotation)可选 +> *模式* → [*枚举用例模式*](#enum-case-pattern) +> *模式* → [*可选模式*](#optional-pattern) +> *模式* → [*类型转换模式*](#type-casting-pattern) +> *模式* → [*表达式模式*](#expression-pattern) ## 通配符模式(Wildcard Pattern) -通配符模式由一个下划线(_)构成,且匹配并忽略任何值。当你不在乎被匹配的值时可以使用该模式。例如,下面这段代码在闭区间`1...3`中循环,每次循环时忽略该区间内的当前值: +通配符模式由一个下划线(`_`)构成,用于匹配并忽略任何值。当你想忽略被匹配的值时可以使用该模式。例如,下面这段代码在闭区间 `1...3` 中迭代,每次迭代都忽略该区间的当前值: ```swift for _ in 1...3 { - // Do something three times. + // ... } ``` > 通配符模式语法 + > *通配符模式* → **_** ## 标识符模式(Identifier Pattern) -标识符模式匹配任何值,并将匹配的值和一个变量或常量绑定起来。例如,在下面的常量声明中,`someValue`是一个标识符模式,匹配了类型是`Int`的`42`。 +标识符模式匹配任何值,并将匹配的值和一个变量或常量绑定起来。例如,在下面的常量声明中,`someValue` 是一个标识符模式,匹配了 `Int` 类型的 `42`: ```swift let someValue = 42 ``` -当匹配成功时,`42`被绑定(赋值)给常量`someValue`。 +当匹配成功时,`42` 被绑定(赋值)给常量 `someValue`。 -如果一个变量或常量声明的左边的模式是一个标识符模式,那么这个标识符模式是一个隐式的值绑定模式(value-binding pattern)。 +如果一个变量或常量声明的左边是一个标识符模式,那么这个标识符模式是值绑定模式的子模式。 > 标识符模式语法 -> *标识符模式* → [*标识符*](LexicalStructure.html#identifier) + +> *标识符模式* → [*标识符*](02_Lexical_Structure.md#identifier) ## 值绑定模式(Value-Binding Pattern) -值绑定模式把匹配到的值绑定给一个变量或常量名。把绑定匹配到的值绑定给常量时,用关键字`let`,绑定给变量时,用关键字`var`。 +值绑定模式把匹配到的值绑定给一个变量或常量。把匹配到的值绑定给常量时,用关键字 `let`,绑定给变量时,用关键字 `var`。 -在值绑定模式中的标识符模式会把新命名的变量或常量与匹配值做绑定。例如,你可以拆开一个元组的元素,然后把每个元素绑定到其相应一个的标识符模式中。 +在值绑定模式中的标识符模式会把新命名的变量或常量与匹配到的值做绑定。例如,你可以拆开一个元组,然后把每个元素绑定到相应的标识符模式中。 ```swift let point = (3, 2) switch point { - // Bind x and y to the elements of point. +// 将 point 中的元素绑定到 x 和 y case let (x, y): print("The point is at (\(x), \(y)).") } -// prints "The point is at (3, 2).” +// 打印 “The point is at (3, 2).” ``` -在上面这个例子中,`let`将元组模式`(x, y)`分配到各个标识符模式。正是由于这么做,`switch`语句中`case let (x, y):`和`case (let x, let y):`匹配到的值是一样的。 +在上面这个例子中,`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) +> 值绑定模式语法 + +> *值绑定模式* → **var** [*模式*](#pattern) | **let** [*模式*](#pattern) -## 元组模式(Tuple Pattern) +## 元组模式 -元组模式是逗号分隔的,有零个或多个模式的列表,并被一对圆括号括起来。元组模式匹配相应元组类型的值。 +元组模式是由逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。 -你可以使用类型标注去限制一个元组模式能匹配哪些种元组类型。例如,在常量声明`let (x, y): (Int, Int) = (1, 2)`中的元组模式`(x, y): (Int, Int)`只匹配两个元素都是`Int`这种类型的元组。如果仅需要限制一个元组模式中的某几个元素,只需要直接对这几个元素提供类型标注即可。例如,在`let (x: String, y)`中的元组模式可以和任何有两个元素,且第一个元素类型是`String`的元组类型匹配。 +你可以使用类型标注去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 `let (x, y): (Int, Int) = (1, 2)` 中的元组模式 `(x, y): (Int, Int)` 只匹配两个元素都是 `Int` 类型的元组。 -当元组模式被用在`for-in`语句或者变量或常量声明时,它仅可以包含通配符模式,标识符模式,可选模式或者其他包含这些模式的元祖模式。比如下面这段代码就不正确,因为`(x, 0)`中的元素`0`是一个表达式模式: +当元组模式被用于 `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 { /* ... */ } ``` -对于只包含一个元素的元组,括号是不起作用的。模式只匹配这个单个元素的类型。举例来说,下面3条语句是等效的: +只包含一个元素的元组模式的圆括号没有效果,模式只匹配这个单个元素的类型。举例来说,下面的语句是等效的: ```swift let a = 2 // a: Int = 2 @@ -116,80 +122,89 @@ 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) + +> *元组模式* → **(** [*元组模式元素列表*](#tuple-pattern-element-list)可选 **)** + +> *元组模式元素列表* → [*元组模式元素*](#tuple-pattern-element) | [*元组模式元素*](#tuple-pattern-element) **,** [*元组模式元素列表*](#tuple-pattern-element-list) + +> *元组模式元素* → [*模式*](#pattern) ## 枚举用例模式(Enumeration Case Pattern) -一个枚举用例模式匹配现有的某个枚举类型的某个用例(case)。枚举用例模式出现在`switch`语句中的case标签中,以及`if`,`while`,`guard`和`for-in`语句的case条件中。 +枚举用例模式匹配现有的某个枚举类型的某个用例。枚举用例模式出现在 `switch` 语句中的 `case` 标签中,以及 `if`、`while`、`guard` 和 `for-in` 语句的 `case` 条件中。 -如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用`switch`语句来匹配包含关联值枚举用例的例子,请参阅`Associated Values`. +如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用 `switch` 语句来匹配包含关联值的枚举用例的例子,请参阅 [关联值](../chapter2/08_Enumerations.md#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) _可选_ + +> *枚举用例模式* → [*类型标识*](03_Types.md#type-identifier)可选 **.** [*枚举用例名*](05_Declarations.md#enum-case-name) [*元组模式*](#tuple-pattern)可选 ## 可选模式(Optional Pattern) -可选模式与封装在一个`Optional(Wrapped)`或者一个`ExplicitlyUnwrappedOptional(Wrapped)`枚举中的`Some(Wrapped)`用例相匹配。可选模式由一个标识符模式和紧随其后的一个问号组成,在某些情况下表现为枚举用例模式。 +可选模式匹配包装在一个 `Optional(Wrapped)` 或者 `ExplicitlyUnwrappedOptional(Wrapped)` 枚举中的 `Some(Wrapped)` 用例中的值。可选模式由一个标识符模式和紧随其后的一个问号组成,可以像枚举用例模式一样使用。 -由于可选模式是`optional`和`ImplicitlyUnwrappedOptional`枚举用例模式的语法糖(syntactic sugar),下面的2种写法是一样的: +由于可选模式是 `Optional` 和 `ImplicitlyUnwrappedOptional` 枚举用例模式的语法糖,下面两种写法是等效的: ```swift let someOptional: Int? = 42 -// Match using an enumeration case pattern +// 使用枚举用例模式匹配 if case .Some(let x) = someOptional { print(x) } -// Match using an optional pattern +// 使用可选模式匹配 if case let x? = someOptional { print(x) } ``` -如果一个数组的元素是可选类型,可选模式为`for-in`语句提供了一种在该数组中迭代的简便方式,只为数组中的非空`non-nil`元素执行循环体。 + +可选模式为 `for-in` 语句提供了一种迭代数组的简便方式,只为数组中非 `nil` 的元素执行循环体。 ```swift let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5] -// Match only non-nil values +// 只匹配非 nil 的元素 for case let number? in arrayOfOptinalInts { print("Found a \(number)") } -//Found a 2 -//Found a 3 -//Found a 5 - +// Found a 2 +// Found a 3 +// Found a 5 ``` + > 可选模式语法 -> *可选模式* → [*标识符模式*](../chapter3/03_Types.html#type_identifier) ? + +> *可选模式* → [*标识符模式*](03_Types.md#type-identifier) **?** ## 类型转换模式(Type-Casting Patterns) -有两种类型转换模式,`is`模式和`as`模式。这两种模式只出现在`switch`语句中的case标签中。`is`模式和`as`模式有以下形式: +有两种类型转换模式,`is` 模式和 `as` 模式。`is` 模式只出现在 `switch` 语句中的 `case` 标签中。`is` 模式和 `as` 模式形式如下: -> is `type` -> `pattern` as `type` +> is `类型` +> `模式` as `类型` -`is`模式仅当一个值的类型在运行时(runtime)和`is`模式右边的指定类型一致 - 或者是该类型的子类 - 的情况下,才会匹配这个值。`is`模式和`is`操作符有相似表现,它们都进行类型转换,却舍弃返回的类型。 +`is` 模式仅当一个值的类型在运行时和 `is` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。`is` 模式和 `is` 运算符有相似表现,它们都进行类型转换,但是 `is` 模式没有返回类型。 -`as`模式仅当一个值的类型在运行时(runtime)和`as`模式右边的指定类型一致 - 或者是该类型的子类 - 的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成`as`模式左边指定的模式。 +`as` 模式仅当一个值的类型在运行时和 `as` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 `as` 模式右边指定的类型。 -关于使用`switch`语句来匹配`is`模式和`as`模式值的例子,请参阅`Type Casting for Any and AnyObject`。 +关于使用 `switch` 语句配合 `is` 模式和 `as` 模式来匹配值的例子,请参阅 [Any 和 AnyObject 的类型转换](../chapter2/19_Type_Casting.md#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) + +> *类型转换模式* → [*is模式*](#is-pattern) | [*as模式*](#as-pattern) + +> *is模式* → **is** [*类型*](03_Types.md#type) + +> *as模式* → [*模式*](#pattern) **as** [*类型*](03_Types.md#type) ## 表达式模式(Expression Pattern) -一个表达式模式代表了一个表达式的值。表达式模式只出现在`switch`语句中的`case`标签中。 +表达式模式代表表达式的值。表达式模式只出现在 `switch` 语句中的 `case` 标签中。 -由表达式模式所代表的表达式与使用了Swift标准库中`~=`操作符的输入表达式的值进行比较。如果`~=`操作符返回`true`,则匹配成功。默认情况下,`~=`操作符使用`==`操作符来比较两个相同类型的值。它也可以将一个整型数值与一个`Range`对象中的一段整数区间做匹配,正如下面这个例子所示: +表达式模式代表的表达式会使用 Swift 标准库中的 `~=` 运算符与输入表达式的值进行比较。如果 `~=` 运算符返回 `true`,则匹配成功。默认情况下,`~=` 运算符使用 `==` 运算符来比较两个相同类型的值。它也可以将一个整型数值与一个 `Range` 实例中的一段整数区间做匹配,正如下面这个例子所示: ```swift let point = (1, 2) @@ -201,24 +216,26 @@ case (-2...2, -2...2): default: print("The point is at (\(point.0), \(point.1)).") } -// prints "(1, 2) is near the origin.” +// 打印 “(1, 2) is near the origin.” ``` -你可以重载`~=`操作符来提供自定义的表达式匹配行为。比如你可以重写上面的例子,拿`point`表达式去比较字符串形式的点。 +你可以重载 `~=` 运算符来提供自定义的表达式匹配行为。比如你可以重写上面的例子,将 `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"): print("(0, 0) is at the origin.") default: print("The point is at (\(point.0), \(point.1)).") } -// prints "(1, 2) is near the origin.” +// 打印 “The point is at (1, 2).” ``` > 表达式模式语法 -> *表达式模式* → [*表达式*](../chapter3/04_Expressions.html#expression) + +> *表达式模式* → [*表达式*](04_Expressions.md#expression) diff --git a/source/chapter3/08_Generic_Parameters_and_Arguments.md b/source/chapter3/08_Generic_Parameters_and_Arguments.md index fc4657f8..84e92707 100755 --- a/source/chapter3/08_Generic_Parameters_and_Arguments.md +++ b/source/chapter3/08_Generic_Parameters_and_Arguments.md @@ -8,34 +8,37 @@ > 2.0 > 翻译+校对:[wardenNScaiyi](https:github.com/wardenNScaiyi) +> 3.0 +> 翻译+校对:[chenmingjia](https:github.com/chenmingjia) + 本页包含内容: - [泛型形参子句](#generic_parameter) + - [Where 子句](#where_clauses) - [泛型实参子句](#generic_argument) -本节涉及泛型类型、泛型函数以及泛型初始化器(**initializer**)的参数,包括形参和实参。声明泛型类型、函数或初始化器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型初始化器时,就用具体的类型实参替代之。 +本节涉及泛型类型、泛型函数以及泛型构造器的参数,包括形参和实参。声明泛型类型、函数或构造器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型构造器时,就用具体的类型实参替代之。 -关于 Swift 语言的泛型概述,见[泛型](../chapter2/23_Generics.md)(第二部分第23章)。 +关于 Swift 语言的泛型概述,请参阅 [泛型](../chapter2/23_Generics.md)。 ## 泛型形参子句 -泛型形参子句指定泛型类型或函数的类型形参,以及这些参数的关联约束和关联类型要求(**requirement**)。泛型形参子句用尖括号(<>)包住,并且有以下两种形式: +泛型形参子句指定泛型类型或函数的类型形参,以及这些参数相关的约束和要求。泛型形参子句用尖括号(`<>`)包住,形式如下: > <`泛型形参列表`> -> <`泛型形参列表` where `关联类型要求`> 泛型形参列表中泛型形参用逗号分开,其中每一个采用以下形式: > `类型形参` : `约束` -泛型形参由两部分组成:类型形参及其后的可选约束。类型形参只是占位符类型(如 T,U,V,Key,Value 等)的名字而已。你可以在泛型类型、函数的其余部分或者初始化器声明,包括函数或初始化器的签名中使用它(与其任何相关类型)。 +泛型形参由两部分组成:类型形参及其后的可选约束。类型形参只是占位符类型(如 `T`,`U`,`V`,`Key`,`Value` 等)的名字而已。你可以在泛型类型、函数的其余部分或者构造器声明,包括函数或构造器的签名中使用它(以及它的关联类型)。 -约束用于指明该类型形参继承自某个类或者遵守某个协议或协议的一部分。例如,在下面的泛型函数中,泛型形参`T: Comparable`表示任何用于替代类型形参`T`的类型实参必须满足`Comparable`协议。 +约束用于指明该类型形参继承自某个类或者符合某个协议或协议组合。例如,在下面的泛型函数中,泛型形参 `T: Comparable` 表示任何用于替代类型形参 `T` 的类型实参必须满足 `Comparable` 协议。 ```swift -func simpleMax(x: T, _ y: T) -> T { +func simpleMax(_ x: T, _ y: T) -> T { if x < y { return y } @@ -43,74 +46,84 @@ func simpleMax(x: T, _ y: T) -> T { } ``` +例如,因为 `Int` 和 `Double` 均满足`Comparable`协议,所以该函数可以接受这两种类型。与泛型类型相反,调用泛型函数或构造器时不需要指定泛型实参子句。类型实参由传递给函数或构造器的实参推断而出。 - -如,`Int`和`Double`均满足`Comparable`协议,该函数接受任何一种类型。与泛型类型相反,调用泛型函数或初始化器时不需要指定泛型实参子句。类型实参由传递给函数或初始化器的实参推断而出。 - - -``` -simpleMax(17, 42) // T被推断出为Int类型 -simpleMax(3.14159, 2.71828) // T被推断出为Double类型 +```swift +simpleMax(17, 42) // T 被推断为 Int 类型 +simpleMax(3.14159, 2.71828) // T 被推断为 Double 类型 ``` -## Where 子句 + +### Where 子句 -要想对类型形参及其关联类型指定额外关联类型要求,可以在泛型形参列表之后添加`where`子句。`where`子句由关键字`where`及其后的用逗号分割的多个关联类型要求组成。 +要想对类型形参及其关联类型指定额外要求,可以在函数体或者类型的大括号之前添加 `where` 子句。`where` 子句由关键字 `where` 及其后的用逗号分隔的一个或多个要求组成。 -`where`子句中的关联关系用于指明该类型形参继承自某个类或遵守某个协议或协议的一部分。尽管`where`子句提供了语法糖使其有助于表达类型形参上的简单约束(如`T: Comparable`等同于`T where T: Comparable`,等等),但是依然可以用来对类型形参及其关联类型提供更复杂的约束。如,``表示泛型类型`T`继承自类`C`且遵守协议`P`。 +> `where` : `类型要求` -如上所述,可以强制约束类型形参的关联类型遵守某个协议。例如``表示`T`遵守`Generator`协议,而且`T`的关联类型`T.Element`遵守`Eauatable`协议(`T`有关联类型`Element`是因为`Generator`声明了`Element`,而`T`遵守`Generator`协议)。 +`where` 子句中的要求用于指明该类型形参继承自某个类或符合某个协议或协议组合。尽管 `where` 子句提供了语法糖使其有助于表达类型形参上的简单约束(如 `` 等同于 ` where T: Comparable`,等等),但是依然可以用来对类型形参及其关联类型提供更复杂的约束,例如你可以强制形参的关联类型遵守协议,如,` where S.Iterator.Element: Equatable` 表示泛型类型 `S` 遵守`Sequence`协议并且关联类型`S.Iterator.Element`遵守`Equatable`协议,这个约束确保队列的每一个元素都是符合 `Equatable` 协议的。 -也可以用操作符`==`来指定两个类型等效的关联关系。例如,有这样一个约束:`T`和`U`遵守`Generator`协议,同时要求它们的关联类型等同,可以这样来表达:``。 +也可以用操作符 `==` 来指定两个类型必须相同。例如,泛型形参子句 ` where S1.Iterator.Element == S2.Iterator.Element` 表示 `S1` 和 `S2` 必须都符合 `SequenceType` 协议,而且两个序列中的元素类型必须相同。 -当然,替代类型形参的类型实参必须满足所有类型形参的约束和关联类型要求。 +当然,替代类型形参的类型实参必须满足所有的约束和要求。 -泛型函数或初始化器可以重载,但在泛型形参子句中的类型形参必须有不同的约束或关联类型要求,抑或二者皆不同。当调用重载的泛型函数或始化器时,编译器会用这些约束来决定调用哪个重载函数或始化器。 +泛型函数或构造器可以重载,但在泛型形参子句中的类型形参必须有不同的约束或要求,抑或二者皆不同。当调用重载的泛型函数或构造器时,编译器会根据这些约束来决定调用哪个重载函数或构造器。 +更多关于泛型where从句的信息和关于泛型函数声明的例子,可以看一看 [泛型where子句](https://github.com/numbbbbb/the-swift-programming-language-in-chinese/blob/gh-pages/source/chapter2/23_Generics.md#where_clauses) > 泛型形参子句语法 -> *泛型参数子句* → **<** [*泛型参数列表*](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-parameter-list) [*约束子句*](#requirement-clause)可选 **>** + +> *泛型形参列表* → [*泛形形参*](#generic-parameter) | [*泛形形参*](#generic-parameter) **,** [*泛型形参列表*](#generic-parameter-list) + +> *泛形形参* → [*类型名称*](03_Types.html#type-name) +> *泛形形参* → [*类型名称*](03_Types.html#type-name) **:** [*类型标识符*](03_Types.html#type-identifier) +> *泛形形参* → [*类型名称*](03_Types.html#type-name) **:** [*协议合成类型*](03_Types.html#protocol-composition-type) + + +> *约束子句* → **where** [*约束列表*](#requirement-list) + +> *约束列表* → [*约束*](#requirement) | [*约束*](#requirement) **,** [*约束列表*](#requirement-list) + +> *约束* → [*一致性约束*](#conformance-requirement) | [*同类型约束*](#same-type-requirement) + + +> *一致性约束* → [*类型标识符*](03_Types.html#type-identifier) **:** [*类型标识符*](03_Types.html#type-identifier) +> *一致性约束* → [*类型标识符*](03_Types.html#type-identifier) **:** [*协议合成类型*](03_Types.html#protocol-composition-type) + +> *同类型约束* → [*类型标识符*](03_Types.html#type-identifier) **==** [*类型*](03_Types.html#type) ## 泛型实参子句 -泛型实参子句指定_泛型类型_的类型实参。泛型实参子句用尖括号(<>)包住,形式如下: +泛型实参子句指定泛型类型的类型实参。泛型实参子句用尖括号(`<>`)包住,形式如下: > <`泛型实参列表`> -泛型实参列表中类型实参有逗号分开。类型实参是实际具体类型的名字,用来替代泛型类型的泛型形参子句中的相应的类型形参。从而得到泛型类型的一个特化版本。如,Swift标准库的泛型字典类型定义如下: - +泛型实参列表中类型实参用逗号分开。类型实参是实际具体类型的名字,用来替代泛型类型的泛型形参子句中的相应的类型形参。从而得到泛型类型的一个特化版本。例如,Swift 标准库中的泛型字典类型的的简化定义如下: ```swift -struct Dictionary: Collection, DictionaryLiteralConvertible { - - /* .. */ - +struct Dictionary: CollectionType, DictionaryLiteralConvertible { + /* ... */ } ``` -泛型`Dictionary`类型的特化版本,`Dictionary`就是用具体的`String`和`Int`类型替代泛型类型`KeyType: Hashable`和`ValueType`产生的。每一个类型实参必须满足它所替代的泛型形参的所有约束,包括任何`where`子句所指定的额外的关联类型要求。上面的例子中,类型形参`Key`类型要求满足`Hashable`协议,因此`String`也必须满足`Hashable`协议。 +泛型 `Dictionary` 类型的特化版本,`Dictionary` 就是用具体的 `String` 和 `Int` 类型替代泛型类型 `Key: Hashable` 和 `Value` 产生的。每一个类型实参必须满足它所替代的泛型形参的所有约束,包括任何 `where` 子句所指定的额外的关联类型要求。上面的例子中,类型形参 `Key` 的类型必须符合 `Hashable` 协议,因此 `String` 也必须满足 `Hashable` 协议。 -可以用本身就是泛型类型的特化版本的类型实参替代类型形参(假设已满足合适的约束和关联类型要求)。例如,为了生成一个元素类型是整型数组的数组,可以用数组的特化版本`Array`替代泛型类型`Array`的类型形参 `T` 来实现。 +可以用本身就是泛型类型的特化版本的类型实参替代类型形参(假设已满足合适的约束和关联类型要求)。例如,为了生成一个元素类型是整型数组的数组,可以用数组的特化版本 `Array` 替代泛型类型 `Array` 的类型形参 `T` 来实现。 -``` +```swift let arrayOfArrays: Array> = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] ``` -如[泛型形参子句](#generic_parameter)所述,不能用泛型实参子句来指定泛型函数或初始化器的类型实参。 +如 [泛型形参子句](#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) + +> *泛型实参子句* → **<** [*泛型实参列表*](#generic-argument-list) **>** + +> *泛型实参列表* → [*泛型实参*](#generic-argument) | [*泛型实参*](#generic-argument) **,** [*泛型实参列表*](#generic-argument-list) + +> *泛型实参* → [*类型*](03_Types.html#type) diff --git a/source/chapter3/09_Summary_of_the_Grammar.md b/source/chapter3/09_Summary_of_the_Grammar.md index 79988361..4377da55 100755 --- a/source/chapter3/09_Summary_of_the_Grammar.md +++ b/source/chapter3/09_Summary_of_the_Grammar.md @@ -239,7 +239,7 @@ > *声明* → [*构造器声明*](../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#subscript_declaration) > *声明* → [*运算符声明*](../chapter3/05_Declarations.html#operator_declaration) > *声明(Declarations)集* → [*声明*](../chapter3/05_Declarations.html#declaration) [*声明(Declarations)集*](../chapter3/05_Declarations.html#declarations) _可选_ @@ -373,7 +373,7 @@ > *协议成员声明* → [*协议属性声明*](../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_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) _可选_ @@ -394,8 +394,8 @@ -> 协议下标脚本声明语法 -> *协议下标脚本声明* → [*下标脚本头(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#subscript_head) [*下标结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter关键字(Keyword)块*](../chapter3/05_Declarations.html#getter_setter_keyword_block) @@ -426,12 +426,12 @@ -> 下标脚本声明语法 -> *下标脚本声明* → [*下标脚本头(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) _可选_ [*声明修改器(declaration-modifiers)*](TODO) _可选_ **subscript** [*参数从句*](../chapter3/05_Declarations.html#parameter_clause) -> *下标脚本结果(Result)* → **->** [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type) +> 下标声明语法 +> *下标声明* → [*下标头(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) _可选_ [*声明修改器(declaration-modifiers)*](TODO) _可选_ **subscript** [*参数从句*](../chapter3/05_Declarations.html#parameter_clause) +> *下标结果(Result)* → **->** [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type) diff --git a/source/chapter3/10_Statements.md b/source/chapter3/10_Statements.md index abd6a674..c5bcabe3 100755 --- a/source/chapter3/10_Statements.md +++ b/source/chapter3/10_Statements.md @@ -4,587 +4,662 @@ > 1.0 > 翻译:[coverxit](https://github.com/coverxit) -> 校对:[numbbbbb](https://github.com/numbbbbb), [coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai), +> 校对:[numbbbbb](https://github.com/numbbbbb), [coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai) > 2.0 > 翻译+校对:[littledogboy](https://github.com/littledogboy) +> 2.2 +> 翻译:[chenmingbiao](https://github.com/chenmingbiao) + +> 3.0 +> 翻译:[chenmingjia](https://github.com/chenmingjia) + 本页包含内容: - [循环语句](#loop_statements) + - [For-In 语句](#for-in_statements) + - [While 语句](#while_statements) + - [Repeat-While 语句](#repeat-while_statements) - [分支语句](#branch_statements) -- [带标签的语句](#labeled_statement) -- [控制传递语句](#control_transfer_statements) + - [If 语句](#if_statements) + - [Guard 语句](#guard_statements) + - [Switch 语句](#switch_statements) +- [带标签的语句](#labeled_statements) +- [控制转移语句](#control_transfer_statements) + - [Break 语句](#break_statement) + - [Continue 语句](#continue_statement) + - [Fallthrough 语句](#fallthrough_statements) + - [Return 语句](#return_statements) + - [Throw 语句](#throw_statements) +- [Defer 语句](#defer_statements) +- [Do 语句](#do_statements) +- [编译器控制语句](#compiler_control_statements) + - [编译配置语句](#build_config_statements) + - [行控制语句](#line_control_statements) +- [可用性条件](#availability_condition) -在 Swift 中,有三种类型的语句:简单语句、编译控制语句和控制流语句。简单语句是最常见的,用于构造表达式或者声明。编译控制语句允许程序改变编译器的行为以及包含构建配置和源代码控制语句。 +在 Swift 中,有三种类型的语句:简单语句、编译器控制语句和控制流语句。简单语句是最常见的,用于构造表达式或者声明。编译器控制语句允许程序改变编译器的行为,包含编译配置语句和行控制语句。 -控制流语句则用于控制程序执行的流程,Swift 中有几种类型的控制流语句:循环语句、分支语句和控制传递语句。循环语句用于重复执行代码块;分支语句用于执行满足特定条件的代码块;控制传递语句则用于修改代码的执行顺序。另外,Swift 提供了 `do` 语句来引入范围以及捕获和处理错误,还提供了 `defer` 语句在退出当前范围之前执行清理操作。 +控制流语句则用于控制程序执行的流程,Swift 中有多种类型的控制流语句:循环语句、分支语句和控制转移语句。循环语句用于重复执行代码块;分支语句用于执行满足特定条件的代码块;控制转移语句则用于改变代码的执行顺序。另外,Swift 提供了 `do` 语句,用于构建局部作用域,还用于错误的捕获和处理;还提供了 `defer` 语句,用于退出当前作用域之前执行清理操作。 -是否将分号(`;`)添加到语句的结尾处是可选的。但若要在同一行内写多条独立语句,请务必使用分号。 +是否将分号(`;`)添加到语句的末尾是可选的。但若要在同一行内写多条独立语句,则必须使用分号。 > 语句语法 -> *语句* → [*表达式*](../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) **;** _可选_ -> *语句* → [*XXX语句*](../chapter3/10_Statements.html#control_transfer_statement) **;** _可选_ -> *多条语句(Statements)* → [*语句*](../chapter3/10_Statements.html#statement) [*多条语句(Statements)*](../chapter3/10_Statements.html#statements) _可选_ + +> *语句* → [*表达式*](04_Expressions.md#expression) **;**可选 +> *语句* → [*声明*](05_Declarations.md#declaration) **;**可选 +> *语句* → [*循环语句*](#loop-statement) **;**可选 +> *语句* → [*分支语句*](#branch-statement) **;**可选 +> *语句* → [*带标签的语句*](#labeled-statement) **;**可选 +> *语句* → [*控制转移语句*](#control-transfer-statement) **;**可选 +> *语句* → [*defer 语句*](#defer-statement) **;**可选 +> *语句* → [*do 语句*](#do-statement) **:**可选 +> *语句* → [*编译器控制语句*](#compiler-control-statement) + +> *多条语句* → [*语句*](#statement) [*多条语句*](#statements)可选 ## 循环语句 -取决于特定的循环条件,循环语句允许重复执行代码块。Swift 提供四种类型的循环语句:`for`语句、`for-in`语句、`while`语句和`do-while`语句。 +循环语句会根据特定的循环条件来重复执行代码块。Swift 提供三种类型的循环语句:`for-in` 语句、`while` 语句和 `repeat-while` 语句。 -通过`break`语句和`continue`语句可以改变循环语句的控制流。有关这两条语句,详情参见 [Break 语句](#break_statement)和 [Continue 语句](#continue_statement)。 +通过 `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* 表达式的值的类型必须遵循`BooleanType `协议。 - -> 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-statement) +> *循环语句* → [*while 语句*](#while-statement) +> *循环语句* → [*repeat-while 语句*](#repeat-while-statement) ### For-In 语句 -`for-in`语句允许在重复执行代码块的同时,迭代集合(或遵循`Sequence`协议的任意类型)中的每一项。 +`for-in` 语句会为集合(或实现了 `SequenceType` 协议的任意类型)中的每一项执行一次代码块。 -`for-in`语句的形式如下: +`for-in` 语句的形式如下: -> for `item` in `collection` { -> `statements` -> } +```swift +for 项 in 集合 { + 循环体语句 +} +``` -`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) +`for-in` 语句在循环开始前会调用集合表达式的 `generate()` 方法来获取一个实现了 `GeneratorType` 协议的类型的值。接下来循环开始,反复调用该值的 `next()` 方法。如果其返回值不是 `None`,它将会被赋给“项”,然后执行循环体语句,执行完毕后回到循环开始处,继续重复这一过程;否则,既不会赋值也不会执行循环体语句,`for-in` 语句至此执行完毕。 +> for-in 语句语法 + +> *for-in 语句* → **for** **case**可选 [*模式*](07_Patterns.md#pattern) **in** [*表达式*](04_Expressions.md#expression) [*where子句*](#where-clause)可选 [*代码块*](05_Declarations.md#code-block) ### While 语句 -`while`语句当循环条件为真时,允许重复执行代码块。 +只要循环条件为真,`while` 语句就会重复执行代码块。 -`while`语句的形式如下: +`while` 语句的形式如下: -> while `condition` { -> `statements` -> } +```swift +while 条件 { + 语句 +} +``` -`while`语句的执行流程如下: +`while` 语句的执行流程如下: -1. 计算 *condition* 表达式: - 如果为真`true`,转到第2步。如果为`false`,`while`至此执行完毕。 -2. 执行 *statements* ,然后转到第1步。 +1. 判断条件的值。如果为 `true`,转到第 2 步;如果为 `false`,`while` 语句至此执行完毕。 +2. 执行循环体中的语句,然后重复第 1 步。 -由于 *condition* 的值在 *statements* 执行前就已计算出,因此`while`语句中的 *statements* 可能会被执行若干次,也可能不会被执行。 +由于会在执行循环体中的语句前判断条件的值,因此循环体中的语句可能会被执行若干次,也可能一次也不会被执行。 -*condition* 表达式的值的类型必须遵循`BooleanType `协议。同时,*condition* 表达式也可以使用可选绑定,详情参见[可选绑定](../chapter2/01_The_Basics.html#optional_binding)。 +条件的结果必须是Bool类型或者Bool的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 -> While 循环语法 -> *while语句* → **while** [*while条件*](../chapter3/10_Statements.html#while_condition) [*代码块*](../chapter3/05_Declarations.html#code_block) -> *条件* → [*表达式*](../chapter3/04_Expressions.html#expression) | [*声明*](../chapter3/05_Declarations.html#declaration) -> *条件* → [*表达式*](../chapter3/04_Expressions.html#expression) -> *条件* → [*表达式*](../chapter3/04_Expressions.html#expression) | [*条件列表*](TODO) -> *条件* → [*可用条件*](../chapter3/10_Statement.html#availability) [*表达式*](../chapter3/04_Expressions.html#expression) -> *条件列表* → [*条件条件*](TODO) [*条件列表*](TODO) -> *条件* → [*可用条件*](../chapter3/10_Statement.html#availability) [可选绑定条件](../chapter2/01_The_Basics.html#optional_binding) -> *case条件* → **case** [*模式*](../chapter3/07_Patterns.html#pattern) [构造器](TODO) [where](DOTO) -> *可选绑定条件* → [可选绑定头](TODO) [持续可选绑定](TODO) [持续可选绑定列表](TODO) -> *可选绑定头* → **let** [*模式*](../chapter3/07_Patterns.html#pattern) [构造器](TODO) **var** [*模式*](../chapter3/07_Patterns.html#pattern) [构造器](TODO) -> *可持续绑定列表* → [*模式*](../chapter3/07_Patterns.html#pattern) | [构造器](TODO) [可选绑定头](TODO) -> +> while 语句语法 + + +> *while 语句* → **while** [*条件子句*](#condition-clause) [*代码块*](05_Declarations.md#code-block) + + +> *条件子句* → [*表达式*](04_Expressions.md#expression) +> *条件子句* → [*表达式*](04_Expressions.md#expression) **,** [*条件列表*](#condition-list) +> *条件子句* → [*条件列表*](#condition-list) +> *条件子句* → [*可用性条件*](#availability-condition) **,** [*表达式*](04_Expressions.md#expression) + + +> *条件列表* → [*条件*](#condition) | [*条件*](#condition) **,** [*条件列表*](#condition-list) + +> *条件* → [*表达式*](04_Expressions.md#expression) |[*可用性条件*](#availability-condition) | [*case条件*](#case-condition) | [*可选绑定条件*](#optional-binding-condition) + +> *case 条件* → **case** [*模式*](07_Patterns.md#pattern) [*构造器*](05_Declarations.md#initializer) + + +> *可选绑定条件* → **let** [*模式*](07_Patterns.md#pattern) [*构造器*](05_Declarations.md#initializer) | **var** [*模式*](07_Patterns.md#pattern) [*构造器*](05_Declarations.md#initializer) - - + ### Repeat-While 语句 -`repeat-while`语句允许代码块被执行一次或多次。 +`repeat-while` 语句至少执行一次代码块,之后只要循环条件为真,就会重复执行代码块。 -`repeat-while`语句的形式如下: +`repeat-while` 语句的形式如下: -> repeat { -> `statements` -> } while `condition` +```swift +repeat { + 语句 +} while 条件 +``` -`repeat-while`语句的执行流程如下: +`repeat-while` 语句的执行流程如下: -1. 执行 *statements*,然后转到第2步。 -2. 计算 *condition* 表达式: - 如果为`true`,转到第1步。如果为`false`,`repeat-while`至此执行完毕。 +1. 执行循环体中的语句,然后转到第 2 步。 +2. 判断条件的值。如果为 `true`,重复第 1 步;如果为 `false`,`repeat-while` 语句至此执行完毕。 -由于 *condition* 表达式的值是在 *statements* 执行后才计算出,因此`repeat-while`语句中的 *statements* 至少会被执行一次。 +由于条件的值是在循环体中的语句执行后才进行判断,因此循环体中的语句至少会被执行一次。 -*condition* 表达式的值的类型必须遵循`BooleanType `协议。同时,*condition* 表达式也可以使用可选绑定,详情参见[可选绑定](../chapter2/01_The_Basics.html#optional_binding)。 +条件的结果必须是Bool类型或者Bool的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 -> Repeat-While 循环语法 -> * repeat-while语句* → **repeat** [*代码块*](../chapter3/05_Declarations.html#code_block) **while** [*while条件*](../chapter3/10_Statements.html#while_condition) +> repeat-while 语句语法 + +> *repeat-while 语句* → **repeat** [*代码块*](05_Declarations.md#code-block) **while** [*表达式*](04_Expressions.md#expression) ## 分支语句 -取决于一个或者多个条件的值,分支语句允许程序执行指定部分的代码。显然,分支语句中条件的值将会决定如何分支以及执行哪一块代码。Swift 提供两种类型的分支语句:`if`语句和`switch`语句。 +分支语句会根据一个或者多个条件来执行指定部分的代码。分支语句中的条件将会决定程序如何分支以及执行哪部分代码。Swift 提供两种类型的分支语句:`if` 语句和 `switch` 语句。 -`switch`语句中的控制流可以用`break`语句修改,详情请见[Break 语句](#break_statement)。 +`if` 语句和 `switch` 语句中的控制流可以用 `break` 语句改变,请参阅 [Break 语句](#break_statement)。 > 分支语句语法 -> *分支语句* → [*if语句*](../chapter3/10_Statements.html#if_statement) -> *分支语句* → [*switch语句*](../chapter3/10_Statements.html#switch_statement) - + +> *分支语句* → [*if 语句*](#if-statement) +> *分支语句* → [*guard 语句*](#guard-statement) +> *分支语句* → [*switch 语句*](#switch-statement) ### If 语句 -取决于一个或多个条件的值,`if`语句将决定执行哪一块代码。 +`if` 语句会根据一个或多个条件来决定执行哪一块代码。 -`if`语句有两种标准形式,在这两种形式里都必须有大括号。 +`if` 语句有两种基本形式,无论哪种形式,都必须有花括号。 第一种形式是当且仅当条件为真时执行代码,像下面这样: -> if `condition` { -> `statements` -> } +```swift +if 条件 { + 语句 +} +``` -第二种形式是在第一种形式的基础上添加 *else 语句*,当只有一个 else 语句时,像下面这样: +第二种形式是在第一种形式的基础上添加 `else` 语句,当只有一个 `else` 语句时,像下面这样: -> if `condition` { -> `statements to execute if condition is true` -> } else { -> `statements to execute if condition is false` -> } +```swift +if 条件 { + 若条件为真则执行这部分语句 +} else { + 若条件为假则执行这部分语句 +} +``` -同时,else 语句也可包含`if`语句,从而形成一条链来测试更多的条件,像下面这样: +`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` -> } +```swift +if 条件1 { + 若条件1为真则执行这部分语句 +} else if 条件2 { + 若条件2为真则执行这部分语句 +} else { + 若前两个条件均为假则执行这部分语句 +} +``` -`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) +`if` 语句中条件的结果必须是Bool类型或者Bool的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。 +> if 语句语法 + +> *if 语句* → **if** [*条件子句*](#condition-clause) [*代码块*](05_Declarations.md#code-block) [*else子句*](#else-clause)可选 + +> *else 子句* → **else** [*代码块*](05_Declarations.md#code-block) | **else** [*if语句*](#if-statement) ### Guard 语句 -`guard` 语句用来转移程序控制出其作用域,如果一个或者多个条件不成立。 - `guard` 语句的格式如下: - > guard `condition` else { - `statements` - >} +如果一个或者多个条件不成立,可用 `guard` 语句用来退出当前作用域。 + +`guard` 语句的格式如下: + +```swift +guard 条件 else { + 语句 +} +``` + +`guard` 语句中条件的结果必须是Bool类型或者Bool的桥接类型。另外,条件也可以是一条可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.html#optional_binding)。 - `guard`语句中条件值的类型必须遵循`LogicValue`协议。且条件可以使用可选绑定,详情参见[可选绑定](../chapter2/01_The_Basics.html#optional_binding)。 +在 `guard` 语句中进行可选绑定的常量或者变量,其可用范围从声明开始直到作用域结束。 - 在`guard`语句中声明的常量或者变量,可用范围从声明开始到作用域结束,常量和变量的值从可选绑定声明中分配。 - - `guard`语句需要有`else`子句,并且必须调用被`noreturn`属性标记的函数,或者使用下面的语句把程序执行转移到guard语句的作用域外。 +`guard` 语句必须有 `else` 子句,而且必须在该子句中调用标记 `noreturn` 特性的函数,或者使用下面的语句退出当前作用域: * `return` * `break` * `continue` * `throw` -执行转移语句详情参见[控制传递语句](TODO) +关于控制转移语句,请参阅 [控制转移语句](#control_transfer_statements)。 +> guard 语句语法 + +> *guard 语句* → **guard** [*条件子句*](#condition-clause) **else** [*代码块*](05_Declarations.html#code-block) ### Switch 语句 -取决于`switch`语句的*控制表达式(control expression)*,`switch`语句将决定执行哪一块代码。 +`switch` 语句会根据控制表达式的值来决定执行哪部分代码。 -`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` -> } +```swift +switch 控制表达式 { +case 模式1: + 语句 +case 模式2 where 条件: + 语句 +case 模式3 where 条件, 模式4 where 条件: + 语句 +default: + 语句 +} +``` -`switch`语句的*控制表达式(control expression)*会首先被计算,然后与每一个 case 的模式(pattern)进行匹配。如果匹配成功,程序将会执行对应的 case 分支里的 *statements*。另外,每一个 case 分支都不能为空,也就是说在每一个 case 分支中至少有一条语句。如果你不想在匹配到的 case 分支中执行代码,只需在该分支里写一条`break`语句即可。 +`switch` 语句会先计算控制表达式的值,然后与每一个 `case` 的模式进行匹配。如果匹配成功,程序将会执行对应的 `case` 中的语句。另外,每一个 `case` 都不能为空,也就是说在每一个 `case` 中必须至少有一条语句。如果你不想在匹配到的 `case` 中执行代码,只需在该 `case` 中写一条 `break` 语句即可。 -可以用作控制表达式的值是十分灵活的,除了标量类型(scalar types,如`Int`、`Character`)外,你可以使用任何类型的值,包括浮点数、字符串、元组、自定义类的实例和可选(optional)类型,甚至是枚举类型中的成员值和指定的范围(range)等。关于在`switch`语句中使用这些类型,详情参见[控制流](../chapter2/05_Control_Flow.html)一章的 [Switch](../chapter2/05_Control_Flow.html#switch)。 +可以用作控制表达式的值是十分灵活的。除了标量类型外,如 `Int`、`Character`,你可以使用任何类型的值,包括浮点数、字符串、元组、自定义类型的实例和可选类型。控制表达式的值还可以用来匹配枚举类型中的成员值或是检查该值是否包含在指定的 `Range` 中。关于如何在 `switch` 语句中使用这些类型,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [Switch](../chapter2/05_Control_Flow.html#switch)。 -你可以在模式后面添加一个起保护作用的表达式(guard expression)。*起保护作用的表达式*是这样构成的:关键字`where`后面跟着一个作为额外测试条件的表达式。因此,当且仅当*控制表达式*匹配一个*case*的某个模式且起保护作用的表达式为真时,对应 case 分支中的 *statements* 才会被执行。在下面的例子中,*控制表达式*只会匹配含两个相等元素的元组,如`(1, 1)`: +每个 `case` 的模式后面可以有一个 `where` 子句。`where` 子句由 `where` 关键字紧跟一个提供额外条件的表达式组成。因此,当且仅当控制表达式匹配一个 `case` 的模式且 `where` 子句的表达式为真时,`case` 中的语句才会被执行。在下面的例子中,控制表达式只会匹配包含两个相等元素的元组,例如 `(1, 1)`: ```swift case let (x, y) where x == y: ``` -正如上面这个例子,也可以在模式中使用`let`(或`var`)语句来绑定常量(或变量)。这些常量(或变量)可以在其对应的起保护作用的表达式和其对应的*case*块里的代码中引用。但是,如果 case 中有多个模式匹配控制表达式,那么这些模式都不能绑定常量(或变量)。 +正如上面这个例子,也可以在模式中使用 `let`(或 `var`)语句来绑定常量(或变量)。这些常量(或变量)可以在对应的 `where` 子句以及 `case` 中的代码中使用。但是,如果一个 `case` 中含有多个模式,所有的模式必须包含相同的常量(或变量)绑定,并且每一个绑定的常量(或变量)必须在所有的条件模式中都有相同的类型。 -`switch`语句也可以包含默认(`default`)分支,只有其它 case 分支都无法匹配控制表达式时,默认分支中的代码才会被执行。一个`switch`语句只能有一个默认分支,而且必须在`switch`语句的最后面。 +`switch` 语句也可以包含默认分支,使用 `default` 关键字表示。只有所有 `case` 都无法匹配控制表达式时,默认分支中的代码才会被执行。一个 `switch` 语句只能有一个默认分支,而且必须在 `switch` 语句的最后面。 -尽管模式匹配操作实际的执行顺序,特别是模式的计算顺序是不可知的,但是 Swift 规定`switch`语句中的模式匹配的顺序和书写源代码的顺序保持一致。因此,当多个模式含有相同的值且能够匹配控制表达式时,程序只会执行源代码中第一个匹配的 case 分支中的代码。 +`switch` 语句中 `case` 的匹配顺序和源代码中的书写顺序保持一致。因此,当多个模式都能匹配控制表达式时,只有第一个匹配的 `case` 中的代码会被执行。 -#### Switch 语句必须是完备的 +#### Switch 语句不能有遗漏 -在 Swift 中,`switch`语句中控制表达式的每一个可能的值都必须至少有一个 case 分支与之对应。在某些情况下(例如,表达式的类型是`Int`),你可以使用默认块满足该要求。 +在 Swift 中,`switch` 语句中控制表达式的每一个可能的值都必须至少有一个 `case` 与之对应。在某些无法面面俱到的情况下(例如,表达式的类型是 `Int`),你可以使用 `default` 分支满足该要求。 -#### 不存在隐式的贯穿(fall through) +#### 不存在隐式落入 -当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这就意味着,如果你想执行下一个 case 分支,需要显式地在你需要的 case 分支里使用`fallthrough`语句。关于`fallthrough`语句的更多信息,详情参见 [Fallthrough 语句](#fallthrough_statement)。 +当匹配到的 `case` 中的代码执行完毕后,`switch` 语句会直接退出,而不会继续执行下一个 `case` 。这就意味着,如果你想执行下一个 `case`,需要显式地在当前 `case` 中使用 `fallthrough` 语句。关于 `fallthrough` 语句的更多信息,请参阅 [Fallthrough 语句](#fallthrough_statements)。 -> 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** **:** -> *where-clause* → **where** [*guard-expression*](../chapter3/10_Statements.html#guard) -> *where-expression* → [*表达式*](../chapter3/04_Expressions.html#expression) +> switch 语句语法 + + +> *switch 语句* → **switch** [*表达式*](04_Expressions.md#expression) **{** [*switch-case列表*](#switch-cases)可选 **}** + +> *switch case 列表* → [*switch-case*](#switch-case) [*switch-case列表*](#switch-cases)可选 + +> *switch case* → [*case标签*](#case-label) [*多条语句*](#statements) | [*default标签*](#default-label) [*多条语句*](#statements) + + +> *case 标签* → **case** [*case项列表*](#case-item-list) **:** + +> *case 项列表* → [*模式*](07_Patterns.md#pattern) [*where子句*](#where-clause)可选 | [*模式*](07_Patterns.md#pattern) [*where子句*](#where-clause)可选 **,** [*case项列表*](#case-item-list) + +> *default 标签* → **default** **:** + + +> *where-clause* → **where** [*where表达式*](#where-expression) + +> *where-expression* → [*表达式*](04_Expressions.md#expression) - ## 带标签的语句 -你可以在循环语句或`switch`语句前面加上*标签*,它由标签名和紧随其后的冒号(:)组成。在`break`和`continue`后面跟上标签名可以显式地在循环语句或`switch`语句中更改控制流,把控制权传递给指定标签标记的语句。关于这两条语句用法,详情参见 [Break 语句](#break_statement)和 [Continue 语句](#continue_statement)。 +你可以在循环语句或 `switch` 语句前面加上标签,它由标签名和紧随其后的冒号(`:`)组成。在 `break` 和 `continue` 后面跟上标签名可以显式地在循环语句或 `switch` 语句中改变相应的控制流。关于这两条语句用法,请参阅 [Break 语句](#break_statement) 和 [Continue 语句](#continue_statement)。 -标签的作用域是该标签所标记的语句之后的所有语句。你可以不使用带标签的语句,但只要使用它,标签名就必唯一。 +标签的作用域在该标签所标记的语句内。可以嵌套使用带标签的语句,但标签名必须唯一。 -关于使用带标签的语句的例子,详情参见[控制流](../chapter2/05_Control_Flow.html)一章的[带标签的语句](../chapter2/05_Control_Flow.html#labeled_statements)。 +关于使用带标签的语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [带标签的语句](../chapter2/05_Control_Flow.md#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) +> 带标签的语句语法 + +> *带标签的语句* → [*语句标签*](#statement-label) [*循环语句*](#loop-statement) | [*语句标签*](#statement-label) [*if语句*](#if-statement) | [*语句标签*](#statement-label) [*switch语句*](#switch-statement) + +> *语句标签* → [*标签名称*](#label-name) **:** + +> *标签名称* → [*标识符*](02_Lexical_Structure.md#identifier) -## 控制传递语句 +## 控制转移语句 -通过无条件地把控制权从一片代码传递到另一片代码,控制传递语句能够改变代码执行的顺序。Swift 提供四种类型的控制传递语句:`break`语句、`continue`语句、`fallthrough`语句和`return`语句。 +控制转移语句能够无条件地把控制权从一片代码转移到另一片代码,从而改变代码执行的顺序。Swift 提供五种类型的控制转移语句:`break` 语句、`continue` 语句、`fallthrough` 语句、`return` 语句和 `throw` 语句。 -> 控制传递语句(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) -> *控制传递语句* → [*throw语句*](../chapter3/10_Statements.html#throw_statement) +> 控制转移语句语法 + +> *控制转移语句* → [*break 语句*](#break-statement) +> *控制转移语句* → [*continue 语句*](#continue-statement) +> *控制转移语句* → [*fallthrough 语句*](#fallthrough-statement) +> *控制转移语句* → [*return 语句*](#return-statement) +> *控制转移语句* → [*throw 语句*](#throw-statement) - + ### Break 语句 -`break`语句用于终止循环或`switch`语句的执行。使用`break`语句时,可以只写`break`这个关键词,也可以在`break`后面跟上标签名(label name),像下面这样: +`break` 语句用于终止循环语句、`if` 语句或 `switch` 语句的执行。使用 `break` 语句时,可以只写 `break` 这个关键词,也可以在 `break` 后面跟上标签名,像下面这样: > break -> break `label name` +> break `标签名` -当`break`语句后面带标签名时,可用于终止由这个标签标记的循环或`switch`语句的执行。 +当 `break` 语句后面带标签名时,可用于终止由这个标签标记的循环语句、`if` 语句或 `switch` 语句的执行。 -而当只写`break`时,则会终止`switch`语句或上下文中包含`break`语句的最内层循环的执行。 +而只写 `break` 时,则会终止 `switch` 语句或 `break` 语句所属的最内层循环语句的执行。不能使用 `break` 语句来终止未使用标签的 `if` 语句。 -在这两种情况下,控制权都会被传递给循环或`switch`语句外面的第一行语句。 +无论哪种情况,控制权都会被转移给被终止的控制流语句后面的第一行语句。 -关于使用`break`语句的例子,详情参见[控制流](../chapter2/05_Control_Flow.html)一章的 [Break](../chapter2/05_Control_Flow.html#break) 和[带标签的语句](../chapter2/05_Control_Flow.html#labeled_statements)。 +关于使用 `break` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Break](../chapter2/05_Control_Flow.md#break) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。 -> Break 语句语法 -> *break语句* → **break** [*标签名称*](../chapter3/10_Statements.html#label_name) _可选_ +> break 语句语法 + +> *break 语句* → **break** [*标签名称*](#label-name)可选 - + ### Continue 语句 -`continue`语句用于终止循环中当前迭代的执行,但不会终止该循环的执行。使用`continue`语句时,可以只写`continue`这个关键词,也可以在`continue`后面跟上标签名(label name),像下面这样: +`continue` 语句用于终止循环中当前迭代的执行,但不会终止该循环的执行。使用 `continue` 语句时,可以只写 `continue` 这个关键词,也可以在 `continue` 后面跟上标签名,像下面这样: > continue -> continue `label name` +> continue `标签名` -当`continue`语句后面带标签名时,可用于终止由这个标签标记的循环中当前迭代的执行。 +当 `continue` 语句后面带标签名时,可用于终止由这个标签标记的循环中当前迭代的执行。 -而当只写`break`时,可用于终止上下文中包含`continue`语句的最内层循环中当前迭代的执行。 +而当只写 `continue` 时,可用于终止 `continue` 语句所属的最内层循环中当前迭代的执行。 -在这两种情况下,控制权都会被传递给循环外面的第一行语句。 +在这两种情况下,控制权都会被转移给循环语句的条件语句。 -在`for`语句中,`continue`语句执行后,*increment* 表达式还是会被计算,这是因为每次循环体执行完毕后 *increment* 表达式都会被计算。 +在 `for` 语句中,`continue` 语句执行后,增量表达式还是会被计算,这是因为每次循环体执行完毕后,增量表达式都会被计算。 -关于使用`continue`语句的例子,详情参见[控制流](../chapter2/05_Control_Flow.html)一章的 [Continue](../chapter2/05_Control_Flow.html#continue) 和[带标签的语句](../chapter2/05_Control_Flow.html#labeled_statements)。 +关于使用 `continue` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Continue](../chapter2/05_Control_Flow.md#continue) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。 -> Continue 语句语法 -> *continue语句* → **continue** [*标签名称*](../chapter3/10_Statements.html#label_name) _可选_ +> continue 语句语法 + +> *continue 语句* → **continue** [*标签名称*](#label-name)可选 ### Fallthrough 语句 -`fallthrough`语句用于在`switch`语句中传递控制权。`fallthrough`语句会把控制权从`switch`语句中的一个 case 传递给下一个 case 。这种传递是无条件的,即使下一个 case 的模式与`switch`语句的控制表达式的值不匹配。 +`fallthrough` 语句用于在 `switch` 语句中转移控制权。`fallthrough` 语句会把控制权从 `switch` 语句中的一个 `case` 转移到下一个 `case`。这种控制权转移是无条件的,即使下一个 `case` 的模式与 `switch` 语句的控制表达式的值不匹配。 -`fallthrough`语句可出现在`switch`语句中的任意 case 里,但不能出现在最后一个 case 分支中。同时,`fallthrough`语句也不能把控制权传递给使用了可选绑定的 case 分支。 +`fallthrough` 语句可出现在 `switch` 语句中的任意 `case` 中,但不能出现在最后一个 `case` 中。同时,`fallthrough` 语句也不能把控制权转移到使用了值绑定的 `case`。 -关于在`switch`语句中使用`fallthrough`语句的例子,详情参见[控制流](../chapter2/05_Control_Flow.html)一章的[控制传递语句](../chapter2/05_Control_Flow.html#control_transfer_statements)。 +关于在 `switch` 语句中使用 `fallthrough` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [控制转移语句](../chapter2/05_Control_Flow.md#control_transfer_statements)。 -> Fallthrough 语句语法 -> *fallthrough语句* → **fallthrough** +> fallthrough 语句语法 + +> *fallthrough 语句* → **fallthrough** ### Return 语句 -`return`语句用于在函数或方法的实现中将控制权传递给调用者,接着程序将会从调用者的位置继续向下执行。 +`return` 语句用于在函数或方法的实现中将控制权转移到调用函数或方法,接着程序将会从调用位置继续向下执行。 -使用`return`语句时,可以只写`return`这个关键词,也可以在`return`后面跟上表达式,像下面这样: +使用 `return` 语句时,可以只写 `return` 这个关键词,也可以在 `return` 后面跟上表达式,像下面这样: > return -> return `expression` +> return `表达式` -当`return`语句后面带表达式时,表达式的值将会返回给调用者。如果表达式值的类型与调用者期望的类型不匹配,Swift 则会在返回表达式的值之前将表达式值的类型转换为调用者期望的类型。 +当 `return` 语句后面带表达式时,表达式的值将会返回给调用函数或方法。如果表达式的值的类型与函数或者方法声明的返回类型不匹配,Swift 则会在返回表达式的值之前将表达式的值的类型转换为返回类型。 -而当只写`return`时,仅仅是将控制权从该函数或方法传递给调用者,而不返回一个值。(这就是说,该函数或方法的返回类型为`Void`或`()`) +> 注意 +> 正如 [可失败构造器](05_Declarations.md#failable_initializers) 中所描述的,`return nil` 在可失败构造器中用于表明构造失败。 -> Return 语句语法 -> *return语句* → **return** [*表达式*](../chapter3/04_Expressions.html#expression) _可选_ +而只写 `return` 时,仅仅是从该函数或方法中返回,而不返回任何值(也就是说,函数或方法的返回类型为 `Void` 或者说 `()`)。 - -### Availability 语句 - -可用性条件,被当做`if` ,`while` 语句的条件,并且 `guard` 语句在运行时会基于特定的语法格式查询接口的可用性。 - -avaliability 语句的形式如下: -> if #available(`platform name version`,` ...`, *) { -> `statements to execute if the APIs are available` -> } else { -> `fallback statements to execute if the APIs are unavailable` -> } - -可用性条件执行一个代码块时,取决于在运行时想要使用的接口是否可用。 -当编译器检查到代码块中的接口是可用的,则从可用性条件中获取相应信息。 - -可用性条件使用逗号分隔平台名称和版本列表。使用`iOS`,`OSX`,以及`watchOS`为平台名称,包括相应的版本号。*参数是必需的。在任何平台上代码块主体都被可用性条件保护起来,由满足最低部署条件的目标设备运行。 - -与布尔类型条件不同,不能用逻辑运算符 **&&** 和 **||** 合并可用性条件。 - -> 可用性条件语法 -> *可用性条件* → **#available** ( [availability-arguments­](TODO) ) -> *可用性条件* → [availability-argument­](TODO) | [availability-argument](TODO)­ ,­ [availability-arguments­](TODO) -> *可用性条件* → [平台名称](TODO) [版本号](TODO) -> *可用性条件* → **\*** -> *平台名称* → **iOS** | **iOSApplicationExtension** -> *平台名称* → **OSX** | **OSXApplicationExtension­** -> *平台名称* → **watchOS** -> *版本号* → [十进制数字](TODO) -> *版本号* → [十进制数字](TODO) **.** [十进制数字](TODO) -> *版本号* → [十进制数字](TODO) **.** [十进制数字](TODO) **.** [十进制数字](TODO) +> return 语句语法 + +> *return 语句* → **return** [*表达式*](04_Expressions.html#expression)可选 - ### Throw 语句 -`throw`语句出现在抛出函数或者抛出方法体内,或者类型被`throws`关键字标记的表达式体内。 + +`throw` 语句出现在抛出函数或者抛出方法体内,或者类型被 `throws` 关键字标记的闭包表达式体内。 -`throw`语句使程序结束执行当前的作用域,并在封闭作用域中传播错误。抛出的错误会一直传播,直到被`do`语句的`catch`子句处理掉。 +`throw` 语句使程序在当前作用域结束执行,并向外围作用域传播错误。抛出的错误会一直传递,直到被 `do` 语句的 `catch` 子句处理掉。 -`throw`语句由`throw`关键字 跟一个表达式组成 ,如下所示。 +`throw` 语句由 `throw` 关键字紧跟一个表达式组成,如下所示: -> throw `expression` +> throw `表达式` -表达式值的类型必须遵循 `LogicValue`协议 +表达式的结果必须符合 `ErrorType` 协议。 -关于如何使用`throw`语句的例子,详情参见[错误处理](TODO)一章的[抛出错误](TODO)。 +关于如何使用 `throw` 语句的例子,请参阅 [错误处理](../chapter2/18_Error_Handling.md) 一章的 [用 throwing 函数传递错误](../chapter2/18_Error_Handling.md#propagating_errors_using_throwing_functions)。 > throw 语句语法 -> *抛出语句* → **throw** *[表达式­](TODO)* + +> *throw 语句* → **throw** [*表达式*](04_Expressions.md#expression) -### Defer 语句 +## Defer 语句 - `defer` 语句用于转移程序控制出延迟语句作用域之前执行代码。 - -在 `defer` 语句中的语句无论程序控制如何转移都会执行。这意味着 `defer` 语句可以被使用在以下这些情况,像手动得执行资源管理,关闭文件描述,或者即使抛出了错误也需要去实现执行一些动作。 +`defer` 语句用于在退出当前作用域之前执行代码。 -如果多个 `defer` 语句出现在同一范围内,那么它们执行的顺序与出现的顺序相反。给定作用域中的第一个`defer` 语句,会在最后执行,这意味着最后执行的延迟语句中的语句涉及的资源可以被其他 `defer`语句清理掉。 +`defer` 语句形式如下: -> 1 func f( ) { -> 2 defer { print("First") } -> 3 defer { print("Second") } -> 4 defer { print("Third") } -> 5 } -> 6 f() -> 7 // prints "Third" -> 8 // prints "Second" -> 9 // prints "First" +```swift +defer { + 语句 +} +``` +在 `defer` 语句中的语句无论程序控制如何转移都会被执行。在某些情况下,例如,手动管理资源时,比如关闭文件描述符,或者即使抛出了错误也需要执行一些操作时,就可以使用 `defer` 语句。 -`defer` 语句中的语句无法转移程序控制出延迟语句。 +如果多个 `defer` 语句出现在同一作用域内,那么它们执行的顺序与出现的顺序相反。给定作用域中的第一个 `defer` 语句,会在最后执行,这意味着代码中最靠后的 `defer` 语句中引用的资源可以被其他 `defer` 语句清理掉。 + +```swift +func f() { + defer { print("First") } + defer { print("Second") } + defer { print("Third") } +} +f() +// 打印 “Third” +// 打印 “Second” +// 打印 “First” +``` + +`defer` 语句中的语句无法将控制权转移到 `defer` 语句外部。 > defer 语句语法 -> *延迟语句* → **defer** *[代码块](TODO)* - + +> *延迟语句* → **defer** [*代码块*](05_Declarations.md#code-block) -### Do 语句 +## Do 语句 -`do` 语句用于引入一个新的作用域,该作用域中可以含有一个或多个`catch`子句,catch子句中定义了一些匹配错误情况的模式。`do` 语句作用域内定义的常量和变量,只能在do语句作用域内访问。 +`do` 语句用于引入一个新的作用域,该作用域中可以含有一个或多个 `catch` 子句,`catch` 子句中定义了一些匹配错误条件的模式。`do` 语句作用域内定义的常量和变量只能在 `do` 语句作用域内使用。 -swift 中的 do 语句与C 中限定代码块界限的大括号 ({})很相似,并且在程序运行的时候并不会造成系统开销。 +Swift 中的 `do` 语句与 C 中限定代码块界限的大括号(`{}`)很相似,也并不会降低程序运行时的性能。 -> do { -> try `expression` -> `statements` -> } catch `pattern 1` { - `statements` -> } catch `pattern 2` where condition { - `statements` -> } +`do` 语句的形式如下: -如同`switch`语句,编译器会判断`catch`子句是否被遗漏。如果catch没有被遗漏,则认为错误被处理。否则,错误会自动传播出包含作用域,被一个封闭的`catch`语句或抛出函数处理掉,包含函数必须以`throws`关键字声明。 +```swift +do { + try 表达式 + 语句 +} catch 模式1 { + 语句 +} catch 模式2 where 条件 { + 语句 +} +``` -为了确保错误已经被处理,使用一个匹配所有错误的`catch`子句,如通配符模式(_)。如果一个`catch`子句不指定一种模式,`catch`子句会匹配和约束任何局部变量命名的`error`。有关在`catch`子句中使用模式的更多信息,详见[模式](TODO)。 +如同 `switch` 语句,编译器会判断 `catch` 子句是否有遗漏。如果 `catch` 子句没有遗漏,则认为错误已被处理。否则,错误会自动传递到外围作用域,被某个 `catch` 子句处理掉或者被用 `throws` 关键字声明的抛出函数继续向外抛出。 -关于在一些`catch`子句中如何使用` do`语句的例子,详情参见[错误处理](TODO)一章的[抛出错误](TODO)。 +为了确保错误已经被处理,可以让 `catch` 子句使用匹配所有错误的模式,如通配符模式(`_`)。如果一个 `catch` 子句不指定一种具体模式,`catch` 子句会匹配任何错误,并绑定到名为 `error` 的局部常量。有关在 `catch` 子句中使用模式的更多信息,请参阅 [模式](07_Patterns.md)。 -> do 语句语法 → **do** *[*代码块*](../chapter3/05_Declarations.html#code_block) [catch](TODO)* -> catch → *[catch子句](TODO) [catch子句](TODO)* -> catch → **catch** *[*模式*](../chapter3/07_Patterns.html#pattern)** *可选的* [*where*]() *可选的* [*代码块*](../chapter3/05_Declarations.html#code_block) +关于如何在 `do` 语句中使用一系列 `catch` 子句的例子,请参阅 [错误处理](../chapter2/18_Error_Handling.md#handling_errors)。 + +> do 语句语法 + +> *do 语句* → **do** [*代码块*](05_Declarations.md#code-block) [*多条 catch子句*](#catch-clauses)可选 + +> *多条 catch 子句* → [*catch子句*](#catch-clause) [*多条 catch子句*](#catch-clauses)可选 + +> *catch 子句* → **catch** [*模式*](07_Patterns.md#pattern)可选 [*where子句*](#where-clause)可选 [*代码块*](05_Declarations.md#code-block) -### 编译控制语句 +## 编译器控制语句 -编译控制语句允许程序改变编译器的行为。Swift 有两种编译控制语句:构建配置语句和源代码控制语句。 +编译器控制语句允许程序改变编译器的行为。Swift 有两种编译器控制语句:编译配置语句和线路控制语句。 -> 编译控制语句语法 -> *编译控制语句* → [*构建配置语句*](../chapter3/04_Expressions.html#build_config_statements) -> *编译控制语句* → [*源代码控制语句*](../chapter3/04_Expressions.html#line_control_statements) +> 编译器控制语句语法 + +> *编译器控制语句* → [*编译配置语句*](#build-config-statement) +> *编译器控制语句* → [*线路控制语句*](#line-control-statement) -#### 构建配置语句 +### 编译配置语句 -构建配置语句可以根据一个或多个配置项来有条件的编译代码。 +编译配置语句可以根据一个或多个配置来有条件地编译代码。 -每一个构建配置语句都以 `#if` 开始, `#endif` 结束。如下是一个简单的构建配置语句: +每一个编译配置语句都以 `#if` 开始,`#endif` 结束。如下是一个简单的编译配置语句: -``` -#if build configuration -statements +```swift +#if 编译配置项 + 语句 #endif ``` -和 `if` 语句的条件不同,构建配置的条件是在编译时进行判断的。它的结果是:只有构建配置在编译时判断为 `true` 的情况下语句才会被编译和执行。 +和 `if` 语句的条件不同,编译配置的条件是在编译时进行判断的。只有编译配置在编译时判断为 `true` 的情况下,相应的语句才会被编译和执行。 -*构建配置* 可以是 `true` 和 `false` 的常量,也可以是使用 `-D` 命令行标志的标识符,或者是下列表格中的任意一个平台测试方法。 +编译配置可以是 `true` 和 `false` 的字面量,也可以是使用 `-D` 命令行标志的标识符,或者是下列表格中的任意一个平台检测函数。 - | 方法 | 可用参数 | - | --- | - | - | os() | OSX, iOS, watchOS, tvOS | - | arch() | i386, x86_64, arm, arm64 | - +| 函数 | 可用参数 | +| --- | --- | +| `os()` | `OSX`, `iOS`, `watchOS`, `tvOS`, `Linux` | +| `arch()` | `i386`, `x86_64`, `arm`, `arm64` | +| `swift()` | `>=` 后跟版本号 | -> 注意 -> `arch(arm)` 构建配置在 ARM 64位设备上不会返回 `true`。如果代码的构建目标是 32 位的 iOS 模拟器,`arch(i386)` 构建配置返回 `true`。 +`swift()`(语言版本检测函数)的版本号参数主要由主版本号和次版本号组成并且使用点号(`.`)分隔开,`>=` 和版本号之间不能有空格。 -你可以使用逻辑操作符 `&&`、`||` 和 `!` 来连接构建配置,还可以使用圆括号来进行分组。 +> 注意 +> `arch(arm)` 平台检测函数在 ARM 64 位设备上不会返回 `true`。如果代码在 32 位的 iOS 模拟器上编译,`arch(i386)` 平台检测函数会返回 `true`。 -就像 `if` 语句一样,你可以使用 `#elseif` 分句来添加任意多个条件分支来测试不同的构建配置。你也可以使用 `#else` 分句来添加最终的条件分支。包含多个分支的构建配置语句例子如下: +你可以使用逻辑操作符 `&&`、`||` 和 `!` 来组合多个编译配置,还可以使用圆括号来进行分组。 -``` -#if build configuration 1 -statements to compile if build configuration 1 is true -#elseif build configuration 2 -statements to compile if build configuration 2 is true +就像 `if` 语句一样,你可以使用 `#elseif` 子句来添加任意多个条件分支来测试不同的编译配置。你也可以使用 `#else` 子句来添加最终的条件分支。包含多个分支的编译配置语句例子如下: + +```swift +#if 编译配置1 + 如果编译配置1成立则执行这部分代码 +#elseif 编译配置2 + 如果编译配置2成立则执行这部分代码 #else -statements to compile if both build configurations are false + 如果编译配置均不成立则执行这部分代码 #endif ``` -> 注意 -> 即使没有被编译,构建配置语句中的每一个分句仍然会被解析。 +> 注意 +> 即使没有被编译,编译配置中的语句仍然会被解析。然而,唯一的例外是编译配置语句中包含语言版本检测函数:仅当 `Swift` 编译器版本和语言版本检测函数中指定的版本号匹配时,语句才会被解析。这种设定能确保旧的编译器不会尝试去解析新 Swift 版本的语法。 ---- -> 构建配置语句语法 -> 单个构建配置语句 → #if­ 多个构建配置语句(可选) 多个构建配置 `elseif` 分句(可选)­ 单个构建配置 `else` 分句(可选)­#endif­ -> 多个构建配置 `elseif` 分句 → 单个构建配置 `elseif`­ 分句 多个构建配置 `elseif` 分句(可选) -> 单个构建配置 `elseif`­ 分句 → #elseif­ 多个构建配置语句(可选) -> 单个构建配置 `else` 分句 → #else­ 语句(可选) -> 构建配置 → 平台测试方法 -> 构建配置 → 标识符 -> 构建配置 → boolean 常量 -> 构建配置 → (­构建配置­)­ -> 构建配置 → !­ 构建配置­ -> 构建配置 → 构建配置 &&­ 构建配置­ -> 构建配置 → 构建配置 ­||­ 构建配置­ -> 平台测试方法 → os­(­操作系统)­ -> 平台测试方法 → arch­(­架构)­ -> 操作系统 → OSX­ iOS­ watchOS­ tvOS­ -> 架构 → i386­ x86_64­ arm­ arm64­ + +> 编译配置语句语法 + + +> *单个编译配置语句* → **#if** [*编译配置*](#build-configuration) [*语句*](#statements)可选 [*多个编译配置elseif子句*](#build-configuration-elseif-clauses)可选 **-** [*单个编译配置else子句*](#build-configuration-else-clause)可选 **#endif** + +> *多个编译配置 elseif 子句* → [*单个编译配置elseif子句*](#build-configuration-elseif-clause) [*多个编译配置elseif子句*](build-configuration-elseif-clauses)可选 + +> *单个编译配置 elseif 子句* → **#elseif** [*编译配置*](#build-configuration) [*语句*](#statements)可选 + +> *单个编译配置 else 子句* → **#else** [*语句*](#statements)可选 + + +> *编译配置* → [*平台检测函数*](#platform-testing-function) +> *编译配置* → [*语言版本检测函数*](#language-version-testing-function) +> *编译配置* → [*标识符*](02_Lexical_Structure.md#identifier) +> *编译配置* → [*布尔值字面量*](02_Lexical_Structure.md#boolean-literal) +> *编译配置* → **(** [*编译配置*](#build-configuration) **)** +> *编译配置* → **!** [*编译配置*](#build-configuration) +> *编译配置* → [*编译配置*](#build-configuration) **&&** [*编译配置*](#build-configuration) +> *编译配置* → [*编译配置*](#build-configuration) **||** [*编译配置*](#build-configuration) + + +> *平台检测函数* → **os** **(** [*操作系统*](#operating-system) **)** +> *平台检测函数* → **arch** **(** [*架构*](#architecture) **)** + +> *语言版本检测函数* → **swift** **(** **>=** [*swift版本*](#swift-version) **)** + +> *操作系统* → **OSX** | **iOS** | **watchOS** | **tvOS** + +> *架构* → **i386** | **x86_64** | **arm** | **arm64** + +> *swift 版本* → [*十进制数字*](02_Lexical_Structure.md#decimal-digit) ­**.** ­[*十进制数字*](02_Lexical_Structure.md#decimal-digit) -#### 源代码控制语句 +### 行控制语句 -源代码控制语句用来给被编译源代码指定一个与原始行号和文件名不同的行号和文件名。使用源代码控制语句可以改变 Swift 使用源代码的位置,以便进行分析和测试。 +行控制语句可以为被编译的源代码指定行号和文件名,从而改变源代码的定位信息,以便进行分析和调试。 -源代码的控制语句的例子如下: +行控制语句形式如下: -``` -#line line number filename +> \#sourceLocation(file: `文件名` , line:`行号`) + +> \#sourceLocation() + +第一种的行控制语句会改变该语句之后的代码中的字面量表达式 `#line` 和 `#file` 所表示的值。`行号` 是一个大于 0 的整形字面量,会改变 `#line` 表达式的值。`文件名` 是一个字符串字面量,会改变 `#file` 表达式的值。 + +第二种的行控制语句, `#sourceLocation()`,会将源代码的定位信息重置回默认的行号和文件名。 + + +> 行控制语句语法 + +> *行控制语句* → **#sourceLocation(file:[*文件名*](#file-name),line:[*行号*](#line-number))** +> *行控制语句* → **#sourceLocation()** + +> *行号* → 大于 0 的十进制整数 + +> *文件名* → [*静态字符串字面量*](02_Lexical_Structure.md#static-string-literal) + + +### 可用性条件 + +可用性条件可作为 `if`,`while`,`guard` 语句的条件,可以在运行时基于特定的平台参数来查询 API 的可用性。 + +可用性条件的形式如下: + +```swift +if #available(平台名称 版本, ..., *) { + 如果 API 可用,则执行这部分语句 +} else { + 如果 API 不可用,则执行这部分语句 +} ``` -源代码控制语句改变了常量表达式 `__LINE__` 和 `__FILE__` 的值,以一行源代码开头,然后跟着源代码控制语句。`line number` 改变了 `__LINE__` 的值,它是一个大于 0 的常量。`filename` 改变了 `__FILE__` 的值,它是一个字符串常量。 +使用可用性条件来执行一个代码块时,取决于使用的 API 在运行时是否可用,编译器会根据可用性条件提供的信息来决定是否执行相应的代码块。 -你可以通过写一句不指定 `line number` 和 `filename` 的源代码控制语句来吧源代码的位置回退到初始的行号和文件。 +可用性条件使用一系列逗号分隔的平台名称和版本。使用 `iOS`,`OSX`,以及 `watchOS` 等作为平台名称,并写上相应的版本号。`*` 参数是必须写的,用于处理未来的潜在平台。可用性条件确保了运行时的平台不低于条件中指定的平台版本时才执行代码块。 + +与布尔类型的条件不同,不能用逻辑运算符 `&&` 和 `||` 组合可用性条件。 -源代码控制语句必须出现在源代码的那一行,而且不能是源代码文件的最后一行。 +> 可用性条件语法 -> 源代码控制语句 + +> *可用性条件* → **#available** **(** [*可用性参数列表*](#availability-arguments) **)** + +> *可用性参数列表* → [*可用性参数*](#availability-argument) | [*可用性参数*](#availability-argument) **,** [*可用性参数列表*](#availability-arguments) + +> *可用性参数* → [平台名称](#platform-name) [平台版本](#platform-version) +> *可用性条件* → __*__ -> 源代码控制语句 → #line­ -> 源代码控制语句 → #line­ line-number­ file-name­ -> line-number → 大于 0 的十进制数 -> file-name → 字符串常量 + +> *平台名称* → **iOS** | **iOSApplicationExtension** +> *平台名称* → **OSX** | **OSXApplicationExtension** +> *平台名称* → **watchOS** + +> *平台版本* → [十进制数字](02_Lexical_Structure.md#decimal-digits) +> *平台版本* → [十进制数字](02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](02_Lexical_Structure.md#decimal-digits) +> *平台版本* → [十进制数字](02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](02_Lexical_Structure.md#decimal-digits) - - - - - - - - - - - - - - - -

发布日期语法变更记录
2014-06-3
    -
  • - 苹果全球开发者大会WWDC2014召开,发布了苹果最新的开发语言Swift,并释放出XCode6 Beta1版本 -
🐶
U+1F436
UTF-16
Code Unit
Unicode Scalar
Code Unit
68 111 103