Чи має конструкцію циклу “foreach”?


110

Майже всі мови мають foreachцикл або щось подібне. У С є такий? Чи можете ви опублікувати якийсь приклад коду?


1
" foreach" з чого?
алк

Наскільки важко було б спробувати написати foreachцикл у програмі C?
MD XF

Відповіді:


195

У C немає передбачення, але макроси часто використовуються для імітації того:

#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 ++ має власний синтаксис для кожного, який називається "діапазон, заснований на"


1
Якщо у вас є оператор "typeof" (розширення gcc; досить поширене для багатьох інших компіляторів), ви можете позбутися цього "int *". Внутрішня для циклу стає чимось на зразок "for (typeof ((масив) +0) item = ..." Тоді ви можете зателефонувати як "foreach (v, значень) ..."
leander

Чому нам потрібні дві для циклів у прикладі масиву? Як щодо цього: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)Одна з проблем, яку я бачу, полягає в тому, що кількість змінних і розміри змінних живуть поза циклом для циклу і можуть спричинити конфлікт. Це причина, що ви використовуєте дві для циклів? [код, вставлений тут ( pastebin.com/immndpwS )]
Lazer

3
@eSKay так врахуйте if(...) foreach(int *v, values) .... Якщо вони знаходяться за межами циклу, вона розшириться до if(...) int count = 0 ...; for(...) ...;і зламається.
Йоханнес Шауб - ліб

1
@rem це не порушує зовнішню петлю, якщо ви використовуєте "break"
Йоханнес Шауб - litb

1
@rem, однак, ви можете спростити мій код, якщо ви зміните внутрішнє "Keep =! Keep" на "Keep = 0". Мені сподобалась "симетрія", тому я просто використовував заперечення, а не пряме призначення.
Йоханнес Шауб - ліб

11

Ось повний приклад програми для кожного макросу в 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?
Різо

9
@Rizo Ні, точка є частиною синтаксису призначених C99 ініціалізаторів . Дивіться en.wikipedia.org/wiki/C_syntax#Initialization
суддя Мейґарден

@Rizo: Зауважте також, що це справді хакізний спосіб створення пов’язаного списку. Це буде зроблено для цієї демонстрації, але не робіть цього на практиці!
Стипендіати Доналу

@Donal Що робить його "хакітним"?
Суддя Мейґарден

2
@Judge: Ну, для однієї речі він має "дивовижний" термін експлуатації (якщо ви працюєте з кодом, який видаляє елементи, швидше за все, ви потрапите в аварію free()), а для іншого він має посилання на значення всередині свого визначення. Це справді приклад чогось, що є занадто проклятим розумним; код досить складний без цілеспрямованого додавання йому кмітливості. Афоризм Керніган ( stackoverflow.com/questions/1103299/… ) застосовується!
стипендіати Доналу

9

У С. немає жодного проповіду.

Ви можете використовувати цикл for для циклічного перегляду даних, але довжина повинна бути відомою або дані повинні бути припинені значенням знань (наприклад, null).

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

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

Ви повинні додати ініціалізацію покажчика nullTerm до початку набору даних. ОП може бути замішана щодо неповного циклу.
cschol

Трохи обробив приклад.
Адам Пек

ви змінюєте свій початковий вказівник, я б зробив щось на кшталт: char * s; s = "..."; для (char * it = s; it! = NULL; it ++) {/ * він вказує на символ * / }
hiena

6

Як ви, напевно, вже знаєте, в циклі немає циклу "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), не видно поза межами циклу (оскільки вони оголошені в заголовку для циклу).

Недоліки:

  • Не працює для багатовимірних масивів
  • Покладається на те 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.

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


4

Хоча C не має для кожної конструкції, вона завжди мала ідіоматичне зображення для одного минулого кінця масиву (&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 має ключові слова "для" та "поки". Якщо заява foreach такою мовою, як C #, виглядає так ...

foreach (Element element in collection)
{
}

... тоді еквівалент цього твердження передбачення в C може бути таким:

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);

EDIT: Ось альтернатива 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

Навіть з покажчиками в моєму попередньому підході результат здається «невизначеною поведінкою». Що ж, добре. Довіряйте подвійному для циклу підходу!
Водяний цикл

Ця версія забруднює сферу застосування, і якщо вона буде використана двічі в одному і тому ж просторі, вона не працює. Також не працює як необмежений блок (напр.if ( bla ) FOREACH(....) { } else....
MM

1
1, С - мова забруднення сфери дії, деякі з нас обмежуються старими компіляторами. 2, Не повторюйте себе / будьте описовими. 3 так, так, на жаль, у вас ОБ'ЄДНІ бути брекети, якщо це буде умовно для циклу (люди зазвичай так чи інакше). Якщо у вас є доступ до компілятора, який підтримує оголошення змінних у циклі for для циклу, обов'язково зробіть це.
Водний цикл

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

1

Відповідь Еріка не працює, коли ви використовуєте "перерва" або "продовжити".

Це можна виправити, переписавши перший рядок:

Оригінальний рядок (переформатований):

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)

Якщо порівнювати його з циклом Йоганнеса, ви побачите, що він насправді робить те саме, лише трохи складніше і потворніше.


1

Ось простий, одинарний для циклу:

#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всякий випадок, якщо користувач випадково змінить 'я', щоб бути більшим, 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. Під час аналізу масиву як точки приймач не знає, як довго триває масив, тому немає можливості сказати, коли ви досягнете кінця масиву. Пам'ятайте, в С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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.