Merge remote-tracking branch 'upstream/gh-pages' into gh-pages

# Conflicts:
#	source/chapter2/09_Structures_And_Classes.md
This commit is contained in:
Nemocdz
2019-06-23 10:59:04 +08:00
51 changed files with 6350 additions and 5689 deletions

5
.gitbook.yaml Normal file
View File

@ -0,0 +1,5 @@
root: ./source/
structure:
readme: ../README.md
summary: SUMMARY.md

View File

@ -3,19 +3,19 @@
中文版 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)
[英文原版在线版](https://docs.swift.org/swift-book/)
[英文原版ePub版](https://swift.org/documentation/)
[英文原版ePub版](https://docs.swift.org/swift-book/TheSwiftProgrammingLanguageSwift5.epub)
# 在线阅读
使用 GitBook 制作,可以直接[在线阅读](http://swiftguide.cn/)。
使用 GitBook 制作,可以直接 [在线阅读](https://swiftgg.gitbook.io/swift/)。
# 当前阶段
Swift 4.2 翻译中,请到 [Issues](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/issues) 中认领章节(把 Issue 的 Assignee 设置成你自己就表示认领,不要认领已经有人认领的章节)。
- 更新到 Swift 4.12018-04-12感谢[@Mylittleswift](https://github.com/Mylittleswift)
- 更新到 Swift 5.02019-04-05
- 更新到 Swift 4.22019-01-29
- 更新到 Swift 4.12018-04-12感谢 [@Mylittleswift](https://github.com/Mylittleswift)
- 更新到 Swift 3.02016-09-23
# 贡献力量
@ -36,6 +36,14 @@ Swift 4.2 翻译中,请到 [Issues](https://github.com/SwiftGGTeam/the-swift-p
- 翻译排版格式要求参考 SwiftGG [排版指南](https://github.com/SwiftGGTeam/translation/blob/master/SwiftGG%20排版指南.md)
- Pull Request 发起方式参考 SwiftGG [Pull Request 说明](https://github.com/SwiftGGTeam/translation/blob/master/%E7%BF%BB%E8%AF%91%E6%B5%81%E7%A8%8B%E6%A6%82%E8%BF%B0%E5%8F%8APR%E8%AF%B4%E6%98%8E.md#%E5%A6%82%E4%BD%95%E5%8F%91%E8%B5%B7-pull-request)
原版文档差异比较:
在翻译时可以通过 Calibre 软件对 [document 目录下](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/tree/gh-pages/document) 不同版本的文档进行 diff检查待更新部分。
diff 操作如下:
将最新文档加入到 Calibre 中,点击 **Edit Book**,然后在编辑界面选择 **File** -> **Compare to other book** 检查各模块的更新内容,详见 [链接](https://manual.calibre-ebook.com/diff.html)。
其他说明:
- 相关术语请严格按照术语表来翻译,如果有问题可以发 Issue 大家一起讨论
@ -118,13 +126,13 @@ Swift 4.2 翻译中,请到 [Issues](https://github.com/SwiftGGTeam/the-swift-p
| literal value | 字面量 |
| alias | 别名 |
| Assertion | 断言 |
| conditional compilation | 条件编译 |
# 贡献历史
# 贡献
[贡献者列表](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/blob/gh-pages/source/contributors.md),感谢大家!
## 简体中文版贡献历史
1. Welcome To Swift 翻译贡献榜[详情](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/tree/gh-pages/source/chapter1/05_contributors.md)
2. Language Guide 翻译贡献榜[详情](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/blob/gh-pages/source/chapter2/27_contributors.md)
# 协议
和 [苹果官方文档](https://swift.org/documentation/) 协议一致:[Creative Commons Attribution 4.0 International (CC BY 4.0) License](https://creativecommons.org/licenses/by/4.0/)。

Binary file not shown.

Binary file not shown.

View File

@ -193,14 +193,9 @@ struct AlternativeRect {
只有 getter 沒有 setter 的計算屬性就是*只讀計算屬性*。只讀計算屬性總是返回一個值,可以通過點運算符訪問,但不能設置新的值。
<<<<<<< HEAD
> 注意:
> 必須使用`var`關鍵字定義計算屬性,包括只讀計算屬性,因為他們的值不是固定的。`let`關鍵字只用來聲明常量屬性,表示初始化後再也無法修改的值。
=======
> 注意:
>
> 必須使用`var`關鍵字定義計算屬性,包括只讀計算屬性,因為它們的值不是固定的。`let`關鍵字只用來聲明常量屬性,表示初始化後再也無法修改的值。
>>>>>>> a516af6a531a104ec88da0d236ecf389a5ec72af
只讀計算屬性的聲明可以去掉`get`關鍵字和花括號:
@ -237,14 +232,9 @@ println("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
類似地,`didSet`觀察器會將舊的屬性值作為參數傳入,可以為該參數命名或者使用默認參數名`oldValue`
<<<<<<< HEAD
> 注意:
> `willSet`和`didSet`觀察器在屬性初始化過程中不會被調用,他們只會當屬性的值在初始化之外的地方被設置時被調用。
=======
> 注意:
>
> `willSet`和`didSet`觀察器在屬性初始化過程中不會被調用,它們只會當屬性的值在初始化之外的地方被設置時被調用。
>>>>>>> a516af6a531a104ec88da0d236ecf389a5ec72af
這裡是一個`willSet``didSet`的實際例子,其中定義了一個名為`StepCounter`的類,用來統計當人步行時的總步數,可以跟計步器或其他日常鍛煉的統計裝置的輸入數據配合使用。

View File

@ -1,12 +1,11 @@
# Summary
* [欢迎使用 Swift](chapter1/chapter1.md)
* 欢迎使用 Swift
* [关于 Swift](chapter1/01_about_swift.md)
* [版本兼容性](chapter1/02_version_compatibility.md)
* [Swift 初见](chapter1/03_a_swift_tour.md)
* [Swift 版本历史记录](chapter1/04_revision_history.md)
* [Swift 1.0 发布内容](v1.0.md)
* [Swift 教程](chapter2/chapter2.md)
* Swift 教程
* [基础部分](chapter2/01_The_Basics.md)
* [基本运算符](chapter2/02_Basic_Operators.md)
* [字符串和字符](chapter2/03_Strings_and_Characters.md)
@ -15,7 +14,7 @@
* [函数](chapter2/06_Functions.md)
* [闭包](chapter2/07_Closures.md)
* [枚举](chapter2/08_Enumerations.md)
* [类和结构体](chapter2/09_Classes_and_Structures.md)
* [类和结构体](chapter2/09_Structures_And_Classes.md)
* [属性](chapter2/10_Properties.md)
* [方法](chapter2/11_Methods.md)
* [下标](chapter2/12_Subscripts.md)
@ -44,12 +43,5 @@
* [模式](chapter3/08_Patterns.md)
* [泛型参数](chapter3/09_Generic_Parameters_and_Arguments.md)
* [语法总结](chapter3/10_Summary_of_the_Grammar.md)
* 苹果官方 Blog 官方翻译
* [Access Control 权限控制的黑与白](chapter4/01_Access_Control.md)
* [造个类型不是梦-白话 Swift 类型创建](chapter4/02_Type_Custom.md)
* [WWDC 里面的那个“大炮打气球”](chapter4/03_Ballons.md)
* [Swift 与 C 语言指针友好合作](chapter4/04_Interacting_with_C_Pointers.md)
* [引用类型和值类型的恩怨](chapter4/05_Value_and_Reference_Types.md)
* [访问控制和 Protected](chapter4/06_Access_Control_and_Protected.md)
* [可选类型完美解决占位问题](chapter4/07_Optional_Case_Study.md)
* 翻译贡献者
* [翻译贡献者](contributors.md)

View File

@ -1,15 +1,10 @@
# 版本兼容性
本书描述的是 Swift 4.1,是 Xcode 9.2 中包含的默认版本。你可以用 Xcode 9.2 来构建 Swift 4 或 Swift 3 写的项目
本书描述的是 Xcode 10.2 中的默认 Swift 版本 Swift 5。你可以使用 Xcode10.2 来构建 Swift 5、Swift 4.2 或 Swift 4 写的项目
> 注意
>
> 当 Swift 4 编译器编译 Swift 3 版本的代码时,它识别的语言版本为 3.2 版本。因此,你可以使用像 `#if swift(>=3.2)` 条件编译块来编写多版本编译器可以并存的代码。
当您使用 Xcode 10.2 构建 Swift 4 和 Swift 4.2 代码时,除了下面的功能仅支持 Swift 5其他大多数功能都依然可用。
当你用 Xcode 9.2 编译 Swift 3 的代码Swift 4 中大部分功能是可以使用的。也就是说,下面的功能仅仅是 Swift 4 的代码中可以使用:
* **try?** 表达式不会为已返回可选类型的代码引入额外的可选类型层级。
* 大数字的整型字面量初始化代码的类型将会被正确推导,例如 **UInt64(0xffff_ffff_ffff_ffff)** 将会被推导为整型类型而非溢出。
* 字符串的子串操作返回的实例是 `Substring` 类型,不再是 `String` 类型
* 在更少的地方显式的添加 `@objc` 属性。
* 同一文件中类型的扩展可以访问这个类型的私有成员。
用 Swift 4 写的项目可以依赖用 Swift 3 写的项目反之亦然。这意味着如果你将一个大的项目分解成多个框架framework你可以每次一个框架地迁移 Swift 3 代码到 Swift 4。
用 Swift 5 写的项目可以依赖用 Swift 4.2 或 Swift 4 写的项目反之亦然。这意味着如果你将一个大的项目分解成多个框架framework你可以每次一个框架地迁移 Swift 4 代码到 Swift 5

View File

@ -1,6 +1,6 @@
# Swift 初见
通常来说,编程语言教程中的第一个程序应该在屏幕上打印 “Hello, world”。在 Swift 中,可以用一行代码实现:
通常来说编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现:
```swift
print("Hello, world!")
@ -16,8 +16,7 @@ print("Hello, world!")
>
> [Download Playground](https://docs.swift.org/swift-book/GuidedTour/GuidedTour.playground.zip)
<a name="simple_values"></a>
## 简单值
## 简单值 {#simple-values}
使用 `let` 来声明常量,使用 `var` 来声明变量。一个常量的值,在编译的时候,并不需要有明确的值,但是你只能为它赋值一次。这说明你可以用一个常量来命名一个值,一次赋值就即可在多个地方使用。
@ -102,8 +101,7 @@ shoppingList = []
occupations = [:]
```
<a name="control_flow"></a>
## 控制流
## 控制流 {#control-flow}
使用 `if``switch` 来进行条件操作,使用 `for-in``while``repeat-while` 来进行循环。包裹条件和循环变量的括号可以省略,但是语句体的大括号是必须的。
@ -223,8 +221,7 @@ print(total)
使用 `..<` 创建的范围不包含上界,如果想包含的话需要使用 `...`
<a name="functions_and_closures"></a>
## 函数和闭包
## 函数和闭包 {#functions-and-closures}
使用 `func` 来声明一个函数,使用名字和参数来调用函数。使用 `->` 来指定函数返回值的类型。
@ -345,8 +342,7 @@ let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
```
<a name="objects_and_classes"></a>
## 对象和类
## 对象和类 {#objects-and-classes}
使用 `class` 和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。
@ -495,8 +491,7 @@ let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
```
<a name="enumerations_and_structure"></a>
## 枚举和结构体
## 枚举和结构体 {#enumerations-and-structure}
使用 `enum` 来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。
@ -609,8 +604,7 @@ let threeOfSpadesDescription = threeOfSpades.simpleDescription()
>
> 给 `Card` 添加一个方法,创建一副完整的扑克牌并把每张牌的 rank 和 suit 对应起来。
<a name="protocols_and_extensions"></a>
## 协议和扩展
## 协议和扩展 {#protocols-and-extensions}
使用 `protocol` 来声明一个协议。
@ -680,8 +674,7 @@ print(protocolValue.simpleDescription)
即使 `protocolValue` 变量运行时的类型是 `simpleClass` ,编译器还是会把它的类型当做 `ExampleProtocol`。这表示你不能调用在协议之外的方法或者属性。
<a name="error_handling"></a>
## 错误处理
## 错误处理 {#error-handling}
使用采用 `Error` 协议的类型来表示错误。
@ -764,8 +757,7 @@ fridgeContains("banana")
print(fridgeIsOpen)
```
<a name="generics"></a>
## 泛型
## 泛型 {#generics}
在尖括号里写一个名字来创建一个泛型函数或者类型。

File diff suppressed because it is too large Load Diff

View File

@ -1,101 +0,0 @@
# 翻译历史记录
## About Swift
> 1.0
> 翻译:[numbbbbb](https://github.com/numbbbbb)
> 校对:[yeahdongcn](https://github.com/yeahdongcn)
> 2.0
> 翻译+校对:[xtymichael](https://github.com/xtymichael)
> 3.0
> 翻译+校对:[shanks](http://codebuild.me)2016-10-06
> 3.0.1
> review : 2016-11-09
> 3.1
> 校对: [SketchK](https://github.com/SketchK) 2017-04-08
> 4.0
> 翻译:[rain2540](https://github.com/rain2540) 2017-09-21
> 4.1
> 翻译:[mylittleswift](https://github.com/mylittleswift)
## Version Compatibility
> 4.0
> 翻译:[muhlenXi](https://github.com/muhlenxi) 2017-09-25
> 4.1
> 翻译:[mylittleswift](https://github.com/mylittleswift)
## A Swift Tour
> 1.0
> 翻译:[numbbbbb](https://github.com/numbbbbb)
> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai)
> 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
> 3.0
> 翻译+校对:[shanks](http://codebuild.me)2016-10-06
> 3.0.1
> review: 2016-11-09
> 3.1 校对: [SketchK](https://github.com/SketchK) 2017-04-08
> 4.0
> 翻译+校对:[muhlenxi](https://github.com/muhlenxi) 2017-09-26
> 4.1
> 翻译:[mylittleswift](https://github.com/mylittleswift)
## Revision History
> 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/)
>
> 3.0
> 翻译+校对:[shanks](http://codebuild.me)2016-10-06
>
> 3.0.1
> 翻译+校对:[shanks](http://codebuild.me)2016-11-10
>
> 3.1
> 翻译+校对:[bq](https://github.com/bqlin)2018-02-22
>
> 4.0
> 翻译+校对:[bq](https://github.com/bqlin)2018-02-22
>
> 4.1
> 翻译+校对:[bq](https://github.com/bqlin)2018-02-22

View File

@ -1,9 +1,8 @@
# 基础部分
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.md)。
就像 C 语言一样Swift 使用变量来进行存储并通过变量名来关联值。在 Swift 中,广泛的使用着值不可变的变量,它们就是常量,而且比 C 语言的常量更强大。在 Swift 中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰地表达你的意图。
@ -13,13 +12,11 @@ Swift 还增加了可选Optional类型用于处理值缺失的情况。
Swift 是一门*类型安全*的语言,这意味着 Swift 可以让你清楚地知道值的类型。如果你的代码需要一个 `String` ,类型安全会阻止你不小心传入一个 `Int` 。同样的,如果你的代码需要一个 `String`,类型安全会阻止你意外传入一个可选的 `String` 。类型安全可以帮助你在开发阶段尽早发现并修正错误。
<a name="constants_and_variables"></a>
## 常量和变量
## 常量和变量 {#constants-and-variables}
常量和变量把一个名字(比如 `maximumNumberOfLoginAttempts` 或者 `welcomeMessage` )和一个指定类型的值(比如数字 `10` 或者字符串 `"Hello"` )关联起来。*常量*的值一旦设定就不能改变,而*变量*的值可以随意更改。
<a name="declaring"></a>
### 声明常量和变量
### 声明常量和变量 {#declaring}
常量和变量必须在使用前声明,用 `let` 来声明常量,用 `var` 来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数:
@ -44,8 +41,7 @@ var x = 0.0, y = 0.0, z = 0.0
>
> 如果你的代码中有不需要改变的值,请使用 `let` 关键字将它声明为常量。只将需要改变的值声明为变量。
<a name="type_annotations"></a>
### 类型标注
### 类型标注 {#type-annotations}
当你声明常量或者变量的时候可以加上*类型标注type annotation*,说明常量或者变量中要存储的值的类型。如果要添加类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
@ -75,10 +71,9 @@ var red, green, blue: Double
> 注意
>
> 一般来说你很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值Swift 可以推断出这个常量或者变量的类型,请参考[类型安全和类型推断](#type_safety_and_type_inference)。在上面的例子中,没有给 `welcomeMessage` 赋初始值,所以变量 `welcomeMessage` 的类型是通过一个类型标注指定的,而不是通过初始值推断的。
> 一般来说你很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值Swift 可以推断出这个常量或者变量的类型,请参考[类型安全和类型推断](#type-safety-and-type-inference)。在上面的例子中,没有给 `welcomeMessage` 赋初始值,所以变量 `welcomeMessage` 的类型是通过一个类型标注指定的,而不是通过初始值推断的。
<a name="naming"></a>
### 常量和变量的命名
### 常量和变量的命名 {#naming}
常量和变量名可以包含任何字符,包括 Unicode 字符:
@ -112,31 +107,29 @@ languageName = "Swift++"
// 这会报编译时错误 - languageName 不可改变
```
<a name="printing"></a>
### 输出常量和变量
### 输出常量和变量 {#printing}
你可以用 `print(_:separator:terminator:)` 函数来输出当前常量或变量的值:
```swift
print(friendlyWelcome)
// 输出 "Bonjour!"
// 输出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.md#default_parameter_values)。
Swift 用*字符串插值string interpolation*的方式把常量名或者变量名当做占位符加入到长字符串中Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:
```swift
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 输出 "The current value of friendlyWelcome is Bonjour!
// 输出The current value of friendlyWelcome is Bonjour!
```
> 注意
>
> 字符串插值所有可用的选项,请参考[字符串插值](./03_Strings_and_Characters.html#string_interpolation)。
> 字符串插值所有可用的选项,请参考[字符串插值](./03_Strings_and_Characters.md#string_interpolation)。
<a name="comments"></a>
## 注释
## 注释 {#comments}
请将你的代码中的非执行文本注释成提示或者笔记以方便你将来阅读。Swift 的编译器将会在编译代码时自动忽略掉注释部分。
@ -163,25 +156,22 @@ Swift 中的注释与 C 语言的注释非常相似。单行注释以双正斜
通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。
<a name="semicolons"></a>
## 分号
## 分号 {#semicolons}
与其他大部分编程语言不同Swift 并不强制要求你在每条语句的结尾处使用分号(`;`),当然,你也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句:
```swift
let cat = "🐱"; print(cat)
// 输出 "🐱"
// 输出“🐱”
```
<a name="integers"></a>
## 整数
## 整数 {#integers}
整数就是没有小数部分的数字,比如 `42``-23` 。整数可以是 `有符号`(正、负、零)或者 `无符号`(正、零)。
Swift 提供了8、16、32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像比如8位无符号整数类型是 `UInt8`32位有符号整数类型是 `Int32` 。就像 Swift 的其他类型一样,整数类型采用大写命名法。
<a name="integer_bounds"></a>
### 整数范围
### 整数范围 {#integer-bounds}
你可以访问不同整数类型的 `min``max` 属性来获取对应类型的最小值和最大值:
@ -192,8 +182,7 @@ let maxValue = UInt8.max // maxValue 为 255是 UInt8 类型
`min``max` 所传回值的类型,正是其所对的整数类型(如上例 UInt8, 所传回的类型是 UInt8可用在表达式中相同类型值旁。
<a name="Int"></a>
### Int
### Int {#Int}
一般来说你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型 `Int`,长度与当前平台的原生字长相同:
@ -202,8 +191,7 @@ let maxValue = UInt8.max // maxValue 为 255是 UInt8 类型
除非你需要特定长度的整数,一般来说使用 `Int` 就够了。这可以提高代码一致性和可复用性。即使是在32位平台上`Int` 可以存储的整数范围也可以达到 `-2,147,483,648` ~ `2,147,483,647`,大多数时候这已经足够大了。
<a name="UInt"></a>
### UInt
### UInt {#UInt}
Swift 也提供了一个特殊的无符号类型 `UInt`,长度与当前平台的原生字长相同:
@ -214,8 +202,7 @@ Swift 也提供了一个特殊的无符号类型 `UInt`,长度与当前平台
>
> 尽量不要使用 `UInt`,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用 `Int`,即使你要存储的值已知是非负的。统一使用 `Int` 可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考[类型安全和类型推断](#type_safety_and_type_inference)。
<a name="floating-point_numbers"></a>
## 浮点数
## 浮点数 {#floating-point-numbers}
浮点数是有小数部分的数字,比如 `3.14159``0.1``-273.15`
@ -228,8 +215,7 @@ Swift 也提供了一个特殊的无符号类型 `UInt`,长度与当前平台
>
> `Double` 精确度很高至少有15位数字而 `Float` 只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围在两种类型都匹配的情况下将优先选择 `Double`。
<a name="type_safety_and_type_inference"></a>
## 类型安全和类型推断
## 类型安全和类型推断 {#type-safety-and-type-inference}
Swift 是一个*类型安全type safe*的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个 `String`,你绝对不可能不小心传进去一个 `Int`
@ -266,8 +252,7 @@ let anotherPi = 3 + 0.14159
原始值 `3` 没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为 `Double` 类型。
<a name="numeric_literals"></a>
## 数值型字面量
## 数值型字面量 {#numeric-literals}
整数字面量可以被写作:
@ -313,15 +298,13 @@ let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
```
<a name="numeric_type_conversion"></a>
## 数值型类型转换
## 数值型类型转换 {#numeric-type-conversion}
通常来讲,即使代码中的整数常量和变量已知非负,也请使用 `Int` 类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。
只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。
<a name="integer_conversion"></a>
### 整数转换
### 整数转换 {#integer-conversion}
不同整数类型的变量和常量可以存储不同范围的数字。`Int8` 类型的常量或者变量可以存储的数字范围是 `-128`~`127`,而 `UInt8` 类型的常量或者变量能存储的数字范围是 `0`~`255`。如果数字超出了常量或者变量可存储的范围,编译的时候会报错:
@ -344,10 +327,9 @@ let twoThousandAndOne = twoThousand + UInt16(one)
现在两个数字的类型都是 `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.md)。
<a name="integer_and_floating_point_conversion"></a>
### 整数和浮点数转换
### 整数和浮点数转换 {#integer-and-floating-point-conversion}
整数和浮点数的转换必须显式指定类型:
@ -373,8 +355,7 @@ let integerPi = Int(pi)
>
> 结合数字类常量和变量不同于结合数字类字面量。字面量 `3` 可以直接和字面量 `0.14159` 相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。
<a name="type_aliases"></a>
## 类型别名
## 类型别名 {#type-aliases}
*类型别名type aliases*就是给现有类型定义另一个名字。你可以使用 `typealias` 关键字来定义类型别名。
@ -393,8 +374,7 @@ var maxAmplitudeFound = AudioSample.min
本例中,`AudioSample` 被定义为 `UInt16` 的一个别名。因为它是别名,`AudioSample.min` 实际上是 `UInt16.min`,所以会给 `maxAmplitudeFound` 赋一个初值 `0`
<a name="booleans"></a>
## 布尔值
## 布尔值 {#booleans}
Swift 有一个基本的*布尔Boolean类型*,叫做 `Bool`。布尔值指*逻辑*上的值因为它们只能是真或者假。Swift 有两个布尔常量,`true``false`
@ -413,10 +393,10 @@ if turnipsAreDelicious {
} else {
print("Eww, turnips are horrible.")
}
// 输出 "Eww, turnips are horrible."
// 输出Eww, turnips are horrible.
```
条件语句,例如 `if`,请参考[控制流](./05_Control_Flow.html)。
条件语句,例如 `if`,请参考[控制流](./05_Control_Flow.md)。
如果你在需要使用 `Bool` 类型的地方使用了非布尔值Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误:
@ -436,12 +416,11 @@ if i == 1 {
}
```
`i == 1` 的比较结果是 `Bool` 类型,所以第二个例子可以通过类型检查。类似 `i == 1` 这样的比较,请参考[基本操作符](./05_Control_Flow.html)。
`i == 1` 的比较结果是 `Bool` 类型,所以第二个例子可以通过类型检查。类似 `i == 1` 这样的比较,请参考[基本操作符](./05_Control_Flow.md)。
和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。
<a name="tuples"></a>
## 元组
## 元组 {#tuples}
*元组tuples*把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
@ -461,9 +440,9 @@ let http404Error = (404, "Not Found")
```swift
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 输出 "The status code is 404"
// 输出The status code is 404
print("The status message is \(statusMessage)")
// 输出 "The status message is Not Found"
// 输出The status message is Not Found
```
如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(`_`)标记:
@ -471,16 +450,16 @@ print("The status message is \(statusMessage)")
```swift
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// 输出 "The status code is 404"
// 输出The status code is 404
```
此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:
```swift
print("The status code is \(http404Error.0)")
// 输出 "The status code is 404"
// 输出The status code is 404
print("The status message is \(http404Error.1)")
// 输出 "The status message is Not Found"
// 输出The status message is Not Found
```
你可以在定义元组的时候给单个元素命名:
@ -493,19 +472,18 @@ let http200Status = (statusCode: 200, description: "OK")
```swift
print("The status code is \(http200Status.statusCode)")
// 输出 "The status code is 200"
// 输出The status code is 200
print("The status message is \(http200Status.description)")
// 输出 "The status message is OK"
// 输出The status message is OK
```
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 `(Int, String)` 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考[函数参数与返回值](./06_Functions.html#Function_Parameters_and_Return_Values)。
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 `(Int, String)` 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考[函数参数与返回值](./06_Functions.md#Function_Parameters_and_Return_Values)。
> 注意
>
> 元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。请参考[类和结构体](./09_Classes_and_Structures.html)。
> 元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。请参考[类和结构体](./09_Classes_and_Structures.md)。
<a name="optionals"></a>
## 可选类型
## 可选类型 {#optionals}
使用*可选类型optionals*来处理值可能缺失的情况。可选类型表示两种可能:
或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。
@ -526,8 +504,7 @@ let convertedNumber = Int(possibleNumber)
因为该构造器可能会失败,所以它返回一个*可选类型*optional`Int`,而不是一个 `Int`。一个可选的 `Int` 被写作 `Int?` 而不是 `Int`。问号暗示包含的值是可选类型,也就是说可能包含 `Int` 值也可能*不包含值*。(不能包含其他任何值比如 `Bool` 值或者 `String` 值。只能是 `Int` 或者什么都没有。)
<a name="nil"></a>
### nil
### nil {#nil}
你可以给可选变量赋值为 `nil` 来表示它没有值:
@ -553,8 +530,7 @@ var surveyAnswer: String?
>
> Swift 的 `nil` 和 Objective-C 中的 `nil` 并不一样。在 Objective-C 中,`nil` 是一个指向不存在对象的指针。在 Swift 中,`nil` 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 `nil`,不只是对象类型。
<a name="if"></a>
### if 语句以及强制解析
### if 语句以及强制解析 {#if}
你可以使用 `if` 语句和 `nil` 比较来判断一个可选值是否包含值。你可以使用“相等”(`==`)或“不等”(`!=`)来执行比较。
@ -564,7 +540,7 @@ var surveyAnswer: String?
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
// 输出 "convertedNumber contains some integer value."
// 输出convertedNumber contains some integer value.
```
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(`!`)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的*强制解析forced unwrapping*
@ -573,19 +549,18 @@ if convertedNumber != nil {
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 输出 "convertedNumber has an integer value of 123."
// 输出convertedNumber has an integer value of 123.
```
更多关于 `if` 语句的内容,请参考[控制流](./05_Control_Flow.html)。
更多关于 `if` 语句的内容,请参考[控制流](./05_Control_Flow.md)。
> 注意
>
> 使用 `!` 来获取一个不存在的可选值会导致运行时错误。使用 `!` 来强制解析值之前,一定要确定可选包含一个非 `nil` 的值。
<a name="optional_binding"></a>
### 可选绑定
### 可选绑定 {#optional-binding}
使用*可选绑定optional binding*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 `if``while` 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if``while` 语句,请参考[控制流](./05_Control_Flow.html)。
使用*可选绑定optional binding*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 `if``while` 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if``while` 语句,请参考[控制流](./05_Control_Flow.md)。
像下面这样在 `if` 语句中写一个可选绑定:
@ -595,7 +570,7 @@ if let constantName = someOptional {
}
```
你可以像上面这样使用可选绑定来重写 在[可选类型](./01_The_Basics.html#optionals)举出的 `possibleNumber` 例子:
你可以像上面这样使用可选绑定来重写 在[可选类型](./01_The_Basics.md#optionals)举出的 `possibleNumber` 例子:
```swift
if let actualNumber = Int(possibleNumber) {
@ -603,7 +578,7 @@ if let actualNumber = Int(possibleNumber) {
} else {
print("\'\(possibleNumber)\' could not be converted to an integer")
}
// 输出 "'123' has an integer value of 123"
// 输出'123' has an integer value of 123
```
这段代码可以被理解为:
@ -620,7 +595,7 @@ if let actualNumber = Int(possibleNumber) {
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
// 输出 "4 < 42 < 100"
// 输出4 < 42 < 100
if let firstNumber = Int("4") {
if let secondNumber = Int("42") {
@ -629,15 +604,14 @@ if let firstNumber = Int("4") {
}
}
}
// 输出 "4 < 42 < 100"
// 输出4 < 42 < 100
```
> 注意
>
> 在 `if` 条件语句中使用常量和变量来创建一个可选绑定,仅在 `if` 语句的句中(`body`)中才能获取到值。相反,在 `guard` 语句中使用常量和变量来创建一个可选绑定,仅在 `guard` 语句外且在语句后才能获取到值,请参考[提前退出](./05_Control_Flow.html#early_exit)。
> 在 `if` 条件语句中使用常量和变量来创建一个可选绑定,仅在 `if` 语句的句中(`body`)中才能获取到值。相反,在 `guard` 语句中使用常量和变量来创建一个可选绑定,仅在 `guard` 语句外且在语句后才能获取到值,请参考[提前退出](./05_Control_Flow.md#early_exit)。
<a name="implicityly_unwrapped_optionals"></a>
### 隐式解析可选类型
### 隐式解析可选类型 {#implicityly-unwrapped-optionals}
如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过 `if` 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
@ -645,7 +619,7 @@ if let firstNumber = Int("4") {
这种类型的可选状态被定义为隐式解析可选类型implicitly unwrapped optionals。把想要用作可选的类型的后面的问号`String?`)改成感叹号(`String!`)来声明一个隐式解析可选类型。
当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考[无主引用以及隐式解析可选属性](./23_Automatic_Reference_Counting.html#unowned_references_and_implicitly_unwrapped_optional_properties)。
当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考[无主引用以及隐式解析可选属性](./23_Automatic_Reference_Counting.md#unowned_references_and_implicitly_unwrapped_optional_properties)。
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 `String` 和隐式解析可选类型 `String` 之间的区别:
@ -669,7 +643,7 @@ let implicitString: String = assumedString // 不需要感叹号
if assumedString != nil {
print(assumedString!)
}
// 输出 "An implicitly unwrapped optional string."
// 输出An implicitly unwrapped optional string.
```
你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值:
@ -678,15 +652,14 @@ if assumedString != nil {
if let definiteString = assumedString {
print(definiteString)
}
// 输出 "An implicitly unwrapped optional string."
// 输出An implicitly unwrapped optional string.
```
> 注意
>
> 如果一个变量之后可能变成 `nil` 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 `nil` 的话,请使用普通可选类型。
<a name="error_handling"></a>
## 错误处理
## 错误处理 {#error-handling}
你可以使用 *错误处理error handling* 来应对程序执行中可能会遇到的错误条件。
@ -734,23 +707,21 @@ do {
如果没有错误被抛出,`eatASandwich()` 函数会被调用。如果一个匹配 `SandwichError.outOfCleanDishes` 的错误被抛出,`washDishes()` 函数会被调用。如果一个匹配 `SandwichError.missingIngredients` 的错误被抛出,`buyGroceries(_:)` 函数会被调用,并且使用 `catch` 所捕捉到的关联值 `[String]` 作为参数。
抛出,捕捉,以及传播错误会在[错误处理](./17_Error_Handling.html)章节详细说明。
抛出,捕捉,以及传播错误会在[错误处理](./17_Error_Handling.md)章节详细说明。
<a name="assertions_and_preconditions"></a>
## 断言和先决条件
## 断言和先决条件 {#assertions-and-preconditions}
断言和先决条件是在运行时所做的检查。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为 true则代码像往常一样继续执行。如果布尔条件评估结果为 false程序的当前状态是无效的则代码执行结束应用程序中止。
你使用断言和先决条件来表达你所做的假设和你在编码时候的期望。你可以将这些包含在你的代码中。断言帮助你在开发阶段找到错误和不正确的假设,先决条件帮助你在生产环境中探测到存在的问题。
除了在运行时验证你的期望值,断言和先决条件也变成了一个在你的代码中的有用的文档形式。和在上面讨论过的[错误处理](./17_Error_Handling.html)不同,断言和先决条件并不是用来处理可以恢复的或者可预期的错误。因为一个断言失败表明了程序正处于一个无效的状态,没有办法去捕获一个失败的断言。
除了在运行时验证你的期望值,断言和先决条件也变成了一个在你的代码中的有用的文档形式。和在上面讨论过的[错误处理](./17_Error_Handling.md)不同,断言和先决条件并不是用来处理可以恢复的或者可预期的错误。因为一个断言失败表明了程序正处于一个无效的状态,没有办法去捕获一个失败的断言。
使用断言和先决条件不是一个能够避免出现程序出现无效状态的编码方法。然而,如果一个无效状态程序产生了,断言和先决条件可以强制检查你的数据和程序状态,使得你的程序可预测的中止(译者:不是系统强制的,被动的中止),并帮助使这个问题更容易调试。一旦探测到无效的状态,执行则被中止,防止无效的状态导致的进一步对于系统的伤害。
断言和先决条件的不同点是,他们什么时候进行状态检测:断言仅在调试环境运行,而先决条件则在调试环境和生产环境中运行。在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。
<a name="debugging_with_assertions"></a>
### 使用断言进行调试
### 使用断言进行调试 {#debugging-with-assertions}
你可以调用 Swift 标准库的 `assert(_:_:file:line:)` 函数来写一个断言。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示:
@ -780,8 +751,7 @@ if age > 10 {
}
```
<a name="enforcing_preconditions"></a>
### 强制执行先决条件
### 强制执行先决条件 {#enforcing-preconditions}
当一个条件可能为假,但是继续执行代码要求条件必须为真的时候,需要使用先决条件。例如使用先决条件来检查是否下标越界,或者来检查是否将一个正确的参数传给函数。
@ -792,7 +762,7 @@ if age > 10 {
precondition(index > 0, "Index must be greater than zero.")
```
你可以调用 `precondition(_:_:file:line:)` 方法来表明出现了一个错误例如switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。
你可以调用 `preconditionFailure(_:file:line:)` 方法来表明出现了一个错误例如switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。
> 注意
>

View File

@ -2,14 +2,13 @@
*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。
Swift 支持大部分标准 C 语言的运算符,且为了减少常见编码错误做了部分改进。如:赋值符(`=`)不再有返回值,这样就消除了手误将判等运算符(`==`)写成赋值符导致代码错误的缺陷。算术运算符(`+``-``*``/``%` 等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](./26_Advanced_Operators.html#overflow_operators)。
Swift 支持大部分标准 C 语言的运算符,且为了减少常见编码错误做了部分改进。如:赋值符(`=`)不再有返回值,这样就消除了手误将判等运算符(`==`)写成赋值符导致代码错误的缺陷。算术运算符(`+``-``*``/``%` 等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](./26_Advanced_Operators.md#overflow_operators)。
Swift 还提供了 C 语言没有的区间运算符,例如 `a..<b``a...b`,这方便我们表达一个区间内的数值。
本章节只描述了 Swift 中的基本运算符,[高级运算符](./26_Advanced_Operators.html)这章会包含 Swift 中的高级运算符,及如何自定义运算符,及如何进行自定义类型的运算符重载。
本章节只描述了 Swift 中的基本运算符,[高级运算符](./26_Advanced_Operators.md)这章会包含 Swift 中的高级运算符,及如何自定义运算符,及如何进行自定义类型的运算符重载。
<a name="terminology"></a>
## 术语
## 术语 {#terminology}
运算符分为一元、二元和三元运算符:
@ -19,8 +18,7 @@ Swift 还提供了 C 语言没有的区间运算符,例如 `a..<b` 或 `a...b`
受运算符影响的值叫*操作数*,在表达式 `1 + 2` 中,加号 `+` 是二元运算符,它的两个操作数是值 `1``2`
<a name="assignment_operator"></a>
## 赋值运算符
## 赋值运算符 {#assignment-operator}
*赋值运算符*`a = b`),表示用 `b` 的值来初始化或更新 `a` 的值:
@ -48,8 +46,7 @@ if x = y {
通过将 `if x = y` 标记为无效语句Swift 能帮你避免把 `==`)错写成(`=`)这类错误的出现。
<a name="arithmetic_operators"></a>
## 算术运算符
## 算术运算符 {#arithmetic-operators}
Swift 中所有数值类型都支持了基本的四则*算术运算符*
@ -65,7 +62,7 @@ Swift 中所有数值类型都支持了基本的四则*算术运算符*
10.0 / 2.5 // 等于 4.0
```
与 C 语言和 Objective-C 不同的是Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如 `a &+ b`)。详情参见[溢出运算符](./26_Advanced_Operators.html#overflow_operators)。
与 C 语言和 Objective-C 不同的是Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如 `a &+ b`)。详情参见[溢出运算符](./26_Advanced_Operators.md#overflow_operators)。
加法运算符也可用于 `String` 的拼接:
@ -73,8 +70,7 @@ Swift 中所有数值类型都支持了基本的四则*算术运算符*
"hello, " + "world" // 等于 "hello, world"
```
<a name="remainder_operator"></a>
### 求余运算符
### 求余运算符 {#remainder-operator}
*求余运算符*`a % b`)是计算 `b` 的多少倍刚刚好可以容入 `a`,返回多出来的那部分(余数)。
@ -118,8 +114,7 @@ Swift 中所有数值类型都支持了基本的四则*算术运算符*
在对负数 `b` 求余时,`b` 的符号会被忽略。这意味着 `a % b``a % -b` 的结果是相同的。
<a name="unary_minus_operator"></a>
### 一元负号运算符
### 一元负号运算符 {#unary-minus-operator}
数值的正负号可以使用前缀 `-`(即*一元负号符*)来切换:
@ -131,8 +126,7 @@ let plusThree = -minusThree // plusThree 等于 3, 或 "负负3"
一元负号符(`-`)写在操作数之前,中间没有空格。
<a name="unary_plus_operator"></a>
### 一元正号运算符
### 一元正号运算符 {#unary-plus-operator}
*一元正号符*`+`)不做任何改变地返回操作数的值:
@ -143,8 +137,7 @@ let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
虽然一元正号符什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。
<a name="compound_assignment_operators"></a>
## 组合赋值运算符
## 组合赋值运算符 {#compound-assignment-operators}
如同 C 语言Swift 也提供把其他运算符和赋值运算(`=`)组合的*组合赋值运算符*,组合加运算(`+=`)是其中一个例子:
@ -162,8 +155,7 @@ a += 2
更多 Swift 标准库运算符的信息,请看[运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。
<a name="comparison_operators"></a>
## 比较运算符Comparison Operators
## 比较运算符Comparison Operators {#comparison-operators}
所有标准 C 语言中的*比较运算符*都可以在 Swift 中使用:
@ -176,7 +168,7 @@ a += 2
> 注意
>
> Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在[类与结构](./09_Classes_and_Structures.html)
> Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在[类与结构](./09_Classes_and_Structures.md)章节的 **Identity Operators** 部分
每个比较运算都返回了一个标识表达式是否成立的布尔值:
@ -198,10 +190,10 @@ if name == "world" {
} else {
print("I'm sorry \(name), but I don't recognize you")
}
// 输出 "hello, world", 因为 `name` 就是等于 "world"
// 输出hello, world", 因为 `name` 就是等于 "world
```
关于 `if` 语句,请看[控制流](./05_Control_Flow.html)。
关于 `if` 语句,请看[控制流](./05_Control_Flow.md)。
如果两个元组的元素相同,且长度相同的话,元组就可以被比较。比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如:
@ -224,8 +216,7 @@ if name == "world" {
>
> Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。
<a name="ternary_conditional_operator"></a>
## 三元运算符Ternary Conditional Operator
## 三元运算符Ternary Conditional Operator {#ternary-conditional-operator}
*三元运算符*的特殊在于它是有三个操作数的运算符,它的形式是 `问题 ? 答案 1 : 答案 2`。它简洁地表达根据 `问题`成立与否作出二选一的操作。如果 `问题` 成立,返回 `答案 1` 的结果;反之返回 `答案 2` 的结果。
@ -266,8 +257,7 @@ if hasHeader {
三元运算为二选一场景提供了一个非常便捷的表达形式。不过需要注意的是,滥用三元运算符会降低代码可读性。所以我们应避免在一个复合语句中使用多个三元运算符。
<a name="nil_coalescing_operator"></a>
## 空合运算符Nil Coalescing Operator
## 空合运算符Nil Coalescing Operator {#nil-coalescing-operator}
*空合运算符*`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解包,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。
@ -304,13 +294,11 @@ colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 非空,因此 colorNameToUse 的值为 "green"
```
<a name="range_operators"></a>
## 区间运算符Range Operators
## 区间运算符Range Operators {#range-operators}
Swift 提供了几种方便表达一个区间的值的*区间运算符*。
<a name="closed_range_operator"></a>
### 闭区间运算符
### 闭区间运算符 {#closed-range-operator}
*闭区间运算符*`a...b`)定义一个包含从 `a``b`(包括 `a``b`)的所有值的区间。`a` 的值不能超过 `b`
@ -327,10 +315,9 @@ for index in 1...5 {
// 5 * 5 = 25
```
关于 `for-in` 循环,请看[控制流](./05_Control_Flow.html)。
关于 `for-in` 循环,请看[控制流](./05_Control_Flow.md)。
<a name="half-open_range_operator"></a>
### 半开区间运算符
### 半开区间运算符 {#half-open-range-operator}
*半开区间运算符*`a..<b`)定义一个从 `a``b` 但不包括 `b` 的区间。
之所以称为*半开区间*,是因为该区间包含第一个值而不包括最后的值。
@ -349,10 +336,9 @@ for i in 0..<count {
// 第 4 个人叫 Jack
```
数组有 4 个元素,但 `0..<count` 只数到3最后一个元素的下标因为它是半开区间。关于数组请查阅[数组](./04_Collection_Types.html#arrays)。
数组有 4 个元素,但 `0..<count` 只数到3最后一个元素的下标因为它是半开区间。关于数组请查阅[数组](./04_Collection_Types.md#arrays)。
<a name="one-sided_ranges"></a>
### 单侧区间
### 单侧区间 {#one-sided-ranges}
闭区间操作符有另一个表达形式,可以表达往一侧无限延伸的区间 —— 例如,一个包含了数组从索引 2 到结尾的所有值的区间。在这些情况下,你可以省略掉区间操作符一侧的值。这种区间叫做单侧区间,因为操作符只有一侧有值。例如:
@ -390,8 +376,7 @@ range.contains(4) // true
range.contains(-1) // true
```
<a name="logical_operators"></a>
## 逻辑运算符Logical Operators
## 逻辑运算符Logical Operators {#logical-operators}
*逻辑运算符*的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。
@ -410,14 +395,14 @@ let allowedEntry = false
if !allowedEntry {
print("ACCESS DENIED")
}
// 输出 "ACCESS DENIED"
// 输出ACCESS DENIED
```
`if !allowedEntry` 语句可以读作「如果非 allowedEntry」接下一行代码只有在「非 allowedEntry」为 `true`,即 `allowEntry``false` 时被执行。
在示例代码中,小心地选择布尔常量或变量有助于代码的可读性,并且避免使用双重逻辑非运算,或混乱的逻辑语句。
### 逻辑与运算符
### 逻辑与运算符 #{logical_and_operator}
*逻辑与运算符*`a && b`)表达了只有 `a``b` 的值都为 `true` 时,整个表达式的值才会是 `true`
@ -433,10 +418,10 @@ if enteredDoorCode && passedRetinaScan {
} else {
print("ACCESS DENIED")
}
// 输出 "ACCESS DENIED"
// 输出ACCESS DENIED
```
### 逻辑或运算符
### 逻辑或运算符 #{logical_or_operator}
逻辑或运算符(`a || b`)是一个由两个连续的 `|` 组成的中置运算符。它表示了两个逻辑表达式的其中一个为 `true`,整个表达式就为 `true`
@ -452,10 +437,10 @@ if hasDoorKey || knowsOverridePassword {
} else {
print("ACCESS DENIED")
}
// 输出 "Welcome!"
// 输出Welcome!
```
### 逻辑运算符组合计算
### 逻辑运算符组合计算 {#combining-logical-operators}
我们可以组合多个逻辑运算符来表达一个复合逻辑:
@ -465,7 +450,7 @@ if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
} else {
print("ACCESS DENIED")
}
// 输出 "Welcome!"
// 输出Welcome!
```
这个例子使用了含多个 `&&``||` 的复合逻辑。但无论怎样,`&&``||` 始终只能操作两个值。所以这实际是三个简单逻辑连续操作的结果。我们来解读一下:
@ -478,7 +463,7 @@ if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
>
> Swift 逻辑操作符 `&&` 和 `||` 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。
### 使用括号来明确优先级
### 使用括号来明确优先级 {#explicit-parentheses}
为了一个复杂表达式更容易读懂,在合适的地方使用括号来明确优先级是很有效的,虽然它并非必要的。在上个关于门的权限的例子中,我们给第一个部分加个括号,使它看起来逻辑更明确:
@ -488,7 +473,7 @@ if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword
} else {
print("ACCESS DENIED")
}
// 输出 "Welcome!"
// 输出Welcome!
```
这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧!

View File

@ -12,8 +12,7 @@ Swift 的 `String` 和 `Character` 类型提供了一种快速且兼容 Unicode
>
> 更多关于在 Foundation 和 Cocoa 中使用 `String` 的信息请查看 *[Bridging Between String and NSString](https://developer.apple.com/documentation/swift/string#2919514)*。
<a name="string_literals"></a>
## 字符串字面量
## 字符串字面量 {#string-literals}
你可以在代码里使用一段预定义的字符串值作为字符串字面量。字符串字面量是由一对双引号包裹着的具有固定顺序的字符集。
@ -25,8 +24,7 @@ let someString = "Some string literal value"
注意Swift 之所以推断 `someString` 常量为字符串类型,是因为它使用了字面量方式进行初始化。
<a name="multiline_string_literals"></a>
### 多行字符串字面量
### 多行字符串字面量 {#multiline-string-literals}
如果你需要一个字符串是跨越多行的,那就使用多行字符串字面量 — 由一对三个双引号包裹着的具有固定顺序的文本字符集:
@ -78,8 +76,7 @@ It also ends with a line break.
在上面的例子中,尽管整个多行字符串字面量都是缩进的(源代码缩进),第一行和最后一行没有以空白字符串开始(实际的变量值)。中间一行的缩进用空白字符串(源代码缩进)比关闭引号(`"""`之前的空白字符串多所以它的行首将有4个空格。
<a name="special_characters_in_string_literals"></a>
### 字符串字面量的特殊字符
### 字符串字面量的特殊字符 {#special-characters-in-string-literals}
字符串字面量可以包含以下特殊字符:
@ -107,8 +104,21 @@ Escaping all three quotes \"\"\"
"""
```
<a name="initializing_an_empty_string"></a>
## 初始化空字符串
### 扩展字符串分隔符 {#extended-string-delimiters}
您可以将字符串文字放在扩展分隔符中,这样字符串中的特殊字符将会被直接包含而非转义后的效果。将字符串放在引号(**"**)中并用数字符号(****)括起来。例如,打印字符串文字 **"Line 1 \ nLine 2"** 打印换行符转义序列(**\n**)而不是进行换行打印。
如果需要字符串文字中字符的特殊效果,请匹配转义字符(**\\**)后面添加与起始位置个数相匹配的 **#** 符。 例如,如果您的字符串是 **"Line 1 \ nLine 2"** 并且您想要换行,则可以使用 **“Line 1 \ #nLine 2”** 来代替。 同样,**###"Line1 \ ### nLine2"###** 也可以实现换行效果。
扩展分隔符创建的字符串文字也可以是多行字符串文字。 您可以使用扩展分隔符在多行字符串中包含文本 **"""**,覆盖原有的结束文字的默认行为。例如:
```swift
let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
"""#
```
## 初始化空字符串 {#initializing-an-empty-string}
要创建一个空字符串作为初始值,可以将空的字符串字面量赋值给变量,也可以初始化一个新的 `String` 实例:
@ -124,11 +134,10 @@ var anotherEmptyString = String() // 初始化方法
if emptyString.isEmpty {
print("Nothing to see here")
}
// 打印输出:"Nothing to see here"
// 打印输出:Nothing to see here
```
<a name="string_mutability"></a>
## 字符串可变性
## 字符串可变性 {#string-mutability}
你可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:
@ -146,17 +155,15 @@ constantString += " and another Highlander"
>
> 在 Objective-C 和 Cocoa 中,需要通过选择两个不同的类(`NSString` 和 `NSMutableString`)来指定字符串是否可以被修改。
<a name="strings_are_value_types"></a>
## 字符串是值类型
## 字符串是值类型 {#strings-are-value-types}
在 Swift 中 `String` 类型是*值类型*。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。在前述任一情况下,都会对已有字符串值创建新副本,并对该新副本而非原始字符串进行传递或赋值操作。值类型在 [结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types) 中进行了详细描述。
在 Swift 中 `String` 类型是*值类型*。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。在前述任一情况下,都会对已有字符串值创建新副本,并对该新副本而非原始字符串进行传递或赋值操作。值类型在 [结构体和枚举是值类型](./09_Classes_and_Structures.md#structures_and_enumerations_are_value_types) 中进行了详细描述。
Swift 默认拷贝字符串的行为保证了在函数/方法向你传递的字符串所属权属于你,无论该值来自于哪里。你可以确信传递的字符串不会被修改,除非你自己去修改它。
在实际编译时Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着你将字符串作为值类型的同时可以获得极高的性能。
<a name="working_with_characters"></a>
## 使用字符
## 使用字符 {#working-with-characters}
你可通过 `for-in` 循环来遍历字符串,获取字符串中每一个字符的值:
@ -171,7 +178,7 @@ for character in "Dog!🐶" {
// 🐶
```
`for-in` 循环在 [For 循环](./05_Control_Flow.html#for_loops) 中进行了详细描述。
`for-in` 循环在 [For 循环](./05_Control_Flow.md#for_loops) 中进行了详细描述。
另外,通过标明一个 `Character` 类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:
@ -185,11 +192,10 @@ let exclamationMark: Character = "!"
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// 打印输出:"Cat!🐱"
// 打印输出:Cat!🐱
```
<a name="concatenating_strings_and_characters"></a>
## 连接字符串和字符
## 连接字符串和字符 {#concatenating-strings-and-characters}
字符串可以通过加法运算符(`+`)相加在一起(或称“连接”)创建一个新的字符串:
@ -249,8 +255,7 @@ print(goodStart + end)
上面的代码,把 `badStart``end` 拼接起来的字符串非我们想要的结果。因为 `badStart` 最后一行没有换行符,它与 `end` 的第一行结合到了一起。相反的,`goodStart` 的每一行都以换行符结尾,所以它与 `end` 拼接的字符串总共有三行,正如我们期望的那样。
<a name="string_interpolation"></a>
## 字符串插值
## 字符串插值 {#string-interpolation}
*字符串插值*是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。**字符串字面量**和**多行字符串字面量**都可以使用字符串插值。你插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:
@ -268,20 +273,17 @@ let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
>
> 插值字符串中写在括号中的表达式不能包含非转义反斜杠(`\`),并且不能包含回车或换行符。不过,插值字符串可以包含其他字面量。
<a name="unicode"></a>
## Unicode
## Unicode {#unicode}
*Unicode*是一个用于在不同书写系统中对文本进行编码、表示和处理的国际标准。它使你可以用标准格式表示来自任意语言几乎所有的字符并能够对文本文件或网页这样的外部资源中的字符进行读写操作。Swift 的 `String``Character` 类型是完全兼容 Unicode 标准的。
<a name="unicode_scalars"></a>
### Unicode 标量
### Unicode 标量 {#unicode-scalars}
Swift 的 `String` 类型是基于 *Unicode 标量* 建立的。Unicode 标量是对应字符或者修饰符的唯一的 21 位数字,例如 `U+0061` 表示小写的拉丁字母(`LATIN SMALL LETTER A`"`a`"`U+1F425` 表示小鸡表情(`FRONT-FACING BABY CHICK`"`🐥`")。
请注意,并非所有 21 位 Unicode 标量值都分配给字符,某些标量被保留用于将来分配或用于 UTF-16 编码。已分配的标量值通常也有一个名称,例如上面示例中的 LATIN SMALL LETTER A 和 FRONT-FACING BABY CHICK。
<a name="extended_grapheme_clusters"></a>
### 可扩展的字形群集
### 可扩展的字形群集 {#extended-grapheme-clusters}
每一个 Swift 的 `Character` 类型代表一个*可扩展的字形群*。而一个可扩展的字形群构成了人类可读的单个字符,它由一个或多个(当组合时) Unicode 标量的序列组成。
@ -318,15 +320,14 @@ let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS 是 🇺🇸
```
<a name="counting_characters"></a>
## 计算字符数量
## 计算字符数量 {#counting-characters}
如果想要获得一个字符串中 `Character` 值的数量,可以使用 `count` 属性:
```swift
let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// 打印输出 "unusualMenagerie has 40 characters"
// 打印输出unusualMenagerie has 40 characters
```
注意在 Swift 中,使用可拓展的字符群集作为 `Character` 值来连接或改变字符串时,并不一定会更改字符串的字符数量。
@ -336,27 +337,25 @@ print("unusualMenagerie has \(unusualMenagerie.count) characters")
```swift
var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// 打印输出 "the number of characters in cafe is 4"
// 打印输出the number of characters in cafe is 4
word += "\u{301}" // 拼接一个重音U+0301
print("the number of characters in \(word) is \(word.count)")
// 打印输出 "the number of characters in café is 4"
// 打印输出the number of characters in café is 4
```
> 注意
>
> 可扩展的字符群集可以组成一个或者多个 Unicode 标量。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果你正在处理一个长字符串,需要注意 `count` 属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。
> 可扩展的字形群可以由多个 Unicode 标量组成。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果你正在处理一个长字符串,需要注意 `count` 属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。
>
> 另外需要注意的是通过 `count` 属性返回的字符数量并不总是与包含相同字符的 `NSString` 的 `length` 属性相同。`NSString` 的 `length` 属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。
<a name="accessing_and_modifying_a_string"></a>
## 访问和修改字符串
## 访问和修改字符串 {#accessing-and-modifying-a-string}
你可以通过字符串的属性和方法来访问和修改它,当然也可以用下标语法完成。
<a name="string_indices"></a>
### 字符串索引
### 字符串索引 {#string-indices}
每一个 `String` 值都有一个关联的索引(*index*)类型,`String.Index`,它对应着字符串中的每一个 `Character` 的位置。
@ -394,15 +393,14 @@ greeting.index(after: endIndex) // error
for index in greeting.indices {
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` 中。
<a name="inserting_and_removing"></a>
### 插入和删除
### 插入和删除 {#inserting-and-removing}
调用 `insert(_:at:)` 方法可以在一个字符串的指定索引插入一个字符,调用 `insert(contentsOf:at:)` 方法可以在一个字符串的指定索引插入一个段字符串。
@ -430,14 +428,13 @@ welcome.removeSubrange(range)
>
> 你可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一个确认的并遵循 `RangeReplaceableCollection` 协议的类型里面,如上文所示是使用在 `String` 中,你也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
<a name="substrings"></a>
## 子字符串
## 子字符串 {#substrings}
当你从字符串中获取一个子字符串 —— 例如,使用下标或者 `prefix(_:)` 之类的方法 —— 就可以得到一个 `SubString` 的实例,而非另外一个 `String`。Swift 里的 `SubString` 绝大部分函数都跟 `String` 一样,意味着你可以使用同样的方式去操作 `SubString``String`。然而,跟 `String` 不同的是,你只有在短时间内需要操作字符串时,才会使用 `SubString`。当你需要长时间保存结果时,就把 `SubString` 转化为 `String` 的实例:
```swift
let greeting = "Hello, world!"
let index = greeting.index(of: ",") ?? greeting.endIndex
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning 的值为 "Hello"
@ -455,15 +452,13 @@ let newString = String(beginning)
>
> `String` 和 `SubString` 都遵循 `StringProtocol<//apple_ref/swift/intf/s:s14StringProtocolP>` 协议,这意味着操作字符串的函数使用 `StringProtocol` 会更加方便。你可以传入 `String` 或 `SubString` 去调用函数。
<a name="comparing_strings"></a>
## 比较字符串
## 比较字符串 {#comparing-strings}
Swift 提供了三种方式来比较文本值:字符串字符相等、前缀相等和后缀相等。
<a name="string_and_character_equality"></a>
### 字符串/字符相等
### 字符串/字符相等 {#string-and-character-equality}
字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),详细描述在[比较运算符](./02_Basic_Operators.html#comparison_operators)
字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),详细描述在[比较运算符](./02_Basic_Operators.md#comparison_operators)
```swift
let quotation = "We're a lot alike, you and I."
@ -471,7 +466,7 @@ let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
print("These two strings are considered equal")
}
// 打印输出 "These two strings are considered equal"
// 打印输出These two strings are considered equal
```
如果两个字符串(或者两个字符)的可扩展的字形群集是标准相等,那就认为它们是相等的。只要可扩展的字形群集有同样的语言意义和外观则认为它们标准相等,即使它们是由不同的 Unicode 标量构成。
@ -488,7 +483,7 @@ let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"
if eAcuteQuestion == combinedEAcuteQuestion {
print("These two strings are considered equal")
}
// 打印输出 "These two strings are considered equal"
// 打印输出These two strings are considered equal
```
相反,英语中的 `LATIN CAPITAL LETTER A`(`U+0041`,或者 `A`)不等于俄语中的 `CYRILLIC CAPITAL LETTER A`(`U+0410`,或者 `A`)。两个字符看着是一样的,但却有不同的语言意义:
@ -501,15 +496,14 @@ let cyrillicCapitalLetterA: Character = "\u{0410}"
if latinCapitalLetterA != cyrillicCapitalLetterA {
print("These two characters are not equivalent")
}
// 打印 "These two characters are not equivalent"
// 打印These two characters are not equivalent
```
> 注意
>
> 在 Swift 中字符串和字符并不区分地域not locale-sensitive
<a name="prefix_and_suffix_equality"></a>
### 前缀/后缀相等
### 前缀/后缀相等 {#prefix-and-suffix-equality}
通过调用字符串的 `hasPrefix(_:)`/`hasSuffix(_:)` 方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个 `String` 类型的参数,并返回一个布尔值。
@ -541,7 +535,7 @@ for scene in romeoAndJuliet {
}
}
print("There are \(act1SceneCount) scenes in Act 1")
// 打印输出 "There are 5 scenes in Act 1"
// 打印输出There are 5 scenes in Act 1
```
相似地,你可以用 `hasSuffix(_:)` 方法来计算发生在不同地方的场景数:
@ -557,15 +551,14 @@ for scene in romeoAndJuliet {
}
}
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
// 打印输出 "6 mansion scenes; 2 cell scenes"
// 打印输出6 mansion scenes; 2 cell scenes
```
> 注意
>
> `hasPrefix(_:)` 和 `hasSuffix(_:)` 方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等,详细描述在[字符串/字符相等](#string_and_character_equality)。
<a name="unicode_representations_of_strings"></a>
## 字符串的 Unicode 表示形式
## 字符串的 Unicode 表示形式 {#unicode-representations-of-strings}
当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种 `编码格式`encoding forms编码。每一个字符串中的小块编码都被称 `代码单元`code units。这些包括 UTF-8 编码格式(编码字符串为 8 位的代码单元), UTF-16 编码格式(编码字符串位 16 位的代码单元),以及 UTF-32 编码格式编码字符串32位的代码单元
@ -583,8 +576,7 @@ Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式
let dogString = "Dog‼🐶"
```
<a name="UTF-8_representation"></a>
### UTF-8 表示
### UTF-8 表示 {#UTF-8-representation}
你可以通过遍历 `String``utf8` 属性来访问它的 `UTF-8` 表示。其为 `String.UTF8View` 类型的属性,`UTF8View` 是无符号 8 位(`UInt8`)值的集合,每一个 `UInt8` 值都是一个字符的 UTF-8 表示:
@ -635,8 +627,7 @@ print("")
上面的例子中,前三个 10 进制 `codeUnit` 值(`68``111``103`)代表了字符 `D``o``g`,它们的 UTF-8 表示与 ASCII 表示相同。接下来的三个 10 进制 `codeUnit` 值(`226``128``188`)是 `DOUBLE EXCLAMATION MARK` 的3字节 UTF-8 表示。最后的四个 `codeUnit` 值(`240``159``144``182`)是 `DOG FACE` 的4字节 UTF-8 表示。
<a name="UTF-16_representation"></a>
### UTF-16 表示
### UTF-16 表示 {#UTF-16-representation}
你可以通过遍历 `String``utf16` 属性来访问它的 `UTF-16` 表示。其为 `String.UTF16View` 类型的属性,`UTF16View` 是无符号16位`UInt16`)值的集合,每一个 `UInt16` 都是一个字符的 UTF-16 表示:
@ -683,8 +674,7 @@ print("")
第五和第六个 `codeUnit` 值(`55357``56374`)是 `DOG FACE` 字符的 UTF-16 表示。第一个值为 `U+D83D`(十进制值为 `55357`),第二个值为 `U+DC36`(十进制值为 `56374`)。
<a name="unicode_scalars_representation"></a>
### Unicode 标量表示
### Unicode 标量表示 {#unicode-scalars-representation}
你可以通过遍历 `String` 值的 `unicodeScalars` 属性来访问它的 Unicode 标量表示。其为 `UnicodeScalarView` 类型的属性,`UnicodeScalarView``UnicodeScalar` 类型的值的集合。

View File

@ -8,10 +8,9 @@ Swift 语言中的 `Arrays`、`Sets` 和 `Dictionaries` 中存储的数据值类
> 注意
>
> Swift 的 `Arrays`、`Sets` 和 `Dictionaries` 类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./23_Generics.html)章节。
> Swift 的 `Arrays`、`Sets` 和 `Dictionaries` 类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./23_Generics.md)章节。
<a name="mutability_of_collections"></a>
## 集合的可变性
## 集合的可变性 {#mutability-of-collections}
如果创建一个 `Arrays``Sets``Dictionaries` 并且把它分配成一个变量,这个集合将会是*可变的*。这意味着你可以在创建之后添加更多或移除已存在的数据项,或者改变集合中的数据项。如果我们把 `Arrays``Sets``Dictionaries` 分配成常量,那么它就是*不可变的*,它的大小和内容都不能被改变。
@ -19,8 +18,7 @@ Swift 语言中的 `Arrays`、`Sets` 和 `Dictionaries` 中存储的数据值类
>
> 在我们不需要改变集合的时候创建不可变集合是很好的实践。如此 Swift 编译器可以优化我们创建的集合。
<a name="arrays"></a>
## 数组Arrays
## 数组Arrays {#arrays}
*数组*使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。
@ -28,20 +26,18 @@ Swift 语言中的 `Arrays`、`Sets` 和 `Dictionaries` 中存储的数据值类
>
> Swift 的 `Array` 类型被桥接到 `Foundation` 中的 `NSArray` 类。更多关于在 `Foundation` 和 `Cocoa` 中使用 `Array` 的信息,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。
<a name="array_type_shorthand_syntax"></a>
### 数组的简单语法
### 数组的简单语法 {#array-type-shorthand-syntax}
写 Swift 数组应该遵循像 `Array<Element>` 这样的形式,其中 `Element` 是这个数组中唯一允许存在的数据类型。我们也可以使用像 `[Element]` 这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。
<a name="creating_an_empty_array"></a>
### 创建一个空数组
### 创建一个空数组 {#creating-an-empty-array}
我们可以使用构造语法来创建一个由特定数据类型构成的空数组:
```swift
var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// 打印 "someInts is of type [Int] with 0 items."
// 打印someInts is of type [Int] with 0 items.
```
注意,通过构造函数的类型,`someInts` 的值类型被推断为 `[Int]`
@ -55,8 +51,7 @@ someInts = []
// someInts 现在是空数组,但是仍然是 [Int] 类型的。
```
<a name="creating_an_array_with_a_default_value"></a>
### 创建一个带有默认值的数组
### 创建一个带有默认值的数组 {#creating-an-array-with-a-default-value}
Swift 中的 `Array` 类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(`count`)和适当类型的初始值(`repeating`)传入数组构造函数:
@ -65,8 +60,7 @@ var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]
```
<a name="creating_an_array_by_adding_two_arrays_together"></a>
### 通过两个数组相加创建一个数组
### 通过两个数组相加创建一个数组 {#creating-an-array-by-adding-two-arrays-together}
我们可以使用加法操作符(`+`)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来:
@ -78,8 +72,7 @@ var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
```
<a name="creating_an_array_with_an_array_literals"></a>
### 用数组字面量构造数组
### 用数组字面量构造数组 {#creating-an-array-with-an-array-literals}
我们可以使用*数组字面量*来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。数组字面量是一系列由逗号分割并由方括号包含的数值:
@ -108,8 +101,7 @@ var shoppingList = ["Eggs", "Milk"]
因为所有数组字面量中的值都是相同的类型Swift 可以推断出 `[String]``shoppingList` 中变量的正确类型。
<a name="accessing_and_modifying_an_array"></a>
### 访问和修改数组
### 访问和修改数组 {#accessing-and-modifying-an-array}
我们可以通过数组的方法和属性来访问和修改数组,或者使用下标语法。
@ -117,7 +109,7 @@ var shoppingList = ["Eggs", "Milk"]
```swift
print("The shopping list contains \(shoppingList.count) items.")
// 输出 "The shopping list contains 2 items."这个数组有2个项
// 输出The shopping list contains 2 items.这个数组有2个项
```
使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`
@ -128,7 +120,7 @@ if shoppingList.isEmpty {
} else {
print("The shopping list is not empty.")
}
// 打印 "The shopping list is not empty."shoppinglist 不是空的)
// 打印The shopping list is not empty.shoppinglist 不是空的)
```
也可以使用 `append(_:)` 方法在数组后面添加新的数据项:
@ -151,7 +143,7 @@ shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
```swift
var firstItem = shoppingList[0]
// 第一项是 "Eggs"
// 第一项是Eggs
```
> 注意
@ -162,7 +154,7 @@ var firstItem = shoppingList[0]
```swift
shoppingList[0] = "Six eggs"
// 其中的第一项现在是 "Six eggs" 而不是 "Eggs"
// 其中的第一项现在是Six eggs而不是Eggs
```
还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把 `"Chocolate Spread"``"Cheese"``"Butter"` 替换为 `"Bananas"``"Apples"`
@ -181,7 +173,7 @@ shoppingList[4...6] = ["Bananas", "Apples"]
```swift
shoppingList.insert("Maple Syrup", at: 0)
// shoppingList 现在有7项
// "Maple Syrup" 现在是这个列表中的第一项
// 现在是这个列表中的第一项是“Maple Syrup”
```
这次 `insert(_:at:)` 方法调用把值为 `"Maple Syrup"` 的新数据项插入列表的最开始位置,并且使用 `0` 作为索引值。
@ -192,7 +184,7 @@ shoppingList.insert("Maple Syrup", at: 0)
let mapleSyrup = shoppingList.remove(at: 0)
// 索引值为0的数据项被移除
// shoppingList 现在只有6项而且不包括 Maple Syrup
// mapleSyrup 常量的值等于被移除数据项的值 "Maple Syrup"
// mapleSyrup 常量的值等于被移除数据项Maple Syrup”的值
```
> 注意
@ -203,7 +195,7 @@ let mapleSyrup = shoppingList.remove(at: 0)
```swift
firstItem = shoppingList[0]
// firstItem 现在等于 "Six eggs"
// firstItem 现在等于Six eggs
```
如果我们只想把数组中的最后一项移除,可以使用 `removeLast()` 方法而不是 `remove(at:)` 方法来避免我们需要获取数组的 `count` 属性。就像后者一样,前者也会返回被移除的数据项:
@ -212,11 +204,10 @@ firstItem = shoppingList[0]
let apples = shoppingList.removeLast()
// 数组的最后一项被移除了
// shoppingList 现在只有5项不包括 Apples
// apples 常量的值现在等于 "Apples" 字符串
// apples 常量的值现在等于Apples字符串
```
<a name="iterating_over_an_array"></a>
### 数组的遍历
### 数组的遍历 {#iterating-over-an-array}
我们可以使用 `for-in` 循环来遍历所有数组中的数据项:
@ -234,7 +225,7 @@ for item in shoppingList {
如果我们同时需要每个数据项的值和索引值,可以使用 `enumerated()` 方法来进行数组遍历。`enumerated()` 返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:
```swift
for (index, value) in shoppingList. enumerated() {
for (index, value) in shoppingList.enumerated() {
print("Item \(String(index + 1)): \(value)")
}
// Item 1: Six eggs
@ -246,8 +237,7 @@ for (index, value) in shoppingList. enumerated() {
更多关于 `for-in` 循环的介绍请参见[for 循环](05_Control_Flow.html#for_loops)。
<a name="sets"></a>
## 集合Sets
## 集合Sets {#sets}
*集合Set*用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。
@ -256,12 +246,11 @@ for (index, value) in shoppingList. enumerated() {
>
> 关于使用 `Foundation` 和 `Cocoa` 中 `Set` 的知识,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。
<a name="hash_values_for_set_types"></a>
### 集合类型的哈希值
### 集合类型的哈希值 {#hash-values-for-set-types}
一个类型为了存储在集合中,该类型必须是*可哈希化*的--也就是说,该类型必须提供一个方法来计算它的*哈希值*。一个哈希值是 `Int` 类型的,相等的对象哈希值必须相同,比如 `a==b`,因此必须 `a.hashValue == b.hashValue`
一个类型为了存储在集合中,该类型必须是*可哈希化*的——也就是说,该类型必须提供一个方法来计算它的*哈希值*。一个哈希值是 `Int` 类型的,相等的对象哈希值必须相同,比如 `a==b`,因此必须 `a.hashValue == b.hashValue`
Swift 的所有基本类型(比如 `String`,`Int`,`Double``Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.html)有讲述)默认也是可哈希化的。
Swift 的所有基本类型(比如 `String``Int``Double``Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.md)有讲述)默认也是可哈希化的。
> 注意
>
@ -273,22 +262,20 @@ Swift 的所有基本类型(比如 `String`,`Int`,`Double` 和 `Bool`)默认
> * `a == b` 意味着 `b == a`(对称性)
> * `a == b && b == c` 意味着 `a == c`(传递性)
关于遵循协议的更多信息,请看[协议](./22_Protocols.html)。
关于遵循协议的更多信息,请看[协议](./22_Protocols.md)。
<a name="set_type_syntax"></a>
### 集合类型语法
### 集合类型语法 {#set-type-syntax}
Swift 中的 `Set` 类型被写为 `Set<Element>`,这里的 `Element` 表示 `Set` 中允许存储的类型,和数组不同的是,集合没有等价的简化形式。
<a name="creating_and_initalizing_an_empty_set"></a>
### 创建和构造一个空的集合
### 创建和构造一个空的集合 {#creating-and-initalizing-an-empty-set}
你可以通过构造器语法创建一个特定类型的空集合:
```swift
var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// 打印 "letters is of type Set<Character> with 0 items."
// 打印letters is of type Set<Character> with 0 items.
```
> 注意
@ -301,11 +288,10 @@ print("letters is of type Set<Character> with \(letters.count) items.")
letters.insert("a")
// letters 现在含有1个 Character 类型的值
letters = []
// letters 现在是一个空的 Set, 但是它依然是 Set<Character> 类型
// letters 现在是一个空的 Set但是它依然是 Set<Character> 类型
```
<a name="creating_a_set_with_an_array_literal"></a>
### 用数组字面量创建集合
### 用数组字面量创建集合 {#creating-a-set-with-an-array-literal}
你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。
@ -330,8 +316,7 @@ var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
由于数组字面量中的所有元素类型相同Swift 可以推断出 `Set<String>` 作为 `favoriteGenres` 变量的正确类型。
<a name="accesing_and_modifying_a_set"></a>
### 访问和修改一个集合
### 访问和修改一个集合 {#accesing-and-modifying-a-set}
你可以通过 `Set` 的属性和方法来访问和修改一个 `Set`
@ -339,7 +324,7 @@ var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
```swift
print("I have \(favoriteGenres.count) favorite music genres.")
// 打印 "I have 3 favorite music genres."
// 打印I have 3 favorite music genres.
```
使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`
@ -350,7 +335,7 @@ if favoriteGenres.isEmpty {
} else {
print("I have particular music preferences.")
}
// 打印 "I have particular music preferences."
// 打印I have particular music preferences.
```
你可以通过调用 `Set``insert(_:)` 方法来添加一个新元素:
@ -368,7 +353,7 @@ if let removedGenre = favoriteGenres.remove("Rock") {
} else {
print("I never much cared for that.")
}
// 打印 "Rock? I'm over it."
// 打印Rock? I'm over it.
```
使用 `contains(_:)` 方法去检查 `Set` 中是否包含一个特定的值:
@ -379,11 +364,10 @@ if favoriteGenres.contains("Funk") {
} else {
print("It's too funky in here.")
}
// 打印 "It's too funky in here."
// 打印It's too funky in here.
```
<a name="iterating_over_a_set"></a>
### 遍历一个集合
### 遍历一个集合 {#iterating-over-a-set}
你可以在一个 `for-in` 循环中遍历一个 `Set` 中的所有值。
@ -396,7 +380,7 @@ for genre in favoriteGenres {
// Hip hop
```
更多关于 `for-in` 循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。
更多关于 `for-in` 循环的信息,参见[For 循环](./05_Control_Flow.md#for_loops)。
Swift 的 `Set` 类型没有确定的顺序,为了按照特定顺序来遍历一个 `Set` 中的值可以使用 `sorted()` 方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符'<'对元素进行比较的结果来确定。
@ -404,20 +388,18 @@ Swift 的 `Set` 类型没有确定的顺序,为了按照特定顺序来遍历
for genre in favoriteGenres.sorted() {
print("\(genre)")
}
// prints "Classical"
// prints "Hip hop"
// prints "Jazz
// Classical
// Hip hop
// Jazz
```
<a name="performing_set_operations"></a>
## 集合操作
## 集合操作 {#performing-set-operations}
你可以高效地完成 `Set` 的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。
<a name="fundamental_set_operations"></a>
### 基本集合操作
### 基本集合操作 {#fundamental-set-operations}
下面的插图描述了两个集合-`a``b`-以及通过阴影部分的区域显示集合各种操作的结果。
下面的插图描述了两个集合 `a``b`以及通过阴影部分的区域显示集合各种操作的结果。
![](https://docs.swift.org/swift-book/_images/setVennDiagram_2x.png)
@ -433,18 +415,17 @@ let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits. intersection(evenDigits).sorted()
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits. symmetricDifference(singleDigitPrimeNumbers).sorted()
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
```
<a name="set_membership_and_equality"></a>
### 集合成员关系和相等
### 集合成员关系和相等 {#set-membership-and-equality}
下面的插图描述了三个集合-`a`,`b``c`,以及通过重叠区域表述集合间共享的元素。集合 `a` 是集合 `b` 的父集合,因为 `a` 包含了 `b` 中所有的元素,相反的,集合 `b` 是集合 `a` 的子集合,因为属于 `b` 的元素也被 `a` 包含。集合 `b` 和集合 `c` 彼此不关联,因为它们之间没有共同的元素。
下面的插图描述了三个集合 `a``b``c`,以及通过重叠区域表述集合间共享的元素。集合 `a` 是集合 `b` 的父集合,因为 `a` 包含了 `b` 中所有的元素,相反的,集合 `b` 是集合 `a` 的子集合,因为属于 `b` 的元素也被 `a` 包含。集合 `b` 和集合 `c` 彼此不关联,因为它们之间没有共同的元素。
![](https://docs.swift.org/swift-book/_images/setEulerDiagram_2x.png)
@ -467,8 +448,7 @@ farmAnimals.isDisjoint(with: cityAnimals)
// true
```
<a name="dictionaries"></a>
## 字典
## 字典 {#dictionaries}
*字典*是一种存储多个相同类型的值的容器。每个值value都关联唯一的键key键作为字典中的这个值数据的标识符。和数组中的数据项不同字典中的数据项并没有具体顺序。我们在需要通过标识符访问数据的时候使用字典这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。
@ -478,8 +458,7 @@ farmAnimals.isDisjoint(with: cityAnimals)
>
> 更多关于在 `Foundation` 和 `Cocoa` 中使用 `Dictionary` 类型的信息,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。
<a name="dictionary_type_shorthand_syntax"></a>
### 字典类型简化语法
### 字典类型简化语法 {#dictionary-type-shorthand-syntax}
Swift 的字典使用 `Dictionary<Key, Value>` 定义,其中 `Key` 是字典中键的数据类型,`Value` 是字典中对应于这些键所存储值的数据类型。
@ -489,8 +468,7 @@ Swift 的字典使用 `Dictionary<Key, Value>` 定义,其中 `Key` 是字典
我们也可以用 `[Key: Value]` 这样简化的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。
<a name="creating_an_empty_dictionary"></a>
### 创建一个空字典
### 创建一个空字典 {#creating-an-empty-dictionary}
我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典:
@ -510,8 +488,7 @@ namesOfIntegers = [:]
// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典
```
<a name="creating_a_dictionary_with_a_dictionary_literal"></a>
### 用字典字面量创建字典
### 用字典字面量创建字典 {#creating-a-dictionary-with-a-dictionary-literal}
我们可以使用*字典字面量*来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作 `Dictionary` 集合的快捷途径。
@ -546,8 +523,7 @@ var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
因为这个语句中所有的键和值都各自拥有相同的数据类型Swift 可以推断出 `Dictionary<String, String>``airports` 字典的正确类型。
<a name="accessing_and_modifying_a_dictionary"></a>
### 访问和修改字典
### 访问和修改字典 {#accessing-and-modifying-a-dictionary}
我们可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。
@ -555,7 +531,7 @@ var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
```swift
print("The dictionary of airports contains \(airports.count) items.")
// 打印 "The dictionary of airports contains 2 items."(这个字典有两个数据项)
// 打印The dictionary of airports contains 2 items.(这个字典有两个数据项)
```
使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`
@ -566,7 +542,7 @@ if airports.isEmpty {
} else {
print("The airports dictionary is not empty.")
}
// 打印 "The airports dictionary is not empty."
// 打印The airports dictionary is not empty.
```
我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值:
@ -593,7 +569,7 @@ airports["LHR"] = "London Heathrow"
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
// 输出 "The old value for DUB was Dublin."
// 输出The old value for DUB was Dublin.
```
我们也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回对应值的类型的可选值。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选值,否则将返回 `nil`
@ -604,14 +580,14 @@ if let airportName = airports["DUB"] {
} else {
print("That airport is not in the airports dictionary.")
}
// 打印 "The name of the airport is Dublin Airport."
// 打印The name of the airport is Dublin Airport.
```
我们还可以使用下标语法来通过给某个键的对应值赋值为 `nil` 来从字典里移除一个键值对:
```swift
airports["APL"] = "Apple Internation"
// "Apple Internation" 不是真的 APL 机场,删除它
// Apple Internation不是真的 APL 机场,删除它
airports["APL"] = nil
// APL 现在被移除了
```
@ -619,16 +595,15 @@ airports["APL"] = nil
此外,`removeValue(forKey:)` 方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回 `nil`
```swift
if let removedValue = airports. removeValue(forKey: "DUB") {
if let removedValue = airports.removeValue(forKey: "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."
// 打印“The removed airport's name is Dublin Airport.
```
<a name="iterating_over_a_dictionary"></a>
### 字典遍历
### 字典遍历 {#iterating-over-a-dictionary}
我们可以使用 `for-in` 循环来遍历某个字典中的键值对。每一个字典中的数据项都以 `(key, value)` 元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组:
@ -640,7 +615,7 @@ for (airportCode, airportName) in airports {
// LHR: London Heathrow
```
更多关于 `for-in` 循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。
更多关于 `for-in` 循环的信息,参见[For 循环](./05_Control_Flow.md#for_loops)。
通过访问 `keys` 或者 `values` 属性,我们也可以遍历字典的键或者值:

View File

@ -6,8 +6,7 @@ Swift 还提供了 `for-in` 循环用来更简单地遍历数组Array
Swift 的 `switch` 语句比许多类 C 语言要更加强大。case 还可以匹配很多不同的模式包括范围匹配元组tuple和特定类型匹配。`switch` 语句的 case 中匹配的值可以声明为临时常量或变量,在 case 作用域内使用,也可以配合 `where` 来描述更复杂的匹配条件。
<a name="for_in_loops"></a>
## For-In 循环
## For-In 循环 {#for-in-loops}
你可以使用 `for-in` 循环来遍历一个集合中的所有元素,例如数组中的元素、范围内的数字或者字符串中的字符。
@ -36,7 +35,7 @@ for (animalName, legCount) in numberOfLegs {
// cats have 4 legs
```
字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见[集合类型](./04_Collection_Types.html)。
字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见[集合类型](./04_Collection_Types.md)。
`for-in` 循环还可以使用数字范围。下面的例子用来输出乘法表的一部分内容:
@ -65,12 +64,12 @@ for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// 输出 "3 to the power of 10 is 59049"
// 输出3 to the power of 10 is 59049
```
这个例子计算 base 这个数的 power 次幂(本例中,是 `3``10` 次幂),从 `1``3``0` 次幂)开始做 `3` 的乘法, 进行 `10` 次,使用 `1``10` 的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号 `_` (替代循环中的变量)能够忽略当前值,并且不提供循环遍历时对值的访问。
在某些情况下,你可能不想使用包括两个端点的闭区间。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅[区间运算符](./02_Basic_Operators.html#range_operators)。
在某些情况下,你可能不想使用包括两个端点的闭区间。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅[区间运算符](./02_Basic_Operators.md#range_operators)。
```swift
let minutes = 60
@ -79,7 +78,7 @@ for tickMark in 0..<minutes {
}
```
一些用户可能在其 UI 中可能需要较少的刻度。他们可以每5分钟作为一个刻度。使用 `stride(from:to:by:)` 函数跳过不需要的标记。
一些用户可能在其 UI 中可能需要较少的刻度。他们可以每 5 分钟作为一个刻度。使用 `stride(from:to:by:)` 函数跳过不需要的标记。
```swift
let minuteInterval = 5
@ -98,16 +97,14 @@ for tickMark in stride(from: 3, through: hours, by: hourInterval) {
}
```
<a name="while_loops"></a>
## While 循环
## While 循环 {#while-loops}
`while` 循环会一直运行一段语句直到条件变成 `false`。这类循环适合使用在第一次迭代前迭代次数未知的情况下。Swift 提供两种 `while` 循环形式:
* `while` 循环,每次在循环开始时计算条件是否符合;
* `repeat-while` 循环,每次在循环结束时计算条件是否符合。
<a name="while"></a>
### While
### While {#while}
`while` 循环从计算一个条件开始。如果条件为 `true`,会重复运行一段语句,直到条件变为 `false`
@ -177,8 +174,7 @@ print("Game over!")
`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏要跑多久,只有在达成指定条件时循环才会结束。
<a name="repeat_while"></a>
### Repeat-While
### Repeat-While {#repeat-while}
`while` 循环的另外一种形式是 `repeat-while`,它和 `while` 的区别是在判断循环条件之前,先执行一次循环的代码块。然后重复循环直到条件为 `false`
@ -226,15 +222,13 @@ print("Game over!")
循环条件(`while square < finalSquare`)和 `while` 方式相同,但是只会在循环结束后进行计算。在这个游戏中,`repeat-while` 表现得比 `while` 循环更好。`repeat-while` 方式会在条件判断 `square` 没有超出后直接运行 `square += board[square]`,这种方式可以比起前面 `while` 循环的版本,可以省去数组越界的检查。
<a name="conditional_statement"></a>
## 条件语句
## 条件语句 {#conditional-statement}
根据特定的条件执行特定的代码通常是十分有用的。当错误发生时,你可能想运行额外的代码;或者,当值太大或太小时,向用户显示一条消息。要实现这些功能,你就需要使用*条件语句*。
Swift 提供两种类型的条件语句:`if` 语句和 `switch` 语句。通常,当条件较为简单且可能的情况很少时,使用 `if` 语句。而 `switch` 语句更适用于条件较复杂、有更多排列组合的时候。并且 `switch` 在需要用到模式匹配pattern-matching的情况下会更有用。
<a name="if"></a>
### If
### If {#if}
`if` 语句最简单的形式就是只包含一个条件,只有该条件为 `true` 时,才执行相关代码:
@ -243,7 +237,7 @@ var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
}
// 输出 "It's very cold. Consider wearing a scarf."
// 输出It's very cold. Consider wearing a scarf.
```
上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行 `if` 块后面的代码。
@ -257,7 +251,7 @@ if temperatureInFahrenheit <= 32 {
} else {
print("It's not that cold. Wear a t-shirt.")
}
// 输出 "It's not that cold. Wear a t-shirt."
// 输出It's not that cold. Wear a t-shirt.
```
显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾。因此,`else` 分支就被触发了。
@ -273,7 +267,7 @@ if temperatureInFahrenheit <= 32 {
} else {
print("It's not that cold. Wear a t-shirt.")
}
// 输出 "It's really warm. Don't forget to wear sunscreen."
// 输出It's really warm. Don't forget to wear sunscreen.
```
在上面的例子中,额外的 `if` 语句用于判断是不是特别热。而最后的 `else` 语句被保留了下来,用于打印既不冷也不热时的消息。
@ -291,8 +285,7 @@ if temperatureInFahrenheit <= 32 {
在这个例子中,由于既不冷也不热,所以不会触发 `if``else if` 分支,也就不会打印任何消息。
<a name="switch"></a>
### Switch
### Switch {#switch}
`switch` 语句会尝试把某个值与若干个模式pattern进行匹配。根据第一个匹配成功的模式`switch` 语句会执行对应的代码。当有可能的情况较多时,通常用 `switch` 语句替换 `if` 语句。
@ -328,13 +321,12 @@ case "z":
default:
print("Some other character")
}
// 输出 "The last letter of the alphabet"
// 输出The last letter of the alphabet
```
在这个例子中,第一个 case 分支用于匹配第一个英文字母 `a`,第二个 case 分支用于匹配最后一个字母 `z`。因为 `switch` 语句必须有一个 case 分支用于覆盖所有可能的字符,而不仅仅是所有的英文字母,所以 switch 语句使用 `default` 分支来匹配除了 `a``z` 外的所有值,这个分支保证了 swith 语句的完备性。
<a name="no_implicit_fallthrough"></a>
#### 不存在隐式的贯穿
#### 不存在隐式的贯穿 {#no-implicit-fallthrough}
与 C 和 Objective-C 中的 `switch` 语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止 `switch` 语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用 `break` 语句。这使得 `switch` 语句更安全、更易用,也避免了漏写 `break` 语句导致多个语言被执行的错误。
@ -368,7 +360,7 @@ case "a", "A":
default:
print("Not the letter A")
}
// 输出 "The letter A
// 输出The letter A
```
为了可读性,符合匹配可以写成多行形式,详情请参考[复合匹配](#compound_cases)
@ -377,8 +369,7 @@ default:
>
> 如果想要显式贯穿 case 分支,请使用 `fallthrough` 语句,详情请参考[贯穿](#fallthrough)。
<a name="interval_matching"></a>
#### 区间匹配
#### 区间匹配 {#interval-matching}
case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式:
@ -401,13 +392,12 @@ default:
naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
// 输出 "There are dozens of moons orbiting Saturn."
// 输出There are dozens of moons orbiting Saturn.
```
在上例中,`approximateCount` 在一个 `switch` 声明中被评估。每一个 `case` 都与之进行比较。因为 `approximateCount` 落在了 12 到 100 的区间,所以 `naturalCount` 等于 `"dozens of"` 值,并且此后的执行跳出了 `switch` 语句。
<a name="tuples"></a>
#### 元组
#### 元组 {#tuples}
我们可以使用元组在同一个 `switch` 语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(`_`)来匹配所有可能的值。
@ -427,7 +417,7 @@ case (-2...2, -2...2):
default:
print("\(somePoint) is outside of the box")
}
// 输出 "(1, 1) is inside the box"
// 输出(1, 1) is inside the box
```
![image](https://docs.swift.org/swift-book/_images/coordinateGraphSimple_2x.png)
@ -436,8 +426,7 @@ default:
不像 C 语言Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点 (0, 0)可以匹配所有_四个 case_。但是如果存在多个匹配那么只会执行第一个被匹配到的 case 分支。考虑点 (0, 0)会首先匹配 `case (0, 0)`,因此剩下的能够匹配的分支都会被忽视掉。
<a name="value_bindings"></a>
#### 值绑定Value Bindings
#### 值绑定Value Bindings {#value-bindings}
case 分支允许将匹配的值声明为临时常量或变量,并且在 case 分支体内使用 —— 这种行为被称为*值绑定*value binding因为匹配的值在 case 分支体内,与临时的常量或变量绑定。
@ -453,7 +442,7 @@ case (0, let y):
case let (x, y):
print("somewhere else at (\(x), \(y))")
}
// 输出 "on the x-axis with an x value of 2"
// 输出on the x-axis with an x value of 2
```
![image](https://docs.swift.org/swift-book/_images/coordinateGraphMedium_2x.png)
@ -466,8 +455,7 @@ case let (x, y):
请注意,这个 `switch` 语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)` 声明了一个可以匹配余下所有值的元组。这使得 `switch` 语句已经完备了,因此不需要再书写默认分支。
<a name="where"></a>
#### Where
#### Where {#where}
case 分支的模式可以使用 `where` 语句来判断额外的条件。
@ -483,7 +471,7 @@ case let (x, y) where x == -y:
case let (x, y):
print("(\(x), \(y)) is just some arbitrary point")
}
// 输出 "(1, -1) is on the line x == -y"
// 输出(1, -1) is on the line x == -y
```
![image](https://docs.swift.org/swift-book/_images/coordinateGraphComplex_2x.png)
@ -494,8 +482,7 @@ case let (x, y):
就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,`switch` 语句就已经完备了,因此不需要再书写默认分支。
<a name="compound_cases"></a>
#### 复合型 Cases
#### 复合型 Cases {#compound-cases}
当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个 `case` 后面,并且用逗号隔开。当 case 后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写:
@ -510,7 +497,7 @@ case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
default:
print("\(someCharacter) is not a vowel or a consonant")
}
// 输出 "e is a vowel"
// 输出e is a vowel
```
这个 `switch` 语句中的第一个 case匹配了英语中的五个小写元音字母。相似的第二个 case 匹配了英语中所有的小写辅音字母。最终,`default` 分支匹配了其它所有字符。
@ -525,14 +512,12 @@ case (let distance, 0), (0, let distance):
default:
print("Not on an axis")
}
// 输出 "On an axis, 9 from the origin"
// 输出“On an axis, 9 from the origin”
```
上面的 case 有两个模式:`(let distance, 0)` 匹配了在 x 轴上的值,`(0, let distance)` 匹配了在 y 轴上的值。两个模式都绑定了 `distance`,并且 `distance` 在两种模式下,都是整型——这意味着分支体内的代码,只要 case 匹配,都可以获取到 `distance`
上面的 case 有两个模式:`(let distance, 0)` 匹配了在 x 轴上的值,`(0, let distance)` 匹配了在 y 轴上的值。两个模式都绑定了 `distance`,并且 `distance` 在两种模式下,都是整型——这意味着分支体内的代码,只要 case 匹配,都可以获取到 `distance`
<a name="control_transfer_statements"></a>
## 控制转移语句
## 控制转移语句 {#control-transfer-statements}
控制转移语句改变你代码的执行顺序通过它可以实现代码的跳转。Swift 有五种控制转移语句:
@ -542,10 +527,9 @@ default:
- `return`
- `throw`
我们将会在下面讨论 `continue``break``fallthrough` 语句。`return` 语句将会在[函数](./06_Functions.html)章节讨论,`throw` 语句会在[错误抛出](./18_Error_Handling.html#throwing_errors)章节讨论。
我们将会在下面讨论 `continue``break``fallthrough` 语句。`return` 语句将会在[函数](./06_Functions.md)章节讨论,`throw` 语句会在[错误抛出](./18_Error_Handling.md#throwing_errors)章节讨论。
<a name="continue"></a>
### Continue
### Continue {#continue}
`continue` 语句告诉一个循环体立刻停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离开整个循环体。
@ -563,23 +547,20 @@ for character in puzzleInput {
}
}
print(puzzleOutput)
// 输出 "grtmndsthnklk"
// 输出grtmndsthnklk
```
在上面的代码中,只要匹配到元音字母或者空格字符,就调用 `continue` 语句,使本次循环结束,重新开始下次循环。这种行为使 `switch` 匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。
<a name="break"></a>
### Break
### Break {#break}
`break` 语句会立刻结束整个控制流的执行。`break` 可以在 `switch` 或循环语句中使用,用来提前结束 `switch` 或循环语句。
<a name="break_in_a_loop_statement"></a>
#### 循环语句中的 break
#### 循环语句中的 break {#break-in-a-loop-statement}
当在一个循环体中使用 `break` 时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环的代码被执行,也不会再有下次的循环产生。
<a name="break_in_a_switch_statement"></a>
#### Switch 语句中的 break
#### Switch 语句中的 break {#break-in-a-switch-statement}
当在一个 `switch` 代码块中使用 `break` 时,会立即中断该 `switch` 代码块的执行,并且跳转到表示 `switch` 代码块结束的大括号(`}`)后的第一行代码。
@ -611,7 +592,7 @@ if let integerValue = possibleIntegerValue {
} else {
print("An integer value could not be found for \(numberSymbol).")
}
// 输出 "The integer value of 三 is 3."
// 输出The integer value of 三 is 3.
```
这个例子检查 `numberSymbol` 是否是拉丁,阿拉伯,中文或者泰语中的 `1``4` 之一。如果被匹配到,该 `switch` 分支语句给 `Int?` 类型变量 `possibleIntegerValue` 设置一个整数值。
@ -620,8 +601,7 @@ if let integerValue = possibleIntegerValue {
在上面的例子中,想要把 `Character` 所有的的可能性都枚举出来是不现实的,所以使用 `default` 分支来包含所有上面没有匹配到字符的情况。由于这个 `default` 分支不需要执行任何动作,所以它只写了一条 `break` 语句。一旦落入到 `default` 分支中后,`break` 语句就完成了该分支的所有代码操作,代码继续向下,开始执行 `if let` 语句。
<a name="fallthrough"></a>
### 贯穿Fallthrough
### 贯穿Fallthrough {#fallthrough}
在 Swift 里,`switch` 语句不会从上一个 case 分支跳转到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个 `switch` 代码块完成了它的执行。相比之下C 语言要求你显式地插入 `break` 语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的 `switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
@ -638,7 +618,7 @@ default:
description += " an integer."
}
print(description)
// 输出 "The number 5 is a prime number, and also an integer."
// 输出The number 5 is a prime number, and also an integer.
```
这个例子定义了一个 `String` 类型的变量 `description` 并且给它设置了一个初始值。函数使用 `switch` 逻辑来判断 `integerToDescribe` 变量的值。当 `integerToDescribe` 的值属于列表中的质数之一时,该函数在 `description` 后添加一段文字,来表明这个数字是一个质数。然后它使用 `fallthrough` 关键字来“贯穿”到 `default` 分支中。`default` 分支在 `description` 的最后添加一段额外的文字,至此 `switch` 代码块执行完了。
@ -651,8 +631,7 @@ print(description)
>
> `fallthrough` 关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough` 简单地使代码继续连接到下一个 case 中的代码,这和 C 语言标准中的 `switch` 语句特性是一样的。
<a name="labeled_statements"></a>
### 带标签的语句
### 带标签的语句 {#labeled-statements}
在 Swift 中,你可以在循环体和条件语句中嵌套循环体和条件语句来创造复杂的控制流结构。并且,循环体和条件语句都可以使用 `break` 语句来提前结束整个代码块。因此,显式地指明 `break` 语句想要终止的是哪个循环体或者条件语句,会很有用。类似地,如果你有许多嵌套的循环体,显式指明 `continue` 语句想要影响哪一个循环体也会非常有用。
@ -723,39 +702,41 @@ print("Game over!")
>
> 同时请注意,当调用 `continue gameLoop` 去跳转到下一次循环迭代时,这里使用 `gameLoop` 标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以 `continue` 语句会影响到哪个循环体是没有歧义的。然而,`continue` 语句使用 `gameLoop` 标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的 `break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。
<a name="early_exit"></a>
## 提前退出
## 提前退出 {#early-exit}
`if` 语句一样,`guard` 的执行取决于一个表达式的布尔值。我们可以使用 `guard` 语句来要求条件必须为真时,以执行 `guard` 语句后的代码。不同于 `if` 语句,一个 `guard` 语句总是有一个 `else` 从句,如果条件不为真则执行 `else` 从句中的代码。
```swift
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("Hello \(name)")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
guard let name = person["name"] else {
return
}
print("Hello \(name)!")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
}
greet(["name": "John"])
// 输出 "Hello John!"
// 输出 "I hope the weather is nice near you."
greet(["name": "Jane", "location": "Cupertino"])
// 输出 "Hello Jane!"
// 输出 "I hope the weather is nice in Cupertino."
greet(person: ["name": "John"])
// 输出“Hello John!”
// 输出“I hope the weather is nice near you.”
greet(person: ["name": "Jane", "location": "Cupertino"])
// 输出“Hello Jane!”
// 输出“I hope the weather is nice in Cupertino.”
```
如果 `guard` 语句的条件被满足,则继续执行 `guard` 语句大括号后的代码。将变量或者常量的可选绑定作为 `guard` 语句的条件,都可以保护 `guard` 语句后面的代码。
如果条件不被满足,在 `else` 分支上的代码就会被执行。这个分支必须转移控制以退出 `guard` 语句出现的代码段。它可以用控制转移语句如 `return`,`break`,`continue` 或者 `throw` 做这件事,或者调用一个不返回的方法或函数,例如 `fatalError()`
如果条件不被满足,在 `else` 分支上的代码就会被执行。这个分支必须转移控制以退出 `guard` 语句出现的代码段。它可以用控制转移语句如 `return``break``continue` 或者 `throw` 做这件事,或者调用一个不返回的方法或函数,例如 `fatalError()`
相比于可以实现同样功能的 `if` 语句,按需使用 `guard` 语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在 `else` 块中,它可以使你在紧邻条件判断的地方,处理违规的情况。
<a name="checking_api_availability"></a>
## 检测 API 可用性
## 检测 API 可用性 {#checking-api-availability}
Swift 内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的 API。
@ -776,7 +757,7 @@ if #available(iOS 10, macOS 10.12, *) {
在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是 `iOS``macOS``watchOS``tvOS`——请访问[声明属性](../chapter3/06_Attributes.html)来获取完整列表。除了指定像 iOS 8 或 macOS 10.10 的大版本号,也可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的小版本号。
```swift
if #available(platform name version, ..., *) {
if #available(平台名称 版本号, ..., *) {
APIs 可用,语句将执行
} else {
APIs 不可用,语句将不执行

View File

@ -6,8 +6,7 @@ Swift 统一的函数语法非常的灵活,可以用来表示任何函数,
在 Swift 中,每个函数都有一个由函数的参数值类型和返回值类型组成的类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。
<a name="Defining_and_Calling_Functions"></a>
## 函数的定义与调用
## 函数的定义与调用 {#Defining-and-Calling-Functions}
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入,称为*参数*,也可以定义某种类型的值作为函数执行结束时的输出,称为*返回类型*。
@ -28,16 +27,16 @@ func greet(person: String) -> String {
```swift
print(greet(person: "Anna"))
// 打印 "Hello, Anna!"
// 打印Hello, Anna!
print(greet(person: "Brian"))
// 打印 "Hello, Brian!"
// 打印Hello, Brian!
```
调用 `greet(person:)` 函数时,在圆括号中传给它一个 `String` 类型的实参,例如 `greet(person: "Anna")`。正如上面所示,因为这个函数返回一个 `String` 类型的值,所以 `greet` 可以被包含在 `print(_:separator:terminator:)` 的调用中,用来输出这个函数的返回值。
> 注意
>
> `print(_:separator:terminator:)` 函数的第一个参数并没有设置一个标签,而其他的参数因为已经有了默认值,因此是可选的。关于这些函数语法上的变化详见下方关于 函数参数标签和参数名 以及 默认参数值。
> `print(_:separator:terminator:)` 函数的第一个参数并没有设置一个标签,而其他的参数因为已经有了默认值,因此是可选的。关于这些函数语法上的变化详见下方关于 函数参数标签和参数名以及默认参数值。
`greet(person:)` 的函数体中,先定义了一个新的名为 `greeting``String` 常量,同时,把对 `personName` 的问候消息赋值给了 `greeting` 。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 被调用,该函数结束它的执行并返回 `greeting` 的当前值。
@ -50,16 +49,14 @@ func greetAgain(person: String) -> String {
return "Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// 打印 "Hello again, Anna!"
// 打印Hello again, Anna!
```
<a name="Function_Parameters_and_Return_Values"></a>
## 函数参数与返回值
## 函数参数与返回值 {#Function-Parameters-and-Return-Values}
函数参数与返回值在 Swift 中非常的灵活。你可以定义任何类型的函数,包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。
<a name="functions_without_parameters"></a>
### 无参数函数
### 无参数函数 {#functions-without-parameters}
函数可以没有参数。下面这个函数就是一个无参数函数,当被调用时,它返回固定的 `String` 消息:
@ -68,13 +65,12 @@ func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// 打印 "hello, world"
// 打印hello, world
```
尽管这个函数没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。
<a name="functions_with_multiple_parameters"></a>
### 多参数函数
### 多参数函数 {#functions-with-multiple-parameters}
函数可以有多种输入参数,这些参数被包含在函数的括号之中,以逗号分隔。
@ -89,13 +85,12 @@ func greet(person: String, alreadyGreeted: Bool) -> String {
}
}
print(greet(person: "Tim", alreadyGreeted: true))
// 打印 "Hello again, Tim!"
// 打印Hello again, Tim!
```
你可以通过在括号内使用逗号分隔来传递一个 `String` 参数值和一个标识为 `alreadyGreeted``Bool` 值,来调用 `greet(person:alreadyGreeted:)` 函数。注意这个函数和上面 `greet(person:)` 是不同的。虽然它们都有着同样的名字 `greet`,但是 `greet(person:alreadyGreeted:)` 函数需要两个参数,而 `greet(person:)` 只需要一个参数。
<a name="functions_without_return_values"></a>
### 无返回值函数
### 无返回值函数 {#functions-without-return-values}
函数可以没有返回值。下面是 `greet(person:)` 函数的另一个版本,这个函数直接打印一个 `String` 值,而不是返回它:
@ -104,7 +99,7 @@ func greet(person: String) {
print("Hello, \(person)!")
}
greet(person: "Dave")
// 打印 "Hello, Dave!"
// 打印Hello, Dave!
```
因为这个函数不需要返回值,所以这个函数的定义中没有返回箭头(->)和返回类型。
@ -124,9 +119,9 @@ func printWithoutCounting(string: String) {
let _ = printAndCount(string: string)
}
printAndCount(string: "hello, world")
// 打印 "hello, world" 并且返回值 12
// 打印hello, world”,并且返回值 12
printWithoutCounting(string: "hello, world")
// 打印 "hello, world" 但是没有返回任何值
// 打印hello, world”,但是没有返回任何值
```
第一个函数 `printAndCount(string:)`,输出一个字符串并返回 `Int` 类型的字符数。第二个函数 `printWithoutCounting(string:)` 调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。
@ -135,8 +130,7 @@ printWithoutCounting(string: "hello, world")
>
> 返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,将导致编译时错误。
<a name="functions_with_multiple_return_values"></a>
### 多重返回值函数
### 多重返回值函数 {#functions-with-multiple-return-values}
你可以用元组tuple类型让多个值作为一个复合值从函数中返回。
@ -166,13 +160,12 @@ func minMax(array: [Int]) -> (min: Int, max: Int) {
```swift
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
// 打印 "min is -6 and max is 109"
// 打印min is -6 and max is 109
```
需要注意的是,元组的成员不需要在元组从函数中返回时命名,因为它们的名字已经在函数返回类型中指定了。
<a name="optional_tuple_return_types"></a>
### 可选元组返回类型
### 可选元组返回类型 {#optional-tuple-return-types}
如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用*可选的* 元组返回类型反映整个元组可以是 `nil` 的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 `(Int, Int)?``(String, Int, Bool)?`
@ -206,11 +199,10 @@ func minMax(array: [Int]) -> (min: Int, max: Int)? {
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min) and max is \(bounds.max)")
}
// 打印 "min is -6 and max is 109"
// 打印min is -6 and max is 109
```
<a name="Function_Argument_Labels_and_Parameter_Names"></a>
## 函数参数标签和参数名称
## 函数参数标签和参数名称 {#Function-Argument-Labels-and-Parameter-Names}
每个函数参数都有一个*参数标签argument label*以及一个*参数名称parameter name*。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。
@ -223,8 +215,7 @@ someFunction(firstParameterName: 1, secondParameterName: 2)
所有的参数都必须有一个独一无二的名字。虽然多个参数拥有同样的参数标签是可能的,但是一个唯一的函数标签能够使你的代码更具可读性。
<a name="specifying_argument_labels"></a>
### 指定参数标签
### 指定参数标签 {#specifying-argument-labels}
你可以在参数名称前指定它的参数标签,中间以空格分隔:
@ -241,13 +232,12 @@ func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 打印 "Hello Bill! Glad you could visit from Cupertino."
// 打印Hello Bill! Glad you could visit from Cupertino.
```
参数标签的使用能够让一个函数在调用时更有表达力,更类似自然语言,并且仍保持了函数内部的可读性以及清晰的意图。
<a name="omitting_argument_labels"></a>
### 忽略参数标签
### 忽略参数标签 {#omitting-argument-labels}
如果你不希望为某个参数添加一个标签,可以使用一个下划线(`_`)来代替一个明确的参数标签。
@ -260,8 +250,7 @@ someFunction(1, secondParameterName: 2)
如果一个参数有一个标签,那么在调用的时候必须使用标签来标记这个参数。
<a name="default_parameter_values"></a>
### 默认参数值
### 默认参数值 {#default-parameter-values}
你可以在函数体中通过给参数赋值来为任意一个参数定义*默认值Deafult Value*。当默认值被定义后,调用这个函数时可以忽略这个参数。
@ -275,8 +264,7 @@ someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12
将不带有默认值的参数放在函数参数列表的最前。一般来说,没有默认值的参数更加的重要,将不带默认值的参数放在最前保证在函数调用时,非默认参数的顺序是一致的,同时也使得相同的函数在不同情况下调用时显得更为清晰。
<a name="variadic_parameters"></a>
### 可变参数
### 可变参数 {#variadic-parameters}
一个*可变参数variadic parameter*可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(`...`)的方式来定义可变参数。
@ -302,8 +290,7 @@ arithmeticMean(3, 8.25, 18.75)
>
> 一个函数最多只能拥有一个可变参数。
<a name="in_out_parameters"></a>
### 输入输出参数
### 输入输出参数 {#in-out-parameters}
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为*输入输出参数In-Out Parameters*。
@ -334,7 +321,7 @@ 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
```
从上面这个例子中,我们可以看到 `someInt``anotherInt` 的原始值在 `swapTwoInts(_:_:)` 函数中被修改,尽管它们的定义在函数体外。
@ -343,8 +330,7 @@ print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
>
> 输入输出参数和返回值是不一样的。上面的 `swapTwoInts` 函数并没有定义任何返回值,但仍然修改了 `someInt` 和 `anotherInt` 的值。输入输出参数是函数对函数体外产生影响的另一种方式。
<a name="Function_Types"></a>
## 函数类型
## 函数类型 {#Function-Types}
每个函数都有种特定的*函数类型*,函数的类型由函数的参数类型和返回类型组成。
@ -375,8 +361,7 @@ func printHelloWorld() {
这个函数的类型是:`() -> Void`,或者叫“没有参数,并返回 `Void` 类型的函数”。
<a name="using_function_types"></a>
### 使用函数类型
### 使用函数类型 {#using-function-types}
在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它:
@ -412,8 +397,7 @@ let anotherMathFunction = addTwoInts
// anotherMathFunction 被推断为 (Int, Int) -> Int 类型
```
<a name="function_types_as_parameter_types"></a>
### 函数类型作为参数类型
### 函数类型作为参数类型 {#function-types-as-parameter-types}
你可以用 `(Int, Int) -> Int` 这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现留给函数的调用者来提供。
@ -424,7 +408,7 @@ func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// 打印 "Result: 8"
// 打印Result: 8
```
这个例子定义了 `printMathResult(_:_:_:)` 函数,它有三个参数:第一个参数叫 `mathFunction`,类型是 `(Int, Int) -> Int`,你可以传入任何这种类型的函数;第二个和第三个参数叫 `a``b`,它们的类型都是 `Int`,这两个值作为已给出的函数的输入值。
@ -433,8 +417,7 @@ printMathResult(addTwoInts, 3, 5)
`printMathResult(_:_:_:)` 函数的作用就是输出另一个适当类型的数学函数的调用结果。它不关心传入函数是如何实现的,只关心传入的函数是不是一个正确的类型。这使得 `printMathResult(_:_:_:)` 能以一种类型安全type-safe的方式将一部分功能转给调用者实现。
<a name="function_types_as_return_types"></a>
### 函数类型作为返回类型
### 函数类型作为返回类型 {#function-types-as-return-types}
你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(->)后写一个完整的函数类型。
@ -483,8 +466,7 @@ print("zero!")
// zero!
```
<a name="Nested_Functions"></a>
## 嵌套函数
## 嵌套函数 {#Nested-Functions}
到目前为止本章中你所见到的所有函数都叫*全局函数global functions*,它们定义在全局域中。你也可以把函数定义在别的函数体中,称作 *嵌套函数nested functions*

View File

@ -21,15 +21,13 @@ Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进
* 参数名称缩写
* 尾随闭包语法
<a name="closure_expressions"></a>
## 闭包表达式
## 闭包表达式 {#closure-expressions}
[嵌套函数](./06_Functions.md#Nested_Functions)作为复杂函数的一部分时,它自包含代码块式的定义和命名形式在使用上带来了方便。当然,编写未完整声明和没有函数名的类函数结构代码是很有用的,尤其是在编码中涉及到函数作为参数的那些方法时。
*闭包表达式*是一种构建内联闭包的方式,它的语法简洁。在保证不丢失它语法清晰明了的同时,闭包表达式提供了几种优化的语法简写形式。下面通过对 `sorted(by:)` 这一个案例的多次迭代改进来展示这个过程,每次迭代都使用了更加简明的方式描述了相同功能。。
<a name="the_sorted_function"></a>
### 排序方法
### 排序方法 {#the-sorted-function}
Swift 标准库提供了名为 `sorted(by:)` 的方法,它会基于你提供的排序闭包表达式的判断结果对数组中的值(类型确定)进行排序。一旦它完成排序过程,`sorted(by:)` 方法会返回一个与旧数组类型大小相同类型的新数组,该数组的元素有着正确的排序顺序。原数组不会被 `sorted(by:)` 方法修改。
@ -57,8 +55,7 @@ var reversedNames = names.sorted(by: backward)
然而,以这种方式来编写一个实际上很简单的表达式(`a > b`),确实太过繁琐了。对于这个例子来说,利用闭包表达式语法可以更好地构造一个内联排序闭包。
<a name="closure_expression_syntax"></a>
### 闭包表达式语法
### 闭包表达式语法 {#closure-expression-syntax}
闭包表达式语法有如下的一般形式:
@ -90,8 +87,7 @@ reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1
该例中 `sorted(by:)` 方法的整体调用保持不变,一对圆括号仍然包裹住了方法的整个参数。然而,参数现在变成了内联闭包。
<a name="inferring_type_from_context"></a>
### 根据上下文推断类型
### 根据上下文推断类型 {#inferring-type-from-context}
因为排序闭包函数是作为 `sorted(by:)` 方法的参数传入的Swift 可以推断其参数和返回值的类型。`sorted(by:)` 方法被一个字符串数组调用,因此其参数必须是 `(String, String) -> Bool` 类型的函数。这意味着 `(String, String)``Bool` 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(`->`)和围绕在参数周围的括号也可以被省略:
@ -103,9 +99,7 @@ reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包。而在 `sorted(by:)` 方法这个例子里,显然闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。
<a name="implicit_returns_from_single_expression_closures"></a>
### 单表达式闭包的隐式返回
### 单表达式闭包的隐式返回 {#implicit-returns-from-single-expression-closures}
单行表达式闭包可以通过省略 `return` 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
@ -115,8 +109,7 @@ reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
在这个例子中,`sorted(by:)` 方法的参数类型明确了闭包必须返回一个 `Bool` 类型值。因为闭包函数体只包含了一个单一表达式(`s1 > s2`),该表达式返回 `Bool` 类型值,因此这里没有歧义,`return` 关键字可以省略。
<a name="shorthand_argument_names"></a>
### 参数名称缩写
### 参数名称缩写 {#shorthand-argument-names}
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 `$0``$1``$2` 来顺序调用闭包的参数,以此类推。
@ -128,8 +121,7 @@ reversedNames = names.sorted(by: { $0 > $1 } )
在这个例子中,`$0``$1` 表示闭包中第一个和第二个 `String` 类型的参数。
<a name="operator_methods"></a>
### 运算符方法
### 运算符方法 {#operator-methods}
实际上还有一种更*简短的*方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此你可以简单地传递一个大于号Swift 可以自动推断找到系统自带的那个字符串函数的实现:
@ -137,10 +129,9 @@ reversedNames = names.sorted(by: { $0 > $1 } )
reversedNames = names.sorted(by: >)
```
更多关于运算符方法的内容请查看[运算符方法](./26_Advanced_Operators.html#operator_methods)。
更多关于运算符方法的内容请查看[运算符方法](./26_Advanced_Operators.md#operator_methods)。
<a name="trailing_closures"></a>
## 尾随闭包
## 尾随闭包 {#trailing-closures}
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,将这个闭包替换成为尾随闭包的形式很有用。尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
@ -223,8 +214,7 @@ let strings = numbers.map {
在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 `map(_:)` 方法的括号内。
<a name="capturing_values"></a>
## 值捕获
## 值捕获 {#capturing-values}
闭包可以在其被定义的上下文中*捕获*常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
@ -243,7 +233,7 @@ func makeIncrementer(forIncrement amount: Int) -> () -> Int {
}
```
`makeIncrementer` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.html#function_types_as_return_types)。
`makeIncrementer` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.md#function_types_as_return_types)。
`makeIncrementer(forIncrement:)` 函数定义了一个初始值为 `0` 的整型变量 `runningTotal`,用来存储当前总计数值。该值为 `incrementer` 的返回值。
@ -300,10 +290,9 @@ incrementByTen()
> 注意
>
> 如果你将闭包赋值给一个类实例的属性并且该闭包通过访问该实例或其成员而捕获了该实例你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考[闭包引起的循环强引用](./23_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)。
> 如果你将闭包赋值给一个类实例的属性并且该闭包通过访问该实例或其成员而捕获了该实例你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考[闭包引起的循环强引用](./23_Automatic_Reference_Counting.md#strong_reference_cycles_for_closures)。
<a name="closures_are_reference_types"></a>
## 闭包是引用类型
## 闭包是引用类型 {#closures-are-reference-types}
上面的例子中,`incrementBySeven``incrementByTen` 都是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是*引用类型*。
@ -317,8 +306,7 @@ alsoIncrementByTen()
// 返回的值为50
```
<a name="escaping_closures"></a>
## 逃逸闭包
## 逃逸闭包 {#escaping-closures}
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中*逃逸*。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 `@escaping`,用来指明这个闭包是允许“逃逸”出这个函数的。
@ -351,15 +339,14 @@ class SomeClass {
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出 "200"
// 打印出200
completionHandlers.first?()
print(instance.x)
// 打印出 "100"
// 打印出100
```
<a name="autoclosures"></a>
## 自动闭包
## 自动闭包 {#autoclosures}
*自动闭包*是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
@ -370,16 +357,16 @@ print(instance.x)
```swift
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 打印出 "5"
// 打印出“5”
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"
// 打印出“5”
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"
// 打印出“4”
```
尽管在闭包的代码中,`customersInLine` 的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。请注意,`customerProvider` 的类型不是 `String`,而是 `() -> String`,一个没有参数且返回值为 `String` 的函数。
@ -392,7 +379,7 @@ func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出 "Now serving Alex!"
// 打印出Now serving Alex!
```
上面的 `serve(customer:)` 函数接受一个返回顾客名字的显式的闭包。下面这个版本的 `serve(customer:)` 完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为 `@autoclosure` 来接收一个自动闭包。现在你可以将该函数当作接受 `String` 类型参数(而非闭包)的函数来调用。`customerProvider` 参数将自动转化为一个闭包,因为该参数被标记了 `@autoclosure` 特性。
@ -403,7 +390,7 @@ func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 打印 "Now serving Ewa!"
// 打印Now serving Ewa!
```
> 注意
@ -422,12 +409,12 @@ collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// 打印 "Collected 2 closures."
// 打印Collected 2 closures.
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// 打印 "Now serving Barry!"
// 打印 "Now serving Daniella!"
// 打印Now serving Barry!
// 打印Now serving Daniella!
```
在上面的代码中,`collectCustomerProviders(_:)` 函数并没有调用传入的 `customerProvider` 闭包,而是将闭包追加到了 `customerProviders` 数组中。这个数组定义在函数作用域范围外,这意味着数组内的闭包能够在函数返回之后被调用。因此,`customerProvider` 参数必须允许“逃逸”出函数作用域。

View File

@ -2,16 +2,15 @@
*枚举*为一组相关的值定义了一个共同的类型,使你可以在你的代码中以类型安全的方式来使用这些值。
如果你熟悉 C 语言,你会知道在 C 语言中枚举会为一组整型值分配相关联的名称。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为原始值),则该值的类型可以是字符串、字符,或是一个整型值或浮点数。
如果你熟悉 C 语言,你会知道在 C 语言中枚举会为一组整型值分配相关联的名称。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为原始值),则该值的类型可以是字符串、字符,或是一个整型值或浮点数。
此外,枚举成员可以指定*任意*类型的关联值存储到枚举成员中就像其他语言中的联合体unions和变体variants。你可以在一个枚举中定义一组相关的枚举成员每一个枚举成员都可以有适当类型的关联值。
在 Swift 中枚举类型是一等first-class类型。它们采用了很多在传统上只被类class所支持的特性例如计算属性computed properties用于提供枚举值的附加信息实例方法instance methods用于提供和枚举值相关联的功能。枚举也可以定义构造函数initializers来提供一个初始值可以在原始实现的基础上扩展它们的功能还可以遵循协议protocols来提供标准的功能。
想了解更多相关信息,请参见[属性](./10_Properties.html)[方法](./11_Methods.html)[构造过程](./14_Initialization.html)[扩展](./20_Extensions.html)和[协议](./21_Protocols.html)。
想了解更多相关信息,请参见[属性](./10_Properties.md)[方法](./11_Methods.md)[构造过程](./14_Initialization.md)[扩展](./20_Extensions.md)和[协议](./21_Protocols.md)。
<a name="enumeration_syntax"></a>
## 枚举语法
## 枚举语法 {#enumeration-syntax}
使用 `enum` 关键词来创建枚举并且把它们的整个定义放在一对大括号内:
@ -46,7 +45,7 @@ enum Planet {
}
```
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如 `CompassPoint``Planet`应该以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于:
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如 `CompassPoint``Planet`)以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于:
```swift
var directionToHead = CompassPoint.west
@ -60,8 +59,7 @@ directionToHead = .east
`directionToHead` 的类型已知时,再次为其赋值可以省略枚举类型名。在使用具有显式类型的枚举值时,这种写法让代码具有更好的可读性。
<a name="matching_enumeration_values_with_a_switch_statement"></a>
## 使用 Switch 语句匹配枚举值
## 使用 Switch 语句匹配枚举值 {#matching-enumeration-values-with-a-switch-statement}
你可以使用 `switch` 语句匹配单个枚举值:
@ -77,7 +75,7 @@ case .east:
case .west:
print("Where the skies are blue")
}
// 打印 "Watch out for penguins”
// 打印Watch out for penguins”
```
你可以这样理解这段代码:
@ -86,7 +84,7 @@ case .west:
……以此类推。
正如在[控制流](./05_Control_Flow.html)中介绍的那样,在判断一个枚举类型的值时,`switch` 语句必须穷举所有情况。如果忽略了 `.west` 这种情况,上面那段代码将无法通过编译,因为它没有考虑到 `CompassPoint` 的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
正如在[控制流](./05_Control_Flow.md)中介绍的那样,在判断一个枚举类型的值时,`switch` 语句必须穷举所有情况。如果忽略了 `.west` 这种情况,上面那段代码将无法通过编译,因为它没有考虑到 `CompassPoint` 的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
当不需要匹配每个枚举成员的时候,你可以提供一个 `default` 分支来涵盖所有未明确处理的枚举成员:
@ -98,11 +96,10 @@ case .earth:
default:
print("Not a safe place for humans")
}
// 打印 "Mostly harmless”
// 打印Mostly harmless”
```
<a name="iterating over enumeration cases"></a>
## 枚举成员的遍历
## 枚举成员的遍历 {#iterating-over-enumeration-cases}
在一些情况下,你会需要得到一个包含枚举所有成员的集合。可以通过如下代码实现:
@ -114,7 +111,7 @@ enum Beverage: CaseIterable {
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// 打印 "3 beverages available"
// 打印3 beverages available
```
在前面的例子中,通过 `Beverage.allCases` 可以访问到包含 `Beverage` 枚举所有成员的集合。`allCases` 的使用方法和其它一般集合一样——集合中的元素是枚举类型的实例,所以在上面的情况中,这些元素是 `Beverage` 值。在前面的例子中,统计了总共有多少个枚举成员。而在下面的例子中,则使用 `for` 循环来遍历所有枚举成员。
@ -128,16 +125,15 @@ for beverage in Beverage.allCases {
// juice
```
在前面的例子中,使用的语法表明这个枚举遵循 [CaseIterable](https://developer.apple.com/documentation/swift/caseiterable) 协议。想了解 protocols 相关信息,请参见[协议](./21_Protocols.html)。
在前面的例子中,使用的语法表明这个枚举遵循 [CaseIterable](https://developer.apple.com/documentation/swift/caseiterable) 协议。想了解 protocols 相关信息,请参见[协议](./21_Protocols.md)。
<a name="associated_values"></a>
## 关联值
## 关联值 {#associated-values}
枚举语法那一小节的例子演示了如何定义和分类枚举的成员。你可以为 `Planet.earth` 设置一个常量或者变量,并在赋值之后查看这个值。然而,有时候能够把其他类型的*关联值*和成员值一起存储起来会很有用。这能让你连同成员值一起存储额外的自定义信息,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。
枚举语法那一小节的例子演示了如何定义和分类枚举的成员。你可以为 `Planet.earth` 设置一个常量或者变量,并在赋值之后查看这个值。然而,有时候把其他类型的和成员值一起存储起来会很有用。这额外的信息称为*关联值*,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。
你可以定义 Swift 枚举来存储任意类型的关联值如果需要的话每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合discriminated unions标签联合tagged unions或者变体variants相似。
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用 `0``9` 的数字的 UPC 格式的一维条形码。每一个条形码都有一个代表数字系统的数字,该数字后接五位代表厂商代码的数字,接下来是五位代表“产品代码”的数字。最后一个数字是检查位,用来验证代码是否被正确扫描:
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用 `0``9` 的数字的 UPC 格式的一维条形码。每一个条形码都有一个代表数字系统的数字,该数字后接五位代表厂商代码的数字,接下来是五位代表“产品代码”的数字。最后一个数字是检查位,用来验证代码是否被正确扫描:
<img width="252" height="120" alt="" src="https://docs.swift.org/swift-book/_images/barcode_UPC_2x.png">
@ -162,7 +158,7 @@ enum Barcode {
这个定义不提供任何 `Int``String` 类型的关联值,它只是定义了,当 `Barcode` 常量和变量等于 `Barcode.upc``Barcode.qrCode` 时,可以存储的关联值的类型。
然后可以使用任意一种条形码类型创建新的条形码,例如:
然后可以使用任意一种条形码类型创建新的条形码,例如:
```swift
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
@ -178,7 +174,7 @@ productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
这时,原始的 `Barcode.upc` 和其整数关联值被新的 `Barcode.qrCode` 和其字符串关联值所替代。`Barcode` 类型的常量和变量可以存储一个 `.upc` 或者一个 `.qrCode`(连同它们的关联值),但是在同一时间只能存储这两个值中的一个。
像先前那样,可以使用一个 switch 语句来检查不同的条形码类型。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在 `switch` 的 case 分支代码中提取每个关联值作为一个常量(用 `let` 前缀)或者作为一个变量(用 `var` 前缀)来使用:
可以使用一个 switch 语句来检查不同的条形码类型,和之前使用 Switch 语句来匹配枚举值的例子一样。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在 `switch` 的 case 分支代码中提取每个关联值作为一个常量(用 `let` 前缀)或者作为一个变量(用 `var` 前缀)来使用:
```swift
switch productBarcode {
@ -187,7 +183,7 @@ case .upc(let numberSystem, let manufacturer, let product, let check):
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
// 打印 "QR code: ABCDEFGHIJKLMNOP."
// 打印QR code: ABCDEFGHIJKLMNOP.
```
如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个 `let` 或者 `var`
@ -199,11 +195,10 @@ case let .upc(numberSystem, manufacturer, product, check):
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// 打印 "QR code: ABCDEFGHIJKLMNOP."
// 打印QR code: ABCDEFGHIJKLMNOP.
```
<a name="raw_values"></a>
## 原始值
## 原始值 {#raw-values}
在[关联值](#associated_values)小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。
@ -217,7 +212,7 @@ enum ASCIIControlCharacter: Character {
}
```
枚举类型 `ASCIIControlCharacter` 的原始值类型被定义为 `Character`,并设置了一些比较常见的 ASCII 控制字符。`Character` 的描述详见[字符串和字符](./03_Strings_and_Characters.html)部分。
枚举类型 `ASCIIControlCharacter` 的原始值类型被定义为 `Character`,并设置了一些比较常见的 ASCII 控制字符。`Character` 的描述详见[字符串和字符](./03_Strings_and_Characters.md)部分。
原始值可以是字符串、字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。
@ -225,8 +220,7 @@ enum ASCIIControlCharacter: Character {
>
> 原始值和关联值是不同的。原始值是在定义枚举时被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终不变。关联值是创建一个基于枚举成员的常量或变量时才设置的值,枚举成员的关联值可以变化。
<a name="implicitly_assigned_raw_values"></a>
### 原始值的隐式赋值
### 原始值的隐式赋值 {#implicitly-assigned-raw-values}
在使用原始值为整数或者字符串类型的枚举时不需要显式地为每一个枚举成员设置原始值Swift 将会自动为你赋值。
@ -264,8 +258,7 @@ let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection 值为 "west"
```
<a name="initializing_from_a_raw_value"></a>
### 使用原始值初始化枚举实例
### 使用原始值初始化枚举实例 {#initializing-from-a-raw-value}
如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做 `rawValue` 的参数,参数类型即为原始值类型,返回值则是枚举成员或 `nil`。你可以使用这个初始化方法来创建一个新的枚举实例。
@ -296,13 +289,12 @@ if let somePlanet = Planet(rawValue: positionToFind) {
} else {
print("There isn't a planet at position \(positionToFind)")
}
// 打印 "There isn't a planet at position 11"
// 打印There isn't a planet at position 11
```
这个例子使用了可选绑定optional binding试图通过原始值 `11` 来访问一个行星。`if let somePlanet = Planet(rawValue: 11)` 语句创建了一个可选 `Planet`,如果可选 `Planet` 的值存在,就会赋值给 `somePlanet`。在这个例子中,无法检索到位置为 `11` 的行星,所以 `else` 分支被执行。
<a name="recursive_enumerations"></a>
## 递归枚举
## 递归枚举 {#recursive-enumerations}
*递归枚举*是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上 `indirect` 来表示该成员可递归。
@ -350,7 +342,7 @@ func evaluate(_ expression: ArithmeticExpression) -> Int {
}
print(evaluate(product))
// 打印 "18"
// 打印18
```
该函数如果遇到纯数字,就直接返回该数字的值。如果遇到的是加法或乘法运算,则分别计算左边表达式和右边表达式的值,然后相加或相乘。

View File

@ -8,8 +8,7 @@
>
> 通常一个*类*的实例被称为*对象*。然而相比其他语言Swift 中结构体和类的功能更加相近,本章中所讨论的大部分功能都可以用在结构体或者类上。因此,这里会使用*实例*这个更通用的术语。
<a name="comparing_structures_and_classes"></a>
## 结构体和类对比
## 结构体和类对比 {#comparing-structures-and-classes}
Swift 中结构体和类有很多共同点。两者都可以:
@ -20,7 +19,7 @@ Swift 中结构体和类有很多共同点。两者都可以:
* 通过扩展以增加默认实现之外的功能
* 遵循协议以提供某种标准功能
更多信息请参见 [属性](./10_Properties.html)、[方法](./11_Methods.html)、[下标](./12_Subscripts.html)、[构造过程](./14_Initialization.html)、[扩展](./20_Extensions.html) 和 [协议](./21_Protocols.html)。
更多信息请参见 [属性](./10_Properties.md)、[方法](./11_Methods.md)、[下标](./12_Subscripts.md)、[构造过程](./14_Initialization.md)、[扩展](./20_Extensions.md) 和 [协议](./21_Protocols.md)。
与结构体相比,类还有如下的附加功能:
@ -29,12 +28,11 @@ Swift 中结构体和类有很多共同点。两者都可以:
* 析构器允许一个类实例释放任何其所被分配的资源
* 引用计数允许对一个类的多次引用
更多信息请参见 [继承](./13_Inheritance.html)、[类型转换](./18_Type_Casting.html)、[析构过程](./15_Deinitialization.html) 和 [自动引用计数](./23_Automatic_Reference_Counting.html)。
更多信息请参见 [继承](./13_Inheritance.md)、[类型转换](./18_Type_Casting.md)、[析构过程](./15_Deinitialization.md) 和 [自动引用计数](./23_Automatic_Reference_Counting.md)。
类支持的附加功能是以增加复杂性为代价的。作为一般准则,优先使用结构体,因为它们更容易理解,仅在适当或必要时才使用类。实际上,这意味着你的大多数自定义数据类型都会是结构体和枚举。更多详细的比较参见 [在结构和类之间进行选择](https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes)。
<a name="definition_syntax"></a>
### 类型定义的语法
### 类型定义的语法 {#definition-syntax}
结构体和类有着相似的定义方式。你通过 `struct` 关键字引入结构体,通过 `class` 关键字引入类,并将它们的具体定义放在一对大括号中:
@ -70,8 +68,7 @@ class VideoMode {
在上面的示例还定义了一个名为 `VideoMode` 的类,用来描述视频显示器的某个特定视频模式。这个类包含了四个可变的存储属性。第一个, `resolution`,被初始化为一个新的 `Resolution` 结构体的实例,属性类型被推断为 `Resolution`。新 `VideoMode` 实例同时还会初始化其它三个属性,它们分别是初始值为 `false``interlaced`(意为“非隔行视频”),初始值为 `0.0``frameRate`,以及值为可选 `String``name`。因为 `name` 是一个可选类型,它会被自动赋予一个默认值 `nil`,意为“没有 `name` 值”。
<a name="class_and_structure_instances"></a>
### 结构体和类的实例
### 结构体和类的实例 {#class-and-structure-instances}
`Resolution` 结构体和 `VideoMode` 类的定义仅描述了什么是 `Resolution``VideoMode`。它们并没有描述一个特定的分辨率resolution或者视频模式video mode。为此你需要创建结构体或者类的一个实例。
@ -82,10 +79,9 @@ let someResolution = Resolution()
let someVideoMode = VideoMode()
```
结构体和类都使用构造器语法来创建新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如 `Resolution()``VideoMode()`。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。[构造过程](./14_Initialization.html) 章节会对类和结构体的初始化进行更详细的讨论。
结构体和类都使用构造器语法来创建新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如 `Resolution()``VideoMode()`。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。[构造过程](./14_Initialization.md) 章节会对类和结构体的初始化进行更详细的讨论。
<a name="accessing_properties"></a>
### 属性访问
### 属性访问 {#accessing-properties}
你可以通过使用*点语法*访问实例的属性。其语法规则是,实例名后面紧跟属性名,两者以点号(`.`)分隔,不带空格:
@ -111,8 +107,7 @@ print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now 1280"
```
<a name="memberwise_initializers_for_structure_types"></a>
### 结构体类型的成员逐一构造器
### 结构体类型的成员逐一构造器 {#memberwise-initializers-for-structure-types}
所有结构体都有一个自动生成的*成员逐一构造器*,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:
@ -120,10 +115,9 @@ print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
let vga = Resolution(width: 640, height: 480)
```
与结构体不同,类实例没有默认的成员逐一构造器。[构造过程](./14_Initialization.html) 章节会对构造器进行更详细的讨论。
与结构体不同,类实例没有默认的成员逐一构造器。[构造过程](./14_Initialization.md) 章节会对构造器进行更详细的讨论。
<a name="structures_and_enumerations_are_value_types"></a>
## 结构体和枚举是值类型
## 结构体和枚举是值类型 {#structures-and-enumerations-are-value-types}
*值类型*是这样一种类型,当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被*拷贝*。
@ -191,8 +185,7 @@ print("The remembered direction is \(rememberedDirection)")
`rememberedDirection` 被赋予了 `currentDirection` 的值,实际上它被赋予的是值的一个拷贝。赋值过程结束后再修改 `currentDirection` 的值并不影响 `rememberedDirection` 所储存的原始值的拷贝。
<a name="classes_are_reference_types"></a>
## 类是引用类型
## 类是引用类型 {#classes-are-reference-types}
与值类型不同,*引用类型*在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,使用的是已存在实例的引用,而不是其拷贝。
@ -230,8 +223,7 @@ print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
需要注意的是 `tenEighty``alsoTenEighty` 被声明为常量而不是变量。然而你依然可以改变 `tenEighty.frameRate``alsoTenEighty.frameRate`,这是因为 `tenEighty``alsoTenEighty` 这两个常量的值并未改变。它们并不“存储”这个 `VideoMode` 实例,而仅仅是对 `VideoMode` 实例的引用。所以,改变的是底层 `VideoMode` 实例的 `frameRate` 属性,而不是指向 `VideoMode` 的常量引用的值。
<a name="identity_operators"></a>
### 恒等运算符
### 恒等运算符 {#identity-operators}
因为类是引用类型,所以多个常量和变量可能在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
@ -251,9 +243,8 @@ if tenEighty === alsoTenEighty {
请注意,“相同”(用三个等号表示,`===`)与“等于”(用两个等号表示,`==`的不同。“相同”表示两个类类型class type的常量或者变量引用同一个类实例。“等于”表示两个实例的值“相等”或“等价”判定时要遵照设计者定义的评判标准。
当在定义你的自定义结构体和类的时候,你有义务来决定判定两个实例“相等”的标准。在章节 [等价操作符](./26_Advanced_Operators.html#equivalence_operators) 中将会详细介绍实现自定义 == 和 !== 运算符的流程。
当在定义你的自定义结构体和类的时候,你有义务来决定判定两个实例“相等”的标准。在章节 [等价操作符](./26_Advanced_Operators.md#equivalence_operators) 中将会详细介绍实现自定义 == 和 !== 运算符的流程。
<a name="pointers"></a>
### 指针
### 指针 {#pointers}
如果你有 CC++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。Swift 中引用了某个引用类型实例的常量或变量,与 C 语言中的指针类似,不过它并不直接指向某个内存地址,也不要求你使用星号(`*`来表明你在创建一个引用。相反Swift 中引用的定义方式与其它的常量或变量的一样。如果需要直接与指针交互,你可以使用标准库提供的指针和缓冲区类型 —— 参见 [手动管理内存](https://developer.apple.com/documentation/swift/swift_standard_library/manual_memory_management)。

View File

@ -1,19 +1,18 @@
# 属性
*属性*将值特定的类、结构或枚举关联。存储属性存储常量变量为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。
*属性*将值特定的类、结构或枚举关联。存储属性会将常量变量存储为实例的一部分,而计算属性则是直接计算(不是存储)值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接类型本身关联,这种属性称为类型属性。
另外,还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己定义的存储属性上,也可以添加到从父类继承的属性上。
另外,还可以定义属性观察器来监控属性值的变化,以此来触发自定义的操作。属性观察器可以添加到类本身定义的存储属性上,也可以添加到从父类继承的属性上。
<a name="stored_properties"></a>
## 存储属性
## 存储属性 {#stored-properties}
简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是*变量存储属性*(用关键字 `var` 定义),也可以是*常量存储属性*(用关键字 `let` 定义)。
可以在定义存储属性的时候指定默认值,请参考[默认构造器](./14_Initialization.html#default_initializers)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[构造过程中常量属性的修改](./14_Initialization.html#assigning_constant_properties_during_initialization)一节。
可以在定义存储属性的时候指定默认值,请参考[默认构造器](./14_Initialization.md#default_initializers)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[构造过程中常量属性的修改](./14_Initialization.md#assigning_constant_properties_during_initialization)一节。
下面的例子定义了一个名为 `FixedLengthRange` 的结构体,该结构体用于描述整数的范围,且这个范围值在被创建后不能被修改。
下面的例子定义了一个名为 `FixedLengthRange` 的结构体,该结构体用于描述整数的区间,且这个范围值在被创建后不能被修改。
```swift
struct FixedLengthRange {
@ -21,43 +20,41 @@ struct FixedLengthRange {
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 该区间表示整数012
// 该区间表示整数 012
rangeOfThreeItems.firstValue = 6
// 该区间现在表示整数678
// 该区间现在表示整数 678
```
`FixedLengthRange` 的实例包含一个名为 `firstValue` 的变量存储属性和一个名为 `length` 的常量存储属性。在上面的例子中,`length` 在创建实例的时候被初始化,因为它是一个常量存储属性,所以之后无法修改它的值
`FixedLengthRange` 的实例包含一个名为 `firstValue` 的变量存储属性和一个名为 `length` 的常量存储属性。在上面的例子中,`length` 在创建实例的时候被初始化,且之后无法修改它的值,因为它是一个常量存储属性。
<a name="stored_properties_of_constant_structure_instances"></a>
### 常量结构体的存储属性
### 常量结构体实例的存储属性 {#stored-properties-of-constant-structure-instances}
如果创建了一个结构体实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行
如果创建了一个结构体实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使被声明为可变属性也不行:
```swift
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数0123
// 该区间表示整数 0123
rangeOfFourItems.firstValue = 6
// 尽管 firstValue 是个变属性,这里还是会报错
// 尽管 firstValue 是个变属性,这里还是会报错
```
因为 `rangeOfFourItems` 被声明成了常量(用 `let` 关键字),即使 `firstValue` 是一个变属性,也无法再修改它了。
因为 `rangeOfFourItems` 被声明成了常量(用 `let` 关键字),所以即使 `firstValue` 是一个变属性,也无法再修改它了。
这种行为是由于结构体struct属于*值类型*。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
这种行为是由于结构体属于*值类型*。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
属于*引用类型*的类class则不一样。把一个引用类型的实例赋给一个常量后,然可以修改该实例的变属性。
属于*引用类型*的类则不一样。把一个引用类型的实例赋给一个常量后,然可以修改该实例的变属性。
<a name="lazy_stored_properties"></a>
### 延迟存储属性
### 延时加载存储属性 {#lazy-stored-properties}
*延存储属性*是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 `lazy` 来标示一个延存储属性。
*延时加载存储属性*是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 `lazy` 来标示一个延时加载存储属性。
> 注意
>
> 必须将延迟存储属性声明成变量(使用 `var` 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性
> 必须将延时加载属性声明成变量(使用 `var` 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延时加载
延迟属性很有用,当属性的值依赖于在实例的构造过程结束后才会知道影响值的外部因素时,或者当获得属性的初始值需要复杂或大量计算时,可以只在需要的时候计算
当属性的值依赖于一些外部因素且这些外部因素只有在构造过程结束后才会知道的时候,延时加载属性就会很有用。或者当获得属性的值因为需要复杂或大量计算,而需要采用需要的时候计算的方式,延时加载属性也会很有用
下面的例子使用了延存储属性来避免复杂类中不必要的初始化。例子中定义了 `DataImporter``DataManager` 两个类,下面是部分代码:
下面的例子使用了延时加载存储属性来避免复杂类中不必要的初始化工作。例子中定义了 `DataImporter``DataManager` 两个类,下面是部分代码:
```swift
class DataImporter {
@ -81,33 +78,31 @@ manager.data.append("Some more data")
// DataImporter 实例的 importer 属性还没有被创建
```
`DataManager` 类包含一个名为 `data` 的存储属性,初始值是一个空的字符串`String`数组。这里没有给出全部代码,只需知道 `DataManager` 类的目的是管理和提供对这个字符串数组的访问即可。
`DataManager` 类包含一个名为 `data` 的存储属性,初始值是一个空的字符串数组。这里没有给出全部代码,只需知道 `DataManager` 类的目的是管理和提供对这个字符串数组的访问即可。
`DataManager` 的一个功能是从文件导入数据。功能由 `DataImporter` 类提供,`DataImporter` 完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。
`DataManager` 的一个功能是从文件导入数据。这个功能由 `DataImporter` 类提供,`DataImporter` 完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件读取文件中的内容到内存
`DataManager` 管理数据时也可能不从文件中导入数据。所以当 `DataManager` 的实例被创建时,没必要创建一个 `DataImporter` 的实例,更明智的做法是第一次用到 `DataImporter` 的时候才去创建它。
由于使用了 `lazy``importer` 属性只有在第一次被访问的时候才被创建。比如访问它的属性 `fileName` 时:
由于使用了 `lazy``DataImporter` 的实例 `importer` 属性只有在第一次被访问的时候才被创建。比如访问它的属性 `fileName` 时:
```swift
print(manager.importer.fileName)
// DataImporter 实例的 importer 属性现在被创建了
// 输出 "data.txt”
// 输出data.txt”
```
> 注意
>
> 如果一个被标记为 `lazy` 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
<a name="stored_properties_and_instance_variables"></a>
### 存储属性和实例变量
### 存储属性和实例变量 {#stored-properties-and-instance-variables}
如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为属性值的后端存储
如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为一个备份存储将变量值赋值给属性。
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——作为类型定义的一部分,都定义在一个地方。
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的备份存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——作为类型定义的一部分,都定义在一个地方。
<a name="computed_properties"></a>
## 计算属性
## 计算属性 {#computed-properties}
除存储属性外,类、结构体和枚举可以定义*计算属性*。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter来间接获取和设置其他属性或变量的值。
@ -138,7 +133,7 @@ var square = Rect(origin: Point(x: 0.0, y: 0.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)”
// 打印square.origin is now at (10.0, 10.0)”
```
这个例子定义了 3 个结构体来描述几何形状:
@ -147,7 +142,7 @@ print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
- `Size` 封装了一个 `width` 和一个 `height`
- `Rect` 表示一个有原点和尺寸的矩形
`Rect` 也提供了一个名为 `center` 的计算属性。一个矩形的中心点可以从原点(`origin`)和大小`size`)算出,所以不需要将它以显式声明的 `Point` 来保存。`Rect` 的计算属性 `center` 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
`Rect` 也提供了一个名为 `center` 的计算属性。一个 `Rect` 的中心点可以从 `origin`原点)和 `size`(大小)算出,所以不需要将中心点以 `Point` 类型的值来保存。`Rect` 的计算属性 `center` 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
上述例子中创建了一个名为 `square``Rect` 实例,初始值原点是 `(0, 0)`,宽度高度都是 `10`。如下图中蓝色正方形所示。
@ -157,8 +152,7 @@ print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
<img src="https://docs.swift.org/swift-book/_images/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
<a name="shorthand_setter_declaration"></a>
### 简化 Setter 声明
### 简化 Setter 声明 {#shorthand-setter-declaration}
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 `newValue`。下面是使用了简化 setter 声明的 `Rect` 结构体代码:
@ -180,10 +174,9 @@ struct AlternativeRect {
}
```
<a name="readonly_computed_properties"></a>
### 只读计算属性
### 只读计算属性 {#readonly-computed-properties}
只有 getter 没有 setter 的计算属性就是*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
只有 getter 没有 setter 的计算属性*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
> 注意
>
@ -200,32 +193,31 @@ struct Cuboid {
}
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"
// 打印the volume of fourByFiveByTwo is 40.0
```
这个例子定义了一个名为 `Cuboid` 的结构体,表示三维空间的立方体,包含 `width``height``depth` 属性。结构体还有一个名为 `volume` 的只读计算属性用来返回立方体的体积。为 `volume` 提供 setter 毫无意义,因为无法确定如何修改 `width``height``depth` 三者的值来匹配新的 `volume`。然而,`Cuboid` 提供一个只读计算属性来让外部用户直接获取体积是很有用的。
<a name="property_observers"></a>
## 属性观察器
## 属性观察器 {#property-observers}
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
你可以为除了延存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。 属性重写请参考[重写](./13_Inheritance.html#overriding)。
你可以为除了延时加载存储属性之外的其他存储属性添加属性观察器,也可以在子类中通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为可以直接通过它的 setter 监控和响应值的变化。属性重写请参考[重写](./13_Inheritance.md#overriding)。
可以为属性添加如下的一个或全部观察器:
可以为属性添加其中一个或两个观察器:
- `willSet` 在新的值被设置之前调用
- `didSet` 在新的值被设置之后立即调用
- `didSet` 在新的值被设置之后调用
`willSet` 观察器会将新的属性值作为常量参数传入,在 `willSet` 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 `newValue` 表示。
同样,`didSet` 观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名 `oldValue`。如果在 `didSet` 方法中再次对该属性赋值,那么新值会覆盖旧的值。
同样,`didSet` 观察器会将旧的属性值作为参数传入,可以为该参数指定一个名称或者使用默认参数名 `oldValue`。如果在 `didSet` 方法中再次对该属性赋值,那么新值会覆盖旧的值。
> 注意
>
> 父类的属性在子类构造器中被赋值时,它在父类中的 `willSet` 和 `didSet` 观察器会被调用,随后才会调用子类的观察器。在父类初始化方法调用之前,子类属性赋值时,观察器不会调用。
> 在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的 `willSet` 和 `didSet` 观察器。在父类初始化方法调用之前,子类属性赋值时不会调用子类属性的观察器
>
> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)和[类的构造器代理规则](./14_Initialization.html#initializer_delegation_for_class_types)。
> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.md#initializer_delegation_for_value_types)和[类的构造器代理](./14_Initialization.md#initializer_delegation_for_class_types)。
下面是一个 `willSet``didSet` 实际运用的例子,其中定义了一个名为 `StepCounter` 的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。
@ -233,28 +225,28 @@ print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
print(" totalSteps 的值设置为 \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
print("增加了 \(totalSteps - oldValue) ")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
// totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
// totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
// totalSteps 的值设置为 896
// 增加了 536 步
```
`StepCounter` 类定义了一个 `Int` 类型的属性 `totalSteps`它是一个存储属性,包含 `willSet``didSet` 观察器。
`StepCounter` 类定义了一个 `totalSteps``Int` 类型的属性。它是一个存储属性,包含 `willSet``didSet` 观察器。
`totalSteps` 被设置新值的时候,它的 `willSet``didSet` 观察器都会被调用,即使新值和当前值完全相同时也会被调用。
@ -264,12 +256,11 @@ stepCounter.totalSteps = 896
> 注意
>
> 如果将属性通过 in-out 方式传入函数,`willSet` 和 `didSet` 也会调用。这是因为 in-out 参数采用了拷入拷出模式:即在函数内部使用的是参数的 copy函数结束后又对参数重新赋值。关于 in-out 参数详细的介绍,请参考[输入输出参数](../chapter3/05_Declarations.html#in-out_parameters)
> 如果将带有观察器的属性通过 in-out 方式传入函数,`willSet` 和 `didSet` 也会调用。这是因为 in-out 参数采用了拷入拷出内存模式:即在函数内部使用的是参数的 copy函数结束后又对参数重新赋值。关于 in-out 参数详细的介绍,请参考[输入输出参数](../chapter3/05_Declarations.html#in-out_parameters)
<a name="global_and_local_variables"></a>
## 全局变量和局部变量
## 全局变量和局部变量 {#global-and-local-variables}
计算属性和属性观察器所描述的功能也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
计算属性和观察属性所描述的功能也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
前面章节提到的全局或局部变量都属于*存储型变量*,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
@ -277,16 +268,15 @@ stepCounter.totalSteps = 896
> 注意
>
> 全局的常量或变量都是延迟计算的,跟[延存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记 `lazy` 修饰符。
> 全局的常量或变量都是延迟计算的,跟[延时加载存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记 `lazy` 修饰符。
>
> 局部范围的常量变量从不延迟计算。
> 局部范围的常量变量从不延迟计算。
<a name="type_properties"></a>
## 类型属性
## 类型属性 {#type-properties}
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。
也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是*类型属性*。
也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是*类型属性*。
类型属性用于定义某个类型所有实例共享的数据,比如*所有*实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
@ -298,10 +288,9 @@ stepCounter.totalSteps = 896
>
> 存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 `lazy` 修饰符。
<a name="type_property_syntax"></a>
### 类型属性语法
### 类型属性语法 {#type-property-syntax}
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为全局(*global*)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为 *global*(全局)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。
使用关键字 `static` 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 `class` 来支持子类对父类的实现进行重写。下面的例子演示了存储型和计算型类型属性的语法:
@ -333,21 +322,20 @@ class SomeClass {
>
> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。
<a name="querying_and_setting_type_properties"></a>
### 获取和设置类型属性的值
### 获取和设置类型属性的值 {#querying-and-setting-type-properties}
跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过类型本身来访问,而不是通过实例。比如:
跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过*类型*本身来访问,而不是通过实例。比如:
```swift
print(SomeStructure.storedTypeProperty)
// 打印 "Some value."
// 打印Some value.
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印 "Another value.”
// 打印Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印 "6"
// 打印“6”
print(SomeClass.computedTypeProperty)
// 打印 "27"
// 打印27
```
下面的例子定义了一个结构体,使用两个存储型类型属性来表示两个声道的音量,每个声道具有 `0``10` 之间的整数音量。
@ -377,9 +365,9 @@ struct AudioChannel {
}
```
结构 `AudioChannel` 定义了 2 个存储型类型属性来实现上述功能。第一个是 `thresholdLevel`,表示音量的最大上限阈值,它是一个值为 `10` 的常量,对所有实例都可见,如果音量高于 `10`,则取最大上限值 `10`(见后面描述)。
`AudioChannel` 结构定义了 2 个存储型类型属性来实现上述功能。第一个是 `thresholdLevel`,表示音量的最大上限阈值,它是一个值为 `10` 的常量,对所有实例都可见,如果音量高于 `10`,则取最大上限值 `10`(见后面描述)。
第二个类型属性是变量存储型属性 `maxInputLevelForAllChannels`,它用来表示所有 `AudioChannel` 实例的最大音量,初始值是 `0`
第二个类型属性是变量存储型属性 `maxInputLevelForAllChannels`,它用来表示所有 `AudioChannel` 实例的最大输入音量,初始值是 `0`
`AudioChannel` 也定义了一个名为 `currentLevel` 的存储型实例属性,表示当前声道现在的音量,取值为 `0``10`
@ -404,9 +392,9 @@ var rightChannel = AudioChannel()
```swift
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// 输出 "7"
// 输出“7”
print(AudioChannel.maxInputLevelForAllChannels)
// 输出 "7"
// 输出“7”
```
如果试图将右声道的 `currentLevel` 设置成 `11`,它会被修正到最大值 `10`,同时 `maxInputLevelForAllChannels` 的值也会更新到 `10`
@ -414,7 +402,7 @@ print(AudioChannel.maxInputLevelForAllChannels)
```swift
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// 输出 "10"
// 输出10
print(AudioChannel.maxInputLevelForAllChannels)
// 输出 "10"
// 输出10
```

View File

@ -4,8 +4,7 @@
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。
<a name="instance_methods"></a>
## 实例方法Instance Methods
## 实例方法Instance Methods {#instance-methods}
*实例方法*是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致,详情参见[函数](./06_Functions.md)。
@ -48,10 +47,9 @@ counter.reset()
// 计数值现在是0
```
函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见[指定外部参数名](./06_Functions.html#specifying_external_parameter_names)。方法参数也一样,因为方法就是函数,只是这个函数与某个类型相关联了。
函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见[指定外部参数名](./06_Functions.md#specifying_external_parameter_names)。方法参数也一样,因为方法就是函数,只是这个函数与某个类型相关联了。
<a name="the_self_property"></a>
### self 属性
### self 属性 {#the-self-property}
类型的每一个实例都有一个隐含属性叫做 `self``self` 完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的 `self` 属性来引用当前实例。
@ -72,21 +70,20 @@ func increment() {
```swift
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOfX(_ x: Double) -> Bool {
func isToTheRightOf(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOfX(1.0) {
if somePoint.isToTheRightOf(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"
// 打印This point is to the right of the line where x == 1.0
```
如果不使用 `self` 前缀Swift 就认为两次使用的 `x` 都指的是名称`x`函数参数。
如果不使用 `self` 前缀Swift会认为 `x` 的两个用法都引用了名`x`方法参数。
<a name="modifying_value_types_from_within_instance_methods"></a>
### 在实例方法中修改值类型
### 在实例方法中修改值类型 {#modifying-value-types-from-within-instance-methods}
结构体和枚举是*值类型*。默认情况下,值类型的属性不能在它的实例方法中被修改。
@ -97,29 +94,28 @@ if somePoint.isToTheRightOfX(1.0) {
```swift
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(_ deltaX: Double, y deltaY: Double) {
mutating func moveBy(x 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)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印 "The point is now at (3.0, 4.0)"
// 打印The point is now at (3.0, 4.0)
```
上面的 `Point` 结构体定义了一个可变方法 `moveByX(_:y:)` 来移动 `Point` 实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 `mutating` 关键字,从而允许修改属性。
上面的 `Point` 结构体定义了一个可变方法 `moveByxy :)` 来移动 `Point` 实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 `mutating` 关键字,从而允许修改属性。
注意不能在结构体类型的常量a constant of structure type上调用可变方法因为其属性不能被改变即使属性是变量属性详情参见[常量结构体的存储属性](./10_Properties.html#stored_properties_of_constant_structure_instances)
注意不能在结构体类型的常量a constant of structure type上调用可变方法因为其属性不能被改变即使属性是变量属性详情参见[常量结构体的存储属性](./10_Properties.md#stored_properties_of_constant_structure_instances)
```swift
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveByX(2.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// 这里将会报告一个错误
```
<a name="assigning_to_self_within_a_mutating_method"></a>
### 在可变方法中给 self 赋值
### 在可变方法中给 self 赋值 {#assigning-to-self-within-a-mutating-method}
可变方法能够赋给隐含属性 `self` 一个全新的实例。上面 `Point` 的例子可以用下面的方式改写:
@ -138,29 +134,28 @@ struct Point {
```swift
enum TriStateSwitch {
case Off, Low, High
case off, low, high
mutating func next() {
switch self {
case .Off:
self = .Low
case .Low:
self = .High
case .High:
self = .Off
case .off:
self = .low
case .low:
self = .high
case .high:
self = .off
}
}
}
var ovenLight = TriStateSwitch.Low
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight 现在等于 .High
// ovenLight 现在等于 .high
ovenLight.next()
// ovenLight 现在等于 .Off
// ovenLight 现在等于 .off
```
上面的例子中定义了一个三态开关的枚举。每次调用 `next()` 方法时,开关在不同的电源状态(`Off``Low``High`)之间循环切换。
上面的例子中定义了一个三态切换的枚举。每次调用 `next()` 方法时,开关在不同的电源状态(`off`, `low`, `high`)之间循环切换。
<a name="type_methods"></a>
## 类型方法
## 类型方法 {#type-methods}
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做*类型方法*。在方法的 `func` 关键字之前加上关键字 `static`,来指定类型方法。类还可以用关键字 `class` 来允许子类重写父类的方法实现。
@ -179,7 +174,7 @@ class SomeClass {
SomeClass.someTypeMethod()
```
在类型方法的方法体body`self` 指向这个类型本身,而不是类型的某个实例。这意味着你可以用 `self` 来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。
在类型方法的方法体body`self` 属性指向这个类型本身,而不是类型的某个实例。这意味着你可以用 `self` 来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。
一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前面加上类型名称。类似地,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称。
@ -218,7 +213,7 @@ struct LevelTracker {
除了类型属性和类型方法,`LevelTracker` 还监测每个玩家的进度。它用实例属性 `currentLevel` 来监测每个玩家当前的等级。
为了便于管理 `currentLevel` 属性,`LevelTracker` 定义了实例方法 `advance(to:)`。这个方法会在更新 `currentLevel` 之前检查所请求的新等级是否已经解锁。`advance(to:)` 方法返回布尔值以指示是否能够设置 `currentLevel`。因为允许在调用 `advance(to:)` 时候忽略返回值,不会产生编译警告,所以函数被标注为 `@ discardableResult` 属性,更多关于属性信息,请参考[](../chapter3/07_Attributes.html)章节。
为了便于管理 `currentLevel` 属性,`LevelTracker` 定义了实例方法 `advance(to:)`。这个方法会在更新 `currentLevel` 之前检查所请求的新等级是否已经解锁。`advance(to:)` 方法返回布尔值以指示是否能够设置 `currentLevel`。因为允许在调用 `advance(to:)` 时候忽略返回值,不会产生编译警告,所以函数被标注为 `@discardableResult` 属性,更多关于属性信息,请参考[](../chapter3/07_Attributes.html)章节。
下面,`Player` 类使用 `LevelTracker` 来监测和更新每个玩家的发展进度:
@ -244,7 +239,7 @@ class Player {
var player = Player(name: "Argyrios")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// 打印 "highest unlocked level is now 2"
// 打印highest unlocked level is now 2
```
如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么试图设置玩家当前等级将会失败:
@ -256,5 +251,5 @@ if player.tracker.advance(to: 6) {
} else {
print("level 6 has not yet been unlocked")
}
// 打印 "level 6 has not yet been unlocked"
// 打印level 6 has not yet been unlocked
```

View File

@ -4,8 +4,7 @@
一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。
<a name="subscript_syntax"></a>
## 下标语法
## 下标语法 {#subscript-syntax}
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存取。语法类似于实例方法语法和计算型属性语法的混合。与定义实例方法类似,定义下标使用 `subscript` 关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性:
@ -41,7 +40,7 @@ struct TimesTable {
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// 打印 "six times three is 18"
// 打印six times three is 18
```
在上例中,创建了一个 `TimesTable` 实例,用来表示整数 `3` 的乘法表。数值 `3` 被传递给结构体的构造函数,作为实例成员 `multiplier` 的值。
@ -52,8 +51,7 @@ print("six times three is \(threeTimesTable[6])")
>
> `TimesTable` 例子基于一个固定的数学公式,对 `threeTimesTable[someIndex]` 进行赋值操作并不合适,因此下标定义为只读的。
<a name="subscript_usage"></a>
## 下标用法
## 下标用法 {#subscript-usage}
下标的确切含义取决于使用场景。下标通常作为访问集合,列表或序列中元素的快捷方式。你可以针对自己特定的类或结构体的功能来自由地以最恰当的方式实现下标。
@ -66,14 +64,13 @@ numberOfLegs["bird"] = 2
上例定义一个名为 `numberOfLegs` 的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs` 字典的类型被推断为 `[String: Int]`。字典创建完成后,该例子通过下标将 `String` 类型的键 `bird``Int` 类型的值 `2` 添加到字典中。
更多关于 `Dictionary` 下标的信息请参考 [读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary)。
更多关于 `Dictionary` 下标的信息请参考 [读取和修改字典](./04_Collection_Types.md#accessing_and_modifying_a_dictionary)。
> 注意
>
> Swift 的 `Dictionary` 类型的下标接受并返回可选类型的值。上例中的 `numberOfLegs` 字典通过下标返回的是一个 `Int?` 或者说“可选的 int”。`Dictionary` 类型之所以如此实现下标,是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式,只需将键对应的值赋值为 `nil` 即可。
<a name="subscript_options"></a>
## 下标选项
## 下标选项 {#subscript-options}
下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型。下标可以使用变量参数和可变参数,但不能使用输入输出参数,也不能给参数设置默认值。
@ -106,7 +103,7 @@ struct Matrix {
}
```
`Matrix` 提供了一个接受两个入参的构造方法,入参分别是 `rows``columns`,创建了一个足够容纳 `rows * columns``Double` 类型的值的数组。通过传入数组长度和初始值 `0.0` 到数组的构造器,将矩阵中每个位置的值初始化为 `0.0`。关于数组的这种构造方法请参考 [创建一个带有默认值的数组](./04_Collection_Types.html#creating_an_array_with_a_default_value)。
`Matrix` 提供了一个接受两个入参的构造方法,入参分别是 `rows``columns`,创建了一个足够容纳 `rows * columns``Double` 类型的值的数组。通过传入数组长度和初始值 `0.0` 到数组的构造器,将矩阵中每个位置的值初始化为 `0.0`。关于数组的这种构造方法请参考 [创建一个带有默认值的数组](./04_Collection_Types.md#creating_an_array_with_a_default_value)。
你可以通过传入合适的 `row``column` 的数量来构造一个新的 `Matrix` 实例:

View File

@ -6,8 +6,7 @@
可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性。
<a name="defining_a_base_class"></a>
## 定义一个基类
## 定义一个基类 {#defining-a-base-class}
不继承于其它类的类,称之为*基类*。
@ -26,7 +25,7 @@ class Vehicle {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 什么也不做-因为车辆不一定会有噪音
// 什么也不做——因为车辆不一定会有噪音
}
}
```
@ -41,13 +40,12 @@ let someVehicle = Vehicle()
```swift
print("Vehicle: \(someVehicle.description)")
// 打印 "Vehicle: traveling at 0.0 miles per hour"
// 打印Vehicle: traveling at 0.0 miles per hour
```
`Vehicle` 类定义了一个具有通用特性的车辆类,但实际上对于它本身来说没什么用处。为了让它变得更加有用,还需要进一步完善它,从而能够描述一个具体类型的车辆。
<a name="subclassing"></a>
## 子类生成
## 子类生成 {#subclassing}
*子类生成*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以进一步完善。你还可以为子类添加新的特性。
@ -83,7 +81,7 @@ bicycle.hasBasket = true
```swift
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// 打印 "Bicycle: traveling at 15.0 miles per hour"
// 打印Bicycle: traveling at 15.0 miles per hour
```
子类还可以继续被其它类继承,下面的示例为 `Bicycle` 创建了一个名为 `Tandem`(双人自行车)的子类:
@ -104,11 +102,10 @@ tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// 打印:"Tandem: traveling at 22.0 miles per hour"
// 打印:Tandem: traveling at 22.0 miles per hour
```
<a name="overriding"></a>
## 重写
## 重写 {#overriding}
子类可以为继承来的实例方法,类方法,实例属性,类属性,或下标提供自己定制的实现。我们把这种行为叫*重写*。
@ -116,7 +113,7 @@ print("Tandem: \(tandem.description)")
`override` 关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
### 访问超类的方法,属性及下标
### 访问超类的方法,属性及下标 {#accessing-superclass-methods-properties-and-subscripts}
当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以完善已有实现的行为,或在一个继承来的变量中存储一个修改过的值。
@ -126,7 +123,7 @@ print("Tandem: \(tandem.description)")
* 在属性 `someProperty` 的 getter 或 setter 的重写实现中,可以通过 `super.someProperty` 来访问超类版本的 `someProperty` 属性。
* 在下标的重写实现中,可以通过 `super[someIndex]` 来访问超类版本中的相同下标。
### 重写方法
### 重写方法 {#overriding-methods}
在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。
@ -145,14 +142,14 @@ class Train: Vehicle {
```swift
let train = Train()
train.makeNoise()
// 打印 "Choo Choo"
// 打印Choo Choo
```
### 重写属性
### 重写属性 {#overriding-properties}
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter或添加属性观察器使重写的属性可以观察到底层的属性值什么时候发生改变。
#### 重写属性的 Getters 和 Setters
#### 重写属性的 Getters 和 Setters {#overriding-property-etters-and-setters}
你可以提供定制的 getter或 setter来重写任何一个继承来的属性无论这个属性是存储型还是计算型属性。子类并不知道继承来的属性是存储型的还是计算型的它只知道继承来的属性会有一个名字和类型。你在重写一个属性时必须将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
@ -182,11 +179,10 @@ let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// 打印 "Car: traveling at 25.0 miles per hour in gear 3"
// 打印Car: traveling at 25.0 miles per hour in gear 3
```
<a name="overriding_property_observers"></a>
#### 重写属性观察器
#### 重写属性观察器 {#overriding-property-observers}
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,无论被继承属性原本是如何实现的,当其属性值发生改变时,你就会被通知到。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。
@ -213,14 +209,13 @@ class AutomaticCar: Car {
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// 打印 "AutomaticCar: traveling at 35.0 miles per hour in gear 4"
// 打印AutomaticCar: traveling at 35.0 miles per hour in gear 4
```
<a name="preventing_overrides"></a>
## 防止重写
## 防止重写 {#preventing-overrides}
你可以通过把方法,属性或下标标记为*`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 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。

View File

@ -4,11 +4,9 @@
你要通过定义*构造器*来实现构造过程,它就像用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同Swift 的构造器没有返回值。它们的主要任务是保证某种类型的新实例在第一次使用前完成正确的初始化。
类的实例也可以通过实现*析构器*来执行它释放之前自定义的清理工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.html)。
类的实例也可以通过实现*析构器*来执行它释放之前自定义的清理工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.md)。
<a name="setting_initial_values_for_stored_properties"></a>
## 存储属性的初始赋值
## 存储属性的初始赋值 {#setting-initial-values-for-stored-properties}
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
@ -18,8 +16,7 @@
>
> 当你为存储型属性分配默认值或者在构造器中为设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者。
<a name="initializers"></a>
### 构造器
### 构造器 {#initializers}
构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何形参的实例方法,以关键字 `init` 命名:
@ -40,13 +37,12 @@ struct Fahrenheit {
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// 打印 "The default temperature is 32.0° Fahrenheit"
// 打印The default temperature is 32.0° Fahrenheit
```
这个结构体定义了一个不带形参的构造器 `init`,并在里面将存储型属性 `temperature` 的值初始化为 `32.0`(华氏温度下水的冰点)。
<a name="default_property_values"></a>
### 默认属性值
### 默认属性值 {#default-property-values}
如前所述,你可以在构造器中为存储型属性设置初始值。同样,你也可以在属性声明时为其设置默认值。
@ -62,14 +58,11 @@ struct Fahrenheit {
}
```
<a name="customizing_initialization"></a>
## 自定义构造过程
## 自定义构造过程 {#customizing-initialization}
你可以通过输入形参和可选属性类型来自定义构造过程,也可以在构造过程中分配常量属性。这些都将在后面章节中提到。
<a name="initialization_parameters"></a>
### 形参的构造过程
### 形参的构造过程 {#initialization-parameters}
自定义构造过程时,可以在定义中提供*构造形参*,指定其值的类型和名字。构造形参的功能和语法跟函数和方法的形参相同。
@ -94,8 +87,7 @@ let freezingPointOfWater = Celsius(fromKelvin: 273.15)
第一个构造器拥有一个构造形参,其实参标签为 `fromFahrenheit`,形参命名为 `fahrenheit`;第二个构造器也拥有一个构造形参,其实参标签为 `fromKelvin`,形参命名为 `kelvin`。这两个构造器都将单一的实参转换成摄氏温度值,并保存在属性 `temperatureInCelsius` 中。
<a name="parameter_names_and_argument_labels"></a>
### 形参命名和实参标签
### 形参命名和实参标签 {#parameter-names-and-argument-labels}
跟函数和方法形参相同,构造形参可以同时使用在构造器里使用的形参命名和一个外部调用构造器时使用的实参标签。
@ -135,8 +127,7 @@ let veryGreen = Color(0.0, 1.0, 0.0)
// 报编译期错误-需要实参标签
```
<a name="initializer_parameters_without_external_names"></a>
### 不带实参标签的构造器形参
### 不带实参标签的构造器形参 {#initializer-parameters-without-external-names}
如果你不希望构造器的某个形参使用实参标签,可以使用下划线(`_`)来代替显式的实参标签来重写默认行为。
@ -162,11 +153,9 @@ let bodyTemperature = Celsius(37.0)
构造器调用 `Celsius(37.0)` 意图明确,不需要实参标签。因此适合使用 `init(_ celsius: Double)` 这样的构造器,从而可以通过提供未命名的 `Double` 值来调用构造器。
<a name="optional_property_types"></a>
### 可选属性类型 {#optional-property-types}
### 可选属性类型
如果你自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值还是因为它在之后某个时机可以赋值为空——都需要将它y声明为 `可选类型`。可选类型的属性将自动初始化为 `nil`,表示这个属性是特意在构造过程设置为空。
如果你自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机可以赋值为空——都需要将它声明为 `可选类型`。可选类型的属性将自动初始化为 `nil`,表示这个属性是特意在构造过程设置为空。
下面例子中定义了类 `SurveyQuestion`,它包含一个可选 ` String` 属性 `response`
@ -184,14 +173,13 @@ class SurveyQuestion {
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// 打印 "Do you like cheese?"
// 打印Do you like cheese?
cheeseQuestion.response = "Yes, I do like cheese."
```
调查问题的答案在询问前是无法确定的,因此我们将属性 `response` 声明为 `String?` 类型,或者说是 “可选类型 `String`“。当 `SurveyQuestion` 的实例初始化时,它将自动赋值为 `nil`,表明“暂时还没有字符“。
<a name="assigning_constant_properties_during_initialization"></a>
### 构造过程中常量属性的赋值
### 构造过程中常量属性的赋值 {#assigning-constant-properties-during-initialization}
你可以在构造过程中的任意时间点给常量属性赋值,只要在构造过程结束时它设置成确定的值。一旦常量属性被赋值,它将永远不可更改。
@ -214,12 +202,11 @@ class SurveyQuestion {
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// 打印 "How about beets?"
// 打印How about beets?
beetsQuestion.response = "I also like beets. (But not with cheese.)"
```
<a name="default_initializers"></a>
## 默认构造器
## 默认构造器 {#default-initializers}
如果结构体或类为所有属性提供了默认值,又没有提供任何自定义的构造器,那么 Swift 会给这些结构体或类提供一个*默认构造器*。这个默认构造器将简单地创建一个所有属性值都设置为它们默认值的实例。
@ -236,8 +223,7 @@ var item = ShoppingListItem()
由于 `ShoppingListItem` 类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个将为所有属性设置默认值的并创建实例的默认构造器(由于 `name` 属性是可选 `String` 类型,它将接收一个默认 `nil` 的默认值,尽管代码中没有写出这个值)。上面例子中使用默认构造器创造了一个 `ShoppingListItem` 类的实例(使用 `ShoppingListItem()` 形式的构造器语法),并将其赋值给变量 `item`
<a name="memberwise_initializers_for_structure_types"></a>
### 结构体的逐一成员构造器
### 结构体的逐一成员构造器 {#memberwise-initializers-for-structure-types}
结构体如果没有定义任何自定义构造器,它们将自动获得一个*逐一成员构造器memberwise initializer*。不像默认构造器,即使存储型属性没有默认值,结构体也能会获得逐一成员构造器。
@ -254,12 +240,11 @@ struct Size {
let twoByTwo = Size(width: 2.0, height: 2.0)
```
<a name="initializer_delegation_for_value_types"></a>
## 值类型的构造器代理
## 值类型的构造器代理 {#initializer-delegation-for-value-types}
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为*构造器代理*,它能避免多个构造器间的代码重复。
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.html))。这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.md))。这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。
对于值类型,你可以使用 `self.init` 在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 `self.init`
@ -267,7 +252,7 @@ let twoByTwo = Size(width: 2.0, height: 2.0)
> 注意
>
> 假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看[扩展](./20_Extensions.html)章节。
> 假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看[扩展](./20_Extensions.md)章节。
下面例子定义一个自定义结构体 `Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体 `Size``Point`,它们各自为其所有的属性提供了默认初始值 `0.0`
@ -329,16 +314,15 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
> 注意
>
> 如果你想用另外一种不需要自己定义 `init()` 和 `init(origin:size:)` 的方式来实现这个例子,请参考[扩展](./21_Extensions.html)。
> 如果你想用另外一种不需要自己定义 `init()` 和 `init(origin:size:)` 的方式来实现这个例子,请参考[扩展](./21_Extensions.md)。
<a name="class_inheritance_and_initialization"></a>
## 类的继承和构造过程
## 类的继承和构造过程 {#class-inheritance-and-initialization}
类里面的所有存储型属性——包括所有继承自父类的属性——都*必须*在构造过程中设置初始值。
Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们被称为指定构造器和便利构造器。
<a name="designated_initializers_and_convenience_initializers"></a>
### 指定构造器和便利构造器
### 指定构造器和便利构造器 {#designated-initializers-and-convenience-initializers}
*指定构造器*是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并调用合适的父类构造器让构造过程沿着父类链继续往上进行。
@ -350,8 +334,7 @@ Swift 为类类型提供了两种构造器来确保实例中所有存储型属
你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。
<a name="syntax_for_designated_and_convenience_initializers"></a>
### 指定构造器和便利构造器的语法
### 指定构造器和便利构造器的语法 {#syntax-for-designated-and-convenience-initializers}
类的指定构造器的写法跟值类型简单构造器一样:
@ -369,8 +352,7 @@ convenience init(parameters) {
}
```
<a name="initializer_delegation_for_class_types"></a>
### 类类型的构造器代理
### 类类型的构造器代理 {#initializer-delegation-for-class-types}
为了简化指定构造器和便利构造器之间的调用关系Swift 构造器之间的代理调用遵循以下三条规则:
@ -407,9 +389,7 @@ convenience init(parameters) {
![复杂构造器代理图](https://docs.swift.org/swift-book/_images/initializerDelegation02_2x.png)
<a name="two_phase_initialization"></a>i
### 两段式构造过程
### 两段式构造过程 {#two-phase-initialization}
Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储型属性。
@ -479,8 +459,7 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的自定义操作。
<a name="initializer_inheritance_and_overriding"></a>
### 构造器的继承和重写
### 构造器的继承和重写 {#initializer-inheritance-and-overriding}
跟 Objective-C 中的子类不同Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,而在用来创建子类时的新实例时没有完全或错误被初始化。
@ -500,7 +479,7 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文[类的构造器代理规则](#initializer_delegation_for_class_types)有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加 `override` 修饰符。
在下面的例子中定义了一个叫 Vehicle 的基类。基类中声明了一个存储型属性 numberOfWheels它是默认值为 Int 类型的 0。numberOfWheels 属性用在一个描述车辆特征 String 类型为 descrpiption 的计算型属性中:
在下面的例子中定义了一个叫 `Vehicle` 的基类。基类中声明了一个存储型属性 `numberOfWheels`,它是默认值为 `Int` 类型的 `0``numberOfWheels` 属性用在一个描述车辆特征 `String` 类型为 `descrpiption` 的计算型属性中:
```swift
class Vehicle {
@ -511,8 +490,7 @@ class Vehicle {
}
```
`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 `numberOfWheels``0``Vehicle`
实例:
`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 `numberOfWheels``0``Vehicle` 实例:
```swift
let vehicle = Vehicle()
@ -540,10 +518,10 @@ class Bicycle: Vehicle {
```swift
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// 打印 "Bicycle: 2 wheel(s)"
// 打印Bicycle: 2 wheel(s)
```
如果类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个无参数的自定义构造器。你可以省略 `super.init()` 的调用在所有父类的存储属性赋值之后
如果类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个无参数的自定义构造器。你可以在所有父类的存储属性赋值之后省略 `super.init()` 的调用。
这个例子定义了另一个 `Vehicle` 的子类 `Hoverboard` ,只设置它的 `color` 属性。这个构造器依赖隐式调用父类的构造器来完成,而不是显示调用 `super.init()`
@ -572,9 +550,7 @@ print("Hoverboard: \(hoverboard.description)")
>
> 子类可以在构造过程修改继承来的变量属性,但是不能修改继承来的常量属性。
<a name="automatic_initializer_inheritance"></a>
### 构造器的自动继承
### 构造器的自动继承 {#automatic-initializer-inheritance}
如上所述,子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。事实上,这意味着对于许多常见场景你不必重写父类的构造器,并且可以在安全的情况下以最小的代价继承父类的构造器。
@ -594,8 +570,7 @@ print("Hoverboard: \(hoverboard.description)")
>
> 子类可以将父类的指定构造器实现为便利构造器来满足规则 2。
<a name="designated_and_convenience_initializers_in_action"></a>
### 指定构造器和便利构造器实践
### 指定构造器和便利构造器实践 {#designated-and-convenience-initializers-in-action}
接下来的例子将在实践中展示指定构造器、便利构造器以及构造器的自动继承。这个例子定义了包含三个类 `Food``RecipeIngredient` 以及 `ShoppingListItem` 的层级结构,并将演示它们的构造器是如何相互作用的。
@ -716,8 +691,7 @@ for item in breakfastList {
如上所述,例子中通过字面量方式创建了一个数组 `breakfastList`,它包含了三个 `ShoppingListItem` 实例,因此数组的类型也能被自动推导为 `[ShoppingListItem]`。在数组创建完之后,数组中第一个 `ShoppingListItem` 实例的名字从 `[Unnamed]` 更改为 `Orange juice`,并标记状态为已购买。打印数组中每个元素的描述显示了它们都已按照预期被赋值。
<a name="failable_initializers"></a>
## 可失败构造器
## 可失败构造器 {#failable-initializers}
有时,定义一个构造器可失败的类,结构体或者枚举是很有用的。这里所指的“失败” 指的是,如给构造器传入无效的形参,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。
@ -742,7 +716,7 @@ let pi = 3.14159
if let valueMaintained = Int(exactly: wholeNumber) {
print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// 打印 "12345.0 conversion to Int maintains value of 12345"
// 打印12345.0 conversion to Int maintains value of 12345
let valueChanged = Int(exactly: pi)
// valueChanged 是 Int? 类型,不是 Int 类型
@ -750,7 +724,7 @@ let valueChanged = Int(exactly: pi)
if valueChanged == nil {
print("\(pi) conversion to Int does not maintain value")
}
// 打印 "3.14159 conversion to Int does not maintain value"
// 打印3.14159 conversion to Int does not maintain value
```
下例中,定义了一个名为 `Animal` 的结构体,其中有一个名为 `species``String` 类型的常量属性。同时该结构体还定义了一个接受一个名为 `species``String` 类型形参的可失败构造器。这个可失败构造器检查传入的`species` 值是否为一个空字符串。如果为空字符串,则构造失败。否则,`species` 属性被赋值,构造成功。
@ -776,7 +750,7 @@ let someCreature = Animal(species: "Giraffe")
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
// 打印 "An animal was initialized with a species of Giraffe"
// 打印An animal was initialized with a species of Giraffe
```
如果你给该可失败构造器传入一个空字符串到形参 `species`,则会导致构造失败:
@ -788,15 +762,14 @@ let anonymousCreature = Animal(species: "")
if anonymousCreature == nil {
print("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"
// 打印The anonymous creature could not be initialized
```
> 注意
>
> 检查空字符串的值(如 `""`,而不是 `"Giraffe"` )和检查值为 `nil` 的可选类型的字符串是两个完全不同的概念。上例中的空字符串(`""`)其实是一个有效的,非可选类型的字符串。这里我们之所以让 `Animal` 的可失败构造器构造失败,只是因为对于 `Animal` 这个类的 `species` 属性来说,它更适合有一个具体的值,而不是空字符串。
<a name="failable_nitializers_for_enumerations"></a>
### 枚举类型的可失败构造器
### 枚举类型的可失败构造器 {#failable-nitializers-for-enumerations}
你可以通过一个带一个或多个形参的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的形参无法匹配任何枚举成员,则构造失败。
@ -827,17 +800,16 @@ let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// 打印 "This is a defined temperature unit, so initialization succeeded."
// 打印This is a defined temperature unit, so initialization succeeded.
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("This is not a defined temperature unit, so initialization failed.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."
// 打印This is not a defined temperature unit, so initialization failed.
```
<a name="failable_initializers_for_enumerations_with_raw_values"></a>
### 带原始值的枚举类型的可失败构造器
### 带原始值的枚举类型的可失败构造器 {#failable-initializers-for-enumerations-with-raw-values}
带原始值的枚举类型会自带一个可失败构造器 `init?(rawValue:)`,该可失败构造器有一个合适的原始值类型的 `rawValue` 形参,选择找到的相匹配的枚举成员,找不到则构造失败。
@ -852,19 +824,18 @@ let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// 打印 "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.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."
// 打印This is not a defined temperature unit, so initialization failed.
```
<a name="propagation_of_initialization_failure"></a>
### 构造失败的传递
### 构造失败的传递 {#propagation-of-initialization-failure}
结构体枚举的可失败构造器可以横向代理到它们自己其他的可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
结构体枚举的可失败构造器可以横向代理到它们自己其他的可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。
@ -901,7 +872,7 @@ class CartItem: Product {
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印 "Item: sock, quantity: 2"
// 打印Item: sock, quantity: 2
```
倘若你以一个值为 0 的 `quantity` 来创建一个 `CartItem` 实例,那么将导致 `CartItem` 构造器失败:
@ -912,7 +883,7 @@ 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
```
同样地,如果你尝试传入一个值为空字符串的 `name` 来创建一个 `CartItem` 实例,那么将导致父类 `Product` 的构造过程失败:
@ -923,11 +894,10 @@ 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
```
<a name="overriding_a_failable_initializer"></a>
### 重写一个可失败构造器
### 重写一个可失败构造器 {#overriding-a-failable-initializer}
如同其它的构造器,你可以在子类中重写父类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器。这使你可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败。
@ -985,15 +955,13 @@ class UntitledDocument: Document {
在这个例子中,如果在调用父类的可失败构造器 `init?(name:)` 时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过字符串常量来调用它,构造器不会失败,所以并不会发生运行时错误。
<a name="the_init!_failable_initializer"></a>
### init! 可失败构造器
### init! 可失败构造器 {#the-init!-failable-initializer}
通常来说我们通过在 `init` 关键字后添加问号的方式(`init?`)来定义一个可失败构造器,但你也可以通过在 `init` 后面添加感叹号的方式来定义一个可失败构造器(`init!`),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。
你可以在 `init?` 中代理到 `init!`,反之亦然。你也可以用 `init?` 重写 `init!`,反之亦然。你还可以用 `init` 代理到 `init!`,不过,一旦 `init!` 构造失败,则会触发一个断言。
<a name="required_initializers"></a>
## 必要构造器
## 必要构造器 {#required-initializers}
在类的构造器前添加 `required` 修饰符表明所有该类的子类都必须实现该构造器:
@ -1019,8 +987,7 @@ class SomeSubclass: SomeClass {
>
> 如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。
<a name="setting_a_default_property_value_with_a_closure_or_function"></a>
## 通过闭包或函数设置属性的默认值
## 通过闭包或函数设置属性的默认值 {#setting-a-default-property-value-with-a-closure-or-function}
如果某个存储型属性的默认值需要一些自定义或设置,你可以使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被构造时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
@ -1077,7 +1044,7 @@ struct Chessboard {
```swift
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// 打印 "true"
// 打印true
print(board.squareIsBlackAt(row: 7, column: 7))
// 打印 "false”
// 打印false”
```

View File

@ -2,10 +2,9 @@
*析构器*只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字 `deinit` 来标示,类似于构造器要用 `init` 来标示。
<a name="how_deinitialization_works"></a>
## 析构过程原理
## 析构过程原理 {#how-deinitialization-works}
Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](./23_Automatic_Reference_Counting.html)章节中所讲述Swift 通过*自动引用计数ARC)* 处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](./23_Automatic_Reference_Counting.md)章节中所讲述Swift 通过*自动引用计数ARC)* 处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数和圆括号,如下所示:
@ -19,8 +18,7 @@ deinit {
因为直到实例的析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性,并且可以根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。
<a name="deinitializers_in_action"></a>
## 析构器实践
## 析构器实践 {#deinitializers-in-action}
这是一个析构器实践的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是 `Bank``Player``Bank` 类管理一种虚拟硬币,确保流通的硬币数量永远不可能超过 10,000。在游戏中有且只能有一个 `Bank` 存在,因此 `Bank` 用类来实现,并使用类型属性和类型方法来存储和管理其当前状态。
@ -68,9 +66,9 @@ class Player {
```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"
// 打印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"
// 打印There are now 9900 coins left in the bank
```
创建一个 `Player` 实例的时候,会向 `Bank` 对象申请得到 100 个硬币,前提是有足够的硬币可用。这个 `Player` 实例存储在一个名为 `playerOne` 的可选类型的变量中。这里使用了一个可选类型的变量,是因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前是否在游戏中。
@ -80,9 +78,9 @@ print("There are now \(Bank.coinsInBank) coins left in the bank")
```swift
playerOne!.win(coins: 2_000)
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
// 打印 "PlayerOne won 2000 coins & now has 2100 coins"
// 打印PlayerOne won 2000 coins & now has 2100 coins
print("The bank now only has \(Bank.coinsInBank) coins left")
// 打印 "The bank now only has 7900 coins left"
// 打印The bank now only has 7900 coins left
```
在这里,玩家已经赢得了 2,000 枚硬币,所以玩家的钱包中现在有 2,100 枚硬币,而 `Bank` 对象只剩余 7,900 枚硬币。
@ -90,9 +88,9 @@ print("The bank now only has \(Bank.coinsInBank) coins left")
```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` 实例,因此该实例会被释放,以便回收内存。在这之前,该实例的析构器被自动调用,玩家的硬币被返还给银行。

View File

@ -6,8 +6,7 @@
>
> Swift 的可选链式调用和 Objective-C 中向 `nil` 发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
<a name="optional_chaining_as_an_alternative_to_forced_unwrapping"></a>
## 使用可选链式调用代替强制展开
## 使用可选链式调用代替强制展开 {#optional-chaining-as-an-alternative-to-forced-unwrapping}
通过在想调用的属性、方法,或下标的可选值后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。
@ -54,7 +53,7 @@ if let roomCount = john.residence?.numberOfRooms {
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “Unable to retrieve the number of rooms.”
// 打印“Unable to retrieve the number of rooms.”
```
`residence` 后面添加问号之后Swift 就会在 `residence` 不为 `nil` 的情况下访问 `numberOfRooms`
@ -77,11 +76,10 @@ if let roomCount = john.residence?.numberOfRooms {
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “John's residence has 1 room(s).”
// 打印“John's residence has 1 room(s).”
```
<a name="defining_model_classes_for_optional_chaining"></a>
## 为可选链式调用定义模型类
## 为可选链式调用定义模型类 {#defining-model-classes-for-optional-chaining}
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法和下标。
@ -156,8 +154,7 @@ class Address {
`Address` 类提供了 `buildingIdentifier()` 方法,返回值为 `String?`。 如果 `buildingName` 有值则返回 `buildingName`。或者,如果 `buildingNumber``street` 均有值,则返回两者拼接得到的字符串。否则,返回 `nil`
<a name="accessing_properties_through_optional_chaining"></a>
## 通过可选链式调用访问属性
## 通过可选链式调用访问属性 {#accessing-properties-through-optional-chaining}
正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
@ -170,7 +167,7 @@ if let roomCount = john.residence?.numberOfRooms {
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “Unable to retrieve the number of rooms.”
// 打印“Unable to retrieve the number of rooms.”
```
因为 `john.residence``nil`,所以这个可选链式调用依旧会像先前一样失败。
@ -186,7 +183,7 @@ john.residence?.address = someAddress
在这个例子中,通过 `john.residence` 来设定 `address` 属性也会失败,因为 `john.residence` 当前为 `nil`
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例,然后将该实例返回用于赋值。该函数会在返回前打印 “Function was called”这使你能验证等号右侧的代码是否被执行。
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”这使你能验证等号右侧的代码是否被执行。
```swift
func createAddress() -> Address {
@ -203,8 +200,7 @@ john.residence?.address = createAddress()
没有任何打印消息,可以看出 `createAddress()` 函数并未被执行。
<a name="calling_methods_through_optional_chaining"></a>
## 通过可选链式调用来调用方法
## 通过可选链式调用来调用方法 {#calling-methods-through-optional-chaining}
可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。
@ -216,7 +212,7 @@ func printNumberOfRooms() {
}
```
这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如[无返回值函数](./06_Functions.html#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。
这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如[无返回值函数](./06_Functions.md#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。
如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是 `Void?`,而不是 `Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用 `if` 语句来判断能否成功调用 `printNumberOfRooms()` 方法,即使方法本身没有定义返回值。通过判断返回值是否为 `nil` 可以判断调用是否成功:
@ -226,7 +222,7 @@ if john.residence?.printNumberOfRooms() != nil {
} else {
print("It was not possible to print the number of rooms.")
}
// 打印 “It was not possible to print the number of rooms.”
// 打印“It was not possible to print the number of rooms.”
```
同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的[通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)的例子中,我们尝试给 `john.residence` 中的 `address` 属性赋值,即使 `residence``nil`。通过可选链式调用给属性赋值会返回 `Void?`,通过判断返回值是否为 `nil` 就可以知道赋值是否成功:
@ -237,11 +233,10 @@ if (john.residence?.address = someAddress) != nil {
} else {
print("It was not possible to set the address.")
}
// 打印 “It was not possible to set the address.”
// 打印“It was not possible to set the address.”
```
<a name="accessing_subscripts_through_optional_chaining"></a>
## 通过可选链式调用访问下标
## 通过可选链式调用访问下标 {#accessing-subscripts-through-optional-chaining}
通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。
@ -257,7 +252,7 @@ if let firstRoomName = john.residence?[0].name {
} else {
print("Unable to retrieve the first room name.")
}
// 打印 “Unable to retrieve the first room name.”
// 打印“Unable to retrieve the first room name.”
```
在这个例子中,问号直接放在 `john.residence` 的后面,并且在方括号的前面,因为 `john.residence` 是可选值。
@ -283,11 +278,10 @@ if let firstRoomName = john.residence?[0].name {
} else {
print("Unable to retrieve the first room name.")
}
// 打印 “The first room name is Living Room.”
// 打印“The first room name is Living Room.”
```
<a name="accessing_subscripts_of_optional_type"></a>
### 访问可选类型的下标
### 访问可选类型的下标 {#accessing-subscripts-of-optional-type}
如果下标返回可选类型值,比如 Swift 中 `Dictionary` 类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用:
@ -301,8 +295,7 @@ testScores["Brian"]?[0] = 72
上面的例子中定义了一个 `testScores` 数组,包含了两个键值对,分别把 `String` 类型的键映射到一个 `Int` 值的数组。这个例子用可选链式调用把 `"Dave"` 数组中第一个元素设为 `91`,把 `"Bev"` 数组的第一个元素 `+1`,然后尝试把 `"Brian"` 数组中的第一个元素设为 `72`。前两个调用成功,因为 `testScores` 字典中包含 `"Dave"``"Bev"` 这两个键。但是 `testScores` 字典中没有 `"Brian"` 这个键,所以第三个调用失败。
<a name="linking_multiple_levels_of_chaining"></a>
## 连接多层可选链式调用
## 连接多层可选链式调用 {#linking-multiple-levels-of-chaining}
可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。
@ -324,7 +317,7 @@ if let johnsStreet = john.residence?.address?.street {
} else {
print("Unable to retrieve the address.")
}
// 打印 “Unable to retrieve the address.”
// 打印“Unable to retrieve the address.”
```
`john.residence` 现在包含一个有效的 `Residence` 实例。然而,`john.residence.address` 的值当前为 `nil`。因此,调用 `john.residence?.address?.street` 会失败。
@ -344,13 +337,12 @@ if let johnsStreet = john.residence?.address?.street {
} else {
print("Unable to retrieve the address.")
}
// 打印 “John's street name is Laurel Street.”
// 打印“John's street name is Laurel Street.”
```
在上面的例子中,因为 `john.residence` 包含一个有效的 `Address` 实例,所以对 `john.residence``address` 属性赋值将会成功。
<a name="chaining_on_methods_with_optional_return_values"></a>
## 在方法的可选返回值上进行可选链式调用
## 在方法的可选返回值上进行可选链式调用 {#chaining-on-methods-with-optional-return-values}
上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。
@ -360,14 +352,13 @@ if let johnsStreet = john.residence?.address?.street {
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// 打印 “John's building identifier is The Larches.”
// 打印“John's building identifier is The Larches.”
```
如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:
```swift
if let beginsWithThe =
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
if beginsWithThe {
print("John's building identifier begins with \"The\".")
@ -375,7 +366,7 @@ if let beginsWithThe =
print("John's building identifier does not begin with \"The\".")
}
}
// 打印 “John's building identifier begins with "The".”
// 打印“John's building identifier begins with "The".”
```
> 注意

View File

@ -10,8 +10,7 @@
>
> Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的 `NSError`。更多详情参见 [用 Swift 解决 Cocoa 错误](https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift)。
<a name="representing_and_throwing_errors"></a>
## 表示与抛出错误
## 表示与抛出错误 {#representing-and-throwing-errors}
在 Swift 中,错误用遵循 `Error` 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
@ -31,8 +30,7 @@ enum VendingMachineError: Error {
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
```
<a name="handling_errors"></a>
## 处理错误
## 处理错误 {#handling-errors}
某个错误被抛出时,附近的某部分代码必须负责处理这个错误,例如纠正这个问题、尝试另外一种方式、或是向用户报告错误。
@ -44,8 +42,7 @@ Swift 中有 `4` 种处理错误的方式。你可以把函数抛出的错误传
>
> Swift 中的错误处理和其他语言中用 `try``catch` 和 `throw` 进行异常处理很像。和其他语言中(包括 Objective-C 的异常处理不同的是Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,`throw` 语句的性能特性是可以和 `return` 语句相媲美的。
<a name="propagating_errors_using_throwing_functions"></a>
### 用 throwing 函数传递错误
### 用 throwing 函数传递错误 {#propagating-errors-using-throwing-functions}
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数之后加上 `throws` 关键字。一个标有 `throws` 关键字的函数被称作 *throwing 函数*。如果这个函数指明了返回值类型,`throws` 关键词需要写在返回箭头(`->`)的前面。
@ -131,7 +128,7 @@ struct PurchasedSnack {
}
```
### 用 Do-Catch 处理错误
### 用 Do-Catch 处理错误 {#handling-errors-using-do-Catch}
你可以使用一个 `do-catch` 语句运行一段闭包代码来处理错误。如果在 `do` 子句中的代码抛出了一个错误,这个错误会与 `catch` 子句做匹配,从而决定哪条子句能处理它。
@ -169,7 +166,7 @@ do {
} catch {
print("Unexpected error: \(error).")
}
// 打印 “Insufficient funds. Please insert an additional 2 coins.”
// 打印“Insufficient funds. Please insert an additional 2 coins.”
```
上面的例子中,`buyFavoriteSnack(person:vendingMachine:)` 函数在一个 `try` 表达式中被调用,是因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到 `catch` 子句中,并判断这个错误是否要被继续传递下去。如果错误没有被匹配,它会被最后一个 `catch` 语句捕获,并赋值给一个 `error` 常量。如果没有错误被抛出,`do` 子句中余下的语句就会被执行。
@ -192,14 +189,14 @@ do {
} catch {
print("Unexpected non-vending-machine-related error: \(error)")
}
// 打印 "Invalid selection, out of stock, or not enough money."
// 打印Invalid selection, out of stock, or not enough money.
```
如果 `vend(itemNamed:)` 抛出的是一个 `VendingMachineError` 类型的错误,`nourish(with:)` 会打印一条消息,否则 `nourish(with:)` 会将错误抛给它的调用方。这个错误之后会被通用的 `catch` 语句捕获。
### 将错误转换成可选值
### 将错误转换成可选值 {#converting_errors_to_optional_values}
可以使用 `try?` 通过将错误转换成一个可选值来处理错误。如果在评估 `try?` 表达式时一个错误被抛出,那么表达式的值就是 `nil`。例如,在下面的代码中,`x``y` 有着相同的数值和等价的含义:
可以使用 `try?` 通过将错误转换成一个可选值来处理错误。如果是在计算 `try?` 表达式时抛出错误,该表达式的结果就为 `nil`。例如,在下面的代码中,`x``y` 有着相同的数值和等价的含义:
```swift
func someThrowingFunction() throws -> Int {
@ -228,7 +225,7 @@ func fetchData() -> Data? {
}
```
### 禁用错误传递
### 禁用错误传递 {#disabling_error_propagation}
有时你知道某个 `throwing` 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写 `try!` 来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。
@ -238,8 +235,7 @@ func fetchData() -> Data? {
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
```
<a name="specifying_cleanup_actions"></a>
## 指定清理操作
## 指定清理操作 {#specifying-cleanup-actions}
你可以使用 `defer` 语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,或是由于诸如 `return``break` 的语句。例如,你可以用 `defer` 语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。

View File

@ -4,10 +4,9 @@
类型转换在 Swift 中使用 `is``as` 操作符实现。这两个操作符分别提供了一种简单达意的方式去检查值的类型或者转换它的类型。
你也可以用它来检查一个类型是否遵循了某个协议,就像在[检验协议遵循](./21_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
你也可以用它来检查一个类型是否遵循了某个协议,就像在[检验协议遵循](./21_Protocols.md#checking_for_protocol_conformance)部分讲述的一样。
<a name="defining_a_class_hierarchy_for_type_casting"></a>
## 为类型转换定义类层次
## 为类型转换定义类层次 {#defining-a-class-hierarchy-for-type-casting}
你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了这些类实例的数组,作为类型转换的例子。
@ -57,8 +56,7 @@ let library = [
在幕后 `library` 里存储的媒体项依然是 `Movie``Song` 类型的。但是,若你迭代它,依次取出的实例会是 `MediaItem` 类型的,而不是 `Movie``Song` 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。
<a name="checking_type"></a>
## 检查类型
## 检查类型 {#checking-type}
用*类型检查操作符*`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`
@ -77,7 +75,7 @@ for item in library {
}
print("Media library contains \(movieCount) movies and \(songCount) songs")
// 打印 "Media library contains 2 movies and 3 songs"
// 打印Media library contains 2 movies and 3 songs
```
示例迭代了数组 `library` 中的所有项。每一次,`for-in` 循环设置
@ -86,8 +84,7 @@ print("Media library contains \(movieCount) movies and \(songCount) songs")
若当前 `MediaItem` 是一个 `Movie` 类型的实例,`item is Movie` 返回
`true`,否则返回 `false`。同样的,`item is Song` 检查 `item` 是否为 `Song` 类型的实例。在循环结束后,`movieCount``songCount` 的值就是被找到的属于各自类型的实例的数量。
<a name="downcasting"></a>
## 向下转型
## 向下转型 {#downcasting}
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试用*类型转换操作符*`as?``as!`)向下转到它的子类型。
@ -132,8 +129,7 @@ for item in library {
>
> 转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。
<a name="type_casting_for_any_and_anyobject"></a>
## `Any` 和 `AnyObject` 的类型转换
## `Any` 和 `AnyObject` 的类型转换 {#type-casting-for-any-and-anyobject}
Swift 为不确定类型提供了两种特殊的类型别名:
@ -200,8 +196,6 @@ for thing in things {
> 注意
>
> `Any` 类型可以表示所有类型的值包括可选类型。Swift 会在你用 `Any` 类型来表示一个可选值的时候,给你一个警告。如果你确实想使用 `Any` 类型来承载可选值,你可以使用 `as` 操作符显式转换为 `Any`,如下所示:
>
>
```swift
let optionalNumber: Int? = 3

View File

@ -4,8 +4,7 @@
要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的 `{}` 内,而且可以根据需要定义多级嵌套。
<a name="nested_types_in_action"></a>
## 嵌套类型实践
## 嵌套类型实践 {#nested-types-in-action}
下面这个例子定义了一个结构体 `BlackjackCard`(二十一点),用来模拟 `BlackjackCard` 中的扑克牌点数。`BlackjackCard` 结构体包含两个嵌套定义的枚举类型 `Suit``Rank`
@ -62,26 +61,25 @@ struct BlackjackCard {
`Rank` 还定义了一个计算型属性 `values`,它将会返回一个 `Values` 结构体的实例。这个计算型属性会根据牌的面值,用适当的数值去初始化 `Values` 实例。对于 `J``Q``K``Ace` 这四种牌,会使用特殊数值。对于数字面值的牌,使用枚举实例的 `Int` 类型的原始值。
`BlackjackCard` 结构体拥有两个属性—— `rank``suit`。它也同样定义了一个计算型属性 `description``description` 属性用 `rank``suit` 中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型 `second` 是否有值,若有值,则在原有的描述中增加对 `second` 的描述。
`BlackjackCard` 结构体拥有两个属性——`rank``suit`。它也同样定义了一个计算型属性 `description``description` 属性用 `rank``suit` 中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型 `second` 是否有值,若有值,则在原有的描述中增加对 `second` 的描述。
因为 `BlackjackCard` 是一个没有自定义构造器的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#memberwise_initializers_for_structure_types)中可知,结构体有默认的成员构造器,所以你可以用默认的构造器去初始化新常量 `theAceOfSpades`
因为 `BlackjackCard` 是一个没有自定义构造器的结构体,在[结构体的逐一成员构造器](./14_Initialization.md#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` 中,但它们的类型仍可从上下文中推断出来,所以在初始化实例时能够单独通过成员名称(`.ace``.spades`)引用枚举实例。在上面的例子中,`description` 属性正确地反映了黑桃 A 牌具有 `1``11` 两个值。
<a name="referring_to_nested_types"></a>
## 引用嵌套类型
## 引用嵌套类型 {#referring-to-nested-types}
在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀:
```swift
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// 红心符号为 “♡”
// 红心符号为“♡”
```
对于上面这个例子,这样可以使 `Suit``Rank``Values` 的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。

View File

@ -17,8 +17,9 @@ Swift 中的扩展可以:
>
> 扩展可以给一个类型添加新的功能,但是不能重写已经存在的功能。
## 扩展的语法
使用 **extension** 关键字声明扩展:
## 扩展的语法 {#extension-syntax}
使用 `extension` 关键字声明扩展:
```swift
extension SomeType {
@ -42,8 +43,9 @@ extension SomeType: SomeProtocol, AnotherProtocol {
>
> 对一个现有的类型,如果你定义了一个扩展来添加新的功能,那么这个类型的所有实例都可以使用这个新功能,包括那些在扩展定义之前就存在的实例。
## 计算型属性
扩展可以给现有类型添加计算型实例属性和计算型类属性。这个例子给 Swift 内建的 **Double** 类型添加了五个计算型实例属性,从而提供与距离单位相关工作的基本支持:
## 计算型属性 {#computed-properties}
扩展可以给现有类型添加计算型实例属性和计算型类属性。这个例子给 Swift 内建的 `Double` 类型添加了五个计算型实例属性,从而提供与距离单位相关工作的基本支持:
```swift
extension Double {
@ -55,40 +57,41 @@ extension Double {
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// 打印“一英寸是 0.0254 米”
// 打印“One inch is 0.0254 meters”
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// 打印“三英尺是 0.914399970739201
// 打印“Three feet is 0.914399970739201 meters
```
这些计算型属性表示的含义是把一个 **Double** 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。
这些计算型属性表示的含义是把一个 `Double` 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。
在上述例子中,**Double** 类型的 **1.0** 代表的是“一米”。这就是为什么计算型属性 **m** 返回的是 **self** - 表达式 **1.m** 被认为是计算一个 **Double** 类型的 **1.0**
在上述例子中,`Double` 类型的 `1.0` 代表的是“一米”。这就是为什么计算型属性 `m` 返回的是 `self`——表达式 `1.m` 被认为是计算一个 `Double` 类型的 `1.0`
其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 **km** 要把值乘以 **1_000.00** 来实现千米到米的单位换算。类似地,一米有 3.28084 英尺,所以计算型属性 **ft** 要把对应的 **Double** 值除以 **3.28084**,来实现英尺到米的单位换算。
其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 `km` 要把值乘以 `1_000.00` 来实现千米到米的单位换算。类似地,一米有 3.28084 英尺,所以计算型属性 `ft` 要把对应的 `Double` 值除以 `3.28084`,来实现英尺到米的单位换算。
这些属性都是只读的计算型属性,所以为了简便,它们的表达式里面都不包含 **get** 关键字。它们使用 **Double** 作为返回值类型,并可用于所有接受 **Double** 类型的数学计算中:
这些属性都是只读的计算型属性,所以为了简便,它们的表达式里面都不包含 `get` 关键字。它们使用 `Double` 作为返回值类型,并可用于所有接受 `Double` 类型的数学计算中:
```swift
let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// 打印“马拉松赛跑全长 42195.0 米。
// 打印“A marathon is 42195.0 meters long
```
> 注意
>
> 扩展可以添加新的计算属性,但是它们不能添加存储属性,或向现有的属性添加属性观察者。
## 构造器
## 构造器 {#initializers}
扩展可以给现有的类型添加新的构造器。它使你可以把自定义类型作为参数来供其他类型的构造器使用,或者在类型的原始实现上添加额外的构造选项。
扩展可以给一个类添加新的便利构造器,但是它们不能给类添加新的指定构造器或者析构器。指定构造器和析构器必须始终由类的原始实现提供。
如果你使用扩展给一个值类型添加构造器只是用于给所有的存储属性提供默认值,并且没有定义任何自定义构造器,那么你可以在该值类型扩展的构造器中使用默认构造器和成员构造器。如果你把构造器写到了值类型的原始实现中,就像 [值类型的构造器委托](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID215) 中所描述的,那么就不属于在扩展中添加构造器。
如果你使用扩展给另一个模块中定义的结构体添加构造器,那么新的构造器直到定义模块中使用一个构造器之前,不能访问 **self**
如果你使用扩展给另一个模块中定义的结构体添加构造器,那么新的构造器直到定义模块中使用一个构造器之前,不能访问 `self`
在下面的例子中,自定义了一个的 **Rect** 结构体用来表示一个几何矩形。这个例子中还定义了两个给予支持的结构体 **Size** **Point**,它们都把属性的默认值设置为 **0.0**
在下面的例子中,自定义了一个的 `Rect` 结构体用来表示一个几何矩形。这个例子中还定义了两个给予支持的结构体 `Size``Point`,它们都把属性的默认值设置为 `0.0`
```swift
struct Size {
@ -103,7 +106,7 @@ struct Rect {
}
```
因为 **Rect** 结构体给所有的属性都提供了默认值,所以它自动获得了一个默认构造器和一个成员构造器,就像 [默认构造器](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID213) 中描述的一样。这些构造器可以用来创建新的 **Rect** 实例:
因为 `Rect` 结构体给所有的属性都提供了默认值,所以它自动获得了一个默认构造器和一个成员构造器,就像 [默认构造器](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID213) 中描述的一样。这些构造器可以用来创建新的 `Rect` 实例:
```swift
let defaultRect = Rect()
@ -111,7 +114,7 @@ let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
```
你可以通过扩展 **Rect** 结构体来提供一个允许指定 point size 的构造器:
你可以通过扩展 `Rect` 结构体来提供一个允许指定 pointsize 的构造器:
```swift
extension Rect {
@ -123,7 +126,7 @@ extension Rect {
}
```
这个新的构造器首先根据提供的 **center****size** 计算一个适当的原点。然后这个构造器调用结构体自带的成员构造器 **init(origin:size:)**,它会将新的 origin 和 size 值储存在适当的属性中:
这个新的构造器首先根据提供的 `center``size` 计算一个适当的原点。然后这个构造器调用结构体自带的成员构造器 `init(origin:size:)`,它会将新的 origin 和 size 值储存在适当的属性中:
```swift
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
@ -135,8 +138,9 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
>
> 如果你通过扩展提供一个新的构造器,你有责任确保每个通过该构造器创建的实例都是初始化完整的。
## 方法
扩展可以给现有类型添加新的实例方法和类方法。在下面的例子中,给 **Int** 类型添加了一个新的实例方法叫做 **repetitions**
## 方法 {#methods}
扩展可以给现有类型添加新的实例方法和类方法。在下面的例子中,给 `Int` 类型添加了一个新的实例方法叫做 `repetitions`
```swift
extension Int {
@ -148,9 +152,9 @@ extension Int {
}
```
**repetitions(task:)** 方法仅接收一个 **() -> Void** 类型的参数,它表示一个没有参数没有返回值的方法。
`repetitions(task:)` 方法仅接收一个 `() -> Void` 类型的参数,它表示一个没有参数没有返回值的方法。
定义了这个扩展之后,你可以对任意整形数值调用 **repetitions(task:)** 方法,来执行对应次数的任务:
定义了这个扩展之后,你可以对任意整形数值调用 `repetitions(task:)` 方法,来执行对应次数的任务:
```swift
3.repetitions {
@ -161,10 +165,11 @@ extension Int {
// Hello!
```
### 可变实例方法
通过扩展添加的实例方法同样也可以修改(或 *mutating(改变)*)实例本身。结构体和枚举的方法,若是可以修改 **self** 或者它自己的属性,则必须将这个实例方法标记为 **mutating**,就像是改变了方法的原始实现。
### 可变实例方法 {#mutating-instance-methods}
在下面的例子中,对 Swift **Int** 类型添加了一个新的 mutating 方法,叫做 **square**,它将原始值求平方:
通过扩展添加的实例方法同样也可以修改(或 *mutating改变*)实例本身。结构体和枚举的方法,若是可以修改 `self` 或者它自己的属性,则必须将这个实例方法标记为 `mutating`,就像是改变了方法的原始实现。
在下面的例子中,对 Swift 的 `Int` 类型添加了一个新的 mutating 方法,叫做 `square`,它将原始值求平方:
```swift
extension Int {
@ -177,11 +182,12 @@ someInt.square()
// someInt 现在是 9
```
## 下标
扩展可以给现有的类型添加新的下标。下面的例子中,对 Swift **Int** 类型添加了一个整数类型的下标。下标 **[n]** 从数字右侧开始,返回小数点后的第 **n** 位:
## 下标 {#subscripts}
- **123456789[0]** returns **9**
- **123456789[1]** returns **8**
扩展可以给现有的类型添加新的下标。下面的例子中,对 Swift 的 `Int` 类型添加了一个整数类型的下标。下标 `[n]` 从数字右侧开始,返回小数点后的第 `n` 位:
- `123456789[0]` 返回 `9`
- `123456789[1]` 返回 `8`
……以此类推:
```swift
@ -195,16 +201,16 @@ extension Int {
}
}
746381295[0]
// returns 5
// 返回 5
746381295[1]
// returns 9
// 返回 9
746381295[2]
// returns 2
// 返回 2
746381295[8]
// returns 7
// 返回 7
```
如果操作的 **Int** 值没有足够的位数满足所请求的下标,那么下标的现实将返回 **0**,将好像在数字的左边补上了 0
如果操作的 `Int` 值没有足够的位数满足所请求的下标,那么下标的现实将返回 `0`,将好像在数字的左边补上了 0
```swift
746381295[9]
@ -212,7 +218,8 @@ extension Int {
0746381295[9]
```
## 嵌套类型
## 嵌套类型 {#nested-yypes}
扩展可以给现有的类,结构体,还有枚举添加新的嵌套类型:
```swift
@ -233,11 +240,11 @@ extension Int {
}
```
这个例子给 **Int** 添加了一个新的嵌套枚举。这个枚举叫做 **Kind**,表示特定整数所代表的数字类型。具体来说,它表示数字是负的、零的还是正的。
这个例子给 `Int` 添加了一个新的嵌套枚举。这个枚举叫做 `Kind`,表示特定整数所代表的数字类型。具体来说,它表示数字是负的、零的还是正的。
这个例子同样给 **Int** 添加了一个新的计算型实例属性,叫做 **kind**,它返回被操作整数所对应的 **Kind** 枚举 case 分支。
这个例子同样给 `Int` 添加了一个新的计算型实例属性,叫做 `kind`,它返回被操作整数所对应的 `Kind` 枚举 case 分支。
现在,任意 **Int** 的值都可以使用这个嵌套类型:
现在,任意 `Int` 的值都可以使用这个嵌套类型:
```swift
func printIntegerKinds(_ numbers: [Int]) {
@ -254,13 +261,11 @@ func printIntegerKinds(_ numbers: [Int]) {
print("")
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
// 打印 "+ + - 0 - 0 + "
// 打印+ + - 0 - 0 +
```
方法 **printIntegerKinds(_:)**,使用一个 **Int** 类型的数组作为输入,然后依次迭代这些值。对于数组中的每一个整数,方法会检查它的 **kind** 计算型属性,然后打印适当的描述。
方法 `printIntegerKinds(_:)`,使用一个 `Int` 类型的数组作为输入,然后依次迭代这些值。对于数组中的每一个整数,方法会检查它的 `kind` 计算型属性,然后打印适当的描述。
> 注意
>
> **number.kind** 已经被认为是 **Int.Kind** 类型。所以,在 **switch** 语句中所有的 **Int.Kind** case 分支可以被缩写,就像使用 **.negative** 替代 **Int.Kind.negative.**
> `number.kind` 已经被认为是 `Int.Kind` 类型。所以,在 `switch` 语句中所有的 `Int.Kind` case 分支可以被缩写,就像使用 `.negative` 替代 `Int.Kind.negative.`

View File

@ -4,8 +4,7 @@
除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵循协议的类型就能够使用这些功能。
<a name="protocol_syntax"></a>
## 协议语法
## 协议语法 {#protocol-syntax}
协议的定义方式与类、结构体和枚举的定义非常相似:
@ -23,7 +22,7 @@ struct SomeStructure: FirstProtocol, AnotherProtocol {
}
```
拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:
若一个拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:
```swift
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
@ -31,14 +30,13 @@ class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
}
```
<a name="property_requirements"></a>
## 属性要求
## 属性要求 {#property-requirements}
协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是*可读可写的*。
协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型。此外,协议还指定属性是*可读*的还是*可读可写的*。
如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。
协议总是用 `var` 关键字来声明变量属性,在类型声明后加上 `{ set get }` 来表示属性是可读可写的,可读属性则用 `{ get }` 来表示:
协议总是用 `var` 关键字来声明变量属性,在类型声明后加上 `{ set get }` 来表示属性是*可读可写*的,*可读*属性则用 `{ get }` 来表示:
```swift
protocol SomeProtocol {
@ -69,7 +67,7 @@ protocol FullyNamed {
```swift
struct Person: FullyNamed {
var fullName: String
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 为 "John Appleseed"
@ -77,9 +75,9 @@ let john = Person(fullName: "John Appleseed")
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 `FullyNamed` 协议。
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好遵循`FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地遵循了协议。(如果协议要求未被完全遵循,在编译时会报错。)
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足`FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地符合了协议。(如果协议要求未被完全满足,在编译时会报错。)
下面是一个更为复杂的类,它适配并遵循了 `FullyNamed` 协议:
下面是一个更为复杂的类,它采纳并遵循了 `FullyNamed` 协议:
```swift
class Starship: FullyNamed {
@ -94,17 +92,16 @@ class Starship: FullyNamed {
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName "USS Enterprise"
// ncc1701.fullName "USS Enterprise"
```
`Starship` 类把 `fullName` 属性实现为只读的计算属性。每一个 `Starship` 类的实例都有一个名为 `name` 的非可选属性和一个名为 `prefix` 的可选属性。 当 `prefix` 存在时,计算属性 `fullName` 会将 `prefix` 插入到 `name` 之前,从而为星际飞船构建一个全名
`Starship` 类把 `fullName` 为只读的计算属性来实现。每一个 `Starship` 类的实例都有一个名为 `name` 的非可选属性和一个名为 `prefix` 的可选属性。 当 `prefix` 存在时,计算属性 `fullName` 会将 `prefix` 插入到 `name` 之前,从而得到一个带有 `prefix``fullName`
<a name="method_requirements"></a>
## 方法要求
## 方法要求 {#method-requirements}
协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认
协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法提供默认参数
正如属性要求中所述,在协议中定义类方法的时候,总是使用 `static` 关键字作为前缀。当类类型遵循协议时,除了 `static` 关键字,还可以使用 `class` 关键字作为前缀:
正如属性要求中所述,在协议中定义类方法的时候,总是使用 `static` 关键字作为前缀。即使在类实现时,类方法要求使用 `class``static` 作为关键字前缀,前面的规则仍然适用
```swift
protocol SomeProtocol {
@ -116,26 +113,26 @@ protocol SomeProtocol {
```swift
protocol RandomNumberGenerator {
func random() -> Double
func random() -> Double
}
```
`RandomNumberGenerator` 协议要求遵循协议的类型必须拥有一个名为 `random` 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值是从0.0但不包括1.0。
`RandomNumberGenerator` 协议要求遵循协议的类型必须拥有一个名为 `random` 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值是从 `0.0` 到(但不包括)`1.0`
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
如下所示,下边是一个遵循 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器linear congruential generator* 的伪随机数算法。
如下所示,下边是一个遵循并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器linear congruential generator* 的伪随机数算法。
```swift
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
@ -144,10 +141,9 @@ print("And another one: \(generator.random())")
// 打印 “And another one: 0.729023776863283”
```
<a name="mutating_method_requirements"></a>
## 异变方法要求
## 异变方法要求 {#mutating-method-requirements}
有时需要在方法中改变(或*异变*)方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在[在实例方法中修改值类型](./11_Methods.html#modifying_value_types_from_within_instance_methods)章节中有详细描述。
有时需要在方法中改变(或*异变*)方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在 [在实例方法中修改值类型](./11_Methods.md#modifying_value_types_from_within_instance_methods) 章节中有详细描述。
如果你在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加 `mutating` 关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。
@ -155,13 +151,13 @@ print("And another one: \(generator.random())")
>
> 实现协议中的 `mutating` 方法时,若是类类型,则不用写 `mutating` 关键字。而对于结构体和枚举,则必须写 `mutating` 关键字。
如下所示,`Togglable` 协议只要求实现一个名为 `toggle` 的实例方法。根据名称的暗示`toggle()` 方法将改变实例属性,从而切换遵循该协议类型的实例的状态。
如下所示,`Togglable` 协议只定义了一个名为 `toggle` 的实例方法。顾名思义`toggle()` 方法将改变实例属性,从而切换遵循该协议类型的实例的状态。
`toggle()` 方法在定义的时候,使用 `mutating` 关键字标记,这表明当它被调用时,该方法将会改变遵循协议的类型的实例:
```swift
protocol Togglable {
mutating func toggle()
mutating func toggle()
}
```
@ -186,8 +182,7 @@ lightSwitch.toggle()
// lightSwitch 现在的值为 .on
```
<a name="initializer_requirements"></a>
## 构造器要求
## 构造器要求 {#initializer-requirements}
协议可以要求遵循协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
@ -197,7 +192,7 @@ protocol SomeProtocol {
}
```
### 协议构造器要求的类实现
### 协议构造器要求的类实现 {#class-implementations-of-protocol-initializer-requirements}
你可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 `required` 修饰符:
@ -209,13 +204,13 @@ class SomeClass: SomeProtocol {
}
```
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能遵循协议。
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
关于 `required` 构造器的更多内容,请参考 [必要构造器](./14_Initialization.md#required_initializers)。
> 注意
>
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见[防止重写](./13_Inheritance.html#preventing_overrides)。
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见 [防止重写](./13_Inheritance.md#preventing_overrides)。
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 `required``override` 修饰符:
@ -239,16 +234,15 @@ class SomeSubClass: SomeSuperClass, SomeProtocol {
}
```
### 可失败构造器要求
### 可失败构造器要求 {#failable-initializer-requirements}
协议还可以为遵循协议的类型定义可失败构造器要求,详见[可失败构造器](./14_Initialization.html#failable_initializers)。
协议还可以为遵循协议的类型定义可失败构造器要求,详见 [可失败构造器](./14_Initialization.md#failable_initializers)。
遵循协议的类型可以通过可失败构造器(`init?`)或非可失败构造器(`init`)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(`init`)或隐式解包可失败构造器(`init!`)来满足。
<a name="protocols_as_types"></a>
## 协议作为类型
## 协议作为类型 {#protocols-as-types}
尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。
尽管协议本身并未实现任何功能,但是协议可以被当做一个功能完备的类型来使用。
协议可以像其他普通类型一样使用,使用场景如下:
@ -264,15 +258,15 @@ class SomeSubClass: SomeSuperClass, SomeProtocol {
```swift
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
```
@ -289,7 +283,7 @@ class Dice {
```swift
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("Random dice roll is \(d6.roll())")
print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
@ -298,8 +292,7 @@ for _ in 1...5 {
// Random dice roll is 4
```
<a name="delegation"></a>
## 委托
## 委托 {#delegation}
*委托*是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。
@ -317,44 +310,46 @@ protocol DiceGameDelegate {
}
```
`DiceGame` 协议可以被任意涉及骰子的游戏遵循。`DiceGameDelegate` 协议可以被任意类型遵循,用来追踪 `DiceGame` 的游戏过程。
`DiceGame` 协议可以被任意涉及骰子的游戏遵循。
如下所示,`SnakesAndLadders` 是 [控制流](./05_Control_Flow.html) 章节引入的蛇梯棋游戏的新版本。新版本使用 `Dice` 实例作为骰子,并且实现了 `DiceGame``DiceGameDelegate` 协议,后者用来记录游戏的过程:
`DiceGameDelegate` 协议可以被任意类型遵循,用来追踪 `DiceGame` 的游戏过程。为了防止强引用导致的循环引用问题,可以把协议声明为弱引用,更多相关的知识请看 [类实例之间的循环强引用](./23_Automatic_Reference_Counting.md#strong_reference_cycles_between_class_instances),当协议标记为类专属可以使 `SnakesAndLadders` 类在声明协议时强制要使用弱引用。若要声明类专属的协议就必须继承于 `AnyObject` ,更多请看 [类专属的协议](#class_only_protocol)。
如下所示,`SnakesAndLadders` 是 [控制流](./05_Control_Flow.md) 章节引入的蛇梯棋游戏的新版本。新版本使用 `Dice` 实例作为骰子,并且实现了 `DiceGame``DiceGameDelegate` 协议,后者用来记录游戏的过程:
```swift
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
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 delegate: DiceGameDelegate?
func play() {
square = 0
delegate?.gameDidStart(self)
gameLoop: while square != finalSquare {
let diceRoll = dice.roll()
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
switch square + diceRoll {
case finalSquare:
break gameLoop
case let newSquare where newSquare > finalSquare:
continue gameLoop
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self)
}
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = Array(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 delegate: DiceGameDelegate?
func play() {
square = 0
delegate?.gameDidStart(self)
gameLoop: while square != finalSquare {
let diceRoll = dice.roll()
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
switch square + diceRoll {
case finalSquare:
break gameLoop
case let newSquare where newSquare > finalSquare:
continue gameLoop
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self)
}
}
```
关于这个*蛇梯棋*游戏的详细描述请参阅 [中断Break](./05_Control_Flow.html#break)。
关于这个*蛇梯棋*游戏的详细描述请参阅 [中断Break](./05_Control_Flow.md#break)。
这个版本的游戏封装到了 `SnakesAndLadders` 类中,该类遵循了 `DiceGame` 协议,并且提供了相应的可读的 `dice` 属性和 `play()` 方法。( `dice` 属性在构造之后就不再改变,且协议只要求 `dice` 为可读的,因此将 `dice` 声明为常量属性。)
@ -392,7 +387,7 @@ class DiceGameTracker: DiceGameDelegate {
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在 `gameDidStart(_:)` 方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
无论当前进行的是何种游戏,由于 `game` 遵循 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice``sides` 属性的值。
无论当前进行的是何种游戏,由于 `game` 符合 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice``sides` 属性的值。
`DiceGameTracker` 的运行情况如下所示:
@ -410,14 +405,13 @@ game.play()
// The game lasted for 4 turns
```
<a name="adding_protocol_conformance_with_an_extension"></a>
## 在扩展里添加协议遵循
## 在扩展里添加协议遵循 {#adding-protocol-conformance-with-an-extension}
即便无法修改源代码,依然可以通过扩展令已有类型遵循协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以遵循协议中的相应要求。详情请在[扩展](./20_Extensions.html)章节中查看。
即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在 [扩展](./20_Extensions.md) 章节中查看。
> 注意
>
> 通过扩展令已有类型遵循协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
> 通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
@ -427,17 +421,17 @@ protocol TextRepresentable {
}
```
可以通过扩展,令先前提到的 `Dice` 类遵循 `TextRepresentable` 协议:
可以通过扩展,令先前提到的 `Dice`可以扩展来采纳和遵循 `TextRepresentable` 协议:
```swift
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
return "A \(sides)-sided dice"
}
}
```
通过扩展遵循协议,和在原始定义中遵循协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
通过扩展遵循并采纳协议,和在原始定义中遵循并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型:
@ -447,22 +441,21 @@ print(d12.textualDescription)
// 打印 “A 12-sided dice”
```
同样,`SnakesAndLadders` 类也可以通过扩展遵循 `TextRepresentable` 协议:
同样,`SnakesAndLadders` 类也可以通过扩展来采纳和遵循 `TextRepresentable` 协议:
```swift
extension SnakesAndLadders: TextRepresentable {
var textualDescription: String {
return "A game of Snakes and Ladders with \(finalSquare) squares"
}
return "A game of Snakes and Ladders with \(finalSquare) squares"
}
}
print(game.textualDescription)
// 打印 “A game of Snakes and Ladders with 25 squares”
```
<a name="Conditionally_Conforming_to_a_Protocol"></a>
## 有条件地遵循协议
## 有条件地遵循协议 {#Conditionally-Conforming-to-a-Protocol}
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你遵循协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见[泛型 Where 分句](./22_Generics.html##where_clauses)。
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见 [泛型 Where 分句](./22_Generics.md##where_clauses)。
下面的扩展让 `Array` 类型只要在存储遵循 `TextRepresentable` 协议的元素时就遵循 `TextRepresentable` 协议。
@ -478,10 +471,9 @@ print(myDice.textualDescription)
// 打印 "[A 6-sided dice, A 12-sided dice]"
```
<a name="declaring_protocol_adoption_with_an_extension"></a>
## 在扩展里声明遵循协议
## 在扩展里声明采纳协议 {#declaring-protocol-adoption-with-an-extension}
当一个类型已经遵循了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展遵循该协议:
当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空的扩展来让它采纳该协议:
```swift
struct Hamster {
@ -506,10 +498,9 @@ print(somethingTextRepresentable.textualDescription)
>
> 即使满足了协议的所有要求,类型也不会自动遵循协议,必须显式地遵循协议。
<a name="collections_of_protocol_types"></a>
## 协议类型的集合
## 协议类型的集合 {#collections-of-protocol-types}
协议类型可以在数组或者字典这样的集合中使用,在[协议类型](./21_Protocols.html##protocols_as_types)提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
协议类型可以在数组或者字典这样的集合中使用,在 [协议类型](./21_Protocols.md##protocols_as_types) 提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
```swift
let things: [TextRepresentable] = [game, d12, simonTheHamster]
@ -519,17 +510,16 @@ let things: [TextRepresentable] = [game, d12, simonTheHamster]
```swift
for thing in things {
print(thing.textualDescription)
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` 等类型,即使实例在幕后确实是这些类型中的一种。由于 `thing``TextRepresentable` 类型,任何 `TextRepresentable` 的实例都有一个 `textualDescription` 属性,所以在每次循环中可以安全地访问 `thing.textualDescription`
注意 `thing` 常量`TextRepresentable` 类型而不是 `Dice``DiceGame``Hamster` 等类型,即使实例在幕后确实是这些类型中的一种。由于 `thing``TextRepresentable` 类型,任何 `TextRepresentable` 的实例都有一个 `textualDescription` 属性,所以在每次循环中可以安全地访问 `thing.textualDescription`
<a name="protocol_inheritance"></a>
## 协议的继承
## 协议的继承 {#protocol-inheritance}
协议能够*继承*一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
@ -543,13 +533,13 @@ protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
```swift
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
var prettyTextualDescription: String { get }
}
```
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何遵循 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求遵循协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
如下所示,扩展 `SnakesAndLadders`,使其遵循 `PrettyTextRepresentable` 协议:
如下所示,扩展 `SnakesAndLadders`,使其遵循并符合 `PrettyTextRepresentable` 协议:
```swift
extension SnakesAndLadders: PrettyTextRepresentable {
@ -584,25 +574,23 @@ print(game.prettyTextualDescription)
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
```
<a name="class_only_protocol"></a>
## 类专属的协议
## 类专属的协议 {#class-only-protocol}
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型遵循(以及非结构体或者非枚举的类型)。
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)。
```swift
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// 这里是类专属协议的定义部分
}
```
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型遵循。如果尝试让结构体或枚举类型遵循 `SomeClassOnlyProtocol` 协议,则会导致编译时错误。
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳 `SomeClassOnlyProtocol`,则会导致编译时错误。
> 注意
>
> 当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看[结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)[类是引用类型](./09_Classes_and_Structures.html#classes_are_reference_types)。
> 当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看 [结构体和枚举是值类型](./09_Classes_and_Structures.md#structures_and_enumerations_are_value_types)[类是引用类型](./09_Classes_and_Structures.md#classes_are_reference_types)。
<a name="protocol_composition"></a>
## 协议合成
## 协议合成 {#protocol-composition}
要求一个类型同时遵循多个协议是很有用的。你可以使用*协议组合*来复合多个协议到一个要求里。协议组合行为就和你定义的临时局部协议一样拥有构成中所有协议的需求。协议组合不定义任何新的协议类型。
@ -629,11 +617,11 @@ wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”
```
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体遵循了这两个协议。
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体采纳了这两个协议。
`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged` 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数遵循这两个协议即可。
`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged` 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数符合这两个协议即可。
上面的例子创建了一个名为 `birthdayPerson``Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时遵循这两个协议,所以这个参数合法,函数将打印生日问候语。
上面的例子创建了一个名为 `birthdayPerson``Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。
这里有一个例子:将 Location 类和前面的 Named 协议进行组合:
@ -659,21 +647,20 @@ func beginConcert(in location: Location & Named) {
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// Prints "Hello, Seattle!"
// 打印 "Hello, Seattle!"
```
`beginConcert(in:)` 方法接受一个类型为 `Location & Named` 的参数,这意味着“任何 Location 的子类,并且遵循 Named 协议”。例如City 就满足这样的条件。
`beginConcert(in:)` 函数接受一个类型为 `Location & Named` 的参数,这意味着“任何 Location 的子类,并且遵循 Named 协议”。例如City 就满足这样的条件。
将 birthdayPerson 传入 `beginConcert(in:)` 函数是不合法的,因为 Person 不是一个 Location 的子类。就像,如果你新建一个类继承 Location但是没有遵循 Named 协议,用这个类的实例去调用 `beginConcert(in:)` 函数也是不合法的。
将 birthdayPerson 传入 `beginConcert(in:)` 函数是不合法的,因为 Person 不是 Location 的子类。同理,如果你新建一个类继承 Location但是没有遵循 Named 协议,用这个类的实例去调用 `beginConcert(in:)` 函数也是法的。
<a name="checking_for_protocol_conformance"></a>
## 检查协议遵循
## 检查协议一致性 {#checking-for-protocol-conformance}
你可以使用[类型转换](./18_Type_Casting.html)中描述的 `is``as` 操作符来检查协议遵循,即是否遵循了某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同
你可以使用[类型转换](./18_Type_Casting.md)中描述的 `is``as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换协议的语法与检查和转换类型是完全一样的
* `is` 用来检查实例是否遵循某个协议,若遵循则返回 `true`,否则返回 `false`
* `as?` 返回一个可选值,当实例遵循某个协议时,返回类型为协议类型的可选值,否则返回 `nil`
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,将触发运行时错误。
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`
@ -704,8 +691,8 @@ class Country: HasArea {
```swift
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
var legs: Int
init(legs: Int) { self.legs = legs }
}
```
@ -713,15 +700,15 @@ class Animal {
```swift
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
```
`objects` 数组使用字面量初始化,数组包含一个 `radius``2``Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs``4``Animal` 实例。
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否遵循 `HasArea` 协议:
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否符合 `HasArea` 协议:
```swift
for object in objects {
@ -736,24 +723,23 @@ for object in objects {
// Something that doesn't have an area
```
当迭代出的元素遵循 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea``HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
当迭代出的元素符合 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea``HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle``Country``Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。
<a name="optional_protocol_requirements"></a>
## 可选的协议要求
## 可选的协议要求 {#optional-protocol-requirements}
协议可以定义*可选要求*,遵循协议的类型可以选择是否实现这些要求。在协议中使用 `optional` 关键字作为前缀来定义可选要求。可选要求用在你需要和 Objective-C 打交道的代码中。协议和可选要求都必须带上 `@objc` 属性。标记 `@objc` 特性的协议只能被继承自 Objective-C 类的类或者 `@objc` 类遵循,其他类以及结构体和枚举均不能遵循这种协议。
使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值。
协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./16_Optional_Chaining.html)章节中查看。
协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./16_Optional_Chaining.md)章节中查看。
下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,包含两个可选要求:
下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,包含两个可选要求:
```swift
@objc protocol CounterDataSource {
@objc optional func incrementForCount(count: Int) -> Int
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
```
@ -771,7 +757,7 @@ class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count) {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
@ -786,7 +772,7 @@ class Counter {
这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `increment(forCount:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `increment(forCount:)` 方法,因为这个方法是可选的。因此,`increment(forCount:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `increment(forCount:)` 方法后边也加上了 `?`
调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅[连接多层可选链式调用](./16_Optional_Chaining)
调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅 [连接多层可选链式调用](./16_Optional_Chaining)
在调用 `increment(forCount:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成。
@ -815,12 +801,12 @@ for _ in 1...4 {
// 12
```
上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `ThreeSource` 的实例,然后调用 `increment()` 方法四次。和预期一样,每次调用都会将 `count` 的值增加 `3`.
上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `ThreeSource` 的实例,然后调用 `increment()` 方法 `4` 次。按照预期预期一样,每次调用都会将 `count` 的值增加 `3`.
下面是一个更为复杂的数据源 `TowardsZeroSource`,它将使得最后的值变为 `0`
```swift
@objc class TowardsZeroSource: NSObject, CounterDataSource {
class TowardsZeroSource: NSObject, CounterDataSource {
func increment(forCount count: Int) -> Int {
if count == 0 {
return 0
@ -833,7 +819,7 @@ for _ in 1...4 {
}
```
`TowardsZeroSource` 实现了 `CounterDataSource` 协议中的 `increment(forCount:)` 方法,以 `count` 参数为依据,计算出每次的增量。如果 `count` 已经为 `0`,此方法返回 `0`,以此表明之后不应再有增量操作发生。
`TowardsZeroSource` 实现了 `CounterDataSource` 协议中的 `increment(forCount:)` 方法,以 `count` 参数为依据,计算出每次的增量。如果 `count` 已经为 `0`,此方法返回 `0`,以此表明之后不应再有增量操作发生。
你可以使用 `TowardsZeroSource` 实例将 `Counter` 实例来从 `-4` 增加到 `0`。一旦增加到 `0`,数值便不会再有变动:
@ -851,8 +837,7 @@ for _ in 1...5 {
// 0
```
<a name="protocol_extensions"></a>
## 协议扩展
## 协议扩展 {#protocol-extensions}
协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。
@ -866,7 +851,7 @@ extension RandomNumberGenerator {
}
```
通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现无需任何额外修改:
通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现无需任何额外修改:
```swift
let generator = LinearCongruentialGenerator()
@ -876,8 +861,7 @@ print("And here's a random Boolean: \(generator.randomBool())")
// 打印 “And here's a random Boolean: true”
```
<a name="providing_default_implementations"></a>
### 提供默认实现
### 提供默认实现 {#providing-default-implementations}
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
@ -885,7 +869,7 @@ print("And here's a random Boolean: \(generator.randomBool())")
>
> 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。
例如,`PrettyTextRepresentable` 协议继承自 `TextRepresentable` 协议,可以为其提供一个默认的 `prettyTextualDescription` 属性,只是简单地返回 `textualDescription` 属性的值:
例如,`PrettyTextRepresentable` 协议继承自 `TextRepresentable` 协议,可以为其提供一个默认的 `prettyTextualDescription` 属性简单地返回 `textualDescription` 属性的值:
```swift
extension PrettyTextRepresentable {
@ -895,12 +879,11 @@ extension PrettyTextRepresentable {
}
```
<a name="adding_constraints_to_protocol_extensions"></a>
### 为协议扩展添加限制条件
### 为协议扩展添加限制条件 {#adding-constraints-to-protocol-extensions}
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型 Where 子句](./22_Generics.html#where_clauses)中所描述的。
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型 Where 子句](./22_Generics.md#where_clauses)中所描述的。
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==``!=` 操作符来检查两个元素的等价性和非等价性。
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==``!=` 操作符来检查两个元素的等价性和非等价性。
```swift
extension Collection where Element: Equatable {
@ -917,7 +900,6 @@ extension Collection where Element: Equatable {
如果集合中的所有元素都一致,`allEqual()` 方法才返回 `true`
看看两个整数数组,一个数组的所有元素都是一样的,另一个不一样:
```swift
@ -927,7 +909,6 @@ let differentNumbers = [100, 100, 200, 100, 200]
由于数组遵循 `Collection` 而且整数遵循 `Equatable``equalNumbers``differentNumbers` 都可以使用 `allEqual()` 方法。
```swift
print(equalNumbers.allEqual())
// 打印 "true"
@ -937,4 +918,4 @@ print(differentNumbers.allEqual())
> 注意
>
> 如果一个遵循的类型满足了为同一方法或属性提供实现的多个限制型扩展的要求, Swift 使用这个实现方法去匹配那个最特殊的限制
> 如果一个遵循的类型满足了为同一方法或属性提供实现的多个限制型扩展的要求, Swift 使用最匹配限制的实现

View File

@ -1,11 +1,10 @@
# 泛型
*泛型代码*让你能根据自定义的需求,编写出适用于任意类型、灵活可用的函数及类型。它能让你避免代码的重复,用一种清晰抽象的方式来表达代码的意图。
*泛型代码*让你能根据自定义的需求,编写出适用于任意类型、灵活可用的函数及类型。你可避免编写重复的代码,用一种清晰抽象的方式来表达代码的意图。
泛型是 Swift 最强大的特性之一,多 Swift 标准库是通过泛型代码构建的。事实上,泛型的使用贯穿了整本语言手册,只是你可能没有发现而已。例如Swift 的 `Array``Dictionary` 都是泛型集合。你可以创建一个 `Int` 数组,也可创建一个 `String` 数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建存储任意指定类型的字典。
泛型是 Swift 最强大的特性之一,多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在*语言指南*中使用泛型。例如Swift 的 `Array``Dictionary` 都是泛型集合。你可以创建一个 `Int` 类型数组,也可创建一个 `String` 类型数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建一个存储任意指定类型的字典,并对该类型没有限制
<a name="the_problem_that_generics_solve"></a>
## 泛型所解决的问题
## 泛型解决的问题 {#the-problem-that-generics-solve}
下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值:
@ -17,19 +16,19 @@ func swapTwoInts(_ a: inout Int, _ b: inout Int) {
}
```
这个函数使用输入输出参数(`inout`)来交换 `a``b` 的值,请参考[输入输出参数](./06_Functions.html#in_out_parameters)。
这个函数使用输入输出参数(`inout`)来交换 `a``b` 的值,具体请参考[输入输出参数](./06_Functions.md#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(_ a: inout String, _ b: inout String) {
@ -45,7 +44,7 @@ func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
}
```
你可能注意到 `swapTwoInts(_:_:)``swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)` 函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是 `Int``String``Double`
你可能注意到了,`swapTwoInts(_:_:)``swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)` 函数体是一样的,唯一的区别是它们接受的参数类型(`Int``String``Double`
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)
@ -53,10 +52,9 @@ func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
>
> 在上面三个函数中,`a` 和 `b` 类型必须相同。如果 `a` 和 `b` 类型不同那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。
<a name="generic_functions"></a>
## 泛型函数
## 泛型函数 {#generic-functions}
泛型函数可适用于任类型,下面 `swapTwoValues(_:_:)` 函数是上面三个函数的泛型版本:
泛型函数可适用于任类型,下面是函数 `swapTwoInts(_:_:)` 的泛型版本,命名为 `swapTwoValues(_:_:)`
```swift
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
@ -66,77 +64,74 @@ func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
}
```
`swapTwoValues(_:_:)` 的函数主体`swapTwoInts(_:_:)` 函数是一样的,它们只在第一行有点不同,如下所示:
`swapTwoValues(_:_:)``swapTwoInts(_:_:)` 函数体内容相同,它们只在第一行不同,如下所示:
```swift
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
```
这个函数的泛型版本使用了占位类型名(这里用字母 `T` 来表示)来代替实际类型名(例如 `Int``String``Double`。占位类型名没有指明 `T` 必须是什么类型,但是它指明了 `a` `b` 必须是同一类型 `T`,无论 `T` 代表什么类型。只有 `swapTwoValues(_:_:)` 函数在调用时,才会根据所传入的实际类型决定 `T` 所代表的类型
泛型版本的函数使用`占位符`类型名(这里叫做 `T` ),而不是 *实际*类型名(例如 `Int``String``Double``占位符`类型名并不关心 `T` 具体的类型,但它要求 `a`` b` 必须是相同的类型,`T` 的实际类型由每次调用 `swapTwoValues(_:_:)` 来决定
泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(`<T>`)。这个尖括号告诉 Swift 那个 `T``swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。
泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(`<T>`)。这个尖括号告诉 Swift 那个 `T``swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 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, and anotherInt 现在 3
// someInt 现在 107anotherInt 现在 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在 "world", and anotherString 现在 "hello"
// someString 现在是“world”,anotherString 现在是“hello
```
> 注意
>
> 上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。
<a name="type_parameters"></a>
## 类型参数
## 类型参数 {#type-parameters}
上面 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是类型参数的一个例子类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 `<T>`)。
上面 `swapTwoValues(_:_:)` 例子中,占位类型 `T`一个类型参数的例子类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 `<T>`)。
一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a``b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T``Int` 替换,第二次调用时,被 `String` 替换。)
你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。
<a name="naming_type_parameters"></a>
## 命名类型参数
## 命名类型参数 {#naming-type-parameters}
大多情况下,类型参数具有一个描述性名字,例如 `Dictionary<Key, Value>` 中的 `Key``Value`,以及 `Array<Element>` 中的 `Element`,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 `T``U``V`如上面演示 `swapTwoValues(_:_:)` 函数中的 `T` 一样
大多情况下,类型参数具有描述下的名称,例如字典 `Dictionary<Key, Value>` 中的 `Key``Value` 及数组 `Array<Element>` 中的 `Element`,这告诉阅读代码的人这些参数类型与泛型类型或函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字符来表示,例如 `T``U``V`如上面演示函数 `swapTwoValues(_:_:)` 中的 `T`
> 注意
>
> 请始终使用大写字母开头的驼峰命名法(例如 `T` 和 `MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。
<a name="generic_types"></a>
## 泛型类型
## 泛型类型 {#generic-types}
除了泛型函数Swift 还允许定义*泛型*类型。这些自定义类、结构体和枚举可以适用于*任何*类型,类似于 `Array``Dictionary`
除了泛型函数Swift 还允许定义*泛型类型*。这些自定义类、结构体和枚举可以适用于*任类型*,类似于 `Array``Dictionary`
这部分内容将向你展示如何编写一个名为 `Stack` (栈)的泛型集合类型。栈是一系列值的有序集合,和 `Array` 类似,但它相比 Swift 的 `Array` 类型有更多的操作限制。数组允许在数组的任意位置插入新元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素(称之为*入*栈)。类似的,栈也只能从末端移除元素(称之为*出*栈)。
本节将向你展示如何编写一个名为 `Stack`(栈)的泛型集合类型。栈是值的有序集合,和数组类似,但比数组有更严格的操作限制。数组允许在其中任意位置插入或是删除元素。而栈只允许在集合的末端添加新的元素(称之为栈)。类似的,栈也只能从末端移除元素(称之为栈)。
> 注意
>
> 栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。
下图展示了一个栈的入栈push和出栈pop的行为
下图展示了入栈push和出栈pop的行为
![此处输入图片的描述](https://docs.swift.org/swift-book/_images/stackPushPop_2x.png)
![](https://docs.swift.org/swift-book/_images/stackPushPop_2x.png)
1. 现在有三个值在栈中。
2. 第四个值被压入到栈的顶部。
3. 现在有四个值在栈中,最近入栈的那个值在顶部。
3. 现在栈中有四个值,最近入栈的那个值在顶部。
4. 栈中最顶部的那个值被移除出栈。
5. 一个值移除出栈后,现在栈又只有三个值了。
下面展示如何编写一个非泛型版本的栈,以 `Int` 型的栈为例:
下面展示如何编写一个非泛型版本的栈,以 `Int` 型的栈为例:
```swift
struct IntStack {
@ -150,16 +145,16 @@ struct IntStack {
}
```
这个结构体在栈中使用一个名为 `items` `Array` 属性来存储值。`Stack` 提供了两个方法:`push(_:)``pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。
这个结构体在栈中使用一个名为 `items`数组属性来存储值。栈提供了两个方法:`push(_:)``pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。
上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理*任意*类型的值。
上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。
下面是相同代码的泛型版本:
```swift
struct Stack<Element> {
var items = [Element]()
   mutating func push(_ item: Element) {
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
@ -168,15 +163,15 @@ struct Stack<Element> {
}
```
注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(`<Element>`)。
注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(<`Element`>)。
`Element` 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 `Element` 来引用。在这个例子中,`Element` 在如下三个地方被用作占位符:
- 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。
- 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。
- 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。
+ 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。
+ 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。
+ 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。
由于 `Stack` 是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,就像 `Array``Dictionary` 那样。
由于 `Stack` 是泛型类型,因此可以用来创建适用于 Swift 中任意有效类型的栈,就像 `Array``Dictionary` 那样。
你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack<String>()`
@ -189,27 +184,26 @@ stackOfStrings.push("cuatro")
// 栈中现在有 4 个字符串
```
下图展示了 `stackOfStrings` 如何将这四个值栈:
下图展示了 `stackOfStrings` 如何将这四个值栈:
![此处输入图片的描述](https://docs.swift.org/swift-book/_images/stackPushedFourStrings_2x.png)
![](https://docs.swift.org/swift-book/_images/stackPushedFourStrings_2x.png)
移除并返回栈顶部的值 `"cuatro"`,即将其出栈:
移除并返回栈顶部的值cuatro”,即出栈:
```swift
let fromTheTop = stackOfStrings.pop()
// fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串
// fromTheTop 的值为cuatro,现在栈中还有 3 个字符串
```
下图展示了 `stackOfStrings` 如何将顶部的值出栈:
下图展示了如何将顶部的值出栈:
![此处输入图片的描述](https://docs.swift.org/swift-book/_images/stackPoppedOneString_2x.png)
![](https://docs.swift.org/swift-book/_images/stackPoppedOneString_2x.png)
<a name="extending_a_generic_type"></a>
## 扩展一个泛型类型
## 泛型扩展 {#extending-a-generic-type}
你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。*原始*类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
对泛型类型进行扩展时,你并不需要提供类型参数列表作为定义的一部分。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶端的元素不会将其从栈中移除:
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶元素不会将其从栈中移除:
```swift
extension Stack {
@ -219,9 +213,9 @@ extension Stack {
}
```
`topItem` 属性会返回一个 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。
`topItem` 属性会返回 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。
注意这个扩展并没有定义一个类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。
注意这个扩展并没有定义类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。
计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素且不移除它:
@ -229,24 +223,22 @@ extension Stack {
if let topItem = stackOfStrings.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.”
```
<a name="type_constraints"></a>
## 类型约束
## 类型约束 {#type-constraints}
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任类型。不过,有的时候如果能将使用在泛型函数泛型类型中的类型添加一个特定的类型约束,将会是非常有用。类型约束可以指定一个类型参数必须继承自指定类,或者遵循一个特定的协议或协议组合。
`swapTwoValues(_:_:)` 函数和 `Stack` 用于任类型。不过,如果能对泛型函数泛型类型中添加特定的*类型约束*,这将在某些情况下非常有用。类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合。
例如Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary`键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
例如Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在 [字典的描述](./04_Collection_Types.md#dictionaries) 字典键的类型必须是可哈希hashable的。也就是说必须有一种方法能够唯一地表示它。字典键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,字典将无法判断是否可以插入或替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
为了实现这个要求,一个类型约束被强制加到 `Dictionary` 键类型上,要求其键类型必须遵循 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有 Swift 基本类型(例如 `String``Int``Double``Bool`)默认都是可哈希的。
这个要求通过 `Dictionary` 键类型上的类型约束实现,它指明了键必须遵循 Swift 标准库中定义的 `Hashable` 协议。所有 Swift 基本类型(例如 `String``Int``Double``Bool`)默认都是可哈希的。
你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
当自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。`可哈希hashable` 这种抽象概念根据它们的概念特征来描述类型,而不是它们的具体类型。
<a name="type_constraint_syntax"></a>
### 类型约束语法
### 类型约束语法 {#type-constraint-syntax}
你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型的语法与之相同):
在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束。下面将展示泛型函数约束的基本语法(与泛型类型的语法相同):
```swift
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
@ -254,12 +246,11 @@ func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
}
```
上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须遵循 `SomeProtocol` 协议的类型约束
上面这个函数有两个类型参数。第一个类型参数 `T` 必须是 `SomeClass` 子类;第二个类型参数 `U` 必须符合 `SomeProtocol` 协议。
<a name="type_constraints_in_action"></a>
### 类型约束实践
### 类型约束实践 {#type-constraints-in-action}
这里有个名为 `findIndex(ofString:in:)` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,` findIndex(ofString:in:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`
这里有个名为 `findIndex(ofString:in:)` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findIndex(ofString:in:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`
```swift
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
@ -272,19 +263,19 @@ func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
}
```
`findIndex(ofString:in:)` 函数可以用于查找字符串数组中的某个字符串:
`findIndex(ofString:in:)` 函数可以用于查找字符串数组中的某个字符串
```swift
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// 打印 “The index of llama is 2”
// 打印“The index of llama is 2”
```
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 `T` 替换 `String` 类型来写出具有相同功能的泛型函数 `findIndex(_:_:)`
下面展示了 `findIndex(ofString:in:)` 函数的泛型版本 `findIndex(ofString:in:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因会在例子后面说明:
下面展示了 `findIndex(ofString:in:)` 函数的泛型版本 `findIndex(of:in:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因将在后面说明:
```swift
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
@ -297,11 +288,11 @@ func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
}
```
上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。比如说,如果你创建一个自定义类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型 `T`,当你试图编译这部分代码时会出现相应的错误。
上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。例如,如果你自定义类或结构体来描述复杂的数据模型,对于这个类或结构体而言Swift 无法明确知道“相等”意味着什么。正因如此,这部分代码无法保证适用于任意类型 `T`,当你试图编译这部分代码时会出现相应的错误。
不过所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何遵循该协议的类型必须实现等式符(`==`)及不等符(`!=`),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。
任何 `Equatable` 类型都可以安全地使用在 `findIndex(of:in:)` 函数,因为其保证支持等式操作符。为了说明这个事,当定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
遵循 `Equatable` 协议的类型都可以安全地用于 `findIndex(of:in:)` 函数,因为其保证支持等式操作符。为了说明这个事,当定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
```swift
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
@ -314,9 +305,9 @@ func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
}
```
`findIndex(of:in:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何遵循 `Equatable` 协议的类型 `T`”。
`findIndex(of:in:)` 类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T`”。
`findIndex(of:in:)` 函数现在可以成功编译了,并且可以作用于任何遵循 `Equatable` 协议的类型,如 `Double``String`
`findIndex(of:in:)` 函数现在可以成功编译了,并且用于任何符合 `Equatable` 的类型,如 `Double``String`
```swift
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
@ -325,13 +316,11 @@ let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex 类型为 Int?,其值为 2
```
<a name="associated_types"></a>
## 关联类型
## 关联类型 {#associated-types}
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。*关联类型*为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被遵循时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型
定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位符名称,其代表的实际类型在协议被遵循时才会被指定。关联类型通过 `associatedtype` 关键字来指定。
<a name="associated_types_in_action"></a>
### 关联类型实践
### 关联类型实践 {#associated-types-in-action}
下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `Item`
@ -344,21 +333,21 @@ protocol Container {
}
```
`Container` 协议定义了三个任何遵循该协议的类型(即容器)必须提供的功能:
`Container` 协议定义了三个任何遵循该协议的类型(即容器)必须提供的功能:
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
+ 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
+ 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 Int 值。
+ 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
这个协议没有指定容器中元素该如何存储以及元素必须是何种类型。这个协议只指定了三个任何遵 `Container` 协议的类型必须提供的功能。遵协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
协议没有指定容器中元素该如何存储以及元素类型。协议只指定了任何遵 `Container` 协议的类型必须提供的三个功能。遵协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
任何遵 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素类型。
任何遵 `Container` 协议的类型必须能够指定其存储的元素的类型。具体来说,它必须确保添加到容器内的元素以及下标返回的元素类型是正确的
为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
为了定义这条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
了达到这个目的`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`这个协议无法定义 `Item` 是什么类型的别名,这个信息留给遵协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行
`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`协议没有定义 `Item` 是什么,这个信息留给遵协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能如预期。
下面是先前的非泛型 `IntStack` 类型,这一版本遵循 `Container` 协议:
这是前面非泛型版本 `IntStack` 类型,使其遵循 `Container` 协议:
```swift
struct IntStack: Container {
@ -388,7 +377,7 @@ struct IntStack: Container {
此外,`IntStack` 在实现 `Container` 的要求时,指定 `Item``Int` 类型,即 `typealias Item = Int`,从而将 `Container` 协议中抽象的 `Item` 类型转换为具体的 `Int` 类型。
由于 Swift 的类型推断,实际上不用`IntStack` 的定义中声明 `Item``Int`。因为 `IntStack` 遵循 `Container` 协议的所有要求Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。
由于 Swift 的类型推断,实际上在 `IntStack` 的定义中不需要声明 `Item``Int`。因为 `IntStack` 符合 `Container` 协议的所有要求Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切也可正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。
你也可以让泛型 `Stack` 结构体遵循 `Container` 协议:
@ -417,23 +406,21 @@ struct Stack<Element>: Container {
这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `Item` 的类型。
<a name="extending_an_existing_type_to_specify_an_associated_type"></a>
### 通过扩展一个存在的类型来指定关联类型
### 扩展现有类型来指定关联类型 {#extending-an-existing-type-to-specify-an-associated-type}
[通过扩展添加协议遵循](./21_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型遵循一个协议,这包括使用了关联类型协议。
[扩展添加协议一致性](./21_Protocols.md#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型协议。
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引的下标用以检索其元素。这三个功能都遵循 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 遵循协议就可以扩展 `Array`,使其遵`Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展遵循协议](./21_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述:
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,`count` 属性,以及带有 `Int` 索引的下标检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需声明 `Array` 遵循`Container` 协议就可以扩展 Array使其遵Container 协议。你可以通过一个空扩展来实现这点,正如通过扩展采纳协议中的描述:
```swift
extension Array: Container {}
```
如同上面的泛型 `Stack` 结构体一样,`Array``append(_:)` 方法和下标确保了 Swift 可以推断出 `Item` 类型。定义了这个扩展后,你可以将任意 `Array` 当作 `Container` 来使用。
`Array``append(_:)` 方法和下标确保了 Swift 可以推断出 `Item` 具体类型。定义了这个扩展后,你可以将任意 `Array` 当作 Container 来使用。
<a name="using_type_annotations_to_constrain_an_associated_type"></a>
### 给关联类型添加约束
### 给关联类型添加约束 {#adding-constraints-to-an-associated-type}
你可以协议里关联类型添加类型注释,让遵循协议的类型必须遵循这个约束条件。例如,下面的代码定义了一个 `Item` 必须遵循 `Equatable` `Container` 类型
你可以协议里关联类型添加约束来要求遵循的类型满足约束。例如,下面的代码定义了 `Container` 协议, 要求关联类型 `Item` 必须遵循 `Equatable` 协议
```swift
protocol Container {
@ -444,12 +431,11 @@ protocol Container {
}
```
为了遵循 `Container` 协议Item 类型也必须遵 `Equatable` 协议。
要遵守 `Container` 协议,`Item` 类型也必须遵 `Equatable` 协议。
<a name="Using_a_Protocol_in_Its_Associated_Types_Constraints"></a>
### 在关联类型约束里使用协议
### 在关联类型约束里使用协议 {#using-a-protocol-in-its-associated-types-constraints}
协议可以作为它自身的要求出现。例如,有一个协议细化了 `Container` 协议,添加了一个 `suffix(_:)` 方法。`suffix(_:)` 方法返回容器中从后往前给定数量的元素,把它们存储在一个 `Suffix` 类型的实例里。
协议可以作为它自身的要求出现。例如,有一个协议细化了 `Container` 协议,添加了一个` suffix(_:)` 方法。`suffix(_:)` 方法返回容器中从后往前给定数量的元素,把它们存储在一个 `Suffix` 类型的实例里。
```swift
protocol SuffixableContainer: Container {
@ -458,9 +444,9 @@ protocol SuffixableContainer: Container {
}
```
在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container``Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `wher`e 分句,它在下面[带有泛型 Where 分句的扩展](#extensions_with_a_generic_where_clause)中有讨论。
在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container``Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `where` 分句,它在下面带有泛型 `Where` 分句的扩展中有讨论。
里有一个来自[闭包的循环强引用](./23_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures) Stack 类型的扩展,它添加了对 `SuffixableContainer` 协议的遵循
是上面 [强引用循环闭包](./23_Automatic_Reference_Counting.md#strong_reference_cycles_for_closures) `Stack` 类型的扩展,它遵循了 SuffixableContainer 协议:
```swift
extension Stack: SuffixableContainer {
@ -471,17 +457,17 @@ extension Stack: SuffixableContainer {
}
return result
}
// Inferred that Suffix is Stack.
// 推断 suffix 结果是Stack
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30
// suffix 包含 20 30
```
在上面的例子中,`Suffix``Stack` 的关联类型,也`Stack` ,所以 `Stack` 的后缀运算返回另一个 `Stack` 。另外,遵循 `SuffixableContainer` 的类型可以拥有一个与它自己不同的 `Suffix` 类型——也就是说后缀运算可以返回不同的类型。比如说,这里有一个非泛型 `IntStack` 类型的扩展,它添加`SuffixableContainer` 遵循,使用 `Stack<Int>` 作为它的后缀类型而不是 `IntStack`
在上面的例子中,`Suffix``Stack` 的关联类型,也是 `Stack` ,所以 `Stack` 的后缀运算返回另一个 `Stack` 。另外,遵循 `SuffixableContainer` 的类型可以拥有一个与它自己不同的 `Suffix` 类型——也就是说后缀运算可以返回不同的类型。比如说,这里有一个非泛型 `IntStack` 类型的扩展,它遵循`SuffixableContainer` 协议,使用 `Stack<Int>` 作为它的后缀类型而不是 `IntStack`
```swift
extension IntStack: SuffixableContainer {
@ -492,17 +478,15 @@ extension IntStack: SuffixableContainer {
}
return result
}
// Inferred that Suffix is Stack<Int>.
// 推断 suffix 结果是 Stack<Int>
}
```
## 泛型 Where 语句 {#where-clauses}
<a name="where_clauses"></a>
## 泛型 Where 语句
[类型约束](#type_constraints)让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。
[类型约束](#type_constraints)让你能够为泛型函数,下标,类型的类型参数定义一些强制要求
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵循某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 `where` 子句来实现。通过泛型 `where` 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 `where` 子句
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`
@ -534,31 +518,31 @@ func allItemsMatch<C1: Container, C2: Container>
这个函数的类型参数列表还定义了对两个类型参数的要求:
- `C1` 必须遵循 `Container` 协议(写作 `C1: Container`)。
- `C2` 必须遵循 `Container` 协议(写作 `C2: Container`)。
- `C1``Item` 必须和 `C2``Item` 类型相同(写作 `C1.Item == C2.Item`)。
- `C1``Item` 必须遵循 `Equatable` 协议(写作 `C1.Item: Equatable`)。
+ `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
+ `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
+ `C1``Item` 必须和 `C2``Item` 类型相同(写作 `C1.Item == C2.Item`)。
+ `C1``Item` 必须符合 `Equatable` 协议(写作 `C1.Item: Equatable`)。
第三个和第四个要求定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分
前两个要求定义在函数的类型形式参数列表里,后两个要求定义在了函数的泛型 `where` 分句中
这些要求意味着:
- `someContainer` 是一个 `C1` 类型的容器。
- `anotherContainer` 是一个 `C2` 类型的容器。
- `someContainer``anotherContainer` 包含相同类型的元素。
- `someContainer` 中的元素可以通过不等于操作符(`!=`)来检查它们是否彼此不同。
+ `someContainer` 是一个 `C1` 类型的容器。
+ `anotherContainer` 是一个 `C2` 类型的容器。
+ `someContainer``anotherContainer` 包含相同类型的元素。
+ `someContainer` 中的元素可以通过不等于操作符(!=)来检查它们是否同。
第三个和第四个要求结合起来意味着 `anotherContainer` 中的元素也可以通过 `!=` 操作符来比较,因为它们和 `someContainer` 中的元素类型相同。
这些要求让 `allItemsMatch(_:_:)` 函数能够比较两个容器,即使它们的容器类型不同。
`allItemsMatch(_:_:)` 函数首先检查两个容器是否拥有相同数量的元素,如果它们的元素数量不同,那么一定不匹配,函数就会返回 `false`
`allItemsMatch(_:_:)` 函数首先检查两个容器元素个数是否相同,如果元素个数不同,那么一定不匹配,函数就会返回 `false`
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 `false`
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 false。
如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 `true`
下面演示了 `allItemsMatch(_:_:)` 函数的使用
下面 `allItemsMatch(_:_:)` 函数的示例
```swift
var stackOfStrings = Stack<String>()
@ -573,13 +557,12 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) {
} else {
print("Not all items match.")
}
// 打印 “All items match.”
// 打印“All items match.”
```
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
上面的例子创建 `Stack` 实例来存储 `String` 值,然后将三个字符串压。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
<a name="extensions_with_a_generic_where_clause"></a>
## 具有泛型 Where 子句的扩展
## 具有泛型 Where 子句的扩展 {#extensions-with-a-generic-where-clause}
你也可以使用泛型 `where` 子句作为扩展的一部分。基于以前的例子,下面的示例扩展了泛型 `Stack` 结构体,添加一个 `isTop(_:)` 方法。
@ -594,7 +577,7 @@ extension Stack where Element: Equatable {
}
```
这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是遵循 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素遵循 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。
这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是符合 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素符合 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。
以下是 `isTop(_:)` 方法的调用方式:
@ -604,10 +587,10 @@ if stackOfStrings.isTop("tres") {
} else {
print("Top element is something else.")
}
// 打印 "Top element is tres."
// 打印Top element is tres.
```
如果尝试在其元素未遵循 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
如果尝试在其元素不符合 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
```swift
struct NotEquatable { }
@ -627,7 +610,7 @@ extension Container where Item: Equatable {
}
```
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何遵循 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是遵循 `Equatable` 协议的。
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是符合 `Equatable` 协议的。
```swift
if [9, 9, 9].startsWith(42) {
@ -635,7 +618,7 @@ if [9, 9, 9].startsWith(42) {
} else {
print("Starts with something else.")
}
// 打印 "Starts with something else."
// 打印Starts with something else.
```
上述示例中的泛型 `where` 子句要求 `Item` 遵循协议,但也可以编写一个泛型 `where` 子句去要求 `Item` 为特定类型。例如:
@ -651,17 +634,16 @@ extension Container where Item == Double {
}
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// 打印 "648.9"
// 打印648.9
```
此示例将一个 `average()` 方法添加到 `Item` 类型为 `Double` 的容器中。此方法遍历容器中的元素将其累加,并除以容器的数量计算平均值。它将数量从 `Int` 转换为 `Double` 确保能够进行浮点除法。
就像可以在其他地方写泛型 `where` 子句一样,你可以在一个泛型 `where` 子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。
<a name="associated_types_with_a_generic_where_clause"></a>
## 具有泛型 Where 子句的关联类型
## 具有泛型 Where 子句的关联类型 {#associated-types-with-a-generic-where-clause}
你可以在关联类型后面加上具有泛型 `where` 的字句。例如建立一个包含迭代器Iterator的容器就像是标准库中使用的 `Sequence` 协议那样。你应该这么写:
你可以在关联类型后面加上具有泛型 `where` 的字句。例如,建立一个包含迭代器(`Iterator`)的容器,就像是标准库中使用的 `Sequence` 协议那样。你应该这么写:
```swift
protocol Container {
@ -675,7 +657,7 @@ protocol Container {
}
```
迭代器Iterator的泛型 `where` 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。`makeIterator()` 则提供了容器的迭代器的访问接口。
迭代器(`Iterator`)的泛型 `where` 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。`makeIterator()` 则提供了容器的迭代器的访问接口。
一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型 `where` 子句,来添加了一个约束到被继承协议的关联类型。例如,下面的代码声明了一个 `ComparableContainer` 协议,它要求所有的 `Item` 必须是 `Comparable` 的。
@ -683,10 +665,9 @@ protocol Container {
protocol ComparableContainer: Container where Item: Comparable { }
```
<a name="generic_subscripts"></a>
##泛型下标
## 泛型下标 {#generic-subscripts}
下标能够是泛型的,他们能够包含泛型 `where` 子句。你可以把占位符类型的名称写`subscript`面的尖括号里,在下标代码体开始的标志的花括号前写下泛型 `where` 子句。例如:
下标可以是泛型,它们能够包含泛型 `where` 子句。你可以在 `subscript`尖括号来写占位符类型,你还可以在下标代码块花括号前写 `where` 子句。例如:
```swift
extension Container {
@ -703,11 +684,8 @@ extension Container {
这个 `Container` 协议的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下:
这个 `Container` 协议的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下:
- 在尖括号中的泛型参数 `Indices`,必须是遵循标准库中的 `Sequence` 协议的类型
- 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
- 泛型 `where` 子句要求 SequenceIndices的迭代器其所有的元素都是 `Int` 类型。这样就能确保在序列Sequence中的索引和容器Container里面的索引类型是一致的。
+ 在尖括号中的泛型参数 `Indices`,必须是符合标准库中的 `Sequence` 协议的类型。
+ 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
+ 泛型 `where` 子句要求 `SequenceIndices`的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(`Sequence`)中的索引和容器(`Container`)里面的索引类型是一致的
综合一下,这些约束意味着,传入到 `indices` 下标,是一个整型的序列。

View File

@ -8,9 +8,7 @@ Swift 使用*自动引用计数ARC*机制来跟踪和管理你的应用程
>
> 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
<a name="how_arc_works"></a>
## 自动引用计数的工作机制
## 自动引用计数的工作机制 {#how-arc-works}
当你每次创建一个类的新的实例的时候ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
@ -18,12 +16,11 @@ Swift 使用*自动引用计数ARC*机制来跟踪和管理你的应用程
然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。
为了确保使用中的实例不会被销毁ARC 会跟踪和计算每一个实例正在被多少属性常量和变量所引用。哪怕实例的引用数为1ARC 都不会销毁这个实例。
为了确保使用中的实例不会被销毁ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为 1ARC 都不会销毁这个实例。
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。
<a name="arc_in_action"></a>
## 自动引用计数实践
## 自动引用计数实践 {#arc-in-action}
下面的例子展示了自动引用计数的工作机制。例子以一个简单的 `Person` 类开始,并定义了一个叫 `name` 的常量属性:
@ -54,7 +51,7 @@ var reference3: Person?
```swift
reference1 = Person(name: "John Appleseed")
// 打印 "John Appleseed is being initialized"
// 打印John Appleseed is being initialized
```
应当注意到当你调用 `Person` 类的构造器的时候,`"John Appleseed is being initialized"` 会被打印出来。由此可以确定构造器被执行。
@ -81,11 +78,10 @@ reference2 = nil
```swift
reference3 = nil
// 打印 "John Appleseed is being deinitialized"
// 打印John Appleseed is being deinitialized
```
<a name="strong_reference_cycles_between_class_instances"></a>
## 类实例之间的循环强引用
## 类实例之间的循环强引用 {#strong-reference-cycles-between-class-instances}
在上面的例子中ARC 会跟踪你所新创建的 `Person` 实例的引用数量,并且会在 `Person` 实例不再被需要时销毁它。
@ -161,9 +157,7 @@ unit4A = nil
`Person``Apartment` 实例之间的强引用关系保留了下来并且不会被断开。
<a name="resolving_strong_reference_cycles_between_class_instances"></a>
## 解决实例之间的循环强引用
## 解决实例之间的循环强引用 {#resolving-strong-reference-cycles-between-class-instances}
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题弱引用weak reference和无主引用unowned reference
@ -171,8 +165,7 @@ Swift 提供了两种办法用来解决你在使用类的属性时所遇到的
当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。
<a name="weak_references"></a>
### 弱引用
### 弱引用 {#weak-references}
*弱引用*不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上 `weak` 关键字表明这是一个弱引用。
@ -223,7 +216,7 @@ unit4A!.tenant = john
```swift
john = nil
// 打印 "John Appleseed is being deinitialized"
// 打印John Appleseed is being deinitialized
```
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁,且 `tenant` 属性会被赋值为 `nil`
@ -234,7 +227,7 @@ john = nil
```swift
unit4A = nil
// 打印 "Apartment 4A is being deinitialized"
// 打印Apartment 4A is being deinitialized
```
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁:
@ -245,8 +238,7 @@ unit4A = nil
>
> 在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。
<a name="unowned_references"></a>
### 无主引用
### 无主引用 {#unowned-references}
和弱引用类似,*无主引用*不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字 `unowned` 表示这是一个无主引用。
@ -318,18 +310,18 @@ john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
```swift
john = nil
// 打印 "John Appleseed is being deinitialized"
// 打印 "Card #1234567890123456 is being deinitialized"
// 打印John Appleseed is being deinitialized
// 打印Card #1234567890123456 is being deinitialized
```
最后的代码展示了在 `john` 变量被设为 `nil``Customer` 实例和 `CreditCard` 实例的析构器都打印出了“销毁”的信息。
> 注意
>
> 上面的例子展示了如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况例如出于性能方面的原因Swift 还提供了不安全的无主引用。与所有不安全的操作一样,你需要负责检查代码以确保其安全性。
> 你可以通过 `unowned(unsafe)` 来声明不安全无主引用。如果你试图在实例被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作。
<a name="unowned_references_and_implicitly_unwrapped_optional_properties"></a>
### 无主引用和隐式解包可选值属性
### 无主引用和隐式解包可选值属性 {#unowned-references-and-implicitly-unwrapped-optional-properties}
上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。
@ -365,9 +357,9 @@ class City {
为了建立两个类的依赖关系,`City` 的构造器接受一个 `Country` 实例作为参数,并且将实例保存到 `country` 属性。
`Country` 的构造器调用了 `City` 的构造器。然而,只有 `Country` 的实例完全初始化后,`Country` 的构造器才能把 `self` 传给 `City` 的构造器。在[两段式构造过程](./14_Initialization.html#two_phase_initialization)中有具体描述。
`Country` 的构造器调用了 `City` 的构造器。然而,只有 `Country` 的实例完全初始化后,`Country` 的构造器才能把 `self` 传给 `City` 的构造器。在[两段式构造过程](./14_Initialization.md#two_phase_initialization)中有具体描述。
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将 `Country``capitalCity` 属性声明为隐式解包可选值类型的属性。这意味着像其他可选类型一样,`capitalCity` 属性的默认值为 `nil`,但是不需要展开它的值就能访问它。在[隐式解包可选值](./01_The_Basics.html#implicityly_unwrapped_optionals)中有描述。
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将 `Country``capitalCity` 属性声明为隐式解包可选值类型的属性。这意味着像其他可选类型一样,`capitalCity` 属性的默认值为 `nil`,但是不需要展开它的值就能访问它。在[隐式解包可选值](./01_The_Basics.md#implicityly_unwrapped_optionals)中有描述。
由于 `capitalCity` 默认值为 `nil`,一旦 `Country` 的实例在构造器中给 `name` 属性赋值后,整个初始化过程就完成了。这意味着一旦 `name` 属性被赋值后,`Country` 的构造器就能引用并传递隐式的 `self``Country` 的构造器在赋值 `capitalCity` 时,就能将 `self` 作为参数传递给 `City` 的构造器。
@ -376,13 +368,12 @@ class City {
```swift
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印 "Canada's capital city is called Ottawa"
// 打印Canada's capital city is called Ottawa
```
在上面的例子中,使用隐式解包可选值值意味着满足了类的构造器的两个构造阶段的要求。`capitalCity` 属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
<a name="strong_reference_cycles_for_closures"></a>
## 闭包的循环强引用
## 闭包的循环强引用 {#strong-reference-cycles-for-closures}
前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。
@ -437,7 +428,7 @@ heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// 打印 "<h1>some default text</h1>"
// 打印<h1>some default text</h1>
```
> 注意
@ -451,7 +442,7 @@ print(heading.asHTML())
```swift
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印 "<p>hello, world</p>"
// 打印<p>hello, world</p>
```
> 注意
@ -462,7 +453,7 @@ print(paragraph!.asHTML())
![](https://docs.swift.org/swift-book/_images/closureReferenceCycle01_2x.png)
实例的 `asHTML` 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 `self`(引用了 `self.name``self.text`),因此闭包捕获了 `self`,这意味着闭包又反过来持有了 `HTMLElement` 实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.html#capturing_values))。
实例的 `asHTML` 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 `self`(引用了 `self.name``self.text`),因此闭包捕获了 `self`,这意味着闭包又反过来持有了 `HTMLElement` 实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.md#capturing_values))。
> 注意
>
@ -476,8 +467,7 @@ paragraph = nil
注意,`HTMLElement` 的析构器中的消息并没有被打印,证明了 `HTMLElement` 实例并没有被销毁。
<a name="resolving_strong_reference_cycles_for_closures"></a>
## 解决闭包的循环强引用
## 解决闭包的循环强引用 {#resolving-strong-reference-cycles-for-closures}
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
@ -485,8 +475,7 @@ paragraph = nil
>
> Swift 有如下要求:只要在闭包内使用 `self` 的成员,就要用 `self.someProperty` 或者 `self.someMethod()`(而不只是 `someProperty` 或 `someMethod()`)。这提醒你可能会一不小心就捕获了 `self`。
<a name="defining_a_capture_list"></a>
### 定义捕获列表
### 定义捕获列表 {#defining-a-capture-list}
捕获列表中的每一项都由一对元素组成,一个元素是 `weak``unowned` 关键字,另一个元素是类实例的引用(例如 `self`)或初始化过的变量(如 `delegate = self.delegate!`)。这些项在方括号中用逗号分开。
@ -502,14 +491,13 @@ lazy var someClosure: (Int, String) -> String = {
如果闭包没有指明参数列表或者返回类型,它们会通过上下文推断,那么可以把捕获列表和关键字 `in` 放在闭包最开始的地方:
```swift
lazy var someClosure: Void -> String = {
lazy var someClosure: () -> String = {
[unowned self, weak delegate = self.delegate!] in
// 这里是闭包的函数体
}
```
<a name="weak_and_unowned_references"></a>
### 弱引用和无主引用
### 弱引用和无主引用 {#weak-and-unowned-references}
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 `无主引用`
@ -527,7 +515,7 @@ class HTMLElement {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
@ -555,7 +543,7 @@ class HTMLElement {
```swift
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印 "<p>hello, world</p>"
// 打印<p>hello, world</p>
```
使用捕获列表后引用关系如下图所示:
@ -566,7 +554,7 @@ print(paragraph!.asHTML())
```swift
paragraph = nil
// 打印 "p is being deinitialized"
// 打印p is being deinitialized
```
你可以查看[捕获列表](../chapter3/04_Expressions.html)章节,获取更多关于捕获列表的信息。

View File

@ -4,8 +4,7 @@
Swift 也保证同时访问同一块内存时不会冲突,通过约束代码里对于存储地址的写操作,去获取那一块内存的访问独占权。因为 Swift 自动管理内存,所以大部分时候你完全不需要考虑内存访问的事情。然而,理解潜在的冲突也是很重要的,可以避免你写出访问冲突的代码。而如果你的代码确实存在冲突,那在编译时或者运行时就会得到错误。
<a name="understanding_conflicting_access_to_memory"></a>
## 理解内存访问冲突
## 理解内存访问冲突 {#understanding-conflicting-access-to-memory}
内存的访问,会发生在你给变量赋值,或者传递参数给函数时。例如,下面的代码就包含了读和写的访问:
@ -33,9 +32,7 @@ print("We're number \(one)!")
>
> 如果你曾经在单线程代码里有访问冲突Swift 可以保证你在编译或者运行时会得到错误。对于多线程的代码,可以使用 [Thread Sanitizer](https://developer.apple.com/documentation/code_diagnostics/thread_sanitizer) 去帮助检测多线程的冲突。
<a name="characteristics_of_memory_access"></a>
### 内存访问性质
### 内存访问性质 {#characteristics-of-memory-access}
内存访问冲突时,要考虑内存访问上下文中的这三个性质:访问是读还是写,访问的时长,以及被访问的存储地址。特别是,冲突会发生在当你有两个访问符合下列的情况:
@ -55,19 +52,18 @@ func oneMore(than number: Int) -> Int {
var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// 打印 "2"
// 打印“2”
```
然而,有几种被称为长期访问的内存访问方式,会在别的代码执行时持续进行。瞬时访问和长期访问的区别在于别的代码有没有可能在访问期间同时访问,也就是在时间线上的重叠。一个长期访问可以被别的长期访问或瞬时访问重叠。
重叠的访问主要出现在使用 in-out 参数的函数和方法或者结构体的 mutating 方法里。Swift 代码里典型的长期访问会在后面进行讨论。
<a name="conflicting_access_to_in-out_parameters"></a>
## In-Out 参数的访问冲突
## In-Out 参数的访问冲突 {#conflicting-access-to-in-out-parameters}
一个函数会对它所有的 in-out 参数进行长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。
长期访问的存在会造成一个结果,你不能在访问以 in-out 形式传入后的原变量,即使作用域原则和访问权限允许 —— 任何访问原变量的行为都会造成冲突。例如:
长期访问的存在会造成一个结果,你不能在访问以 in-out 形式传入后的原变量,即使作用域原则和访问权限允许——任何访问原变量的行为都会造成冲突。例如:
```swift
var stepSize = 1
@ -119,8 +115,7 @@ balance(&playerOneScore, &playerOneScore)
>
> 因为操作符也是函数,它们也会对 in-out 参数进行长期访问。例如,假设 `balance(_:_:)` 是一个名为 `<^>` 的操作符函数,那么 `playerOneScore <^> playerOneScore` 也会造成像 `balance(&playerOneScore, &playerOneScore)` 一样的冲突。
<a name="conflicting_access_to_self_in_methods"></a>
## 方法里 self 的访问冲突
## 方法里 self 的访问冲突 {#conflicting-access-to-self-in-methods}
一个结构体的 mutating 方法会在调用期间对 `self` 进行写访问。例如,想象一下这么一个游戏,每一个玩家都有血量,受攻击时血量会下降,并且有敌人的数量,使用特殊技能时会减少敌人数量。
@ -162,13 +157,11 @@ oscar.shareHealth(with: &oscar)
// 错误oscar 访问冲突
```
mutating 方法在调用期间需要对 `self` 发起写访问,而同时 in-out 参数也需要写访问。在方法里,`self``teammate` 都指向了同一个存储地址 —— 就像下面展示的那样。对于同一块内存同时进行两个写访问,并且它们重叠了,就此产生了冲突。
mutating 方法在调用期间需要对 `self` 发起写访问,而同时 in-out 参数也需要写访问。在方法里,`self``teammate` 都指向了同一个存储地址——就像下面展示的那样。对于同一块内存同时进行两个写访问,并且它们重叠了,就此产生了冲突。
![](https://docs.swift.org/swift-book/_images/memory_share_health_oscar_2x.png)
<a name="conflicting_access_to_properties"></a>
## 属性的访问冲突
## 属性的访问冲突 {#conflicting-access-to-properties}
如结构体,元组和枚举的类型都是由多个独立的值组成的,例如结构体的属性或元组的元素。因为它们都是值类型,修改值的任何一部分都是对于整个值的修改,意味着其中一个属性的读或写访问都需要访问整一个值。例如,元组元素的写访问重叠会产生冲突:
@ -198,12 +191,10 @@ func someFunction() {
上面的例子里,`oscar``health``energy` 都作为 in-out 参数传入了 `balance(_:_:)` 里。编译器可以保证内存安全,因为两个存储属性任何情况下都不会相互影响。
限制结构体属性的重叠访问对于保证内存安全不是必要的。保证内存安全是必要的,但因为访问独占权的要求比内存安全还要更严格 —— 意味着即使有些代码违反了访问独占权的原则,也是内存安全的,所以如果编译器可以保证这种非专属的访问是安全的,那 Swift 就会允许这种行为的代码运行。特别是当你遵循下面的原则时,它可以保证结构体属性的重叠访问是安全的:
限制结构体属性的重叠访问对于保证内存安全不是必要的。保证内存安全是必要的,但因为访问独占权的要求比内存安全还要更严格——意味着即使有些代码违反了访问独占权的原则,也是内存安全的,所以如果编译器可以保证这种非专属的访问是安全的,那 Swift 就会允许这种行为的代码运行。特别是当你遵循下面的原则时,它可以保证结构体属性的重叠访问是安全的:
* 你访问的是实例的存储属性,而不是计算属性或类的属性
* 结构体是本地变量的值,而非全局变量
* 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获了
如果编译器无法保证访问的安全性,它就不会允许那次访问。

View File

@ -10,8 +10,7 @@ Swift 不仅提供了多种不同的访问级别,还为某些典型场景提
>
> 为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。
<a name="modules_and_source_files"></a>
## 模块和源文件
## 模块和源文件 {#modules-and-source-files}
Swift 中的访问控制模型基于模块和源文件这两个概念。
@ -21,8 +20,7 @@ Swift 中的访问控制模型基于模块和源文件这两个概念。
*源文件*就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。
<a name="access_levels"></a>
## 访问级别
## 访问级别 {#access-levels}
Swift 为代码中的实体提供了五种不同的*访问级别*。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。
@ -42,10 +40,9 @@ Open 只能作用于类和类的成员,它和 Public 的区别如下:
把一个类标记为 `open`,明确的表示你已经充分考虑过外部模块使用此类作为父类的影响,并且设计好了你的类的代码了。
<a name="guiding_principle_of_access_levels"></a>
### 访问级别基本原则
### 访问级别基本原则 {#guiding-principle-of-access-levels}
Swift 中的访问级别遵循一个基本原则:*不可以在某个实体中定义访问级别更低(更严格)的实体*。
Swift 中的访问级别遵循一个基本原则:*实体不能定义在具有更低访问级别(更严格)的实体*。
例如:
@ -54,34 +51,29 @@ Swift 中的访问级别遵循一个基本原则:*不可以在某个实体中
关于此原则在各种情况下的具体表现,将在下文有所体现。
<a name="default_access_levels"></a>
### 默认访问级别
### 默认访问级别 {#default-access-levels}
如果你没有为代码中的实体显式指定访问级别,那么它们默认为 `internal` 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。
<a name="access_levels_for_single-target_apps"></a>
### 单 target 应用程序的访问级别
### 单 target 应用程序的访问级别 {#access-levels-for-single-target-apps}
当你编写一个单目标应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 Internal 即可。但是,你也可以使用 `fileprivate` 访问或 `private` 访问级别,用于隐藏一些功能的实现细节。
<a name="access_levels_for_frameworks"></a>
### 框架的访问级别
### 框架的访问级别 {#access-levels-for-frameworks}
当你开发框架时,就需要把一些对外的接口定义为 Open 或 Public以便使用者导入该框架后可以正常使用其功能。这些被你定义为对外的接口就是这个框架的 API。
> 注意
>
> 框架依然会使用默认的 `internal` ,也可以指定为 `fileprivate` 访问或者 `private` 访问级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定开放访问或公开访问级别
> 框架的内部实现仍然可以使用默认的访问级别 `internal`,当你需要对框架内部其它部分隐藏细节时可以使用 `private` 或 `fileprivate`。对于框架的对外 API 部分,你就需要将它们设置为 `open` 或 `public` 了
<a name="access_levels_for_unit_test_targets"></a>
### 单元测试 target 的访问级别
### 单元测试 target 的访问级别 {#access-levels-for-unit-test-targets}
当你的应用程序包含单元测试 target 时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有 `open``public` 级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用 `@testable` 特性,然后在允许测试的编译设置(`Build Options -> Enable Testability`)下编译这个应用程序模块,单元测试目标就可以访问应用程序模块中所有内部级别的实体。
<a name="access_control_syntax"></a>
## 访问控制语法
## 访问控制语法 {#access-control-syntax}
通过修饰符 `open``public``internal``fileprivate``private` 来声明实体的访问级别:
通过修饰符 `open``public``internal``fileprivate``private` 来声明实体的访问级别:
```swift
public class SomePublicClass {}
@ -102,12 +94,11 @@ class SomeInternalClass {} // 隐式 internal
var someInternalConstant = 0 // 隐式 internal
```
<a name="custom_types"></a>
## 自定义类型
## 自定义类型 {#custom-types}
如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 `fileprivate` 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。
一个类型的访问级别也会影响到类型*成员*(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 或者 `fileprivate` 级别,那么该类型的所有成员的默认访问级别也会变成 `private` 或者 `fileprivate` 级别。如果你将类型指定为公开或者 `internal` (或者不明确指定访问级别,而使用默认的 `internal` ),那么该类型的所有成员的默认访问级别将是内部访问
一个类型的访问级别也会影响到类型*成员*(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 或者 `fileprivate` 级别,那么该类型的所有成员的默认访问级别也会变成 `private` 或者 `fileprivate` 级别。如果你将类型指定为 `internal` `public`(或者不明确指定访问级别,而使用默认的 `internal` ),那么该类型的所有成员的默认访问级别将是 `internal`
> 重点
>
@ -135,10 +126,8 @@ fileprivate class SomeFilePrivateClass { // 显式 fileprivate 类
private class SomePrivateClass { // 显式 private 类
func somePrivateMethod() {} // 隐式 private 类成员
}
```swift
<a name="tuple_types"></a>
### 元组类型
```
### 元组类型 {#tuple-types}
元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal`,另一个类型为 `private`,那么这个元组的访问级别为 `private`
@ -146,8 +135,7 @@ private class SomePrivateClass { // 显式 private 类
>
> 元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。
<a name="function_types"></a>
### 函数类型
### 函数类型 {#function-types}
函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。
@ -155,7 +143,7 @@ private class SomePrivateClass { // 显式 private 类
```swift
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此处是函数实现部分
// 此处是函数实现部分
}
```
@ -165,14 +153,13 @@ func someFunction() -> (SomeInternalClass, SomePrivateClass) {
```swift
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此处是函数实现部分
// 此处是函数实现部分
}
```
将该函数指定为 `public``internal`,或者使用默认的访问级别 `internal` 都是错误的,因为如果把该函数当做 `public``internal` 级别来使用的话,可能会无法访问 `private` 级别的返回值。
<a name="enumeration_types"></a>
### 枚举类型
### 枚举类型 {#enumeration-types}
枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。
@ -180,25 +167,22 @@ private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
```swift
public enum CompassPoint {
case North
case South
case East
case West
case north
case south
case east
case west
}
```
<a name="raw_values_and_associated_values"></a>
#### 原始值和关联值
#### 原始值和关联值 {#raw-values-and-associated-values}
枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 `internal` 的枚举中定义 `private` 的原始值类型。
<a name="nested_types"></a>
### 嵌套类型
### 嵌套类型 {#nested-types}
如果在 `private` 的类型中定义嵌套类型,那么该嵌套类型就自动拥有 `private` 访问级别。如果在 `public` 或者 `internal` 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 `internal` 访问级别。如果想让嵌套类型拥有 `public` 访问级别,那么需要明确指定该嵌套类型的访问级别。
<a name="subclassing"></a>
## 子类
## 子类 {#subclassing}
子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 `internal`,子类的访问级别就不能是 `public`
@ -208,11 +192,11 @@ public enum CompassPoint {
```swift
public class A {
private func someMethod() {}
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
override internal func someMethod() {}
}
```
@ -220,7 +204,7 @@ internal class B: A {
```swift
public class A {
private func someMethod() {}
fileprivate func someMethod() {}
}
internal class B: A {
@ -232,8 +216,7 @@ internal class B: A {
因为父类 `A` 和子类 `B` 定义在同一个源文件中,所以在子类 `B` 可以在重写的 `someMethod()` 方法中调用 `super.someMethod()`
<a name="constants_variables_properties_subscripts"></a>
## 常量、变量、属性、下标
## 常量、变量、属性、下标 {#constants-variables-properties-subscripts}
常量、变量、属性不能拥有比它们的类型更高的访问级别。例如,你不能定义一个 `public` 级别的属性,但是它的类型却是 `private` 级别的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。
@ -243,8 +226,7 @@ internal class B: A {
private var privateInstance = SomePrivateClass()
```
<a name="getters_and_setters"></a>
### Getter 和 Setter
### Getter 和 Setter {#getters-and-setters}
常量、变量、属性、下标的 `Getters``Setters` 的访问级别和它们所属类型的访问级别相同。
@ -279,7 +261,7 @@ 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)")
// 打印 “The number of edits is 3”
// 打印“The number of edits is 3”
```
虽然你可以在其他的源文件中实例化该结构体并且获取到 `numberOfEdits` 属性的值,但是你不能对其进行赋值。这一限制保护了该记录功能的实现细节,同时还提供了方便的访问方式。
@ -298,29 +280,25 @@ public struct TrackedString {
}
```
<a name="initializers"></a>
## 构造器
## 构造器 {#initializers}
自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是[必要构造器](./14_Initialization.html#required_initializers),它的访问级别必须和所属类型的访问级别相同。
自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是[必要构造器](./14_Initialization.md#required_initializers),它的访问级别必须和所属类型的访问级别相同。
如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别。
<a name="default_initializers"></a>
### 默认构造器
### 默认构造器 {#default-initializers}
如[默认构造器](./14_Initialization.html#default_initializers)所述Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。
如[默认构造器](./14_Initialization.md#default_initializers)所述Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。
默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 `public`。如果一个类型被指定为 `public` 级别,那么默认构造器的访问级别将为 `internal`。如果你希望一个 `public` 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 `public` 访问级别的无参数构造器。
<a name="default_memberwise_initializers_for_structure_types"></a>
### 结构体默认的成员逐一构造器
### 结构体默认的成员逐一构造器 {#default-memberwise-initializers-for-structure-types}
如果结构体中任意存储型属性的访问级别为 `private`,那么该结构体默认的成员逐一构造器的访问级别就是 `private`。否则,这种构造器的访问级别依然是 `internal`
如同前面提到的默认构造器,如果你希望一个 `public` 级别的结构体也能在其他模块中使用其默认的成员逐一构造器,你依然只能自己提供一个 `public` 访问级别的成员逐一构造器。
<a name="protocols"></a>
## 协议
## 协议 {#protocols}
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被遵循。
@ -330,13 +308,11 @@ public struct TrackedString {
>
> 如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,当类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`。
<a name="protocol_inheritance"></a>
### 协议继承
### 协议继承 {#protocol-inheritance}
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。
<a name="protocol_conformance"></a>
### 协议遵循
### 协议遵循 {#protocol-conformance}
一个类型可以遵循比它级别更低的协议。例如,你可以定义一个 `public` 级别类型,它能在别的模块中使用,但是如果它遵循一个 `internal` 协议,这个遵循的部分就只能在这个 `internal` 协议所在的模块中使用。
@ -348,8 +324,7 @@ public struct TrackedString {
>
> Swift 和 Objective-C 一样,协议遵循是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。
<a name="extensions"></a>
## Extension
## Extension {#extensions}
Extension 可以在访问级别允许的情况下对类、结构体、枚举进行扩展。Extension 的成员具有和原始类型成员一致的访问级别。例如,你使用 extension 扩展了一个 `public` 或者 `internal` 类型extension 中的成员就默认使用 `internal` 访问级别,和原始类型中的成员一致。如果你使用 extension 扩展了一个 `private` 类型,则 extension 的成员默认使用 `private` 访问级别。
@ -357,8 +332,7 @@ Extension 可以在访问级别允许的情况下对类、结构体、枚举进
如果你使用 extension 来遵循协议的话,就不能显式地声明 extension 的访问级别。extension 每个 protocol 要求的实现都默认使用 protocol 的访问级别。
<a name="Private Members in Extensions"></a>
### Extension 的私有成员
### Extension 的私有成员 {#Private Members in Extensions}
扩展同一文件内的类结构体或者枚举extension 里的代码会表现得跟声明在原类型里的一模一样。也就是说你可以这样:
@ -370,7 +344,7 @@ Extension 可以在访问级别允许的情况下对类、结构体、枚举进
```swift
protocol SomeProtocol {
func doSomething() {}
func doSomething()
}
```
@ -388,13 +362,11 @@ extension SomeStruct: SomeProtocol {
}
```
<a name="generics"></a>
## 泛型
## 泛型 {#generics}
泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。
<a name="type_aliases"></a>
## 类型别名
## 类型别名 {#type-aliases}
你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,`private` 级别的类型别名可以作为 `private``file-private``internal``public` 或者 `open` 类型的别名,但是 `public` 级别的类型别名只能作为 `public` 类型的别名,不能作为 `internal``file-private``private` 类型的别名。

View File

@ -1,108 +1,101 @@
# 高级运算符
除了之前介绍过的[基本运算符](./02_Basic_Operators.html)Swift 中还有许多可以对数值进行复杂运算的高级运算符。这些高级运算符包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。
除了之前介绍过的[基本运算符](./02_Basic_Operators.md)Swift 还提供了数种可以对数值进行复杂运算的高级运算符。它们包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。
与 C 语言中的算术运算符不同Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。
自定义结构体、类和枚举时,如果也为它们提供标准 Swift 运算符的实现,将会非常有用。在 Swift 中自定义运算符非常简单,运算符也会针对不同类型使用对应实现。
自定义结构体、类和枚举时,如果也为它们提供标准 Swift 运算符的实现,将会非常有用。在 Swift 中为这些运算符提供自定义的实现非常简单,运算符也会针对不同类型使用对应实现。
我们不用被预定义的运算符所限制。在 Swift 中可以自由地定义中缀、前缀、后缀和赋值运算符,以及相应的优先级与结合性。这些运算符在代码中可以像预定义的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义运算符。
我们不用被预定义的运算符所限制。在 Swift 中可以自由地定义中缀、前缀、后缀和赋值运算符,它们具有自定义的优先级与关联值。这些运算符在代码中可以像预定义的运算符一样使用,甚至可以扩展已有的类型以支持自定义运算符。
<a name="bitwise_operators"></a>
## 位运算符
## 位运算符 {#bitwise-operators}
*位运算符*可以操作数据结构中每个独立的比特位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。
Swift 支持 C 语言中的全部位运算符,接下来会一一介绍。
<a name="bitwise_not_operator"></a>
### 按位取反运算符
### Bitwise NOT Operator按位取反运算符 {#bitwise-not-operator}
*按位取反运算符(`~`*可以对一个数值的全部比特位进行取反:
*按位取反运算符(`~`*对一个数值的全部比特位进行取反:
![Art/bitwiseNOT_2x.png](https://docs.swift.org/swift-book/_images/bitwiseNOT_2x.png)
按位取反运算符是一个前缀运算符,需要直接放在运算数之前,并且它们之间不能添加任何空格:
按位取反运算符是一个前缀运算符,直接放在运算数之前,并且它们之间不能添加任何空格:
```swift
```Swift
let initialBits: UInt8 = 0b00001111
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`
<a name="bitwise_and_operator"></a>
### 按位与运算符
### Bitwise AND Operator按位与运算符 {#bitwise-and-operator}
*按位与运算符(`&`*可以对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位`1` 的时候,新数的对应位才为 `1`
*按位与运算符(`&`* 对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位*都*`1` 的时候,新数的对应位才为 `1`
![Art/bitwiseAND_2x.png](https://docs.swift.org/swift-book/_images/bitwiseAND_2x.png)
在下面的示例当中,`firstSixBits``lastSixBits` 中间 4 个位的值都为 `1`。按位与运算符对它们进行了运算,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`
在下面的示例当中,`firstSixBits``lastSixBits` 中间 4 个位的值都为 `1`使用按位与运算符之后,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`
```swift
```Swift
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
```
<a name="bitwise_or_operator"></a>
### 按位或运算符
### Bitwise OR Operator按位或运算符 {#bitwise-or-operator}
*按位或运算符(`|`*可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有任意一个为 `1` 时,新数的对应位就为 `1`
*按位或运算符(`|`*可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有*任意一个*`1` 时,新数的对应位就为 `1`
![Art/bitwiseOR_2x.png](https://docs.swift.org/swift-book/_images/bitwiseOR_2x.png "Art/bitwiseOR_2x.png")
![Art/bitwiseOR_2x.png](https://docs.swift.org/swift-book/_images/bitwiseOR_2x.png)
在下面的示例中,`someBits``moreBits` 不同的位被设置为 `1`位或运算符对它们进行了运算,得到二进制数值 `11111110`,等价于无符号十进制数的 `254`
在下面的示例中,`someBits``moreBits` 存在不同的位被设置为 `1`使用按位或运算符之后,得到二进制数值 `11111110`,等价于无符号十进制数的 `254`
```swift
```Swift
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 等于 11111110
```
<a name="bitwise_xor_operator"></a>
### 按位异或运算符
### Bitwise XOR Operator按位异或运算符 {#bitwise-xor-operator}
*按位异或运算符(`^`*可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 `1`
*按位异或运算符*,或称“排外的或运算符”`^`可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 `1`,并且对应位相同时则为 `0`
![Art/bitwiseXOR_2x.png](https://docs.swift.org/swift-book/_images/bitwiseXOR_2x.png "Art/bitwiseXOR_2x.png")
![Art/bitwiseXOR_2x.png](https://docs.swift.org/swift-book/_images/bitwiseXOR_2x.png)
在下面的示例当中,`firstBits``otherBits` 都有一个自己的位`1` 而对方的对应位`0` 的位。 按位异或运算符将新数的这两个位都设置为 `1`,同时将其它位都设置为 `0`
在下面的示例当中,`firstBits``otherBits` 都有一个自己为 `1`而对方为 `0` 的位。按位异或运算符将新数的这两个位都设置为 `1`。在其余的位上 `firstBits``otherBits` 是相同的,所以设置为 `0`
```swift
```Swift
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001
```
<a name="bitwise_left_and_right_shift_operators"></a>
### 按位左移、右移运算符
### Bitwise Left and Right Shift Operators按位左移、右移运算符 {#bitwise-left-and-right-shift-operators}
*按位左移运算符(`<<`**按位右移运算符(`>>`*可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
*按位左移运算符(`<<`**按位右移运算符(`>>`*可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2同样地将一个整数右移一位等价于将这个数除以 2。
<a name="shifting_behavior_for_unsigned_integers"></a>
#### 无符号整数的移位运算
#### 无符号整数的移位运算 {#shifting-behavior-for-unsigned-integers}
对无符号整数进行移位的规则如下:
1.存在的位按指定的位数进行左移和右移。
1. 已存在的位按指定的位数进行左移和右移。
2. 任何因移动而超出整型存储范围的位都会被丢弃。
3.`0` 来填充移位后产生的空白位。
这种方法称为*逻辑移位*。
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 `1` 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 `1` 位)的结果。蓝色的部分是被移位的,灰色的部分是被抛弃的,橙色的部分则是被填充进来的:
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 `1` 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 `1` 位)的结果。蓝色的数字是被移位的,灰色的数字是被抛弃的,橙色的 `0` 则是被填充进来的:
![Art/bitshiftUnsigned_2x.png](https://docs.swift.org/swift-book/_images/bitshiftUnsigned_2x.png "Art/bitshiftUnsigned_2x.png")
![Art/bitshiftUnsigned_2x.png](https://docs.swift.org/swift-book/_images/bitshiftUnsigned_2x.png)
下面的代码演示了 Swift 中的移位运算:
```swift
```Swift
let shiftBits: UInt8 = 4 // 即二进制的 00000100
shiftBits << 1 // 00001000
shiftBits << 2 // 00010000
@ -113,105 +106,100 @@ shiftBits >> 2 // 00000001
可以使用移位运算对其他的数据类型进行编码和解码:
```swift
```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
```
这个示例使用了一个命名为 `pink``UInt32` 型常量来存储 CSS 中粉色的颜色值。该 CSS 的十六进制颜色值 `#CC6699`,在 Swift 中表示为 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。
这个示例使用了一个命名为 `pink``UInt32` 型常量来存储 Cascading Style SheetsCSS中粉色的颜色值。该 CSS 的颜色值 `#CC6699`,在 Swift 中表示为十六进制的 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。
红色部分是通过对 `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`。这里不需要再向右移位,`0x000099` 也就是 `0x99` ,也就是十进制数值的 `153`
<a name="shifting_behavior_for_signed_integers"></a>
#### 有符号整数的移位运算
#### 有符号整数的移位运算 {#shifting-behavior-for-signed-integers}
对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 比特的有符号整数,但是其中的原理对任何位数的有符号整数都是通用的。)
对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 比特的有符号整数,但是其中的原理对任何位数的有符号整数都是通用的。)
有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。
有符号整数使用第 1 个比特位(通常被称为*符号位*)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。
其余的比特位(通常被称为数值位)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4``Int8` 型整数的二进制位表现形式:
其余的比特位(通常被称为*数值位*)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4``Int8` 型整数的二进制位表现形式:
![Art/bitshiftSignedFour_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedFour_2x.png "Art/bitshiftSignedFour_2x.png")
![Art/bitshiftSignedFour_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedFour_2x.png)
符号位为 `0`,说明这是一个正数,另外 7 位则代表了十进制数值 `4` 的二进制表示。
符号位为 `0`(代表这是一个正数”),另外 7 位则代表了十进制数值 `4` 的二进制表示。
负数的存储方式略有不同。它存储的值的绝对值等于 `2``n` 次方减去它的实际值(也就是数值位表示的值),这里的 `n` 数值位的比特位数。一个 8 比特位的数有 7 个比特位是数值位,所以是 `2``7` 次方,即 `128`
负数的存储方式略有不同。它存储 `2``n` 次方减去实际值的绝对值,这里的 `n` 数值位的位数。一个 8 比特位的数有 7 个比特位是数值位,所以是 `2``7` 次方,即 `128`
这是值为 `-4``Int8` 型整数的二进制表现形式:
这是值为 `-4``Int8` 型整数的二进制表现形式:
![Art/bitshiftSignedMinusFour_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedMinusFour_2x.png "Art/bitshiftSignedMinusFour_2x.png")
![Art/bitshiftSignedMinusFour_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedMinusFour_2x.png)
这次的符号位为 `1`,说明这是一个负数,另外 7 个位则代表了数值 `124`(即 `128 - 4`)的二进制表示:
![Art/bitshiftSignedMinusFourValue_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedMinusFourValue_2x.png "Art/bitshiftSignedMinusFourValue_2x.png")
![Art/bitshiftSignedMinusFourValue_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedMinusFourValue_2x.png)
负数的表示通常被称为*二进制补码*表示。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
负数的表示通常被称为*二进制补码*。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
首先,如果想对 `-1``-4` 进行加法运算,我们只需要这两个数的全部 8 个比特位进行相加,并且将计算结果中超出 8 位的数值丢弃:
首先,如果想对 `-1``-4` 进行加法运算,我们只需要这两个数的全部 8 个比特位执行标准的二进制相加(包括符号位),并且将计算结果中超出 8 位的数值丢弃:
![Art/bitshiftSignedAddition_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedAddition_2x.png "Art/bitshiftSignedAddition_2x.png")
![Art/bitshiftSignedAddition_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedAddition_2x.png)
其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2每向右一位就将自身的数值除以 2。要达到此目的对有符号整数的右移有一个额外的规则
其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2每向右一位就将自身的数值除以 2。要达到此目的对有符号整数的右移有一个额外的规则当对有符号整数进行按位右移运算时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用*符号位*进行填充,而不是用 `0`
* 当对整数进行按位右移运算时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用*符号位*进行填充,而不是用 `0`
![Art/bitshiftSigned_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSigned_2x.png "Art/bitshiftSigned_2x.png")
![Art/bitshiftSigned_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSigned_2x.png)
这个行为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为*算术移位*。
由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 `0`。在移位的过程中保持符号位不变,意味着负整数在接近 `0` 的过程中会一直保持为负。
<a name="overflow_operators"></a>
## 溢出运算符
## 溢出运算符 {#overflow-operators}
在默认情况下,当向一个整数赋予超过它容量的值时Swift 默认会报错,而不是生成一个无效的数。这个行为为我们在运算过大或过小的数的时候提供了额外的安全性。
当向一个整数类型的常量或者变量赋予超过它容量的值时Swift 默认会报错,而不是允许生成一个无效的数。这个行为为我们在运算过大或过小的数提供了额外的安全性。
例如,`Int16` 型整数能容纳的有符号整数范围是 `-32768``32767`当为一个 `Int16` 型变量赋的值超过这个范围时,系统就会报错:
例如,`Int16` 型整数能容纳的有符号整数范围是 `-32768``32767`当为一个 `Int16` 类型的变量或常量赋予的值超过这个范围时,系统就会报错:
```swift
```Swift
var potentialOverflow = Int16.max
// potentialOverflow 的值是 32767这是 Int16 能容纳的最大整数
potentialOverflow += 1
// 这里会报错
```
为过大或者过小的数值提供错误处理,能让我们在处理边界值时更加灵活。
在赋值时为过大或者过小的情况提供错误处理,能让我们在处理边界值时更加灵活。
然而,也可以选择让系统在数值溢出的时候采取截断处理,而非报错。可以使用 Swift 提供的三个溢出运算符来让系统支持整数溢出运算。这些运算符都是以 `&` 开头的:
然而,当你希望的时候也可以选择让系统在数值溢出的时候采取截断处理而非报错。Swift 提供的三个*溢出运算符*来让系统支持整数溢出运算。这些运算符都是以 `&` 开头的:
* 溢出加法 `&+`
* 溢出减法 `&-`
* 溢出乘法 `&*`
<a name="value_overflow"></a>
### 数值溢出
### 数值溢出 {#value-overflow}
数值有可能出现上溢或者下溢。
这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么:
```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://docs.swift.org/swift-book/_images/overflowAddition_2x.png "Art/overflowAddition_2x.png")
![Art/overflowAddition_2x.png](https://docs.swift.org/swift-book/_images/overflowAddition_2x.png)
同样地,当我们对一个无符号整数使用溢出减法(`&-`进行下溢运算时也会产生类似的现象
当允许对一个无符号整数进行下溢运算时也会产生类似的情况。这里有一个使用溢出减法运算符`&-`的例子
```swift
```Swift
var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容纳的最小整数 0
unsignedOverflow = unsignedOverflow &- 1
@ -220,38 +208,37 @@ unsignedOverflow = unsignedOverflow &- 1
`UInt8` 型整数能容纳的最小值是 `0`,以二进制表示即 `00000000`。当使用溢出减法运算符对其进行减 `1` 运算时,数值会产生下溢并被截断为 `11111111` 也就是十进制数值的 `255`
![Art/overflowUnsignedSubtraction_2x.png](https://docs.swift.org/swift-book/_images/overflowUnsignedSubtraction_2x.png "Art/overflowAddition_2x.png")
![Art/overflowUnsignedSubtraction_2x.png](https://docs.swift.org/swift-book/_images/overflowUnsignedSubtraction_2x.png)
溢出也会发生在有符号整型数值上。对有符号整型数值进行溢出加法或溢出减法运算,符号位也需要参与计算,正如[按位左移、右移运算符](#bitwise_left_and_right_shift_operators)所描述的。
溢出也会发生在有符号整型上。对有符号整型的所有溢出加法或减法运算都是按位运算的方式执行的,符号位也需要参与计算,正如[按位左移、右移运算符](#bitwise_left_and_right_shift_operators)所描述的。
```swift
```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://docs.swift.org/swift-book/_images/overflowSignedSubtraction_2x.png "Art/overflowSignedSubtraction_2x.png")
![Art/overflowSignedSubtraction_2x.png](https://docs.swift.org/swift-book/_images/overflowSignedSubtraction_2x.png)
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大数。
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大数。
<a name="precedence_and_associativity"></a>
## 优先级和结合性
## 优先级和结合性 {#precedence-and-associativity}
运算符的*优先级*使得一些运算符优先于其他运算符,高优先级的运算符会先被计算
运算符的*优先级*使得一些运算符优先于其他运算符;它们会先被执行
*结合性*定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组。可以将这意思理解为“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”。
*结合性*定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组。可以将理解为“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”。
复合表达式的算顺序,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个表达式的运算结果会是 `17`
当考虑一个复合表达式的算顺序,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个表达式的运算结果会是 `17`
```swift
```Swift
2 + 3 % 4 * 5
// 结果是 17
```
如果完全从左到右进行运算,运算的过程是这样的:
如果你直接从左到右进行运算,你可能认为运算的过程是这样的:
- 2 + 3 = 5
- 5 % 4 = 1
@ -259,42 +246,41 @@ signedOverflow = signedOverflow &- 1
但是正确答案是 `17` 而不是 `5`。优先级高的运算符要先于优先级低的运算符进行计算。与 C 语言类似,在 Swift 中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。
而乘法与取余的优先级相同。这时为了得到正确的运算顺序,还需要考虑结合性。乘法与取余运算都是左结合的。可以将这考虑成为这两部分表达式都隐式地加上括号:
而乘法运算与取余运算的优先级*相同*。这时为了得到正确的运算顺序,还需要考虑结合性。乘法运算与取余运算都是左结合的。可以将这考虑成,从它们的左边开始为这两部分表达式都隐式地加上括号:
```swift
```Swift
2 + ((3 % 4) * 5)
```
`(3 % 4)` 等于 `3`,所以表达式相当于:
```swift
```Swift
2 + (3 * 5)
```
`3 * 5` 等于 `15`,所以表达式相当于:
```swift
```Swift
2 + 15
```
因此计算结果为 `17`
如果想查看完整的 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)。
有关 Swift 标准库提供的操作符信息,包括操作符优先级和结核性设置的完整列表,请参[操作符声明](https://developer.apple.com/documentation/swift/operator_declarations)。
> 注意
>
> 相对 C 语言和 Objective-C 来说Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合你的预期。
> 相对 C 语言和 Objective-C 来说Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合你的预期。
<a name="operator_functions"></a>
## 运算符函数
## 运算符函数 {#operator-functions}
类和结构体可以为现有的运算符提供自定义的实现这通常被称为*运算符重载*。
类和结构体可以为现有的运算符提供自定义的实现这通常被称为运算符*重载*。
下面的例子展示了如何自定义的结构体实现加法运算符(`+`)。算术加法运算符是一个双目运算符,因为它可以对两个值进行运算,同时它还中缀运算符,因为它出现在两个值中间。
下面的例子展示了如何自定义的结构体支持加法运算符(`+`)。算术加法运算符是一个*二元运算符*,因为它对两个值进行运算,同时它还可以称为*中缀*运算符,因为它出现在两个值中间。
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以两个 `Vector2D` 结构体进行相加的运算符函数:
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以两个 `Vector2D` 结构体实例进行相加的*运算符函数*
```swift
```Swift
struct Vector2D {
var x = 0.0, y = 0.0
}
@ -306,13 +292,13 @@ extension Vector2D {
}
```
该运算符函数被定义为 `Vector2D` 上的一个类方法,并且函数的名字与它要进行重载的 `+` 名字一致。因为加法运算并不是一个向量必需的功能,所以这个类方法被定义在 `Vector2D` 的一个扩展中,而不是 `Vector2D` 结构体声明内。而算术加法运算符是双目运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的参数,同时有一个 `Vector2D` 类型的返回值。
该运算符函数被定义为 `Vector2D` 上的一个类方法,并且函数的名字与它要进行重载的 `+` 名字一致。因为加法运算并不是一个向量必需的功能,所以这个类方法被定义在 `Vector2D` 的一个扩展中,而不是 `Vector2D` 结构体声明内。而算术加法运算符是二元运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的参数,同时有一个 `Vector2D` 类型的返回值。
在这个实现中,输入参数分别被命名为 `left``right`,代表在 `+` 运算符左边和右边的两个 `Vector2D` 实例。函数返回了一个新的 `Vector2D` 实例,这个实例的 `x``y` 分别等于作为参数的两个实例的 `x``y` 的值之和。
这个类方法可以在任意两个 `Vector2D` 实例中间作为中缀运算符来使用:
```swift
```Swift
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
@ -321,16 +307,15 @@ let combinedVector = vector + anotherVector
这个例子实现两个向量 `(3.01.0)``(2.04.0)` 的相加,并得到新的向量 `(5.05.0)`。这个过程如下图示:
![Art/vectorAddition_2x.png](https://docs.swift.org/swift-book/_images/vectorAddition_2x.png "Art/vectorAddition_2x.png")
![Art/vectorAddition_2x.png](https://docs.swift.org/swift-book/_images/vectorAddition_2x.png)
<a name="prefix_and_postfix_operators"></a>
### 前缀和后缀运算符
### 前缀和后缀运算符 {#prefix-and-postfix-operators}
上个例子演示了一个双目中缀运算符的自定义实现。类与结构体也能提供标准*单目运算符*的实现。单目运算符只运算一个值。当运算符出现在值之前时,它就是*前缀*的(例如 `-a`),而当它出现在值之后时,它就是*后缀*的(例如 `b!`)。
上个例子演示了一个二元中缀运算符的自定义实现。类与结构体也能提供标准*一元运算符*的实现。一元运算符只运算一个值。当运算符出现在值之前时,它就是*前缀*的(例如 `-a`),而当它出现在值之后时,它就是*后缀*的(例如 `b!`)。
要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 `func` 关键字之前指定 `prefix` 或者 `postfix` 修饰符:
```swift
```Swift
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
@ -338,11 +323,11 @@ extension Vector2D {
}
```
这段代码为 `Vector2D` 类型实现了单目负号运算符。由于该运算符是前缀运算符,所以这个函数需要加上 `prefix` 修饰符。
这段代码为 `Vector2D` 类型实现了一元运算符(`-a`。由于该运算符是前缀运算符,所以这个函数需要加上 `prefix` 修饰符。
对于简单数值,单目负号运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,该运算将其 `x``y` 属性的正负性都进行了改变:
对于简单数值,一元负号运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,该运算将其 `x``y` 属性的正负性都进行了改变:
```swift
```Swift
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例
@ -350,12 +335,13 @@ let alsoPositive = -negative
// alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例
```
<a name="compound_assignment_operators"></a>
### 复合赋值运算符
### 复合赋值运算符 {#compound-assignment-operators}
*复合赋值运算符*将赋值运算符(`=`)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接被修改。
```swift
在下面的例子中,对 `Vector2D` 实例实现了一个加法赋值运算符函数:
```Swift
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
@ -365,7 +351,7 @@ extension Vector2D {
因为加法运算在之前已经定义过了,所以在这里无需重新定义。在这里可以直接利用现有的加法运算符函数,用它来对左值和右值进行相加,并再次赋值给左值:
```swift
```Swift
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
@ -374,16 +360,15 @@ original += vectorToAdd
> 注意
>
> 不能对默认的赋值运算符(`=`)进行重载。只有合赋值运算符可以被重载。同样地,也无法对三条件运算符 `a ? b : c` 进行重载。
> 不能对默认的赋值运算符(`=`)进行重载。只有合赋值运算符可以被重载。同样地,也无法对三条件运算符 `a ? b : c` 进行重载。
<a name="equivalence_operators"></a>
### 等价运算符
### 等价运算符 {#equivalence-operators}
自定义的类和结构体没有对*等价运算符*进行默认实现,等价运算符通常被称为相等运算符(`==`)与不等运算符(`!=`)。对于自定义类型Swift 无法判断其是否“相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。
通常情况下,自定义的类和结构体没有对*等价运算符*进行默认实现,等价运算符通常被称为*相等*运算符(`==`)与*不等*运算符(`!=`)。
为了使用等价运算符对自定义的类型进行判等运算,需要为提供自定义实现,实现的方法与其它中缀运算符一样, 并且增加对标准库 `Equatable` 协议的遵循:
为了使用等价运算符对自定义的类型进行判等运算,需要为“相等”运算符提供自定义实现,实现的方法与其它中缀运算符一样, 并且增加对标准库 `Equatable` 协议的遵循:
```swift
```Swift
extension Vector2D: Equatable {
static func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
@ -391,30 +376,30 @@ extension Vector2D: Equatable {
}
```
上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 实例是否相等。对于 `Vector2D` 类型来说,“相等”意味着“两个实例的 `x` 属性`y` 属性都相等”,这也是代码中用来进行判等的逻辑。示例里同时也实现了“不等”运算符(`!=`,它简单地将“相等”运算符的结果进行取反后返回。
上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 实例是否相等。对于 `Vector2D` 来说,“相等”意味着“两个实例的 `x``y` 都相等”,这也是代码中用来进行判等的逻辑。如果你已经实现了“相等”运算符,通常情况下你并不需要自己再去实现“不等”运算符(`!=`)。标准库对于“不等”运算符提供了默认的实现,它简单地将“相等”运算符的结果进行取反后返回。
现在我们可以使用这两个运算符来判断两个 `Vector2D` 实例是否相等:
```swift
```Swift
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
print("These two vectors are equivalent.")
}
// 打印 “These two vectors are equivalent.”
// 打印“These two vectors are equivalent.”
```
Swift 为以下自定义类型提等价运算符供合成实现:
多数简单情况下,您可以使用 Swift 为您提供的等价运算符默认实现。Swift 为以下数种自定义类型提等价运算符的默认实现:
- 只拥有遵循 `Equatable` 协议存储属性的结构体
- 只拥有遵循 `Equatable` 协议关联类型的枚举
- 没有关联类型的枚举
- 只拥有存储属性,并且它们全都遵循 `Equatable` 协议的结构体
- 只拥有关联类型,并且它们全都遵循 `Equatable` 协议的枚举
- 没有关联类型的枚举
在类型原的声明中声明遵循 `Equatable` 来接收这些默认实现。
在类型原的声明中声明遵循 `Equatable` 来接收这些默认实现。
下面为三维位置向量 `(x, y, z)` 定义的 `Vector3D` 结构体,与 `Vector2D` 类似由于 `x``y``z` 属性都是 `Equatable` 类型,`Vector3D` 就收到默认的等价运算符实现
下面为三维位置向量 `(x, y, z)` 定义的 `Vector3D` 结构体,与 `Vector2D` 类似由于 `x``y``z` 属性都是 `Equatable` 类型,`Vector3D` 获得了默认的等价运算符实现。
```swift
```Swift
struct Vector3D: Equatable {
var x = 0.0, y = 0.0, z = 0.0
}
@ -424,23 +409,22 @@ let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
print("These two vectors are also equivalent.")
}
// Prints "These two vectors are also equivalent."
// 打印“These two vectors are also equivalent.
```
<a name="custom_operators"></a>
## 自定义运算符
## 自定义运算符 {#custom-operators}
除了实现标准运算符,在 Swift 中还可以声明和实现*自定义运算符*。可以用来自定义运算符的字符列表请参考[运算符](../chapter3/02_Lexical_Structure.html#operators)。
新的运算符要使用 `operator` 关键字在全局作用域内进行定义,同时还要指定 `prefix``infix` 或者 `postfix` 修饰符:
```swift
```Swift
prefix operator +++
```
上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有意义,因此我们针对 `Vector2D` 实例定义的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法运算符来让矩阵自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y` 属性值翻倍。实现 `+++` 运算符的方式如下
上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有已知的意义,因此针对 `Vector2D` 实例的特定上下文中,给予了它自定义的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法运算符来让矩阵自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y` 属性值翻倍。你可以像下面这样通过对 `Vector2D` 添加一个 `+++` 类方法,来实现 `+++` 运算符:
```swift
```Swift
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
@ -454,16 +438,15 @@ let afterDoubling = +++toBeDoubled
// afterDoubling 现在的值也为 (2.0, 8.0)
```
<a name="precedence_and_associativity_for_custom_infix_operators"></a>
### 自定义中缀运算符的优先级
### 自定义中缀运算符的优先级 {#precedence-and-associativity-for-custom-infix-operators}
每个自定义中缀运算符都属于某个优先级组。这个优先级组指定了这个运算符其他中缀运算符的优先级和结合性。[优先级和结合性](#precedence_and_associativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
每个自定义中缀运算符都属于某个优先级组。优先级组指定了这个运算符相对于其他中缀运算符的优先级和结合性。[优先级和结合性](#precedence_and_associativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
而没有明确放入优先级组的自定义中缀运算符放到一个默认的优先级组内,其优先级高于三元运算符。
而没有明确放入某个优先级组的自定义中缀运算符将会被放到一个默认的优先级组内,其优先级高于三元运算符。
以下例子定义了一个新的自定义中缀运算符 `+-`,此运算符属于 `AdditionPrecedence` 优先组:
```swift
```Swift
infix operator +-: AdditionPrecedence
extension Vector2D {
static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
@ -476,8 +459,9 @@ let plusMinusVector = firstVector +- secondVector
// plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0)
```
这个运算符把两个向量的 `x` 值相加,同时第一个向量的 `y` 减去第二个向量的 `y` 。因为它本质上是属于“相加型”运算符,所以将它放置 `+``-` 等默认中缀“相加型”运算符相同的优先级组中。关于 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)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考[运算符声明](#operator_declaration)
这个运算符把两个向量的 `x` 值相加,同时第一个向量的 `y` 减去第二个向量的 `y` 。因为它本质上是属于“相加型”运算符,所以将它放置 `+``-` 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考[运算符声明](./06_Declarations.md#operator_declaration)
> 注意
>
> 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。

View File

@ -1,613 +0,0 @@
# 翻译历史记录
## The Basic
> 1.0
> 翻译:[numbbbbb](https://github.com/numbbbbb), [lyuka](https://github.com/lyuka), [JaySurplus](https://github.com/JaySurplus)
> 校对:[lslxdx](https://github.com/lslxdx)
> 2.0
> 翻译+校对:[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
> 3.0.1, 2016-11-11shanks
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io)
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Basic_Operators
> 1.0
> 翻译:[XieLingWang](https://github.com/xielingwang)
> 校对:[EvilCome](https://github.com/Evilcome)
> 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
> 3.0.1shanks2016-11-11
> 4.0
> 翻译+校对:[kemchenj](https://kemchenj.github.io)
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Strings_and_Characters
> 1.0
> 翻译:[wh1100717](https://github.com/wh1100717)
> 校对:[Hawstein](https://github.com/Hawstein)
> 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
> 3.0.1, shanks, 2016-11-11
> 4.0
> 翻译:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## 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
> 3.0
> 校对:[shanks](http://codebuild.me) 2016-10-09
> 3.0.1shanks2016-11-12
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Control Flow
> 1.0
> 翻译:[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
> 翻译+校对:[JackAlan](https://github.com/AlanMelody)
> 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
> 3.0.1shanks2016-11-12
> 3.1
> 翻译:[qhd](https://github.com/qhd) 2017-04-17
> 4.0
> 翻译:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Functions
> 1.0
> 翻译:[honghaoz](https://github.com/honghaoz)
> 校对:[LunaticM](https://github.com/LunaticM)
> 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
> 校对: [shanks](http://codebuild.me) 2016-09-27
> 3.0.1shanks2016-11-12
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Closures
> 1.0
> 翻译:[wh1100717](https://github.com/wh1100717)
> 校对:[lyuka](https://github.com/lyuka)
> 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
> 3.0.1shanks2016-11-12
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Enumerations
> 1.0
> 翻译:[yankuangshi](https://github.com/yankuangshi)
> 校对:[shinyzhu](https://github.com/shinyzhu)
> 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
> 3.0
> 翻译+校对:[shanks](https://codebuild.me) 2016-09-24
> 3.0.1shanks2016-11-12
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Classes And Structures
> 1.0
> 翻译:[JaySurplus](https://github.com/JaySurplus)
> 校对:[sg552](https://github.com/sg552)
> 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
>
> 3.0.1 shanks 2016-11-12
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## 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)
> 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
>
> 3.0.1shanks2016-11-12
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Methods
> 1.0
> 翻译:[pp-prog](https://github.com/pp-prog)
> 校对:[zqp](https://github.com/zqp)
> 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
> 3.0.1shanks2016-11-13
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Subscripts
> 1.0
> 翻译:[siemenliu](https://github.com/siemenliu)
> 校对:[zq54zquan](https://github.com/zq54zquan)
> 2.0
> 翻译+校对:[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
> 3.0.1shanks2016-11-13
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Inheritance
> 1.0
> 翻译:[Hawstein](https://github.com/Hawstein)
> 校对:[menlongsheng](https://github.com/menlongsheng)
> 2.02.1
> 翻译+校对:[shanks](http://codebuild.me)
> 2.2
> 校对:[SketchK](https://github.com/SketchK) 2016-05-13
> 3.0.1shanks2016-11-13
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Initialization
> 1.0
> 翻译:[lifedim](https://github.com/lifedim)
> 校对:[lifedim](https://github.com/lifedim)
> 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
> 3.0.1shanks2016-11-13
> 3.1
> 翻译:[qhd](https://github.com/qhd) 2017-04-18
> 4.0
> 翻译:[muhlenXi](https://github.com/muhlenxi) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Deinitialization
> 1.0
> 翻译:[bruce0505](https://github.com/bruce0505)
> 校对:[fd5788](https://github.com/fd5788)
> 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
> 3.0.1shanks2016-11-13
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Optional Chaining
> 1.0
> 翻译:[Jasonbroker](https://github.com/Jasonbroker)
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
> 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
> 3.0.1shanks2016-11-13
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Error Handling
> 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
> 3.0
> 翻译+校对:[shanks](http://codebuild.me) 2016-09-24
> 3.0.1shanks2016-11-13
> 4.0
> 翻译+校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Type Casting
> 1.0
> 翻译:[xiehurricane](https://github.com/xiehurricane)
> 校对:[happyming](https://github.com/happyming)
> 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
> 3.0.1shanks2016-11-13
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Nested Types
> 1.0
> 翻译:[Lin-H](https://github.com/Lin-H)
> 校对:[shinyzhu](https://github.com/shinyzhu)
> 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
> 3.0.1shanks2016-11-13
> 4.0
> 翻译+校对:[EyreFree](https://www.eyrefree.org/) 2017-10-19
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Extensions
> 1.0
> 翻译:[lyuka](https://github.com/lyuka)
> 校对:[Hawstein](https://github.com/Hawstein)
> 2.0
> 翻译+校对:[shanks](http://codebuild.me)
> 2.1
> 校对:[shanks](http://codebuild.me)
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16
> 3.0.1shanks2016-11-13
> 4.0
> 校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Protocols
> 1.0
> 翻译:[geek5nan](https://github.com/geek5nan)
> 校对:[dabing1022](https://github.com/dabing1022)
> 2.0
> 翻译+校对:[futantan](https://github.com/futantan)
> 2.1
> 翻译:[小铁匠 Linus](https://github.com/kevin833752)
> 校对:[shanks](http://codebuild.me)
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK)
>
> 3.0
> 校对:[CMB](https://github.com/chenmingbiao)版本日期2016-09-13
> 3.0.1
> shanks2016-11-13
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Generics
> 1.0
> 翻译:[takalard](https://github.com/takalard)
> 校对:[lifedim](https://github.com/lifedim)
> 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
> 3.0.1shanks2016-11-13
> 3.1:翻译:[qhd](https://github.com/qhd)2017-04-10
> 4.0
> 翻译+校对:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Automatic Reference Counting
> 1.0
> 翻译:[TimothyYe](https://github.com/TimothyYe)
> 校对:[Hawstein](https://github.com/Hawstein)
> 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
> 3.0.1
> shanks2016-11-13
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Memory Safety
> 4.0
> 翻译:[kemchenj](https://kemchenj.github.io/) 2017-09-21
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Access Control
> 1.0
> 翻译:[JaceFu](http://www.devtalking.com/)
> 校对:[ChildhoodAndy](http://childhood.logdown.com)
> 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
> 3.0.1
> 翻译+校对: shanks2016-11-13
> 4.0
> 翻译kemchenj2017-09-23
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
## Advanced Operators
> 1.0
> 翻译:[xielingwang](https://github.com/xielingwang)
> 校对:[numbbbbb](https://github.com/numbbbbb)
> 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
>
> 3.0
> 翻译+校对:[mmoaay](https://github.com/mmoaay) 2016-09-20
> 3.0.1
> shanks2016-11-13
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)

View File

@ -1,26 +1,10 @@
# 关于语言参考About the Language Reference
-----------------
> 1.0
> 翻译:[dabing1022](https://github.com/dabing1022)
> 校对:[numbbbbb](https://github.com/numbbbbb)
> 2.0
> 翻译+校对:[KYawn](https://github.com/KYawn)
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页内容包括:
- [如何阅读语法](#how_to_read_the_grammar)
本书的这一节描述了 Swift 编程语言的形式语法。这里描述的语法是为了帮助您了解该语言的更多细节,而不是让您直接实现一个解析器或编译器。
Swift 语言相对较小,这是由于 Swift 代码中在各种地方出现的常见的类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。
Swift 语言相对较小,这是由于 Swift 代码中常用的类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。
<a name="how_to_read_the_grammar"></a>
## 如何阅读语法
## 如何阅读语法 {#how-to-read-the-grammar}
用来描述 Swift 编程语言形式语法的符号遵循下面几个约定:
@ -35,11 +19,14 @@ Swift 语言相对较小,这是由于 Swift 代码中在各种地方出现的
> getter-setter 方法块语法
>
> *getter-setter 方法块* → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)<sub>可选</sub> } | { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) }
> *getter-setter 方法块* → { [*getter 子句*](./06_Declarations.md#getter-clause) [*setter 子句*](./06_Declarations.md#setter-clause)<sub>可选</sub> } | { [*setter 子句*](./06_Declarations.md#setter-clause) [*getter 子句*](./06_Declarations.md#getter-clause) }
>
这个定义表明,一个 getter-setter 方法块可以由一个 getter 分句后跟一个可选的 setter 分句构成,然后用大括号括起来,或者由一个 setter 分句后跟一个 getter 分句构成,然后用大括号括起来。上述的语法产式等价于下面的两个语法产式,
> getter-setter 方法块语法
>
> getter-setter 方法块 → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)<sub>可选</sub> }
> getter-setter 方法块 → { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) }
> getter-setter 方法块 → { [*getter 子句*](./06_Declarations.md#getter-clause) [*setter 子句*](./06_Declarations.md#setter-clause)<sub>可选</sub> }
>
> getter-setter 方法块 → { [*setter 子句*](./06_Declarations.md#setter-clause) [*getter 子句*](./06_Declarations.md#getter-clause) }
>

View File

@ -1,72 +1,70 @@
# 词法结构Lexical Structure
-----------------
> 1.0
> 翻译:[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)
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
- [空白与注释](#whitespace_and_comments)
- [标识符](#identifiers)
- [关键字和标点符号](#keywords)
- [字面量](#literals)
- [整数字面量](#integer_literals)
- [浮点数字面量](#floating_point_literals)
- [字符串字面量](#string_literals)
- [运算符](#operators)
Swift 的*“词法结构lexical structure”* 描述了能构成该语言中有效符号token的字符序列。这些合法符号组成了语言中最底层的构建基块并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符identifier、关键字keyword、标点符号punctuation、字面量literal或运算符operator组成。
通常情况下,通过考虑输入文本当中可能的最长子串,并且在随后将介绍的语法约束之下,根据随后将介绍的语法约束生成的,根据 Swift 源文件当中的字符来生成相应的“符号”。这种方法称为*“最长匹配longest match”*,或者*“最大适合maximal munch”*。
<a id="whitespace_and_comments"></a>
## 空白与注释
## 空白与注释 {#whitespace}
空白whitespace有两个用途分隔源文件中的符号以及帮助区分运算符属于前缀还是后缀参见 [运算符](#operators)在其他情况下空白则会被忽略。以下的字符会被当作空白空格U+0020、换行符U+000A、回车符U+000D、水平制表符U+0009、垂直制表符U+000B、换页符U+000C以及空字符U+0000
注释被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符U+000A或者回车符U+000D。多行注释由 `/*` 开始,以 `*/` 结束。注释允许嵌套,但注释标记必须匹配。
> 空白语法
<a id="whitespace"></a>
>
> *空白* → [*空白项*](#whitespace-item) [*空白*](#whitespace)<sub>可选</sub>
>
#### whitespace-item {#whitespace-item}
>
> *空白项* → [*断行符*](#line-break)
>
> *空白项* → [*注释*](#comment)
>
> *空白项* → [*多行注释*](#multiline-comment)
>
> *空白项* → U+0000U+0009U+000BU+000C 或者 U+0020
<a id="line-break"></a>
>
>
#### line-break {#line-break}
>
> *断行符* → U+000A
>
> *断行符* → U+000D
>
> *断行符* → U+000D 接着是 U+000A
<a id="comment"></a>
> *注释* → // [*注释内容 断行*](#comment-text line-break)
>
>
#### comment {#comment}
>
> *注释* → // [*注释内容*](#comment-text) [断行符*](#line-break)
>
>
#### multiline-comment {#multiline-comment}
>
> *多行注释* → `/*` [*多行注释内容*](#multiline-commnet-text) `*/`
>
>
#### comment-text {#comment-text}
>
> *注释内容* → [*注释内容项*](#comment-text-item) [*注释内容*](#comment-text)<sub>可选</sub>
>
>
#### comment-text-item {#comment-text-item}
>
> *注释内容项* → 任何 Unicode 标量值, 除了 U+000A 或者 U+000D
>
>
#### multiline-commnet-text {#multiline-commnet-text}
>
> *多行注释内容* → [*多行注释内容项*](#multiline-comment-text-item) [*多行注释内容*](#multiline-comment-text)<sub>可选</sub>
>
> *多行注释内容项* → [*多行注释*](#multiline-comment).
>
> *多行注释内容项* → [*注释内容项*](#comment-text-item)
>
> *多行注释内容项* → 任何 Unicode 标量值, 除了 `/*` 或者 `*/`
注释可以包含额外的格式和标记,正如 [*Markup Formatting Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497) 所述。
<a id="identifiers"></a>
## 标识符
## 标识符 {#identifiers}
*标识符identifier* 可以由以下的字符开始:大写或小写的字母 `A``Z`、下划线(`_`、基本多文种平面Basic Multilingual Plane中非字符数字组合的 Unicode 字符以及基本多文种平面以外的非个人专用区字符。在首字符之后,允许使用数字和组合 Unicode 字符。
@ -75,59 +73,83 @@ Swift 的*“词法结构lexical structure”* 描述了能构成该语言
闭包中如果没有明确指定参数名称,参数将被隐式命名为 `$0`、`$1`、`$2` 等等。这些命名在闭包作用域范围内是合法的标识符。
> 标识符语法
<a id="identifier"></a>
>
> *标识符* → [*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
>
> *标识符* → \`[*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>\`
>
> *标识符* → [*隐式参数名*](#implicit-parameter-name)
<a id="identifier-list"></a>
> *标识符列表* → [*标识符*](#identifier) | [*标识符*](#identifier) **,** [*标识符列表*](#identifier-list)
<a id="identifier-head"></a>
>
> *标识符列表* → [*标识符*](#identifier) | [*标识符*](#identifier) **,** [*标识符列表*](#identifier)
>
>
#### identifier-head {#identifier-head}
>
> *头部标识符* → 大写或小写字母 A - Z
>
> *头部标识符* → _
>
> *头部标识符* → U+00A8U+00AAU+00ADU+00AFU+00B2U+00B5或者 U+00B7U+00BA
>
> *头部标识符* → U+00BCU+00BEU+00C0U+00D6U+00D8U+00F6或者 U+00F8U+00FF
>
> *头部标识符* → U+0100U+02FFU+0370U+167FU+1681U+180D或者 U+180FU+1DBF
>
> *头部标识符* → U+1E00U+1FFF
>
> *头部标识符* → U+200BU+200DU+202AU+202EU+203FU+2040U+2054或者 U+2060U+206F
>
> *头部标识符* → U+2070U+20CFU+2100U+218FU+2460U+24FF或者 U+2776U+2793
>
> *头部标识符* → U+2C00U+2DFF 或者 U+2E80U+2FFF
>
> *头部标识符* → U+3004U+3007U+3021U+302FU+3031U+303F或者 U+3040U+D7FF
>
> *头部标识符* → U+F900U+FD3DU+FD40U+FDCFU+FDF0U+FE1F或者 U+FE30U+FE44
>
> *头部标识符* → U+FE47U+FFFD
>
> *头部标识符* → U+10000U+1FFFDU+20000U+2FFFDU+30000U+3FFFD或者 U+40000U+4FFFD
>
> *头部标识符* → U+50000U+5FFFDU+60000U+6FFFDU+70000U+7FFFD或者 U+80000U+8FFFD
>
> *头部标识符* → U+90000U+9FFFDU+A0000U+AFFFDU+B0000U+BFFFD或者 U+C0000U+CFFFD
>
> *头部标识符* → U+D0000U+DFFFD 或者 U+E0000U+EFFFD
<a id="identifier-character"></a>
>
> *标识符字符* → 数值 0 - 9
>
>
#### identifier-character {#identifier-character}
>
> *标识符字符* → U+0300U+036FU+1DC0U+1DFFU+20D0U+20FF或者 U+FE20U+FE2F
>
> *标识符字符* → [*头部标识符*](#identifier-head)
> <a id="identifier-characters"></a>
>
>
#### identifier-characters {#identifier-characters}
>
> *标识符字符组* → [*标识符字符*](#identifier-character) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
>
>
#### implicit-parameter-name {#implicit-parameter-name}
>
> *隐式参数名* → **$** [*十进制数字列表*](#decimal-digit)
<a id="implicit-parameter-name"></a>
> *隐式参数名* → **$** [*十进制数字列表*](#decimal-digits)
## 关键字和标点符号 {#keywords-and-punctuation}
<a id="keywords"></a>
## 关键字和标点符号
下面这些被保留的关键字不允许用作标识符,除非使用反引号转义,具体描述请参考 [标识符](#identifiers)。除了 `inout``var` 以及 `let` 之外的关键字可以用作某个函数声明或者函数调用当中的外部参数名,无需添加反引号转义。当一个成员与一个关键字具有相同的名称时,不需要使用反引号来转义对该成员的引用,除非在引用该成员和使用该关键字之间存在歧义 - 例如,`self``Type``Protocol` 在显式的成员表达式中具有特殊的含义,因此它们必须在该上下文中使用反引号进行转义。
下面这些被保留的关键字不允许用作标识符,除非使用反引号转义,具体描述请参考 [标识符](#identifiers)。除了 `inout``var` 以及 `let` 之外的关键字可以用作某个函数声明或者函数调用当中的外部参数名,不用添加反引号转义
* 用在声明中的关键字: `associatedtype``class``deinit``enum``extension``func``import``init``inout``internal``let``operator``private``protocol``public``static``struct``subscript``typealias` 以及 `var`
* 用在声明中的关键字: `associatedtype``class``deinit``enum``extension``fileprivate ``func``import``init``inout``internal``let``open``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`
* 用在表达式和类型中的关键字:`as``Any``catch``false``is``nil``rethrows``super``self``Self``throw``throws``true` 以及 `try `
* 用在模式中的关键字:`_`
* 以井字号(`#`)开头的关键字:`#available``#column``#else#elseif``#endif``#file``#function``#if``#line` 以及 `#selector`
* 以井字号(`#`)开头的关键字:`#available``#colorLiteral``#column``#else``#elseif``#endif``#error``#file``#fileLiteral``#function``#if``#imageLiteral ``#line``#selector``#sourceLocation`以及 `#warning`
* 特定上下文中被保留的关键字: `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`。这些关键字在特定上下文之外可以被用做标识符。
以下符号被当作保留符号,不能用于自定义运算符: `(``)``{``}``[``]``.``,``:``;``=``@``#``&`(作为前缀运算符)、`->`、`` ` ``、`?`、`!`(作为后缀运算符)。
<a id="literals"></a>
## 字面量
## 字面量 {#literal}
*字面量literal* 用来表示源码中某种特定类型的值,比如一个数字或字符串。
@ -146,17 +168,16 @@ true // 布尔值字面量
> 字面量语法
>
> *字面量* → [*数值字面量*](#numeric-literal) | [*字符串字面量*](#string-literal) | [*布尔值字面量*](#boolean-literal) | [*nil 字面量*](#nil-literal)
<a id="numeric-literal"></a>
> *字面量* → [*数值字面量*](#integer-literal) | [*字符串字面量*](#string-literal) | [*布尔值字面量*](#integer-literal) | [*nil 字面量*](#integer-literal)
>
> *数值字面量* → **-**<sub>可选</sub> [*整数字面量*](#integer-literal) | **-**<sub>可选</sub> [*浮点数字面量*](#floating-point-literal)
> <a id="boolean-literal"></a>
>
> *布尔值字面量* → **true** | **false**
> <a id="nil-literal"></a>
>
> *nil 字面量* → **nil**
<a id="integer_literals"></a>
### 整数字面量
### 整数字面量{#integer-literal}
*整数字面量Integer Literals* 表示未指定精度整数的值。整数字面量默认用十进制表示,可以加前缀来指定其他的进制。二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。
@ -166,56 +187,92 @@ true // 布尔值字面量
整型字面面可以使用下划线(`_`)来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,这同样也会被系统所忽略,并不会影响字面量的值。
除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../chapter2/01_The_Basics.html#integers)。
除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../chapter2/01_The_Basics.md#integers)。
> 整数字面量语法
>
<a id="integer-literal"></a>
>
#### integer-literal {#integer-literal}
>
> *整数字面量* → [*二进制字面量*](#binary-literal)
>
> *整数字面量* → [*八进制字面量*](#octal-literal)
>
> *整数字面量* → [*十进制字面量*](#decimal-literal)
>
> *整数字面量* → [*十六进制字面量*](#hexadecimal-literal)
<a id="binary-literal"></a>
>
>
#### binary-literal {#binary-literal}
>
> *二进制字面量* → **0b** [*二进制数字*](#binary-digit) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
> <a id="binary-digit"></a>
>
>
#### binary-digit {#binary-digit}
>
> *二进制数字* → 数值 0 到 1
> <a id="binary-literal-character"></a>
>
> *二进制字面量字符* → [*二进制数字*](#binary-digit) | _
> <a id="binary-literal-characters"></a>
>
>
#### binary-literal-characters {#binary-literal-characters}
>
> *二进制字面量字符组* → [*二进制字面量字符*](#binary-literal-character) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
<a id="octal-literal"></a>
>
>
#### octal-literal {#octal-literal}
>
> *八进制字面量* → **0o** [*八进字数字*](#octal-digit) [*八进制字符组*](#octal-literal-characters)<sub>可选</sub>
> <a id="octal-digit"></a>
>
>
#### octal-digit {#octal-digit}
>
> *八进字数字* → 数值 0 到 7
> <a id="octal-literal-character"></a>
>
> *八进制字符* → [*八进字数字*](#octal-digit) | _
> <a id="octal-literal-characters"></a>
>
>
#### octal-literal-characters {#octal-literal-characters}
>
> *八进制字符组* → [*八进制字符*](#octal-literal-character) [*八进制字符组*](#octal-literal-characters)<sub>可选</sub>
<a id="decimal-literal"></a>
>
>
#### decimal-literal {#decimal-literal}
>
> *十进制字面量* → [*十进制数字*](#decimal-digit) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
> <a id="decimal-digit"></a>
>
>
#### decimal-digit {#decimal-digit}
>
> *十进制数字* → 数值 0 到 9
> <a id="decimal-digits"></a>
> *十进制数字组* → [*十进制数字*](#decimal-digit) [*十进制数字组*](#decimal-digits)<sub>可选</sub>
> <a id="decimal-literal-character"></a>
>
>
#### decimal-literal-characters {#decimal-literal-characters}
>
> *十进制数字组* → [*十进制数字*](#decimal-digit) [*十进制数字组*](#decimal-literal-characters)<sub>可选</sub>
>
> *十进制字符* → [*十进制数字*](#decimal-digit) | _
> <a id="decimal-literal-characters"></a>
> *十进制字符组* → [*十进制字符*](#decimal-literal-character) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
<a id="hexadecimal-literal"></a>
>
> *十进制字符组* → [*十进制字符*](#decimal-literal-characters) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
>
>
#### hexadecimal-literal {#hexadecimal-literal}
>
> *十六进制字面量* → **0x** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
> <a id="hexadecimal-digit"></a>
>
>
#### hexadecimal-digit {#hexadecimal-digit}
>
> *十六进制数字* → 数值 0 到 9, 字母 a 到 f, 或 A 到 F
> <a id="hexadecimal-literal-character"></a>
>
> *十六进制字符* → [*十六进制数字*](#hexadecimal-digit) | _
> <a id="hexadecimal-literal-characters"></a>
> *十六进制字面量字符组* → [*十六进制字符*](#hexadecimal-literal-character) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
>
>
#### hexadecimal-literal-characters {#hexadecimal-literal-characters}
>
> *十六进制字面量字符组* → [*十六进制字符*](#hexadecimal-literal-characters) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
<a id="floating_point_literals"></a>
### 浮点数字面量
### 浮点数字面量{#floating-point-literal}
*浮点数字面量Floating-point literals* 表示未指定精度浮点数的值。
@ -233,29 +290,49 @@ true // 布尔值字面量
> 浮点数字面量语法
>
<a id="floating-point-literal"></a>
>
#### floating-point-literal {#floating-point-literal}
>
> *浮点数字面量* → [*十进制字面量*](#decimal-literal) [*十进制分数*](#decimal-fraction)<sub>可选</sub> [*十进制指数*](#decimal-exponent)<sub>可选</sub>
>
> *浮点数字面量* → [*十六进制字面量*](#hexadecimal-literal) [*十六进制分数*](#hexadecimal-fraction)<sub>可选</sub> [*十六进制指数*](#hexadecimal-exponent)
<a id="decimal-fraction"></a>
>
>
#### decimal-fraction {#decimal-fraction}
>
> *十进制分数* → **.** [*十进制字面量*](#decimal-literal)
> <a id="decimal-exponent"></a>
>
>
#### decimal-exponent {#decimal-exponent}
>
> *十进制指数* → [*十进制指数 e*](#floating-point-e) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
<a id="hexadecimal-fraction"></a>
>
>
#### hexadecimal-fraction {#hexadecimal-fraction}
>
> *十六进制分数* → **.** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
> <a id="hexadecimal-exponent"></a>
>
>
#### hexadecimal-exponent {#hexadecimal-exponent}
>
> *十六进制指数* → [*十六进制指数 p*](#floating-point-p) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
<a id="floating-point-e"></a>
>
>
#### floating-point-e {#floating-point-e}
>
> *十进制指数 e* → **e** | **E**
> <a id="floating-point-p"></a>
>
>
#### floating-point-p {#floating-point-p}
>
> *十六进制指数 p* → **p** | **P**
> <a id="sign"></a>
>
>
#### sign {#sign}
>
> *正负号* → **+** | **-**
<a id="string_literals"></a>
### 字符串字面量
### 字符串字面量 {#string-literal}
字符串字面量是被引号包括的一串字符组成。 单行字符串字面量被包在双引号中的一串字符组成,形式如下:
@ -268,7 +345,7 @@ true // 布尔值字面量
> `字符`
> """
与单行字符串字面量不同的是,多行字符串字面量可以包含不转义的双引号("),回车以及换行。它不能包含三个转义的连续双引号。
与单行字符串字面量不同的是,多行字符串字面量可以包含不转义的双引号("),回车以及换行。它不能包含三个转义的连续双引号。
""" 之后的回车或者换行开始多行字符串字面量,不是字符串的一部分。 """ 之前回车或者换行结束字面量,也不是字符串的一部分。要让多行字符串字面量的开始或结束带有换行,就在第一行或者最后一行写一个空行。
@ -301,7 +378,41 @@ true // 布尔值字面量
let x = 3; "1 2 \(x)"
```
字符串字面量的默认推导类型为 `String`。更多有关 `String` 类型的信息请参考 [字符串和字符](../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)。
可以使用一对或多对扩展分隔符(#)包裹字符串进行分隔,被分隔的字符串的形式如下所示:
> \#"`characters`"#
>
> \#"""
>
> `characters`
>
> """#
特殊字符在被分隔符分隔的结果字符串中会展示为普通字符,而不是特殊字符。你可以使用扩展分隔符来创建一些具有特殊效果的字符串。例如,生成字符串插值,启动或终止转义序列(字符串)。
以下所示,由字符串字面量和扩展分隔符所创建的字符串是等价的:
```swift
let string = #"\(x) \ " \u{2603}"#
let escaped = "\\(x) \\ \" \\u{2603}"
print(string)
// Prints "\(x) \ " \u{2603}"
print(string == escaped)
// Prints "true"
```
如果在一个字符串中使用多对扩展分隔符,请不要在分隔符之间使用空格。
```swift
print(###"Line 1\###nLine 2"###) // OK
print(# # #"Line 1\# # #nLine 2"# # #) // Error
```
使用扩展分隔符创建的多行字符串字面量与普通多行字符串字面量具有相同的缩进要求。
字符串字面量的默认推导类型为 `String`。更多有关 `String` 类型的信息请参考 [字符串和字符](../chapter2/03_Strings_and_Characters.md) 以及 [*字符串结构参考*](https://developer.apple.com/documentation/swift/string)。
`` 操作符连接的字符型字面量是在编译时进行连接的。比如下面的 `textA``textB` 是完全一样的,`textA` 没有任何运行时的连接操作。
@ -312,34 +423,104 @@ let textB = "Hello world"
> 字符串字面量语法
>
<a id="string-literal"></a>
> *字符串字面量* → [*静态字符串字面量*](#static-string-literal) | [*插值字符串字面量*](#interpolated-string-literal)
<a id="static-string-literal"></a>
> *静态字符串字面量* → **"**[*引用文本*](#quoted-text)<sub>可选</sub>**"**
> <a id="quoted-text"></a>
>
> *字符串开分隔定界符* → [*字符串扩展分隔符*](#extended-string-literal-delimiter) **"**
>
> *字符串闭分隔定界符* → **"** [*字符串扩展分隔符*](#extended-string-literal-delimiter)<sub>可选</sub>
>
>
#### static-string-literal {#static-string-literal}
>
> *静态字符串字面量* → [*字符串开分隔定界符*](#extended-string-literal-delimiter) [*引用文本*](#quoted-text)<sub>可选</sub> [*字符串闭分隔定界符*](#extended-string-literal-delimiter)
>
> *静态字符串字面量* → [*多行字符串开分隔定界符*](#extended-string-literal-delimiter) [*多行引用文本*](#multiline-quoted-text)<sub>可选</sub> [*多行字符串闭分隔定界符*](#extended-string-literal-delimiter)
>
> *多行字符串开分隔定界符* → [*字符串扩展分隔符*](#extended-string-literal-delimiter) **"""**
>
> *多行字符串闭分隔定界符* → **"""** [*字符串扩展分隔符*](#extended-string-literal-delimiter)
>
>
#### extended-string-literal-delimiter {#extended-string-literal-delimiter}
>
> *字符串扩展分隔符* → **#** [*字符串扩展分隔符*](#extended-string-literal-delimiter)<sub>可选</sub>
>
>
#### quoted-text {#quoted-text}
>
> *引用文本* → [*引用文本项*](#quoted-text-item) [*引用文本*](#quoted-text)<sub>可选</sub>
> <a id="quoted-text-item"></a>
>
>
#### quoted-text-item {#quoted-text-item}
>
> *引用文本项* → [*转义字符*](#escaped-character)
>
> *引用文本项* → 除了 **"**、**\\**、U+000A、U+000D 以外的所有 Unicode 字符
<a id="interpolated-string-literal"></a>
> *插值字符串字面量* → **"**[*插值文本*](#interpolated-text)<sub>可选</sub>**"**
> <a id="interpolated-text"></a>
>
>
#### multiline-quoted-text {#multiline-quoted-text}
>
> *多行引用文本* → [*多行引用文本项*](#multiline-quoted-text-item) [*多行引用文本*](#multiline-quoted-text)<sub>可选</sub>
>
>
#### multiline-quoted-text-item {#multiline-quoted-text-item}
>
> *多行引用文本项* [*转义字符*](#escaped-character)<sub>可选</sub>
>
>
#### multiline-quoted-text {#multiline-quoted-text}
>
> *多行引用文本* → 除了 **\** 以外的任何Unicode标量值
>
> *多行引用文本* → [*转义换行*](#escaped-newline)
>
>
#### interpolated-string-literal {#interpolated-string-literal}
>
> *插值字符串字面量* → [*字符串开分隔定界符*](#extended-string-literal-delimiter) [*插值文本*](#interpolated-text)<sub>可选</sub> [*字符串闭分隔定界符*](#extended-string-literal-delimiter)
>
> *插值字符串字面量* → [*多行字符串开分隔定界符*](#extended-string-literal-delimiter) [*插值文本*](#interpolated-text)<sub>可选</sub> [*多行字符串闭分隔定界符*](#extended-string-literal-delimiter)
>
>
#### interpolated-text {#interpolated-text}
>
> *插值文本* → [*插值文本项*](#interpolated-text-item) [*插值文本*](#interpolated-text)<sub>可选</sub>
> <a id="interpolated-text-item"></a>
> *插值文本项* → **\\****(**[*表达式*](./04_Expressions.html)**)** | [*引用文本项*](#quoted-text-item)
<a id="escaped-character"></a>
> *转义字符* → **\\****0** | **\\****\\** | **\t** | **\n** | **\r** | **\\"** | **\\'**
> *转义字符* → **\u {** [*unicode 标量数字*](#unicode-scalar-digits) **}**
> <a id="unicode-scalar-digits"></a>
>
>
#### interpolated-text-item {#interpolated-text-item}
>
> *插值文本项* → **\\****(**[*表达式*](./04_Expressions.md)**)** | [*引用文本项*](#quoted-text-item)
>
> *多行插值文本* → [*多行插值文本项*](#multiline-quoted-text-item) [*多行插值文本*](#multiline-quoted-text)<sub>可选</sub>
>
> *多行插值文本项* → **\\(** [表达式](./04_Expressions.md) **)** | [多行引用文本项](#multiline-quoted-text-item)
>
>
#### escape-sequence {#escape-sequence}
>
> *转义序列* → **\\** [字符串扩展分隔符](#extended-string-literal-delimiter)
>
>
#### escaped-character {#escaped-character}
>
> *转义字符* → [*转义序列*](#escape-sequence) **0** | [*转义序列*](#escape-sequence) **\\** | [*转义序列*](#escape-sequence) **t** | [*转义序列*](#escape-sequence) **n** | [*转义序列*](#escape-sequence) **r** | [*转义序列*](#escape-sequence) **\"** | [*转义序列*](#escape-sequence) **'**
>
> *转义字符* → [*转义序列*](#escape-sequence) **u {** [*unicode 标量数字*](#unicode-scalar-digits) **}**
>
>
#### unicode-scalar-digits {#unicode-scalar-digits}
>
> *unicode 标量数字* → 一到八位的十六进制数字
>
>
#### escaped-newline {#escaped-newline}
>
> *转义换行符* → [*转义序列*](#escape-sequence) [*空白*](#whitespace)<sub>可选</sub> [*断行符*](#line-break)
<a id="operators"></a>
## 运算符
Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基础运算符](../chapter2/02_Basic_Operators.html) 和 [高级运算符](../chapter2/25_Advanced_Operators.html) 中进行了阐述。这一小节将描述哪些字符能用于自定义运算符。
## 运算符 {#operator}
Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基础运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/26_Advanced_Operators.md) 中进行了阐述。这一小节将描述哪些字符能用于自定义运算符。
自定义运算符可以由以下其中之一的 ASCII 字符 `/``=``-``+``!``*``%``<``>``&``|``^``?` 以及 `~`,或者后面语法中规定的任一个 Unicode 字符(其中包含了*数学运算符*、*零散符号Miscellaneous Symbols* 以及印刷符号Dingbats之类的 Unicode 块)开始。在第一个字符之后,允许使用组合型 Unicode 字符。
@ -364,52 +545,86 @@ Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基
在某些特定的设计中 ,以 `<``>` 开头的运算符会被分离成两个或多个符号,剩余部分可能会以同样的方式被再次分离。因此,在 `Dictionary<String, Array<Int>>` 中没有必要添加空白来消除闭合字符 `>` 的歧义。在这个例子中, 闭合字符 `>` 不会被视为单独的符号,因而不会被错误解析为 `>>` 运算符。
要学习如何自定义运算符,请参考 [自定义运算符](../chapter2/25_Advanced_Operators.html#custom_operators) 和 [运算符声明](05_Declarations.html#operator_declaration)。要学习如何重载运算符,请参考 [运算符函数](../chapter2/25_Advanced_Operators.html#operator_functions)。
要学习如何自定义运算符,请参考 [自定义运算符](../chapter2/26_Advanced_Operators.md#custom_operators) 和 [运算符声明](./06_Declarations.md#operator_declaration)。要学习如何重载运算符,请参考 [运算符函数](../chapter2/26_Advanced_Operators.md#operator_functions)。
> 运算符语法
>
<a id="operator"></a>
> *运算符* → [*头部运算符*](#operator-head) [*运算符字符组*](#operator-characters)<sub>可选</sub>
> *运算符* → [*头部点运算符*](#dot-operator-head) [*点运算符字符组*](#dot-operator-characters)<sub>可选</sub>
<a id="operator-head"></a>
>
> *运算符* → [*头部点运算符*](#dot-operator-head) [*点运算符字符组*](#dot-operator-characters)
>
>
#### operator-head {#operator-head}
>
> *头部运算符* → **/** | **=** | **-** | **+** | **!** | __*__ | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **?**
>
> *头部运算符* → U+00A1U+00A7
>
> *头部运算符* → U+00A9 或 U+00AB
>
> *头部运算符* → U+00AC 或 U+00AE
>
> *头部运算符* → U+00B0U+00B1U+00B6U+00BBU+00BFU+00D7或 U+00F7
>
> *头部运算符* → U+2016U+2017 或 U+2020U+2027
>
> *头部运算符* → U+2030U+203E
>
> *头部运算符* → U+2041U+2053
>
> *头部运算符* → U+2055U+205E
>
> *头部运算符* → U+2190U+23FF
>
> *头部运算符* → U+2500U+2775
>
> *头部运算符* → U+2794U+2BFF
>
> *头部运算符* → U+2E00U+2E7F
>
> *头部运算符* → U+3001U+3003
>
> *头部运算符* → U+3008U+3030
<a id="operator-character"></a>
>
>
#### operator-character {#operator-character}
>
> *运算符字符* → [*头部运算符*](#operator-head)
>
> *运算符字符* → U+0300U+036F
>
> *运算符字符* → U+1DC0U+1DFF
>
> *运算符字符* → U+20D0U+20FF
>
> *运算符字符* → U+FE00U+FE0F
>
> *运算符字符* → U+FE20U+FE2F
>
> *运算符字符* → U+E0100U+E01EF
> <a id="operator-characters"></a>
>
>
#### operator-characters {#operator-characters}
>
> *运算符字符组* → [*运算符字符*](#operator-character) [*运算符字符组*](#operator-characters)<sub>可选</sub>
<a id="dot-operator-head"></a>
>
>
#### dot-operator-head {#dot-operator-head}
>
> *头部点运算符* → **..**
> <a id="dot-operator-character"></a>
>
>
#### dot-operator-character {#dot-operator-character}
>
> *点运算符字符* → **.** | [*运算符字符*](#operator-character)
> <a id="dot-operator-characters"></a>
>
>
#### dot-operator-characters {#dot-operator-characters}
>
> *点运算符字符组* → [*点运算符字符*](#dot-operator-character) [*点运算符字符组*](#dot-operator-characters)<sub>可选</sub>
<a id="binary-operator"></a>
>
> *二元运算符* → [*运算符*](#operator)
> <a id="prefix-operator"></a>
>
> *前缀运算符* → [*运算符*](#operator)
> <a id="postfix-operator"></a>
>
> *后缀运算符* → [*运算符*](#operator)

View File

@ -1,64 +1,46 @@
# 类型Types
-----------------
> 1.0
> 翻译:[lyuka](https://github.com/lyuka)
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
Swift 语言存在两种类型:命名型类型和复合型类型。*命名型类型*是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义类 `MyClass` 的实例拥有类型 `MyClass`。除了用户定义的命名型类型Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
> 2.0
> 翻译+校对:[EudeMorgen](https://github.com/EudeMorgen)
那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../chapter2/20_Extensions.md) 和 [扩展声明](./06_Declarations.md#extension_declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。
> 2.1
> 翻译:[mmoaay](https://github.com/mmoaay)
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
- [类型注解](#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 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../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)`
你可以在命名型类型和复合型类型使用小括号。但是在类型旁加小括号没有任何作用。举个例子,`(Int)` 等同于 `Int`
本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。
#### type {#type}
> 类型语法
>
<a name="type"></a>
> *类型* → [*数组类型*](#array-type)
>
> *类型* → [*字典类型*](#dictionary-type)
>
> *类型* → [*函数类型*](#function-type)
>
> *类型* → [*类型标识*](#type-identifier)
>
> *类型* → [*元组类型*](#tuple-type)
>
> *类型* → [*可选类型*](#optional-type)
>
> *类型* → [*隐式解析可选类型*](#implicitly-unwrapped-optional-type)
>
> *类型* → [*协议合成类型*](#protocol-composition-type)
>
> *类型* → [*元型类型*](#metatype-type)
>
> *类型* → **任意类型**
>
> *类型* → **自身类型**
>
> *类型* → [*(类型)*](#type)
>
<a name="type_annotation"></a>
## 类型注解
类型注解显式地指定一个变量或表达式的值。类型注解始于冒号 `:` 终于类型,比如下面两个例子:
## 类型注解 {#type-annotation}
*类型注解*显式地指定一个变量或表达式的类型。类型注解始于冒号 `:` 终于类型,比如下面两个例子:
```swift
let someTuple: (Double, Double) = (3.14159, 2.71828)
@ -71,12 +53,12 @@ func someFunction(a: Int) { /* ... */ }
> 类型注解语法
>
<a name="type-annotation"></a>
> *类型注解* → **:** [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [*类型*](#type)
<a name="type_identifier"></a>
## 类型标识符
#### type-annotation {#type-annotation}
> *类型注解* → **:** [*特性列表*](./07_Attributes.md#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [*类型*](#type)
>
## 类型标识符 {#type-identifier}
类型标识符引用命名型类型,还可引用命名型或复合型类型的别名。
大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 `Int` 引用命名型类型 `Int`,同样,类型标识符 `Dictionary<String, Int>` 引用命名型类型 `Dictionary<String, Int>`
@ -96,17 +78,19 @@ var someValue: ExampleModule.MyType
> 类型标识符语法
>
<a name="type-identifier"></a>
> *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)<sub>可选</sub> | [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)<sub>可选</sub> **.** [*类型标识符*](#type-identifier)
<a name="type-name"></a>
> *类型名称* → [*标识符*](02_Lexical_Structure.html#identifier)
<a name="tuple_type"></a>
## 元组类型
#### type-identifier {#type-identifier}
> *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](./09_Generic_Parameters_and_Arguments.md#generic_argument_clause)<sub>可选</sub> | [*类型名称*](#type-name) [*泛型参数子句*](./09_Generic_Parameters_and_Arguments.md#generic_argument_clause)<sub>可选</sub> **.** [*类型标识符*](#type-identifier)
>
#### type-name {#type-name}
> *类型名称* → [*标识符*](./02_Lexical_Structure.md#identifier)
>
## 元组类型 {#tuple-type}
元组类型是使用括号括起来的零个或多个类型,类型间用逗号隔开。
你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../chapter2/06_Functions.html#functions_with_multiple_return_values) 章节里有一个展示上述特性的例子。
你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../chapter2/06_Functions.md#functions_with_multiple_return_values) 章节里有一个展示上述特性的例子。
当一个元组类型的元素有名字的时候,这个名字就是类型的一部分。
@ -121,31 +105,37 @@ someTuple = (left: 5, right: 5) // 错误:命名类型不匹配
> 元组类型语法
>
<a name="tuple-type"></a>
> *元组类型* → **(** [*元组类型元素列表*](#tuple-type-element-list) <sub>可选</sub> **)**
<a name="tuple-type-element-list"></a>
#### tuple-type {#tuple-type}
> *元组类型* → **(** **)** | **(** [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list) **)**
>
#### tuple-type-element-list {#tuple-type-element-list}
> *元组类型元素列表* → [*元组类型元素*](#tuple-type-element) | [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list)
<a name="tuple-type-element"></a>
>
#### tuple-type-element {#tuple-type-element}
> *元组类型元素* → [*元素名*](#element-name) [*类型注解*](#type-annotation) | [*类型*](#type)
<a name="element-name"></a>
> *元素名* → [*标识符*](02_Lexical_Structure.html#identifier)
>
<a name="function_type"></a>
## 函数类型
#### element-name {#element-name}
> *元素名* → [*标识符*](./02_Lexical_Structure.md#identifier)
>
## 函数类型 {#function-type}
函数类型表示一个函数、方法或闭包的类型,它由参数类型和返回值类型组成,中间用箭头(`->`)隔开:
> `参数类型` -> `返回值类型`
> `参数类型`->`返回值类型`
参数类型是由逗号间隔的类型列表。由于参数类型和返回值类型可以是元组类型,所以函数类型支持多参数与多返回值的函数与方法。
*参数类型*是由逗号间隔的类型列表。由于*返回值类型*可以是元组类型,所以函数类型支持多返回值的函数与方法。
你可以对函数参数 `() -> T`(其中 T 是任何类型)使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被调用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.html#autoclosures)
你可以对参数类型为 `() -> T`(其中 T 是任何类型)的函数使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被调用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.md#autoclosures)。
函数类型可以拥有一个可变长参数作为参数类型中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.html#variadic_parameters)。
函数类型可以拥有一个可变长参数作为*参数类型*中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.md#variadic_parameters)。
为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.html#in_out_parameters)。
为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.md#in_out_parameters)。
如果一个函数类型只有一个形式参数而且形式参数的类型是元组类型,那么元组类型在写函数类型的时候必须用圆括号括起来。比如说,`((Int, Int)) -> Void` 是接收一个元组 `(Int, Int)` 作为形式参数的函数类型。与此相,不加括号的 `(Int, Int) -> Void` 是一个接收两个 `Int` 形式参数并且不返回任何值的函数类型。相似地,因为 `Void` 是空元组类型 `()` 的别名, 函数类型 `(Void)-> Void`一个空元组的变量的函数类型 `(()) -> ()` 是一样的。但这些类型和无变量的函数类型 `() -> ()` 是不一样的。
如果一个函数类型只有一个形式参数而且形式参数的类型是元组类型,那么元组类型在写函数类型的时候必须用圆括号括起来。比如说,`((Int, Int)) -> Void` 是接收一个元组 `(Int, Int)` 作为形式参数并且不返回任何值的函数类型。与此相,不加括号的 `(Int, Int) -> Void` 是一个接收两个 `Int` 作为形式参数并且不返回任何值的函数类型。相似地,因为 `Void` 是空元组类型 `()` 的别名,函数类型 `(Void)-> Void``(()) -> ()` 是一样的 - 一个将空元组作为唯一参数的函数。但这些类型和无变量的函数类型 `() -> ()` 是不一样的。
函数和方法中的变量名并不是函数类型的一部分。例如:
@ -155,13 +145,14 @@ func anotherFunction(left: Int, right: Int) {}
func functionWithDifferentLabels(top: Int, bottom: Int) {}
var f = someFunction // 函数 f 的类型为 (Int, Int) -> Void, 而不是 (left: Int, right: Int) -> Void.
f = anotherFunction // 正确
f = functionWithDifferentLabels // 正确
func functionWithDifferentArgumentTypes(left: Int, right: String) {}
func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
f = functionWithDifferentArgumentTypes // 错误
func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
f = functionWithDifferentNumberOfArguments // 错误
```
@ -173,13 +164,14 @@ var operation: (_ lhs: Int, _ rhs: Int) -> Int // 正确
var operation: (Int, Int) -> Int // 正确
```
如果一个函数类型包涵多个箭头(->),那么函数类型将从右向左进行组合。例如,函数类型 `Int -> Int -> Int` 可以理解为 `Int -> (Int -> Int)`,也就是说,该函数类型的参数为 `Int` 类型,其返回类型是一个参数类型为 `Int`,返回类型为 `Int` 的函数类型
如果一个函数类型包涵多个箭头(->),那么函数类型将从右向左进行组合。例如,函数类型 `(Int) -> (Int) -> Int` 可以理解为 `(Int) -> ((Int) -> Int)`,也就是说,该函数类型的参数为 `Int` 类型,其返回类型是一个参数类型为 `Int`,返回类型为 `Int` 的函数。
函数类型若要抛出错误就必须使用 `throws` 关键字来标记,若要重抛错误则必须使用 `rethrows` 关键字来标记。`throws` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](05_Declarations.html#throwing_functions_and_methods) 和 [重抛函数与方法](05_Declarations.html#rethrowing_functions_and_methods)。
函数类型若要抛出错误就必须使用 `throws` 关键字来标记,若要重抛错误则必须使用 `rethrows` 关键字来标记。`throws` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](./06_Declarations.md#throwing_functions_and_methods) 和 [重抛函数与方法](./06_Declarations.md#rethrowing_functions_and_methods)。
<a name="Restrictions for Nonescaping Closures"></a>
### 对非逃逸闭包的限制
非逃逸闭包函数不能作为参数传递到另一个非逃逸闭包函数的参数。这样的限制可以让 Swift 在编译时就完成更多的内存访问冲突检查, 而不是在运行时。举个例子:
### 对非逃逸闭包的限制 {#Restrictions for Nonescaping Closures}
当非逃逸闭包函数是参数时,不能存储在属性、变量或任何 `Any` 类型的常量中,因为这可能导致值的逃逸。
当非逃逸闭包函数是参数时,不能作为参数传递到另一个非逃逸闭包函数中。这样的限制可以让 Swift 在编译时就完成更多的内存访问冲突检查,而不是在运行时。举个例子:
```swift
let external: (Any) -> Void = { _ in () }
@ -195,34 +187,43 @@ func takesTwoFunctions(first: (Any) -> Void, second: (Any) -> Void) {
}
```
在上面代码里,`takesTwoFunctions(first:second:)` 的两个参数都是函数。 它们都没有标记为 `@escaping`, 因此它们都是非逃逸的。
在上面代码里,`takesTwoFunctions(first:second:)` 的两个参数都是函数。它们都没有标记为 `@escaping`, 因此它们都是非逃逸的。
上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为第一个和第二个参数是非逃逸函数,它们不能够被当作变量被传递到另一个非闭包函数参数。与此相反, 标记“正确”的两个函数不产生编译错误。这些函数调用不会违反限制, 因为 `外部(external` 不是 `takesTwoFunctions(first:second:)` 里的一个参数
如果你需要避免这个限制, 标记其中之一的参数为逃逸, 或者使用 `withoutActuallyEscaping(_:do:)` 函数临时地转换非逃逸函数的其中一个参数为逃逸函数。关于避免内存访问冲突,可以参阅[内存安全](../chapter2/24_Memory_Safety.html)。
上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为参数 `first``second` 是非逃逸函数,它们不能够作为参数被传递到另一个非闭包函数。相对的, 标记“正确”的两个函数不产生编译错误。这些函数调用不会违反限制,因为 `external` 不是 `takesTwoFunctions(first:second:)` 的参数之一
如果你需要避免这个限制,标记其中之一的参数为逃逸,或者使用 `withoutActuallyEscaping(_:do:)` 函数临时地转换非逃逸函数的其中一个参数为逃逸函数。关于避免内存访问冲突,可以参阅[内存安全](../chapter2/24_Memory_Safety.md)。
> 函数类型语法
>
<a name="function-type"></a>
> *函数类型* → [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> [*函数类型子句*](#function-type-argument-clause) **throws**<sub>可选</sub> **->** [*类型*](#type)
> *函数类型* → [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> [*函数类型子句*](#function-type-argument-clause) **rethrows­** **->** [*类型*](#type)
<a name="function-type-argument-clause"></a>
> *函数类型子句* → (­)­
> *函数类型子句* → ([*函数类型参数列表*](#function-type-argument-list)*...*­<sub>可选</sub>)­
<a name="function-type-argument-list"></a>
#### function-type {#function-type}
> *函数类型* → [*特性列表*](./07_Attributes.md#attributes)<sub>可选</sub> [*函数类型子句*](#function-type-argument-clause) **throws**<sub>可选</sub> **->** [*类型*](#type)
>
> *函数类型* → [*特性列表*](./07_Attributes.md#attributes)<sub>可选</sub> [*函数类型子句*](#function-type-argument-clause) **rethrows­** **->** [*类型*](#type)
>
#### function-type-argument-clause {#function-type-argument-clause}
> *函数类型子句* → **(**­ **)**­
> *函数类型子句* → **(** [*函数类型参数列表*](#function-type-argument-list) *...*­ <sub>可选</sub> **)**
>
#### function-type-argument-list {#function-type-argument-list}
> *函数类型参数列表* → [*函数类型参数*](function-type-argument) | [*函数类型参数*](function-type-argument) [*函数类型参数列表*](#function-type-argument-list)
<a name="function-type-argument"></a>
> *函数类型参数* → [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [*类型*](#type) | [*参数标签*](#argument-label) [*类型注解*](#type-annotation)
<a name="argument-label"></a>
> *参数标签* → [*标识符*](02_Lexical_Structure.html#identifier)
>
<a name="array_type"></a>
## 数组类型
#### function-type-argument {#function-type-argument}
> *函数类型参数* → [*特性列表*](./07_Attributes.md#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [*类型*](#type) | [*参数标签*](#argument-label) [*类型注解*](#type-annotation)
>
#### argument-label {#argument-label}
> *参数标签* → [*标识符*](./02_Lexical_Structure.md#identifier)
>
## 数组类型 {#array-type}
Swift 语言为标准库中定义的 `Array<Element>` 类型提供了如下语法糖:
> [`类型`]
>
换句话说,下面两个声明是等价的:
@ -241,19 +242,20 @@ 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` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.html#arrays)。
关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.md#arrays)。
> 数组类型语法
>
<a name="array-type"></a>
#### array-type {#array-type}
> *数组类型* → **[** [*类型*](#type) **]**
>
<a name="dictionary_type"></a>
## 字典类型
## 字典类型 {#dictionary-type}
Swift 语言为标准库中定义的 `Dictionary<Key, Value>` 类型提供了如下语法糖:
> [`键类型` : `值类型`]
>
换句话说,下面两个声明是等价的:
@ -264,21 +266,21 @@ let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]
上面两种情况,常量 `someDictionary` 被声明为一个字典,其中键为 `String` 类型,值为 `Int` 类型。
字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。如果键在字典中不存在的话,则这个下标返回 `nil`
字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。通过下标访问会获取对应值的可选类型。如果键在字典中不存在的话,则这个下标返回 `nil`
字典中键的类型必须符合 Swift 标准库中的 `Hashable` 协议。
关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../chapter2/04_Collection_Types.html#dictionaries)。
关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../chapter2/04_Collection_Types.md#dictionaries)。
> 字典类型语法
>
<a name="dictionary-type"></a>
#### dictionary-type {#dictionary-type}
> *字典类型* → **[** [*类型*](#type) **:** [*类型*](#type) **]**
>
<a name="optional_type"></a>
## 可选类型
Swift 定义后缀 `?` 来作为标准库中的定义的命名型类型 `Optional<Wrapped>` 的语法糖。换句话说,下面两个声明是等价的:
## 可选类型 {#optional-type}
Swift 定义后缀 `?` 来作为标准库中定义的命名型类型 `Optional<Wrapped>` 的语法糖。换句话说,下面两个声明是等价的:
```swift
var optionalInteger: Int?
@ -287,7 +289,7 @@ var optionalInteger: Optional<Int>
在上述两种情况下,变量 `optionalInteger` 都被声明为可选整型类型。注意在类型和 `?` 之间没有空格。
类型 `Optional<Wrapped>` 是一个枚举,有两个成员,`none``some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明或定义可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`
类型 `Optional<Wrapped>` 是一个枚举,有两个成员,`none``some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`
如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 `!` 来获取该值,正如下面描述的:
@ -300,17 +302,17 @@ optionalInteger! // 42
你也可以使用可选链式调用和可选绑定来选择性地在可选表达式上执行操作。如果值为 `nil`,不会执行任何操作,因此也就没有运行错误产生。
更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../chapter2/01_The_Basics.html#optionals)。
更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../chapter2/01_The_Basics.md#optionals)。
> 可选类型语法
>
<a name="optional-type"></a>
#### optional-type {#optional-type}
> *可选类型* → [*类型*](#type) **?**
>
<a name="implicitly_unwrapped_optional_type"></a>
## 隐式解析可选类型
当可以被访问时Swift 语言定义后缀 `!` 作为标准库中命名类型 `Optional<Wrapped>` 的语法糖,来实现自动解包的功能。换句话说,下面两个声明等价:
## 隐式解析可选类型 {#implicitly-unwrapped-optional-type}
当可以被访问时Swift 语言定义后缀 `!` 作为标准库中命名类型 `Optional<Wrapped>` 的语法糖,来实现自动解包的功能。如果尝试对一个值为 `nil` 的可选类型进行隐式解包,将会产生运行时错误。因为隐式解包,下面两个声明等价:
```swift
var implicitlyUnwrappedString: String!
@ -319,7 +321,7 @@ var explicitlyUnwrappedString: Optional<String>
注意类型与 `!` 之间没有空格。
由于隐式解包修改了包涵其类型的声明语义,嵌套在元组类型或泛型可选类型(比如字典元素类型或数组元素类型),不能被标记为隐式解包。例如:
由于隐式解包会更改包含该类型的声明语义,嵌套在元组类型或泛型可选类型(比如字典元素类型或数组元素类型),不能被标记为隐式解包。例如:
```swift
let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // 错误
@ -329,49 +331,61 @@ let arrayOfImplicitlyUnwrappedElements: [Int!] // 错误
let implicitlyUnwrappedArray: [Int]! // 正确
```
由于隐式解析可选类型和可选类型有同样的表达式 `Optional<Wrapped>`,你可以在使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。
由于隐式解析可选类型和可选类型有同样的类型 `Optional<Wrapped>`,你可以在所有使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。
正如可选类型一样,你在声明隐式解析可选类型的变量或属性的时候也不用指定初始值,因为它有默认值 `nil`
正如可选类型一样,如果你在声明隐式解析可选类型的变量或属性的时候没有指定初始值,它的值则会自动赋为默认值 `nil`
可以使用可选链式调用来在隐式解析可选表达式选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。
可以使用可选链式调用隐式解析可选表达式选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。
关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../chapter2/01_The_Basics.html#implicityly_unwrapped_optionals)。
关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../chapter2/01_The_Basics.md#implicityly_unwrapped_optionals)。
> 隐式解析可选类型语法
>
<a name="implicitly-unwrapped-optional-type"></a>
#### implicitly-unwrapped-optional-type {#implicitly-unwrapped-optional-type}
> *隐式解析可选类型* → [*类型*](#type) **!**
>
<a name="protocol_composition_type"></a>
## 协议合成类型
协议合成类型是一种符合协议列表中每个指定协议的类型。协议合成类型可能会用在类型注解和泛型参数中。
## 协议合成类型 {#protocol-composition-type}
协议合成类型定义了一种遵循协议列表中每个指定协议的类型,或者一个现有类型的子类并遵循协议列表中每个指定协议。协议合成类型只能用在类型注解、泛型参数子句和泛型 `where` 子句中指定类型。
协议合成类型的形式如下:
> `Protocol 1` & `Procotol 2`
>
协议合成类型允许你指定一个值,其类型符合多个协议的要求不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `Protocol A & Protocol B & Protocol C` 等效于一个从 `Protocol A``Protocol B``Protocol C` 继承而来的新协议 `Protocol D`,很显然这样做有效率的多,甚至不需引入一个新名字
协议合成类型允许你指定一个值,其类型遵循多个协议的要求不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `Protocol A & Protocol B & Protocol C` 等效于一个从 `Protocol A``Protocol B``Protocol C` 继承而来的新协议。同样的,你可以使用 `SuperClass & ProtocolA` 来取代申明一个新的协议作为 `SuperClass` 的子类并遵循 `ProtocolA`
协议合成列表中的每项必须是协议名或协议合成类型的类型别名。
协议合成列表中的每一项都必须是下面所列情况之一,列表中最多只能包含一个类:
- 类名
- 协议名
- 一个类型别名,它的潜在类型是一个协议合成类型、一个协议或者一个类
当协议合成类型包含类型别名时,同一个协议可能多次出现在定义中 — 重复被忽略。例如,下面代码中定义的 `PQR` 等同于 `P & Q & R`
```swift
typealias PQ = P & Q
typealias PQR = PQ & Q & R
```
> 协议合成类型语法
>
<a name="protocol-composition-type"></a>
#### protocol-composition-type {#protocol-composition-type}
> *协议合成类型* → [*协议标识符*](#protocol-identifier) & [*协议合成延续*](#protocol-composition-continuation)
<a name="protocol-composition-continuation"></a>
>
#### protocol-composition-continuation {#protocol-composition-continuation}
> *协议合成延续* → [*协议标识符*](#protocol-identifier) | [*协议合成类型*](#protocol-composition-type)
<a name="protocol-identifier"></a>
> *协议标识符* → [*类型标识符*](#type-identifier)
>
<a name="metatype_type"></a>
## 元类型
## 元类型 {#metatype-type}
元类型是指任意类型的类型,包括类类型、结构体类型、枚举类型和协议类型。
元类型是指类型的类型,包括类类型、结构体类型、枚举类型和协议类型
类、结构体或枚举类型的元类型是相应的类型名紧跟 `.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` 的某个类型的实例。还可以对类型的实例使用 `type(of:)` 表达式来获取该实例在运行阶段的类型,如下所示:
你可以使用后缀 `self` 表达式来获取类型。比如,`SomeClass.self` 返回 `SomeClass` 本身,而不`SomeClass` 的一个实例。同样,`SomeProtocol.self` 返回 `SomeProtocol` 本身,而不是运行时遵循 `SomeProtocol` 的某个类型的实例。还可以对类型的实例使用 `type(of:)` 表达式来获取该实例动态的、在运行阶段的类型,如下所示:
```swift
class SomeBaseClass {
@ -388,10 +402,10 @@ let someInstance: SomeBaseClass = SomeSubClass()
// someInstance 在编译期是 SomeBaseClass 类型,
// 但是在运行期则是 SomeSubClass 类型
type(of: someInstance).printClassName()
// 打印 “SomeSubClass”
// 打印“SomeSubClass”
```
更多信息可以查看 Swift 标准库里的 `type(of:)`
更多信息可以查看 Swift 标准库里的 [type(of:)](https://developer.apple.com/documentation/swift/2885064-type)
可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,被调用的构造器必须使用 `required` 关键字标记,或者整个类使用 `final` 关键字标记。
@ -411,32 +425,35 @@ let anotherInstance = metatype.init(string: "some string")
> 元类型语法
>
<a name="metatype-type"></a>
#### metatype-type {#metatype-type}
> *元类型* → [*类型*](#type) **.** **Type** | [*类型*](#type) **.** **Protocol**
>
<a name="type_inheritance_clause"></a>
## 类型继承子句
## 类型继承子句 {#type-inheritance-clause}
类型继承子句被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句开始于冒号 `:`,其后是类型标识符列表。
型继承子句被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句也用来指定一个类类型专属协议。类型继承子句开始于冒号 `:`,其后是所需要的类、类型标识符列表或两者都有
可以继承自单个超类,并遵循任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要遵循的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../chapter2/13_Inheritance.md)
类可以继承单个超类,采纳任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要采纳的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../chapter2/13_Inheritance.html)
其它命名型类型只能继承自或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件
其它命名型类型可能只继承或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件。正如在 [协议声明](05_Declarations.html#protocol_declaration) 中所讨论的那样,可以把 `class` 关键字放到协议类型的类型继承子句的首位,这样就可以声明一个类类型专属协议
枚举定义中的类型继承子句可以是一系列协议,或是枚举的原始值类型的命名型类型。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../chapter2/08_Enumerations.html#raw_values)。
枚举定义中的类型继承子句可以是一系列协议,或者是指定单一的命名类型,此时枚举为其用例分配原始值。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../chapter2/08_Enumerations.md#raw_values)
> 类型继承子句语法
>
<a name="type_inheritance_clause"></a>
#### type_inheritance_clause {#type-inheritance-clause}
> *类型继承子句* → **:** [*类型继承列表*](#type-inheritance-list)
<a name="type-inheritance-list"></a>
>
#### type-inheritance-list {#type-inheritance-list}
> *类型继承列表* → [*类型标识符*](#type-identifier) | [*类型标识符*](#type-identifier) **,** [*类型继承列表*](#type-inheritance-list)
<a name="class-requirement"></a>
>
#### class-requirement {#class-requirement}
<a name="type_inference"></a>
## 类型推断
## 类型推断 {#type-inference}
Swift 广泛使用类型推断,从而允许你省略代码中很多变量和表达式的类型或部分类型。比如,对于 `var x: Int = 0`,你可以完全省略类型而简写成 `var x = 0`,编译器会正确推断出 `x` 的类型 `Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以省略类型的一部分。比如,如果你写了 `let dict: Dictionary = ["A" : 1]`,编译器能推断出 `dict` 的类型是 `Dictionary<String, Int>`
在上面的两个例子中,类型信息从表达式树的叶子节点传向根节点。也就是说,`var x: Int = 0``x` 的类型首先根据 `0` 的类型进行推断,然后将该类型信息传递到根节点(变量 `x`)。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,208 +1,336 @@
# 特性Attributes
-----------------
> 1.0
> 翻译:[Hawstein](https://github.com/Hawstein)
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
在 Swift 中有两种特性,分别用于修饰声明和类型。特性提供了有关声明和类型的更多信息。例如,使用 `discardableResult` 特性声明的函数,表明该函数虽然有返回值,但如果没有使用该返回值,编译器不会产生警告。
> 2.0
> 翻译+校对:[KYawn](https://github.com/KYawn)
您可以通过以下方式指定一个特性,通过符号 `@` 后跟特性的名称和特性接收的任何参数:
> 2.1
> 翻译:[小铁匠 Linus](https://github.com/kevin833752)
@`特性名`
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
@`特性名`(`特性参数`)
本页内容包括:
有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰某个特定的声明的。这些_特性的参数_写在圆括号内它们的格式由它们所属的特性来定义。
- [声明特性](#declaration_attributes)
- [Interface Builder 使用的声明特性](#declaration_attributes_used_by_interface_builder)
- [类型特性](#type_attributes)
特性提供了有关声明和类型的更多信息。在 Swift 中有两种特性,分别用于修饰声明和类型。
您可以通过以下方式指定一个特性:符号 `@` 后跟特性的名称和特性接收的任何参数:
> @ `特性名`
> @ `特性名``特性参数`
有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰某个特定的声明的。这些特性的参数写在圆括号内,它们的格式由它们所属的特性来定义。
<a name="declaration_attributes"></a>
##声明特性
## 声明特性 {#declaration-attributes}
声明特性只能应用于声明。
`available`
### `available` {#available}
`available` 特性用于声明时,表示该声明的生命周期特定的平台和操作系统版本有关
`available` 特性用于声明时,表示该声明的生命周期是相对于特定的平台和操作系统版本。
`available` 特性经常与参数列表一同出现,该参数列表至少有两个特性参数,参数之间由逗号分隔。这些参数由以下这些平台名字中的一个起头:
- iOS
- iOSApplicationExtension
- macOS
- macOSApplicationExtension
- watchOS
- watchOSApplicationExtension
- tvOS
- tvOSApplicationExtension
- `iOS`
- `iOSApplicationExtension`
- `macOS`
- `macOSApplicationExtension`
- `watchOS`
- `watchOSApplicationExtension`
- `tvOS`
- `tvOSApplicationExtension`
- `swift`
当然,你也可以用一个星号(`*`)来表示上面提到的所有平台。指定 Swift 版本的 `available` 特性参数,不能使用星号表示。
当然,你也可以用一个星号(*)来表示上面提到的所有平台。
其余的参数,可以按照任何顺序出现,并且可以添加关于声明生命周期的附加信息,包括重要事件。
- `unavailable` 参数表示该声明在指定的平台上是无效的。
- `unavailable` 参数表示该声明在指定的平台上是无效的。当指定 Swift 版本可用性时不可使用该参数。
- `introduced` 参数表示指定平台从哪一版本开始引入该声明。格式如下:
`introduced`=` 版本号 `
`introduced`: `版本号`
*版本号*由一个或多个正整数构成,由句点分隔的。
*版本号*由一至三个正整数构成,由句点分隔的。
- `deprecated` 参数表示指定平台从哪一版本开始弃用该声明。格式如下:
`deprecated`=` 版本号 `
`deprecated`: `版本号`
可选的*版本号*由一个或多个正整数构成,由句点分隔的。省略版本号表示该声明目前已弃用,当弃用出现时无需给出任何有关信息。如果你省略了版本号,冒号(:)也可省略。
可选的*版本号*由一个或多个正整数构成,由句点分隔的。省略版本号表示该声明目前已弃用,当弃用出现时没有给出任何有关信息。如果你省略了版本号,冒号(`:`)也可省略。
- `obsoleted` 参数表示指定平台从哪一版本开始废弃该声明。当一个声明被废弃后,它就从平台中移除,不能再被使用。格式如下:
- `obsoleted` 参数表示指定平台或语言从哪一版本开始废弃该声明。当一个声明被废弃后,它就从平台或语言中移除,不能再被使用。格式如下:
`obsoleted`=` 版本号 `
`obsoleted`: `版本号`
*版本号*由一个或多个正整数构成,由句点分隔的。
*版本号*由一至三个正整数构成,由句点分隔的。
- `message` 参数用来提供文本信息。当使用被弃用或者被废弃的声明时,编译器会抛出警告或错误信息。格式如下:
`message`=` 信息内容 `
`message`: `信息内容`
信息内容由一个字符串构成。
_信息内容_由一个字符串构成。
- `renamed` 参数用来提供文本信息,用以表示被重命名的声明的新名字。当使用声明的旧名字时,编译器会报错提示新名字。格式如下:
`renamed`=` 新名字 `
`renamed`: `新名字`
新名字由一个字符串构成。
_新名字_由一个字符串构成。
你可以将 `renamed` 参数和 `unavailable` 参数以及类型别名声明组合使用,以此向用户表示某个声明已经被重命名。当某个声明的名字在一个框架或者库的不同发布版本间发生变化时,这会相当有用
你可以将 `renamed` 参数和 `unavailable` 参数用于 `available` 特性,来表示声明在不同平台和 Swift 版本上的可用性。如下所示,表示声明的名字在一个框架或者库的不同发布版本间发生变化。以此组合表示该声明被重命名的编译错误
```swift
// 首发版本
protocol MyProtocol {
// 这里是协议定义
// 这里是协议定义
}
```
```swift
// 后续版本重命名了 MyProtocol
protocol MyRenamedProtocol {
// 这里是协议定义
// 这里是协议定义
}
@available(*, unavailable, renamed:"MyRenamedProtocol")
typealias MyProtocol = MyRenamedProtocol
```
你可以在某个声明上使用多个 `available` 特性,以指定该声明在不同平台上的可用性。编译器只有在当前目标平台和 `available` 特性中指定的平台匹配时,才会使用 `available` 特性。
你可以在某个声明上使用多个 `available` 特性,以指定该声明在不同平台和 Swift 版本上的可用性。编译器只有在 `available` 特性中指定的平台或语言版本匹配时,才会使用 `available` 特性。
如果 `available` 特性除了平台名称参数外,只指定了一个 `introduced` 参数,那么可以使用以下简写语法代替:
@available平台名称 版本号,*
@available(`平台名称` `版本号`, *)
@available(swift `版本号`)
`available` 特性的简写语法可以简明地表达出声明在多个平台上的可用性。尽管这两种形式在功能上是相同的,但请尽可能地使用简写语法形式。
```swift
@available(iOS 10.0, macOS 10.12, *)
class MyClass {
// 这里是类定义
// 这里是类定义
}
```
`discardableResult`
`available` 特性需要同时指定 Swift 版本和平台可用性,需要使用单独的 `available` 特性来声明。
该特性用于的函数或方法声明,以抑制编译器中 函数或方法的返回值被调而没有使用其结果的警告。
```swift
@available(swift 3.0.2)
@available(macOS 10.12, *)
struct MyStruct {
// 这里是结构体定义
}
```
`GKInspectable`
### `discardableResult` {#discardableresult}
该特性用于的函数或方法声明,以抑制编译器中函数或方法的返回值被调而没有使用其结果的警告。
### `dynamicCallable` {#dynamiccallable}
该特性用于类、结构体、枚举或协议,以将该类型的实例视为可调用的函数。该类型必须实现 `dynamicallyCall(withArguments:)``dynamicallyCall(withKeywordArguments:)` 方法之一,或两者同时实现。
你可以调用 `dynamicCallable` 特性的实例,就像是调用一个任意数量参数的函数。
```swift
@dynamicCallable
struct TelephoneExchange {
func dynamicallyCall(withArguments phoneNumber: [Int]) {
if phoneNumber == [4, 1, 1] {
print("Get Swift help on forums.swift.org")
} else {
print("Unrecognized number")
}
}
}
let dial = TelephoneExchange()
// 使用动态方法调用
dial(4, 1, 1)
// 打印“Get Swift help on forums.swift.org”
dial(8, 6, 7, 5, 3, 0, 9)
// 打印“Unrecognized number”
// 直接调用底层方法
dial.dynamicallyCall(withArguments: [4, 1, 1])
```
`dynamicallyCall(withArguments:)` 方法的声明必须至少有一个参数遵循 [`ExpressibleByArrayLiteral`](https://developer.apple.com/documentation/swift/expressiblebyarrayliteral) 协议,如 `[Int]`,而返回值类型可以是任意类型。
```swift
@dynamicCallable
struct Repeater {
func dynamicallyCall(withKeywordArguments pairs: KeyValuePairs<String, Int>) -> String {
>
return pairs
.map { label, count in
repeatElement(label, count: count).joined(separator: " ")
}
.joined(separator: "\n")
}
}
let repeatLabels = Repeater()
print(repeatLabels(a: 1, b: 2, c: 3, b: 2, a: 1))
// a
// b b
// c c c
// b b
// a
```
`dynamicallyCall(withKeywordArguments:)` 方法声明必须至少有一个参数遵循 [`ExpressibleByDictionaryLiteral`](https://developer.apple.com/documentation/swift/expressiblebydictionaryliteral) 协议,返回值可以任意类型。参数的 [`Key`](https://developer.apple.com/documentation/swift/expressiblebydictionaryliteral/2294108-key) 必须遵循 [`ExpressibleByStringLiteral`](https://developer.apple.com/documentation/swift/expressiblebystringliteral) 协议。上述的示例使用 [`KeyValuePairs`](https://developer.apple.com/documentation/swift/keyvaluepairs) 作为参数类型,以便调用者可以传入重复的参数标签,`a``b` 在调用 `repeat`中多次使用。
如果你同时实现两种 `dynamicallyCall` 方法,则当在方法调用中包含关键字参数时,会调用 `dynamicallyCall(withKeywordArguments:)` 方法,否则调用 `dynamicallyCall(withArguments:)` 方法。
你只能调用参数和返回值与 `dynamicallyCall` 方法实现匹配的动态调用实例。在下面示例的调用无法编译,因为其 `dynamicallyCall(withArguments:)` 实现不接受 `KeyValuePairs<String, String>` 参数。
```swift
repeatLabels(a: "four") // Error
```
### `dynamicMemberLookup` {#dynamicmemberlookup}
该特性用于类、结构体、枚举或协议,让其能在运行时查找成员。该类型必须实现 `subscript(dynamicMemberLookup:)` 下标。
在显式成员表达式中,如果没有成名指定成员,则该表达式被理解为对该类型的 `subscript(dynamicMemberLookup:)` 下标的调用,传递包含成员名称字符串的参数。下标的参数只需遵循 `ExpressibleByStringLiteral` 协议,返回值类型可以为任意类型。在大多数情况下,下标的参数是一个 `String` 值。例如:
```swift
@dynamicMemberLookup
struct DynamicStruct {
let dictionary = ["someDynamicMember": 325,
"someOtherMember": 787]
subscript(dynamicMember member: String) -> Int {
>
return dictionary[member] ?? 1054
}
}
let s = DynamicStruct()
// 使用动态成员查找
let dynamic = s.someDynamicMember
print(dynamic)
// 打印“325”
// 直接调用底层下标
let equivalent = s[dynamicMember: "someDynamicMember"]
print(dynamic == equivalent)
// 打印“true”
```
### `GKInspectable` {#gkinspectable}
应用此属性,暴露一个自定义 GameplayKit 组件属性给 SpriteKit 编辑器 UI。
`objc`
### `inlinable` {#inlinable}
该特性用于修饰任何可以在 Objective-C 中表示的声明。比如,非嵌套类、协议、非泛型枚举(仅限原始值为整型的枚举)、类和协议中的属性和方法(包括存取方法)、构造器析构器以及下标运算符。`objc` 特性告诉编译器这个声明可以在 Objective-C 代码中使用
该特性用于函数、方法、计算属性、下标、便利构造器析构器的声明,以将该声明的实现公开为模块公开接口的一部分。编译器允许在调用处把 `inlinable` 标记的符号替换为符号实现的副本
标有 `objc` 特性的类必须继承自 Objective-C 中定义的类。如果你将 `objc` 特性应用于一个类或协议,它也会隐式地应用于类或协议中兼容 Objective-C 的成员。对于标记 `objc` 特性的类,编译器会隐式地为它的子类添加 `objc` 特性。标记了 `objc` 特性的协议不能继承没有标记 `objc` 的协议
内联代码可以与任意模块中 `public` 访问级别的符号进行交互,同时可以与在相同模块中标记 `usableFromInline` 特性的 `internal` 访问级别的符号进行交互。内联代码不能与 `private``fileprivate` 级别的符号进行交互
如果你将 `objc` 特性应用于枚举,每一个枚举用例都会以枚举名称和用例名称组合的方式暴露在 Objective-C 代码中。例如,在 `Planet` 枚举中有一个名为 `Venus` 的用例,该用例暴露在 Objective-C 代码中时叫做 `PlanetVenus`
该特性不能用于嵌套在函数内的声明,也不能用于 `fileprivate``private` 访问级别的声明。在内联函数定义的函数和闭包是隐式非内联的,即使他们不能标记该特性
`objc` 特性有一个可选的参数,由标识符构成。当你想把 objc 所修饰的实体以一个不同的名字暴露给 Objective-C 时,你就可以使用这个特性参数。你可以使用这个参数来命名类、枚举类型、枚举用例、协议、方法、存取方法以及构造器。下面的例子把 `ExampleClass` 中的 `enabled` 属性的取值方法暴露给 Objective-C名字是 `isEnabled`,而不是它原来的属性名。
```swift
@objc
class ExampleClass: NSObject {
var enabled: Bool {
@objc(isEnabled) get {
// 返回适当的值 }
}
}
```
`nonobjc`
### `nonobjc` {#nonobjc}
该特性用于方法、属性、下标、或构造器的声明,这些声明本可以在 Objective-C 代码中使用,而使用 `nonobjc` 特性则告诉编译器这个声明不能在 Objective-C 代码中使用。
该特性用在扩展中,与在没有明确标记为 `objc` 特性的扩展中给每个成员添加该特性具有相同效果。
可以使用 `nonobjc` 特性解决标有 `objc` 的类中桥接方法的循环问题,该特性还允许对标有 `objc` 的类中的构造器和方法进行重载。
标有 `nonobjc` 特性的方法不能重写标有 `objc` 特性的方法。然而,标有 `objc` 特性的方法可以重写标有 `nonobjc` 特性的方法。同样,标有 `nonobjc` 特性的方法不能满足标有 `@objc` 特性的协议中的方法要求。
`NSApplicationMain`
### `NSApplicationMain` {#nsapplicationmain}
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `NSApplicationMain`(\_:_:) 函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `NSApplicationMain(_:_:)` 函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
如果你不想使用这个特性,可以提供一个 main.swift 文件,并在代码**顶层**调用 `NSApplicationMain`(\_:_:) 函数,如下所示:
如果你不想使用这个特性,可以提供一个 `main.swift` 文件,并在代码**顶层**调用 `NSApplicationMain(_:_:)` 函数,如下所示:
```swift
import AppKit
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
```
`NSCopying`
### `NSCopying` {#nscopying}
该特性用于修饰一个类的存储型变量属性。该特性将使属性的设值方法使用传入值的副本进行赋值,这个值由传入值的 `copyWithZone`(\_:) 方法返回。该属性的类型必需符合 `NSCopying` 协议。
该特性用于修饰一个类的存储型变量属性。该特性将使属性的设值方法使用传入值的副本进行赋值,这个值由传入值的 `copyWithZone(_:)` 方法返回。该属性的类型必需符合 `NSCopying` 协议。
`NSCopying` 特性的行为与 Objective-C 中的 `copy` 特性相似。
`NSManaged`
### `NSManaged` {#nsmanaged}
该特性用于修饰 `NSManagedObject` 子类中的实例方法或存储型变量属性,表明它们的实现由 `Core Data` 在运行时基于相关实体描述动态提供。对于标记了 `NSManaged` 特性的属性,`Core Data` 也会在运行时为其提供存储。应用这个特性也意味着 `objc` 特性。
`testable`
### `objc` {#objc}
该特性用于修饰任何可以在 Objective-C 中表示的声明。比如,非嵌套类、协议、非泛型枚举(仅限原始值为整型的枚举)、类和协议中的属性和方法(包括存取方法)、构造器、析构器以及下标运算符。`objc` 特性告诉编译器这个声明可以在 Objective-C 代码中使用。
该特性用在扩展中,与在没有明确标记为 `nonobjc` 特性的扩展中给每个成员添加该特性具有相同效果。
编译器隐式地将 `objc` 特性添加到 Objective-C 中定义的任何类的子类。但是,子类不能是泛型的,并且不能继承于任何泛型类。你可以将 `objc` 特性显式添加到满足这些条件的子类,来指定其 Objective-C 名称,如下所述。添加了 `objc` 的协议不能继承于没有此特性的协议。
在以下情况中隐式添加了 `objc` 特性。
- 父类有 `objc` 特性,且重写为子类的声明。
- 遵循带有 `objc` 特性协议的声明。
- 带有 `IBAction``IBOutlet``IBDesignable``IBInspectable``NSManaged``GKInspectable` 特性的声明。
如果你将 `objc` 特性应用于枚举,每一个枚举用例都会以枚举名称和用例名称组合的方式暴露在 Objective-C 代码中。例如,在 `Planet` 枚举中有一个名为 `Venus` 的用例,该用例暴露在 Objective-C 代码中时叫做 `PlanetVenus`
`objc` 特性有一个可选的参数,由标识符构成。当你想把 `objc` 所修饰的实体以一个不同的名字暴露给 Objective-C 时,你就可以使用这个特性参数。你可以使用这个参数来命名类、枚举类型、枚举用例、协议、方法、存取方法以及构造器。如果你要指定类、协议或枚举在 Objective-C 下的名称,在名称上包含三个字母的前缀,如 [Objective-C 编程](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011210) 中的 [约定](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Conventions/Conventions.html#//apple_ref/doc/uid/TP40011210-CH10-SW1)。下面的例子把 `ExampleClass` 中的 `enabled` 属性的取值方法暴露给 Objective-C名字是 `isEnabled`,而不是它原来的属性名。
```swift
class ExampleClass: NSObject {
@objc var enabled: Bool {
@objc(isEnabled) get {
// 返回适当的值
}
}
}
```
### `objcMembers` {#objcmembers}
该特性用于类声明,以将 `objc` 特性应用于该类、扩展、子类以及子类的扩展的所有 Objective-C 兼容成员。
大多数代码应该使用 `objc` 特性,以暴露所需的声明。如果需要暴露多个声明,可以将其分组到添加 `objc` 特性的扩展中。`objcMembers` 特性为大量使用 Objective-C 运行时的内省工具的库提供了便利。添加不必要的 `objc` 特性会增加二进制体积并影响性能。
### `requires_stored_property_inits` {#requires-stored-property-inits}
该特性用于类声明,以要求类中所有存储属性提供默认值作为其定义的一部分。对于从中继承的任何类都推断出 `NSManagedObject` 特性。
### `testable` {#testable}
在导入允许测试的编译模块时,该特性用于修饰 `import` 声明,这样就能访问被导入模块中的任何标有 `internal` 访问级别修饰符的实体,犹如它们被标记了 `public` 访问级别修饰符。测试也可以访问使用 `internal` 或者 `public` 访问级别修饰符标记的类和类成员,就像它们是 `open` 访问修饰符声明的。
`UIApplicationMain`
### `UIApplicationMain` {#uiapplicationmain}
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `UIApplicationMain` 函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
如果你不想使用这个特性,可以提供一个 main.swift 文件,并在代码顶层调用 `UIApplicationMain`(\_:\_:\_:) 函数。比如,如果你的应用程序使用一个继承于 UIApplication 的自定义子类作为主要类,你可以调用 `UIApplicationMain`(\_:\_:\_:) 函数而不是使用该特性。
如果你不想使用这个特性,可以提供一个 `main.swift` 文件,并在代码顶层调用 `UIApplicationMain(_:_:_:_:)` 函数。比如,如果你的应用程序使用一个继承于 UIApplication 的自定义子类作为主要类,你可以调用 `UIApplicationMain(_:_:_:_:)` 函数而不是使用该特性。
<a name="declaration_attributes_used_by_interface_builder"></a>
###Interface Builder 使用的声明特性
### `usableFromInline` {#usablefrominline}
该特性用于函数、方法、计算属性、下标、构造器或析构器的声明,以在同一模块中允许该符号用于内联代码的声明。声明必须具有 `internal` 访问级别修饰符。
`public` 访问修饰符相同的是,该特性将声明公开为模块公共接口的一部分。区别于 `public`,编译器不允许在模块外部的代码通过名称引用 `usableFromInline` 标记的声明,即使导出了声明符号也是无法引用。但是,模块外的代码仍然可以通过运行时交换声明符号。
标记为 `inlinable` 特性的声明,在内联代码中可以隐式使用。虽然 `inlinable``usableFromInline` 可以用于 `internal` 声明,但这两者不能同时使用。
### `warn_unqualified_access` {#warn-unqualified-access}
该特性应用于顶级函数、实例方法、类方法或静态方法,以在没有前置限定符(例如模块名称、类型名称、实例变量或常量)的情况下使用该函数或方法时触发警告。使用该特性可以帮助减少在同一作用于访问同名函数之间的歧义。
例如Swift 标准库包含 [`min(_:_:)`](https://developer.apple.com/documentation/swift/1538339-min/) 顶级函数和用于序列比较元素的 [`min()`](https://developer.apple.com/documentation/swift/sequence/1641174-min) 方法。序列方法声明使用了 `warn_unqualified_access`,以减少在 `Sequence` 扩展中使用它们的歧义。
### Interface Builder 使用的声明特性 {#declaration-attributes-used-by-interface-builder}
`Interface Builder` 特性是 `Interface Builder` 用来与 Xcode 同步的声明特性。`Swift` 提供了以下的 `Interface Builder` 特性:`IBAction``IBOutlet``IBDesignable`,以及 `IBInspectable` 。这些特性与 Objective-C 中对应的特性在概念上是相同的。
`IBOutlet``IBInspectable` 用于修饰一个类的属性声明,`IBAction` 特性用于修饰一个类的方法声明,`IBDesignable` 用于修饰类的声明。
`IBAction``IBOutlet` 特性都意味着 `objc` 特性。
应用 `IBAction``IBOutlet``IBDesignable` 或者 `IBInspectable` 特性都意味着同时应用 `objc` 特性。
<a name="type_attributes"></a>
##类型特性
## 类型特性 {#type-attributes}
类型特性只能用于修饰类型。
`autoclosure`
### `autoclosure` {#autoclosure}
这个特性通过把表达式自动封装成无参数的闭包来延迟表达式的计算。它可以修饰类型为返回表达式结果类型的无参数函数类型的函数参数。关于如何使用 autoclosure 特性的例子,请参阅 [自动闭包](http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html/) 和 [函数类型](http://wiki.jikexueyuan.com/project/swift/chapter3/03_Types.html)。
这个特性通过把表达式自动封装成无参数的闭包来延迟表达式的计算。它可以修饰类型为返回表达式结果类型的无参数函数类型的函数参数。关于如何使用 autoclosure 特性的例子,请参阅 [自动闭包](../chapter2/07_Closures.md#autoclosures) 和 [函数类型](./03_Types.md#function_type)。
### `convention` {#convention}
`convention`
该特性用于修饰函数类型,它指出了函数调用的约定。
convention 特性总是与下面的参数之一一起出现。
@ -215,19 +343,57 @@ convention 特性总是与下面的参数之一一起出现。
使用 C 函数调用约定的函数也可用作使用 Objective-C 块调用约定的函数,同时使用 Objective-C 块调用约定的函数也可用作使用 Swift 函数调用约定的函数。然而,只有非泛型的全局函数、局部函数以及未捕获任何局部变量的闭包,才可以被用作使用 C 函数调用约定的函数。
`escaping`
在函数或者方法声明上使用该特性,它表示参数将不会被存储以供延迟执行,这将确保参数不会超出函数调用的生命周期。在使用 `escaping` 声明特性的函数类型中访问属性和方法时不需要显式地使用 `self.`。关于如何使用 `escaping` 特性的例子,请参阅 [逃逸闭包](http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html)。
### `escaping` {#escaping}
在函数或者方法声明上使用该特性,它表示参数将不会被存储以供延迟执行,这将确保参数不会超出函数调用的生命周期。在使用 `escaping` 声明特性的函数类型中访问属性和方法时不需要显式地使用 `self.`。关于如何使用 `escaping` 特性的例子,请参阅 [逃逸闭包](../chapter2/07_Closures.md#escaping_closures)。
## Switch Case 特性 {#switch-case-attributes}
你只能在 switch cases 中使用 switch case 特性。
### `unknown` {#unknown}
次特性用于 switch case表示在编译时该地方不会匹配枚举的任何情况。有关如何使用 `unknown` 特性的示例,可参阅 [对未来枚举的 `case` 进行 `switch`](./05_Statements.md#future-case)。
> 特性语法
>
> *特性 *→ @ <font color = 0x3386c8>特性名 特性参数子句</font><sub>可选</sub>
> *特性名* → <font color = 0x3386c8>标识符
> *特性参数子句* → ( <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub> )
> *特性列表* → <font color = 0x3386c8>特性 特性列表</font><sub>可选</sub>
>
> *均衡令牌列表* → <font color = 0x3386c8>均衡令牌 均衡令牌列表</font><sub>可选</sub>
> *均衡令牌* → ( <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub> )
> *均衡令牌* → [ <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub> ]
> *均衡令牌* → { <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub>}
>
#### attribute {#attribute}
>
> *特性*→ [特性名](#attribute_name) [特性参数子句](#atribute_argument_clause)<sub>可选</sub>
>
>
#### attribute_name {#attribute-name}
>
> *特性名* → [标识符](./02_Lexical_Structure.md#identifier)
>
>
#### atribute_argument_clause {#atribute-argument-clause}
>
> *特性参数子句* → **(** [均衡令牌列表](#balanced_tokens)<sub>可选</sub> **)**
>
>
#### attributes {#attributes}
>
> *特性列表* → [特性](#attribute) [特性列表](#attributes)<sub>可选</sub>
>
>
>
#### balanced_tokens {#balanced-tokens}
>
> *均衡令牌列表* → [均衡令牌](#balanced_token) [均衡令牌列表](#balanced_tokens)<sub>可选</sub>
>
>
#### balanced_token {#balanced-token}
>
> *均衡令牌* → **(** [均衡令牌列表](#balanced_tokens)<sub>可选</sub> **)**
>
> *均衡令牌* → **\[** [均衡令牌列表](#balanced_tokens)<sub>可选</sub> **\]**
>
> *均衡令牌* → **{** [均衡令牌列表](#balanced_tokens)<sub>可选</sub> **}**
>
> *均衡令牌* → 任意标识符,关键字,字面量或运算符
> *均衡令牌* → 任意标点除了 ()[]{,或 }
>
> *均衡令牌* → 任意标点除了 **(****)****[****]****{**,或 **}**
>

View File

@ -1,29 +1,4 @@
# 模式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),
> 2.1
> 翻译:[BridgeQ](https://github.com/WXGBridgeQ)
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页内容包括:
- [通配符模式Wildcard Pattern](#wildcard_pattern)
- [标识符模式Identifier Pattern](#identifier_pattern)
- [值绑定模式Value-Binding Pattern](#value-binding_pattern)
- [元组模式Tuple Pattern](#tuple_pattern)
- [枚举用例模式Enumeration Case Pattern](#enumeration_case_pattern)
- [可选模式Optional Pattern](#optional_pattern)
- [类型转换模式Type-Casting Pattern](#type-casting_patterns)
- [表达式模式Expression Pattern](#expression_pattern)
*模式*代表单个值或者复合值的结构。例如,元组 `(1, 2)` 的结构是由逗号分隔的,包含两个元素的列表。因为模式代表一种值的结构,而不是特定的某个值,你可以利用模式来匹配各种各样的值。比如,`(x, y)` 可以匹配元组 `(1, 2)`,以及任何含两个元素的元组。除了利用模式匹配一个值以外,你可以从复合值中提取出部分或全部值,然后分别把各个部分的值和一个常量或变量绑定起来。
@ -35,18 +10,26 @@ Swift 中的模式分为两类:一种能成功匹配任何类型的值,另
> 模式语法
>
<a name="pattern"></a>
> *模式* → [*通配符模式*](#wildcard_pattern) [*类型标注*](03_Types.html#type-annotation)<sub>可选</sub>
> *模式* → [*标识符模式*](#identifier_pattern) [*类型标注*](03_Types.html#type-annotation)<sub>可选</sub>
> *模式* → [*值绑定模式*](#value-binding-pattern)
> *模式* → [*元组模式*](#tuple-pattern) [*类型标注*](03_Types.html#type-annotation)<sub>可选</sub>
> *模式* → [*枚举用例模式*](#enum-case-pattern)
> *模式* → [*可选模式*](#optional-pattern)
> *模式* → [*类型转换模式*](#type-casting-pattern)
> *模式* → [*表达式模式*](#expression-pattern)
<a name="wildcard_pattern"></a>
## 通配符模式Wildcard Pattern
#### pattern {#pattern}
> *模式* → [*通配符模式*](#wildcard_pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
>
> *模式* → [*标识符模式*](#identifier_pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
>
> *模式* → [*值绑定模式*](#value-binding-pattern)
>
> *模式* → [*元组模式*](#tuple-pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
>
> *模式* → [*枚举用例模式*](#enum-case-pattern)
>
> *模式* → [*可选模式*](#optional-pattern)
>
> *模式* → [*类型转换模式*](#type-casting-pattern)
>
> *模式* → [*表达式模式*](#expression-pattern)
>
## 通配符模式Wildcard Pattern {#wildcard-pattern}
*通配符模式*由一个下划线(`_`)构成,用于匹配并忽略任何值。当你想忽略被匹配的值时可以使用该模式。例如,下面这段代码在闭区间 `1...3` 中迭代,每次迭代都忽略该区间的当前值:
@ -58,12 +41,12 @@ for _ in 1...3 {
> 通配符模式语法
>
<a name="wildcard-pattern"></a>
#### wildcard-pattern {#wildcard-pattern}
> *通配符模式* → **_**
>
<a name="identifier_pattern"></a>
## 标识符模式Identifier Pattern
## 标识符模式Identifier Pattern {#identifier-pattern}
*标识符模式*匹配任何值,并将匹配的值和一个变量或常量绑定起来。例如,在下面的常量声明中,`someValue` 是一个标识符模式,匹配了 `Int` 类型的 `42`
```swift
@ -76,12 +59,12 @@ let someValue = 42
> 标识符模式语法
>
<a name="identifier-pattern"></a>
> *标识符模式* → [*标识符*](02_Lexical_Structure.html#identifier)
<a name="value-binding_pattern"></a>
## 值绑定模式Value-Binding Pattern
#### identifier-pattern {#identifier-pattern}
> *标识符模式* → [*标识符*](./02_Lexical_Structure.md#identifier)
>
## 值绑定模式Value-Binding Pattern {#value-binding-pattern}
*值绑定模式*把匹配到的值绑定给一个变量或常量。把匹配到的值绑定给常量时,用关键字 `let`,绑定给变量时,用关键字 `var`
在值绑定模式中的标识符模式会把新命名的变量或常量与匹配到的值做绑定。例如,你可以拆开一个元组,然后把每个元素绑定到相应的标识符模式中。
@ -93,19 +76,19 @@ switch point {
case let (x, y):
print("The point is at (\(x), \(y)).")
}
// 打印 “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):` 的匹配效果是一样的。
> 值绑定模式语法
>
<a name="value-binding-pattern"></a>
#### value-binding-pattern {#value-binding-pattern}
> *值绑定模式* → **var** [*模式*](#pattern) | **let** [*模式*](#pattern)
>
<a name="tuple_pattern"></a>
## 元组模式
## 元组模式 {#tuple-pattern}
*元组模式*是由逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。
你可以使用类型标注去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 `let (x, y): (Int, Int) = (1, 2)` 中的元组模式 `(x, y): (Int, Int)` 只匹配两个元素都是 `Int` 类型的元组。
@ -130,28 +113,32 @@ let (a): Int = 2 // a: Int = 2
> 元组模式语法
>
<a name="tuple-pattern"></a>
#### tuple-pattern {#tuple-pattern}
> *元组模式* → **(** [*元组模式元素列表*](#tuple-pattern-element-list)<sub>可选</sub> **)**
<a name="tuple-pattern-element-list"></a>
>
#### tuple-pattern-element-list {#tuple-pattern-element-list}
> *元组模式元素列表* → [*元组模式元素*](#tuple-pattern-element) | [*元组模式元素*](#tuple-pattern-element) **,** [*元组模式元素列表*](#tuple-pattern-element-list)
<a name="tuple-pattern-element"></a>
>
#### tuple-pattern-element {#tuple-pattern-element}
> *元组模式元素* → [*模式*](#pattern)
>
<a name="enumeration_case_pattern"></a>
## 枚举用例模式Enumeration Case Pattern
## 枚举用例模式Enumeration Case Pattern {#enumeration-case-pattern}
*枚举用例模式*匹配现有的某个枚举类型的某个用例。枚举用例模式出现在 `switch` 语句中的 `case` 标签中,以及 `if``while``guard``for-in` 语句的 `case` 条件中。
如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用 `switch` 语句来匹配包含关联值的枚举用例的例子,请参阅 [关联值](../chapter2/08_Enumerations.html#associated_values)。
如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用 `switch` 语句来匹配包含关联值的枚举用例的例子,请参阅 [关联值](../chapter2/08_Enumerations.md#associated_values)。
> 枚举用例模式语法
>
<a name="enum-case-pattern"></a>
> *枚举用例模式* → [*类型标识*](03_Types.html#type-identifier)<sub>可选</sub> **.** [*枚举用例名*](05_Declarations.html#enum-case-name) [*元组模式*](#tuple-pattern)<sub>可选</sub>
<a name="optional_pattern"></a>
## 可选模式Optional Pattern
#### enum-case-pattern {#enum-case-pattern}
> *枚举用例模式* → [*类型标识*](./03_Types.md#type-identifier)<sub>可选</sub> **.** [*枚举用例名*](./06_Declarations.md#enum-case-name) [*元组模式*](#tuple-pattern)<sub>可选</sub>
>
## 可选模式Optional Pattern {#optional-pattern}
*可选模式*匹配包装在一个 `Optional(Wrapped)` 或者 `ExplicitlyUnwrappedOptional(Wrapped)` 枚举中的 `Some(Wrapped)` 用例中的值。可选模式由一个标识符模式和紧随其后的一个问号组成,可以像枚举用例模式一样使用。
由于可选模式是 `Optional``ImplicitlyUnwrappedOptional` 枚举用例模式的语法糖,下面两种写法是等效的:
@ -184,35 +171,41 @@ for case let number? in arrayOfOptinalInts {
> 可选模式语法
>
<a name="optional-pattern"></a>
> *可选模式* → [*标识符模式*](03_Types.html#type-identifier) **?**
<a name="type-casting_patterns"></a>
## 类型转换模式Type-Casting Patterns
#### optional-pattern {#optional-pattern}
> *可选模式* → [*标识符模式*](./03_Types.md#type-identifier) **?**
>
## 类型转换模式Type-Casting Patterns {#type-casting-patterns}
有两种类型转换模式,`is` 模式和 `as` 模式。`is` 模式只出现在 `switch` 语句中的 `case` 标签中。`is` 模式和 `as` 模式形式如下:
> is `类型`
>
> `模式` as `类型`
>
`is` 模式仅当一个值的类型在运行时和 `is` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。`is` 模式和 `is` 运算符有相似表现,它们都进行类型转换,但是 `is` 模式没有返回类型。
`as` 模式仅当一个值的类型在运行时和 `as` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 `as` 模式右边指定的类型。
关于使用 `switch` 语句配合 `is` 模式和 `as` 模式来匹配值的例子,请参阅 [Any 和 AnyObject 的类型转换](../chapter2/18_Type_Casting.html#type_casting_for_any_and_anyobject)。
关于使用 `switch` 语句配合 `is` 模式和 `as` 模式来匹配值的例子,请参阅 [Any 和 AnyObject 的类型转换](../chapter2/18_Type_Casting.md#type_casting_for_any_and_anyobject)。
> 类型转换模式语法
>
<a name="type-casting-pattern"></a>
#### type-casting-pattern {#type-casting-pattern}
> *类型转换模式* → [*is 模式*](#is-pattern) | [*as 模式*](#as-pattern)
<a name="is-pattern"></a>
> *is 模式* → **is** [*类型*](03_Types.html#type)
<a name="as-pattern"></a>
> *as 模式* → [*模式*](#pattern) **as** [*类型*](03_Types.html#type)
>
<a name="expression_pattern"></a>
## 表达式模式Expression Pattern
#### is-pattern {#is-pattern}
> *is 模式* → **is** [*类型*](./03_Types.md#type)
>
#### as-pattern {#as-pattern}
> *as 模式* → [*模式*](#pattern) **as** [*类型*](03_Types.md#type)
>
## 表达式模式Expression Pattern {#expression-pattern}
*表达式模式*代表表达式的值。表达式模式只出现在 `switch` 语句中的 `case` 标签中。
表达式模式代表的表达式会使用 Swift 标准库中的 `~=` 运算符与输入表达式的值进行比较。如果 `~=` 运算符返回 `true`,则匹配成功。默认情况下,`~=` 运算符使用 `==` 运算符来比较两个相同类型的值。它也可以将一个整型数值与一个 `Range` 实例中的一段整数区间做匹配,正如下面这个例子所示:
@ -227,7 +220,7 @@ case (-2...2, -2...2):
default:
print("The point is at (\(point.0), \(point.1)).")
}
// 打印 “(1, 2) is near the origin.”
// 打印“(1, 2) is near the origin.”
```
你可以重载 `~=` 运算符来提供自定义的表达式匹配行为。比如你可以重写上面的例子,将 `point` 表达式与字符串形式表示的点进行比较。
@ -235,6 +228,7 @@ default:
```swift
// 重载 ~= 运算符对字符串和整数进行比较
func ~=(pattern: String, value: Int) -> Bool {
>
return pattern == "\(value)"
}
@ -244,10 +238,12 @@ case ("0", "0"):
default:
print("The point is at (\(point.0), \(point.1)).")
}
// 打印 “The point is at (1, 2).”
// 打印“The point is at (1, 2).”
```
> 表达式模式语法
>
<a name="expression-pattern"></a>
> *表达式模式* → [*表达式*](04_Expressions.html#expression)
#### expression-pattern {#expression-pattern}
> *表达式模式* → [*表达式*](./04_Expressions.md#expression)
>

View File

@ -1,39 +1,19 @@
# 泛型参数Generic Parameters and Arguments
---------
> 1.0
> 翻译:[fd5788](https://github.com/fd5788)
> 校对:[yankuangshi](https://github.com/yankuangshi), [stanzhai](https://github.com/stanzhai)
> 2.0
> 翻译+校对:[wardenNScaiyi](https:github.com/wardenNScaiyi)
> 3.0
> 翻译+校对:[chenmingjia](https:github.com/chenmingjia)
> 4.1
> 翻译+校对:[mylittleswift](https://github.com/mylittleswift)
本页包含内容:
- [泛型形参子句](#generic_parameter)
- [Where 子句](#where_clauses)
- [泛型实参子句](#generic_argument)
本节涉及泛型类型、泛型函数以及泛型构造器的参数,包括形参和实参。声明泛型类型、函数或构造器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型构造器时,就用具体的类型实参替代之。
关于 Swift 语言的泛型概述,请参阅 [泛型](../chapter2/22_Generics.html)。
<a name="generic_parameter"></a>
## 泛型形参子句
关于 Swift 语言的泛型概述,请参阅 [泛型](../chapter2/22_Generics.md)。
## 泛型形参子句 {#generic-parameter}
*泛型形参子句*指定泛型类型或函数的类型形参,以及这些参数相关的约束和要求。泛型形参子句用尖括号(`<>`)包住,形式如下:
> <`泛型形参列表`>
>
泛型形参列表中泛型形参用逗号分开,其中每一个采用以下形式:
> `类型形参` : `约束`
>
泛型形参由两部分组成:类型形参及其后的可选约束。类型形参只是占位符类型(如 `T``U``V``Key``Value` 等)的名字而已。你可以在泛型类型、函数的其余部分或者构造器声明,包括函数或构造器的签名中使用它(以及它的关联类型)。
@ -41,6 +21,7 @@
```swift
func simpleMax<T: Comparable>(_ x: T, _ y: T) -> T {
>
if x < y {
return y
}
@ -55,53 +36,72 @@ simpleMax(17, 42) // T 被推断为 Int 类型
simpleMax(3.14159, 2.71828) // T 被推断为 Double 类型
```
<a name="where_clauses"></a>
### Where 子句
### Where 子句 {#where-clauses}
要想对类型形参及其关联类型指定额外要求,可以在函数体或者类型的大括号之前添加 `where` 子句。`where` 子句由关键字 `where` 及其后的用逗号分隔的一个或多个要求组成。
> `where` : `类型要求`
>
`where` 子句中的要求用于指明该类型形参继承自某个类或符合某个协议或协议组合。尽管 `where` 子句提供了语法糖使其有助于表达类型形参上的简单约束(如 `<T: Comparable>` 等同于 `<T> where T: Comparable`,等等),但是依然可以用来对类型形参及其关联类型提供更复杂的约束,例如你可以强制形参的关联类型遵守协议,如,`<S: Sequence> where S.Iterator.Element: Equatable` 表示泛型类型 `S` 遵守 `Sequence` 协议并且关联类型 `S.Iterator.Element` 遵守 `Equatable` 协议,这个约束确保队列的每一个元素都是符合 `Equatable` 协议的。
>
也可以用操作符 `==` 来指定两个类型必须相同。例如,泛型形参子句 `<S1: Sequence, S2: Sequence> 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/22_Generics.html#where_clauses)
更多关于泛型 where 从句的信息和关于泛型函数声明的例子,可以看一看 [泛型 where 子句](../chapter2/22_Generics.md#where_clauses)
> 泛型形参子句语法
>
<a name="generic-parameter-clause"></a>
#### generic-parameter-clause {#generic-parameter-clause}
> *泛型形参子句* → **<** [*泛型形参列表*](#generic-parameter-list) [*约束子句*](#requirement-clause)<sub>可选</sub> **>**
<a name="generic-parameter-list"></a>
>
#### generic-parameter-list {#generic-parameter-list}
> *泛型形参列表* → [*泛形形参*](#generic-parameter) | [*泛形形参*](#generic-parameter) **,** [*泛型形参列表*](#generic-parameter-list)
<a name="generic-parameter"></a>
> *泛形形参* → [*类型名称*](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)
>
<a name="requirement-clause"></a>
#### generic-parameter {#generic-parameter}
> *泛形形参* → [*类型名称*](./03_Types.md#type-name)
>
> *泛形形参* → [*类型名称*](./03_Types.md#type-name) **:** [*类型标识符*](./03_Types.md#type-identifier)
>
> *泛形形参* → [*类型名称*](./03_Types.md#type-name) **:** [*协议合成类型*](./03_Types.md#protocol-composition-type)
>
>
#### requirement-clause {#requirement-clause}
>
> *约束子句* → **where** [*约束列表*](#requirement-list)
<a name="requirement-list"></a>
>
#### requirement-list {#requirement-list}
> *约束列表* → [*约束*](#requirement) | [*约束*](#requirement) **,** [*约束列表*](#requirement-list)
<a name="requirement"></a>
>
#### requirement {#requirement}
> *约束* → [*一致性约束*](#conformance-requirement) | [*同类型约束*](#same-type-requirement)
>
>
#### conformance-requirement {#conformance-requirement}
>
> *一致性约束* → [*类型标识符*](./03_Types.md#type-identifier) **:** [*类型标识符*](./03_Types.md#type-identifier)
>
> *一致性约束* → [*类型标识符*](./03_Types.md#type-identifier) **:** [*协议合成类型*](./03_Types.md#protocol-composition-type)
>
<a name="conformance-requirement"></a>
> *一致性约束* → [*类型标识符*](03_Types.html#type-identifier) **:** [*类型标识符*](03_Types.html#type-identifier)
> *一致性约束* → [*类型标识符*](03_Types.html#type-identifier) **:** [*协议合成类型*](03_Types.html#protocol-composition-type)
<a name="same-type-requirement"></a>
> *同类型约束* → [*类型标识符*](03_Types.html#type-identifier) **==** [*类型*](03_Types.html#type)
<a name="generic_argument"></a>
## 泛型实参子句
#### same-type-requirement {#same-type-requirement}
> *同类型约束* → [*类型标识符*](./03_Types.md#type-identifier) **==** [*类型*](./03_Types.md#type)
>
## 泛型实参子句 {#generic-argument}
*泛型实参子句*指定泛型类型的类型实参。泛型实参子句用尖括号(`<>`)包住,形式如下:
> <`泛型实参列表`>
>
泛型实参列表中类型实参用逗号分开。类型实参是实际具体类型的名字用来替代泛型类型的泛型形参子句中的相应的类型形参。从而得到泛型类型的一个特化版本。例如Swift 标准库中的泛型字典类型的的简化定义如下:
@ -117,15 +117,22 @@ struct Dictionary<Key: Hashable, Value>: CollectionType, DictionaryLiteralConver
```swift
let arrayOfArrays: Array<Array<Int>> = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>
```
如 [泛型形参子句](#generic_parameter) 所述,不能用泛型实参子句来指定泛型函数或构造器的类型实参。
> 泛型实参子句语法
>
<a name="generic-argument-clause"></a>
#### generic-argument-clause {#generic-argument-clause}
> *泛型实参子句* → **<** [*泛型实参列表*](#generic-argument-list) **>**
<a name="generic-argument-list"></a>
>
#### generic-argument-list {#generic-argument-list}
> *泛型实参列表* → [*泛型实参*](#generic-argument) | [*泛型实参*](#generic-argument) **,** [*泛型实参列表*](#generic-argument-list)
<a name="generic-argument"></a>
> *泛型实参* → [*类型*](03_Types.html#type)
>
#### generic-argument {#generic-argument}
> *泛型实参* → [*类型*](./03_Types.md#type)
>

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,7 @@
小伙伴们Swift 中的 Bool 类型有着非常重要的语法功能,并支撑起了整个 Swift 体系中的逻辑判断体系,经过老码的研究和学习, Bool 类型本身其实是对基础 Boolean 类型封装,小伙伴们可能咬着手指头问老码,怎么一会 Bool 类型,一会 Boolean 类型,其区别在于,前者是基于枚举的组合类型,而后者则是基本类型,只有两种 true 和 false。
<a name="prefix_expressions"></a>
####自定义原型
####自定义原型 {#prefix-expressions}
接下老码根据 Bool 的思想来创建一个 OCBool 类型,来让小伙伴们了解一下 Swift 中到底是怎么玩儿的。
来我们先看一下 OCBool 的定义。
@ -35,8 +34,7 @@ case ocFalse
- 代码中第2行和第3行可以合并到一行写如苹果官方 Blog 所写的一样
- 代码中命名需要注意OCBool 是类型名,所以首字母必须大写,而 case 中的 ocTrue 和 ocFalse 是小类型则需要首字母小写。
<a name="imp-default"></a>
####实现默认值
####实现默认值 {#imp-default}
我们给了一个漂亮的定义不过按照传统语言的经验Bool 值默认情况下是假, 所以我们的 OCBool 也应该如此,我们使用类型扩展技术增加这个默认特性:
@ -61,8 +59,7 @@ var result:OCBool = OCBool()
var result1:OCBool = .ocTrue
```
<a name="init-by-bool"></a>
####支持基本布尔型初始化
####支持基本布尔型初始化 {#init-by-bool}
正如上述代码所述,我们只能通过类型或者枚举项目赋值,这是组合类型的用法,但是编码的日子里,我们总是希望和 truefalse 直接打交道,也就是说,我们希望这么做,
代码示例如下:
@ -113,8 +110,7 @@ protocol BooleanLiteralConvertible {
- 这个定义中有个类方法 convertFromBooleanLiteral它的参数为 BooleanLiteralType 类型,也就是我传入的 Bool 类型, 且返回值为实现这个协议的类型本身,在我们的 OCBool 类型中,其返回值就是 OCBool 本身。经过这个定义,我们可以直接对 OCBool 类型直接进行布尔字面量初始化了。
<a name="condition-by-bool"></a>
####支持 Bool 类型判断
####支持 Bool 类型判断 {#condition-by-bool}
小伙伴们不安分, 肯定想着我怎么用它实现逻辑判断,所以如果你这么写,
@ -188,9 +184,7 @@ Program ended with exit code: 0
- 如果小伙伴们现在用的是 Beta 版的 Xcode注意苹果官方 Blog 中在代码第17行如果在 Xcode Beta4下是错误的这里的协议是LogicValue 而不是 BooleanVue所以记得看错误提示才是好习惯。
- 注意代码第34行完美支持 if 判断,且输出结果为“老码请你吃火锅”,老码也是说说而已,请不要当真。
<a name="support-all-type"></a>
####支持兼容各们各派的类型
####支持兼容各们各派的类型 {#support-all-type}
小伙伴们,江湖风险,门派众多,老码有自己的 OCBool 类型,可能嵩山少林有自己的 SSBool 类型,甚至连郭美美都可能有自己的 MMBool 类型,所以 OCBool 必须能够识别这些类型,这些各门各派的类型,只要支持 LogicValue 协议,就应该可以被识别,看老码怎么做,
@ -232,8 +226,7 @@ Program ended with exit code: 0
- 代码中第2行“_”下横杠的用法这是一个功能强大的小强在此的目的是屏蔽外部参数名所以小伙伴们可以直接var ocResult:OCBool = OCBool(mmResult)而不是var ocResult:OCBool = OCBool(v: mmResult),小伙伴们惊呆了!这个 init 函数中本来就没有外部参数名啊还记得老码在书里说过没Swift 的初始化函数会默认使用内部参数名,作为外部参数名。
<a name="make-up-type"></a>
####完善 OCBool 的布尔基因体系:
####完善 OCBool 的布尔基因体系: {#make-up-type}
小伙伴们bool 类型的价值就是在于各种判断,诸如==!=, &|,^,!,以及各种组合逻辑运算,我们 OCBool 也要具备这些功能,否则就会基因缺陷,且看老码如何实现:

View File

@ -17,8 +17,7 @@
在这篇博文里面,我们会介绍两种类型各自的优点,以及应该怎么选择使用。
<a name="difference-two"></a>
#### 值类型与引用类型的区别
#### 值类型与引用类型的区别 {#difference-two}
值类型和引用类型最基本的分别在复制之后的结果。当一个值类型被复制的时候,相当于创造了一个完全独立的实例,这个实例保有属于自己的独有数据,数据不会受到其他实例的数据变化影响:
@ -44,8 +43,7 @@
println("\(x.data), \(y.data)") // 输出结果 "42, 42"
```
<a name="act-in=mutation"></a>
#### Mutation修改在安全中扮演的角色
#### Mutation修改在安全中扮演的角色 {#act-in=mutation}
值类型较引用类型来说,会让你更容易在大量代码中理清状况。如果你总是得到一个独立的拷贝出来的实例,你就可以放心它不会被你 app 里面的其他部分代码默默地修改。这在多线程的环境里面是尤为重要的,因为另外一个线程可能会在暗地里修改你的数据。因此可能会造成严重的程序错误,这在调试过程中非常难以排除。
@ -53,8 +51,7 @@
你可能在想,有的时候我可能也需要一个完全不变的类。这样使用 `Cocoa NSObject` 对象的时候会比较容易,又可以保留值语义的好处。在今天,你可以通过只使用不可变的存储属性,和避开任何可以修改状态的 API用 Swift 写出一个不可变类 `immutable class`。实际上,很多基本的 Cocoa 类,例如 `NSURL`都是设计成不可变类的。然而Swift 语言目前只强制 `struct``enum` 这种值类型的不可变性,对类这种引用类型则没有。(例如还不支持强制将子类的限制为不可变类)
<a name="how-to-choose"></a>
#### 如何选择类型?
#### 如何选择类型? {#how-to-choose}
所以当我们想要建立一个新的类型的时候,怎么决定用值类型还是引用类型呢?当你使用 Cocoa 框架的时候,很多 API 都要通过 NSObject 的子类使用,所以这时候必须要用到引用类型 class。在其他情况下有下面几个准则

View File

@ -13,10 +13,10 @@
可选类型是 Swift 中新引入的,功能很强大。在这篇博文里讨论的,是在 Swift 里,如何通过可选类型来保证强类型的安全性。作为例子,我们来创建一个 Objective-C API 的 Swift 版本,但实际上 Swift 本身并不需要这样的 API。
<a name="#add-function"></a>
#### 为 Dictionary 增加 objectsForKeys 函数
#### 为 Dictionary 增加 objectsForKeys 函数 {#add-function}
在 Objective-C 中,`NSDictionary` 有一个方法 `-objectsForKeys:NoFoundMarker:`, 这个方法需要一个 `NSArray` 数组作为键值参数,然后返回一个包含相关值的数组。文档里写到:“返回数组中的第 N 个值,和输入数组中的第 N 个值相对应”,那如果有某个键值在字典里不存在呢?于是就有了 `notFoundMarker` 作为返回提示。比如第三个键值没有找到,那么在返回数组中第三个值就是这个 `notFoundMarker`,而不是字典中的第三个值,但是这个值只是用来提醒原字典中没有找到对应值,但在返回数组中该元素存在,且用 `notFoundMarker` 作为占位符,因为这个对象不能直接使用,所以在 Foundation 框架中有个专门的类处理这个情况:`NSNull`
在 Objective-C 中,`NSDictionary` 有一个方法 `-objectsForKeys:NoFoundMarker:`, 这个方法需要一个 `NSArray` 数组作为键值参数,然后返回一个包含相关值的数组。文档里写到:“返回数组中的第 N 个值,
和输入数组中的第 N 个值相对应”,那如果有某个键值在字典里不存在呢?于是就有了 `notFoundMarker` 作为返回提示。比如第三个键值没有找到,那么在返回数组中第三个值就是这个 `notFoundMarker`,而不是字典中的第三个值,但是这个值只是用来提醒原字典中没有找到对应值,但在返回数组中该元素存在,且用 `notFoundMarker` 作为占位符,因为这个对象不能直接使用,所以在 Foundation 框架中有个专门的类处理这个情况:`NSNull`
在 Swift 中,`Dictionary` 类没有类似 `objectsForKeys` 的函数,为了说明问题,我们动手加一个,并且使其成为操作字典值的通用方法。我们可以用 `extension` 来实现:
@ -43,8 +43,7 @@ extension Dictionary{
}
```
<a name="#easy-function"></a>
#### Swift 中更简便的方法
#### Swift 中更简便的方法 {#easy-function}
小伙伴们可能会问,为什么 Swift 中不需要实现这么一个 API 呢?其实其有更简单的实现,如下面代码所示:
@ -73,8 +72,7 @@ t = dic.valuesForKeys([])
//结果为:[]
```
<a name="#nested-optional"></a>
#### 内嵌可选类型
#### 内嵌可选类型 {#nested-optional}
现在,如果我们为每一个结果调用 `last` 方法,看下结果如何?
@ -114,8 +112,7 @@ var last:T? { get }
不管是 Swift 版本还是 Objective-C 版本,返回值为 `nil` 都意味数组是空的,所以它就没有最后一个元素。 但是如果返回是 `Optional(nil)` 或者 Objective-C 中的 `NSNull` 都表示数组中的最后一个元素存在,但是元素的内容是空的。在 Objective-C 中只能借助 `NSNull` 作为占位符来达到这个目的,但是 Swift 却可以语言系统类型的角度的实现。
<a name="#provide-default"></a>
#### 提供一个默认值
#### 提供一个默认值 {#provide-default}
进一步封装,如果我字典中的某个或某些元素不存在,我们想提供一个默认值怎么办呢?实现方法很简单:

146
source/contributors.md Executable file
View File

@ -0,0 +1,146 @@
# 文档翻译 & 校对工作记录
Swift 官方文档中文翻译由 [numbbbbb](https://github.com/numbbbbb) 发起并主导,本项目已经得到了苹果官方的 [认可](https://swift.org/documentation/)Translations 部分)。下面是各个版本官方文档翻译和校对工作的主要贡献者,排名不分先后。
## Swift 5.x 主要贡献者
- [Adolf-L](https://github.com/Adolf-L)
- [BigNerdCoding](https://github.com/bignerdcoding)
- [bqlin](https://github.com/bqlin)
- [Byelaney](https://github.com/Byelaney)
- [CMB](https://github.com/chenmingbiao)
- [DarrenChen123](https://github.com/DarrenChen123)
- [dzyding](https://github.com/dzyding)
- [Hale](https://github.com/wuqiuhao)
- [jojotov](https://github.com/jojotov)
- [Khala-wan](https://github.com/Khala-wan)
- [Nemocdz](https://github.com/Nemocdz)
- [numbbbbb](https://github.com/numbbbbb)
- [WAMaker](https://github.com/WAMaker)
- [Yousanflics](https://github.com/Yousanflics)
## Swift 4.x 主要贡献者
- [Adolf-L](https://github.com/Adolf-L)
- [BigNerdCoding](https://github.com/bignerdcoding)
- [bqlin](https://github.com/bqlin)
- [Cee](https://github.com/Cee)
- [CMB](https://github.com/chenmingbiao)
- [Damonwong](https://github.com/Damonvvong)
- [Desgard](https://github.com/Desgard)
- [dzyding](https://github.com/dzyding)
- [EyreFree](https://www.eyrefree.org/)
- [Forelas](https://github.com/ForelaxX)
- [Hale](https://github.com/wuqiuhao)
- [kemchenj](https://kemchenj.github.io)
- [jojotov](https://github.com/jojotov)
- [Meler](https://github.com/pmtao)
- [mobilefellow](https://github.com/mobilefellow)
- [muhlenXi](https://github.com/muhlenxi)
- [mylittleswift](https://github.com/mylittleswift)
- [Nemocdz](https://github.com/Nemocdz)
- [numbbbbb](https://github.com/numbbbbb)
- [rain2540](https://github.com/rain2540)
- [Rsenjoyer](https://github.com/Rsenjoyer)
- [WAMaker](https://github.com/WAMaker)
- [YiYiZheng](https://github.com/YiYiZheng)
- [ZhangChi](https://github.com/zhangchi25806)
## Swift 3.x 主要贡献者
- [bqlin](https://github.com/bqlin)
- [chenmingjia](https://github.com/chenmingjia)
- [CMB](https://github.com/chenmingbiao)
- [crayygy](https://github.com/crayygy)
- [kemchenj](https://kemchenj.github.io)
- [Lanford](https://github.com/LanfordCai)
- [mmoaay](https://github.com/mmoaay)
- [mylittleswift](https://github.com/mylittleswift)
- [qhd](https://github.com/qhd)
- [shanks](https://github.com/shanksyang)
## Swift 2.x 主要贡献者
- [100mango](https://github.com/100mango)
- [175](https://github.com/Brian175)
- [BridgeQ](https://github.com/WXGBridgeQ)
- [buginux](https://github.com/buginux)
- [Cee](https://github.com/Cee)
- [Channe](https://github.com/Channe)
- [CMB](https://github.com/chenmingbiao)
- [DianQK](https://github.com/DianQK)
- [dreamkidd](https://github.com/dreamkidd)
- [EudeMorgen](https://github.com/EudeMorgen)
- [futantan](https://github.com/futantan)
- [JackAlan](https://github.com/AlanMelody)
- [KYawn](https://github.com/KYawn)
- [Lanford](https://github.com/LanfordCai)
- [Lenhoon](https://github.com/Lenhoon)
- [littledogboy](https://github.com/littledogboy)
- [LinusLing](https://github.com/linusling)
- [lyojo](https://github.com/lyojo)
- [miaosiqi](https://github.com/miaosiqi)
- [mmoaay](https://github.com/mmoaay)
- [overtrue](https://github.com/overtrue)
- [pmst](https://github.com/colourful987)
- [Prayer](https://github.com/futantan)
- [qhd](https://github.com/qhd)
- [ray16897188](https://github.com/ray16897188)
- [Realank](https://github.com/realank)
- [saitjr](https://github.com/saitjr)
- [SergioChan](https://github.com/SergioChan)
- [shanks](https://github.com/shanksyang)
- [SketchK](https://github.com/SketchK)
- [SkyJean](https://github.com/SkyJean)
- [wardenNScaiyi](https:github.com/wardenNScaiyi)
- [xtymichael](https://github.com/xtymichael)
- [yangsiy](https://github.com/yangsiy)
- [星夜暮晨](https://github.com/semperidem)
- [小铁匠 Linus](https://github.com/kevin833752)
## Swift 1.x 主要贡献者
- [bruce0505](https://github.com/bruce0505)
- [changkun](http://changkun.us/about/)
- [ChildhoodAndy](http://childhood.logdown.com)
- [coverxit](https://github.com/coverxit)
- [dabing1022](https://github.com/dabing1022)
- [EvilCome](https://github.com/Evilcome)
- [feiin](https://github.com/feiin)
- [fd5788](https://github.com/fd5788)
- [geek5nan](https://github.com/geek5nan)
- [happyming](https://github.com/happyming)
- [Hawstein](https://github.com/Hawstein)
- [honghaoz](https://github.com/honghaoz)
- [JaceFu](http://www.devtalking.com/)
- [Jasonbroker](https://github.com/Jasonbroker)
- [JaySurplus](https://github.com/JaySurplus)
- [Lenhoon](https://github.com/marsprince)
- [lifedim](https://github.com/lifedim)
- [Lin-H](https://github.com/Lin-H)
- [lslxdx](https://github.com/lslxdx)
- [LunaticM](https://github.com/LunaticM)
- [lyuka](https://github.com/lyuka)
- [marsprince](https://github.com/marsprince)
- [menlongsheng](https://github.com/menlongsheng)
- [NicePiao](https://github.com/NicePiao)
- [numbbbbb](https://github.com/numbbbbb)
- [pp-prog](https://github.com/pp-prog)
- [sg552](https://github.com/sg552)
- [stanzhai](https://github.com/stanzhai)
- [shinyzhu](https://github.com/shinyzhu)
- [superkam](https://github.com/superkam)
- [takalard](https://github.com/takalard)
- [TimothyYe](https://github.com/TimothyYe)
- [vclwei](https://github.com/vclwei)
- [wh1100717](https://github.com/wh1100717)
- [xiehurricane](https://github.com/xiehurricane)
- [XieLingWang](https://github.com/xielingwang)
- [yangsiy](https://github.com/yangsiy)
- [yankuangshi](https://github.com/yankuangshi)
- [yeahdongcn](https://github.com/yeahdongcn)
- [yangsiy](https://github.com/yangsiy)
- [zqp](https://github.com/zqp)
- [成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
- [成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)