絵で見てわかるC言語入門 - 第1章

さて、C言語を使い慣れた人には常識ですが、C言語初心者にとって少し難しいのが、変数の宣言です。

例えば、初心者にとって、関数ポインタの配列へのポインタを返す関数の宣言を書くのはあまり簡単ではありません。


C言語の初心者には、以下のようなコードを書くのは少し難しく、また、C言語にあまり慣れていない人は、typedefなどを使ってしまいがちです。

typedef int (*func_t)(int);
typedef func_t func_arr_t[10];

extern func_arr_t *x[40];
int (*(*x[40])[10])(int);

int (*(*func(int i))[10])(int)
{
    return x[i];
}

そのようなことでは、いつまでたってもC言語の初心者を脱出することはできません。この章では、変数の宣言について詳しく勉強することにしましょう。

まず、C言語を使い慣れた人には常識だと思いますが、Cの型を解釈するときは「方向」が大事なので、「英語読み」か「日本語読み」のどちらにするか、統一しておく必要があります。
これは、「Array of Pointer to hogehoge」と読むのか、「hogehogeを指すポインタの配列」と読むのか、という問題です。いずれにせよ、関数の型が入ってくると厳密ではなくなるので、気分の問題ですが、ここでは日本人のコンプレックスらしく英語読みを採用することにしましょう。

さて、イメージとして、次のような矢印を「常に」想像するようにしてください。

  • 括弧ではね返る
  • 左よりも右のほうが強い
  • 一番左の型名は一番弱い

あとは、この矢印の通ったとおりに読むだけです。「Array[10] of Array[20] of Pointer to Pointer to Array[40] of int」。
日本語読みならば、矢印が逆です。「int10個の配列へのポインタへのポインタ20個の配列10個の配列」。

おっと、なんだか簡単な気がしてきましたね。少し難しいのが、関数の型です。関数の型は引数と戻り値を持ちます。
関数も矢印のルールは同じです。

  • 括弧ではね返る
  • 左よりも右のほうが強い
  • 一番左の型名は一番弱い

ただ、関数はそこに複数の型が含まれます。すなわち、「戻り値の型」と「引数の型」です。すなわち、どこかに、型がいくつかあるわけですね。
こんな簡単なことをC言語を使い慣れてる人に説明するのは非常に申し分けないのですが、ここでも矢印のイメージが重要です。引数の括弧の上は、矢印が通り抜けていくイメージをしましょう。すると、

  • 矢印が通り抜けてるところに型がある
  • 通り抜けた先に型がある

と、なるわけです。ここで、型がたくさん出てきますね。

  • 矢印が通り抜けてるところに型がある→引数の型
  • 通り抜けた先に型がある→戻り値の型

と、なります。



C言語では、「配列を返す関数」や「関数を返す関数」は書けないので、引数の括弧を抜けたあとは必ず左へ方向転換するわけですが、このように覚えておけば、将来配列を返す関数が書けるようになった時にも対応しやすいでしょう。

これを読むと、「func(int) returns pointer to array[10] of pointer to int」「intを指すポインタ10個の配列を指すポインタを返してintを引数に取る関数」になります。

おっと、一番内側の型が関数であることに注意してください!Cを使い慣れた人は当然気付いていると思いますが、これは関数宣言の宣言子になることができます。(すいません、ちょっと間違ってて、引数の識別子が必要です)

int *(*func(int x))[10]
{
    return nanika;
}

C言語は詳しいという人は、変数'nanika'の宣言はどうなるか考えてみましょう。



もう読むのは問題ありませんね!それでは、最初の例、

int (*(*func(int i))[10])(int)

を書いてみましょう。

これは、

  • 「func takes int and returns pointer to array[10] of pointer to function that takes int and returns int」
  • 「int を戻り値にして、intを引数に取る関数へのポインタ10個の配列へのポインタを戻り値にしてintを引数にとる関数」

の宣言子です。


書くときはスペースの都合上、英語読みのほうがいいです。日本語読みだと、空白の幅を予想しながら書かないといけません。英語読みなら内側から書くことができます。すなわち、

  • 識別子を書く
  • array of なら右に角括弧を書く
  • pointer to なら左に'*'を書く
  • pointer to の次がarray of か、function なら、そこまでのものを括弧でくくる
  • takes うんたら が出てきたら丸括弧の中にそれを書いたものを右に置く
  • 最後の型を一番左に書いて終了

です。それでは、書いてみましょう!!

できましたか?

ちなみに僕はできません。


まとめ : typedefして書くべき。