Swift 数组迭代:索引与元素深入解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

如何在 Swift 中遍历数组时获取元素索引?深入探讨这个热门问题

\n\n

想象一下:你正在用 Swift 编写代码,不仅想遍历数组,还想知道每个元素的位置。无论你是需要根据索引修改元素、比较相邻值,还是仅仅输出一个编号列表,任务都是一样的。"How to iterate a loop with index and element in Swift" 这个问题在 Stack Overflow 上获得了数千次浏览。这说明这个话题困扰着很多人。

\n\n

在本文中,我们不仅会给出现成的答案。我们还会深入底层:分析有哪些方法,哪种更快,哪种更安全,以及为什么 enumerated() 成为了事实上的标准。开始吧!

\n\n

问题:为什么简单的 for-in 不够用?

\n\n

假设你有一个字符串数组:

\n\n
let fruits = ["苹果", "香蕉", "樱桃", "甜瓜"]
\n\n

最直接的方法是 for-in:

\n\n
for fruit in fruits {\n    print(fruit)\n}
\n\n

这会输出水果的名称。但如果需要知道索引呢?例如:"1. 苹果, 2. 香蕉..."?简单的 for-in 不提供索引。你不得不使用一些笨拙的方法:

\n\n
var index = 0\nfor fruit in fruits {\n    print("\\(index). \\(fruit)")\n    index += 1\n}
\n\n

能工作吗?能。优雅吗?不。如果数组是空的呢?如果你不小心忘记增加 index 呢?欢迎来到 bug 的世界。Swift 提供了更优雅的解决方案。

\n\n

方法 #1:经典方法 —— enumerated()

\n\n

这是最流行且推荐的方法。enumerated() 方法返回一个由元组 (offset: Int, element: Element) 组成的序列。让我们看看它是如何工作的:

\n\n
for (index, fruit) in fruits.enumerated() {\n    print("\\(index + 1). \\(fruit)")\n}
\n\n

输出:

\n
1. 苹果\n2. 香蕉\n3. 樱桃\n4. 甜瓜
\n\n

底层发生了什么?

\n\n

enumerated() 不会创建新数组。它返回一个类型为 EnumeratedSequence 的惰性序列。这意味着元素不会被复制到新的数据结构中 —— 它们会被即时“包装”。每次访问下一个元素时,序列只是增加计数器并返回一个元组。这在内存方面非常高效,尤其对于大型集合。

\n\n

一个重要细节:offset 总是从 0 开始。如果你需要从 1 开始的索引(如列表编号),只需加 1。Swift 不会自动为你做这件事 —— 这是语言开发人员有意为之,以保持与数组索引的一致性。

\n\n

enumerated() 与 forEach 结合使用

\n\n

forEach 方法也支持 enumerated():

\n\n
fruits.enumerated().forEach { (index, fruit) in\n    print("\\(index). \\(fruit)")\n}
\n\n

for-in 和 forEach 之间的区别很重要:在 forEach 内部不能使用 break 或 continue。如果需要提前退出循环 —— 请使用 for-in。

\n\n

方法 #2:通过范围 (Range) 进行索引

\n\n

一种较旧但仍然可见的方法 —— 按索引迭代:

\n\n
for index in 0..<fruits.count {\n    print("\\(index). \\(fruits[index])")\n}
\n\n

看起来很简单。但有一些陷阱:

\n\n
    \n
  • 安全性: 如果数组在迭代期间发生变化(例如在多线程代码中),fruits.count 可能已过时,你会超出数组边界。
  • \n
  • 可读性: 代码看起来更“底层”。其他开发者需要多花一秒钟才能理解你在做什么。
  • \n
  • 性能: 通过索引 fruits[index] 访问对于数组来说是 O(1) 操作,但对于其他

博客

书籍推荐