HOWTO · C

C 语言中的 foreach 循环

以下内容将调查计算机语言 C 是否支持 foreach 循环。

本页内容

C 没有内置的 for-each 循环来自动访问集合中的每个元素。你可以用普通的 for 或 while 循环实现类似的遍历方式,但代码必须知道集合在哪里结束。对于数组,要保存元素个数;对于链表,则沿着每个节点的 next 指针前进,直到它为空。

C 有内置的 foreach 循环吗?

没有。C 语言没有像 Python 或 JavaScript 那样专门的 foreach 结构。不过,只要集合及其边界明确,就很容易写出 for-each 风格的循环。遍历数组时,常用的方法是按索引循环;遍历链表时,则从一个节点移动到下一个节点。

与许多高级语言的关键区别在于,C 不会把数组长度附加在数组指针上。函数需要处理数组时,应同时传入长度。这样停止条件一目了然,也能避免读到集合元素范围之外。

用 for 循环遍历数组

如果数组是在当前作用域中声明的,可以用 sizeof array / sizeof array[0] 计算元素个数。然后使用索引,从零开始循环到该个数之前,也就是不包含该个数。

下面的独立程序会按顺序打印每个元素。这里可以使用该长度表达式,因为在 main 中,numbers 仍然是数组。

本节中的代码以及显示的标签或输出仍为英文。

#include <stddef.h>
#include <stdio.h>

int main(void) {
    int numbers[] = {10, 20, 30, 40, 50};
    size_t length = sizeof numbers / sizeof numbers[0];

    for (size_t i = 0; i < length; ++i) {
        printf("%d\n", numbers[i]);
    }

    return 0;
}
10
20
30
40
50

将数组传给函数时,函数参数会成为指向首个元素的指针;数组大小不会随指针一起传入。因此,对该参数使用 sizeof 得到的是指针大小,而不是原数组的字节数。应另行传入元素个数,例如 size_t length,并在 i < length 时循环。调用方可以在传入数组前计算元素个数。对于动态分配的数组,应保存分配时得到的元素个数,或从管理该数组的数据结构中取得它。

使用 while 循环或遍历链表

如果循环条件自然地表示是否还有下一个元素,使用 while 循环同样合适。遍历数组时,将索引初始化为零,在它小于已保存的长度时继续循环,并在每次迭代后递增。与 for 循环一样,边界条件必须正确。

链表的停止条件不同:只要当前节点指针不为空,就处理该节点,然后移动到 current->next。例如,节点可以声明为 struct Node { int value; struct Node *next; };。由于 current 是指向节点的指针,应使用 -> 访问节点字段。当 current 变为 NULL 时,链表遍历结束。不要对链表使用数组的 sizeof 公式:链表节点是由指针连接的独立对象,链表没有数组长度。如果需要知道链表长度,可以遍历并计数,或在所属的数据结构中维护计数。

什么时候应该把遍历封装成函数或宏?

如果程序的多个部分都要对数组的每个元素执行相同操作,可以使用函数。函数可以接收元素指针、size_t 计数,以及要执行的操作。回调参数可以具有 void (*visit)(int) 这样的类型,函数则对每个有效索引调用 visit(array[i])。要显式传入计数:在函数内部,数组参数是指针,无法通过 sizeof 还原原数组的大小。

接收回调的函数还可以支持不同操作,例如打印或累加值。回调类型应与元素类型相符;也要考虑回调是否需要额外上下文。在 C 中,可以通过 void * 参数将上下文传给回调。

宏可以让循环更简洁,但它执行的是文本替换,而且可能多次计算参数。宏还可能掩盖确保遍历安全所需的边界和类型。使用宏时,要确保边界与数组匹配,并避免传入带副作用的表达式。对于可复用代码,显式接收长度的函数通常更容易检查、调试和维护。