天天看点

Swift编程六(控制流)控制流

案例代码下载

控制流

Swift提供了各种控制流程语句。这些包括多次执行任务的while循环; if,guard和switch基于特定条件执行不同代码分支的语句; 和如break和continue对执行流在代码中转移到另一个点的语句。

Swift还提供了for- in循环,可以很容易地遍历数组,字典,范围,字符串和其它序列。

Swift的switch声明比许多类C语言中的声明强大得多。案例可以匹配许多不同的模式,包括区间匹配,元组和特定类型的强制转换。switch案例中的匹配值可以绑定到临时常量或变量以在案例主体中使用,复杂匹配条件可以为每个案例添加用where子句表示。

for-in循环

使用for- in循环迭代序列,例如数组中的项,数字范围或字符串中的字符。

此示例使用for- in循环迭代数组中的项:

let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("Hello, \(name)!")
}

/*
打印结果:

Hello, Anna!
Hello, Alex!
Hello, Brian!
Hello, Jack!
*/
           

还可以迭代字典以访问其键值对。字典进行迭代时,在字典中的每个项目返回一个元组(key, value),并且可以分解的元组的成员为明确的命名常数在for-in循环主体内使用。在下面的代码示例中,字典的键被分解为一个animalName常量,并且字典的值被分解为一个legCount常量。

let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
    print("\(animalName)s have \(legCount) legs")
}

/*
打印结果:

ants have 6 legs
spiders have 8 legs
cats have 4 legs
*/
           

Dictionary的内容本质上是无序的,并且迭代它们并不能保证它们的检索顺序。特别是,将项目插入到Dictionary中的顺序不会定义它们被迭代的顺序。有关数组和字典的更多信息,请参阅集合类型。

还可以使用for-in实现具有数字范围的循环。此示例打印五次表中的前几个条目:

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}

/*
打印结果:

1 times 5 is 5
2 times 5 is 10
3 times 5 is 15
4 times 5 is 20
5 times 5 is 25
*/
           

正如通过使用闭区间运算符(…)所指示的那样,迭代的序列是从1到的数字5范围。index的值设置为范围中的第一个数字(1),然后执行循环内的语句。在这种情况下,循环只包含一个语句,该语句从五次表中输出当前条目index的值。执行语句后,index更新值以包含范围中的第二个值(2),并再次调用print(_:separator:terminator:)函数。此过程一直持续到达范围结束。

在上面的示例中,index是一个常量,其值在循环的每次迭代开始时自动设置。因此,index在使用之前不必声明。它只是通过包含在循环声明中而隐式声明,而不需要let声明关键字。

如果不需要序列中的每个值,则可以使用下划线代替变量名来忽略这些值。

let base = 3
let power = 10
var answer = 1
for _ in 1...power {
    answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
           

上面的例子计算一个数字与另一个数字的幂(在这种情况下,计算3的10次方)。它使用以1开头和10结尾的闭合范围将起始值1(即,与其3幂0)相乘3。对于此计算,每次循环时各个计数值都是不必要的 - 代码只是执行循环正确的次数。用于代替循环变量的下划线字符(_)会导致单个值被忽略,并且在循环的每次迭代期间不提供对当前值的访问。

在某些情况下,可能不希望使用包含两个端点的闭合范围。考虑在表盘上绘制每分钟的刻度线。想绘制60刻度线,从0分钟开始。使用半开范围运算符(…<)包括下限但不包括上限。有关范围的更多信息,请参阅范围运算符。

let minutes = 60
for tickMark in 0..<minutes {
    // 每分钟标记一次(60次)
}
           

某些用户可能希望在其UI中使用更少的刻度线。他们可能更喜欢每5分钟一个标记。使用此stride(from:to:by:)功能可跳过不需要的标记。

let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    // 每5分钟标记一次 (0, 5, 10, 15 ... 45, 50, 55)
}
           

也可以使用封闭范围stride(from:through:by:):

let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
    // 每3小时标记一次 (3, 6, 9, 12)
}
           

While循环

while循环执行一组语句,直到条件变为false。当在第一次迭代开始之前未知迭代次数时,最好使用这些类型的循环。Swift提供了两种while循环:

  • while 在每次循环开始时计算其条件。
  • repeat-while在每次循环结束时计算其条件。

While

一个while循环通过计算一个条件开始。如果条件是true,则重复一组语句,直到条件变为false。

