C'nin "foreach" döngü yapısı var mı?


110

Hemen hemen tüm dillerde bir foreachdöngü veya benzer bir şey vardır. C'nin var mı? Örnek bir kod gönderebilir misin?


1
" foreach" neyin?
alk

foreachBir C programında döngü yazmayı denemek ne kadar zor olurdu ?
MD XF

Yanıtlar:


195

C'nin bir foreach'i yoktur, ancak makrolar bunu taklit etmek için sıklıkla kullanılır:

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

Ve gibi kullanılabilir

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

Bir dizi üzerinde yineleme de mümkündür:

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

Ve gibi kullanılabilir

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

Düzenleme: C ++ çözümleriyle de ilgileniyorsanız, C ++ her biri için "aralık tabanlı" adlı yerel bir sözdizimine sahiptir.


1
Eğer "typeof" operatörüne sahipseniz (gcc uzantısı; diğer birçok derleyicide oldukça yaygındır) bu "int *" işlecinden kurtulabilirsiniz. Döngü iç "...) foreach (v, değerler" "(typeof ((dizi) 0) öğe için = ..." O zaman olarak çağırabilirsiniz gibi bir şey olur
Leander

Dizi örneğindeki döngüler için neden ikiye ihtiyacımız var? Şuna ne dersiniz: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)Görebildiğim bir sorun, değişkenlerin sayısının ve boyutunun for döngüsünün dışında yaşaması ve bir çatışmaya neden olabileceğidir. Döngüler için iki kullanmanın nedeni bu mu? [kod buraya yapıştırıldı ( pastebin.com/immndpwS )]
Lazer

3
@eSKay evet düşünün if(...) foreach(int *v, values) .... Döngünün dışındaysa genişler if(...) int count = 0 ...; for(...) ...;ve kırılır.
Johannes Schaub -

1
@rem, "ara" kullanırsanız dış döngüyü kırmaz
Johannes Schaub - litb

1
@rem Bununla birlikte, içteki "keep =! keep" değerini "keep = 0" olarak değiştirirseniz, kodumu basitleştirebilirsiniz. "Simetri" yi sevdim, bu yüzden sadece olumsuzlama kullandım, düz bir ödev değil.
Johannes Schaub - litb

11

İşte C99'daki her makro için tam bir program örneği:

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

Tanımdaki nokta ne işe list[]yarar? nextBunun yerine basitçe yazamaz .nextmısın?
Rizo

9
@Rizo Hayır, nokta, C99 tarafından belirlenen başlatıcıların sözdiziminin bir parçasıdır . Bkz en.wikipedia.org/wiki/C_syntax#Initialization
Hakim Maygarden

@Rizo: Ayrıca bunun bağlantılı bir liste oluşturmanın gerçekten hilekar bir yolu olduğunu unutmayın. Bu demo için yapacağım ama yok pratikte bu şekilde yapmak!
Donal Fellows

@Donal Onu "hacky" yapan nedir?
Yargıç Maygarden

2
@Judge: Bir şey için "şaşırtıcı" bir ömre sahip (öğeleri kaldıran bir kodla çalışıyorsanız, büyük olasılıkla çarpışacaksınız free()) ve bir başkası için tanımının içindeki değere bir referans var. Gerçekten çok zeki olan bir şeye örnek; kod, kasıtlı olarak akıllılık eklemeden yeterince karmaşıktır. Kernighan'ın aforizması ( stackoverflow.com/questions/1103299/… ) geçerlidir!
Donal Fellows

9

C'de foreach yoktur.

Veriler arasında döngü yapmak için bir for döngüsü kullanabilirsiniz, ancak uzunluğun bilinmesi veya verilerin bir bilinen değerle (örn. Null) sonlandırılması gerekir.

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

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

NullTerm işaretçisinin başlatılmasını veri kümesinin başına eklemelisiniz. OP, tamamlanmamış for döngüsü konusunda kafa karıştırabilir.
cschol

Örneği biraz açıkladı.
Adam Peck

