C ha un costrutto di ciclo "foreach"?


110

Quasi tutte le lingue hanno un foreachciclo o qualcosa di simile. C ne ha uno? Puoi pubblicare un codice di esempio?


1
" foreach" di cosa?
alk

Quanto sarebbe stato difficile provare a scrivere un foreachciclo in un programma C?
MD XF

Risposte:


195

C non ha un foreach, ma le macro vengono spesso utilizzate per emularlo:

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

E può essere usato come

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

È anche possibile l'iterazione su un array:

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

E può essere usato come

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

Modifica: nel caso in cui sei interessato anche alle soluzioni C ++, C ++ ha una sintassi nativa per ogni chiamata "basata su intervallo per"


1
Se hai l'operatore "typeof" (estensione gcc; abbastanza comune su molti altri compilatori) puoi sbarazzarti di "int *". Il ciclo for interno diventa qualcosa come "for (typeof ((array) +0) item = ..." Quindi puoi chiamare "foreach (v, values) ..."
leander

Perché abbiamo bisogno di due cicli for nell'esempio dell'array? Che ne dici di questo: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)un problema che posso vedere è che le variabili count e size vivono al di fuori del ciclo for e potrebbero causare un conflitto. È questo il motivo per cui usi due cicli for? [codice incollato qui ( pastebin.com/immndpwS )]
Lazer

3
@eSKay yes consider if(...) foreach(int *v, values) .... Se sono al di fuori del ciclo, si espande if(...) int count = 0 ...; for(...) ...;e si romperà.
Johannes Schaub - litb

1
@rem non interrompe il ciclo esterno se usi "break"
Johannes Schaub - litb

1
@rem puoi comunque semplificare il mio codice se cambi il "keep =! keep" interno in "keep = 0". Mi piaceva la "simmetria" quindi ho usato solo la negazione e non l'assegnazione diretta.
Johannes Schaub - litb

11

Ecco un esempio di programma completo di una macro for-each 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;
}

Cosa fa il punto nella list[]definizione? Non potresti semplicemente scrivere nextinvece di .next?
Rizo

9
@Rizo No, il punto fa parte della sintassi per gli inizializzatori designati C99 . Vedi en.wikipedia.org/wiki/C_syntax#Initialization
Judge Maygarden

@Rizo: Nota anche che questo è un modo davvero hacker di costruire un elenco collegato. Funzionerà per questa demo, ma non farlo in questo modo in pratica!
Donal Fellows

@Donal Cosa lo rende "hacky"?
Judge Maygarden

2
@ Judge: Beh, per prima cosa ha una durata "sorprendente" (se stai lavorando con codice che rimuove elementi, è probabile che ti sbagli free()) e per un altro ha un riferimento al valore all'interno della sua definizione. È davvero un esempio di qualcosa che è troppo dannatamente intelligente; il codice è abbastanza complesso senza aggiungervi intenzionalmente intelligenza. Si applica l'aforisma di Kernighan ( stackoverflow.com/questions/1103299/… )!
Donal Fellows

9

Non c'è foreach in C.

È possibile utilizzare un ciclo for per scorrere i dati, ma la lunghezza deve essere nota oppure i dati devono essere terminati da un valore noto (es. Null).

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

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

È necessario aggiungere l'inizializzazione del puntatore nullTerm all'inizio del set di dati. L'OP potrebbe essere confuso riguardo al ciclo for incompleto.
cschol

Ha arricchito un po 'l'esempio.
Adam Peck

stai cambiando il tuo puntatore originale, farei qualcosa del tipo: char * s; s = "..."; for (char * it = s; it! = NULL; it ++) {/ * punta al carattere * / }
Hiena

6

Come probabilmente già saprai, non esiste un ciclo in stile "foreach" in C.

Sebbene ci siano già tonnellate di ottime macro fornite qui per aggirare questo problema, forse troverai utile questa macro:

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

... che può essere utilizzato con for(come in for each (...)).

Vantaggi di questo approccio:

  • item viene dichiarato e incrementato all'interno dell'istruzione for (proprio come in Python!).
  • Sembra funzionare su qualsiasi matrice unidimensionale
  • Tutte le variabili create nella macro ( p, item), non sono visibili al di fuori dell'ambito del ciclo (poiché sono dichiarate nell'intestazione del ciclo for).

svantaggi:

  • Non funziona per array multidimensionali
  • Si basa su typeof(), che è un'estensione GNU, no parte dello standard C
  • Poiché dichiara le variabili nell'intestazione del ciclo for, funziona solo in C11 o versioni successive.

Solo per farti risparmiare un po 'di tempo, ecco come potresti testarlo:

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

Sembra funzionare su gcc e clang per impostazione predefinita; non hanno testato altri compilatori.


5

Questa è una domanda abbastanza vecchia, ma ho pensato di pubblicarla. È un ciclo foreach per 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;
}

Questo codice è stato testato per funzionare con gcc, icc e clang su GNU / Linux.


4

Sebbene C non abbia un costrutto per ogni, ha sempre avuto una rappresentazione idiomatica per uno oltre la fine di un array (&arr)[1]. Ciò ti consente di scrivere un semplice idiomatico per ogni ciclo come segue:

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

