Apakah C memiliki konstruksi loop "foreach"?

110

Hampir semua bahasa memiliki foreachlingkaran atau sesuatu yang serupa. Apakah C punya? Bisakah Anda memposting beberapa kode contoh?

Pertanyaan
sumber
1
" foreach" dari apa?
alk
Seberapa sulit mencoba menulis foreachloop dalam program C?
MD XF

Jawaban:

195

C tidak memiliki foreach, tetapi makro sering digunakan untuk meniru bahwa:

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

Dan bisa digunakan seperti

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

Iterasi melalui larik juga dimungkinkan:

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

Dan bisa digunakan seperti

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

Edit: Jika Anda juga tertarik dengan solusi C ++, C ++ memiliki native untuk setiap sintaks yang disebut "berbasis rentang"

Johannes Schaub - litb
sumber
1
Jika Anda memiliki operator "typeof" (ekstensi gcc; cukup umum pada banyak kompiler lain) Anda dapat membuang "int *" itu. Bagian dalam for loop menjadi seperti "for (typeof ((array) +0) item = ..." Kemudian Anda dapat memanggil "foreach (v, values) ..."
leander
Mengapa kita membutuhkan dua loop untuk contoh array? Bagaimana dengan ini: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)Satu masalah yang dapat saya lihat adalah bahwa jumlah dan ukuran variabel berada di luar loop for, dan mungkin menyebabkan konflik. Apakah ini alasan Anda menggunakan dua untuk loop? [kode ditempel di sini ( pastebin.com/immndpwS )]
Lazer
3
@eSKay ya pertimbangkan if(...) foreach(int *v, values) .... Jika mereka berada di luar loop maka akan mengembang if(...) int count = 0 ...; for(...) ...;dan akan putus.
Johannes Schaub - litb
1
@rem itu tidak merusak loop luar jika Anda menggunakan "break"
Johannes Schaub - litb
1
@rem Anda dapat menyederhanakan kode saya jika Anda mengubah bagian dalam "keep =! keep" menjadi "keep = 0". Saya menyukai "simetri" jadi saya hanya menggunakan negasi dan bukan tugas langsung.
Johannes Schaub - litb
11

Berikut adalah contoh program lengkap untuk setiap makro di 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;
}
Hakim Maygarden
sumber
Apa fungsi titik dalam list[]definisi? Tidak bisakah Anda menulis nextsaja .next?
Rizo
9
@Rizo Tidak, titik adalah bagian dari sintaks untuk penginisialisasi yang ditunjuk C99 . Lihat en.wikipedia.org/wiki/C_syntax#Initialization
Judge Maygarden
@Rizo: Perhatikan juga bahwa itu adalah cara yang benar-benar hack untuk membuat daftar tertaut. Itu akan dilakukan untuk demo ini tetapi jangan melakukannya seperti itu dalam praktiknya!
Donal Fellows
@Donal Apa yang membuatnya "hacky"?
Hakim Maygarden
2
@Judge: Nah, untuk satu hal ia memiliki masa hidup yang "mengejutkan" (jika Anda bekerja dengan kode yang menghapus elemen, kemungkinan Anda akan crash free()) dan untuk hal lain ia memiliki referensi ke nilai di dalam definisinya. Ini benar-benar contoh dari sesuatu yang terlalu pintar; kode cukup kompleks tanpa sengaja menambahkan kepintaran padanya. Pepatah Kernighan ( stackoverflow.com/questions/1103299/… ) berlaku!
Donal Fellows
9

Tidak ada foreach dalam C.

Anda dapat menggunakan perulangan for untuk mengulang melalui data tetapi panjangnya perlu diketahui atau data harus diakhiri dengan nilai yang diketahui (mis. Null).

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

for(;nullTerm != NULL;nullTerm++)
{
    //nullTerm will now point to the next character.
}
Adam Peck
sumber
Anda harus menambahkan inisialisasi penunjuk nullTerm ke awal kumpulan data. OP mungkin bingung tentang loop for yang tidak lengkap.
cschol
Berikan contoh sedikit.
Adam Peck
Anda mengubah penunjuk asli Anda, saya akan melakukan sesuatu seperti: char * s; s = "..."; for (char * it = s; it! = NULL; it ++) {/ * itu mengarah ke karakter * / }
hiena
6

