Hat C ein “foreach”-Schleifenkonstrukt?

Lesezeit: 9 Minuten

Benutzeravatar der Frage
Frage

Fast alle Sprachen haben eine foreach Schleife oder etwas ähnliches. Hat C einen? Kannst du einen Beispielcode posten?

  • foreach” von was?

    – alk

    6. März 2016 um 11:13 Uhr


  • Wie schwer wäre es gewesen, zu versuchen, a zu schreiben foreach Schleife in einem C-Programm?

    – MDXF

    17. Oktober 2016 um 0:04 Uhr

Johannes Schaub - Benutzerbild der litb
Johannes Schaub – litb

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

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

Und kann gerne verwendet werden

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

Auch eine Iteration über ein Array ist 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 gerne 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, C++ hat eine native for-each-Syntax namens “range based for”.

  • Wenn Sie den “typeof”-Operator (gcc-Erweiterung; bei vielen anderen Compilern ziemlich üblich) haben, können Sie dieses “int *” loswerden. Die innere for-Schleife wird zu etwas wie “for(typeof((array)+0) item = …” Dann können Sie als “foreach( v, values ​​) …” aufrufen.

    – schlanker

    6. August 2009 um 4:46 Uhr

  • @eSKay ja erwägen if(...) foreach(int *v, values) ... . Wenn sie sich außerhalb der Schleife befinden, wird sie erweitert if(...) int count = 0 ...; for(...) ...; und wird brechen.

    – Johannes Schaub – litb

    9. Mai 2010 um 11:37 Uhr

  • @rem es unterbricht die äußere Schleife nicht, wenn Sie “break” verwenden

    – Johannes Schaub – litb

    26. September 2016 um 11:05 Uhr

  • @rem Sie können meinen Code jedoch vereinfachen, wenn Sie das innere “keep = !keep” in “keep = 0” ändern. Ich mochte die “Symmetrie”, also habe ich nur Negation und keine direkte Zuweisung verwendet.

    – Johannes Schaub – litb

    26. September 2016 um 11:12 Uhr

  • @rem ja das ist möglich. Aber nicht tragbar. Typeof ist kein Standard

    – Johannes Schaub – litb

    28. September 2016 um 17:53 Uhr

Hier ist ein vollständiges Programmbeispiel eines for-each-Makros 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;
}

  • Was macht der Punkt in der list[] Definition? Könntest du nicht einfach schreiben next Anstatt von .next?

    – Rizo

    28. Juni 2010 um 10:25 Uhr

  • @Rizo Nein, der Punkt ist Teil der Syntax für C99 designierte Initialisierer. Sehen en.wikipedia.org/wiki/C_syntax#Initialisierung

    – Richter Maygarden

    28. Juni 2010 um 15:27 Uhr


  • @Rizo: Beachten Sie auch, dass dies eine wirklich hackige Art ist, eine verknüpfte Liste zu erstellen. Für diese Demo reicht es aber nicht Mach es in der Praxis so!

    – Donal Fellows

    21. Juli 2010 um 15:18 Uhr

  • @Donal Was macht es “hacky”?

    – Richter Maygarden

    22. Juli 2010 um 13:17 Uhr

  • @Judge: Nun, 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 hat es einen Verweis auf den Wert in seiner Definition. Es ist wirklich ein Beispiel für etwas, das einfach zu verdammt schlau ist; Code ist komplex genug, ohne ihm absichtlich Cleverness hinzuzufügen. Kernighans Aphorismus (stackoverflow.com/questions/1103299/…) trifft zu!

    – Donal Fellows

    22. Juli 2010 um 23:25 Uhr

Benutzeravatar von Saeed Baig
Said Baig

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 vielleicht 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 verwendet werden können for (wie in for each (...)).

Vorteile dieses Ansatzes:

  • item innerhalb der for-Anweisung deklariert und inkrementiert (genau wie in Python!).
  • Scheint auf jedem 1-dimensionalen Array zu funktionieren
  • Alle im Makro erstellten Variablen (p, item), sind außerhalb des Gültigkeitsbereichs der Schleife nicht sichtbar (da sie im Header der for-Schleife deklariert sind).

Nachteile:

  • Funktioniert nicht für mehrdimensionale Arrays
  • Beruht auf typeof()das ist eine GNU-Erweiterung, nicht Teil von Standard C
  • Da es Variablen im Header der for-Schleife deklariert, funktioniert es nur in C11 oder höher.

Um Ihnen etwas Zeit zu sparen, können Sie es 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 auf gcc und clang zu funktionieren; habe keine anderen Compiler getestet.

