C a-t-il une construction de boucle «foreach»?


110

Presque toutes les langues ont une foreachboucle ou quelque chose de similaire. Est-ce que C en a un? Pouvez-vous publier un exemple de code?


1
" foreach" de quoi?
alk

À quel point cela aurait-il été difficile d'essayer d'écrire une foreachboucle dans un programme C?
MD XF

Réponses:


195

C n'a pas de foreach, mais les macros sont fréquemment utilisées pour émuler cela:

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

Et peut être utilisé comme

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

L'itération sur un tableau est également possible:

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

Et peut être utilisé comme

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

Edit: Si vous êtes également intéressé par les solutions C ++, C ++ a une syntaxe native for-each appelée "range based for"


1
Si vous avez l'opérateur "typeof" (extension gcc; assez courant sur de nombreux autres compilateurs), vous pouvez vous débarrasser de cet "int *". La boucle for interne devient quelque chose comme "for (typeof ((array) +0) item = ..." Ensuite, vous pouvez appeler comme "foreach (v, values) ..."
leander

Pourquoi avons-nous besoin de deux boucles for dans l'exemple de tableau? Que diriez-vous de ceci: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)un problème que je peux voir est que le nombre et la taille des variables vivent en dehors de la boucle for et peuvent provoquer un conflit. Est-ce la raison pour laquelle vous utilisez deux boucles for? [code collé ici ( pastebin.com/immndpwS )]
Lazer

3
@eSKay oui considérez if(...) foreach(int *v, values) .... S'ils sont en dehors de la boucle, il s'agrandit if(...) int count = 0 ...; for(...) ...;et se cassera.
Johannes Schaub - litb

1
@rem il ne rompt pas la boucle extérieure si vous utilisez "break"
Johannes Schaub - litb

1
@rem vous pouvez cependant simplifier mon code si vous changez le "keep =! keep" interne en "keep = 0". J'ai aimé la «symétrie», alors j'ai juste utilisé la négation et non une affectation directe.
Johannes Schaub - litb le

11

Voici un exemple de programme complet d'une macro for-each en 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;
}

Que fait le point dans la list[]définition? Ne pourriez-vous pas simplement écrire à la nextplace de .next?
Rizo

9
@Rizo Non, le point fait partie de la syntaxe des initialiseurs désignés C99 . Voir en.wikipedia.org/wiki/C_syntax#Initialization
Judge Maygarden

@Rizo: Notez également que c'est une manière vraiment pirate de créer une liste chaînée. Cela fera l'affaire pour cette démo, mais ne le faites pas de cette façon en pratique!
Donal Fellows

@Donal Qu'est-ce qui le rend "hacky"?
Judge Maygarden

2
@Judge: Eh bien, d'une part, il a une durée de vie «surprenante» (si vous travaillez avec du code qui supprime des éléments, il y a de fortes chances que vous vous plantiez free()) et d'autre part, il a une référence à la valeur dans sa définition. C'est vraiment un exemple de quelque chose qui est tout simplement trop intelligent; le code est assez complexe sans y ajouter intentionnellement de l'intelligence. L'aphorisme de Kernighan ( stackoverflow.com/questions/1103299/… ) s'applique!
Donal Fellows

9

Il n'y a pas de foreach dans C.

Vous pouvez utiliser une boucle for pour parcourir les données mais la longueur doit être connue ou les données doivent être terminées par une valeur connue (par exemple, null).

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

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

Vous devez ajouter l'initialisation du pointeur nullTerm au début de l'ensemble de données. L'OP peut être confus à propos de la boucle for incomplète.
cschol

Complétez un peu l'exemple.
Adam Peck

vous changez votre pointeur d'origine, je ferais quelque chose comme: char * s; s = "..."; for (char * it = s; it! = NULL; it ++) {/ * il pointe vers le caractère * / }
hiena

6

Comme vous le savez probablement déjà, il n'y a pas de boucle de style "foreach" dans C.

Bien qu'il existe déjà des tonnes de bonnes macros fournies ici pour contourner ce problème, vous trouverez peut-être cette macro utile:

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

... qui peut être utilisé avec for(comme dans for each (...)).

Avantages de cette approche:

  • item est déclaré et incrémenté dans l'instruction for (comme en Python!).
  • Semble fonctionner sur n'importe quel tableau à 1 dimension
  • Toutes les variables créées dans la macro ( p, item), ne sont pas visibles en dehors de la portée de la boucle (puisqu'elles sont déclarées dans l'en-tête de la boucle for).

Désavantages:

  • Ne fonctionne pas pour les tableaux multidimensionnels
  • Dépend de typeof(), qui est une extension GNU, ne fait pas partie du standard C
  • Puisqu'il déclare des variables dans l'en-tête de la boucle for, il ne fonctionne qu'en C11 ou version ultérieure.

Juste pour vous faire gagner du temps, voici comment vous pouvez le tester:

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

Semble fonctionner sur gcc et clang par défaut; n'ont pas testé d'autres compilateurs.


5

C'est une question assez ancienne, mais je pensais que je devrais la poster. C'est une boucle foreach pour 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;
}

Ce code a été testé pour fonctionner avec gcc, icc et clang sous GNU / Linux.


4

Bien que C n'ait pas de pour chaque construction, il a toujours eu une représentation idiomatique pour un après la fin d'un tableau (&arr)[1]. Cela vous permet d'écrire une idiomatique simple pour chaque boucle comme suit:

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

