C是否具有“ foreach”循环结构?


108

几乎所有语言都有foreach循环或类似的内容。C有一个吗?您可以发布一些示例代码吗?


1
foreach”是什么?
2016年

尝试foreach在C程序中编写循环有多困难?
MD XF

Answers:


193

C没有foreach,但是宏经常用于模拟:

#define for_each_item(item, list) \
    for(T * item = list->head; item != NULL; item = item->next)

并且可以像

for_each_item(i, processes) {
    i->wakeup();
}

也可以在数组上进行迭代:

#define foreach(item, array) \
    for(int keep = 1, \
            count = 0,\
            size = sizeof (array) / sizeof *(array); \
        keep && count != size; \
        keep = !keep, count++) \
      for(item = (array) + count; keep; keep = !keep)

并且可以像

int values[] = { 1, 2, 3 };
foreach(int *v, values) {
    printf("value: %d\n", *v);
}

编辑:如果您也对C ++解决方案感兴趣,则C ++具有本机的for-each语法,称为“基于范围”


1
如果您拥有“ typeof”运算符(gcc扩展;在许多其他编译器上很常见),则可以摆脱该“ int *”。内部的for循环变为类似“ for(typeof((array)+0)item = ...”的东西),然后您可以将其称为“ foreach(v,values)...”
leander

为什么在数组示例中需要两个for循环?怎么样:#define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)我可以看到的一个问题是变量的数量和大小在for循环之外,并且可能引起冲突。这是您使用两个for循环的原因吗?[粘贴在此处的代码(pastebin.com/immndpwS)]
Lazer 2010年

3
@eSKay是的,请考虑if(...) foreach(int *v, values) ...。如果它们不在循环中,它将扩展到if(...) int count = 0 ...; for(...) ...;并且将中断。
Johannes Schaub-litb 2010年

1
@rem如果您使用“ break”,它不会破坏外部循环
Johannes Schaub-litb

1
@rem但是,如果将内部的“ keep =!keep”更改为“ keep = 0”,则可以简化我的代码。我喜欢“对称性”,所以我只使用否定而不是直接分配。
约翰内斯·绍布

11

这是C99中for-each宏的完整程序示例:

#include <stdio.h>

typedef struct list_node list_node;
struct list_node {
    list_node *next;
    void *data;
};

#define FOR_EACH(item, list) \
    for (list_node *(item) = (list); (item); (item) = (item)->next)

int
main(int argc, char *argv[])
{
    list_node list[] = {
        { .next = &list[1], .data = "test 1" },
        { .next = &list[2], .data = "test 2" },
        { .next = NULL,     .data = "test 3" }
    };

    FOR_EACH(item, list)
        puts((char *) item->data);

    return 0;
}

点在list[]定义中起什么作用?您不能简单地写next而不是.next吗?
Rizo 2010年

9
@Rizo不,点是C99指定的初始化程序语法的一部分。参见en.wikipedia.org/wiki/C_syntax#Initialization
Maygarden法官2010年

@Rizo:还请注意,这是构建链接列表的一种真正的技巧。它会为这个演示做准备,但在实践中不要那样做!
Donal Fellows 2010年

@Donal是什么使它“变态”?
Maygarden法官10年

2
@Judge:好吧,一方面,它具有“令人惊讶的”生命周期(如果您正在使用删除元素的代码,则很可能会崩溃free());另一方面,它在其定义中引用了该值。这确实是一个太愚蠢的事的例子。代码足够复杂,而没有故意增加它的智能性。Kernighan的格言(stackoverflow.com/questions/1103299/…)适用!
Donal Fellows,2010年

9

C语言中没有foreach。

您可以使用for循环遍历数据,但是需要知道长度,或者需要用已知值终止数据(例如null)。

char* nullTerm;
nullTerm = "Loop through my characters";

for(;nullTerm != NULL;nullTerm++)
{
    //nullTerm will now point to the next character.
}

您应该将nullTerm指针的初始化添加到数据集的开头。OP可能会对for循环不完整感到困惑。
cschol

