WisdomSoft - for your serial experiences.

4.1 関数を作る

繰り返し使われる処理は、関数として部品化できます。

4.1.1 特定の処理を関数にまとめる

これまで、プログラムは main() 関数に書いてきました。最初に説明したように main() 関数はプログラムの実行時に最初に呼び出されるアプリケーション・エントリーポイントを表す特殊な関数です。私たちは、必要に応じて main() 以外の関数を作成することができます。これまでは標準関数として定められている printf() や scanf() などの関数を使ってきましたが、こういった何らかの機能をまとめた関数を自分で作ることができるのです。

プログラムの中で何度も使われる処理を関数としてまとめることで、同じコードを何度も記述する面倒から開放され、プログラム全体に整合性を持たせることができます。これは、アプリケーションの設計において極めて重要なことです。最初に少しだけ説明しましたが、関数を定義するには次のように記述します。

関数の定義
戻り値の型 関数名(パラメータリスト) {
	文
	...
}

基本的な書き方は、main() 関数と同じです。関数名は変数同様に C 言語の命名規則にさえ従っていれば自由に指定できます。関数の機能を表すような分かりやすい名前にすることが望まれます。戻り値とパラメータリストについては「4.2 パラメータと戻り値」で詳しく解説しますが、これらを利用すると、関数間でデータをやり取りすることができるようになります。この場では、とりあえず関数は次のように定義します。

void 関数名() { ... }

この関数は、値を受け取らず値を返さないことを表しています。戻り値の型で指定している void は関数が値を返さないことを意味しています。値を返さない関数は return キーワードで値を返すことはできません。関数を呼び出すときは printf() などの標準関数と同じように呼び出すことができます。

関数名();

一度作成した関数は何度でも呼び出すことができるため、プログラムの再利用が可能になります。次のプログラムは新しい関数 Function() を定義し、main() 関数からこれを呼び出して利用しています。

コード1
#include <stdio.h>

void Function() {
	printf("Kitty on your lap\n");
}

int main() {
	Function();
	Function();
	return 0;
}
実行結果
コード1 実行結果

Function() 関数は、printf() 関数を呼び出して画面に文字を表示させる単純な処理を行います。main() 関数から Function() 関数を2回呼び出しています。その結果、画面には Kitty on your lap という文字が2行にわたって表示されます。ただし、関数は使用される関数よりも前に記述しなければなりません。コード1を見て分かるように、Function() 関数は、これを呼び出している main() 関数よりも前に定義されています。

なぜ、関数を使用されるよりも前の位置に指定する必要があるのかというと、関数を発見する前に関数を呼び出した場合、コンパイラはデフォルトとして int 型の戻り値を持ち、引数を受け取らない関数として認識します。そのため、デフォルト型以外の関数を定義より前に呼び出すと、型の不一致でコンパイルエラーとなってしまうのです。

例えば、次のプログラムでは Function1() は問題なく呼び出せますが Function2() はエラーとなります。

int main() {
	Function1();
	Function2(); /*型の不一致によるエラー*/
}
int Function1() {
	return 0;
}
void Function2() {}

Function1() は戻り値が int 型で、パラメータのない関数なので、定義が呼び出しより後ろにあっても、呼び出しと同じ型なので問題なく呼び出せますが、Function2() は戻り値が void 型なので、その前の呼び出しと型が異なります。コンパイラは関数の再宣言とみなし、型が異なっているためエラーを発生させるでしょう。

コード1の流れを追うと、ます main() 関数が実行され Function() 関数が呼び出されます。関数が呼び出されると、プログラムはその関数に制御を移します。コード1の場合 Function() 関数の本体に制御が移行することになります。Function() 関数の処理が終了すると、プログラムは関数を呼び出した元の場所に制御を戻します。この場合は main() 関数に戻ることになります。

図1 関数の呼び出しと復帰
関数の呼び出しと復帰

関数を呼び出しても、関数の処理が終了すると制御が関数を呼び出した元の位置に戻されるので、プログラムの流れは最終的に main() 関数まで戻ります。

void 型の戻り値を持つ関数は値を返す必要がないので return 文を省略することができます。main() 関数は int 型の終了コードをシステムに返す必要があるので return 文を使って値を返しますが、Function() 関数は return 文を使っていません。しかし、次のように明示的に return 文を使って関数を終了させることも可能です。ただし、値は返せないので式を指定することはできません。

void Function() {
	printf("Kitty on your lap\n");
	return;
}

特定の場所で関数を終わらせたい場合は、return 文を使って制御を返すことができます。

関数は何重にも呼び出しを重ねることができます。main() 関数が Function1() 関数を呼び出し、Function1() 関数が Function2() 関数を呼び出し……というように、関数から別の関数を何重に呼び出したとしても、関数は自分を呼び出した場所に制御を戻す性質があるため、必ずトップレベルの制御、すなわち main() 関数に復帰します。

コード2
#include <stdio.h>

void Function2() {
	printf("Function2() : return\n");
}

void Function1() {
	printf("Function1() : Call Function2()\n");
	Function2();
	printf("Function1() : return\n");
}

int main() {
	printf("main() : Call Function1()\n");
	Function1();
	printf("main() : return\n");

	return 0;
}
実行結果
コード2 実行結果

コード2は Function1() 関数から Function2() 関数を呼び出しています。このプログラムでは、制御の流れを視覚的に確認できるようにするため、各関数は自分の関数名と処理を画面に表示します。

プログラムはまず main() 関数から Function1() 関数を呼び出し、Function1() 関数は Function2() 関数を呼び出します。Function2() 関数はすぐに制御を返すので、Function1() 関数に制御が戻ります。そして、Function1() 関数も終了して、最終的に main() 関数に復帰していることが確認できます。表示された結果を見れば、プログラムがどのような順番で実行されているか理解できます。