Swfit - 控制流


前言

1 - Swift 提供了多种流程控制结构:包括可以多次执行任务的 while 循环;基于特定条件选择执行不同代码分支的 if、guard 和 switch

2 - Swift 中的 switch 语句比 C语言 加强大

① 它可以匹配很多不同的模式,包括范围匹配、元组和特定类型匹配

② 还可以声明为临时常量或变量在 case 作用域内使用

③ 也可以配合 where 来描述更复杂的匹配条件

For-In

1 - 遍历数组、字典、范围区间

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

// 字典
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
    
    print("\(animalName)s have \(legCount) legs")
}
// 范围
for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}

2 - 如果不需要区间序列内每一项的值,可以使用下划线 _ 替代变量名来忽略这个值。下划线符号能够忽略当前值,并且不提供循环遍历时对值的访问

let base = 3
let power = 4
var answer = 1
// 计算 base 这个数的 power 次幂
for _ in 1...power {
    
    answer = answer * base
}
print(answer)// 81

3 - stride(from:to:by:)  | stride(from:through:by:) 两个函数可跳过不需要的标记,前者是开区间,后者是闭区间

let minutes = 60
let minuteInterval = 15
// 15 为间隔遍历
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    print(tickMark)
}
/* 遍历结果
 * 0
 * 15
 * 30
 * 45
 */

While | repeat ... while

1 - Swift 提供两种 while 循环形式:while 和 repeat ... while(同 do ... while)

2 - 代码示例:制作一款游戏

需求如下

    游戏盘面包括 25 个方格,游戏目标是达到或者超过第 25 个方格

    每一轮,你通过掷一个六面体骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示

    如果在某轮结束,你移动到了梯子的底部,可以顺着梯子爬上去

    如果在某轮结束,你移动到了蛇的头部,你会顺着蛇的身体滑下去

   

② 思路

    游戏盘面可以使用一个 Int 数组来表达。数组的长度由一个 finalSquare 常量储存,用来初始化数组和检测最终胜利条件

    梯子底部的方格是一个正值,使你可以向上移动;蛇头处的方格是一个负值,会让你向下移动

 1 // 方式一:使用 while
 2 let finalSquare = 25
 3 var board = [Int](repeating: 0, count: finalSquare + 1)// 游戏盘面由 26 个 Int 0 值初始化
 4 // 前进、后退
 5 board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
 6 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
 7 
 8 var diceRoll = 0// 掷甩子
 9 var square = 0  // 移动步数
10 
11 // 没达到顶格
12 while square < finalSquare {
13     // 掷骰子
14     diceRoll += 1
15 
16     // 当 diceRoll 的值等于 7 时,就超过了骰子的最大值,重置为 1
17     if diceRoll == 7{
18 
19         diceRoll = 1
20     }
21     // 根据点数移动
22     square  = square + diceRoll
23 
24     // 如果玩家还在棋盘上
25     if square < board.count {
26         // 顺着梯子爬上去或者顺着蛇滑下去
27         square += board[square]
28     }
29 }
30 print("Game over!")
31 
32 
33 // 方式二:使用 Repeat-While
34 let finalSquare02 = 25
35 var board02 = [Int](repeating: 0, count: finalSquare02 + 1)
36 board02[03] = +08; board02[06] = +11; board02[09] = +09; board02[10] = +02
37 board02[14] = -10; board02[19] = -11; board02[22] = -02; board02[24] = -08
38 var square02 = 0
39 var diceRoll02 = 0
40 repeat {
41     // 顺着梯子爬上去或者顺着蛇滑下去
42     square02 += board02[square]
43     // 掷骰子
44     diceRoll02 += 1
45     if diceRoll02 == 7 { diceRoll02 = 1 }
46     // 根据点数移动
47     square02 += diceRoll02
48 } while square02 < finalSquare02
49 print("Game over!")

条件语句

1 - if、if ... else、if ... else if ....

2 - switch