3
Sinon, c'est bien défini. (&arr)[1]ne signifie pas un élément de tableau après la fin du tableau, cela signifie un tableau après la fin du tableau. (&arr)[1]n'est pas le dernier élément du tableau [0], c'est le tableau [1], qui se désintègre en un pointeur vers le premier élément (du tableau [1]). Je pense que ce serait bien mieux, plus sûr et idiomatique à faire const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);et ensuite for(const int* a = begin; a != end; a++).
Lundin

1
@Lundin C'est bien défini. Vous avez raison, c'est un tableau après la fin du tableau, mais ce type de tableau se convertit en un pointeur dans ce contexte (une expression), et ce pointeur est un après la fin du tableau.
Steve Cox

2

C a des mots-clés «for» et «while». Si une instruction foreach dans un langage comme C # ressemble à ceci ...

foreach (Element element in collection)
{
}

... alors l'équivalent de cette instruction foreach en C pourrait être comme:

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

1
Vous devez mentionner que votre exemple de code n'est pas écrit dans la syntaxe C.
cschol

> Vous devez mentionner que votre exemple de code n'est pas écrit en syntaxe C Vous avez raison, merci: je vais éditer l'article.
ChrisW

@ monjardin-> bien sûr, vous pouvez simplement définir le pointeur pour fonctionner dans la structure et il n'y a aucun problème pour faire l'appel comme celui-ci.
Ilya

2

Voici ce que j'utilise quand je suis coincé avec C.Vous ne pouvez pas utiliser le même nom d'élément deux fois dans la même portée, mais ce n'est pas vraiment un problème car nous ne pouvons pas tous utiliser de nouveaux compilateurs sympas :(

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

Usage:

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: Voici une alternative pour FOREACHutiliser la syntaxe c99 pour éviter la pollution de l'espace de noms:

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

Remarque: VAR(i) < (size) && (item = array[VAR(i)])s'arrêterait une fois que l'élément du tableau avait une valeur de 0. Donc, l'utiliser avec double Array[]peut ne pas parcourir tous les éléments. On dirait que le test de boucle devrait être l'un ou l'autre: i<nou A[i]. Ajoutez peut-être des exemples de cas d'utilisation pour plus de clarté.
chux - Réintégrer Monica

Même avec des pointeurs dans mon approche précédente, le résultat semble être un «comportement indéfini». Tant pis. Faites confiance à l'approche double pour la boucle!
Watercycle

Cette version pollue la portée et échouera si elle est utilisée deux fois dans la même portée. Ne fonctionne pas non plus comme un bloc non contreventé (par exempleif ( bla ) FOREACH(....) { } else....
MM

1
1, C est le langage de la pollution de portée, certains d'entre nous sont limités aux anciens compilateurs. 2, ne vous répétez pas / soyez descriptif. 3, oui, malheureusement, vous DEVEZ avoir des accolades s'il s'agit d'une boucle for conditionnelle (les gens le font généralement de toute façon). Si vous avez accès à un compilateur qui prend en charge les déclarations de variables dans une boucle for, faites-le.
Watercycle

@Watercycle: J'ai pris la liberté d'éditer votre réponse avec une version alternative de FOREACHqui utilise la syntaxe c99 pour éviter la pollution de l'espace de noms.
chqrlie

1

La réponse d'Eric ne fonctionne pas lorsque vous utilisez "pause" ou "continuer".

Cela peut être corrigé en réécrivant la première ligne:

Ligne d'origine (reformatée):

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

Fixé:

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

Si vous le comparez à la boucle de Johannes, vous verrez qu'il fait en fait la même chose, juste un peu plus compliqué et plus laid.


1

En voici une simple, une seule boucle 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

Vous donne accès à l'index si vous le souhaitez ( i) et à l'élément actuel sur lequel nous itérons ( it). Notez que vous pouvez rencontrer des problèmes de dénomination lors de l'imbrication de boucles, vous pouvez faire des noms d'élément et d'index des paramètres de la macro.

Edit: Voici une version modifiée de la réponse acceptée foreach. Vous permet de spécifier l' startindex, sizeafin qu'il fonctionne sur des tableaux décomposés (pointeurs), inutile int*et changé count != sizeen i < sizejuste au cas où l'utilisateur modifierait accidentellement `` i '' pour être plus grand que sizeet rester coincé dans une boucle infinie.

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

Production:

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

1

Si vous prévoyez de travailler avec des pointeurs de fonction

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

Usage:

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

Mais je pense que cela ne fonctionnera que sur gcc (pas sûr).


1

C n'a pas d'implémentation de for-each. Lors de l'analyse d'un tableau en tant que point, le récepteur ne sait pas combien de temps dure le tableau, il n'y a donc aucun moyen de savoir quand vous atteignez la fin du tableau. Souvenez-vous qu'en C int*est un point vers une adresse mémoire contenant un int. Il n'y a pas d'objet d'en-tête contenant des informations sur le nombre d'entiers placés en séquence. Ainsi, le programmeur doit garder une trace de cela.

Cependant, pour les listes, il est facile d'implémenter quelque chose qui ressemble à une for-eachboucle.

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

Pour obtenir quelque chose de similaire pour les tableaux, vous pouvez effectuer l'une des deux opérations suivantes.

  1. utilisez le premier élément pour stocker la longueur du tableau.
  2. envelopper le tableau dans une structure qui contient la longueur et un pointeur vers le tableau.

Voici un exemple d'une telle structure:

typedef struct job_t {
   int count;
   int* arr;
} arr_t;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.