Seperti yang mungkin sudah Anda ketahui, tidak ada loop bergaya "foreach" di C.

Meskipun sudah ada banyak sekali makro hebat yang disediakan di sini untuk mengatasi hal ini, mungkin makro ini berguna bagi Anda:

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

... yang dapat digunakan dengan for(seperti dalam for each (...)).

Keuntungan dari pendekatan ini:

  • item dideklarasikan dan bertambah dalam pernyataan for (seperti di Python!).
  • Tampaknya berfungsi pada larik 1 dimensi apa pun
  • Semua variabel yang dibuat dalam makro ( p, item), tidak terlihat di luar lingkup loop (karena mereka dideklarasikan di header loop for).

Kekurangan:

  • Tidak berfungsi untuk array multi-dimensi
  • Bergantung pada typeof(), yang merupakan ekstensi GNU, bukan bagian dari C standar
  • Karena mendeklarasikan variabel di header for loop, ini hanya berfungsi di C11 atau yang lebih baru.

Hanya untuk menghemat waktu, berikut cara mengujinya:

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

Sepertinya bekerja pada gcc dan clang secara default; belum menguji kompiler lain.

Saeed Baig
sumber
5

Ini adalah pertanyaan yang cukup lama, tetapi saya pikir saya harus memposting ini. Ini adalah foreach loop untuk 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;
}

Kode ini telah diuji untuk bekerja dengan gcc, icc dan clang di GNU / Linux.

Joe D
sumber
4

Meskipun C tidak memiliki a untuk setiap konstruk, C selalu memiliki representasi idiomatik untuk salah satu dari akhir array (&arr)[1]. Ini memungkinkan Anda untuk menulis idiomatik sederhana untuk setiap loop sebagai berikut:

int arr[] = {1,2,3,4,5};
for(int *a = arr; a < (&arr)[1]; ++a)
    printf("%d\n", *a);
Steve Cox
sumber
3
Jika tidak begitu yakin ini didefinisikan dengan baik. (&arr)[1]tidak berarti satu item larik melewati akhir larik, itu berarti satu larik melewati akhir larik. (&arr)[1]bukan item terakhir dari larik [0], ini adalah larik [1], yang meluruh menjadi penunjuk ke elemen pertama (dari larik [1]). Saya percaya akan jauh lebih baik, lebih aman dan idiomatis untuk dilakukan const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);dan kemudian for(const int* a = begin; a != end; a++).
Lundin
1
@Lundin Ini didefinisikan dengan baik. Anda benar, itu satu larik setelah akhir larik, tetapi jenis larik itu diubah menjadi penunjuk dalam konteks ini (ekspresi), dan penunjuk itu satu melewati akhir larik.
Steve Cox
2

C memiliki kata kunci 'untuk' dan 'sementara'. Jika pernyataan foreach dalam bahasa seperti C # terlihat seperti ini ...

foreach (Element element in collection)
{
}

... maka padanan dari pernyataan foreach di C ini mungkin seperti:

for (
    Element* element = GetFirstElement(&collection);
    element != 0;
    element = GetNextElement(&collection, element)
    )
{
    //TODO: do something with this element instance ...
}
ChrisW
sumber
1
Anda harus menyebutkan bahwa kode contoh Anda tidak ditulis dalam sintaks C.
cschol
> Anda harus menyebutkan bahwa kode contoh Anda tidak ditulis dalam sintaks C Anda benar, terima kasih: Saya akan mengedit posting.
ChrisW
@ monjardin-> yakin Anda bisa mendefinisikan pointer untuk berfungsi di struct dan tidak ada masalah untuk membuat panggilan seperti ini.
Ilya
2

