C tem uma construção de loop “foreach”?

110

Quase todas as linguagens possuem um foreachloop ou algo semelhante. C tem um? Você pode postar algum código de exemplo?

Questão
fonte
1
" foreach" de quê?
alk
Seria muito difícil tentar escrever um foreachloop em um programa C?
MD XF

Respostas:

195

C não tem um foreach, mas as macros são freqüentemente usadas para emular que:

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

E pode ser usado como

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

A iteração sobre uma matriz também é possível:

#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 pode ser usado como

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

Editar: caso você também esteja interessado em soluções C ++, C ++ tem uma sintaxe nativa para cada chamada "intervalo baseado em"

Johannes Schaub - litb
fonte
1
Se você tem o operador "typeof" (extensão gcc; bastante comum em muitos outros compiladores), você pode se livrar desse "int *". O loop for interno torna-se algo como "for (typeof ((array) +0) item = ..." Então você pode chamar como "foreach (v, values) ..."
leander
Por que precisamos de dois loops for no exemplo de array? Que tal isto: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)um problema que posso ver é que a contagem e o tamanho das variáveis ​​residem fora do loop for e podem causar um conflito. É este o motivo de você usar dois loops for? [código colado aqui ( pastebin.com/immndpwS )]
Lazer
3
@eSKay sim, considere if(...) foreach(int *v, values) .... Se eles estiverem fora do loop, ele se expandirá if(...) int count = 0 ...; for(...) ...;e será interrompido.
Johannes Schaub - litb
1
@rem não quebra o loop externo se você usar "break"
Johannes Schaub - litb
1
@rem, no entanto, você pode simplificar meu código se alterar o interno "keep =! keep" para "keep = 0". Eu gostei da "simetria", então usei apenas negação e não atribuição direta.
Johannes Schaub - litb
11

Aqui está um exemplo de programa completo de uma macro para cada em 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;
}
Juiz Maygarden
fonte
O que o ponto faz na list[]definição? Você não poderia simplesmente escrever em nextvez de .next?
Rizo
9
@Rizo Não, o ponto é uma parte da sintaxe dos inicializadores designados do C99 . Ver en.wikipedia.org/wiki/C_syntax#Initialization
Juiz Maygarden
@Rizo: Observe também que essa é uma maneira realmente hacky de construir uma lista vinculada. Isso servirá para esta demonstração, mas não faça dessa forma na prática!
Donal Fellows
@Donal O que o torna "hacky"?
Juiz Maygarden
2
@Judge: Bem, por um lado ele tem um tempo de vida “surpreendente” (se você estiver trabalhando com código que remove elementos, é provável que você caia nele free()) e por outro tem uma referência ao valor dentro de sua definição. É realmente um exemplo de algo que é muito inteligente; o código é complexo o suficiente sem adicionar inteligência propositalmente a ele. O aforismo de Kernighan ( stackoverflow.com/questions/1103299/… ) se aplica!
Donal Fellows
9

Não há foreach em C.

Você pode usar um loop for para percorrer os dados, mas o comprimento precisa ser conhecido ou os dados precisam ser encerrados por um valor conhecido (por exemplo, nulo).

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

for(;nullTerm != NULL;nullTerm++)
{
    //nullTerm will now point to the next character.
}
Adam Peck
fonte
Você deve adicionar a inicialização do ponteiro nullTerm ao início do conjunto de dados. O OP pode ser confuso sobre o loop for incompleto.
cschol
Acabei um pouco com o exemplo.
Adam Peck
você está mudando seu ponteiro original, eu faria algo como: char * s; s = "..."; para (char * it = s; it! = NULL; it ++) {/ * ele aponta para o caractere * / }
hiena
6

Como você provavelmente já sabe, não há loop no estilo "foreach" em C.

Embora já existam muitas macros excelentes fornecidas aqui para contornar isso, talvez você ache esta macro útil:

// "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 pode ser usado com for(como em for each (...)).

Vantagens desta abordagem:

  • item é declarado e incrementado dentro da instrução for (assim como no Python!).
  • Parece funcionar em qualquer matriz unidimensional
  • Todas as variáveis ​​criadas na macro ( p, item) não são visíveis fora do escopo do loop (já que são declaradas no cabeçalho do loop for).

Desvantagens:

  • Não funciona para arrays multidimensionais
  • Depende de typeof(), que é uma extensão GNU, não faz parte do padrão C
  • Uma vez que declara variáveis ​​no cabeçalho do loop for, só funciona em C11 ou posterior.

Só para economizar seu tempo, veja como você pode testá-lo:

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 no gcc e clang por padrão; não testei outros compiladores.

Saeed Baig
fonte
5

Esta é uma pergunta bastante antiga, mas eu pensei que deveria postar isto. É um loop 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 foi testado para funcionar com gcc, icc e clang no GNU / Linux.

Joe D
fonte
4

Embora C não tenha um para cada construção, sempre teve uma representação idiomática para um após o final de um array (&arr)[1]. Isso permite que você escreva uma linguagem idiomática simples para cada loop da seguinte maneira:

int arr[] = {1,2,3,4,5};
for(int *a = arr; a < (&arr)[1]; ++a)
    printf("%d\n", *a);
