C มีโครงสร้างลูป "foreach" หรือไม่?


110

เกือบทุกภาษามีการforeachวนซ้ำหรือคล้ายกัน C มีหรือไม่? คุณสามารถโพสต์โค้ดตัวอย่างได้หรือไม่?


1
" foreach" ของอะไร?
alk

การลองเขียนforeachลูปในโปรแกรม C นั้นยากแค่ไหน?
MD XF

คำตอบ:


195

C ไม่มี foreach แต่มักใช้มาโครเพื่อเลียนแบบ:

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

และสามารถใช้เช่น

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

การทำซ้ำบนอาร์เรย์ยังทำได้:

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

และสามารถใช้เช่น

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

แก้ไข: ในกรณีที่คุณสนใจโซลูชัน C ++ C ++ มีเนทีฟสำหรับไวยากรณ์แต่ละรายการเรียกว่า "range based for"


1
หากคุณมีตัวดำเนินการ "typeof" (ส่วนขยาย gcc ซึ่งเป็นเรื่องธรรมดาในคอมไพเลอร์อื่น ๆ ) คุณสามารถกำจัด "int *" นั้นได้ inner for loop จะกลายเป็น "for (typeof ((array) +0) item = ... " จากนั้นเรียกเป็น "foreach (v, values) ... "
leander

ทำไมเราต้องมีสองตัวสำหรับลูปในตัวอย่างอาร์เรย์ วิธีนี้: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)ปัญหาหนึ่งที่ฉันเห็นคือตัวแปรนับและขนาดอยู่นอกลูป for และอาจทำให้เกิดความขัดแย้ง นี่คือเหตุผลที่คุณใช้สองสำหรับลูปหรือไม่? [วางโค้ดที่นี่ ( pastebin.com/immndpwS )]
Lazer

3
ใช่ @eSKay if(...) foreach(int *v, values) ...พิจารณา หากอยู่นอกวงจะขยายif(...) int count = 0 ...; for(...) ...;และจะแตก
Johannes Schaub - litb

1
@rem จะไม่ทำลายวงนอกถ้าคุณใช้ "break"
Johannes Schaub - litb

1
@rem คุณสามารถทำให้รหัสของฉันง่ายขึ้นได้หากคุณเปลี่ยน "keep =! keep" ภายในเป็น "keep = 0" ฉันชอบ "สมมาตร" ดังนั้นฉันจึงใช้การปฏิเสธไม่ใช่การมอบหมายแบบตรงไปตรงมา
Johannes Schaub - litb

11

นี่คือตัวอย่างโปรแกรมทั้งหมดของมาโครสำหรับแต่ละมาโครใน 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;
}

จุดทำหน้าที่อะไรในlist[]นิยาม? คุณไม่สามารถเขียนnextแทนได้.nextหรือไม่?
Rizo

9
@Rizo ไม่มีจุดเป็นส่วนหนึ่งของไวยากรณ์สำหรับinitializers C99 กำหนด ดูen.wikipedia.org/wiki/C_syntax#Initialization
Judge Maygarden

@Rizo: โปรดทราบว่านั่นเป็นวิธีที่แฮ็กจริงๆในการสร้างรายการที่เชื่อมโยง มันจะทำเพื่อการสาธิตนี้ แต่อย่าทำแบบนั้นในทางปฏิบัติ!
Donal Fellows

@Donal อะไรที่ทำให้ "แฮ็ก"?
ผู้พิพากษา Maygarden

2
@ Judge: สิ่งหนึ่งที่มันมีอายุการใช้งานที่“ น่าประหลาดใจ” (ถ้าคุณกำลังทำงานกับโค้ดที่ลบองค์ประกอบออกโอกาสที่คุณจะพังfree()) และอีกอย่างก็มีการอ้างอิงถึงค่าภายในคำจำกัดความ มันเป็นตัวอย่างของสิ่งที่ฉลาดเกินไป รหัสมีความซับซ้อนเพียงพอโดยไม่ต้องเพิ่มความฉลาดให้กับมัน คำพังเพยของ Kernighan ( stackoverflow.com/questions/1103299/… ) ใช้!
Donal Fellows