这是while循环的一般形式:

while condition {
    statements
}
           

这个例子玩一个简单的蛇和梯子游戏(也称为滑道和梯子):

Swift编程六(控制流)控制流

游戏规则如下:

  • 该板有25个正方形,目标是在25号方格或更远的地方着陆。
  • 玩家的起始方格是“方形零”,它位于棋盘的左下角。
  • 每滚动一个六面骰子,按照上面虚线箭头所示的水平路径移动那个数量的方块。
  • 如果终点在梯子的底部,向上移动那个梯子顶部。
  • 如果终点在蛇的头部,就会向下移动那条蛇的尾部。

游戏板由一系列Int值表示。它的大小基于一个被叫做finalSquare的常量,用于初始化数组,并在示例后面检查获胜条件。因为玩家从“方形零”开始,所以面板初始化为26个Int值0,而不是25个。

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
           

方格3包含梯子底座要移动到方格11.要表示这样的阶梯的底部,board[03]等于+08,这相当于一个整数值8(3和11之间的差)。为了对齐值和语句,一元加运算符(+i)明确地与一元减运算符(-i)一起使用,并且低于10的数字用零填充。(技术风格都不是必需的,但它们会导致更简洁的代码。)

var square = 0
var diceRoll = 0
while square < finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    square += diceRoll
    if square < board.count {
        square += board[square]
    }
}
print("Game over!")
           

上面的例子使用一种非常简单的方法来进行骰子滚动。它不是生成随机数,而是以diceRoll值为0开头。每次while循环时,diceRoll都会增加1,然后检查它是否变得太大。每当此返回值等于时7,骰子滚动变得太大并重置为值1。diceRoll的结果是一个序列,值始终是1,2,3,4,5,6,1,2等。

掷骰子后,玩家向前移动diceRoll。掷骰子可能已经将玩家移动到25以上,在这种情况下游戏结束了。为了应对这种情况,代码检查square释放小于board数组的count属性。如果square有效,则将存储的值board[square]添加到当前square值,以使玩家向上或向下移动。

注意

如果未执行此检查,board[square]可能会尝试访问board数组边界之外的值,这将触发运行时错误。

然后,当前while循环执行结束,并检查循环的条件以查看是否应该再次执行循环。如果玩家已经移动到或超过方格25,则循环的条件评估为false并且游戏结束。

在这种情况下while循环是适当的,因为游戏的长度在开始不是明确的。相反,执行循环直到满足特定条件。

Repeat-While

while循环的另一个变体(称为repeat-while循环)在考虑循环条件之前首先执行单个循环通过循环块。然后它继续重复循环,直到条件为止false。

注意

Swift的repeat-while循环类似于其他语言的do-while循环。

这里有一个的一般形式repeat- while循环:

repeat {
    statements
} while condition
           

这里再次是蛇和梯子示例,写为repeat-while循环而不是while循环。finalSquare,board,square,和diceRoll的值以和while循环中完全相同的方式初始化。

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
           

在这个版本的游戏中,循环中的第一个动作是检查梯子或蛇。棋盘上没有任何梯子将玩家直接拉到25方,所以不可能通过向上移动梯子来赢得比赛。因此,检查蛇或梯子作为循环中的第一个动作是安全的。

在游戏开始时,玩家处于“方形零”。board[0]总是等于0并且没有效果。

条件语句

基于某些条件执行不同的代码段通常很有用。可能希望在发生错误时运行额外的代码,或者在值变得太高或太低时显示消息。为此,可以使代码的一部分作为条件。

Swift提供了两种方法来为代码添加条件分支:if语句和switch语句。通常,使用该if语句来评估只有少数可能结果的简单条件。switch语句更适合具有多种可能排列的更复杂条件,并且模式匹配在帮助选择要执行的适当代码分支的情况下非常有用。

If

在最简单的形式中,if语句只有一个if条件。它仅在该条件为true时才执行一组语句。

var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
}

/*
打印结果:

It's very cold. Consider wearing a scarf.
*/
           

上面的例子检查温度是否小于或等于32华氏度(水的冰点)。如果是,则打印一条消息。否则,不会打印任何消息,并且在if语句的右括号后继续执行代码。

if语句对于条件false为的情况,if语句可以提供另一组语句,称为else子句。这些语句由关键字else指示。

temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else {
    print("It's not that cold. Wear a t-shirt.")
}

