【问题标题】:Does C have a "foreach" loop construct?C 是否有“foreach”循环结构?
【发布时间】:2010-09-28 21:27:38
【问题描述】:

几乎所有语言都有foreach loop 或类似名称。 C有吗?你能发布一些示例代码吗?

【问题讨论】:

  • foreach”是什么?
  • 尝试在 C 程序中编写 foreach 循环有多难?

标签: c foreach


【解决方案1】:

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 语法,称为“基于范围的 for”

【讨论】:

  • 如果你有“typeof”运算符(gcc 扩展;在许多其他编译器上很常见),你可以去掉那个“int *”。内部 for 循环变成类似“for(typeof((array)+0) item = ...” 然后你可以调用为“foreach( v, values ) ...”
  • @eSKay 可以考虑if(...) foreach(int *v, values) ...。如果它们在循环之外,它会扩展为 if(...) int count = 0 ...; for(...) ...; 并会中断。
  • @rem 如果你使用“break”,它不会破坏外循环
  • @rem 但是,如果将内部“keep = !keep”更改为“keep = 0”,您可以简化我的代码。我喜欢“对称”,所以我只使用否定而不是直接赋值。
  • @rem 是的,这是可能的。但不便携。 typeof 是非标准的
【解决方案2】:

这是 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 不,点是C99 指定初始化器 语法的一部分。见en.wikipedia.org/wiki/C_syntax#Initialization
  • @Rizo:另请注意,这是构建链表的一种非常老套的方式。这个演示可以,但不要在实践中这样做!
  • @Donal 是什么让它“hacky”?
  • @Judge:嗯,一方面它有“令人惊讶”的生命周期(如果你正在使用删除元素的代码,你很可能会在free() 中崩溃),另一方面它有对其定义中的值的引用。这确实是一个太聪明的例子。代码足够复杂而没有故意增加它的聪明之处。 Kernighan 的格言 (stackoverflow.com/questions/1103299/…) 适用!
【解决方案3】:

您可能已经知道,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 上工作;没有测试过其他编译器。

【讨论】:

    【解决方案4】:

    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 循环感到困惑。
    • 稍微充实了这个例子。
    • 您正在更改原始指针,我会执行以下操作: char* s;s="...";for(char *it=s;it!=NULL;it++){/ *它指向字符*/}
    【解决方案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 一起使用。

    【讨论】:

      【解决方案6】:

      虽然 C 没有 for each 构造,但它总是有一个惯用表示,表示数组末尾之后的一个 (&amp;arr)[1]。这允许您为每个循环编写一个简单的惯用语,如下所示:

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

      【讨论】:

      • 如果不太确定这是明确定义的。 (&amp;arr)[1] 并不意味着一个数组项超过了数组的末尾,而是意味着一个数组超过了数组的末尾。 (&amp;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++)会更好、更安全、更习惯。
      • @Lundin 这个定义很好。没错,它是数组末尾之后的一个数组,但是该数组类型在此上下文中转换为一个指针(一个表达式),并且该指针是数组末尾之后的一个。
      【解决方案7】:

      这是我在使用 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)
      

      【讨论】:

      • 注意:一旦数组元素的值为 0,VAR(i) &lt; (size) &amp;&amp; (item = array[VAR(i)]) 将停止。因此将其与 double Array[] 一起使用可能不会遍历所有元素。似乎循环测试应该是其中之一:i&lt;nA[i]。为了清楚起见,可能会添加示例用例。
      • 即使在我以前的方法中使用指针,结果似乎也是“未定义的行为”。那好吧。相信双重 for 循环方法!
      • 这个版本会污染作用域,如果在同一个作用域中使用两次就会失败。也不能用作无支撑块(例如if ( bla ) FOREACH(....) { } else....
      • 1,C 是范围污染的语言,我们中的一些人仅限于较旧的编译器。 2,不要重复自己/描述性。 3,是的,不幸的是,如果它是一个条件 for 循环,你必须有大括号(人们通常会这样做)。如果您可以访问支持在 for 循环中声明变量的编译器,请务必这样做。
      • @Watercycle:我冒昧地使用 FOREACH 的替代版本编辑您的答案,该版本使用 c99 语法以避免命名空间污染。
      【解决方案8】:

      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 ...
      }
      

      【讨论】:

      • 您应该提到您的示例代码不是用 C 语法编写的。
      • > 你应该提到你的示例代码不是用 C 语法编写的你是对的,谢谢:我会编辑帖子。
      • @monjardin-> 确定你可以在结构中定义指向函数的指针,这样调用没有问题。
      【解决方案9】:

      Eric's answer 在您使用“中断”或“继续”时不起作用。

      这可以通过重写第一行来解决:

      原行(重新格式化):

      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 的循环进行比较,你会发现他实际上也在做同样的事情,只是更复杂更丑陋一些。

      【讨论】:

        【解决方案10】:

        这是一个简单的单 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 != size 更改为i &lt; 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
        

        【讨论】:

          【解决方案11】:

          如果您打算使用函数指针

          #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(不确定)。

          【讨论】:

            【解决方案12】:

            C 没有for-each 的实现。当将数组解析为一个点时,接收器不知道数组有多长,因此无法判断何时到达数组的末尾。 请记住,在 C 中,int* 是指向包含 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;
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-12-05
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多