如何在 Swift 中遍历数组时获取元素索引?深入探讨这个热门问题
\n\n想象一下:你正在用 Swift 编写代码,不仅想遍历数组,还想知道每个元素的位置。无论你是需要根据索引修改元素、比较相邻值,还是仅仅输出一个编号列表,任务都是一样的。"How to iterate a loop with index and element in Swift" 这个问题在 Stack Overflow 上获得了数千次浏览。这说明这个话题困扰着很多人。
\n\n在本文中,我们不仅会给出现成的答案。我们还会深入底层:分析有哪些方法,哪种更快,哪种更安全,以及为什么 enumerated() 成为了事实上的标准。开始吧!
问题:为什么简单的 for-in 不够用?
\n\n假设你有一个字符串数组:
\n\nlet fruits = ["苹果", "香蕉", "樱桃", "甜瓜"]\n\n最直接的方法是 for-in:
for fruit in fruits {\n print(fruit)\n}\n\n这会输出水果的名称。但如果需要知道索引呢?例如:"1. 苹果, 2. 香蕉..."?简单的 for-in 不提供索引。你不得不使用一些笨拙的方法:
var index = 0\nfor fruit in fruits {\n print("\\(index). \\(fruit)")\n index += 1\n}\n\n能工作吗?能。优雅吗?不。如果数组是空的呢?如果你不小心忘记增加 index 呢?欢迎来到 bug 的世界。Swift 提供了更优雅的解决方案。
方法 #1:经典方法 —— enumerated()
\n\n这是最流行且推荐的方法。enumerated() 方法返回一个由元组 (offset: Int, element: Element) 组成的序列。让我们看看它是如何工作的:
for (index, fruit) in fruits.enumerated() {\n print("\\(index + 1). \\(fruit)")\n}\n\n输出:
\n1. 苹果\n2. 香蕉\n3. 樱桃\n4. 甜瓜\n\n底层发生了什么?
\n\nenumerated() 不会创建新数组。它返回一个类型为 EnumeratedSequence 的惰性序列。这意味着元素不会被复制到新的数据结构中 —— 它们会被即时“包装”。每次访问下一个元素时,序列只是增加计数器并返回一个元组。这在内存方面非常高效,尤其对于大型集合。
一个重要细节:offset 总是从 0 开始。如果你需要从 1 开始的索引(如列表编号),只需加 1。Swift 不会自动为你做这件事 —— 这是语言开发人员有意为之,以保持与数组索引的一致性。
enumerated() 与 forEach 结合使用
\n\nforEach 方法也支持 enumerated():
fruits.enumerated().forEach { (index, fruit) in\n print("\\(index). \\(fruit)")\n}\n\nfor-in 和 forEach 之间的区别很重要:在 forEach 内部不能使用 break 或 continue。如果需要提前退出循环 —— 请使用 for-in。
方法 #2:通过范围 (Range) 进行索引
\n\n一种较旧但仍然可见的方法 —— 按索引迭代:
\n\nfor index in 0..<fruits.count {\n print("\\(index). \\(fruits[index])")\n}\n\n看起来很简单。但有一些陷阱:
\n\n- \n
- 安全性: 如果数组在迭代期间发生变化(例如在多线程代码中),
fruits.count可能已过时,你会超出数组边界。 \n - 可读性: 代码看起来更“底层”。其他开发者需要多花一秒钟才能理解你在做什么。 \n
- 性能: 通过索引
fruits[index]访问对于数组来说是 O(1) 操作,但对于其他