/*
打印结果:

It's not that cold. Wear a t-shirt.
*/
           

总是执行这两个分支中的一个。因为温度升高到40华氏度,所以不再冷到建议戴围巾,因此else分支被触发。

可以将多个if语句链接在一起以考虑其他子句。

temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
    print("It's really warm. Don't forget to wear sunscreen.")
} else {
    print("It's not that cold. Wear a t-shirt.")
}

/*
打印结果:

It's really warm. Don't forget to wear sunscreen.
*/
           

在这里,增加了一个if语句来应对特别温暖的气温。最后一个else条款仍然存在,并且它会针对任何既不太温暖也不太冷的温度打印响应。

但是,最后一个else子句是可选的,如果不需要完成条件集,则可以不要该子句。

temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
    print("It's really warm. Don't forget to wear sunscreen.")
}
           

由于温度既不太冷也不太热,不能触发if或else if条件,因此不会打印信息。

Switch

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还为每种情况提供了几种方法来指定更复杂的匹配模式。这些选项将在本章后面介绍。

与if语句的主体一样,每个语句case都是代码执行的独立分支。switch语句确定应选择哪个分支。此过程称为切换正在考虑的值。

每个switch声明都必须详尽无遗。也就是说,所考虑类型的每个可能值必须与其中一个switch案例相匹配。如果为每个可能的值提供案例是不合适的,则可以定义默认案例以涵盖未明确解决的任何值。此默认情况由default关键字指示,并且必须始终显示在最后。

let someCharacter: Character = "z"
switch someCharacter {
case "a":
    print("The first letter of the alphabet")
case "z":
    print("The last letter of the alphabet")
default:
    print("Some other character")
}

/*
打印结果:

The last letter of the alphabet
*/
           

switch陈述的第一个案例与英文字母的第一个字母a相匹配,其第二个案例与最后一个字母z相匹配。因为switch必须具有每个可能字符,而不仅仅是每个字母字符,所以此switch语句使用default来匹配除a和z之外的所有字符。该条款确保switch声明是详尽无遗的。

没有隐含的匹配后面情况

switchC和Objective-C中的switch语句相比,Swift中的语句不会在每个案例的底部落入默认情况下的下一个。相反,switch只要第一个匹配的switch情况完成,整个语句就会完成执行,而不需要显式break语句。这使得switch语句比C中的语句更安全,更容易使用,并且避免switch错误地执行多个案例。

注意

尽管break在Swift中不需要,但可以使用break语句来匹配和忽略特定情况,或者在该情况完成执行之前中断匹配的情况。有关详细信息,请参阅Switch语句中的Break。

每个案例的主体必须包含至少一个可执行语句。编写以下代码无效,因为第一种情况是空的:

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": 
case "A":
    print("The letter A")
default:
    print("Not the letter A")
}
           

不像C的switch语句,该switch语句不会匹配"a"和"A"。相反,它报告一个case “a”:不包含任何可执行语句的编译时错误。这种方法避免了从一个案例到另一个案例的意外泄漏,并使得代码更安全更加清晰。

为了使switch与匹配"a"和"A"二者的之一的情况,这两个值组合成的情况下,用逗号分隔的值。

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
    print("The letter A")
default:
    print("Not the letter A")
}

/*
打印结果:

The letter A
*/
           

为了便于阅读,复合案例也可以通过多行编写。有关复合案例的更多信息,请参阅复合案例。

注意

要在特定switch情况的最后明确地落空,请使用fallthrough关键字,如Fallthrough中所述。

区间匹配

