C có cấu trúc vòng lặp “foreach” không?


109

Hầu hết tất cả các ngôn ngữ đều có một foreachvòng lặp hoặc một cái gì đó tương tự. C có một cái không? Bạn có thể đăng một số mã ví dụ?


1
" foreach" của cái gì?
alk

Sẽ khó như thế nào nếu bạn thử viết một foreachvòng lặp trong chương trình C?
MD XF

Câu trả lời:


194

C không có foreach, nhưng macro thường được sử dụng để mô phỏng điều đó:

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

Và có thể được sử dụng như

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

Lặp lại trên một mảng cũng có thể:

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

Và có thể được sử dụng như

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

Chỉnh sửa: Trong trường hợp bạn cũng quan tâm đến các giải pháp C ++, C ++ có một cú pháp riêng cho từng cú pháp được gọi là "phạm vi dựa trên cho"


1
Nếu bạn có toán tử "typeof" (phần mở rộng gcc; khá phổ biến trên nhiều trình biên dịch khác), bạn có thể loại bỏ "int *" đó. Các bên trong cho vòng lặp trở thành một cái gì đó như "for (typeof ((mảng) +0) item = ..." Sau đó, bạn có thể gọi là "foreach (v, giá trị) ..."
leander

Tại sao chúng ta cần hai vòng lặp for trong ví dụ mảng? Vậy còn điều này thì sao: #define foreach(item, array) int count=0, size=sizeof(array)/sizeof(*(array)); for(item = (array); count != size; count++, item = (array)+count)Một vấn đề mà tôi có thể thấy là số lượng và kích thước các biến nằm ngoài vòng lặp for và có thể gây ra xung đột. Đây có phải là lý do bạn sử dụng hai vòng lặp for? [mã được dán ở đây ( pastebin.com/immndpwS )]
Lazer

3
@eSKay vâng xem xét if(...) foreach(int *v, values) .... Nếu chúng nằm ngoài vòng lặp, nó sẽ mở rộng if(...) int count = 0 ...; for(...) ...;và sẽ phá vỡ.
Johannes Schaub -

1
@rem nó không phá vỡ các vòng ngoài nếu bạn sử dụng "phá vỡ"
Johannes Schaub - litb

1
@rem, tuy nhiên, bạn có thể đơn giản hóa mã của tôi nếu bạn thay đổi nội dung "keep =! keep" thành "keep = 0". Tôi thích "đối xứng" vì vậy tôi chỉ sử dụng phép phủ định chứ không phải phép gán thẳng.
Johannes Schaub - litb

11

Đây là một ví dụ chương trình đầy đủ về macro cho từng macro trong 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;
}

Dấu chấm làm gì trong list[]định nghĩa? Bạn không thể viết đơn giản nextthay vì .next?
Rizo

9
@Rizo Không, dấu chấm là một phần của cú pháp cho các trình khởi tạo được chỉ định C99 . Xem en.wikipedia.org/wiki/C_syntax#Initialization
Judge Maygarden

@Rizo: Cũng lưu ý rằng đó là một cách thực sự khó hiểu để xây dựng danh sách liên kết. Nó sẽ làm cho bản demo này nhưng đừng làm theo cách đó trong thực tế!
Donal Fellows

@Donal Điều gì làm cho nó "hacky"?
Judge Maygarden,

2
@Judge: Chà, vì một điều là nó có thời gian tồn tại “đáng ngạc nhiên” (nếu bạn đang làm việc với mã loại bỏ các phần tử, rất có thể bạn sẽ gặp sự cố free()) và mặt khác, nó có tham chiếu đến giá trị bên trong định nghĩa của nó. Nó thực sự là một ví dụ về một cái gì đó quá thông minh; mã đủ phức tạp nếu không có mục đích thêm sự thông minh vào nó. Cách ngôn của Kernighan ( stackoverflow.com/questions/1103299/… ) được áp dụng!
Donal Fellows

9

Không có foreach nào trong C.

Bạn có thể sử dụng vòng lặp for để lặp qua dữ liệu nhưng độ dài cần phải biết hoặc dữ liệu cần được kết thúc bởi một giá trị biết (ví dụ: null).

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

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

Bạn nên thêm phần khởi tạo con trỏ nullTerm vào đầu tập dữ liệu. OP có thể nhầm lẫn về vòng lặp for không hoàn chỉnh.
cschol 30/12/08

Lấy ví dụ ra một chút.
Adam Peck

