现在位置: 首页 > Swift 教程 > 正文

Swift 循环与控制转移

循环让同一段代码重复执行,直到满足退出条件。

Swift 提供 for-in、while、repeat-while 三种循环结构,配合 break、continue 和带标签的跳转语句控制流程。

本文合并了旧版分散的 for-in、while、repeat-while、break、continue 等多篇内容,并统一到 Swift 6 的写法。


for-in 循环

for-in 是 Swift 中最常用的循环,用来遍历区间、数组、字典、字符串等序列。

遍历数字区间

闭区间运算符 ... 包含两端的值,半开区间 ..< 不包含右端。

实例

import Foundation

// 闭区间 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,这种写法常用于配合数组下标。

实例

import Foundation

// 半开区间 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 后面,循环变量会依次取到数组的每个元素。

实例

import Foundation

let sites = ["Runoob", "RUNOOB", "www.runoob.com"]

// site 每次拿到一个数组元素
for site in sites {
    print(site)
}

以上代码执行输出结果为:

Runoob
RUNOOB
www.runoob.com

用 enumerated() 同时取下标

调用 enumerated() 会返回「下标 + 元素」的元组,元组可以拆成两个循环变量。

实例

import Foundation

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 的遍历顺序不保证,也不代表插入顺序。

下面的例子先对键排序再遍历,这样输出结果才是确定的。

实例

import Foundation

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 两个视图属性。

实例

import Foundation

let ages = ["Runoob": 10, "RUNOOB": 5, "Swift": 11]

print(ages.keys.sorted())    // 键排序后输出
print(ages.values.sorted())  // 值排序后输出

以上代码执行输出结果为:

["RUNOOB", "Runoob", "Swift"]
[5, 10, 11]

遍历字符串

字符串由字符组成,可以直接用 for-in 逐个取字符。

实例

import Foundation

for ch in "RUNOOB" {
    print(ch, terminator: "-")
}
print()

以上代码执行输出结果为:

R-U-N-O-O-B-

用 where 过滤元素

在 in 后面追加 where 子句,只对满足条件的元素执行循环体。

实例

import Foundation

// 只处理 1 到 10 中能被 3 整除的数
for i in 1...10 where i % 3 == 0 {
    print(i)
}

以上代码执行输出结果为:

3
6
9

stride(from:to:by:)

需要按固定步长循环时用 stride,它可以用整数,也可以用浮点数。

to 表示不包含终点,through 表示包含终点,步长可以为负数。

实例

import Foundation

// 从 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 不允许把整数、字符串或可选值当作条件。

实例

import Foundation

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 先执行一次循环体,再在末尾判断条件。

因此循环体至少会执行一次,这适合「至少要做一次,再决定是否继续」的场景。

实例

import Foundation

var n = 10

// 先执行一次循环体,再判断 n < 10 是否成立
repeat {
    print("至少执行一次,n = \(n)")
    n += 1
} while n < 10

以上代码执行输出结果为:

至少执行一次,n = 10

如果换成 while n < 10,因为条件一开始就是假,循环体一次都不会执行。

循环写法选择流程图:按次数是否已知选择 for-in、while、repeat-while,下半部分列出 break、continue 以及带标签的 break label、continue label 的作用范围


break 与 continue

break 立刻结束整个循环,continue 只结束本轮迭代,直接进入下一轮。

实例

import Foundation

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 时直接结束整个外层循环。

实例

import Foundation

// 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 标签 会跳过本轮外层循环的剩余内容,直接开始外层循环的下一轮。

实例

import Foundation

// 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 比较。