switch可以检查案例中的值是否包含在间隔中。此示例使用数字间隔为任何大小的数字提供自然```计数:

let approximateCount = 62
let countedThings = "moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
    naturalCount = "no"
case 1..<5:
    naturalCount = "a few"
case 5..<12:
    naturalCount = "several"
case 12..<100:
    naturalCount = "dozens of"
case 100..<1000:
    naturalCount = "hundreds of"
default:
    naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")

/*
打印结果:

There are dozens of moons orbiting Saturn.
*/
           

在上面的示例中,approximateCount将在switch语句中进行评估。每个case都将该值与数字或间隔进行比较。因为approximateCount值介于12和100之间,所以naturalCount会赋"dozens of"值,并且执行将从switch语句中转移出来。

元组

可以使用元组在同一switch语句中测试多个值。可以针对不同的值或值的间隔来测试元组的每个元素。或者,使用下划线(_)(也称为通配符模式)来匹配任何可能的值。

下面的示例采用(x,y)点,表示为(Int, Int)类型的简单元组,并将其描绘在示例后面的图表上。

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("\(somePoint) is at the origin")
case (_, 0):
    print("\(somePoint) is on the x-axis")
case (0, _):
    print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
    print("\(somePoint) is inside the box")
default:
    print("\(somePoint) is outside of the box")
}

/*
打印结果:

(1, 1) is inside the box
*/
           
Swift编程六(控制流)控制流

该switch语句确定该点是在原点(0,0),红色x轴,橙色y轴上,在原点中心的蓝色4×4框内,还是在框的外部。

与C不同,Swift允许多个switch案例考虑相同的值或值域。实际上,点(0,0)可以匹配此示例中的所有四种情况。但是,如果可以进行多次匹配,则始终使用第一个匹配写。点(0,0)将首先匹配case (0, 0),因此将忽略所有其他匹配的情况。

值绑定

一个switch情况下,可以将其命名为临时常量或变量,情况下使用相匹配的一个或多个值。此行为称为值绑定,因为值绑定到案例正文中的临时常量或变量。

下面的示例采用(x,y)点,表示为(Int, Int)类型的元组,并将其描绘在下面的图表上:

let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}

/*
打印结果:

on the x-axis with an x value of 2
*/
           
Swift编程六(控制流)控制流

该switch语句确定该点是在红色x轴上,橙色y轴上还是其他位置)。

这三种switch情况下,声明占位符常量x和y,暂时采取在一个或两个元组值的anotherPoint。第一种情况case (let x, 0),匹配任何y具有值0的点,并将该点的x值分配给临时常量x。类似地,第二种情况case (0, let y)匹配x具有值0的任何点,并将该点的y值分配给临时常量y。

声明临时常量后,可以在案例的代码块中使用它们。在这里,它们用于打印点的分类。

本switch声明没有default案例。最后一种情况case let (x, y),声明了一个可以匹配任何值的两个占位符常量的元组。因为anotherPoint总是两个值的元组,所以这个案例匹配所有可能的剩余值,并且不需要一个default案例来使switch语句详尽无遗。

Where

一个switch情况下可以使用where子句来检查附加条件。

以下示例对下图中的(x,y)点进行了分类:

let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
    print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    print("(\(x), \(y)) is just some arbitrary point")
}

/*
打印结果:

(1, -1) is on the line x == -y
*/
           
Swift编程六(控制流)控制流

该switch语句确定该点是否在绿色对角线x == y上,在紫色对角线x == -y上,或者两者都没有。

这三种switch情况下,声明占位符常量x和y,暂时采取从yetAnotherPoint的两元组值。这些常量用作where子句的一部分,以创建动态过滤器。仅当switch语句的where子句的条件为true时,该案例才匹配point当前值。

与前面的示例一样,最终匹配所有可能的剩余值,因此不需要一个default案例来使switch语句详尽无遗。

复合案例

共享相同主体的多个Switch案例可以通过在每个模式之间用逗号分隔写几个模式来组合。如果任何模式匹配,则认为该情况匹配。如果列表很长,则可以在多行上写入模式。例如:

let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
    print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
     "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
    print("\(someCharacter) is a consonant")
default:
    print("\(someCharacter) is not a vowel or a consonant")
}

/*
打印结果:

e is a vowel
*/
           

该switch声明的第一个案例与英语中的所有五个小写元音相匹配。同样,它的第二个案例匹配所有小写英语辅音。最后,default案例与任何其他角色匹配。

复合案例还可以包括值绑定。复合案例的所有模式都必须包含同一组值绑定,并且每个绑定必须从复合案例中的所有模式中获取相同类型的值。这确保了,无论复合案例的哪个部分匹配,案例正文中的代码总是可以访问绑定的值,并且值始终具有相同的类型。

let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
    print("On an axis, \(distance) from the origin")
default:
    print("Not on an axis")
}

/*
打印结果:

On an axis, 9 from the origin
*/
           

在上面有两个模式:在x轴的匹配点(let distance, 0)和在y轴的匹配点(0, let distance)。这两种模式都包含一个绑定distanced,并且在两种模式中都是一个整数 - 这意味着该主体中的代码总是可以访问一个distance的值。

控制转移语句

控制转移语句通过将控制从一段代码转移到另一段代码来改变代码执行的顺序。Swift有五个控制转移语句:

  • continue
  • break
  • fallthrough
  • return
  • throw

continue,break和fallthrough语句描述如下。函数中描述了return语句,并且在使用Throwing函数传播错误中描述了throw语句。

Continue

continue语句告诉循环停止它正在执行的,并开始循环的下一次迭代启动。意思是“完成了当前的循环迭代”而没有完全离开循环。

以下示例从小写字符串中删除所有元音和空格,以创建一个神秘的拼图短语:

let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]
for character in puzzleInput {
    if charactersToRemove.contains(character) {
        continue
    }
    puzzleOutput.append(character)
}
print(puzzleOutput)

/*
打印结果:

grtmndsthnklk
*/
           

上面的代码在匹配元音或空格时调用continue关键字,导致循环的当前迭代立即结束并直接跳转到下一次迭代的开始。

Break

break语句立即结束整个控制流语句的执行。当想要比其他情况更早地终止语句的执行时,break可以在一个switch或循环语句中使用。

break循环

在循环语句中使用break时,立即结束循环的执行,并在循环的右括号(})之后将控制权转移给代码。不执行来自当前循环迭代的进一步代码,并且不再开始循环的迭代。

break Switch语句

在switch语句中使用break时,会使switch语句立即结束其执行,并在switch语句的右括号(})后将控制权转移给代码。

此行为可用于匹配和忽略switch语句中的一个或多个案例。由于Swift的switch陈述是详尽的,并且不允许空案件,因此有时需要故意匹配并忽略案例,以使意图明确。可以通过将break语句写为要忽略的案例的整个主体来完成此操作。当该情况与switch语句匹配时,案例内的break语句立即结束switch语句的执行。

注意

一个switch仅包含comment的情况下被报告为编译时错误。comment不是陈述,也不会导致switch案例被忽略。始终使用break语句来忽略switch案例。

以下示例打开一个Character值,并确定它是否表示四种语言之一的数字符号。为简洁起见,单个switch案例涵盖了多个值。

let numberSymbol: Character = "三"
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变量具有隐式初始值nil,因此只有possibleIntegerValue在switch语句的前四种情况之一被设置为实际值时,可选绑定才会成功。

因为在上面的示例中列出每个可能的Character值是不切实际的,所以一个default案例处理任何不匹配的字符。这种default情况不需要执行任何操作,因此它使用单个break语句作为其主体。只要default案例匹配,break语句就会结束switch语句的执行,并从if let语句继续执行代码。

Fallthrough

在Swift中,switch语句不会落入每个案例的底部并进入下一个案例。也就是说,switch一旦第一个匹配的案例完成,整个语句就完成了它的执行。相反,C要求break在每个switch案例的末尾插入一个明确的语句,以防止通过。避免默认的下降意味着Swift switch语句比C中的对应语句更简洁和可预测,因此它们避免switch错误地执行多个案例。

如果需要C样式的直通行为,则可以使用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.
*/
           

此示例声明一个名为description的新String变量,并为其分配一个初始值。然后使用switch语句考虑integerToDescribe的值。如果integerToDescribe值是列表中的素数之一,则将文本附加到description末尾,以指示该数字是素数。然后它使用fallthrough关键字“落入” default案例。default案例在描述的末尾添加了一些额外的文本,并且switch语句已完成。

除非integerToDescribe值在已知素数列表中,否则它与第一种switch情况完全不匹配。因为没有其他特定情况,所以integerToDescribe匹配default情况。

在之后switch的语句执行完毕,数的描述是使用print(_:separator:terminator:)函数打印。在此示例中,数字5被正确识别为素数。

注意

fallthrough关键字不检查使switch执行陷入此情况的条件。fallthrough关键字简单地使代码执行直接移动到下一个的情况下(或内的default语句的情况下)嵌段,如C的标准switch语句的行为。

标签语句

在Swift中,可以在其他循环和条件语句中嵌套循环和条件语句,以创建复杂的控制流结构。但是,循环和条件语句都可以使用break语句过早地结束执行。因此,有时候明确要求break语句终止的循环或条件语句是有用的。类似地,如果有多个嵌套循环,那么明确该continue语句应该影响哪个循环可能很有用。

要实现这些目标,可以使用语句标签标记循环语句或条件语句。使用条件语句,可以使用带标签语句的break语句来结束带标签语句的执行。使用循环语句,可以使用带有标签语句的break、continue语句来结束或继续执行带标签的语句。

标签语句通过在语句相同行上关键字前放置标签名来指示,后跟冒号。这是while循环的这种语法的一个例子,然而所有循环和switch语句的原理是相同的:

label name: while condition {
    statements
}
           

下面的示例使用带有标签循环的break和continue语句,以获得本章前面while所述的蛇和梯子游戏的改编版本。这一次,游戏有一个额外的规则:

要赢,你必须准确降落在25号方格上。

如果一个特定的骰子卷超过25号方格,必须再次滚动,直到你滚动所需的确切数字落在方形25上。

游戏板和以前一样。

Swift编程六(控制流)控制流

值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循环的条件为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!")

/*
打印结果:

Game over!
*/
           

骰子在每个循环开始时滚动。循环使用switch语句来考虑移动的结果并确定是否允许移动,而不是立即移动:

  • 如果骰子将玩家移动到最后的方格,则游戏结束。break gameLoop语句将控制转移到while循环外的第一行代码,结束游戏。
  • 如果骰子将移动玩家超越最后的方格,此举是无效的,玩家需要再次滚动。continue gameLoop语句结束当前循环while迭代并开始循环的下一次迭代。
  • 在所有其他情况下,掷骰子是一个有效的举动。玩家通过diceRoll向前移动,游戏逻辑检查任何蛇和梯子。然后循环结束,控制返回到while条件。

注意

如果上面的break语句没有使用gameLoop标签,将结束switch语句,而不是while语句。使用gameLoop标签可以清楚地终止哪个控制语句。

在调用continue gameLoop跳转到循环的下一次迭代时,不一定要使用gameLoop标签。游戏中只有一个循环,因此对于continue语句将影响哪个循环没有歧义。但是,在语句中使用gameLoop标签没有任何害处。这样做带有标签的continue与break语句一起使用,有助于使游戏的逻辑更清晰,易读和理解。

提前退出

guard语句,像if语句,执行以布尔值表达式为条件的语句。使用guard语句要求条件必须为true才能执行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"])
greet(person: ["name": "Jane", "location": "Cupertino"])

/*
打印结果:

Hello John!
I hope the weather is nice near you.
Hello Jane!
I hope the weather is nice in Cupertino.
*/
           

如果满足guard语句的条件,则guard语句的右括号后继续执行代码。使用可选绑定作为条件的一部分分配值的任何变量或常量都可用于guard语句的其余代码块。

如果不满足该条件,则执行else分支内的代码。该分支必须转移控制以退出guard语句出现的代码块。可以做到转移控制权这一点的语句,如return,break,continue,或者throw,也可以调用一个函数或方法不返回,如fatalError(_:file:line:)。

guard相比if语句进行相同的检查,使用guard语句可以提高代码的可读性。它允许编写通常执行的代码而不将其包装在else块中,并且它允许保留处理违反条件的代码。

检查API可用性

Swift内置支持检查API可用性,这可确保不会意外使用在给定部署目标上不可用的API。

编译器使用SDK中的可用性信息来验证代码中使用的所有API是否在项目指定的部署目标上可用。如果尝试使用不可用的API,Swift会在编译时报告错误。

可以在if或guard语句中使用可用性条件来有条件地执行代码块,具体取决于要使用的API是否在运行时可用。当编译器验证该代码块中的API可用时,编译器将使用可用性条件中的信息。

if #available(iOS 10, macOS 10.12, *) {
    // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
    // Fall back to earlier iOS and macOS APIs
}
           

上面的可用性条件指定在iOS中,if语句的主体仅在iOS 10及更高版本中执行; 在macOS中,仅在macOS 10.12及更高版本中。最后一个参数*是必需的,指定在任何其他平台上,在if的主体执行的最小部署目标被指定目标。

在其一般形式中,可用性条件采用平台名称和版本的列表。可以使用平台的名称,如iOS,macOS,watchOS,和tvOS-对于完整列表,请参阅声明属性。除了指定主要版本号(如iOS 8或macOS 10.10)之外,还可以指定次要版本号,如iOS 11.2.6和macOS 10.13.3。

if #available(platform name version, ..., *) {
    statements to execute if the APIs are available
} else {
    fallback statements to execute if the APIs are unavailable
}