控制转移语句
控制转移语句改变你代码的执⾏顺序,通过它可以实现代码的跳转。Swift 有五种控制转移语句:
- continue
- break
- fallthrough
- return
- throw
我们将会在下面讨论 continue 、 break 和 fallthrough 语句。 return 语句将会在《函数》章节讨论, throw语句会在《错误抛
出》章节讨论。
Continue
continue 语句告诉一个循环体立刻停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离
开整个循环体。
下面的例子把一个小写字符串中的元音字母和空格字符移除,⽣成了一个含义模糊的短句:
let puzzleInput = "great minds think alike"
var puzzleOutput = ""
for character in puzzleInput {
switch character {
case "a", "e", "i", "o", "u", " ":
continue
default:
puzzleOutput.append(character)
}
}
print(puzzleOutput)
// 输出“grtmndsthnklk”
在上⾯的代码中,只要匹配到元音字母或者空格字符,就调用 continue 语句,使本次循环结束,重新开始下次循环。 这种⾏为
使 switch 匹配到元音字母和空格字符时不做处理,⽽不是让每一个匹配到的字符都被打印。
Break
break 语句会立刻结束整个控制流的执⾏。 break 可以在 switch 或循环语句中使用,用来提前结束 switch 或 循环语句。
循环语句中的 break
当在一个循环体中使用 break 时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号( } )后的第一 ⾏代码。不会
再有本次循环的代码被执⾏,也不会再有下次的循环产生。
Switch 语句中的 break
当在一个 switch 代码块中使用 break 时,会立即中断该 switch 代码块的执⾏,并且跳转到表示 switch 代码块结束的大括号( }
)后的第一⾏代码。
这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的 switch 需要包含所有的分⽀⽽且不允许有为空的分支,有时
为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上 break 语句。当那
个分支被匹配到时,分支内的 break 语句立即结束 switch 代码块。
注意
当一个 switch 分支仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让 switch 分支达到被忽略的效果。你应该
使用 break 来忽略某个分支。
下⾯的例子通过 switch 来判断一个 Character 值是否代表下⾯四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。
let numberSymbol: Character = "三" // 简体中文里的数字 3
var possibleIntegerValue: Int?
switch numberSymbol {
case "1", "١", "⼀一", "๑":
possibleIntegerValue = 1
case "2", "٢", "⼆二", "๒":
possibleIntegerValue = 2
case "3", "٣", "三", "๓":
possibleIntegerValue = 3
case "4", "٤", "四", "๔":
possibleIntegerValue = 4
default:
break
}
if let integerValue = possibleIntegerValue {
print("The integer value of \(numberSymbol) is \(integerValue).")
} else {
print("An integer value could not be found for \(numberSymbol).")
}
// 输出“The integer value of 三 is 3.”
这个例子检查 numberSymbol 是否是拉丁,阿拉伯,中⽂或者泰语中的 1 到 4 之一。如果被匹配到,该 switch 分支语句给 Int?
类型变量 possibleIntegerValue 设置一个整数值。
当 switch 代码块执⾏完后,接下来的代码通过使用可选绑定来判断 possibleIntegerValue 是否曾经被设置过值。因为是可选类型
的缘故, possibleIntegerValue 有一个隐式的初始值 nil ,所以仅当 possibleIntegerValue 曾被 switch 代码块的前四个分支中的
某个设置过一个值时,可选的绑定才会被判定为成功。
在上面的例子中,想要把 Character 所有的的可能性都枚举出来是不现实的,所以使用 default 分支来包含所有上面没有匹配到
字符的情况。由于这个 default 分支不需要执⾏任何动作,所以它只写了一条 break 语句。一旦落入 到 default 分支中后, break
语句就完成了该分支的所有代码操作,代码继续向下,开始执⾏ if let 语句。
贯穿(Fallthrough)
在 Swift ⾥, switch 语句不会从上一个 case 分支跳转到下一个 case 分支中。相反,只要第一个匹配到的 case 分支 完成了它
需要执⾏的语句,整个 switch 代码块就完成了它的执⾏。相比之下,C 语言要求你显式地插入 break 语句到每个 case 分支的
末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的 switch 功能要比 C 语
⾔的更加清晰和可预测,可以避免无意识地执⾏多个 case 分支从而引发的错误。
如果你确实需要 C 风格的贯穿特性,你可以在每个需要该特性的 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."
}
print(description)
// 输出“The number 5 is a prime number, and also an integer.”
这个例子定义了一个 String 类型的变量 description 并且给它设置了一个初始值。函数使用 switch 逻辑来判断
integerToDescribe 变量的值。当 integerToDescribe 的值属于列表中的质数之一时,该函数在 description 后添加一段文字,来
表明这个数字是一个质数。然后它使用 fallthrough 关键字来“贯穿”到default 分支中。 default 分支在 description 的最后添加一
段额外的文字,至此 switch 代码块执⾏完了。
如果 integerToDescribe 的值不属于列表中的任何质数,那么它不会匹配到第一个 switch 分支。⽽这⾥没有其他特别的分支情
况,所以 integerToDescribe 被匹配到 default 分支中。
当 switch 代码块执⾏完后,使用 print(_:separator:terminator:) 函数打印该数字的描述。在这个例子中,数字 5 被准确的识别为
了一个质数。
注意
fallthrough 关键字不会检查它下一个将会落入执⾏的 case 中的匹配条件。 fallthrough 简单地使代码继续连接到下一个 case
中的代码,这和 C 语言标准中的 switch 语句特性是一样的。
带标签的语句
在 Swift 中,你可以在循环体和条件语句中嵌套循环体和条件语句来创造复杂的控制流结构。并且,循环体和条件语句都可以使
用 break 语句来提前结束整个代码块。因此,显式地指明 break 语句想要终止的是哪个循环体或者条件语句,会很有用。类似
地,如果你有许多嵌套的循环体,显式指明 continue 语句想要影响哪一个循环体也会非常有用。
为了实现这个目的,你可以使用标签(statement label)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用 break
加标签的⽅式,来结束这个被标记的语句。对于一个循环语句,你可以使用 break 或者 continue 加标签,来结束或者继续这条
被标记语句的执⾏。
声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字(introducor
keyword),并且该标签后面跟随一个冒号。下⾯是⼀个针对 while 循环体的标签语法,同样的规则适用于所有的循环体和条件语
句。
label name: while condition {
statements
}
下⾯的例子是前⾯章节中蛇和梯⼦的适配版本,在此版本中,我们将使用一个带有标签的 while 循环体中调用 break 和 continue
语句。
这次,游戏增加了一条额外的规则:
为了获胜,你必须刚好落在第 25 个方块中。
如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中。
游戏的棋盘和之前一样:
finalSquare 、 board 、 square 和 diceRoll 值和之前一样的方式初始化:
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0
这个版本的游戏使用 while 循环和 switch 语句来实现游戏的逻辑。 while 循环有一个标签名 gameLoop ,来表明它是游戏的主循
环。该 while 循环体的条件判断语句是 while square !=finalSquare ,这表明你必须刚好落在方格 25 中。
gameLoop: while square != finalSquare {
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
switch square + diceRoll {
case finalSquare:
// 骰子数刚好使玩家移动到最终的方格里,游戏结束。
break gameLoop
case let newSquare where newSquare > finalSquare:
// 骰⼦数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰⼦
continue gameLoop
default:
// 合法移动,做正常的处理
square += diceRoll
square += board[square]
}
}
print("Game over!")
每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这⾥使⽤了 switch 语句来考虑每次移动可能产⽣的结果,
从而决定玩家本次是否能够移动。
如果骰子数刚好使玩家移动到最终的方格里,游戏结束。 break gameLoop 语句跳转控制去执⾏ while 循环体后的第一⾏代码,
意味着游戏结束。 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。 continue
gameLoop 语句结束本次 while 循环,开始下一次循环。 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动
diceRoll 个方格,然后游戏逻辑再处理 家当前是否处于蛇头或者梯子的底部。接着本次循环结束,控制跳转到 while 循环体的条
件判断语句处,再决定是否需要继续执行下次循环。
注意
如果上述的 break 语句没有使用 gameLoop 标签,那么它将会中断 switch 语句⽽不是 while 循环。使⽤ gameLoop 标签清晰
的表明了 break 想要中断的是哪个代码块。
同时请注意,当调用 continue gameLoop 去跳转到下一次循环迭代时,这⾥使⽤ gameLoop 标签并不是严格必须的。因为在这
个游戏中,只有一个循环体,所以 continue 语句会影响到哪个循环体是没有歧义的。然而,continue 语句使⽤ gameLoop 标
签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的 break gameLoop ,能够使游戏的逻辑更加清晰和易于理
解。
提前退出
像 if 语句⼀样, guard 的执⾏取决于一个表达式的布尔值。我们可以使用 guard 语句来要求条件必须为真时,以执⾏ guard 语
句后的代码。不同于 if 语句,一个 guard 语句总是有一个 else 从句,如果条件不为真则执⾏ else 从句中的代码。
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).")
}
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() 。
相比于可以实现同样功能的 if 语句,按需使用 guard 语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将
它包在 else 块中,它可以使你在紧邻条件判断的地方,处理违规的情况。
检测 API 可⽤用性
Swift 内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不⼩心地使用了不可用的 API。
编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个
不可⽤的 API,Swift 会在编译时报错。
我们在 if 或 guard 语句中使用可用性条件(availability condition) 去有条件的执⾏一段代码,来在运⾏时判断调用的 API 是否可
用。编译器使用从可用性条件语句中获取的信息去验证,在这个代码块中调用的 API 是否可用。
if #available(iOS 10, macOS 10.12, *) {
// 在 iOS 使用 iOS10 的 API, 在 macOS 使用 macOS10.12 的 API
} else {
// 使用先前版本的 iOS 和 macOS 的 API
}
以上可用性条件指定, if 语句的代码块仅仅在 iOS 10 或 macOS 10.12 及更高版本才运⾏。最后一个参数, * ,是必须的,⽤于
指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if 语句的代码块将会运行。
在它一般的形式中,可用性条件使⽤了一个平台名字和版本的列表。平台名字可以是 iOS , macOS , watchOS 和tvOS ——请
访问声明属性来获取完整列表。除了指定像 iOS 8 或 macOS 10.10 的大版本号,也可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的
小版本号。
if #available(平台名称 版本号, ..., *) {
APIs 可⽤,语句将执⾏
} else {
APIs 不可用,语句将执⾏的回退语句
}