関数、引数、戻り値
これまでも SysDialog という関数を使ってメッセージの表示を行ってきました。このように、何かデータを渡して処理を行うものを関数といいます (厳密な説明ではありませんが、とりあえずそのように理解してください)。関数に渡すデータを「引数 (ひきすう)」あるいは「パラメータ」といいます。渡されたデータに対する処理は関数本体で定義されます。SysDialog は Queek II が提供する「システム関数」なので、ユーザーが中身を気にする必要はありません。関数は C 言語の中でも最も重要な機能で、ユーザーも任意の処理を自作関数として定義することができます。これまで SysDialog 関数を使ってきましたが、データの流れは「呼び出し側→関数」の一方通行でした。関数の便利なところは、「呼び出し側←関数」という風に、関数からデータを受け取ることもできるという点です。この、関数から呼び出し側に渡されるデータを「戻り値 (もどりち)」といいます。関数には変数と同様、戻り値の型が決まっています。関数の戻り値は、変数と同様そのまま数式中で使うことができます。何も戻り値を返さない関数は void 型として定義されます。void 型の関数は数式中で使用することはできません。引数には、変数と同様に型と名前が決められています。
関数のプロトタイプ
関数の戻り値型と引数型、引数名をまとめて表したものをプロトタイプといいます。プロトタイプはこれ以降、様々な関数を説明するために使われますので、その表す意味を理解できるようにしてください。例として、SysDialog 関数のプロトタイプは次のように表されます。
void SysDialog(string str);
このプロトタイプの意味は、まず最初の void が戻り値を返さないことを、次の SysDialog は関数名を、( ) 内の string と str は引数型と引数名を表しています。プロトタイプを示す場合にもセミコロンは最後に付けることになっています。これまで SysDialog 関数には string 型以外にも数値を渡していましたが、暗黙的に型の変換 (キャスト) が行われていたので気にすることなく使っていたのです。一般の C 言語では、関数本体の定義より前や、別のソースファイルに定義された関数を使いたい場合に、ソースコード中にプロトタイプを書いて「プロトタイプ宣言」を行う必要があるのですが、Queek C ではプロトタイプ宣言が不要となっていますので、プロトタイプは関数の解説にのみ使用されます。さて、ここで今まで使っていなかった関数のプロトタイプを見てみましょう。
int YesNoDialog(string str);
上記プロトタイプは「この関数の名前は YesNoDialog で、str という名前の string 型の変数をひとつ取り、int 型の戻り値を返す」ということを表しています。YesNoDialog 関数は大体 SysDialog 関数と同じですが、「OK」の代わりに「はい」「いいえ」のボタンが表示され、「はい」が押されると 1 が、「いいえ」が押されると 0 が返される、という動作が定義されています。実際にこの関数を使ったスクリプトを見てみましょう。
関数の戻り値を受け取るテスト
void main(){ int a = YesNoDialog("選択してください。"); SysDialog(a); }
解説
関数の戻り値は、そこに変数や数値を書いた場合と全く同じように使用することができます。上記の例では、YesNoDialog 関数で「はい」「いいえ」を選択した結果の戻り値を a という int 型の変数に一旦代入し、その数値を SysDialog 関数で表示しています。それぞれのボタンを押したときにどのような値が返されているか確かめてみてください。なお、関数が行う処理の内容だけが重要で、戻り値を使う必要がないような場合には、関数呼び出しだけの文を書いてもかまいません。この場合、戻り値は使用されずに破棄されます。ここまでですでに気付かれている方もいると思いますが、main というのも実は関数になっています。この学習用のシナリオデータは、起動すると main という関数が呼ばれるように設定されているのです。main 関数は戻り値もなければ引数もない関数です。関数の定義の仕方はこれまでやってきたように、プロトタイプのセミコロン ; のかわりにブロック { } を付けてその中に行いたい処理を書けばよいのです。次は、引数をとって戻り値も返す関数を作ってみましょう。
自作関数を作ってみる
/* * 2 個の実数の平均値を返す関数 */ float average(float a, float b){ return (a+b)*0.5f; // 足して 2 で割る } /* * エントリーポイント */ void main(){ SysDialog(average(10.0f, 5.0f)); // 関数を呼び出して使ってみる }
解説
関数名のつけ方は変数と同じですが、システム関数と同じ名前や予約語は使用できません。複数の引数をとる場合は、型名と引数名を 1 セットにして、それらをコンマで区切って ( ) 内に並べます。関数の定義の仕方は説明しましたが、戻り値を返す方法をまだ説明していませんでしたね。上のスクリプトを見てもらえばわかると思いますが、関数が戻り値を返すには、
return 戻り値;
と書きます。これを「return ステートメント」といいます。return は戻り値を返し、その関数を終了する命令です。これはどちらかというと本当に「命令」であって、関数ではありません (このように、関数ではなく言語自体の機能として実装されている命令をステートメントといいます)。このため、return には関数呼び出しを示す ( ) を付ける必要がありません。( ) を付けても問題はありませんが、それは演算子の優先順位を制御するカッコとして処理されます。関数が終了すると、呼び出し元に制御が戻ります。ちなみに、main 関数 (エントリーポイント) の呼び出し元は Queek II の内部で、main 関数が終了すると RPG ランタイム自体が終了するようになっています (実際に RPG を開発するときはもう少し違う感じになりますが)。さて、自作した関数 average ですが、この関数は 2 個の float 型の値をとり、それらの平均値を返すというものです。コメントには「足して 2 で割る」と書いてありますが、コードは 0.5f を掛けるという処理になっています。コンピュータは除算より乗算のほうが (速度面で) 得意なので、このようにしてみました (ただし 1 回程度の演算では差は感じられません)。原則としてプログラムは上から順に実行されると書きましたが、関数単位となると話は変わってきます。エントリーポイントは main 関数で決まっているので、まず main が実行され、それ以降の実行順序は関数の呼び出しによります。関数はブロック { } の最後に到達すると自動的に終了します。値を返すよう定義された関数が return をせずに関数の最後まで来た場合、どのような値が返されるかは予測できません。戻り値が void の関数でも、関数を途中で終了するために return を使うことができます。この場合、戻り値の場所には何も書かずに、単に return; と書きます。
仮引数と実引数
呼び出し元が関数に渡す値と、関数が呼び出し元から受け取る値は同じものですが、それぞれ名称が異なり区別されます。呼び出し元から見て関数に渡す値は「実引数」と呼ばれ、関数から見て呼び出し元から渡された値は「仮引数」と呼ばれます。呼び出し元が、関数に渡す実引数を数式で記述していた場合、実際に関数が呼び出される前に実引数の数式が処理され、数式中に関数呼び出しが入っていればそちらの関数呼び出しが先に行われます。関数に渡すための実引数の値を計算することを、引数の評価といいます。引数が評価される順序は、一般の C 言語ではコンパイラが自由に決められることになっていますが、Queek C では記述された順序で評価されることが保障されます。関数呼び出しの際、定義されている仮引数の数と呼び出し元から渡した実引数の数が異なっていると、コンパイルエラーとなります。
演習問題
以下の文を実行すると、変数 a の値はどうなるか。また、average 関数は合計 4 回用いられているが、どういった順序で呼び出されるか。
float a = average(average(1.0f, average(2.0f, 3.0f)), average(4.0f, 5.0f));