orijinal işaretçinizi değiştiriyorsunuz, şöyle bir şey yapacağım: char * s; s = "..."; for (char * it = s; it! = NULL; it ++) {/ * karaktere işaret ediyor * / }
hiena

6

Muhtemelen zaten bildiğiniz gibi, C'de "foreach" tarzı döngü yoktur.

Bu sorunu çözmek için burada zaten çok sayıda harika makro sağlanmış olsa da, belki bu makroyu yararlı bulabilirsiniz:

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

... ile birlikte kullanılabilir for(olduğu gibi for each (...)).

Bu yaklaşımın avantajları:

  • item for ifadesi içinde bildirilir ve artırılır (tıpkı Python'da olduğu gibi!).
  • Herhangi bir 1 boyutlu dizi üzerinde çalışıyor gibi görünüyor
  • Makroda ( p, item) oluşturulan tüm değişkenler döngünün kapsamı dışında görünmez (çünkü bunlar for döngüsü başlığında bildirilir).

Dezavantajları:

  • Çok boyutlu diziler için çalışmıyor
  • Standart C'nin parçası olmayantypeof() bir GNU uzantısı olana güvenir
  • Değişkenleri for döngüsü başlığında bildirdiği için, yalnızca C11 veya sonrasında çalışır.

Size biraz zaman kazandırmak için bunu şu şekilde test edebilirsiniz:

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

Varsayılan olarak gcc ve clang üzerinde çalışıyor gibi görünüyor; diğer derleyicileri test etmedi.


5

Bu oldukça eski bir soru, ama bunu göndermem gerektiğini düşünüyorum. GNU C99 için bir foreach döngüsüdür.

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

Bu kodun GNU / Linux üzerinde gcc, icc ve clang ile çalışması test edilmiştir.


4

C, her yapı için bir yapıya sahip olmasa da, her zaman bir dizinin sonunu geçen biri için deyimsel bir temsile sahip olmuştur (&arr)[1]. Bu, her döngü için aşağıdaki gibi basit bir deyim yazmanıza olanak tanır:

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

3
O kadar emin değilseniz, bunun iyi tanımlandığından emin olun. (&arr)[1]dizinin sonunu geçen bir dizi öğesi anlamına gelmez, dizinin sonunu geçen bir dizi anlamına gelir. (&arr)[1][0] dizisinin son öğesi değil, ilk öğeye (dizi [1]) göstericiye dönüşen [1] dizisidir. Ben çok daha iyi, daha güvenli ve deyimsel yapmak olacağına inanıyorum const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);o zaman ve for(const int* a = begin; a != end; a++).
Lundin

1
@Lundin Bu iyi tanımlanmıştır. Haklısın, dizinin sonundan bir dizi geçiyor, ama bu dizi türü bu bağlamda bir göstericiye (bir ifade) dönüşüyor ve bu işaretçi dizinin sonunu bir geçiyor.
Steve Cox

2

C 'for' ve 'while' anahtar kelimelerine sahiptir. C # gibi bir dilde bir foreach ifadesi şöyle görünüyorsa ...

foreach (Element element in collection)
{
}

... o zaman bu foreach ifadesinin C'deki karşılığı şöyle olabilir:

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

1
Örnek kodunuzun C sözdiziminde yazılmadığını belirtmelisiniz.
cschol

> Örnek kodunuzun C sözdiziminde yazılmadığını söylemelisiniz Haklısınız, teşekkür ederim: Yazıyı düzenleyeceğim.
ChrisW

@ monjardin-> yapı içinde işlev görecek bir gösterici tanımlayabilirsiniz ve bu şekilde çağrıyı yapmakta sorun yoktur.
Ilya

2

