Есть ли в C конструкция цикла "foreach"?

Почти во всех языках есть foreach петля или что-то подобное. Есть ли у C? Можете ли вы опубликовать пример кода?

"foreach" чего?

alk 06.03.2016 14:13

Насколько сложно было бы попробовать написать цикл foreach в программе на языке C?

MD XF 17.10.2016 03:04
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
115
2
153 665
12

Ответы 12

В 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 30.12.2008 20:54

Немного конкретизировал пример.

Adam Peck 30.12.2008 21:13

вы меняете исходный указатель, я бы сделал что-то вроде: char * s; s = "..."; for (char * it = s; it! = NULL; it ++) {/ * он указывает на символ * / }

hiena 06.08.2009 08:32

В C есть ключевые слова "для" и "пока". Если оператор foreach на языке вроде C# выглядит так ...

foreach (Element element in collection)
{
}

... тогда эквивалент этого оператора foreach в C может быть таким:

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

Вы должны упомянуть, что ваш примерный код написан не на синтаксисе C.

cschol 30.12.2008 20:48

> Вы должны упомянуть, что ваш примерный код написан не на синтаксисе C. Вы правы, спасибо: я отредактирую сообщение.

ChrisW 30.12.2008 20:53

@ monjardin-> уверен, что вы можете просто определить указатель на функцию в структуре, и нет проблем сделать такой вызов.

Ilya 31.12.2008 10:53

В 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++ имеет собственный синтаксис для каждого, называемый "диапазон на основе для"

Если у вас есть оператор «typeof» (расширение gcc; довольно часто встречается во многих других компиляторах), вы можете избавиться от этого «int *». Внутренний цикл for становится чем-то вроде "for (typeof ((array) +0) item = ...". Затем вы можете вызывать как "foreach (v, values) ..."

leander 06.08.2009 08:46

Зачем нам нужны два цикла 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 09.05.2010 15:23

@eSKay да считай if (...) foreach(int *v, values) .... Если они находятся вне цикла, он расширяется до if (...) int count = 0 ...; for(...) ...; и прерывается.

Johannes Schaub - litb 09.05.2010 15:37

Для чего нужна переменная keep?

fredoverflow 15.02.2012 14:30

У меня есть несколько вопросов. Будет ли это сломано, если пользователь сделает foreach (int * keep, keep)? Кроме того, что делает скобка вокруг массива во внутреннем цикле?

Russell 27.04.2012 18:49

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

Brans Ds 01.07.2014 13:07

@Russell Да, к сожалению, это сломается. Если вы используете это для реального кода, вам лучше всего использовать странные имена для keep и других.

Johannes Schaub - litb 02.07.2014 00:21

@ JohannesSchaub-litb: было бы лучше, если бы вы обновили свой ответ, включив в него цикл на основе диапазона, представленный C++ 11.

Destructor 21.09.2015 13:11