使这个例子有些浮夸。
亚当·派克

如果您要更改原始指针,则可以执行以下操作:char * s; s =“ ...”; for(char * it = s; it!= NULL; it ++){/ *指向字符* / }
hiena

6

您可能已经知道,C中没有“ foreach”样式的循环。

尽管这里已经提供了很多宏宏来解决此问题,但是也许您会发现此宏有用:

// "length" is the length of the array.   
#define each(item, array, length) \
(typeof(*(array)) *p = (array), (item) = *p; p < &((array)[length]); p++, (item) = *p)

...可与使用for(如在for each (...))。

这种方法的优点:

  • item 在for语句中声明并递增(就像在Python中一样!)。
  • 似乎可以在任何一维数组上工作
  • 在宏(pitem)中创建的所有变量在循环范围之外都不可见(因为它们是在for循环头中声明的)。

缺点:

  • 对多维数组不起作用
  • 依赖于typeof(),它是GNU扩展,不是标准C的一部分
  • 由于它在for循环头中声明了变量,因此仅在C11或更高版本中有效。

为了节省您的时间,下面是测试方法:

typedef struct {
    double x;
    double y;
} Point;

int main(void) {
    double some_nums[] = {4.2, 4.32, -9.9, 7.0};
    for each (element, some_nums, 4)
        printf("element = %lf\n", element);

    int numbers[] = {4, 2, 99, -3, 54};
    // Just demonstrating it can be used like a normal for loop
    for each (number, numbers, 5) { 
        printf("number = %d\n", number);
        if (number % 2 == 0)
                printf("%d is even.\n", number);
    }

    char* dictionary[] = {"Hello", "World"};
    for each (word, dictionary, 2)
        printf("word = '%s'\n", word);

    Point points[] = {{3.4, 4.2}, {9.9, 6.7}, {-9.8, 7.0}};
    for each (point, points, 3)
        printf("point = (%lf, %lf)\n", point.x, point.y);

    // Neither p, element, number or word are visible outside the scope of
    // their respective for loops. Try to see if these printfs work
    // (they shouldn't):
    // printf("*p = %s", *p);
    // printf("word = %s", word);

    return 0;
}

似乎默认情况下可以在gcc和clang上工作;尚未测试其他编译器。


5

这是一个相当老的问题,但是我应该发布。它是GNU C99的foreach循环。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define FOREACH_COMP(INDEX, ARRAY, ARRAY_TYPE, SIZE) \
  __extension__ \
  ({ \
    bool ret = 0; \
    if (__builtin_types_compatible_p (const char*, ARRAY_TYPE)) \
      ret = INDEX < strlen ((const char*)ARRAY); \
    else \
      ret = INDEX < SIZE; \
    ret; \
  })

#define FOREACH_ELEM(INDEX, ARRAY, TYPE) \
  __extension__ \
  ({ \
    TYPE *tmp_array_ = ARRAY; \
    &tmp_array_[INDEX]; \
  })

#define FOREACH(VAR, ARRAY) \
for (void *array_ = (void*)(ARRAY); array_; array_ = 0) \
for (size_t i_ = 0; i_ && array_ && FOREACH_COMP (i_, array_, \
                                    __typeof__ (ARRAY), \
                                    sizeof (ARRAY) / sizeof ((ARRAY)[0])); \
                                    i_++) \
for (bool b_ = 1; b_; (b_) ? array_ = 0 : 0, b_ = 0) \
for (VAR = FOREACH_ELEM (i_, array_, __typeof__ ((ARRAY)[0])); b_; b_ = 0)

/* example's */
int
main (int argc, char **argv)
{
  int array[10];
  /* initialize the array */
  int i = 0;
  FOREACH (int *x, array)
    {
      *x = i;
      ++i;
    }

  char *str = "hello, world!";
  FOREACH (char *c, str)
    printf ("%c\n", *c);

  return EXIT_SUCCESS;
}

该代码已经过测试,可以在GNU / Linux上与gcc,icc和clang一起使用。