İşte C ile takılıp kaldığımda kullandığım şey şu: Aynı öğe adını aynı kapsamda iki kez kullanamazsınız, ancak bu gerçekten bir sorun değil çünkü hepimiz güzel yeni derleyiciler kullanamayız :(

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

Kullanımı:

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

DÜZENLEME:FOREACH Ad alanı kirliliğini önlemek için c99 sözdizimini kullanmanın bir alternatifi :

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

Not: VAR(i) < (size) && (item = array[VAR(i)])dizi öğesi 0 değerine sahip olduğunda durur. Bu nedenle, bunu kullanmak double Array[]tüm öğeleri yinelemeyebilir. Görünüşe göre döngü testi şu veya bu olmalıdır: i<nveya A[i]. Anlaşılır olması için örnek kullanım durumları ekleyebilirsiniz.
chux - Eski Monica

Önceki yaklaşımımdaki işaretçilerle bile, sonuç 'tanımsız davranış' gibi görünüyor. Oh iyi. Çifte döngü yaklaşımına güvenin!
Watercycle

Bu sürüm kapsamı kirletir ve aynı kapsamda iki kez kullanılırsa başarısız olur. Ayrıca çaprazlanmamış bir blok olarak da çalışmaz (örneğinif ( bla ) FOREACH(....) { } else....
MM

1
1, C kapsam kirliliğinin dilidir, bazılarımız eski derleyicilerle sınırlıdır. 2, Kendinizi Tekrar Etmeyin / açıklayıcı olun. 3, evet, maalesef bu bir for döngüsü olacaksa parantezlerinizin olması GEREKİR (insanlar genellikle zaten yapar). Bir for döngüsünde değişken bildirimlerini destekleyen bir derleyiciye erişiminiz varsa, kesinlikle yapın.
Watercycle

@Watercycle: Cevabınızı, FOREACHad alanı kirliliğini önlemek için c99 sözdizimini kullanan alternatif bir sürümüyle düzenleme özgürlüğüne sahip oldum .
chqrlie

1

Eric'in cevabı , "ara" veya "devam et" kullandığınızda işe yaramıyor.

Bu, ilk satırı yeniden yazarak düzeltilebilir:

Orijinal satır (yeniden biçimlendirilmiş):

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

Sabit:

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

Bunu Johannes'in döngüsüyle karşılaştırırsanız, aslında aynı şeyi yaptığını göreceksiniz, sadece biraz daha karmaşık ve daha çirkin.


1

İşte basit bir tane, tek for döngüsü:

#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

İsterseniz dizine ( i) ve üzerinde yinelediğimiz geçerli öğeye ( ) erişmenizi sağlar it. Döngüleri iç içe yerleştirirken adlandırma sorunları yaşayabileceğinizi unutmayın, öğe ve dizin adlarını makronun parametreleri yapabilirsiniz.

Düzenleme: İşte kabul edilen cevabın değiştirilmiş bir versiyonu foreach. Belirttiğiniz Lets startendeksi, sizebu nedenle çürük diziler (işaretçiler) üzerinde çalıştığı, gerek int*ve değiştirilemez count != sizeiçin i < sizeher ihtimale karşı kullanıcı yanlışlıkla değiştirir 'i' den büyük olması sizeve sonsuz döngüye takılıyorum.

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

Çıktı:

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

1

İşlev işaretçileriyle çalışmayı planlıyorsanız

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

Kullanımı:

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

Ancak bunun yalnızca gcc üzerinde çalışacağını düşünüyorum (emin değilim).


1

C'nin bir uygulaması yoktur for-each. Bir diziyi nokta olarak ayrıştırırken, alıcı dizinin ne kadar uzun olduğunu bilmez, bu nedenle dizinin sonuna ne zaman ulaştığınızı söylemenin bir yolu yoktur. Unutma, C'deint* bir int içeren bir bellek adresine bir noktadır. Sırayla kaç tane tamsayı yerleştirildiği hakkında bilgi içeren bir başlık nesnesi yoktur. Bu nedenle, programcının bunu takip etmesi gerekir.

Bununla birlikte, listeler için bir for-eachdöngüye benzeyen bir şey uygulamak kolaydır .

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

Diziler için benzer bir şey elde etmek için iki şeyden birini yapabilirsiniz.

  1. dizinin uzunluğunu saklamak için ilk öğeyi kullanın.
  2. diziyi uzunluğu ve dizinin göstericisini tutan bir yapıya sarın.

Aşağıda bu tür yapılara bir örnek verilmiştir:

typedef struct job_t {
   int count;
   int* arr;
} arr_t;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.