Рекомендовать int -> size_t в for(size_t keep = 1, \ ...

chux - Reinstate Monica 16.10.2015 18:52

У меня были небольшие проблемы с пониманием ваших двух утверждений keep = !keep. Я думаю, что следующее работает так же хорошо и (на мой взгляд) немного понятнее: #define array_elements(arr) ( sizeof(arr) / sizeof(arr[0]) )#define array_for(item, array) for(unsigned int cont=1, i=0; i<array_elements(array); cont=1, i++) for(item=&(array)[i]; cont; cont=0)

rem 26.09.2016 12:56

@rem он не прерывает внешний цикл, если вы используете "break"

Johannes Schaub - litb 26.09.2016 14:05

@rem, однако, вы можете упростить мой код, изменив внутреннее «keep =! keep» на «keep = 0». Мне понравилась «симметрия», поэтому я просто использовал отрицание, а не прямое присвоение.

Johannes Schaub - litb 26.09.2016 14:12

Я понял, что вы также можете полностью исключить вложенный цикл, увеличив указатель вместо отдельного итератора: #define array_for(item, array) for(typeof(array[0])* item=array; item < &array[sizeof(array) / sizeof(array[0])]; item++)

rem 28.09.2016 12:55

@rem да, это возможно. Но не переносной. Typeof нестандартный

Johannes Schaub - litb 28.09.2016 20:53
T* - это что именно?
Tomáš Zato - Reinstate Monica 14.11.2016 20:34

@rem Ваше решение возможно без typeof. Вместо этого просто объявите свою переменную вне цикла for: int *v; array_for(v, values) { ... }

Charles W 03.08.2017 02:01

Макрос for_each_item работает так же только для навязчивых списков, где следующий указатель является частью самих элементов.

Andreas Haferburg 20.11.2018 01:38

Макросы плохие. Я думаю, вам следует использовать статические встроенные функции вместо использования таких макросов

traducerad 15.09.2019 11:31

Вот полный пример программы для каждого макроса в C99:

#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 28.06.2010 14:25

@Rizo Нет, точка является частью синтаксиса C99 назначенные инициализаторы. См. en.wikipedia.org/wiki/C_syntax#Initialization

Judge Maygarden 28.06.2010 19:27

@Rizo: Также обратите внимание, что это действительно хакерский способ создания связного списка. Это годится для этой демонстрации, но не делает это на практике!

Donal Fellows 21.07.2010 19:18

@Donal Что делает его "хакерским"?

Judge Maygarden 22.07.2010 17:17

@Judge: Ну, с одной стороны, у него «удивительное» время жизни (если вы работаете с кодом, который удаляет элементы, есть вероятность, что у вас произойдет сбой в free()), а с другой стороны, он имеет ссылку на значение внутри своего определения. Это действительно пример чего-то чертовски умного; код достаточно сложен, но без намеренного добавления в него умных способностей. Применяется афоризм Кернигана (stackoverflow.com/questions/1103299/…)!

Donal Fellows 23.07.2010 03:25

Это довольно старый вопрос, но я должен опубликовать его. Это цикл foreach для GNU C99.

#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;
}

Этот код был протестирован для работы с gcc, icc и clang в GNU / Linux.

Ответ Эрика не работает, когда вы используете "break" или "continue".

Это можно исправить, переписав первую строку:

Исходная строка (переформатированная):

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)

Если вы сравните это с циклом Йоханнеса, вы увидите, что он на самом деле делает то же самое, только немного сложнее и уродливее.

Вот простой, одиночный цикл 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 < 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

Вот что я использую, когда застрял на 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. Таким образом, использование этого с double Array[] может не выполнять итерацию по всем элементам. Похоже, шлейфовый тест должен быть одним из двух: i<n или A[i]. Возможно, добавьте примеры использования для ясности.

chux - Reinstate Monica 16.10.2015 20:32

Даже с указателями в моем предыдущем подходе результат выглядит как «неопределенное поведение». Ну что ж. Доверьтесь подходу с двойным циклом!

Watercycle 22.10.2015 04:54

Эта версия загрязняет область видимости и выйдет из строя, если будет использоваться дважды в одной и той же области. Также не работает как блок без скобок (например, if ( bla ) FOREACH(....) { } else....

M.M 06.11.2015 01:15

1, C - это язык загрязнения области видимости, некоторые из нас ограничиваются более старыми компиляторами. 2, не повторяйся / будь описательным. 3, да, к сожалению, у вас ДОЛЖНЫ быть фигурные скобки, если это будет условный цикл for (как правило, люди так и делают). Если у вас есть доступ к компилятору, который поддерживает объявления переменных в цикле for, обязательно сделайте это.

Watercycle 06.11.2015 02:13

@Watercycle: Я взял на себя смелость отредактировать ваш ответ с помощью альтернативной версии FOREACH, которая использует синтаксис c99, чтобы избежать загрязнения пространства имен.

chqrlie 18.08.2019 00:14

Хотя C не имеет a для каждой конструкции, он всегда имел идиоматическое представление для одного за концом массива (&arr)[1]. Это позволяет вам написать простую идиоматику для каждого цикла следующим образом:

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

Если не уверен, что это четко определено. (&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++).

Lundin 05.10.2016 12:22

@Lundin Это хорошо определено. Вы правы, это один массив за концом массива, но этот тип массива преобразуется в указатель в этом контексте (выражение), и этот указатель находится за концом массива.

Steve Cox 05.10.2016 17:09

Если вы планируете работать с указателями на функции

#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 (не уверен).

Как вы, наверное, уже знаете, в 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!).
  • Кажется, работает с любым одномерным массивом
  • Все переменные, созданные в макросе (p, item), не видны за пределами область действия цикла (поскольку они объявлены в заголовке цикла 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; не тестировал другие компиляторы.

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;

Другие вопросы по теме