¿C tiene una construcción de bucle "foreach"?


109

Casi todos los idiomas tienen un foreachbucle o algo similar. ¿C tiene uno? ¿Puedes publicar algún código de ejemplo?


1
" foreach" ¿de qué?
Alk

¿Qué tan difícil hubiera sido intentar escribir un foreachbucle en un programa en C?
MD XF

Respuestas:


194

C no tiene un foreach, pero las macros se usan con frecuencia para emular eso:

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

Y se puede usar como

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

La iteración sobre una matriz también es posible:

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

Y se puede usar como

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

Editar: en caso de que también esté interesado en las soluciones de C ++, C ++ tiene una sintaxis nativa para cada llamada llamada "rango basado en"


1
Si tiene el operador "typeof" (extensión gcc; bastante común en muchos otros compiladores) puede deshacerse de ese "int *". El bucle for interno se convierte en algo así como "for (typeof ((array) +0) item = ..." Luego puede llamar como "foreach (v, values) ..."
leander

¿Por qué necesitamos dos bucles for en el ejemplo de matriz? ¿Qué tal esto? #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)Un problema que puedo ver es que las variables cuentan y tamaño viven fuera del ciclo for y pueden causar un conflicto. ¿Es esta la razón por la que usa dos bucles for? [código pegado aquí ( pastebin.com/immndpwS )]
Lazer

3
@eSKay sí, considérelo if(...) foreach(int *v, values) .... Si están fuera del bucle, se expande if(...) int count = 0 ...; for(...) ...;y se rompe.
Johannes Schaub - litb

1
@rem no rompe el bucle exterior si usa "romper"
Johannes Schaub - litb

1
@rem, sin embargo, puedes simplificar mi código si cambias el "keep =! keep" interno por "keep = 0". Me gustó la "simetría", así que utilicé la negación y no la asignación directa.
Johannes Schaub - litb

11

A continuación se muestra un ejemplo de programa completo de una macro para cada 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;
}

¿Qué hace el punto en la list[]definición? ¿No podrías simplemente escribir en nextlugar de .next?
Rizo

9
@Rizo No, el punto es parte de la sintaxis de los inicializadores designados C99 . Ver en.wikipedia.org/wiki/C_syntax#Initialization
Juez Maygarden

@Rizo: Tenga en cuenta también que esa es una forma realmente peligrosa de crear una lista vinculada. Servirá para esta demostración, ¡pero no lo haga de esa manera en la práctica!
Donal Fellows

@Donal ¿Qué lo hace "hacky"?
Juez Maygarden

2
@Judge: Bueno, por un lado tiene una vida útil "sorprendente" (si está trabajando con código que elimina elementos, es probable que se bloquee free()) y por otro tiene una referencia al valor dentro de su definición. Es realmente un ejemplo de algo que es demasiado inteligente; el código es lo suficientemente complejo sin agregarle inteligencia a propósito. ¡Se aplica el aforismo de Kernighan ( stackoverflow.com/questions/1103299/… )!
Donal Fellows

9

No hay foreach en C.

Puede usar un bucle for para recorrer los datos, pero se debe conocer la longitud o los datos deben terminarse con un valor conocido (por ejemplo, nulo).

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

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

Debe agregar la inicialización del puntero nullTerm al comienzo del conjunto de datos. El OP puede estar confundido acerca del bucle for incompleto.
cschol

Desarrolló un poco el ejemplo.
Adam Peck

está cambiando su puntero original, haría algo como: char * s; s = "..."; for (char * it = s; it! = NULL; it ++) {/ * apunta al carácter * / }
hiena

6

Como probablemente ya sepa, no hay un bucle de estilo "foreach" en C.

Aunque ya hay toneladas de macros geniales proporcionadas aquí para solucionar esto, tal vez encuentre útil esta 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)

... que se puede usar con for(como en for each (...)).

Ventajas de este enfoque:

  • item se declara y se incrementa dentro de la declaración for (¡como en Python!).
  • Parece funcionar en cualquier matriz unidimensional
  • Todas las variables creadas en la macro ( p, item) no son visibles fuera del alcance del bucle (ya que están declaradas en el encabezado del bucle for).

Desventajas:

  • No funciona para matrices multidimensionales
  • Se basa en typeof(), que es una extensión GNU, no parte del estándar C
  • Dado que declara variables en el encabezado del bucle for, solo funciona en C11 o posterior.

Solo para ahorrarle algo de tiempo, así es como puede probarlo:

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

Parece funcionar en gcc y clang de forma predeterminada; no he probado otros compiladores.


5

Esta es una pregunta bastante antigua, pero pensé que debería publicar esto. Es un bucle foreach para 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;
}

Este código ha sido probado para funcionar con gcc, icc y clang en GNU / Linux.


4

Si bien C no tiene una para cada construcción, siempre ha tenido una representación idiomática para una más allá del final de una matriz (&arr)[1]. Esto le permite escribir un modismo simple para cada bucle de la siguiente manera:

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