9

ไม่มี foreach ใน C.

คุณสามารถใช้ for loop ในการวนซ้ำข้อมูลได้ แต่ต้องทราบความยาวหรือข้อมูลจะต้องถูกยกเลิกด้วยค่าทราบ (เช่น null)

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

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

คุณควรเพิ่มการเตรียมใช้งานของตัวชี้ nullTerm ที่จุดเริ่มต้นของชุดข้อมูล OP อาจสับสนเกี่ยวกับความไม่สมบูรณ์สำหรับลูป
cschol

ออกตัวอย่างเล็กน้อย
Adam Peck

คุณกำลังเปลี่ยนตัวชี้เดิมของคุณฉันจะทำบางอย่างเช่น char * s; s = "... "; สำหรับ (char * it = s; it! = NULL; it ++) {/ * ชี้ไปที่อักขระ * / }
hiena

6

อย่างที่คุณทราบกันดีอยู่แล้วว่าไม่มีลูปสไตล์ "foreach" ใน C

แม้ว่าจะมีมาโครที่ดีมากมายให้ที่นี่เพื่อแก้ไขปัญหานี้ แต่คุณอาจพบว่ามาโครนี้มีประโยชน์:

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

... ซึ่งสามารถใช้ได้กับfor(เช่นเดียวกับในfor each (...))

ข้อดีของแนวทางนี้:

  • item ถูกประกาศและเพิ่มขึ้นภายในคำสั่ง for (เช่นเดียวกับใน Python!)
  • ดูเหมือนว่าจะทำงานกับอาร์เรย์ 1 มิติใด ๆ
  • ตัวแปรทั้งหมดที่สร้างในมาโคร ( p, item) ไม่สามารถมองเห็นได้นอกขอบเขตของลูป (เนื่องจากมีการประกาศไว้ในส่วนหัวสำหรับลูป)

ข้อเสีย:

  • ใช้ไม่ได้กับอาร์เรย์หลายมิติ
  • อาศัยtypeof()ซึ่งเป็นส่วนขยาย GNU ไม่ใช่ส่วนหนึ่งของมาตรฐาน C
  • เนื่องจากมีการประกาศตัวแปรในส่วนหัว for loop จึงใช้ได้เฉพาะใน C11 หรือใหม่กว่า

เพื่อช่วยคุณประหยัดเวลานี่คือวิธีทดสอบ:

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

ดูเหมือนว่าจะทำงานกับ gcc และเสียงดังตามค่าเริ่มต้น ยังไม่ได้ทดสอบคอมไพเลอร์อื่น ๆ


5

นี่เป็นคำถามที่ค่อนข้างเก่า แต่ฉันควรโพสต์คำถามนี้ เป็น foreach loop สำหรับ 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;
}

รหัสนี้ได้รับการทดสอบว่าทำงานกับ gcc, icc และ clang บน GNU / Linux


4

ในขณะที่ C (&arr)[1]ไม่ได้สำหรับแต่ละสร้างมันได้เคยมีการแสดงสำนวนหนึ่งที่ผ่านมาในตอนท้ายของอาร์เรย์ สิ่งนี้ช่วยให้คุณสามารถเขียนสำนวนง่ายๆสำหรับแต่ละลูปได้ดังนี้:

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

3
หากไม่แน่ใจว่ามีการกำหนดไว้อย่างดี (&arr)[1]ไม่ได้หมายถึงอาร์เรย์หนึ่งรายการที่อยู่ถัดจากจุดสิ้นสุดของอาร์เรย์ แต่หมายถึงอาร์เรย์หนึ่งรายการที่อยู่ถัดจากจุดสิ้นสุดของอาร์เรย์ (&arr)[1]ไม่ใช่รายการสุดท้ายของอาร์เรย์ [0] แต่เป็นอาร์เรย์ [1] ซึ่งสลายตัวเป็นตัวชี้ไปยังองค์ประกอบแรก (ของอาร์เรย์ [1]) ผมเชื่อว่ามันจะดีมาก, ความปลอดภัยและสำนวนที่จะทำแล้วconst int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr); for(const int* a = begin; a != end; a++)
Lundin