4

尽管C没有为每个构造提供a,但它在array的末尾始终有一个惯用的表示形式(&arr)[1]。这使您可以为每个循环编写一个简单的习惯用法,如下所示:

int arr[] = {1,2,3,4,5};
for(int *a = arr; a < (&arr)[1]; ++a)
    printf("%d\n", *a);

3
如果不是这样,请确保定义明确。(&arr)[1]并不意味着在数组末尾超过一个数组项,而是意味着在数组末尾超过一个数组。(&arr)[1]不是数组[0]的最后一项,而是数组[1],它衰减为指向第一个元素(数组[1]的指针)的指针。我相信这将是更好的,更安全和习惯做的const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);,然后for(const int* a = begin; a != end; a++)
伦丁

1
@Lundin定义明确。没错,它是数组末尾的一个数组,但是该数组类型在此上下文(表达式)中转换为指针,而该指针是数组末尾的一个指针。
史蒂夫·考克斯

2

C具有“ for”和“ while”关键字。如果像C#这样的语言中的foreach语句看起来像这样...

foreach (Element element in collection)
{
}

...那么等效于C中此foreach语句可能类似于:

for (
    Element* element = GetFirstElement(&collection);
    element != 0;
    element = GetNextElement(&collection, element)
    )
{
    //TODO: do something with this element instance ...
}

1
您应该提到您的示例代码不是用C语法编写的。
cschol

>您应该提到您的示例代码不是用C语法编写的。是的,谢谢:我将编辑这篇文章。
ChrisW

@ monjardin->确保您只可以在结构体中定义函数的指针,并且进行这样的调用没有问题。
伊利亚

2

