ログイン
kid's world

関数に関する注意

関数に関する注意

前回まではmain関数1つを使ってプログラムを作成してきた。今回からは複数の関数を使っていくわけだが,ここで2つばかり注意しなければならないことがある。それは,関数と変数の宣言である。

プロトタイプ宣言

ここまで書いてきたプログラムはすべて正常に動作するが,基本的には関数を作るにはプロトタイプ宣言を行う。この宣言は,「関数がどのような引数を受け取り,どのような型の値を戻り値として返すのか」をコンパイラに対して明確に伝えることが目的で,プログラムリストのはじめのほう(インクルードした直後)で行うのが一般的である。

プロトタイプ宣言を行わないと,現在位置よりも後ろに定義されている関数は呼び出すことができない(Fig. 1)。それを呼び出すためにはプロトタイプ宣言を行い,その関数が「どのような引数を持ち,どのような型の戻り値を返すのか」をあらかじめ明確にしておかなければならないのである。すなわち,プロトタイプ宣言は,関数間の呼び出しを自在に行えるようにするために,なくてはならない作業なのである。

Fig. 1: プロトタイプ宣言を行わない場合

/c/function-fig1.png

では,プロトタイプ宣言はどのように行えばいいのだろうか。実は非常に簡単である。関数の定義の1行目の最後にセミコロンをつけた形が,プロトタイプ宣言の形である。つまり,float型の値を返すffoo関数の場合,そのプロトタイプ宣言は,

float ffoo();

とする。ここでList 3を見ていただこう。このプログラムにはffoo関数,fbar関数,main関数が順に並んでいる。ffoo関数はfbar関数を呼び出し,main関数はffoo関数を呼び出している(Fig. 2)。このプログラム自体には何の意味もないのだが,ここでプロトタイプ宣言をしないでコンパイルを行ったときにどうなるのかを見ていただきたい(Fig. 3)。

List 3:プロトタイプ宣言の必要性

#include <stdio.h>

float ffoo();
float fbar();

float ffoo()
{
  return fbar();
}

float fbar()
{
  printf("fbar\n");
  return 3.14159;
}

main()
{
  printf("%f", ffoo());
}

Fig.2: List 3の構造

/c/function-fig2.png

Fig.3: プロトタイプ宣言なしでのコンパイル

/c/function-fig3.png

ffoo関数では自分よりも後ろで定義されているfbar関数を呼び出しているが,コンパイラがその呼び出しを行っている行まで解析を行ったとき,もしプロトタイプ宣言を行っていなければ,ここで呼び出そうとしているfbar関数が,どのような引数をとり,どのような型の値を返すのかがまったくわからないため,戻り値をint型とみなしてしまうのだ。そして解析を進めていくと,int型とみなしたはずの関数がfloat型と定義されていることがわかったため,その時点でコンパイルを中断したというわけである。

したがって,関数を定義するときは「同時に必ずプロトタイプ宣言を行う」という癖をつけておいたほうがよい。そうすることによって,このようなエラーを回避することができ,自由に関数をどこからでも呼び出すことができるようになる。

変数のスコープ

次に注意しなければならないのは,変数の宣言である。今までmain関数のみでプログラムを作ってきたときにはさほど気にする必要はなかったのだが,実は関数の中で宣言した変数は,その関数の中でしか利用することはできない。これを,ローカル変数と呼んでいる。すなわち,これまで利用してきた変数はすべてローカル変数である。

それに対して,どの関数からでも利用できる変数がある。それを「ローカル」とは逆の「グローバル」という名前を使って,グローバル変数と呼んでいる。

ローカル変数

ローカル変数は,その変数が宣言された関数内でのみ存在している。たとえばfoo関数の内部でint型変数を宣言しているとき,foo関数が呼び出されたときに変数iが作られ,foo関数の実行が終わったときにその変数はメモリ上から破棄される。

また,ローカル変数はほかの関数から利用することはできない。その制限は逆によい副作用を生む。すなわち,異なる関数では,同じ名前の変数を宣言してもよいということになる。その関数の中で,ほかの関数には気がねなく,自分の好きな名前の変数を作ればよいということになる。いちいちプログラム全体を見直して「この変数名は使えたかな?」などと考えなくてもよいのである。また,ほかの関数から変数の値を変えられてしまうということは起こらないため,完全にその1つの関数の中でのみ考えればよくなる。考えなければならない範囲を狭くすることで,プログラマはその作業のみに集中することができるのだ。

このような変数の有効な範囲のことを変数のスコープと呼んでいる。ローカル変数のスコープは,ローカル変数が宣言されている関数の内部だけである。

グローバル変数

しかし,どうしてもすべての関数で利用できる変数を作りたい場合が出てくる。そのときは,グローバル変数を利用する。グローバル変数の宣言は,関数の「外」で行う。すなわち,List 4のようにする。

List 4:グローバル変数の宣言

#include <stdio.h>

int g;

void foo()
{
  g=100;
}

main()
{
  foo();
  printf("%d", g);
}

List 4にはfoo関数とmain関数があり,int型のグローバル変数gがある。foo関数では変数gに100を代入していて,main関数ではfoo関数を呼び出したあとに,変数gの内容を表示している。結果として,画面には100という数値が表示される。このように,グローバル変数は,どの関数からでも自由にアクセスすることができる。

しかし,初心者の場合はグローバル変数はできるだけ使わないほうがよい。ローカル変数の利点としてプログラマに対する負担の軽減を説明したが,グローバル変数の作りすぎは,いろいろな意味でプログラマに負担をかけることになるからだ。関数をまたがる値の受け渡しは,関数の戻り値や,引数を十分活用することで実現できる。ほとんどの場合,グローバル変数は使うことはない,と考えてもよいだろう。


値を受け取る関数」へ進む

広告


©Toshio Koide 1996-2007.

目次

リンクについて

リンクは御自由にどうぞ。

メール

mail.gif

広告