首页 / Swift 编程语言教程 / 常用高阶函数

Swift 编程语言教程

常用高阶函数

本教程共 93 篇 · 第 38 篇 · 更新于 2026-08-08 · 约 9 分钟阅读

Swift高阶函数mapfilterreducesortedforEach闭包

本节目标:掌握 Swift 标准库里以闭包为核心的高阶函数,能用它们替代大段 for 循环,写出更简洁的集合处理代码。

所谓高阶函数,就是”把函数(或闭包)当参数、或者把函数当返回值”的函数。前面几章闭包讲那么多,很大一部分用途就是喂给这些高阶函数。它们大多作用在数组、集合等容器上,把”遍历 + 处理”这件事写成一行声明式的代码,比手搓 for 循环清爽太多。

1-1 forEach:遍历但不返回的循环

forEach(_:) 对集合里每个元素调用一次闭包,行为和 for 循环几乎一样。区别在于它不能像循环那样用 break 提前退出,也不返回任何值。

let numbers = [1, 2, 3, 4, 5]
numbers.forEach { number in
    print("值是 \(number)")
}

如果只是想对元素逐个做点事(打印、改外部变量),forEach 很合适。但如果你在循环里需要根据条件 break,还是老老实实用 for 循环,forEach 做不到提前终止。

1-2 map:把每个元素变成另一个值

map(_:) 对集合每个元素调用闭包,把返回值收集成一个新数组,新数组长度和原数组一样。它最适合”转换”场景,比如把数字数组变成字符串数组:

let values = [1, 2, 3]
let strings = values.map { "数字\($0)" }
// strings 为 ["数字1", "数字2", "数字3"]

闭包返回的类型可以和原元素不同,map 返回的新数组类型跟着变。注意 map 不会改动原数组,它总是产出一份新的。把上一章那个”整数转中文数字串”的逻辑用 map 写出来,就是尾随闭包最经典的落地点。

1-3 filter:按条件筛出元素

filter(_:) 的闭包返回一个 Bool,只有返回 true 的元素才会被保留,最终得到一个新数组。它用来”筛选”:

let numbers = [1, 2, 3, 4, 5, 6]
let evens = numbers.filter { $0 % 2 == 0 }
// evens 为 [2, 4, 6]

闭包里写判断条件即可。filter 同样不动原数组,返回的是筛选后的新数组。它和 map 经常搭档:先用 filter 留想要的,再用 map 转成需要的样子。

1-4 reduce:把元素合并成一个值

reduce(_:_:) 把集合”折叠”成单个值。它接收两个参数:初始值,以及一个把”当前累加结果”和”下一个元素”合并的闭包。最经典的例子是求和:

let numbers = [1, 2, 3, 4]
let sum = numbers.reduce(0) { $0 + $1 }
// sum 为 10

第一个参数 0 是累加起点,$0 是当前累加值,$1 是正在处理的这个元素。如果想算乘积,把初始值改成 1、闭包改成乘法即可。还可以用 reduce 把数组拼成一个字符串、统计出现次数,灵活得很。

Tip

reduce(0, +) 这种写法里,+ 本身就是一个接收两个参数、返回它们的和的函数,可以直接当闭包传进去,和 sorted(by: >) 是一个套路。

1-5 sorted:排序

sorted(by:) 我们已经见过多次。它返回排好序的新数组,原数组不动。不传参数时用元素自身的默认排序(要求元素遵循 Comparable);传一个比较闭包则按你的规则排:

let names = ["Chris", "Alex", "Ewa"]
let ascending = names.sorted()        // 默认升序
let descending = names.sorted { $0 > $1 }  // 倒序

如果元素是自定义类型,又不遵循 Comparable,就必须提供闭包告诉它怎么比。

1-6 把它们串起来用

高阶函数最爽的地方,是彼此可以链式调用,读起来像在描述”我要做什么”,而不是”我怎么一步步做”。比如:求一组数字里偶数之和。

let numbers = [1, 2, 3, 4, 5, 6]
let evenSum = numbers
    .filter { $0 % 2 == 0 }
    .reduce(0) { $0 + $1 }
// evenSum 为 12

filter 出偶数,再 reduce 求和,一目了然。这类链式写法没有中间变量,也不改动原数据,是函数式风格的核心好处。但也要克制:如果一条链长到读不顺,拆成几步、起个有意义的变量名,反而更好维护。

Note

这些高阶函数返回的都是新集合或新值,原始集合保持不变。这符合 Swift 值类型”默认不可变”的设计哲学,也避免了意外改到别处引用的数据。

1-7 更多高阶函数与思维转变

除了前面五个,标准库里还有几个高频的高阶函数值得认识。contains(where:) 判断是否有元素满足闭包条件,比写 filter{}.isEmpty 更高效;first(where:) 找出第一个满足条件的元素,也是短路的,找到就停。flatMap 能把”数组的数组”压平,或者把可选值展开;而 compactMap 专门用来”映射并丢弃 nil”,比如把一组字符串转成整数时,自动跳过那些转不成的。partition(by:) 则按条件把集合分成两半(注意它会改动原集合顺序)。

这些函数背后有个统一的思维转变:从”我怎么一步步改数据”变成”我对数据要做什么”。命令式写法里你写循环、写下标、写临时变量;声明式写法里你只描述规则,循环和索引交给标准库。初学时会觉得别扭,但习惯后代码更短、更不容易出下标越界这类 bug。

还有一个性能小贴士:map 之类会产生新集合,如果只是为了副作用(打印、累加外部变量),用 forEach 更贴切,避免白白生成一份用不上的数组。链式调用虽爽,但每一环都产出新集合,数据量很大时要留意中间数组的内存开销——那种场景未必链得越长越好。

1-8 易错点速记

这些高阶函数都返回新集合,不动原数据。最容易混的是 mapflatMapmap 是一一映射,flatMap 会顺手压平一层嵌套;想”映射并丢掉 nil”用 compactMapreduce 的初始值别漏,求和用 0、求积用 1。链式调用很爽,但每一环都产生新数组,数据量巨大时要留意内存。需要副作用就用 forEach,别为了链式而生造不需要的中间数组。还有一个直觉:能一行声明式写完的变换,就别手写 for 循环;但变换逻辑长到一行放不下时,拆成有名字的步骤反而更清楚。

1-9 小结

forEach 遍历、map 转换、filter 筛选、reduce 合并、sorted 排序,这五个是日常最高频的高阶函数。它们把闭包当参数,用声明式写法取代命令式循环。闭包这个模块到这里告一段落,下一模块我们进入可选类型与错误处理——Swift 安全性的另一根支柱。