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.