Hat C ein "foreach" -Schleifenkonstrukt?

108

Fast alle Sprachen haben eine foreachSchleife oder ähnliches. Hat C einen? Können Sie einen Beispielcode posten?

Frage
quelle
1
" foreach" von was?
Alk
Wie schwer wäre es gewesen, eine foreachSchleife in ein C-Programm zu schreiben ?
MD XF

Antworten:

193

C hat kein foreach, aber Makros werden häufig verwendet, um Folgendes zu emulieren:

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

Und kann wie verwendet werden

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

Eine Iteration über ein Array ist ebenfalls möglich:

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

Und kann wie verwendet werden

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

Bearbeiten: Falls Sie auch an C ++ - Lösungen interessiert sind, verfügt C ++ über eine native Syntax für jede Syntax namens "range based for".

Johannes Schaub - litb
quelle
1
Wenn Sie den Operator "typeof" haben (gcc-Erweiterung; bei vielen anderen Compilern ziemlich häufig), können Sie diesen "int *" entfernen. Die innere for-Schleife wird so etwas wie "for (typeof ((array) +0) item = ...". Dann können Sie als "foreach (v, values) ..." aufrufen
leander
Warum brauchen wir im Array-Beispiel zwei for-Schleifen? Wie wäre es damit: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)Ein Problem, das ich sehen kann, ist, dass die Anzahl und Größe der Variablen außerhalb der for-Schleife liegen und einen Konflikt verursachen können. Verwenden Sie deshalb zwei for-Schleifen? [Code hier eingefügt ( pastebin.com/immndpwS )]
Lazer
3
@eSKay ja überlegen if(...) foreach(int *v, values) .... Wenn sie sich außerhalb der Schleife befinden, dehnt sie sich aus if(...) int count = 0 ...; for(...) ...;und bricht ab.
Johannes Schaub - litb
1
@rem es bricht nicht die äußere Schleife, wenn Sie "break"
Johannes Schaub - litb
1
@rem Sie können jedoch meinen Code vereinfachen, wenn Sie das innere "keep =! keep" in "keep = 0" ändern. Ich mochte die "Symmetrie", also habe ich nur die Negation und nicht die direkte Zuordnung verwendet.
Johannes Schaub - litb
11

Hier ist ein vollständiges Programmbeispiel für jedes Makro in 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;
}
Richter Maygarden
quelle
Was macht der Punkt in der list[]Definition? Könntest du nicht einfach schreiben nextstatt .next?
Rizo
9
@Rizo Nein, der Punkt ist Teil der Syntax für C99-Initialisierer . Siehe en.wikipedia.org/wiki/C_syntax#Initialization
Richter Maygarden
@Rizo: Beachten Sie auch, dass dies eine wirklich hackige Art ist, eine verknüpfte Liste zu erstellen. Es wird für diese Demo tun , aber nicht tut es auf diese Weise in der Praxis!
Donal Fellows
@Donal Was macht es "hacky"?
Richter Maygarden
2
@Judge: Zum einen hat es eine „überraschende“ Lebensdauer (wenn Sie mit Code arbeiten, der Elemente entfernt, besteht die Möglichkeit, dass Sie abstürzen free()), und zum anderen verweist es auf den Wert in seiner Definition. Es ist wirklich ein Beispiel für etwas, das einfach zu schlau ist. Code ist komplex genug, ohne ihm absichtlich Klugheit zu verleihen. Kernighans Aphorismus ( stackoverflow.com/questions/1103299/… ) gilt!
Donal Fellows
9

Es gibt kein foreach in C.

Sie können eine for-Schleife verwenden, um die Daten zu durchlaufen, aber die Länge muss bekannt sein oder die Daten müssen durch einen bekannten Wert (z. B. null) abgeschlossen werden.

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

for(;nullTerm != NULL;nullTerm++)
{
    //nullTerm will now point to the next character.
}
Adam Peck
quelle
Sie sollten die Initialisierung des nullTerm-Zeigers am Anfang des Datensatzes hinzufügen. Das OP ist möglicherweise verwirrt über die unvollständige for-Schleife.
Cschol
Das Beispiel ein wenig ausgearbeitet.
Adam Peck
Wenn Sie Ihren ursprünglichen Zeiger ändern, würde ich etwas tun wie: char * s; s = "..."; for (char * it = s; it! = NULL; it ++) {/ * zeigt auf das Zeichen * / }
Hiena
6