Inilah yang saya gunakan ketika saya terjebak dengan C. Anda tidak dapat menggunakan nama item yang sama dua kali dalam lingkup yang sama, tetapi itu sebenarnya bukan masalah karena tidak semua dari kita dapat menggunakan kompiler baru yang bagus :(

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

Pemakaian:

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: Berikut adalah alternatif untuk FOREACHmenggunakan sintaks c99 untuk menghindari polusi 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)
Siklus air
sumber
Catatan: VAR(i) < (size) && (item = array[VAR(i)])akan berhenti setelah elemen array memiliki nilai 0. Jadi menggunakan ini dengan double Array[]tidak dapat melakukan iterasi melalui semua elemen. Sepertinya tes loop harus salah satu atau yang lain: i<natau A[i]. Mungkin menambahkan contoh kasus penggunaan untuk kejelasan.
chux
Bahkan dengan petunjuk dalam pendekatan saya sebelumnya, hasilnya tampaknya 'perilaku tidak terdefinisi'. Baiklah. Percayai pendekatan double for loop!
Sepeda air
Versi ini mengotori cakupan dan akan gagal jika digunakan dua kali dalam cakupan yang sama. Juga tidak berfungsi sebagai blok tanpa penguat (mis.if ( bla ) FOREACH(....) { } else....
MM
1
1, C adalah bahasa pencemaran ruang lingkup, beberapa dari kita terbatas pada kompiler yang lebih tua. 2, Jangan Ulangi Diri Anda / bersikap deskriptif. 3, ya, sayangnya Anda HARUS memiliki kawat gigi jika itu akan menjadi bersyarat untuk loop (orang biasanya tetap melakukannya). Jika Anda memiliki akses ke kompilator yang mendukung deklarasi variabel dalam perulangan for, lakukanlah.
Sepeda air
@ Watercycle: Saya dengan bebas mengedit jawaban Anda dengan versi alternatif FOREACHyang menggunakan sintaks c99 untuk menghindari polusi namespace.
chqrlie
1

Jawaban Eric tidak berfungsi saat Anda menggunakan "istirahat" atau "lanjutkan".

Ini bisa diperbaiki dengan menulis ulang baris pertama:

Baris asli (diformat ulang):

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

Tetap:

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

Jika Anda membandingkannya dengan putaran Johannes, Anda akan melihat bahwa dia sebenarnya melakukan hal yang sama, hanya sedikit lebih rumit dan lebih buruk.

Michael Blurb
sumber
1

Ini yang sederhana, 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

Memberi Anda akses ke indeks jika Anda menginginkannya ( i) dan item saat ini yang sedang kita iterasi ( it). Perhatikan bahwa Anda mungkin memiliki masalah penamaan saat membuat loop bersarang, Anda dapat membuat item dan nama indeks menjadi parameter untuk makro.

Sunting: Berikut adalah versi modifikasi dari jawaban yang diterima foreach. Memungkinkan Anda menentukan startindeks, sizesehingga ia bekerja pada array yang membusuk (pointer), tidak perlu int*dan diubah count != sizemenjadi untuk i < sizeberjaga-jaga jika pengguna secara tidak sengaja mengubah 'i' menjadi lebih besar dari sizedan terjebak dalam loop tak terbatas.

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

Keluaran:

index: 2. element: 3
index: 3. element: 4
index: 4. element: 5
menjengkelkan
sumber
1

Jika Anda berencana untuk bekerja dengan pointer fungsi

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

Pemakaian:

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

Tapi saya pikir ini hanya akan berfungsi di gcc (tidak yakin).

Naheel
sumber
1

C tidak memiliki implementasi for-each. Saat mem-parsing array sebagai titik, penerima tidak mengetahui berapa panjang array tersebut, sehingga tidak ada cara untuk mengetahui kapan Anda mencapai akhir array. Ingat, di C int*adalah titik ke alamat memori yang mengandung int. Tidak ada objek header yang berisi informasi tentang berapa banyak bilangan bulat yang ditempatkan secara berurutan. Jadi, programmer perlu melacak ini.

Namun, untuk list, mudah untuk mengimplementasikan sesuatu yang menyerupai for-eachloop.

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

Untuk mencapai sesuatu yang serupa untuk array, Anda dapat melakukan salah satu dari dua hal.

  1. gunakan elemen pertama untuk menyimpan panjang array.
  2. membungkus array dalam struct yang menampung panjang dan penunjuk ke array.

Berikut ini adalah contoh dari struct tersebut:

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