diff --git a/source/chapter1/02_a_swift_tour.md b/source/chapter1/02_a_swift_tour.md index b2649081..d3ed904b 100755 --- a/source/chapter1/02_a_swift_tour.md +++ b/source/chapter1/02_a_swift_tour.md @@ -22,10 +22,10 @@ 通常来说,编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现: ```swift -print("Hello, world") +print("Hello, world!") ``` -如果你写过 C 或者 Objective-C 代码,那你应该很熟悉这种形式——在 Swift 中,这行代码就是一个完整的程序。你不需要为了输入输出或者字符串处理导入一个单独的库。全局作用域中的代码会被自动当做程序的入口点,所以你也不需要`main`函数。你同样不需要在每个语句结尾写上分号。 +如果你写过 C 或者 Objective-C 代码,那你应该很熟悉这种形式——在 Swift 中,这行代码就是一个完整的程序。你不需要为了输入输出或者字符串处理导入一个单独的库。全局作用域中的代码会被自动当做程序的入口点,所以你也不需要`main()`函数。你同样不需要在每个语句结尾写上分号。 这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解。 @@ -84,9 +84,7 @@ let fruitSummary = "I have \(apples + oranges) pieces of fruit." ```swift var shoppingList = ["catfish", "water", "tulips", "blue paint"] shoppingList[1] = "bottle of water" -``` -```swift var occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic", @@ -152,20 +150,21 @@ if let name = optionalName { let vegetable = "red pepper" switch vegetable { case "celery": - let vegetableComment = "Add some raisins and make ants on a log." + print("Add some raisins and make ants on a log.") case "cucumber", "watercress": - let vegetableComment = "That would make a good tea sandwich." + print("That would make a good tea sandwich.") case let x where x.hasSuffix("pepper"): - let vegetableComment = "Is it a spicy \(x)?" + print("Is it a spicy \(x)?") default: - let vegetableComment = "Everything tastes good in soup." + print("Everything tastes good in soup.") } ``` > 练习: > 删除`default`语句,看看会有什么错误? -声明`let`可用于匹配某部分固定值的模式 + +注意`let`在上述例子的等式中是如何使用的,它将匹配等式的值赋给常量`x`。 运行`switch`中匹配到的子句之后,程序会退出`switch`语句,并不会继续向下运行,所以不需要在每个子句结尾写`break`。 @@ -442,12 +441,12 @@ class EquilateralTriangle: NamedShape { } var perimeter: Double { - get { - return 3.0 * sideLength - } - set { - sideLength = newValue / 3.0 - } + get { + return 3.0 * sideLength + } + set { + sideLength = newValue / 3.0 + } } override func simpleDescription() -> String { @@ -475,14 +474,14 @@ print(triangle.sideLength) ```swift class TriangleAndSquare { var triangle: EquilateralTriangle { - willSet { - square.sideLength = newValue.sideLength - } + willSet { + square.sideLength = newValue.sideLength + } } var square: Square { - willSet { - triangle.sideLength = newValue.sideLength - } + willSet { + triangle.sideLength = newValue.sideLength + } } init(size: Double, name: String) { square = Square(sideLength: size, name: name) @@ -535,9 +534,9 @@ let aceRawValue = ace.rawValue > 练习: > 写一个函数,通过比较它们的原始值来比较两个`Rank`值。 -在上面的例子中,枚举原始值的类型是`Int`,所以你只需要设置第一个原始值。剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用`rawValue`来访问一个枚举成员的原始值。 +在上面的例子中,枚举原始值的类型是`Int`,所以你只需要设置第一个原始值。剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用`rawValue`属性来访问一个枚举成员的原始值。 -使用`init?(rawValue:)`构造器来从原始值中枚举一个例子。 +使用`init?(rawValue:)`初始化构造器在原始值和枚举值之间进行转换。 ```swift if let convertedRank = Rank(rawValue: 3) { @@ -562,7 +561,6 @@ enum Suit { return "clubs" } } - } let hearts = Suit.Hearts let heartsDescription = hearts.simpleDescription() @@ -599,10 +597,10 @@ enum ServerResponse { case Result(String, String) case Error(String) } - + let success = ServerResponse.Result("6:00 am", "8:09 pm") let failure = ServerResponse.Error("Out of cheese.") - + switch success { case let .Result(sunrise, sunset): let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)." @@ -663,7 +661,7 @@ let bDescription = b.simpleDescription ```swift extension Int: ExampleProtocol { var simpleDescription: String { - return "The number \(self)" + return "The number \(self)" } mutating func adjust() { self += 42 @@ -679,8 +677,8 @@ print(7.simpleDescription) ```swift let protocolValue: ExampleProtocol = a -protocolValue.simpleDescription -// protocolValue.anotherProperty // Uncomment to see the error +print(protocolValue.simpleDescription) +// print(protocolValue.anotherProperty) // Uncomment to see the error ``` 即使`protocolValue`变量运行时的类型是`simpleClass`,编译器会把它的类型当做`ExampleProtocol`。这表示你不能调用类在它实现的协议之外实现的方法或者属性。 @@ -705,18 +703,18 @@ repeatItem("knock", numberOfTimes:4) ```swift // Reimplement the Swift standard library's optional type -enum OptionalValue { +enum OptionalValue { case None - case Some(T) + case Some(Wrapped) } var possibleInteger: OptionalValue = .None possibleInteger = .Some(100) ``` -在类型名后面使用`where`来指定对类型的需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类 +在类型名后面使用`where`来指定对类型的需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。 ```swift -func anyCommonElements (lhs: T, _ rhs: U) -> Bool { +func anyCommonElements (lhs: T, _ rhs: U) -> Bool { for lhsItem in lhs { for rhsItem in rhs { if lhsItem == rhsItem { @@ -732,4 +730,4 @@ anyCommonElements([1, 2, 3], [3]) > 练习: > 修改`anyCommonElements(_:_:)`函数来创建一个函数,返回一个数组,内容是两个序列的共有元素。 -简单起见,你可以忽略`where`,只在冒号后面写协议或者类名。` `和``是等价的。 +` `和``是等价的。 diff --git a/source/chapter2/19_Nested_Types.md b/source/chapter2/19_Nested_Types.md new file mode 100644 index 00000000..fe0bf822 --- /dev/null +++ b/source/chapter2/19_Nested_Types.md @@ -0,0 +1,99 @@ +> 1.0 +> 翻译:[Lin-H](https://github.com/Lin-H) +> 校对:[shinyzhu](https://github.com/shinyzhu) + +> 2.0 +> 翻译+校对:[SergioChan](https://github.com/SergioChan) + +# 嵌套类型 +----------------- + +本页包含内容: + +- [嵌套类型实例](#nested_types_in_action) +- [嵌套类型的引用](#referring_to_nested_types) + +枚举类型常被用于实现特定类或结构体的功能。也能够在有多种变量类型的环境中,方便地定义通用类或结构体来使用,为了实现这种功能,Swift允许你定义嵌套类型,可以在枚举类型、类和结构体中定义支持嵌套的类型。 + +要在一个类型中嵌套另一个类型,将需要嵌套的类型的定义写在被嵌套类型的区域{}内,而且可以根据需要定义多级嵌套。 + + +##嵌套类型实例 + +下面这个例子定义了一个结构体`BlackjackCard`(二十一点),用来模拟`BlackjackCard`中的扑克牌点数。`BlackjackCard`结构体包含2个嵌套定义的枚举类型`Suit` 和 `Rank`。 + +在`BlackjackCard`规则中,`Ace`牌可以表示1或者11,`Ace`牌的这一特征用一个嵌套在枚举型`Rank`的结构体`Values`来表示。 + +```swift +struct BlackjackCard { + // 嵌套定义枚举型Suit + enum Suit: Character { + case Spades = "♠", Hearts = "♡", Diamonds = "♢", Clubs = "♣" + } + + // 嵌套定义枚举型Rank + enum Rank: Int { + case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten + case Jack, Queen, King, Ace + struct Values { + let first: Int, second: Int? + } + var values: Values { + switch self { + case .Ace: + return Values(first: 1, second: 11) + case .Jack, .Queen, .King: + return Values(first: 10, second: nil) + default: + return Values(first: self.rawValue, second: nil) + } + } + } + + // BlackjackCard 的属性和方法 + let rank: Rank, suit: Suit + var description: String { + var output = "suit is \(suit.rawValue)," + output += " value is \(rank.values.first)" + if let second = rank.values.second { + output += " or \(second)" + } + return output + } +} +``` + +枚举型的`Suit`用来描述扑克牌的四种花色,并分别用一个`Character`类型的值代表花色符号。 + +枚举型的`Rank`用来描述扑克牌从`Ace`~10,`J`,`Q`,`K`,13张牌,并分别用一个`Int`类型的值表示牌的面值。(这个`Int`类型的值不适用于`Ace`,`J`,`Q`,`K`的牌)。 + +如上文所提到的,枚举型`Rank`在自己内部定义了一个嵌套结构体`Values`。在这个结构体中,只有`Ace`有两个数值,其余牌都只有一个数值。结构体`Values`中定义的两个属性: + +`first`, 为` Int` +`second`, 为 `Int?`, 或 “optional `Int`” + +`Rank`定义了一个计算属性`values`,它将会返回一个结构体`Values`的实例。这个计算属性会根据牌的面值,用适当的数值去初始化`Values`实例,并赋值给`values`。对于`J`,`Q`,`K`,`Ace`会使用特殊数值,对于数字面值的牌使用`Int`类型的值。 + +`BlackjackCard`结构体自身有两个属性—`rank`与`suit`,也同样定义了一个计算属性`description`,`description`属性用`rank`和`suit`的中内容来构建对这张扑克牌名字和数值的描述,并用可选类型`second`来检查是否存在第二个值,若存在,则在原有的描述中增加对第二数值的描述。 + +因为`BlackjackCard`是一个没有自定义构造函数的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#memberwise_initializers_for_structure_types)中知道结构体有默认的成员构造函数,所以你可以用默认的`initializer`去初始化新的常量`theAceOfSpades`: + +```swift +let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades) +print("theAceOfSpades: \(theAceOfSpades.description)") +// 打印出 "theAceOfSpades: suit is ♠, value is 1 or 11" +``` + +尽管`Rank`和`Suit`嵌套在`BlackjackCard`中,但仍可被引用,所以在初始化实例时能够通过枚举类型中的成员名称单独引用。在上面的例子中`description`属性能正确得输出对`Ace`牌有1和11两个值。 + + +##嵌套类型的引用 + +在外部对嵌套类型的引用,以被嵌套类型的名字为前缀,加上所要引用的属性名: + +```swift +let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue +// 红心的符号 为 "♡" +``` + +对于上面这个例子,这样可以使`Suit`, `Rank`, 和 `Values`的名字尽可能的短,因为它们的名字会自然的由定义它们的上下文来限定。 diff --git a/source/chapter2/20_Type_Casting.md b/source/chapter2/20_Type_Casting.md new file mode 100644 index 00000000..8ecbbfb7 --- /dev/null +++ b/source/chapter2/20_Type_Casting.md @@ -0,0 +1,261 @@ +> 1.0 +> 翻译:[xiehurricane](https://github.com/xiehurricane) +> 校对:[happyming](https://github.com/happyming) + +> 2.0 +> 翻译+校对:[yangsiy](https://github.com/yangsiy) + +# 类型转换(Type Casting) +----------------- + +本页包含内容: + +- [定义一个类层次作为例子](#defining_a_class_hierarchy_for_type_casting) +- [检查类型](#checking_type) +- [向下转型(Downcasting)](#downcasting) +- [`Any`和`AnyObject`的类型转换](#type_casting_for_any_and_anyobject) + + +_类型转换_可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。 + +类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。 + +你也可以用它来检查一个类是否实现了某个协议,就像在 [检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。 + + +## 定义一个类层次作为例子 + +你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了几个这些类实例的数组,作为类型转换的例子。 + +第一个代码片段定义了一个新的基础类 `MediaItem`。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 `String` 类型的 `name` 属性,和一个 `init name` 初始化器。(假定所有的媒体项都有个名称。) + +```swift +class MediaItem { + var name: String + init(name: String) { + self.name = name + } +} +``` + +下一个代码段定义了 `MediaItem` 的两个子类。第一个子类 `Movie` 封装了与电影相关的额外信息,在父类(或者说基类)的基础上增加了一个 `director`(导演)属性,和相应的初始化器。第二个子类 `Song`,在父类的基础上增加了一个 `artist`(艺术家)属性,和相应的初始化器: + +```swift +class Movie: MediaItem { + var director: String + init(name: String, director: String) { + self.director = director + super.init(name: name) + } +} + +class Song: MediaItem { + var artist: String + init(name: String, artist: String) { + self.artist = artist + super.init(name: name) + } +} +``` + +最后一个代码段创建了一个数组常量 `library`,包含两个 `Movie` 实例和三个 `Song` 实例。`library` 的类型是在它被初始化时根据它数组中所包含的内容推断来的。Swift的类型检测器能够推理出 `Movie` 和 `Song` 有共同的父类 `MediaItem`,所以它推断出 `[MediaItem]` 类作为 `library` 的类型。 + +```swift +let library = [ + Movie(name: "Casablanca", director: "Michael Curtiz"), + Song(name: "Blue Suede Shoes", artist: "Elvis Presley"), + Movie(name: "Citizen Kane", director: "Orson Welles"), + Song(name: "The One And Only", artist: "Chesney Hawkes"), + Song(name: "Never Gonna Give You Up", artist: "Rick Astley") +] +// the type of "library" is inferred to be [MediaItem] +``` + +在幕后 `library` 里存储的媒体项依然是 `Movie` 和 `Song` 类型的。但是,若你迭代它,依次取出的实例会是 `MediaItem` 类型的,而不是 `Movie` 和 `Song` 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。 + + +## 检查类型(Checking Type) + +用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。 + +下面的例子定义了两个变量,`movieCount` 和 `songCount`,用来计算数组 `library` 中 `Movie` 和 `Song` 类型的实例数量。 + +```swift +var movieCount = 0 +var songCount = 0 + +for item in library { + if item is Movie { + ++movieCount + } else if item is Song { + ++songCount + } +} + +print("Media library contains \(movieCount) movies and \(songCount) songs") +// prints "Media library contains 2 movies and 3 songs" +``` + +示例迭代了数组 `library` 中的所有项。每一次,`for`-`in` 循环设置 +`item` 为数组中的下一个 `MediaItem`。 + +若当前 `MediaItem` 是一个 `Movie` 类型的实例,`item is Movie` 返回 +`true`,相反返回 `false`。同样的,`item is +Song` 检查item是否为 `Song` 类型的实例。在循环结束后,`movieCount` 和 `songCount` 的值就是被找到属于各自的类型的实例数量。 + + +## 向下转型(Downcasting) + +某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(`as?` 或 `as!`) + +因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式(conditional form) `as?` 返回一个你试图向下转成的类型的可选值(optional value)。强制形式 `as!` 把试图向下转型和强制解包(force-unwraps)结果作为一个混合动作。 + +当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。 + +只有你可以确定向下转型一定会成功时,才使用强制形式(`as!`)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。 + +下面的例子,迭代了 `library` 里的每一个 `MediaItem`,并打印出适当的描述。要这样做,`item` 需要真正作为 `Movie` 或 `Song` 的类型来使用,不仅仅是作为 `MediaItem`。为了能够在描述中使用 `Movie` 或 `Song` 的 `director` 或 `artist` 属性,这是必要的。 + +在这个示例中,数组中的每一个 `item` 可能是 `Movie` 或 `Song`。事前你不知道每个 `item` 的真实类型,所以这里使用条件形式的类型转换(`as?`)去检查循环里的每次下转。 + +```swift +for item in library { + if let movie = item as? Movie { + print("Movie: '\(movie.name)', dir. \(movie.director)") + } else if let song = item as? Song { + print("Song: '\(song.name)', by \(song.artist)") + } +} + +// Movie: 'Casablanca', dir. Michael Curtiz +// Song: 'Blue Suede Shoes', by Elvis Presley +// Movie: 'Citizen Kane', dir. Orson Welles +// Song: 'The One And Only', by Chesney Hawkes +// Song: 'Never Gonna Give You Up', by Rick Astley +``` + +示例首先试图将 `item` 下转为 `Movie`。因为 `item` 是一个 `MediaItem` +类型的实例,它可能是一个 `Movie`;同样,它也可能是一个 `Song`,或者仅仅是基类 +`MediaItem`。因为不确定,`as?`形式在试图下转时将返回一个可选值。`item as? Movie` 的返回值是 `Movie?` 或 “可选 `Movie`”类型。 + +当向下转型为 `Movie` 应用在两个 `Song` +实例时将会失败。为了处理这种情况,上面的例子使用了可选绑定(optional binding)来检查可选 `Movie` 真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“`if let movie = item as? Movie`”,可以这样解读: + +“尝试将 `item` 转为 `Movie` 类型。若成功,设置一个新的临时常量 `movie` 来存储返回的可选 `Movie`” + +若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director` 。相近的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。 + +> 注意: +> 转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。 + + +## `Any`和`AnyObject`的类型转换 + +Swift为不确定类型提供了两种特殊类型别名: + +* `AnyObject`可以代表任何class类型的实例。 +* `Any`可以表示任何类型,包括方法类型(function types)。 + +> 注意: +> 只有当你明确的需要它的行为和功能时才使用`Any`和`AnyObject`。在你的代码里使用你期望的明确的类型总是更好的。 + + +### `AnyObject`类型 + +当在工作中使用 Cocoa APIs,我们一般会接收一个`[AnyObject]`类型的数组,或者说“一个任何对象类型的数组”。这是因为 Objective-C 没有明确的类型化数组。但是,你常常可以从 API 提供的信息中清晰地确定数组中对象的类型。 + +在这些情况下,你可以使用强制形式的类型转换(`as`)来下转在数组中的每一项到比 `AnyObject` 更明确的类型,不需要可选解析(optional unwrapping)。 + +下面的示例定义了一个 `[AnyObject]` 类型的数组并填入三个`Movie`类型的实例: + +```swift +let someObjects: [AnyObject] = [ + Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"), + Movie(name: "Moon", director: "Duncan Jones"), + Movie(name: "Alien", director: "Ridley Scott") +] +``` + +因为知道这个数组只包含 `Movie` 实例,你可以直接用(`as!`)下转并解包到不可选的`Movie`类型: + +```swift +for object in someObjects { + let movie = object as! Movie + print("Movie: '\(movie.name)', dir. \(movie.director)") +} +// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick +// Movie: 'Moon', dir. Duncan Jones +// Movie: 'Alien', dir. Ridley Scott +``` + +为了变为一个更短的形式,下转`someObjects`数组为`[Movie]`类型来代替下转数组中每一项的方式。 + +```swift +for movie in someObjects as! [Movie] { + print("Movie: '\(movie.name)', dir. \(movie.director)") +} +// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick +// Movie: 'Moon', dir. Duncan Jones +// Movie: 'Alien', dir. Ridley Scott +``` + +### `Any`类型 + +这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括方法类型和非 `class` 类型。它创建了一个可以存储`Any`类型的数组 `things`。 + +```swift +var things = [Any]() + +things.append(0) +things.append(0.0) +things.append(42) +things.append(3.14159) +things.append("hello") +things.append((3.0, 5.0)) +things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman")) +things.append({ (name: String) -> String in "Hello, \(name)" }) +``` + +`things` 数组包含两个 `Int` 值,2个 `Double` 值,1个 `String` 值,一个元组 `(Double, Double)` ,电影“Ghostbusters”,和一个获取 `String` 值并返回另一个 `String` 值的闭包表达式。 + +你可以在 `switch` 表达式的cases中使用 `is` 和 `as` 操作符来发觉只知道是 `Any` 或 `AnyObject` 的常量或变量的类型。下面的示例迭代 `things` 数组中的每一项的并用`switch`语句查找每一项的类型。这几种 `switch` 语句的情形绑定它们匹配的值到一个规定类型的常量,让它们的值可以被打印: + +```swift +for thing in things { + switch thing { + case 0 as Int: + print("zero as an Int") + case 0 as Double: + print("zero as a Double") + case let someInt as Int: + print("an integer value of \(someInt)") + case let someDouble as Double where someDouble > 0: + print("a positive double value of \(someDouble)") + case is Double: + print("some other double value that I don't want to print") + case let someString as String: + print("a string value of \"\(someString)\"") + case let (x, y) as (Double, Double): + print("an (x, y) point at \(x), \(y)") + case let movie as Movie: + print("a movie called '\(movie.name)', dir. \(movie.director)") + case let stringConverter as String -> String: + print(stringConverter("Michael")) + default: + print("something else") + } +} + +// zero as an Int +// zero as a Double +// an integer value of 42 +// a positive double value of 3.14159 +// a string value of "hello" +// an (x, y) point at 3.0, 5.0 +// a movie called 'Ghostbusters', dir. Ivan Reitman +// Hello, Michael +``` + + +> 注意: +> 在一个switch语句的case中使用强制形式的类型转换操作符(as, 而不是 as?)来检查和转换到一个明确的类型。在 `switch` case 语句的内容中这种检查总是安全的。