Wie Sie wahrscheinlich bereits wissen, gibt es in C keine Schleife im "foreach" -Stil.

Obwohl hier bereits unzählige großartige Makros bereitgestellt werden, um dies zu umgehen, finden Sie dieses Makro möglicherweise nützlich:

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

... die mit for(wie in for each (...)) verwendet werden können.

Vorteile dieses Ansatzes:

  • item wird innerhalb der for-Anweisung deklariert und inkrementiert (genau wie in Python!).
  • Scheint auf jedem eindimensionalen Array zu funktionieren
  • Alle in macro ( p, item) erstellten Variablen sind außerhalb des Bereichs der Schleife nicht sichtbar (da sie im for-Schleifenheader deklariert sind).

Nachteile:

  • Funktioniert nicht für mehrdimensionale Arrays
  • Verlässt sich auf typeof()eine GNU-Erweiterung, die nicht Teil von Standard C ist
  • Da es Variablen im for-Schleifen-Header deklariert, funktioniert es nur in C11 oder höher.

Um Ihnen Zeit zu sparen, können Sie dies folgendermaßen testen:

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

Scheint standardmäßig mit gcc und clang zu arbeiten; habe andere Compiler nicht getestet.

Saeed Baig
quelle
5

Dies ist eine ziemlich alte Frage, aber ich denke, ich sollte dies posten. Es ist eine foreach-Schleife für 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;
}

Dieser Code wurde getestet, um mit gcc, icc und clang unter GNU / Linux zu funktionieren.

Joe D.
quelle
4

Während C nicht für jedes Konstrukt ein hat, hatte es immer eine idiomatische Darstellung für eines nach dem Ende eines Arrays (&arr)[1]. Auf diese Weise können Sie für jede Schleife wie folgt eine einfache Redewendung schreiben:

int arr[] = {1,2,3,4,5};
for(int *a = arr; a < (&arr)[1]; ++a)
    printf("%d\n", *a);
Steve Cox
quelle
3
Wenn nicht so sicher, ist dies genau definiert. (&arr)[1]bedeutet nicht, dass ein Array-Element nach dem Ende des Arrays liegt, sondern dass ein Array nach dem Ende des Arrays liegt. (&arr)[1]ist nicht das letzte Element des Arrays [0], sondern das Array [1], das in einen Zeiger auf das erste Element (des Arrays [1]) zerfällt. Ich glaube, es wäre viel besser, sicherer und idiomatischer const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);und dann for(const int* a = begin; a != end; a++).
Lundin
1
@Lundin Das ist gut definiert. Sie haben Recht, es ist ein Array nach dem Ende des Arrays, aber dieser Array-Typ wird in diesem Kontext (ein Ausdruck) in einen Zeiger konvertiert, und dieser Zeiger befindet sich nach dem Ende des Arrays.
Steve Cox
2

C hat die Schlüsselwörter 'for' und 'while'. Wenn eine foreach-Anweisung in einer Sprache wie C # so aussieht ...

foreach (Element element in collection)
{
}

... dann könnte das Äquivalent dieser foreach-Anweisung in C wie folgt lauten:

for (
    Element* element = GetFirstElement(&collection);
    element != 0;
    element = GetNextElement(&collection, element)
    )
{
    //TODO: do something with this element instance ...
}
ChrisW
quelle
1
Sie sollten erwähnen, dass Ihr Beispielcode nicht in C-Syntax geschrieben ist.
Cschol
> Sie sollten erwähnen, dass Ihr Beispielcode nicht in C-Syntax geschrieben ist. Sie haben Recht, danke: Ich werde den Beitrag bearbeiten.
ChrisW
@ monjardin-> sicher, dass Sie nur den Zeiger auf die Funktion in der Struktur definieren können und es kein Problem gibt, den Aufruf so auszuführen.
Ilya
2