bạn đang thay đổi con trỏ ban đầu của mình, tôi sẽ làm như sau: char * s; s = "..."; for (char * it = s; it! = NULL; it ++) {/ * nó trỏ đến ký tự * / }
hiena

6

Như bạn có thể đã biết, không có vòng lặp kiểu "foreach" trong C.

Mặc dù đã có rất nhiều macro tuyệt vời được cung cấp ở đây để giải quyết vấn đề này, nhưng có thể bạn sẽ thấy macro này hữu ích:

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

... có thể được sử dụng với for(như trong for each (...)).

Ưu điểm của phương pháp này:

  • item được khai báo và tăng dần trong câu lệnh for (giống như trong Python!).
  • Có vẻ hoạt động trên bất kỳ mảng 1 chiều nào
  • Tất cả các biến được tạo trong macro ( p, item), không hiển thị bên ngoài phạm vi của vòng lặp (vì chúng được khai báo trong tiêu đề vòng lặp for).

Nhược điểm:

  • Không hoạt động đối với mảng đa chiều
  • Dựa vào typeof(), là một phần mở rộng GNU, không phải là một phần của tiêu chuẩn C
  • Vì nó khai báo các biến trong tiêu đề vòng lặp for, nó chỉ hoạt động trong C11 trở lên.

Chỉ để giúp bạn tiết kiệm thời gian, đây là cách bạn có thể kiểm tra nó:

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

Có vẻ như hoạt động trên gcc và tiếng kêu theo mặc định; chưa thử nghiệm các trình biên dịch khác.


5

Đây là một câu hỏi khá cũ, nhưng tôi nên đăng câu hỏi này. Nó là một vòng lặp foreach cho 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;
}

Mã này đã được thử nghiệm để hoạt động với gcc, icc và clang trên GNU / Linux.


4

Trong khi C không có một cho mỗi cấu trúc, nó luôn có một đại diện thành ngữ cho một quá khứ cuối của một mảng (&arr)[1]. Điều này cho phép bạn viết một thành ngữ đơn giản cho mỗi vòng lặp như sau:

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

3
Nếu không chắc chắn điều này được xác định rõ. (&arr)[1]không có nghĩa là một mục của mảng ở cuối mảng, nó có nghĩa là một mảng ở cuối mảng. (&arr)[1]không phải là mục cuối cùng của mảng [0], nó là mảng [1], phân rã thành con trỏ đến phần tử đầu tiên (của mảng [1]). Tôi tin rằng nó sẽ tốt hơn, an toàn hơn và dễ hiểu hơn khi làm const int* begin = arr; const int* end = arr + sizeof(arr)/sizeof(*arr);và sau đó for(const int* a = begin; a != end; a++).
Lundin

1
@Lundin Điều này được xác định rõ ràng. Bạn nói đúng, nó là một mảng ở phía sau của mảng, nhưng kiểu mảng đó sẽ chuyển đổi thành con trỏ trong ngữ cảnh này (một biểu thức) và con trỏ đó ở một phía sau cuối mảng.
Steve Cox

2

C có các từ khóa 'for' và 'while'. Nếu một câu lệnh foreach bằng ngôn ngữ như C # trông như thế này ...

foreach (Element element in collection)
{
}

... thì tương đương với câu lệnh foreach này trong C có thể giống như sau:

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

1
Bạn nên lưu ý rằng mã ví dụ của bạn không được viết bằng cú pháp C.
cschol

> Bạn nên đề cập rằng mã ví dụ của bạn không được viết bằng cú pháp C Bạn nói đúng, cảm ơn bạn: Tôi sẽ chỉnh sửa bài đăng.
ChrisW 30/12/08

@ monjardin-> chắc chắn rằng bạn chỉ có thể xác định con trỏ hoạt động trong cấu trúc và không có vấn đề gì khi thực hiện cuộc gọi như thế này.
Ilya,

2

