(例えば)20 個の int 型変数の配列 a[ ]を宣言し、 それらを(例えば)a[0] は 1 で、 a[1] は a[0] の二倍で、 a[2] は a[1] の二倍で、 ……と埋めてゆく。 そしてそれらを順に出力せよ。
C言語では、自分で関数を作ることができます。 K&R2 §1.7, 1.8 も合わせて読んでください。
次のプログラムは K&R2 §1.7 からの引用です。 (一部変更してあります。)
#include <stdio.h> int power(int base, int n); /* プロトタイプ宣言 */ int main() /* 関数 power() をテストする */ { int i; for (i = 0; i < 10; i++) { printf("%d %d %d\n", i, power(2, i), power(-3, i)); } return 0; } int power(int base, int n) /* base の n 乗を返す (n >= 0) */ { int i, p; p = 1; for (i = 1; i <= n; i++) { p = p * base; } return p; }
まずは「プロトタイプ宣言」の行は飛ばして、 「power(x,y) は x の y 乗を計算する関数」 と思って main() の終わりまでを読んでみてください。 関数 power() を何回か利用して、 2 と -3 のベキ乗を計算しているプログラムであることがわかると思います。
※ 実は main() にもいままでと違う点が二箇所ある。それはあとで。
※ 「power(2, i)」などとしてその値を計算させることを 「関数 power() を呼び出す」と言うときがある。
この power() は、C言語に元々そなえつけの関数ではなく、 自分で作ったものです。
main() のあとの残りの部分が関数 power() の定義です。 そこを説明します。
関数は、cos(0) が 1 であるように、値をもっています。 その値のことを「返り値(かえりち)」と言います。 「int power」の int は、 power() の返り値が int 型であることを示しています。
また、 関数を使うときには power(x,y) のようにして使いますが、 この x と y のことを「引数(ひきすう)」と言います。 この power() の場合は x, y は int 型と決めましたので、 「関数 power() は int 型の引数を二つとる」と言ったりします。
関数の定義のところでも x, y と書いてもいいのですが、 引数の意味がわかりやすいように、 最初の引数を base, あとの引数を n と呼ぶことにしましょう。 「int power」 のあとの小カッコの中には引数の型と名前を、 引数ごとにカンマで区切って書きます。 この例では第一引数は int 型で名前は base, 第二引数も int 型で名前は n, というわけです。
※ 引数には「仮引数」と「実引数」の区別があるがここではふれない。
その次の行の「int i, p;」は、 関数 power() の中で使う変数の宣言です。 いままで main() の中で宣言していたのと全く同じスタイルになっています。
そのあとの部分では base の n 乗を計算していることがわかりますか? そして最後の「return p;」でその値を返しています。 つまり、return のうしろに書かれた式の値がこの関数の値になる、 という約束です。
「プロトタイプ宣言」に戻りましょう。 ここには、power() の定義の一行目と同じものを書きます。 ただし、最後にはセミコロン「;」をつけます。 これはコンパイラに 「これから出てくる power() というのはこういう関数 (int 型の引数を二つとって int 型の値を返す関数) なんだぞ」 と教えるためのものです。
※ C言語に元々そなえつけの関数(printf(), sin() など) を使う場合はプロトタイプ宣言を自分で書く必要はなかった。 実はそれらは include された stdio.h や math.h に書いてあるのだ。 これらのファイルはディレクトリ /usr/include にある。
さて、 main() を注意深く読んだ人は、 いままで「main()」となっていた部分が 「int main()」となっていること、 main() の最後に 「return 0;」があることに気づいたでしょう。
main() も関数の一つなので返り値があります。 実は main() は OS に int 型の値を返します。 その使いみちについてはこの実習では説明しないと思いますが、 プログラムが正常に終わったときは 0 を、 そうでないときは 0 以外を返すのが普通とされています。
このプログラムは main() の中で変数 i を使い、 関数 power() の中でも変数 i を使っています。 しかし、どちらもそれぞれの関数の中だけの変数なので、 両者は全く無関係です。 すなわち、 power() の中で i の値が変わっても、 main() 側の i は変わりません。
※ return は、必要なら一つの関数の中に複数個おいてもよい。
if (...) { return 0; } else { return 1; }のように。
※ 実用の上では、 ベキ乗関数は前に紹介した pow() を使うのがよい。 なお、 二乗や三乗の場合は pow(x,2), pow(x,3) よりも x*x, x*x*x のほうが速い。
次のプログラム片は、K&R2 §1.8 からの引用です。 (一部変更してあります。)
int power(int base, int n) /* K&R2 より */ { int p; for (p = 1; n > 0; n--) { p = p * base; } return p; }
前のプログラムの、 関数 power() の定義の部分だけを上で置き換えても、 プログラムは同じように動きます。
例えば、 main() の中の for ループを何度か回わり、 i が 3 となって power(2, i) が実行されるときのことを考えてみてください。 power() の側では最初 base が 2, n が 3 です。 その n をだんだん減らしながら power() 内の for ループを回わり、 n が 0 になるとループを抜けて main() に帰ります。 main() に戻ったとき、i は 0 に減っているのでしょうか?
そうではありません。 i は 3 のままです。 power(2, 3) を計算するときに power() に渡した 3 は i のコピーであって変数 i そのものではない、 というのがC言語の約束です。
このことを「call by value(値による呼び出し)」ということがあります。
※ 教科書では省略されている中カッコもこのプリントでは省略せずにつけているのは、 次のようなミスに気づかないことがあるからだ。 上の main() の for ループを K&R2 のように中カッコなしで書いたあと、 「0 のベキ乗もテストに加えよう」と思いたち
int main() { int i; for (i = 0; i < 10; i++) printf("%d %d %d\n", i, power(2, i), power(-3, i)); printf("%d\n", power(0, i)); return 0; }のようにもう一つの文をつけ加えたが、 0 のベキ乗は一つしか出力されない……?!
いろいろな関数を書いて、くり返しと関数の両方の練習をしましょう。 すぐできるもの、手間のかかるものが混じっていますので、 適当に取捨選択して取り組んでください。
なお、「関数を書いてみる」というのは、 「テキストファイルとして作成して終わり」ではなく、 検査用の main() も書いて、コンパイルして実行し、 思ったように動くことを確認することです。
上の int power(int base, int n) を、 double 型のベキ乗も計算できるよう、 double power(double base, int n) に改造せよ。 さらに、n が負の整数のときも正しく計算できるように改良せよ。
※ 単に「関数 power()」でなく 「関数 int power(int base, int n)」と呼ぶことで、 引数の数や型、返り値の型をも伝えることができる。
0 の階乗は 1 です. 1 の階乗は 1 です. 2 の階乗は 2 です. 3 の階乗は 6 です. 4 の階乗は 24 です. ...のように出力させてみるとよいだろう。
※ すぐにできない人はその前に
int n, f; n = 4; /* ここで 4 の階乗を計算 */ printf("%d の階乗は %d です.\n", n, f);として 「4 の階乗は 24 です.」が出力されるようなプログラムを書いてみよ。 それを参考にすればできるはずだ。
0 以上の整数 n および 0 以上 n 以下の整数 r に対し、 n 個のものから互いに異なる r 個のものを取り出す方法の総数 nCr の値を返す関数 int comb(int n, int r) を書け。 テスト用の main() は各自で工夫せよ。 パスカルの三角形が書ければ美しかろう。
二つの整数の最大公約数を返す関数を書け。
初等超越関数(指数関数、対数関数、三角関数など) を自作してみよ。 Taylor 展開を利用して計算するので、忘れている人は調べておくこと。 「収束するまで足す」というのは(現段階では)むずかしいので、 決まった項数で打ち切ればよいだろう。 テスト用 main() ではもともとある関数と自作の関数を呼んだ結果を並べて出力するとよい。
※ 自作の exp や sin に「exp」「sin」という名前をつけると、 もともとある関数の名前と重複してしまってまずい。 こんなときは「myexp」「mysin」とするのも一案だ。 「私の exp」「私の sin」といった感じ。