Swift 循环与控制转移
循环让同一段代码重复执行,直到满足退出条件。
Swift 提供 for-in、while、repeat-while 三种循环结构,配合 break、continue 和带标签的跳转语句控制流程。
本文合并了旧版分散的 for-in、while、repeat-while、break、continue 等多篇内容,并统一到 Swift 6 的写法。
for-in 循环
for-in 是 Swift 中最常用的循环,用来遍历区间、数组、字典、字符串等序列。
遍历数字区间
闭区间运算符 ... 包含两端的值,半开区间 ..< 不包含右端。
实例
// 闭区间 1...5 会依次取到 1、2、3、4、5
for i in 1...5 {
print("\(i) 乘以 5 等于 \(i * 5)")
}
以上代码执行输出结果为:
1 乘以 5 等于 5 2 乘以 5 等于 10 3 乘以 5 等于 15 4 乘以 5 等于 20 5 乘以 5 等于 25
半开区间 0..<3 只取到 0、1、2,这种写法常用于配合数组下标。
实例
// 半开区间 0..<3 不包含 3
for i in 0..<3 {
print("runoob-\(i)")
}
以上代码执行输出结果为:
runoob-0 runoob-1 runoob-2
注意:C 语言风格的 for 循环(在 for 后面用分号写初始化、条件和自增三段)已经在 Swift 3 中移除,Swift 中不要再用这种写法。需要按固定步长循环时,用区间加 stride(from:to:by:)。
遍历数组
把数组放在 in 后面,循环变量会依次取到数组的每个元素。
实例
let sites = ["Runoob", "RUNOOB", "www.runoob.com"]
// site 每次拿到一个数组元素
for site in sites {
print(site)
}
以上代码执行输出结果为:
Runoob RUNOOB www.runoob.com
用 enumerated() 同时取下标
调用 enumerated() 会返回「下标 + 元素」的元组,元组可以拆成两个循环变量。
实例
let sites = ["Runoob", "RUNOOB", "www.runoob.com"]
// index 从 0 开始,所以显示时加 1
for (index, site) in sites.enumerated() {
print("\(index + 1). \(site)")
}
以上代码执行输出结果为:
1. Runoob 2. RUNOOB 3. www.runoob.com
遍历字典
字典的元素是「键 + 值」的元组,同样可以拆成两个循环变量。
但字典是无序集合,for-in 的遍历顺序不保证,也不代表插入顺序。
下面的例子先对键排序再遍历,这样输出结果才是确定的。
实例
let ages = ["Runoob": 10, "RUNOOB": 5, "Swift": 11]
// 字典遍历顺序不保证,这里按 key 排序后输出,方便对照
for (key, value) in ages.sorted(by: { $0.key < $1.key }) {
print("\(key): \(value)")
}
以上代码执行输出结果为:
RUNOOB: 5 Runoob: 10 Swift: 11
大写字母的 Unicode 值小于小写字母,所以 RUNOOB 排在 Runoob 前面。
只想遍历键或值,可以用 keys 与 values 两个视图属性。
实例
let ages = ["Runoob": 10, "RUNOOB": 5, "Swift": 11]
print(ages.keys.sorted()) // 键排序后输出
print(ages.values.sorted()) // 值排序后输出
以上代码执行输出结果为:
["RUNOOB", "Runoob", "Swift"] [5, 10, 11]
遍历字符串
字符串由字符组成,可以直接用 for-in 逐个取字符。
实例
for ch in "RUNOOB" {
print(ch, terminator: "-")
}
print()
以上代码执行输出结果为:
R-U-N-O-O-B-
用 where 过滤元素
在 in 后面追加 where 子句,只对满足条件的元素执行循环体。
实例
// 只处理 1 到 10 中能被 3 整除的数
for i in 1...10 where i % 3 == 0 {
print(i)
}
以上代码执行输出结果为:
3 6 9
stride(from:to:by:)
需要按固定步长循环时用 stride,它可以用整数,也可以用浮点数。
to 表示不包含终点,through 表示包含终点,步长可以为负数。
实例
// 从 0 到 10,步长 2,不包含 10
for i in stride(from: 0, to: 10, by: 2) {
print(i, terminator: " ")
}
print()
// 从 10 到 0,步长 -2,包含 0
for i in stride(from: 10, through: 0, by: -2) {
print(i, terminator: " ")
}
print()
// 步长也可以是小数
for ratio in stride(from: 0.0, to: 1.0, by: 0.25) {
print(ratio, terminator: " ")
}
print()
以上代码执行输出结果为:
0 2 4 6 8 10 8 6 4 2 0 0.0 0.25 0.5 0.75
while 循环
while 在每轮循环开始前判断条件,条件为 true 才执行循环体。
条件必须是一个 Bool 值,Swift 不允许把整数、字符串或可选值当作条件。
实例
var count = 1
// 只要 count 小于等于 3 就继续循环
while count <= 3 {
print("Runoob \(count)")
count += 1 // 用 += 1 递增,Swift 没有 ++ 运算符
}
以上代码执行输出结果为:
Runoob 1 Runoob 2 Runoob 3
注意:Swift 中没有 ++ 和 -- 运算符,自增自减请写成 += 1 与 -= 1。
repeat-while 循环
repeat-while 先执行一次循环体,再在末尾判断条件。
因此循环体至少会执行一次,这适合「至少要做一次,再决定是否继续」的场景。
实例
var n = 10
// 先执行一次循环体,再判断 n < 10 是否成立
repeat {
print("至少执行一次,n = \(n)")
n += 1
} while n < 10
以上代码执行输出结果为:
至少执行一次,n = 10
如果换成 while n < 10,因为条件一开始就是假,循环体一次都不会执行。
break 与 continue
break 立刻结束整个循环,continue 只结束本轮迭代,直接进入下一轮。
实例
for i in 1...10 {
// 奇数直接跳过本轮
if i % 2 != 0 {
continue
}
// 超过 6 就结束整个循环
if i > 6 {
break
}
print(i)
}
以上代码执行输出结果为:
2 4 6
带标签的 break 与 continue
嵌套循环中,单独的 break 只能跳出最内层循环。
给外层循环起一个标签,就能用 break 标签 一次跳出多层。
break 标签
下面的例子在 i * j 大于 4 时直接结束整个外层循环。
实例
// outer 是给外层 for 起的标签
outer: for i in 1...3 {
for j in 1...3 {
if i * j > 4 {
print("break outer 于 \(i) x \(j)")
break outer // 一次跳出两层循环
}
print("\(i) x \(j) = \(i * j)")
}
}
以上代码执行输出结果为:
1 x 1 = 1 1 x 2 = 2 1 x 3 = 3 2 x 1 = 2 2 x 2 = 4 break outer 于 2 x 3
continue 标签
continue 标签 会跳过本轮外层循环的剩余内容,直接开始外层循环的下一轮。
实例
// row 是给外层 for 起的标签
row: for i in 1...3 {
for j in 1...3 {
if j == 2 {
continue row // 跳过内层剩余内容,直接进入外层下一轮
}
print("\(i)-\(j)", terminator: " ")
}
}
print()
以上代码执行输出结果为:
1-1 2-1 3-1
三种循环的对比
选择哪种循环,主要看「循环次数是否已知」以及「是否需要先执行一次」。
| 循环 | 适用场景 | 特点 |
|---|---|---|
| for-in | 遍历区间、数组、字典、字符串等序列 | 次数通常已知,写法最简洁 |
| while | 次数未知,靠条件决定是否继续 | 先判断条件,可能一次都不执行 |
| repeat-while | 至少要执行一次,再判断是否继续 | 后判断条件,循环体至少执行一次 |
| stride | 按固定步长遍历数字 | 步长可为负数,也支持浮点数 |
常见问题
下面是循环与控制转移中最常被问到的几个问题。
为什么不能写 C 风格的 for 循环
C 风格的三段式 for 已在 Swift 3 中移除,编译器会直接报错。
改用 for i in 0..<n 或 stride(from:to:by:),语义更清晰,也不容易写出下标越界。
字典遍历为什么每次顺序不一样
字典是无序集合,Swift 不承诺任何遍历顺序,这是设计如此,不是 bug。
需要固定顺序时,先对 keys 排序,或者遍历 sorted(by:) 的结果。
循环变量能在循环体内修改吗
不能,for-in 的循环变量在每轮开始时就被赋值,声明为常量。
需要边循环边改的值,请在循环外单独声明一个 var。
while 的条件为什么不能写 0 或可选值
Swift 的条件必须是 Bool 类型,整数 0、字符串、可选值都不能当条件用。
判断可选值是否有内容,请用 if let 或与 nil 比较。