1
@ ลุนดินนี่ถูกกำหนดไว้อย่างดี คุณพูดถูกมันคืออาร์เรย์หนึ่งอาร์เรย์ที่อยู่ถัดจากจุดสิ้นสุดของอาร์เรย์ แต่ประเภทอาร์เรย์นั้นจะแปลงเป็นตัวชี้ในบริบทนี้ (นิพจน์) และตัวชี้นั้นอยู่ถัดจากจุดสิ้นสุดของอาร์เรย์
Steve Cox

2

C มีคำหลัก 'for' และ 'while' ถ้าประโยค foreach ในภาษาเช่น C # มีลักษณะเช่นนี้ ...

foreach (Element element in collection)
{
}

... จากนั้นคำสั่ง foreach ที่เทียบเท่าใน C อาจเป็นดังนี้:

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

1
คุณควรระบุว่าโค้ดตัวอย่างของคุณไม่ได้เขียนด้วยไวยากรณ์ C
cschol

> คุณควรระบุว่าโค้ดตัวอย่างของคุณไม่ได้เขียนด้วยไวยากรณ์ C คุณพูดถูกขอบคุณ: ฉันจะแก้ไขโพสต์
ChrisW

@ monjardin-> แน่ใจว่าคุณสามารถกำหนดตัวชี้ให้ทำงานในโครงสร้างได้และไม่มีปัญหาในการโทรเช่นนี้
Ilya

2