这是我在使用C时遇到的问题。您不能在同一范围内两次使用相同的项目名称,但这并不是真正的问题,因为并不是我们所有人都可以使用漂亮的新编译器:(

#define FOREACH(type, item, array, size) \
    size_t X(keep), X(i); \
    type item; \
    for (X(keep) = 1, X(i) = 0 ; X(i) < (size); X(keep) = !X(keep), X(i)++) \
        for (item = (array)[X(i)]; X(keep); X(keep) = 0)

#define _foreach(item, array) FOREACH(__typeof__(array[0]), item, array, length(array))
#define foreach(item_in_array) _foreach(item_in_array)

#define in ,
#define length(array) (sizeof(array) / sizeof((array)[0]))
#define CAT(a, b) CAT_HELPER(a, b) /* Concatenate two symbols for macros! */
#define CAT_HELPER(a, b) a ## b
#define X(name) CAT(__##name, __LINE__) /* unique variable */

用法:

int ints[] = {1, 2, 0, 3, 4};
foreach (i in ints) printf("%i", i);
/* can't use the same name in this scope anymore! */
foreach (x in ints) printf("%i", x);

编辑:这是FOREACH使用c99语法避免命名空间污染的一种替代方法:

#define FOREACH(type, item, array, size) \
    for (size_t X(keep) = 1, X(i) = 0; X(i) < (size); X(keep) = 1, X(i)++) \
    for (type item = (array)[X(i)]; X(keep); X(keep) = 0)

注意:VAR(i) < (size) && (item = array[VAR(i)])一旦数组元素的值为0,它就会停止。因此使用with double Array[]可能不会遍历所有元素。似乎循环测试应该是其中之一:i<nA[i]。为了清楚起见,也许添加示例用例。
chux-恢复莫妮卡2015年

即使在我以前的方法中使用了指针,结果也似乎是“未定义的行为”。那好吧。相信double for loop方法!
2015年

此版本会污染范围,并且如果在同一范围内使用两次,将会失败。也不能用作无支撑的块(例如if ( bla ) FOREACH(....) { } else....
MM

1
1,C是范围污染的语言,我们中的某些人仅限于较旧的编译器。2,不要重复自己/进行描述。3,是的,不幸的是,如果这将成为循环的条件(人们通常还是这样做的话),则必须带括号。如果可以访问支持for循环中变量声明的编译器,则一定要这样做。
2015年

@Watercycle:我自由地FOREACH使用c99语法以避免命名空间污染来编辑您的答案。
chqrlie

1

当您使用“中断”或“继续”时,Eric的答案不起作用。

可以通过重写第一行来解决此问题:

原始行(重新格式化):

for (unsigned i = 0, __a = 1; i < B.size(); i++, __a = 1)

固定:

for (unsigned i = 0, __a = 1; __a && i < B.size(); i++, __a = 1)

如果将其与Johannes的循环进行比较,您会发现他实际上在做同样的事情,只是更加复杂和丑陋。


1

这是一个简单的for循环:

#define FOREACH(type, array, size) do { \
        type it = array[0]; \
        for(int i = 0; i < size; i++, it = array[i])
#define ENDFOR  } while(0);

int array[] = { 1, 2, 3, 4, 5 };

FOREACH(int, array, 5)
{
    printf("element: %d. index: %d\n", it, i);
}
ENDFOR

允许您访问索引(如果需要i)和我们正在迭代的当前项(it)。请注意,嵌套循环时可能会出现命名问题,您可以使项目和索引名称成为宏的参数。

编辑:这是已接受答案的修改版本foreach。让您指定start索引,size这样它就可以在衰减的数组(指针)上使用,不需要int*并进行更改count != sizei < size以防万一用户不小心将'i'修改为大于size并陷入无限循环。

#define FOREACH(item, array, start, size)\
    for(int i = start, keep = 1;\
        keep && i < size;\
        keep = !keep, i++)\
    for (item = array[i]; keep; keep = !keep)

int array[] = { 1, 2, 3, 4, 5 };
FOREACH(int x, array, 2, 5)
    printf("index: %d. element: %d\n", i, x);

输出:

index: 2. element: 3
index: 3. element: 4
index: 4. element: 5

1

如果您打算使用函数指针

#define lambda(return_type, function_body)\
    ({ return_type __fn__ function_body __fn__; })

#define array_len(arr) (sizeof(arr)/sizeof(arr[0]))

#define foreachnf(type, item, arr, arr_length, func) {\
    void (*action)(type item) = func;\
    for (int i = 0; i<arr_length; i++) action(arr[i]);\
}

#define foreachf(type, item, arr, func)\
    foreachnf(type, item, arr, array_len(arr), func)

#define foreachn(type, item, arr, arr_length, body)\
    foreachnf(type, item, arr, arr_length, lambda(void, (type item) body))

#define foreach(type, item, arr, body)\
    foreachn(type, item, arr, array_len(arr), body)

用法:

int ints[] = { 1, 2, 3, 4, 5 };
foreach(int, i, ints, {
    printf("%d\n", i);
});

char* strs[] = { "hi!", "hello!!", "hello world", "just", "testing" };
foreach(char*, s, strs, {
    printf("%s\n", s);
});

char** strsp = malloc(sizeof(char*)*2);
strsp[0] = "abcd";
strsp[1] = "efgh";
foreachn(char*, s, strsp, 2, {
    printf("%s\n", s);
});

void (*myfun)(int i) = somefunc;
foreachf(int, i, ints, myfun);

但我认为这仅适用于gcc(不确定)。


1

C没有的实现for-each。将数组解析为点时,接收方不知道数组有多长,因此无法得知何时到达数组末尾。记住,用Cint*中的指向包含int的内存地址。没有标头对象包含有关按顺序放置多少个整数的信息。因此,程序员需要对此进行跟踪。

但是,对于列表,很容易实现类似于for-each循环的内容。

for(Node* node = head; node; node = node.next) {
   /* do your magic here */
}

要实现与数组相似的功能,可以执行以下两项操作之一。

  1. 使用第一个元素存储数组的长度。
  2. 将数组包装在一个结构中,该结构包含长度和指向数组的指针。

以下是此类结构的示例:

typedef struct job_t {
   int count;
   int* arr;
} arr_t;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.