3
Si no está tan seguro, esto está bien definido. (&arr)[1]no significa un elemento de la matriz más allá del final de la matriz, significa una matriz más allá del final de la matriz. (&arr)[1]no es el último elemento de la matriz [0], es la matriz [1], que decae en un puntero al primer elemento (de la matriz [1]). Creo que sería mucho mejor, más seguro e idiomático hacerlo const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);y luego for(const int* a = begin; a != end; a++).
Lundin

1
@Lundin Esto está bien definido. Tiene razón, es una matriz más allá del final de la matriz, pero ese tipo de matriz se convierte en un puntero en este contexto (una expresión), y ese puntero está uno más allá del final de la matriz.
Steve Cox

2

C tiene palabras clave "para" y "mientras". Si una declaración foreach en un lenguaje como C # se ve así ...

foreach (Element element in collection)
{
}

... entonces el equivalente de esta declaración foreach en C podría ser como:

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

1
Debe mencionar que su código de ejemplo no está escrito en sintaxis C.
cschol

> Deberías mencionar que tu código de ejemplo no está escrito en sintaxis C Tienes razón, gracias: editaré la publicación.
ChrisW

@ monjardin-> seguro que puedes definir el puntero para que funcione en la estructura y no hay problema para hacer la llamada de esta manera.
Ilya

2

Esto es lo que uso cuando estoy atascado con C. No puede usar el mismo nombre de elemento dos veces en el mismo alcance, pero eso no es realmente un problema ya que no todos podemos usar compiladores nuevos y agradables :(

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

EDITAR: Aquí hay una alternativa para FOREACHusar la sintaxis c99 para evitar la contaminación del espacio de nombres:

#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)])se detendría una vez que el elemento de la matriz tuviera un valor de 0. Por lo tanto, usar esto con double Array[]puede no iterar a través de todos los elementos. Parece que la prueba de bucle debería ser una u otra: i<no A[i]. Tal vez agregue casos de uso de muestra para mayor claridad.
chux - Reincorporar a Monica

Incluso con los indicadores de mi enfoque anterior, el resultado parece ser un "comportamiento indefinido". Oh bien. ¡Confíe en el enfoque de doble bucle!
Watercycle

Esta versión contamina el alcance y fallará si se usa dos veces en el mismo alcance. Tampoco funciona como un bloque sin refuerzos (por ejemploif ( bla ) FOREACH(....) { } else....
MM

1
1, C es el lenguaje de la contaminación del alcance, algunos de nosotros estamos limitados a compiladores más antiguos. 2, no se repita / sea descriptivo. 3, sí, desafortunadamente DEBE tener aparatos ortopédicos si va a ser un bucle for condicional (la gente generalmente lo hace de todos modos). Si tiene acceso a un compilador que admita declaraciones de variables en un bucle for, hágalo.
Watercycle

@Watercycle: Me tomé la libertad de editar su respuesta con una versión alternativa FOREACHque usa la sintaxis c99 para evitar la contaminación del espacio de nombres.
chqrlie

1

La respuesta de Eric no funciona cuando usa "romper" o "continuar".

Esto se puede solucionar reescribiendo la primera línea:

Línea original (reformateada):

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

Fijo:

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

Si lo comparas con el bucle de Johannes, verás que en realidad él está haciendo lo mismo, solo que un poco más complicado y feo.


1

Aquí hay uno simple, single for loop:

#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

Le da acceso al índice si lo desea ( i) y al elemento actual sobre el que estamos iterando ( it). Tenga en cuenta que puede tener problemas de nomenclatura al anidar bucles, puede hacer que los nombres de los elementos y los índices sean parámetros de la macro.

Editar: aquí hay una versión modificada de la respuesta aceptada foreach. Le permite especificar el startíndice, sizepara que funcione en matrices deterioradas (punteros), sin necesidad int*y cambiado count != sizea i < sizesolo en caso de que el usuario modifique accidentalmente 'i' para que sea más grande que sizey se quede atascado en un bucle 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);

Salida:

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

1

Si planea trabajar con punteros de función

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

Pero creo que esto solo funcionará en gcc (no estoy seguro).


1

C no tiene una implementación de for-each. Al analizar una matriz como un punto, el receptor no sabe cuánto dura la matriz, por lo que no hay forma de saber cuándo llega al final de la matriz. Recuerda, en Cint* hay un punto a una dirección de memoria que contiene un int. No hay ningún objeto de encabezado que contenga información sobre cuántos enteros se colocan en secuencia. Por lo tanto, el programador debe realizar un seguimiento de esto.

Sin embargo, para las listas, es fácil implementar algo que se parezca a un for-eachbucle.

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

Para lograr algo similar para las matrices, puede hacer una de dos cosas.

  1. use el primer elemento para almacenar la longitud de la matriz.
  2. envuelve la matriz en una estructura que contiene la longitud y un puntero a la matriz.

El siguiente es un ejemplo de tal estructura:

typedef struct job_t {
   int count;
   int* arr;
} arr_t;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.