C言語でforeachマクロを実装する方法
- 作成日: 2022-01-17
- 更新日: 2023-12-25
- カテゴリ: C言語
C言語でforeachマクロを実装する
ほかの言語ではforeach文などに相当する機能があります。
これは添え字を使わないで配列などから要素だけを取り出していく文です。
たとえばPythonなどでは↓のように書くことができます。
lis = [1, 2, 3]
for el in lis:
print(el)
C言語にこのような機能は存在するのか? というと、結論から言うとありません。
ですが、foreach文を書きたい場合はマクロを使って実現することが多いです。
この記事ではその方法について解説します。
関連記事
目が覚めるC言語のdo-while文の使い方【ループ処理、初心者向け】
明快!C言語のcontinue文の使い方
君はまだC言語のdefineのすべてを知らない【マクロ、プリプロセス】
プログラミングのポインタをわかりやすく解説【C言語】
コードで見るC言語とC++の7つの違い
int型の配列用のforeachマクロを定義する
↓のような配列があるとします。
int ary[] = {1, 2, 3};
この配列をforeach的に回したい場合は↓のようなマクロを用意します。
#include <stdio.h>
// int型の配列用のforeachマクロ
#define foreach_int_array(el, ary) \
for (int i = 0, el = ary[0]; i < sizeof(ary) / sizeof(ary[0]); i += 1, el = ary[i])
int main(void) {
int ary[] = {1, 2, 3};
foreach_int_array(el, ary) {
printf("%d\n", el);
}
return 0;
}
↑のコードをコンパイルして実行すると↓の結果になります。
1
2
3
まずこのforeach_int_array
マクロはfor文の初期化部分でint型の変数を2つ定義します。
i
とel
です。
そしてel
には配列の0番目の要素を入れておきます。
for文の判定部分ではi
が配列の要素数以下かどうか判定します。
sizeof(ary) / sizeof(ary[0])
で、ローカル変数の配列の要素数が求まります。
sizeof
演算子は変数などのバイト数を求めます。
配列に使うと配列全体のバイト数が求まります。そのバイト数に配列の1要素分のバイト数を割っています。
こうするとたとえばint型(4バイト)の配列で要素数が3つある場合は、その配列全体のバイト数は4バイト * 3
で12バイトになります。
要素1つが4バイトなので12バイト / 4バイト = 3
で要素数3が求まります。
sizeof
演算子は関数の引数の配列などには使えません。その点は注意が必要です。
for文の更新部分ではi += 1
でカウント変数i
をインクリメントします。
そしてel = ary[i]
でel
の値を現在の添え字の要素で更新します。
このforeach_int_array
マクロは一見すると問題なく機能しますが、実際には色々問題を含んでいます。
というのも、このマクロはintの配列にしか正しく機能しないからです。
このマクロが上手くいっているのは、for文の初期化部分でint i = 0, el = ary[0];
とやって、i
とel
を同時に定義しているからです。
C言語のfor文では初期化部分には型は1つしか使えません。
そのため配列の要素がdoubleなどになったら、このマクロは使えなくなります。
カウント変数のiもdouble型にしなければいけないからです。
ですので、汎用的なforeachマクロを定義するにはある程度妥協する必要があります。
汎用的なforeachマクロを定義する
先ほどのマクロではforeach_int_array(el, ary)
とやって、要素el
をfor文側で定義していました。
汎用的なマクロを定義するにはこの点を妥協する必要があります。
つまり要素ではなく添え字を定義するということです。
以下が汎用的なforeachマクロの実装例です。
#include <stdio.h>
// 汎用的なforeachマクロ
#define foreach_array(i, ary) \
for (size_t i = 0; i < sizeof(ary) / sizeof(ary[0]); i += 1)
int main(void) {
int iary[] = {1, 2, 3};
foreach_array(i, iary) {
printf("%d\n", iary[i]);
}
double dary[] = {1.2, 3.4, 5.6};
foreach_array(i, dary) {
printf("%lf\n", dary[i]);
}
return 0;
}
上記のforeach_array
マクロは配列の要素ではなく配列の添え字を取り出します。
int iary[] = {1, 2, 3};
foreach_array(i, iary) {
printf("%d\n", iary[i]);
}
iary
はint型の配列です。添え字であるカウント変数はi
になります。
要素を参照するときはiary[i]
のように参照します。
このforeach_array
マクロはdouble
型の配列でも機能します。
double dary[] = {1.2, 3.4, 5.6};
foreach_array(i, dary) {
printf("%lf\n", dary[i]);
}
マクロも配列の添え字を取り出しているだけなので、ずいぶんシンプルになっています。
マクロの注意点
さきほどのforeach_array
マクロですが、これは関数の引数の配列に使おうとするとバグになるので注意が必要です。
つまり↓のようなコードは書けません。
void func(int ary[]) {
foreach_array(i, ary) {
printf("%d\n", ary[i]);
}
}
理由ですが、sizeof
の仕様の問題です。
sizeof
は関数の引数の配列は、ポインタ変数と見なします。
つまりsizeof ary
では配列全体のサイズではなくポインタ変数のサイズが求まります。
この仕様の影響で、sizeof(ary) / sizeof(ary[0])
という配列の要素数を求める式が使えなくなります。
よって関数の引数に対してはforeach_array
マクロは正しく機能しません。
ポインタ配列をforeachする
NULL番兵がいるポインタ配列などは、NULL番兵をチェックすることでforeachマクロで回すことができます。
#include <stdio.h>
// NULL番兵がいるポインタ配列を回すマクロ
#define foreach_ptr_array(i, ary) \
for (size_t i = 0; ary[i]; i += 1)
int main(void) {
const char *pary[] = {
"Cat", "Dog", "Bird", NULL,
};
foreach_ptr_array(i, pary) {
printf("%s\n", pary[i]);
}
return 0;
}
NULL番兵がいる配列であれば、このマクロは関数の引数に対しても使うことができます。
自作コンテナをforeachマクロで回す
C言語では動的配列などのライブラリを自分で作ることもあります。
そういった独自のコンテナをforeachマクロで回すこともよくあります。
#include <stdio.h>
typedef struct {
size_t len;
const char *buf;
} String;
#define foreach_string(i, str) \
for (size_t i = 0; i < str.len; i += 1)
int main(void) {
String s = {5, "Hello"};
foreach_string(i, s) {
printf("%c\n", s.buf[i]);
}
return 0;
}
↑の実行結果は↓になります。
H
e
l
l
o
こういったマクロを使えば添え字の処理が抽象化されるので、バグを減らすこともできます。
おわりに
C言語のマクロによるforeachは、データ構造に合わせてそれぞれ定義する必要があります。
しかし一度定義してしまえば便利に使うことが可能です。
🦝 < foreachで楽しよう
🐭 < 添え字を取り出そう