Folgendes verwende ich, wenn ich bei C stecke. Sie können nicht denselben Elementnamen zweimal im selben Bereich verwenden, aber das ist kein wirkliches Problem, da nicht alle von uns nette neue Compiler verwenden können :(

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

Verwendung:

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

BEARBEITEN: Hier ist eine Alternative zur FOREACHVerwendung der c99-Syntax, um eine Verschmutzung des Namespace zu vermeiden:

#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)
Wasserkreislauf
quelle
Hinweis: VAR(i) < (size) && (item = array[VAR(i)])würde aufhören, sobald das Array-Element den Wert 0 hat. Wenn Sie dies also mit verwenden, werden double Array[]möglicherweise nicht alle Elemente durchlaufen. Scheint, als sollte der Schleifentest der eine oder andere sein: i<noder A[i]. Fügen Sie zur Verdeutlichung möglicherweise Anwendungsbeispiele hinzu.
chux
Selbst mit Zeigern in meinem vorherigen Ansatz scheint das Ergebnis "undefiniertes Verhalten" zu sein. Naja. Vertrauen Sie dem Double-for-Loop-Ansatz!
Watercycle
Diese Version verschmutzt den Bereich und schlägt fehl, wenn sie zweimal im selben Bereich verwendet wird. Funktioniert auch nicht als nicht verspannter Block (zBif ( bla ) FOREACH(....) { } else....
MM
1
1, C ist die Sprache der Scope-Verschmutzung, einige von uns sind auf ältere Compiler beschränkt. 2, wiederhole dich nicht / sei beschreibend. 3, ja, leider MÜSSEN Sie Zahnspangen haben, wenn dies eine Bedingung für eine Schleife sein soll (die Leute tun dies normalerweise sowieso). Wenn Sie Zugriff auf einen Compiler haben, der Variablendeklarationen in einer for-Schleife unterstützt, tun Sie dies auf jeden Fall.
Watercycle
@Watercycle: Ich habe mir erlaubt, Ihre Antwort mit einer alternativen Version zu bearbeiten FOREACH, die die c99-Syntax verwendet, um eine Verschmutzung des Namespace zu vermeiden.
Chqrlie
1

Erics Antwort funktioniert nicht, wenn Sie "break" oder "continue" verwenden.

Dies kann durch Umschreiben der ersten Zeile behoben werden:

Originalzeile (neu formatiert):

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

Fest:

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

Wenn Sie es mit Johannes 'Schleife vergleichen, werden Sie sehen, dass er tatsächlich dasselbe tut, nur ein bisschen komplizierter und hässlicher.

Michael Blurb
quelle
1

Hier ist eine einfache Single-for-Schleife:

#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

Ermöglicht Ihnen den Zugriff auf den Index, falls Sie ihn möchten ( i) und das aktuelle Element, über das wir iterieren ( it). Beachten Sie, dass beim Verschachteln von Schleifen möglicherweise Namensprobleme auftreten. Sie können festlegen, dass die Element- und Indexnamen Parameter für das Makro sind.

Bearbeiten: Hier ist eine modifizierte Version der akzeptierten Antwort foreach. Hiermit können Sie den startIndex angeben , sizedamit er auf verfallenen Arrays (Zeigern) funktioniert, ohne dass dies erforderlich ist int*und geändert wird count != size, i < sizefalls der Benutzer versehentlich 'i' so ändert, dass es größer als ist sizeund in einer Endlosschleife stecken bleibt.

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

Ausgabe:

index: 2. element: 3
index: 3. element: 4
index: 4. element: 5
ärgerlich
quelle
1

Wenn Sie mit Funktionszeigern arbeiten möchten

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

Verwendung:

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

Aber ich denke, das wird nur auf gcc funktionieren (nicht sicher).

Naheel
quelle
1

C hat keine Implementierung von for-each. Wenn Sie ein Array als Punkt analysieren, weiß der Empfänger nicht, wie lang das Array ist. Daher können Sie nicht feststellen, wann Sie das Ende des Arrays erreichen. Denken Sie daran, in C.int* ein Punkt auf eine Speicheradresse steht, die ein int enthält. Es gibt kein Header-Objekt, das Informationen darüber enthält, wie viele Ganzzahlen nacheinander platziert werden. Daher muss der Programmierer dies verfolgen.

Für Listen ist es jedoch einfach, etwas zu implementieren, das einer for-eachSchleife ähnelt .

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

Um etwas Ähnliches für Arrays zu erreichen, können Sie eines von zwei Dingen tun.

  1. Verwenden Sie das erste Element, um die Länge des Arrays zu speichern.
  2. Wickeln Sie das Array in eine Struktur, die die Länge und einen Zeiger auf das Array enthält.

Das Folgende ist ein Beispiel für eine solche Struktur:

typedef struct job_t {
   int count;
   int* arr;
} arr_t;
Jonas
quelle