前章で文字に関する関数をいくつか紹介しましたが、 gets 関数は知らなくても同じ機能のプログラムを書くことができます。試しに getchar 関数を使って gets 関数と同じ機能を持つプログラムを書いてみましょう。(練習になるので、できたらリストを見ないで作ってみてください。)
/*01*/ #include <stdio.h>
/*02*/
/*03*/ main()
/*04*/ {
/*05*/ char ch, str[32];
/*06*/ char *p = str;
/*07*/
/*08*/ while((ch = getchar()) != '\n'){
/*09*/ *p = ch;
/*10*/ p++;
/*11*/ }
/*12*/ *p = '\0';
/*13*/
/*14*/ printf("%s\n", str);
/*15*/
/*16*/ return 0;
/*17*/ }
どうですか?文字列を入れるための変数、str と、それに対応するポインタ変数 p を作ります。そして、while 文を使い、ポインタを移動させながら1文字づつ「’\n’」まで getchar で代入していくのです。
もし、gets 関数が無かったらこのようにして文字列を入力すればよいのですが、入力のたびにこんなことを書いていたら大変ですね。そこで、この機能を関数にしてしまうことができます。
それでは先ほどの例から文字列を読み取る部分を抜き出して gets 関数の代わりとなる mygets を作ってみます。
/*01*/ #include <stdio.h>
/*02*/
/*03*/ main()
/*04*/ {
/*05*/ void mygets(char *);
/*06*/ char str1[32], str2[32];
/*07*/
/*08*/ mygets(str1);
/*09*/ mygets(str2);
/*10*/
/*11*/ printf("str1 -> %s\n", str1);
/*12*/ printf("str2 -> %s\n", str2);
/*13*/
/*14*/ return 0;
/*15*/ }
/*16*/
/*17*/ void mygets(char *p)
/*18*/ {
/*19*/ int ch;
/*20*/
/*21*/ while((ch = getchar()) != '\n'){
/*22*/ *p = ch;
/*23*/ p++;
/*24*/ }
/*25*/ *p = '\0';
/*26*/ }
main 関数の中で mygets という関数を呼び出しています。この関数が自作の関数で、標準ライブラリの gets 関数と同等の機能を持たせています。使い方も gets 関数と同じに作ってあります。そして、mygets の実態は「void mygets(char *p)」以下にある部分で書かれています。
なお、一般に関数を書いて実行することを
さて、関数の説明を行うにあたりイメージはつかんでもらえたでしょうか。具体的な説明をする上でこの例はちょっと大変なので、次はまずもう少し簡単な例から見ていくことにします。
べき乗を計算する関数としては math ヘッダの中の pow 関数がありますが、整数だけを扱う pow 関数の簡易版を考えてみます。
まず始めに思い出してもらいたいことがあります。それは main も関数だった、という事です。ただし、main 関数はC言語にとって特殊なものであり、プログラムを実行すると最も初めに main 関数が実行されるようになっているのです。いうなれば、OS が main 関数を呼び出しているのです。いずれにしても、関数を作る場合、main 関数と同じように「 ~ ( ) { } 」(~は関数名:変数名と同様、自由につけてかまいません)の形で書けばよさそうだということがわかります。
全体の概略を見てみましょう。最初に main 関数があって、その下に pow 関数の実態が書いてあります。さて、プログラムは main 関数から実行されるのですが、と同時にプログラムは上から順に実行されていくという大原則もあります。つまり、main 関数が始まった時にはまだ pow 関数がどんなものか読まれておらず、そのままでは何の効力も持ちません。そこで変数のように、その関数(ここでは main 関数)の先頭でどんな関数を使うのか宣言する必要があります。
int pow(int m, int n);
こうして、実態が下のほうに書かれている関数を使うために、それを宣言することを
では、プロトタイプ宣言は少し置いといて関数の実態部分を説明します。
関数の書式を簡単に書くと
戻り値の型 関数名(引数1, 引数2, ...)
{
/*実行文*/
}
一般に
一般には: [何か] ---> [関数(処理)] ---> [ただ1つの結果] C言語では: [引数] ---> [関数(処理)] ---> [ただ1つの戻り値]
では
戻り値の型 関数名(引数1, 引数2, ...)
まず「戻り値の型」ですが、C言語では返る値(戻り値)がどのような型の値なのか明示する必要があります。それが上に書いた「戻り値の型」で、例では「int」となっています。つまりこの関数は int 型の結果を返します。
そして関数名というのは見てのとおり、この例では pow です。変数名と同様の規則で好きな名前をつけてかまいません。 (*1)
次に引数の部分ですがここには、関数に渡ってくる引数を格納するための変数を宣言しておき、例のように必要に応じた分だけ用意することができます。逆に、この関数を呼び出す時は必ずここに書いた引数の分だけ、引数を渡してやらなければなりません。この pow 関数は、2つの引数を必要としているので、呼び出す時もきちんと対応するように2つ書かなければいけないという事です。
さて、プロトタイプ宣言はこの部分とまったく同じように、
int pow(int m, int n);
int pow(int x, int y);
int pow(int, int);
また、実態部分の後ろは main 関数同様「;」がないのに対し、プロトタイプ宣言には「;」を付けるのも忘れないで下さい。
もう1つ呼び出す側の注意なのですが、引数について気を付けて欲しいことがあります。
z = pow(x, y);
これに関連して「スコープ」を思い出してください。「ある関数内で宣言された変数は、その関数内でしか使えない」ということに気をつけましょう。変数の有効範囲について復習しておくとよいでしょう。
main()
{
int func1(int m);
int func2(int n);
int func3(int x);
int x;
/* 処理 */
}
int func1(int m)
{
x = 10; /*これはダメ。他の関数内の変数は操作できない*/
/* 処理 */
}
int func2(int n)
{
int x; /*この x は main、func3 関数の x とは無関係*/
/* 処理 */
}
int func3(int x){ /*この x は main、func2 関数の x とは無関係*/
/* 処理 */
}
再び実態部分の説明に戻ります。最初の引数部分で宣言した変数ですがこの変数は、その関数の中で普通に宣言された変数として自由に使えます。唯一違うのは、渡された値が最初から入っているということです。また、これ以外の変数を使いたい時は main 関数のときと同様に宣言が必要となります。
以上のことをふまえて関数の中に処理を書く(for 文、printf 関数など、main 関数に記述してきたのと同様の命令が書けます)わけですが具体的には、戻り値にするための値を1つ求めておくことになります。この例では「re」がそれになります。
最後に、戻すべき値が求まったら return 文で値を返し、関数を終了します。
return 戻す値;
return 文が実行されるとその関数は、指定された値を呼び出し側に返すと共に、関数自体の処理も正常終了させて、処理の流れをもとの場所(呼び出したところ)に戻します。簡単に言えば、return 文が関数終了の合図となり、それ以下の命令は実行されません。ですから、複数の値を返そうと思って return 文をいくつか書いても、最初の return 文が実行されれば関数はそこで正常終了し、それ以下の実行文及び return 文はまったく無意味なものになってしまいます(if 文などでどの return 文を実行するか制御することは可能です)。逆にいえば、if 文等で条件分岐した際に「もう関数の処理を終了したい」という場合は、goto 文などで関数の最後まで処理を飛ばさなくても、その場で return して構わないわけです。このことは main 関数でもいえることです。
次に、関数を呼び出した側についてです。以前にも書きましたが、関数を呼び出して戻り値がある場合、呼び出した部分にはあたかもその戻り値の値がそこに記述されているかのように処理されます。例では
z = pow(x, y);
z = 10;
printf("計算結果は %d です。\n", pow(x, y));
ちなみに、戻り値は無視してもかまいません。つまり、関数は呼び出したけどその戻り値は必要ないという場合、代入などをせずにほおっておいても,、何も害はないという事です。実際、printf 関数は「出力した文字数」を戻り値で返していますが、普通は無視しています。試しに、次のような実行文を行なってみてください。
n = printf("abcde\n"); /*「\n」 も1文字*/
printf("%d\n", n);
また、戻り値を型キャストすることもできます。例えば pow 関数は int 型の値を返しますが、z が float 型の変数だったら
z = (float)pow(x, y);
最後に補足説明になりますが、関数はいくつでもつくることができ、きちんと関数内でプロトタイプ宣言しておけばその中で別の関数を使うこともできます。しかし、関数の中で関数を使うような場合、きちんとした階層構造を意識して作るなどしないと、goto 文のように複雑になり、また、関数の独立性も失われてしまうでしょう。ですから自分で関数を作る場合は各関数の関係が簡潔になるよう、分かりやすくする工夫が必要でしょう。