3
Se non è così sicuro, questo è ben definito. (&arr)[1]non significa un elemento dell'array oltre la fine dell'array, significa un array oltre la fine dell'array. (&arr)[1]non è l'ultimo elemento dell'array [0], è l'array [1], che decade in un puntatore al primo elemento (dell'array [1]). Credo che sarebbe molto meglio, più sicuro e idiomatico da fare const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);e poi for(const int* a = begin; a != end; a++).
Lundin

1
@ Lundin Questo è ben definito. Hai ragione, è un array dopo la fine dell'array, ma quel tipo di array si converte in un puntatore in questo contesto (un'espressione) e quel puntatore è uno dopo la fine dell'array.
Steve Cox,

2

C contiene le parole chiave "for" e "while". Se un'istruzione foreach in un linguaggio come C # ha questo aspetto ...

foreach (Element element in collection)
{
}

... quindi l'equivalente di questa istruzione foreach in C potrebbe essere come:

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

1
Dovresti menzionare che il tuo codice di esempio non è scritto con la sintassi C.
cschol

> Dovresti menzionare che il tuo codice di esempio non è scritto con la sintassi C Hai ragione, grazie: modifico il post.
ChrisW

@ monjardin-> certo che puoi semplicemente definire il puntatore alla funzione nella struttura e non ci sono problemi a fare la chiamata in questo modo.
Ilya

2

Ecco cosa uso quando sono bloccato con C.Non puoi usare lo stesso nome di elemento due volte nello stesso ambito, ma non è davvero un problema poiché non tutti noi possiamo usare nuovi compilatori carini :(

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

Uso:

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: ecco un'alternativa per l' FOREACHutilizzo della sintassi c99 per evitare l'inquinamento dello spazio dei nomi:

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

Nota: VAR(i) < (size) && (item = array[VAR(i)]) si fermerebbe una volta che l'elemento dell'array aveva un valore di 0. Quindi l'uso di questo con double Array[]potrebbe non iterare su tutti gli elementi. Sembra che il test del ciclo dovrebbe essere uno o l'altro: i<no A[i]. Forse aggiungi casi d'uso di esempio per chiarezza.
chux - Ripristina Monica il

Anche con i puntatori nel mio approccio precedente il risultato sembra essere un "comportamento indefinito". Oh bene. Fidati dell'approccio double for loop!
Watercycle

Questa versione inquina l'ambito e non funzionerà se utilizzata due volte nello stesso ambito. Inoltre non funziona come un blocco senza controventi (ad esempioif ( bla ) FOREACH(....) { } else....
MM

1
1, C è la lingua dell'inquinamento dell'ambito, alcuni di noi sono limitati ai compilatori più vecchi. 2, non ripetere te stesso / sii descrittivo. 3, sì, sfortunatamente DEVI avere le parentesi graffe se sarà un ciclo for condizionale (le persone di solito lo fanno comunque). Se hai accesso a un compilatore che supporta le dichiarazioni di variabili in un ciclo for, fallo con tutti i mezzi.
Watercycle

@Watercycle: mi sono preso la libertà di modificare la tua risposta con una versione alternativa FOREACHche utilizza la sintassi c99 per evitare l'inquinamento dello spazio dei nomi.
chqrlie

1

La risposta di Eric non funziona quando usi "interrompi" o "continua".

Questo può essere risolto riscrivendo la prima riga:

Riga originale (riformattata):

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

Fisso:

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

Se lo confronti con il loop di Johannes, vedrai che in realtà sta facendo lo stesso, solo un po 'più complicato e più brutto.


1

Eccone uno semplice, ciclo for singolo:

#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

Ti dà accesso all'indice se lo desideri ( i) e l'elemento corrente su cui stiamo iterando (it ). Tieni presente che potresti avere problemi di denominazione durante la nidificazione dei loop, puoi fare in modo che i nomi degli elementi e degli indici siano parametri della macro.

Modifica: ecco una versione modificata della risposta accettata foreach. Consente di specificare l' startindice, in sizemodo che funzioni su array decaduti (puntatori), non è necessario int*e modificato count != sizein i < sizesolo nel caso in cui l'utente modifichi accidentalmente 'i' per essere più grande di sizee rimanere bloccato in un ciclo infinito.

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

Produzione:

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

1

Se hai intenzione di lavorare con i puntatori a funzione

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

Uso:

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

Ma penso che funzionerà solo su gcc (non sono sicuro).


1

C non ha un'implementazione di for-each. Quando si analizza un array come un punto, il ricevitore non sa quanto è lungo l'array, quindi non c'è modo di sapere quando si raggiunge la fine dell'array. Ricorda, in Cint* è un punto a un indirizzo di memoria contenente un int. Non esiste alcun oggetto di intestazione contenente informazioni su quanti numeri interi vengono inseriti in sequenza. Pertanto, il programmatore deve tenerne traccia.

Tuttavia, per gli elenchi, è facile implementare qualcosa che assomigli a un for-eachciclo.

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

Per ottenere qualcosa di simile per gli array puoi fare una delle due cose.

  1. usa il primo elemento per memorizzare la lunghezza dell'array.
  2. avvolgere l'array in una struttura che contiene la lunghezza e un puntatore all'array.

Il seguente è un esempio di tale struttura:

typedef struct job_t {
   int count;
   int* arr;
} arr_t;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.