これまでに printf, scanf, sqrt などの組み込み関数をいくつか使ってきた。 またプログラム本体は main 関数に書くことも学んだ。 ここではユーザー定義関数の書き方を学ぶ。
プログラムを書き慣れていない人がある程度大きなプログラムを書くとき、 そのほとんどの部分を main 関数の中に書いてしまうことがある。 この様にすると、プログラム全体の流れが理解しにくくなり、またたくさん用いられるであろう 変数の管理も煩雑になる。 この状況は数学における定理の証明と似ている。 多くの場合、難しい定理を証明する場合は、いくつかの補題などを用意し、 それを組み合わせて用いることによってなされる。 場合によっては補題の証明に更に他の補題が用いられる。 また補題の証明中で用いられた記号は、補題を用いる場合には意識する必要がない。 C 言語における関数は、まさにこれらの補題のようなものである。
2 次関数 f(x)=x2+1 を C 言語の関数として定義してみよう。
float polyfunc(float x) { return x*x+1; }関数はいくつかの値に対して一つの値を返す。 受け取る値を関数の引数といい、 返す値を関数の戻り値という。 上の例では polyfunc が関数の名前、 x が引数でその型は float、 また初めに書いてある float は戻り値の型である。 一般に関数は、何らかの処理をした後で return によって戻り値を返す。 利用の仕方は組み込み関数の場合と同じで以下のようになる。
#include <stdio.h> float polyfunc(float x) { return x*x+1; } int main(int argc, char *argv[]) { float x; printf("Input a number : "); scanf("%f", &x); printf("%f\n", polyfunc(x)); return 0; }このような簡単な関数は実際には書く必要がないように思うかもしれないが、 実際のプログラムでも非常に有効である。 例えば何らかの計算をする場合に、多項式を変えたらどのようになるかを調べたくなるときもある。 このときに、もしも関数が複数の場所にそのまま書いてあれば、そのすべてを書き直さなくてはならない。 しかし、関数として定義したものを呼び出しているのであれば、呼び出される関数を書き換えるだけでよい。 簡単なものでも複数回呼び出すならば関数として独立させた方がよい、と覚えておこう。
ここでもう一つ注意しておこう。 上の例では関数 polyfunc を main の前に書いた。 しかし関数を書く位置には特に指定はなく main の後でもよい。 プログラムはコンパイル時に引数の型のチェックが行われ、 問題があればエラーまたは警告を表示する。 関数が定義される前に呼び出されていると型のチェックがうまく機能しない。 そこで関数の引数、及び戻り値の型だけを前もって宣言しておく。 以下の例の 3 行目が宣言で、実際の定義は main 関数の後で行われている。
1 #include <stdio.h> 2 3 float polyfunc(float x); 4 5 int main(int argc, char *argv[]) 6 { 7 float x; 8 9 printf("Input a number : "); 10 scanf("%f", &x); 11 printf("%f\n", polyfunc(x)); 12 13 return 0; 14 } 15 16 float polyfunc(float x) 17 { 18 return x*x+1; 19 }関数は複数の引数をとることもでき、その場合には func(float x, int a) などのように書く。 また関数は必ずしも引数を必要とはしない。 この場合には func(void) と書く。 関数は可変数の引数をとることもできるが、ここでは説明しない。 例えば printf, scanf は可変数引数をとる関数の例である。 関数が戻り値を必要としない場合には void func(...) と書く。
もう一つ簡単な例を見ておこう。 次の例は二つの整数に対して大きい方の数を返す関数 max を定義して利用している。
#include <stdio.h> int max(int a, int b) { if (a > b) return a; else return b; } int main(int argc, char *argv[]) { int a, b, c; printf("Input two integers : "); scanf("%d %d", &a, &b); c = max(a, b); printf("max is %d\n", c); return 0; }main 関数と同じように関数内の任意の場所に return を書くことができる。
C 言語では利用する変数は予め宣言しておかなければならない。 これまでの例では main 関数のはじめの部分で宣言をしていた。 同様のことが他の関数でもできて、その変数はその関数内でのみ有効である。 この様な変数を局所 (local) 変数という。 これによって関数の独立性が高まり、他の関数で利用されているかどうかを 意識することなく、自由に変数名を利用することができる。 これに対して、関数の外で定義された変数はどの関数からも参照することができる。 この様な変数を大域 (global) 変数という。 局所変数と大域変数に同じ名前が用いられた場合には局所変数が優先される。 大域変数は使うことによる利点がはっきりとしている場合を除いて、 なるべく使わない方がよい。
簡単な例で様子を見てみよう。
#include <stdio.h> int a; /* global */ void func(int i) { int a; a = 1; printf("in func : a = %d\n", a); i++; printf("in func : i = %d\n", i); return; } int main(int argc, char *argv[]) { int i; a = 2; printf("in main : a = %d\n", a); i = 1; printf("in main : i = %d\n", i); func(i); printf("in main : i = %d\n", i); printf("in main : a = %d\n", a); return 0; }このプログラムの出力は以下の通りである。
in main : a = 2 in main : i = 1 in func : a = 1 in func : i = 2 in main : i = 1 in main : a = 2変数 a は関数の外で宣言されているので大域変数である。 main 関数で、まず大域変数 a に 2 を代入して出力している。 次に main 関数の局所変数 i に 1 を代入して表示する。 次に i を引数として func を呼び出す。 func では、同じ変数名 i でそれを受け取っているが、この i は func の局所変数として扱われる。 したがって func 内でインクリメントして i = 2 となっているが、 main に戻れば i = 1 のままである。 また func 内で宣言された局所変数 a は大域変数 a に優先するので func 内では a = 1 となっていることが確認できるが、 main に戻れば a = 2 である。
この例で、変数 i を関数に渡して関数内で i を書き換えても、i 自身は変わっていない。 これは、実際には i を渡しているのではなく i に代入されている値 1 を渡しているからである。 これは通常「C 言語の関数は値渡しである」と説明される。 変数 i に代入されている値自体を書き換える方法についてはポインタの所で説明する。
課題 04.01
二つの自然数 m, n に対して、二項係数 mCn を求める関数 binomial を書き、
それを実行するプログラムを書け。
課題 04.02
整数 (自然数) を引数とし、それが素数ならば 1、素数でなければ 0 を返す
関数 isprime を書き、それを用いて素数を判定するプログラムを書け。
課題 04.03
前の課題の関数 isprime を利用して、ゴールドバッハの予想を 100 までの偶数について
検証するプログラムを書け。
(ゴールドバッハの予想とは「4 以上の偶数は二つの素数の和である」というもので、
有名な未解決問題の一つである。)
課題 04.04
二つの整数を引数とし、ユークリッドの互除法によってその最大公約数 gcd を返す関数を書き、
それを実行するプログラムを書け。
フィボナッチ数を求める関数 Fibonacci を書いてみよう。フィボナッチ数とは F(0)=0, F(1)=1, F(n+2)=F(n+1)+F(n) で定まる数である (課題 03.8 参照)。 このとき、もしも関数が自分自身を呼び出すことができたならば定義は簡単であり、 C 言語では実際にそれが可能である。 これを関数の再帰呼出しといい、 自分自身を呼び出す関数を再帰関数という。 再帰関数を書くときにはどのようにして停止するのか (停止条件) をきちんと考えておく必要がある。 この例の場合には n=0 または n=1 で停止するようにすればよい。 したがって Fibonacci は
int Fibonacci(int n) { if (n == 0) return 0; if (n == 1) return 1; return Fibonacci(n-1) + Fibonacci(n-2); }と簡単に定義できる。(この関数は n が負だと無限ループに陥る。)
課題 04.05
関数 Fibonacci を実行するプログラムを書け。
また負の n の場合にはメッセージを出力して終了するように改良せよ。
課題 04.06
課題 04.01 の関数 binomial を再帰関数として書け。
課題 04.07
課題 04.04 の関数 gcd を再帰関数として書け。
再帰関数は計算効率が良いとはいえず、 また再帰にしなくても書くことができる。 したがって不用意に多用するのは勧められないが、 手法は美しく数学的なので参考にするとよい。