Logo
Section 1 / 8

foreach 循环

本课程讲解了 foreach 循环及其在 D 语言中与不同数据结构的使用方法。

导入

foreach 循环

本课程讲解了 foreach 循环及其在 D 语言中与不同数据结构的使用方法。

什么是 foreach 循环?

D语言中最常用的语句之一是 foreach 循环。它用于对容器(或范围)中的每个元素执行相同的操作。

对容器中元素进行操作在编程中非常常见。我们在for循环课程中已经看到,数组元素是通过一个递增的索引值来访问的:

1import std.stdio; 2 3void main() { 4 int[] array = [1, 2, 3, 4, 5]; 5 for (int i = 0; i < array.length; ++i) { 6 writeln(array[i]); 7 } 8}

遍历所有元素需要以下步骤:

  • 定义一个计数器变量,通常命名为 i
  • 循环次数达到数组的 .length 属性值
  • 递增 i
  • 访问元素

foreach 的行为本质上与上述相同,但它通过自动处理这些步骤简化了代码:

1foreach (element; array) { 2 writeln(element); 3}

foreach 的强大之处在于,无论容器类型如何,它的使用方式都是一样的。正如我们之前所见,在 for 循环中遍历关联数组的值时,需要先调用数组的 .values 属性:

1auto values = aa.values; 2for (int i = 0; i != values.length; ++i) { 3 writeln(values[i]); 4}

而 foreach 对关联数组无需特殊处理,使用方式与普通数组完全相同:

1foreach (value; aa) { 2 writeln(value); 3}

foreach 的语法

foreach 由三个部分组成:

1foreach (names; container or range) { 2 operations 3}
  • 容器或范围:指定元素所在的来源。
  • 操作:指定要对每个元素执行的操作。
  • 名称:指定元素的名称,根据容器或范围的类型,可能还包含其他变量。虽然名称由程序员自由选择,但名称的数量和类型取决于容器的类型。

continue 和 break

这两个关键字在 foreach 中的意义与在 for 循环中相同:continue 会跳过当前元素的剩余操作并进入下一次迭代;break 则会立即终止整个循环。

foreach 与数组

当使用 foreach 遍历普通数组,并且在名称部分只指定一个名字时,该名字代表每次迭代中元素的值:

1foreach (element; array) { 2 writeln(element); 3}

当在名称部分指定两个名字时,它们分别代表自动计数器和元素值:

1foreach (i, element; array) { 2 writeln(i, ": ", element); 3}
1import std.stdio; 2 3void main() { 4 int[] array = [1, 2, 3, 4, 5]; 5 6 foreach (i, element; array) { 7 writeln(i, ": ", element); 8 } 9}

计数器由 foreach 自动递增。虽然可以使用任意名称,但 i 是最常用的自动计数器名称。

foreach 与字符串

由于字符串是字符数组,foreach 对字符串的处理方式与数组相同:单个名称表示字符,两个名称分别表示计数器和字符:

1import std.stdio; 2 3void main() { 4 foreach (c; "hello") { 5 writeln(c); 6 } 7 8 foreach (i, c; "hello") { 9 writeln(i, ": ", c); 10 } 11}

然而,由于 char 和 wchar 分别表示 UTF-8 和 UTF-16 编码单元,对于这类字符串类型,我们需要按编码单元迭代,而不是 Unicode 码点:

1import std.stdio; 2 3void main() { 4 foreach (i, code; "abcçd") { 5 writeln(i, ": ", code); 6 } 7}

表示 ç 的两个 UTF-8 编码单元会被当作独立元素访问:

10: a 21: b 32: c 43: 54: 65: d

在 foreach 循环中遍历字符串的 Unicode 字符的一种方法是将循环变量声明为 dchar:

1import std.stdio; 2 3void main() { 4 foreach (dchar c; "abcçd") { 5 writeln(c); 6 } 7}

foreach 与关联数组

当使用 foreach 遍历关联数组时,单个名称表示值,两个名称分别表示键和值:

1import std.stdio; 2 3void main() { 4 int[string] aa = ["blue": 10, "green": 20]; 5 6 foreach (value; aa) { 7 writeln(value); 8 } 9 foreach (key, value; aa) { 10 writeln(key, ": ", value); 11 } 12}

关联数组也可以提供键和值作为范围。.byKey、.byValue 和 .byKeyValue 返回高效的范围对象,适用于 foreach 之外的场景。

.byValue 在 foreach 循环中与直接遍历值没有区别。而 .byKey 是高效遍历关联数组键的唯一方式:

1import std.stdio; 2 3void main() { 4 int[string] aa = ["blue": 10, "green": 20]; 5 6 foreach (key; aa.byKey) { 7 writeln(key); 8 } 9}

.byKeyValue 提供了一种类似元组的变量,用于访问每个键值对。可以通过该变量的 .key 和 .value 属性分别获取键和值:

1import std.stdio; 2 3void main() { 4 int[string] aa = ["blue": 10, "green": 20]; 5 6 foreach (element; aa.byKeyValue) { 7 writefln("键 %s 的值是 %s", element.key, element.value); 8 } 9}

foreach 与数字范围

我们在切片及其他数组特性课程中已经接触过数字范围。可以在“容器或范围”部分直接指定一个数字范围:

1import std.stdio; 2 3void main() { 4 foreach (number; 10..15) { 5 writeln(number); 6 } 7}

请注意,10 包含在范围内,而 15 不包含。

foreach 与结构体、类及范围

foreach 也可以用于用户自定义类型的对象,只要这些类型在 foreach 循环中定义了自己的迭代方式。结构体和类通过 opApply() 成员函数或一组范围成员函数支持 foreach 迭代。


在下一课中,我们将进一步探讨 foreach 循环的更多特性。

格式化输出与输入

foreach 循环特性