Cによるプログラミング入門9
値を戻さない関数
Cでは、printfやscanfなど、何かしてくれるものを関数というのでした。
今回は、関数の自作方法を紹介します。
「関数」とは、一般に、何かしてくれるものです。printfなどは、標準で定義された関数ですが、自分で関数を定義することもできます。まず、サンプルを見てください。
/* func.c */
#include <stdio.h>
/* 関数helloの定義 */
void hello(void){
printf("hello\n");
}
int main(void)
{
hello(); /* helloを使う */
return 0;
}
Fig.1 func.exeの実行
(BorlandC++では、コンパイル時に警告がでますが問題ありません。)
ここで、最初にある
void hello(void){
中身
}
は、「helloという名前の関数」の定義ということになります。
まず、あたまにあるvoidは、「この関数は実行後結果を報告しない(値を戻さない)」という程度の意味です。しかし、まだ、チンプンカンプンですよね。詳しくは次回に説明しますので、ここでは、悩まず、「今回見る関数のあたまにはいつもvoidが付くんだな」と思ってください。
次のhelloが関数の名前で、その後の丸カッコ「(」と「)」は、これが関数であることを示しています。また、丸カッコの間にあるvoidは、「この関数は外から値を受け取らない」という意味です。これも、何がなんだか、だと思いますが、こちらの意味はすぐに説明します。ちょっとだけ待ってください。とりあえず、あたまにあるvoidと同様に、おまじないと思って書いておきましょう。
そして、「{」と「}」の間に書かれていることが、「helloという関数の処理内容」なのです。それは、単に、「hello」と表示して、改行するだけですね。
helloの定義を書いても、実際に使わなければ、この処理内容が実行されることはありません。実際に使うときは、
hello();
とすればよいのです。mainの中にそう書いてありますね。これがそうです。これは定義ではないので、あたまのvoidも丸カッコのvoidもありません。また、中カッコもありません。そういうものなのです。
なお、このように、「関数を実行する」ことを「関数を呼び出す」などと言います。
一番簡単な関数の定義方法を説明しました。しかし、なぜ関数などというものを考えるのでしょうか。それは、「関数をプログラムの部品」にするためです。上のhelloは、たいした仕事をしない関数ですが、「意味のある仕事」をするような関数を書いておけば、それをあとで使えるのです。たとえば、「おみくじ関数」を次のように書くこともできます。
void omikuji(void){
switch(rand() % 5){
case 0:
printf("大吉\n");
break;
case 1:
printf("吉\n");
break;
case 2:
printf("普通\n");
break;
case 3:
printf("凶\n");
break;
case 4:
printf("大凶\n");
break;
}
}
この関数は、呼び出すと、占い結果を表示してくれるわけです。
たとえば、次のように使うことができますね。
/* omikuji3.c */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void omikuji(void){
switch(rand() % 5){
case 0:
printf("大吉\n");
break;
case 1:
printf("吉\n");
break;
case 2:
printf("普通\n");
break;
case 3:
printf("凶\n");
break;
case 4:
printf("大凶\n");
break;
}
}
int main(void)
{
srand( (unsigned)time(NULL) );
printf("おみくじです。\n");
omikuji();
return 0;
}
Fig.2 omikuji3.exeの実行
mainの中では、「おみくじです。」と表示した後に、omikujiを実行しているだけです。
このように書くと、単にタイプが長くなっただけと思うかもしれません。実際、長くなっているのですが、とてもよいことがあるのです。というのは、「おみくじの処理部分」が完全に、関数omikujiの定義内に収まっているので、おみくじの内容を変更したい場合、この部分だけを変更すればよいということになります。
今のプログラムは短いので特にどうということはありませんが、mainの中にえんえんと処理が書いてあるプログラムは、間違えたときなどに直すのがかなり大変です。
そのようなプログラムは、はじめから、処理内容ごとにいくつかの関数に分けて定義しておけばよいのです。関数にはそれぞれ特定の仕事が割り振られるので(もちろん、そのように書けば、の話です)、「直したい処理を担当している関数」だけを調べればよいからです。
そして、関数は、一度書いてしまえば使うのは簡単です。たとえば、関数omikujiを一度書いておけば、あとは、「omikuji();」と書くだけで、そこでおみくじを実行できるわけです。
次のようなサンプルを見てください。
/* ghensu.c */
#include <stdio.h>
int x = 0;
void hello(void){
printf("hello : %d\n", x);
}
int main(void)
{
hello();
x = 3;
hello();
return 0;
}
Fig.3 ghnesu.exeの実行
ここで、xは、mainの外で定義されています。このようにmainや他の関数の外で変数を定義することもでき、そのような変数を、グローバル変数などといいます。(実は、数値型のグローバル変数は、はじめにゼロに初期化されることになっているので、「int
x = 0;」の「= 0」は不要です。が、初期値をはっきり示すために書いておきました。)
今度のhello関数は、「hello」とxの値を表示するだけですね。
mainの中では、2回helloを呼び出し(つまり、実行し)ています。最初の呼び出し時には、xの値が0ですが、そのあとで3にしているので、2度目の呼び出し時にxは3になっているわけです。表示もそのようになっていますね。
このようなグローバル変数は、便利なようですが、「プログラムをわかりにくくする」という欠点も持っています。上のサンプルは短いものですが、ながーいプログラムを想像してみましょう。そのプログラムを他人が読む場合(あるいは、何もかも忘れるくらいあとで自分が読む場合)、途中に「hello();」などという記述があっても、その中で使われているxの値は、どこでどう設定されているかわかりません。「xに値を与えている箇所」をプログラム中探しまわらなければならなくなるのです。
本人ははじめ、「xを変えるのは関数を使う直前だけ」というつもりで書きはじめるかもしれません。それなら、xの変更はあきらかです。しかし、最初はそのつもりでいても、長いプログラムになるとどうなるかわかりません。このようなグローバル変数と、そのグローバル変数を使う関数がたくさんある場合、おそろしく混乱したプログラムになってしまいそうです。
グローバル変数は文法的に否定されるものではないので、「使ってはいけない」とは言えません。しかし、よほどのことがない限り使わない方がよさそうです。
グローバル変数ほど「簡単」ではありませんが、関数が外部とデータの授受をすることができます。まず、データを外から受け取るには、引数というものを使うことができます。これは、おすすめの方法です。
例から見ることにしましょう。以下のプログラムではグローバル変数を定義していません。
/* func2.c */
#include <stdio.h>
void hello(int x){
printf("hello : %d\n", x);
}
int main(void)
{
hello(0);
hello(3);
return 0;
}
Fig.4 func2.cの実行
まず、mainの中を見てください。「hello(0);」は「helloという関数に整数値0を与えて実行」、「hello(3);」は「helloという関数に整数値3を与えて実行」という意味なのです。実行する関数の丸カッコの中に値(または値を持った変数でも可)を入れることで、この関数に値を与えることができるのです。
ところで、今回のhelloの定義では、丸カッコ「(」と「)」の間に、voidではなく、「int
x」が入っていますね。これは、「この関数は、整数値を受け取る。それをxで表す」という意味なのです。逆に言うと、この「int
x」をhelloの後の丸カッコの中に書いたからこそ、helloは「整数値を外から受け取る」ことができるのです。もう一度、func2.cのhelloの定義を見直しておいてください。
そして、helloが実行されるとき、helloに与えられた引数はxにコピーされ、helloが実行されるわけです。そのため、Fig.4のような実行画面になるのです。
ところで、hello(0)やhello(3)の0や3のように、関数に与える値を引数などと言います。そして、関数の定義の中の「引数を表わす変数」を仮引数とかパラメータなどとよびます。helloのxが仮引数であるわけです。この言葉も知っておいてください。
また、これで、func.cのhelloのように「丸カッコの中に書くvoid」の意味もわかったかと思います。関数の定義の丸カッコの中にvoidを書くと、「この関数は、外から値を受け取らない」という意味になるのです。実は、voidとは「空」というような意味なのです。func.cをもう一度見直しておてください。
引数を2つ取る関数を書きたければ、丸カッコの中に、カンマでつなげて書きます。たとえば、次のように書けばよいのです。
void hello(int x, int y){
int i;
for(i = 0; i < y; i++)
printf("hello : %d\n", x);
}
これは、「hello」とxの表示をy回繰り返す関数です。この関数を使うプログラムは、たとえば、次のように書けます。
/* func3.c */
#include <stdio.h>
void hello(int x, int y){
int i;
for(i = 0; i < y; i++)
printf("hello : %d\n", x);
}
int main(void)
{
int n;
printf("繰り返しの数を入力してください:");
scanf("%d", &n);
hello(3, n);
return 0;
}
Fig.5 func3.exeの実行
func3.cでは、後ろの引数(繰り返しの数)をユーザに入力してもらうようにしました(Fig.5では、「5」と入力していますね)が、もちろん、
hello(3, 5);
のように、プログラム中で与えてもかまいません。また、前の引数も、ユーザに入力してもらうようにプログラムを変えても問題ありません。
いずれにしても、与えられた2つの引数が、それぞれhelloの定義のxとyにコピーされ、定義内で使われるのです。
また、最後のhelloのように、関数内で使う変数を関数内で定義することもできます。(上のhelloでは、「i」がそうです。)関数内で定義した変数は、関数内でのみ有効となります。