们并不知道游戏的长度或者循环的次数,只有在达成指定条件时循环才会结束。
Do-While
while 循环的另外一种形式是 do-while,它和 while 的区别是在判断循环条件之前,先执行一次循环的代码块,然后重复循环直到条件为 false。
下面是一般情况下 do-while 循环的格式:
- do {
- statements
- } while condition
还是蛇和梯子的游戏,使用 do-while 循环来替代 while 循环。finalSquare,board,square 和 diceRoll 的值初始化同 while 循环一样:
- let finalSquare = 25
- var board = Int[](count: finalSquare + 1, repeatedValue: 0)
- board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
- board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
- var square = 0
- var diceRoll = 0
do-while 的循环版本,循环中第一步就需要去检测是否在梯子或者蛇的方块上。没有梯子会让玩家直接上到第 25 个方格,所以玩家不会通过梯子直接赢得游戏。这样在循环开始时先检测是否踩在梯子或者蛇上是安全的。
游戏开始时,玩家在第 0 个方格上,board[0] 一直等于 0, 不会有什么影响:
- do {
- // move up or down for a snake or ladder
- square += board[square]
- // roll the dice
- if ++diceRoll == 7 { diceRoll = 1 }
- // move by the rolled amount
- square += diceRoll
- } while square < finalSquare
- println("Game over!")
检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动 diceRoll 个方格,本轮循环结束。
循环条件(while square < finalSquare)和 while 方式相同,但是只会在循环结束后进行计算。在这个游戏中,do-while 表现得比 while 循环更好。 do-while 方式会在条件判断 square 没有超出后直接运行 square += board[square] ,这种方式可以去掉 while 版本中的数组越界判断。
条件语句
根据特定的条件执行特定的代码通常是十分有用的,例如:当错误发生时,你可能想运行额外的代码;或者,当输入的值太大或太小时,向用户显示一条消息等。要实现这些功能,你就需要使用条件语句。
Swift 提供两种类型的条件语句:if语句和switch语句。通常,当条件较为简单且可能的情况很少时,使用if语句。而switch语句更适用于复杂的条件、可能的情况很多且需要用到模式匹配(pattern-matching)的情境。
If
if语句最简单的形式就是只包含一个条件,当且仅当该条件为真时,才执行相关代码:
- var temperatureInFahrenheit = 30
- if temperatureInFahrenheit <= 32 {
- println("It's very cold. Consider wearing a scarf.")
- }
- // prints "It's very cold. Consider wearing a scarf."
上面的例子会判断温度是否小于等于32华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行if块后面的代码。
当然,if语句允许二选一,也就是当条件为假时,执行else语句:
- temperatureInFahrenheit = 40
- if temperatureInFahrenheit <= 32 {
- println("It's very cold. Consider wearing a scarf.")
- } else {
- println("It's not that cold. Wear a t-shirt.")
- }
- // prints "It's not that cold. Wear a t-shirt."
显然,这两条分支中总有一条会被执行。由于温度已升至40华氏度,不算太冷,没必要再围围巾——因此,else分支就被触发了。
你可以把多个if语句链接在一起,像下面这样:
- temperatureInFahrenheit = 90
- if temperatureInFahrenheit <= 32 {
- println("It's very cold. Consider wearing a scarf.")
- } else if temperatureInFahrenheit >= 86 {
- println("It's really warm. Don't forget to wear sunscreen.")
- } else {
- println("It's not that cold. Wear a t-shirt.")
- }
- // prints "It's really warm. Don't forget to wear sunscreen."
在上面的例子中,额外的if语句用于判断是不是特别热。而最后的else语句被保留了下来,用于打印既不冷也不热时的消息。
实际上,最后的else语句是可选的:
- temperatureInFahrenheit = 72
- if temperatureInFahrenheit <= 32 {
- println("It's very cold. Consider wearing a scarf.")
- } else if temperatureInFahrenheit >= 86 {
- println("It's really warm. Don't forget to wear sunscreen.")
- }
在这个例子中,由于既不冷也不热,所以不会触发if或else if分支,也就不会打印任何消息。
Switch
switch语句会尝试把某个值与若干个模式(pattern)进行匹配。根据第一个匹配成功的模式,switch语句会执行对应的代码。当有可能的情况较多时,通常用switch语句替换if语句。
switch语句最简单的形式就是把某个值与一个或若干个相同类型的值作比较:
- switch `some value to consider` {
- case `value 1`:
- `respond to value 1`
- case `value 2`,
- `value 3`:
- `respond to value 2 or 3`
- default:
- `otherwise, do something else`
- }
switch语句都由多个case构成。为了匹配某些更特定的值,Swift 提供了几种更复杂的匹配模式,这些模式将在本节的稍后部分提到。
每一个case都是代码执行的一条分支,这与if语句类似。与之不同的是,switch语句会决定哪一条分支应该被执行。
switch语句必须是完备的。这就是说,每一个可能的值都必须至少有一个case块与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(default)块满足该要求,这个默认块必须在switch语句的最后面。
下面的例子使用switch语句来匹配一个名为someCharacter的小写字符:
- let someCharacter: Character = "e"
- switch someCharacter {
- case "a", "e", "i", "o", "u":
- println("\(someCharacter) is a vowel")
- case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
- "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
- println("\(someCharacter) is a consonant")
- default:
- println("\(someCharacter) is not a vowel or a consonant")
- }
- // prints "e is a vowel"
在这个例子中,第一个case块用于匹配五个元音,第二个case块用于匹配所有的辅音。
由于为其它可能的字符写case快没有实际的意义,因此在这个例子中使用了默认块来处理剩下的既不是元音也不是辅音的字符——这就保证了switch语句的完备性。
不存在隐式的贯穿(Fallthrough)
与C语言和Objective-C中的switch语句不同,在 Swift 中,当匹配的case块中的代码执行完毕后,程序会终止switch语句,而不会继续执行下一个case块。这也就是说,不需要在case块中显式地使用break语句。这使得switch语句更安全、更易用,也避免了因忘记写break语句而产生的错误。
注意:你依然可以在case块中的代码执行完毕前跳出,详情请参考Switch 语句中的 Break待添加链接
每一个case块都必须包含至少一条语句。像下面这样书写代码是无效的,因为第一个case块是空的:
- let anotherCharacter: Character = "a"
- switch anotherCharacter {
- case "a":
- case "A":
- println("The letter A")
- default:
- println("Not the letter A")
- }
- // this will report a compile-time error
不像C语言里的switch语句,在 Swift 中,switch语句不会同时匹配"a"和"A"。相反的,上面的代码会引起编译期错误:case "a": does not contain any executable statements——这就避免了意外地从一个case块贯穿到另外一个,使得代码更安全、也更直观。
一个case也可以包含多个模式,用逗号把它们分开(如果太长了也可以分行写):
- switch `some value to consider` {
- case `value 1`,
- `value 2`:
- `statements`
- }
注意:如果想要贯穿特定的case块中,请使用fallthrough语句,详情请参考贯穿 (Fallthrough)待添加链接
范围匹配
case块的模式也可以是一个值的范围。下面的例子展示了如何使用范围匹配来输出任意数字对应的自然语言格式:
- let count = 3_000_000_000_000
- let countedThings = "stars in the Milky Way"
- var naturalCount: String
- switch count {
- case 0:
- naturalCount = "no"
- case 1...3:
- naturalCount = "a few"
- case 4...9:
- naturalCount = "several"
- case 10...99:
- naturalCount = "tens of"
- case 100...999:
- naturalCount = "hundreds of"
- case 1000...999_999:
- naturalCount = "thousands of"
- default:
- naturalCount = "millions and millions of"
- }
- println("There are \(naturalCount) \(countedThings).")
- // prints "There are millions and millions of stars in the Milky Way."
元组 (Tuple)
你可以使用元组在同一个switch语句中测试多个值。元组中的元素可以是值,也可以是范围。另外,使用下划线(_)来匹配所有可能的值。
下面的例子展示了如何使用一个(Int, Int)类型的元组来分类下图中的点(x, y):
- let somePoint = (1, 1)
- switch somePoint {
- case (0, 0):
- println("(0, 0) is at the origin")
- case (_, 0):
- println("(\(somePoint.0), 0) is on the x-axis")
- case (0, _):
- println("(0, \(somePoint.1)) is on the y-axis")
- case (-2...2, -2...2):
- println("(\(somePoint.0), \(somePoint.1)) is inside the box")
- default:
- println("(\(somePoint.0), \(somePoint.1)) is outside of the box")
- }
- // prints "(1, 1) is inside the box"
![](http://www.cocoachina.com/cms/uploads/allimg/140611/8370_140611131555_1.png)
在上面的例子中,switch语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在黄色y轴上,是否在一个以原点为中心的4x4的矩形里,或者在这个矩形外面。
不像C语言,Swift 允许多个case匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有四个case。但是,如果存在多个匹配,那么只会执行第一个被匹配到的case块。考虑点(0, 0)会首先匹配case (0, 0),因此剩下的能够匹配(0, 0)的case块都会被忽视掉。
值绑定 (Value Bindings)
case块的模式允许将匹配的值绑定到一个临时的常量或变量,这些常量或变量在该case块里就可以被引用了——这种行为被称为值绑定。
下面的例子展示了如何在一个(Int, Int)类型的元组中使用值绑定来分类下图中的点(x, y):
- let anotherPoint = (2, 0)
- switch anotherPoint {
- case (let x, 0):
- println("on the x-axis with an x value of \(x)")
- case (0, let y):
- println("on the y-axis with a y value of \(y)")
- case let (x, y):
- println("somewhere else at (\(x), \(y))")
- }
- // prints "on the x-axis with an x value of 2"
![](http://www.cocoachina.com/cms/uploads/allimg/140611/8370_140611131652_1.png)
在上面的例子中,switch语句会判断某个点是否在红色的x轴上,是否在黄色y轴上,或者不在坐标轴上。
这三个case都声明了常量x和y的占位符,用于临时获取元组anotherPoint的一个或两个值。第一个case——case (let x, 0)将匹配一个纵坐标为0的点,并把这个点的横坐标赋给临时的常量x。类似的,第二个case——case (0, let y)将匹配一个横坐标为0的点,并把这个点的纵坐标赋给临时的常量y。
一旦声明了这些临时的常量,它们就可以在其对应的case块里引用。在这个例子中,它们用于简化println的书写。
请注意,这个switch语句不包含默认块。这是因为最后一个case——case let(x, y)声明了一个可以匹配余下所有值的元组。这使得switch语句已经完备了,因此不需要再书写默认块。
在上面的例子中,x和y是常量,这是因为没有必要在其对应的case块中修改它们的值。然而,它们也可以是变量——程序将会创建临时变量,并用相应的值初始化它。修改这些变量只会影响其对应的case块。
Where
case块的模式可以使用where语句来判断额外的条件。
下面的例子把下图中的点(x, y)进行了分类:
- let yetAnotherPoint = (1, -1)
- switch yetAnotherPoint {
- case let (x, y) where x == y:
- println("(\(x), \(y)) is on the line x == y")
- case let (x, y) where x == -y:
- println("(\(x), \(y)) is on the line x == -y")
- case let (x, y):
- println("(\(x), \(y)) is just some arbitrary point")
- }
- // prints "(1, -1) is on the line x == -y"
![](http://www.cocoachina.com/cms/uploads/allimg/140611/8370_140611131736_1.png)
在上面的例子中,switch语句会判断某个点是否在绿色的对角线x == y上,是否在紫色的对角线x == -y上,或者不在对角线上。
这三个case都声明了常量x和y的占位符,用于临时获取元组yetAnotherPoint的两个值。这些常量被用作where语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当where语句的条件为真时,匹配到的case块才会被执行。
就像是值绑定中的例子,由于最后一个case块匹配了余下所有可能的值,switch语句就已经完备了,因此不需要再书写默认块。
控制转移语句
控制转移语句改变你代码的执行顺序,通过它你可以实现代码的跳转。Swift有四种控制转移语句。
- continue
- break
- fallthrough
- return
我们将会在下面讨论continue ,break,和fallthrough语句。return语句将会在函数章节讨论。
Continue
continue告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。就好像在说“本次循环迭代我已经执行完了”,但是并不会离开整个循环体。
NOTE:在一个for-condition-increment循环体中,在调用continue语句后,迭代增量仍然会被计算求值。循环体继续像往常一样工作,仅仅只是循环体中的执行代码会被跳过。
下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句:
- let puzzleInput = "great minds think alike"
- var puzzleOutput = ""
- for character in puzzleInput {
- switch character {
- case "a", "e", "i", "o", "u", " ":
- continue
- default:
- puzzleOutput += character
- }
- }
- println(puzzleOutput)
- // prints "grtmndsthnklk"
在上面的代码中,只要匹配到元音字母或者空格字符,就调用continue语句,使本次循环迭代结束,从新开始下次循环迭代。这种行为使switch匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。
Break
break语句会立刻结束整个控制流的执行。当你想要更早的结束一个switch代码块或者一个循环体时,你都可以使用break语句。
在循环体中使用Break
当在一个循环体中使用break时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(})后的第一行代码。不会再有本次循环迭代的代码被执行,也不会再有下次的循环迭代产生。
在Switch代码块中使用Break
当在一个switch代码块中使用break时,会立即中断该switch代码块的执行,并且跳转到表示switch代码块结束的大括号(})后的第一行代码。
这种特性可以被用来匹配或者忽略一个或多个分支。因为Swift语言的switch需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上break语句。当那个分支被匹配到时,分支内的break语句立即结束switch代码块。
NOTE:当一个switch分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让switch分支达到被忽略的效果。你总是可以使用break来忽略某个分支。
下面的例子通过switch来判断一个Character值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。
- let numberSymbol: Character = "三" // Simplified Chinese for the number 3
- possibleIntegerValue: Int?
- switch numberSymbol {
- case "1", "?", "一", "?":
- possibleIntegerValue = 1
- case "2", "?", "二", "?":
- possibleIntegerValue = 2
- case "3", "?", "三", "?":
- possibleIntegerValue = 3
- case "4", "?", "四", "?":
- possibleIntegerValue = 4
- default:
- break
- }
- if let integerValue = possibleIntegerValue {
- println("The integer value of \(numberSymbol) is \(integerValue).")
- } else {
- println("An integer value could not be found for \(numberSymbol).")
- }
- // prints "The integer value of 三 is 3."
这个例子检查numberSymbol是否是拉丁,阿拉伯,中文或者泰语中的1...4之一。如果被匹配到,该switch分支语句给Int?类型变量possibleIntegerValue设置一个整数值。
当switch代码块执行完后,接下来的代码通过使用可选绑定来判断'possibleIntegerValue'是否曾经被设置过值。因为是可选类型的缘故,'possibleIntegerValue'有一个隐式的初始值nil,所以仅仅当possibleIntegerValue曾被switch代码块的前四个分支中的某个设置过一个值时,可选的绑定将会被判定为成功。
在上面的例子中,想要把Character所有的的可能性都枚举出来是不现实的,所以使用default分支来包含所有上面没有匹配到字符的情况。由于这个default分支不需要执行任何动作,所以它只写了一条break语句。一旦落入到default分支中后,break语句就完成了该分支的所有代码操作,代码继续向下,开始执行if let语句。
Fallthrough
Swift语言中的switch不会从上一个case分支落入到下一个case分支中。相反,只要第一个匹配到的case分支完成了它需要执行的语句,整个switch代码块完成了它的执行。相比之下,C语言要求你显示的插入break语句到每个switch分支的末尾来阻止自动落入到下一个case分支中。Swift语言的这种避免默认落入到下一个分支中的特性意味着它的switch 功能要比C语言的更加清晰和可预测,可以避免无意识地执行多个case分支从而引发的错误。
如果你确实需要C风格的落入(fallthrough)的特性,你可以在每个需要该特性的case分支中使用fallthrough关键字。下面的例子使用fallthrough来创建一个数字的描述语句。
- let integerToDescribe = 5
- var description = "The number \(integerToDescribe) is"
- switch integerToDescribe {
- case 2, 3, 5, 7, 11, 13, 17, 19:
- description += " a prime number, and also"
- fallthrough
- default:
- description += " an integer."
- }
- println(description)
- // prints "The number 5 is a prime number, and also an integer."
这个例子定义了一个String类型的变量description并且给它设置了一个初始值。函数使用switch逻辑来判断integerToDescribe变量的值。当integerToDescribe的值属于列表中的质数之一时,该函数添加一段文字在description后,来表明这个是数字是一个质数。然后它使用fallthrough关键字来"落入"到default分支中。default分支添加一段额外的文字在description的最后,至此switch代码块执行完了。
如果integerToDescribe的值不属于列表中的任何质数,那么它不会匹配到第一个switch分支。而这里没有其他特别的分支情况,所以integerToDescribe匹配到包含所有的default分支中。
当switch代码块执行完后,使用println函数打印该数字的描述。在这个例子中,数字5被准确的识别为了一个质数。
NOTE:fallthrough关键字不会检查它下一个将会落入执行的case中的匹配条件。fallthrough简单地使代码执行继续连接到下一个case中的执行代码,这和C语言标准中的switch语句特性是一样的。
Labeled Statements
在Swift语言中,你可以在循环体和switch代码块中嵌套循环体和switch代码块来创造复杂的控制流结构。然而,循环体和switch代码块两者都可以使用break语句来提前结束整个方法体。因此,显示地指明break语句想要终止的是哪个循环体或者switch代码块,会很有用。类似地,如果你有许多嵌套的循环体,显示指明continue语句想要影响哪一个循环体也会非常有用。
为了实现这个目的,你可以使用标签来标记一个循环体或者switch代码块,当使用break或者continue时,带上这个标签,可以控制该标签代表对象的中断或者执行。
产生一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,并且该标签后面还需带着一个冒号。下面是一个while循环体的语法,同样的规则适用于所有的循环体和switch代码块。
- label name: while condition {
- statements
- }
下面的例子是在一个带有标签的while循环体中调用break和continue语句,该循环体是上述章节中蛇梯棋游戏的改编版本。这次,游戏增加了一条额外的规则:
- 为了获胜,你必须刚好落在方格25中。
如果某次掷骰子使你的移动超出方格25,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在方格25中。
游戏的棋盘和之前一样:
![](http://www.cocoachina.com/cms/uploads/allimg/140611/8370_140611132055_1.png)
值finalSquare,board,square和diceRoll的初始化也和之前一样:
- let finalSquare = 25
- var board = Int[](count: finalSquare + 1, repeatedValue: 0)
- board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
- board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
- var square = 0
- var diceRoll = 0
这个版本的游戏使用while循环体和switch方法块来实现游戏的逻辑。while循环体有一个标签名gameLoop,来表明它是蛇梯棋游戏的主循环。
该while循环体的条件判断语句是while square !=finalSquare,这表明你必须刚好落在方格25中。
- gameLoop: while square != finalSquare {
- if ++diceRoll == 7 { diceRoll = 1 }
- switch square + diceRoll {
- case finalSquare:
- // diceRoll will move us to the final square, so the game is over
- break gameLoop
- case let newSquare where newSquare > finalSquare:
- // diceRoll will move us beyond the final square, so roll again
- continue gameLoop
- default:
- // this is a valid move, so find out its effect
- square += diceRoll
- square += board[square]
- }
- }
- println("Game over!")
每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了switch来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。
- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。break gameLoop语句跳转控制去执行while循环体后的第一行代码,游戏结束。
- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。continue gameLoop语句结束本次while循环的迭代,开始下一次循环迭代。
- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动骰子数个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。本次循环迭代结束,控制跳转到while循环体的条件判断语句处,再决定是否能够继续执行下次循环迭代。
NOTE:如果上述的break语句没有使用gameLoop标签,那么它将会中断switch代码块而不是while循环体。使用gameLoop标签清晰的表明了break想要中断的是哪个代码块。 同时请注意,当调用continue gameLoop去跳转到下一次循环迭代时,这里使用gameLoop标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以continue语句会影响到哪个循环体是没有歧义的。然而,continue语句使用gameLoop标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的break gameLoop,能够使游戏的逻辑更加清晰和易于理解。
CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocostudio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!