Steve Cox
fonte
3
Se não tiver certeza, isso está bem definido. (&arr)[1]não significa um item do array após o final do array, significa um array após o final do array. (&arr)[1]não é o último item da matriz [0], é a matriz [1], que se transforma em um ponteiro para o primeiro elemento (da matriz [1]). Eu acredito que seria muito melhor, mais seguro e idiomático de fazer const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);e então for(const int* a = begin; a != end; a++).
Lundin,
1
@Lundin Isso está bem definido. Você está certo, é um array após o final do array, mas esse tipo de array se converte em um ponteiro neste contexto (uma expressão), e esse ponteiro está um após o final do array.
Steve Cox
2

C tem palavras-chave 'para' e 'enquanto'. Se uma instrução foreach em uma linguagem como C # for assim ...

foreach (Element element in collection)
{
}

... então o equivalente a esta instrução foreach em C pode ser como:

for (
    Element* element = GetFirstElement(&collection);
    element != 0;
    element = GetNextElement(&collection, element)
    )
{
    //TODO: do something with this element instance ...
}
ChrisW
fonte
1
Você deve mencionar que seu código de exemplo não foi escrito na sintaxe C.
cschol
> Você deve mencionar que seu código de exemplo não está escrito na sintaxe C. Você está certo, obrigado: vou editar o post.
ChrisW
@ monjardin-> certeza de que você pode apenas definir um ponteiro para função na estrutura e não há nenhum problema em fazer a chamada dessa forma.
Ilya
2

Aqui está o que eu uso quando estou preso com C. Você não pode usar o mesmo nome de item duas vezes no mesmo escopo, mas isso não é realmente um problema, pois nem todos nós conseguimos usar novos compiladores legais :(

#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: Aqui está uma alternativa para FOREACHusar a sintaxe c99 para evitar poluição do namespace:

#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)
Ciclo da água
fonte
Nota: VAR(i) < (size) && (item = array[VAR(i)])iria parar quando o elemento da matriz tivesse um valor 0. Portanto, usar isso com double Array[]pode não iterar por todos os elementos. Parece que o teste de loop deve ser um ou outro: i<nou A[i]. Talvez adicione casos de uso de amostra para maior clareza.
chux - Reintegrar Monica em
Mesmo com sugestões em minha abordagem anterior, o resultado parece ser um 'comportamento indefinido'. Ah bem. Confie na abordagem double for loop!
Watercycle,
Esta versão polui o escopo e falhará se usada duas vezes no mesmo escopo. Também não funciona como um bloco não reforçado (por exemploif ( bla ) FOREACH(....) { } else....
MM
1
1, C é a linguagem da poluição do escopo, alguns de nós estão limitados a compiladores mais antigos. 2, Não se repita / seja descritivo. 3, sim, infelizmente você DEVE ter colchetes se for um loop for condicional (as pessoas geralmente usam de qualquer maneira). Se você tiver acesso a um compilador que suporte declarações de variáveis ​​em um loop for, faça-o com certeza.
Watercycle
@Watercycle: Tomei a liberdade de editar sua resposta com uma versão alternativa FOREACHque usa a sintaxe c99 para evitar poluição do namespace.
chqrlie de
1

A resposta de Eric não funciona quando você está usando "break" ou "continue".

Isso pode ser corrigido reescrevendo a primeira linha:

Linha original (reformatada):

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

Fixo:

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

Se você comparar com o loop de Johannes, verá que ele está realmente fazendo o mesmo, só que um pouco mais complicado e feio.

Michael Blurb
fonte
1

Aqui está um simples, único loop 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

Fornece acesso ao índice, caso você queira ( i) e ao item atual sobre o qual estamos iterando ( it). Observe que você pode ter problemas de nomenclatura ao aninhar loops, você pode fazer com que os nomes dos itens e índices sejam parâmetros para a macro.

Editar: aqui está uma versão modificada da resposta aceita foreach. Permite que você especifique o startíndice, o sizepara que funcione em matrizes decadentes (ponteiros), sem necessidade de int*e alterado count != sizepara i < sizeapenas no caso de o usuário modificar acidentalmente 'i' para ser maior que sizee ficar preso em um loop 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);

Resultado:

index: 2. element: 3
index: 3. element: 4
index: 4. element: 5
vexe
fonte
1

Se você está planejando trabalhar com ponteiros de função

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

Mas acho que isso funcionará apenas no gcc (não tenho certeza).

Naheel
fonte
1

C não tem uma implementação de for-each. Ao analisar um array como um ponto, o receptor não sabe o tamanho do array, portanto, não há como saber quando você chega ao final do array. Lembre-se, em C int*é um ponto para um endereço de memória contendo um int. Não há nenhum objeto de cabeçalho contendo informações sobre quantos inteiros são colocados em sequência. Portanto, o programador precisa acompanhar isso.

No entanto, para listas, é fácil implementar algo que se pareça com um for-eachloop.

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

Para obter algo semelhante para arrays, você pode fazer uma das duas coisas.

  1. use o primeiro elemento para armazenar o comprimento da matriz.
  2. envolva a matriz em uma estrutura que contém o comprimento e um ponteiro para a matriz.

A seguir está um exemplo de tal estrutura:

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