นี่คือสิ่งที่ฉันใช้เมื่อฉันติดกับ C. คุณไม่สามารถใช้ชื่อรายการเดียวกันสองครั้งในขอบเขตเดียวกัน แต่นั่นไม่ใช่ปัญหาจริงๆเนื่องจากเราทุกคนไม่ได้ใช้คอมไพเลอร์ใหม่ที่ดี :(

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

การใช้งาน:

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

แก้ไข:นี่คือทางเลือกสำหรับการFOREACHใช้ไวยากรณ์ c99 เพื่อหลีกเลี่ยงมลพิษเนมสเปซ:

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

หมายเหตุ: VAR(i) < (size) && (item = array[VAR(i)])จะหยุดเมื่อองค์ประกอบอาร์เรย์มีค่าเป็น 0 ดังนั้นการใช้สิ่งนี้กับdouble Array[]อาจไม่วนซ้ำทุกองค์ประกอบ ดูเหมือนว่าการทดสอบห่วงควรจะเป็นหนึ่งหรืออื่น ๆ : หรือi<n A[i]อาจเพิ่มกรณีการใช้งานตัวอย่างเพื่อความชัดเจน
chux - คืนสถานะ Monica

แม้จะมีพอยน์เตอร์ในแนวทางก่อนหน้าของฉันผลลัพธ์ก็ดูเหมือนจะเป็น 'พฤติกรรมที่ไม่ได้กำหนด' โอ้ดี. เชื่อมั่นคู่สำหรับวิธีการวนซ้ำ!
Watercycle

เวอร์ชันนี้ก่อให้เกิดมลพิษและจะล้มเหลวหากใช้สองครั้งในขอบเขตเดียวกัน ยังไม่ทำงานเป็นบล็อกที่ไม่มีการค้ำยัน (เช่นif ( bla ) FOREACH(....) { } else....
MM

1
1, C เป็นภาษาของมลพิษขอบเขตพวกเราบางคน จำกัด เฉพาะคอมไพเลอร์รุ่นเก่า 2 อย่าพูดซ้ำตัวเอง / เป็นคำอธิบาย 3 ใช่น่าเสียดายที่คุณต้องจัดฟันถ้ามันจะเป็นเงื่อนไขสำหรับการวนซ้ำ (คนมักจะทำอยู่แล้ว) หากคุณสามารถเข้าถึงคอมไพเลอร์ที่สนับสนุนการประกาศตัวแปรใน for loop ให้ทำเช่นนั้น
Watercycle

@ Watercycle: ฉันใช้เสรีภาพในการแก้ไขคำตอบของคุณด้วยเวอร์ชันทางเลือกFOREACHที่ใช้ไวยากรณ์ c99 เพื่อหลีกเลี่ยงมลพิษเนมสเปซ
chqrlie

1

คำตอบของ Eric ใช้ไม่ได้เมื่อคุณใช้ "break" หรือ "continue"

สามารถแก้ไขได้โดยเขียนบรรทัดแรกใหม่:

บรรทัดเดิม (ฟอร์แมตใหม่):

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

แก้ไขแล้ว:

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

ถ้าคุณเปรียบเทียบกับวงของโยฮันเนสคุณจะเห็นว่าเขาทำแบบเดียวกันจริง ๆ ซับซ้อนและน่าเกลียดกว่าเล็กน้อย


1

นี่เป็นวิธีง่ายๆสำหรับการวนซ้ำ:

#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

ให้คุณเข้าถึงดัชนีได้ตามที่คุณต้องการ ( i) และรายการปัจจุบันที่เรากำลังทำซ้ำอยู่ ( it) โปรดทราบว่าคุณอาจมีปัญหาในการตั้งชื่อเมื่อซ้อนลูปคุณสามารถทำให้ชื่อรายการและดัชนีเป็นพารามิเตอร์ของแมโครได้

แก้ไข: foreachนี่เป็นรุ่นที่ปรับเปลี่ยนคำตอบที่ได้รับการยอมรับ ช่วยให้คุณระบุstartดัชนีsizeเพื่อให้ทำงานกับอาร์เรย์ที่สลายตัว (พอยน์เตอร์) ไม่จำเป็นต้องมีint*และเปลี่ยนcount != sizeเป็นi < sizeในกรณีที่ผู้ใช้ปรับเปลี่ยน 'i' ให้ใหญ่กว่าโดยไม่ได้ตั้งใจsizeและติดอยู่ในลูปที่ไม่มีที่สิ้นสุด

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

เอาท์พุต:

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

1

หากคุณกำลังวางแผนที่จะทำงานกับตัวชี้ฟังก์ชัน

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

การใช้งาน:

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

แต่ฉันคิดว่ามันจะใช้ได้กับ gcc เท่านั้น (ไม่แน่ใจ)


1

C ไม่มีการใช้งานfor-each. เมื่อแยกวิเคราะห์อาร์เรย์เป็นจุดผู้รับจะไม่ทราบว่าอาร์เรย์มีความยาวเท่าใดจึงไม่มีทางบอกได้ว่าเมื่อคุณไปถึงจุดสิ้นสุดของอาร์เรย์ โปรดจำไว้ว่าใน C int*คือจุดไปยังที่อยู่หน่วยความจำที่มี int ไม่มีออบเจ็กต์ส่วนหัวที่มีข้อมูลเกี่ยวกับจำนวนจำนวนเต็มที่วางในลำดับ ดังนั้นโปรแกรมเมอร์จำเป็นต้องติดตามสิ่งนี้

อย่างไรก็ตามสำหรับรายการคุณสามารถใช้สิ่งที่คล้ายกับการfor-eachวนซ้ำได้อย่างง่ายดาย

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

เพื่อให้บรรลุสิ่งที่คล้ายกันสำหรับอาร์เรย์คุณสามารถทำสิ่งใดสิ่งหนึ่งจากสองสิ่งได้

  1. ใช้องค์ประกอบแรกเพื่อจัดเก็บความยาวของอาร์เรย์
  2. ห่ออาร์เรย์ในโครงสร้างที่เก็บความยาวและตัวชี้ไปยังอาร์เรย์

ต่อไปนี้เป็นตัวอย่างของโครงสร้างดังกล่าว:

typedef struct job_t {
   int count;
   int* arr;
} arr_t;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.