Đây là những gì tôi sử dụng khi tôi gặp khó khăn với C. Bạn không thể sử dụng cùng một tên mục hai lần trong cùng một phạm vi, nhưng đó thực sự không phải là vấn đề vì không phải tất cả chúng ta đều có thể sử dụng các trình biên dịch mới tốt đẹp :(

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

Sử dụng:

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

CHỈNH SỬA: Đây là một giải pháp thay thế cho FOREACHviệc sử dụng cú pháp c99 để tránh ô nhiễm không gian tên:

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

Lưu ý: VAR(i) < (size) && (item = array[VAR(i)])sẽ dừng khi phần tử mảng có giá trị bằng 0. Vì vậy, việc sử dụng điều này với double Array[]có thể không lặp lại qua tất cả các phần tử. Có vẻ như kiểm tra vòng lặp phải là một hoặc khác: i<nhoặc A[i]. Có thể thêm các trường hợp sử dụng mẫu cho rõ ràng.
Chux - Khôi phục Monica

Ngay cả với các con trỏ trong cách tiếp cận trước đây của tôi, kết quả dường như là 'hành vi không xác định'. Ồ tốt. Hãy tin tưởng vào cách tiếp cận vòng lặp for gấp đôi!
Watercycle

Phiên bản này gây ô nhiễm phạm vi và sẽ không thành công nếu được sử dụng hai lần trong cùng một phạm vi. Cũng không hoạt động như một khối không có thanh giằng (ví dụif ( bla ) FOREACH(....) { } else....
MM

1
1, C là ngôn ngữ của phạm vi ô nhiễm, một số người trong chúng ta bị giới hạn trong các trình biên dịch cũ hơn. 2, Đừng lặp lại bản thân / hãy mô tả. 3, vâng, không may là bạn PHẢI có dấu ngoặc nhọn nếu nó là một vòng lặp for có điều kiện (mọi người vẫn thường làm vậy). Nếu bạn có quyền truy cập vào trình biên dịch hỗ trợ khai báo biến trong vòng lặp for, hãy làm điều đó bằng mọi cách.
Watercycle

@Watercycle: Tôi đã có quyền chỉnh sửa câu trả lời của bạn bằng một phiên bản thay thế FOREACHsử dụng cú pháp c99 để tránh ô nhiễm không gian tên.
chqrlie

1

Câu trả lời của Eric không hoạt động khi bạn đang sử dụng "break" hoặc "continue".

Điều này có thể được khắc phục bằng cách viết lại dòng đầu tiên:

Dòng gốc (được định dạng lại):

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

Đã sửa:

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

Nếu bạn so sánh nó với vòng lặp của Johannes, bạn sẽ thấy rằng anh ấy thực sự đang làm như vậy, chỉ phức tạp hơn và xấu hơn một chút.


1

Đây là một vòng lặp đơn giản, đơn giản:

#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

Cung cấp cho bạn quyền truy cập vào chỉ mục nếu bạn muốn nó ( i) và mục hiện tại mà chúng tôi đang lặp lại ( it). Lưu ý rằng bạn có thể gặp sự cố đặt tên khi lồng các vòng lặp, bạn có thể đặt tên mục và chỉ mục là tham số cho macro.

Chỉnh sửa: Đây là phiên bản sửa đổi của câu trả lời được chấp nhận foreach. Cho phép bạn chỉ định startchỉ mục, sizeđể nó hoạt động trên các mảng đã phân rã (con trỏ), không cần int*và thay đổi count != sizethành i < sizechỉ trong trường hợp người dùng vô tình sửa đổi 'i' lớn hơn sizevà bị mắc kẹt trong một vòng lặp vô hạn.

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

Đầu ra:

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

1

Nếu bạn định làm việc với con trỏ hàm

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

Sử dụng:

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

Nhưng tôi nghĩ rằng điều này sẽ chỉ hoạt động trên gcc (không chắc chắn).


1

C không có triển khai của for-each. Khi phân tích cú pháp một mảng dưới dạng một điểm, bộ thu không biết mảng dài bao nhiêu, do đó không có cách nào để biết khi nào bạn đến cuối mảng. Hãy nhớ rằng, trong C int*là một điểm đến địa chỉ bộ nhớ có chứa int. Không có đối tượng tiêu đề nào chứa thông tin về bao nhiêu số nguyên được đặt trong chuỗi. Vì vậy, lập trình viên cần theo dõi điều này.

Tuy nhiên, đối với danh sách, có thể dễ dàng triển khai một cái gì đó giống như một for-eachvòng lặp.

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

Để đạt được điều gì đó tương tự cho các mảng, bạn có thể làm một trong hai điều.

  1. sử dụng phần tử đầu tiên để lưu trữ độ dài của mảng.
  2. bọc mảng trong một cấu trúc chứa độ dài và một con trỏ đến mảng.

Sau đây là một ví dụ về cấu trúc như vậy:

typedef struct job_t {
   int count;
   int* arr;
} arr_t;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.