Benutzeravatar von Adam Peck
Adam Pick

In C gibt es kein foreach.

Sie können eine for-Schleife verwenden, um die Daten zu durchlaufen, aber die Länge muss bekannt sein oder die Daten müssen mit einem 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.
}

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

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

  • Wenn nicht so sicher, ist dies wohldefiniert. (&arr)[1] bedeutet nicht ein Array-Element nach dem Ende des Arrays, sondern ein Array nach dem Ende des Arrays. (&arr)[1] ist nicht das letzte Element des Arrays [0]es ist das Array [1]der in einen Zeiger auf das erste Element (von array [1]). Ich glaube, es wäre viel besser, sicherer und idiomatischer zu tun const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr); und dann for(const int* a = begin; a != end; a++).

    – Ludin

    5. Oktober 2016 um 9:22 Uhr

  • @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 in einen Zeiger (einen Ausdruck) konvertiert, und dieser Zeiger ist eins nach dem Ende des Arrays.

    – Steve Cox

    5. Oktober 2016 um 14:09 Uhr

Benutzeravatar von Joe D
Joe D

Dies ist eine ziemlich alte Frage, aber ich dachte, 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.

  • Wenn nicht so sicher, ist dies wohldefiniert. (&arr)[1] bedeutet nicht ein Array-Element nach dem Ende des Arrays, sondern ein Array nach dem Ende des Arrays. (&arr)[1] ist nicht das letzte Element des Arrays [0]es ist das Array [1]der in einen Zeiger auf das erste Element (von array [1]). Ich glaube, es wäre viel besser, sicherer und idiomatischer zu tun const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr); und dann for(const int* a = begin; a != end; a++).

    – Ludin

    5. Oktober 2016 um 9:22 Uhr

  • @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 in einen Zeiger (einen Ausdruck) konvertiert, und dieser Zeiger ist eins nach dem Ende des Arrays.

    – Steve Cox

    5. Oktober 2016 um 14:09 Uhr

Benutzeravatar von chqrlie
chqrlie

Folgendes verwende ich, wenn ich bei C feststecke. Sie können denselben Elementnamen nicht zweimal im selben Bereich verwenden, aber das ist nicht wirklich ein 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 */

Verwendungszweck:

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 für FOREACH Verwendung der c99-Syntax, um Namespace-Verschmutzung 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)

  • Notiz: VAR(i) < (size) && (item = array[VAR(i)]) würde aufhören, sobald das Array-Element einen Wert von 0 hatte. Verwenden Sie dies also mit double Array[] kann nicht durch alle Elemente iterieren. Scheint, als ob der Schleifentest der eine oder andere sein sollte: i<n oder A[i]. Fügen Sie zur Verdeutlichung möglicherweise Beispielanwendungsfälle hinzu.

    – chux – Wiedereinsetzung von Monica

    16. Oktober 2015 um 17:32 Uhr

  • Selbst mit Hinweisen in meinem vorherigen Ansatz scheint das Ergebnis ein „undefiniertes Verhalten“ zu sein. Nun ja. Vertrauen Sie dem Double-for-Loop-Ansatz!

    – Wasserkreislauf

    22. Oktober 2015 um 1:54 Uhr


  • Diese Version verschmutzt den Geltungsbereich und schlägt fehl, wenn sie zweimal im selben Geltungsbereich verwendet wird. Funktioniert auch nicht als unverspannter Block (zB if ( bla ) FOREACH(....) { } else....

    – MM

    5. November 2015 um 22:15 Uhr

  • 1, C ist die Sprache der Bereichsverschmutzung, einige von uns sind auf ältere Compiler beschränkt. 2, Wiederholen Sie sich nicht / seien Sie beschreibend. 3, ja, leider MÜSSEN Sie Klammern haben, wenn es eine bedingte for-Schleife sein soll (die Leute tun es normalerweise sowieso). Wenn Sie Zugriff auf einen Compiler haben, der Variablendeklarationen in einer for-Schleife unterstützt, tun Sie dies auf jeden Fall.

    – Wasserkreislauf

    5. November 2015 um 23:13 Uhr

  • @Watercycle: Ich habe mir erlaubt, Ihre Antwort mit einer alternativen Version von zu bearbeiten FOREACH die die c99-Syntax verwendet, um Namespace-Verschmutzung zu vermeiden.

    – chqrlie

    17. August 2019 um 21:14 Uhr

1422890cookie-checkHat C ein “foreach”-Schleifenkonstrukt?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy