Czy C ma konstrukcję pętli „foreach”?


110

Prawie wszystkie języki mają foreachpętlę lub coś podobnego. Czy C ma taki? Czy możesz przesłać przykładowy kod?


1
„ foreach” czego?
— alk

Jak trudno byłoby napisać foreachpętlę w programie C?
— MD XF

Odpowiedzi:


195

C nie ma foreach, ale makra są często używane do emulacji tego:

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

I może być używany jak

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

Możliwa jest również iteracja po tablicy:

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

I może być używany jak

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

Edycja: jeśli jesteś również zainteresowany rozwiązaniami C ++, C ++ ma natywną składnię dla każdego o nazwie „zakres oparty na”


1
Jeśli masz operator „typeof” (rozszerzenie gcc; dość powszechne w wielu innych kompilatorach), możesz pozbyć się tego „int *”. Wewnętrzna pętla for zmienia się w coś w rodzaju „for (typeof ((tablica) +0) item = ...” Następnie możesz wywołać jako „foreach (v, values) ...”
— leander

Dlaczego potrzebujemy dwóch pętli for w przykładzie tablicy? Co powiesz na to: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)jednym z problemów, które widzę, jest to, że liczba i rozmiar zmiennych znajdują się poza pętlą for i mogą powodować konflikt. Czy to jest powód, dla którego używasz dwóch dla pętli? [kod wklejony tutaj ( pastebin.com/immndpwS )]
— Lazer

3
@eSKay yes, rozważ if(...) foreach(int *v, values) .... Jeśli są poza pętlą, rozszerza się if(...) int count = 0 ...; for(...) ...;i pęka.
— Johannes Schaub - litb

1
@rem nie przerywa zewnętrznej pętli, jeśli użyjesz „break”
— Johannes Schaub - litb

1
@rem, możesz jednak uprościć mój kod, jeśli zmienisz wewnętrzne „keep =! keep” na „keep = 0”. Podobała mi się „symetria”, więc użyłem po prostu negacji, a nie prostego przypisania.
— Johannes Schaub - litb

11

Oto przykład pełnego programu makra for-each w 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;
}

Co robi kropka w list[]definicji? Nie mógłbyś po prostu napisać nextzamiast tego .next?
— Rizo

9
@Rizo Nie, kropka jest częścią składni wyznaczonych inicjatorów C99 . Zobacz en.wikipedia.org/wiki/C_syntax#Initialization
— sędzia Maygarden

@Rizo: Zwróć również uwagę, że jest to naprawdę hacky sposób tworzenia połączonej listy. To wystarczy , ale w praktyce nie rób tego w ten sposób!
— Donal Fellows

@Donal Co sprawia, że ​​jest to „hacky”?
— Sędzia Maygarden

2
@Judge: Cóż, z jednej strony ma „zaskakujący” czas życia (jeśli pracujesz z kodem, który usuwa elementy, jest szansa, że ​​się zawiesisz free()), az drugiej ma odniesienie do wartości w swojej definicji. To naprawdę przykład czegoś, co jest po prostu cholernie sprytne; kod jest wystarczająco złożony bez celowego dodawania do niego sprytu. Aforyzm Kernighana ( stackoverflow.com/questions/1103299/ ... ) obowiązuje!
— Donal Fellows

9

W C. nie ma foreach.

Możesz użyć pętli for do wykonania pętli przez dane, ale długość musi być znana lub dane muszą być zakończone znaną wartością (np. Null).

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

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

Należy dodać inicjalizację wskaźnika nullTerm na początku zestawu danych. OP może być zdezorientowany co do niepełnej pętli for.
— cschol

Trochę rozwinął przykład.
— Adam Peck

zmieniasz swój oryginalny wskaźnik, zrobiłbym coś takiego: char * s; s = "..."; for (char * it = s; it! = NULL; it ++) {/ * wskazuje na znak * / }
— hiena

6

Jak zapewne już wiesz, w C. nie ma pętli w stylu „foreach”.

Chociaż dostępnych jest już mnóstwo świetnych makr, aby obejść ten problem, być może uznasz to makro za przydatne:

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

... które mogą być używane z for(jak wfor each (...) ).

Zalety tego podejścia:

  • item jest deklarowana i zwiększana w instrukcji for (podobnie jak w Pythonie!).
  • Wydaje się działać na dowolnej 1-wymiarowej tablicy
  • Wszystkie zmienne utworzone w makro ( p, item) nie są widoczne poza zakresem pętli (ponieważ są zadeklarowane w nagłówku pętli for).

Niedogodności:

  • Nie działa w przypadku tablic wielowymiarowych
  • Polega na typeof(), co jest rozszerzeniem GNU, a nie częścią standardowego C.
  • Ponieważ deklaruje zmienne w nagłówku pętli for, działa tylko w C11 lub nowszym.

Aby zaoszczędzić trochę czasu, oto jak możesz to przetestować:

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

Wygląda na to, że domyślnie działa na gcc i clang; nie testowałem innych kompilatorów.


5

To dość stare pytanie, ale pomyślałem, że powinienem to opublikować. Jest to pętla foreach dla 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;
}

Ten kod został przetestowany pod kątem współpracy z gcc, icc i clang w systemie GNU / Linux.


4

Chociaż C nie ma dla każdej konstrukcji, zawsze miał idiomatyczną reprezentację dla jednego poza końcem tablicy (&arr)[1]. Pozwala to na napisanie prostego idiomatycznego dla każdej pętli w następujący sposób:

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

3
Jeśli nie masz pewności, że jest to dobrze zdefiniowane. (&arr)[1]nie oznacza jednego elementu tablicy za końcem tablicy, oznacza to jedną tablicę za końcem tablicy. (&arr)[1]nie jest ostatnim elementem tablicy [0], jest to tablica [1], która rozpada się na wskaźnik do pierwszego elementu (z tablicy [1]). Wierzę, że byłoby znacznie lepiej, bezpieczniej i idiomatyczne zrobić const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);i potem for(const int* a = begin; a != end; a++).
— Lundin

1
@Lundin To jest dobrze zdefiniowane. Masz rację, jest to jedna tablica za końcem tablicy, ale ten typ tablicy jest konwertowany na wskaźnik w tym kontekście (wyrażenie), a ten wskaźnik znajduje się jeden za końcem tablicy.
— Steve Cox

2

C ma słowa kluczowe „for” i „while”. Jeśli instrukcja foreach w języku takim jak C # wygląda tak ...

foreach (Element element in collection)
{
}

... wtedy odpowiednik tej instrukcji foreach w C mógłby wyglądać następująco:

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

1
Należy wspomnieć, że przykładowy kod nie jest napisany w składni C.
— cschol

> Powinieneś wspomnieć, że twój przykładowy kod nie jest napisany w składni C Masz rację, dziękuję: zmienię post.
— ChrisW

@ monjardin-> na pewno możesz po prostu zdefiniować wskaźnik do funkcji w strukturze i nie ma problemu z wykonaniem takiego wywołania.
— Ilya

2

Oto, czego używam, gdy utknąłem w C.Nie możesz użyć tej samej nazwy elementu dwa razy w tym samym zakresie, ale nie jest to tak naprawdę problem, ponieważ nie wszyscy możemy używać nowych, ładnych kompilatorów :(

#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 */

Stosowanie:

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

EDYCJA: Oto alternatywa dla FOREACHużywania składni c99 w celu uniknięcia zanieczyszczenia przestrzeni nazw:

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

Uwaga: VAR(i) < (size) && (item = array[VAR(i)])zatrzyma się, gdy element tablicy będzie miał wartość 0. Więc użycie tego z double Array[]może nie powodować iteracji przez wszystkie elementy. Wygląda na to, że test pętli powinien być jeden lub drugi: i<nlub A[i]. Może dla jasności dodaj przykładowe przypadki użycia.
— chux - Przywróć Monikę

Nawet jeśli zastosowałem wskazówki z mojego poprzedniego podejścia, rezultatem wydaje się być „niezdefiniowane zachowanie”. No cóż. Zaufaj podwójnemu podejściu do pętli!
— Watercycle

Ta wersja zanieczyszcza zakres i zakończy się niepowodzeniem, jeśli zostanie użyta dwukrotnie w tym samym zakresie. Nie działa również jako blok bez stężenia (np.if ( bla ) FOREACH(....) { } else....
— MM

1
1, C to język zanieczyszczenia zakresu, niektórzy z nas ograniczają się do starszych kompilatorów. 2, nie powtarzaj się / bądź opisowy. 3, tak, niestety MUSISZ mieć nawiasy klamrowe, jeśli ma to być warunkowa pętla for (ludzie i tak zwykle to robią). Jeśli masz dostęp do kompilatora, który obsługuje deklaracje zmiennych w pętli for, zrób to.
— Rower wodny

@Watercycle: Pozwoliłem sobie edytować twoją odpowiedź za pomocą alternatywnej wersji, FOREACHktóra używa składni c99, aby uniknąć zanieczyszczenia przestrzeni nazw.
— chqrlie

1

Odpowiedź Erica nie działa, gdy używasz „przerwa” lub „kontynuuj”.

Można to naprawić, przepisując pierwszą linię:

Oryginalna linia (ponownie sformatowana):

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

Naprawiony:

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

Jeśli porównasz to z pętlą Johannesa, zobaczysz, że faktycznie robi to samo, tylko trochę bardziej skomplikowane i brzydsze.


1

Oto prosta, pojedyncza pętla 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

Daje ci dostęp do indeksu, jeśli chcesz ( i) i bieżącego elementu, nad którym iterujemy ( it). Zauważ, że możesz mieć problemy z nazewnictwem podczas zagnieżdżania pętli, możesz ustawić nazwy elementów i indeksów jako parametry makra.

Edycja: Oto zmodyfikowana wersja zaakceptowanej odpowiedzi foreach. Pozwala określić startindeks sizetak, aby działał na zepsutych tablicach (wskaźnikach), bez potrzeby int*i zmieniany count != sizena i < sizewypadek, gdyby użytkownik przypadkowo zmodyfikował „i”, aby był większy niż sizei utknął w nieskończonej pętli.

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

Wynik:

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

1

Jeśli planujesz pracować ze wskaźnikami funkcji

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

Stosowanie:

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

Ale myślę, że to zadziała tylko na gcc (nie jestem pewien).


1

C nie ma implementacji for-each. Podczas analizowania tablicy jako punktu odbiorca nie wie, jak długa jest tablica, dlatego nie ma sposobu, aby stwierdzić, kiedy osiągniesz koniec tablicy. Pamiętaj, w Cint* jest punkt na adres pamięci zawierający int. Nie ma obiektu nagłówka zawierającego informacje o liczbie liczb całkowitych, które są umieszczane w kolejności. Dlatego programista musi to śledzić.

Jednak w przypadku list łatwo jest zaimplementować coś, co przypomina for-eachpętlę.

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

Aby osiągnąć coś podobnego dla tablic, możesz zrobić jedną z dwóch rzeczy.

  1. użyj pierwszego elementu do przechowywania długości tablicy.
  2. zawiń tablicę w strukturę, która przechowuje długość i wskaźnik do tablicy.

Oto przykład takiej struktury:

typedef struct job_t {
   int count;
   int* arr;
} arr_t;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.