foreach 与数组


在处理数组时,经常会是从头至尾迭代数组元素,针对这类需求,现代语言中都有 foreach 之类的语法,C++ 11 之后也有提供,然而 C 语言没有。

透过宏,可以来创建简单的 foreach,以进行数组的迭代,方式有许多种,这边采取的出发点是以下范例:

#include <stdio.h>

int main(void) {
    int arr[] = {10, 20, 30, 40, 50}; 
    int size = *(&arr + 1) - arr;
    for(int i = 0; i < size; i++) {
        printf("%d ", *(arr + i));
    }

    return 0;
}

这边希望的 foreach 语法,可以如下使用:

int arr[] = {10, 20, 30, 40, 50};     
foreach(int *v, arr) {
    printf("%d ", *v);
}

这个语法如何展开为方才范例的完整语法呢?因为可用的展开项目只有int *varr,因此必须整理一下范例:

int arr[] = {10, 20, 30, 40, 50}; 
for(int size = *(&arr + 1) - arr, i = 0; i < size; i++) {
    int *v = arr + i;
    printf("%d ", *v);
}

现在可以初步定义宏:

#define foreach(item, arr)                                  \
    for(int size = *(&arr + 1) - arr, i = 0; i < size; i++) \
        item = (arr + i);

问题来了,你不能在宏中直接写死{},因为 foreach 的使用者要能自行决定是否使用{},因此item = arr + i这行必须能创建一个范畴,而 foreach 的使用者决定该范畴要不要使用{},要能创建范畴又可以创建变量的语句,也只有for了,因此试着将范例再整理一下:

int arr[] = {10, 20, 30, 40, 50}; 
for(int size = *(&arr + 1) - arr, i = 0, j = 0; i < size; i++, j = 0) {
    for(int *v = arr + i; j < 1; j++)
        printf("%d ", *v);
}

内层的for作用纯粹用来创建范畴,因此只执行一次,现在可以定义宏如下:

#include <stdio.h>

#define foreach(item, arr)                                                \
    for(int size = *(&arr + 1) - arr, i = 0, j = 0; i < size; i++, j = 0) \
        for(item = arr + i; j < 1; j++)

int main(void) {
    int arr[] = {10, 20, 30, 40, 50}; 
    foreach(int *v, arr) {
        printf("%d ", *v);
    }

    return 0;
}

看来好像 OK,不过这个 foreach 在处理break时会有问题,底下还是会显示全部的数组元素:

#include <stdio.h>

#define foreach(item, arr)                                                \
    for(int size = *(&arr + 1) - arr, i = 0, j = 0; i < size; i++, j = 0) \
        for(item = arr + i; j < 1; j++)

int main(void) {
    int arr[] = {10, 20, 30, 40, 50}; 
    foreach(int *v, arr) {
        printf("%d ", *v);
        if(*v > 30) {
            break;
        }
    }

    return 0;
}

理由很简单,展开就是break只中断内部的for循环,必须有个方式知道内层for被中断了,因此不采取计数j的方式,改用个loop标志:

#include <stdio.h>

#define foreach(item, arr)                                                                  \
    for(int size = *(&arr + 1) - arr, i = 0, loop = 1; loop && i < size; i++, loop = !loop) \
        for(item = arr + i; loop; loop = !loop)

int main(void) {
    int arr[] = {10, 20, 30, 40, 50, 60}; 
    foreach(int *v, arr) {
        printf("%d ", *v);
        if(*v > 30) {
            break;
        }
    }

    return 0;
}

这个foreach可以处理break,因为loop一开始是 1,表示默认执行外层与内层for,内层for执行过后将loop反相,此时loop为 0,外层for又将之反相,此时loop又回到 1,也就可以继续下次的循环。

如果内层forbreakloop就不会被反相,也就是loop维持为 1,外层for将之反相后loop成为 0,接下来外层for也就不会执行了。

这个 foreach 也可以用于其他类型的数组,例如字符串数组:

#include <stdio.h>

#define foreach(item, arr)                                                                  \
    for(int size = *(&arr + 1) - arr, i = 0, loop = 1; loop && i < size; i++, loop = !loop) \
        for(item = arr + i; loop; loop = !loop)

typedef const char* String;

int main(void) {
    String names[] = {"Justin", "Monica", "Irene"};
    foreach(String *name, names) {
        printf("%s ", *name);
    }

    return 0;
}




展开阅读全文