① 为了匹配某些更特定的值,Swift 提供了几种方法来进行更复杂的模式匹配

 1 let someCharacter: Character = "z"
 2 switch someCharacter {
 3     
 4 case "a":
 5     print("The first letter of the alphabet")
 6 
 7 case "z":
 8     // 在 Swift 中不需要在 case 分支中显式地使用 break 语句
 9     // 当匹配的 case 分支中的代码执行完毕后,程序会终止该分支
10     print("The last letter of the alphabet") // 执行此分支
11     // 虽然 break 不是必须的,但你依然可以使用
12     break
13 
14 // 每一个 case 分支都必须包含至少一条语句,如下使用方式报错
15 // case "k": // 无效:这个分支下面没有语句
16 
17 // 为了让单个 case 同时匹配多个值,可以将这些值组合成一个复合匹配,用逗号分开
18 case "m","n":
19      print("The letter A")
20 default:
21     print("Some other character")
22 }

② 区间匹配

// 区间匹配
let approximateCount = 62
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"
}

③ 使用元组在同一个 switch 语句中测试多个值

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")
}

④ 值绑定:case 分支允许将匹配的值声明为临时常量或变量,并且在 case 分支体内使用

let anotherPoint = (2, 0)
switch anotherPoint {
// 常量 x 和 y 的占位符,用于临时获取元组 anotherPoint 的值
// (let x, 0) 将匹配一个纵坐标为 0 的点,并把这个点的横坐标赋给临时的常量 x。以下同理
// 一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里使用
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("at (\(x), \(y))")
}

⑤ 使用 where 语句来判断额外的条件

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")
}

⑥ 复合型 Cases

// 复合型 Cases
// 当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个 case 后面,并且用逗号隔开
let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
    print("\(someCharacter) is a vowel")// 执行该分支
case "b", "c", "d", "f", "g":
    print("\(someCharacter) is a consonant")
default:
    print("\(someCharacter) is not a vowel or a consonant")
}

// 复合匹配同样可以包含值绑定:绑定的值类型必须相同
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")
}

控制转移语句

1 - 改变代码执行顺序,可以实现代码的跳转

2 - 有五种控制转移语句

① continue :本次循环我已经执行完

② break:束整个控制流的执行

③ fallthrough :贯穿

④ return

⑤ throw

// 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.

标签名

1 - 可以使用标签(statement label)来标记一个循环体或者条件语句

2 - 我们学习 while 时写了一个小游戏,现在改变规则:为了获胜,你必须刚好落在第 25 个方块中,就是说如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中

 1 // finalSquare、board、square 和 diceRoll 值被和之前一样的方式初始化
 2 let finalSquare = 25
 3 var board = [Int](repeating: 0, count: finalSquare + 1)
 4 board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
 5 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
 6 var square = 0
 7 var diceRoll = 0
 8 
 9 // 标签名 gameLoop
10 gameLoop: while square != finalSquare {
11     
12     diceRoll += 1
13     if diceRoll == 7 { diceRoll = 1 }
14     
15     switch square + diceRoll {
16     case finalSquare:
17         // 骰子数刚好使玩家移动到最终的方格里,游戏结束。
18         break gameLoop // 如果 break 语句没有使用 gameLoop 标签,那么它将会中断 switch 语句而不是 while 循环
19         
20     case let newSquare where newSquare > finalSquare:
21         // 骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子
22         continue gameLoop
23         
24     default:
25         // 合法移动,做正常的处理
26         square += diceRoll
27         square += board[square]
28     }
29 }
30 print("Game over!")

guard

1 - guard 的执行取决于一个表达式的布尔值,不同于 if 语句,一个 guard 语句总是有一个 else 从句。相比于可以实现同样功能的 if 语句,按需使用 guard 语句会提升我们代码的可读性。值得注意的是 else 分支必须转移控制以退出 guard 语句出现的代码段,如使用  return、break、continue 或者 throw

 1 func greet(person: [String: String]) {
 2     guard let name = person["name"] else {
 3         return
 4     }
 5 
 6     print("Hello \(name)!")
 7 
 8     guard let location = person["location"] else {
 9         print("I hope the weather is nice near you.")
10         return
11     }
12 
13     print("I hope the weather is nice in \(location).")
14 }
15 
16 greet(person: ["name": "John"])
17 // Hello John!
18 // I hope the weather is nice near you.
19 
20 greet(person: ["name": "Jane", "location": "Cupertino"])
21 // Hello Jane!
22 // I hope the weather is nice in Cupertino.

检测 API 可用性

1 - Swift 支持检查 API 可用

// 最后一个参数,* 是必须的,用于指定在所有其它平台中
if #available(iOS 10, macOS 10.12, *) {
    // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
    // 使用先前版本的 iOS 和 macOS 的 API
}