C++言語講座 第1章 8回 関数の宣言
関数の宣言
簡単な関数
今までは、関数は標準ライブラリのもの(max()やmin()など)を呼び出して使うだけでした。ここでは、自ら関数を新たに作る方法をお教えしましょう。標準ライブラリの関数も全て、ここで解説するのと同じ方法で定義されています。
単純な例として、こんな関数を考えてみます。皆さん、西暦何年が平成何年か分からなくなったことはありませんか? 1988 を西暦から引けば平成になるのですが、なかなかいざという時には思い付かないものです。ですから、ここでは、西暦を int型で与えると平成何年かを int型で返してくれる関数を作ってみましょう。次のコードを入力/実行してみて下さい。
[List 1]
1 #include <iostream>
2 using namespace std;
3
4 // 西暦を平成に変換する関数
5 int ad_to_heisei(int ad_year)
6 {
7 int heisei_year;
8 heisei_year = ad_year - 1988;
9 return heisei_year;
10 }
11
12 // main関数
13 int main()
14 {
15 int ad_year; // 西暦を格納
16 int heisei_year; // 平成の年を格納
17 cout << "西暦を入力して下さい:";
18 cin >> ad_year;
19 heisei_year = ad_to_heisei(ad_year);
20 cout << "西暦 " << ad_year << " 年は平成 " << heisei_year << " 年" << endl;
21
22 return 0;
23 }
新たな関数 ad_to_heisei() を作っているのが
int ad_to_heisei(int ad_year)
{
から
}
までです。見てみると、main()関数の書き方と全く変わりないことが分かります。順番に見て行きましょう。
まずは、
int ad_to_heisei(int ad_year)
です。復習ですが、これが関数の宣言です。まず最初の int は main()関数の最初の int と同じで、この関数ad_to_heisei()の返値が int型、つまり整数であることを示しています。つづく、 ad_to_heisei は新しく宣言する関数のなまえです。適当な名前をつけましょう。そのあとの ( ) の中身がmain()関数とは違います。ここには、関数に与えることのできる引数の型と名前を並べて書きます。普通のオブジェクトの宣言と同じような感じですね。つまり、関数 ad_to_heisei() は ad_year という名前の int型の引数を一つとり、int型の返値を返す関数となったのです。
これにつづく { } の中が実際の関数の処理、すなわち定義でしたよね。これもmain()関数と同じです。
関数定義の最後では返値をreturn文で返しています。返している値は main() 関数内での
heisei_year = ad_to_heisei(ad_year);
の行で、main()関数のhersei_yearオブジェクトに代入されています。
オブジェクトのスコープ
ところで、上の例ではmain()関数とad_to_heisei()関数の両方で同じオブジェクト名、 ad_yearとheisei_yearを使っていましたが、大丈夫なのでしょうか? もしくは、これは必要なことなのでしょうか? 試しに、どちらかのオブジェクト名を適当に変えてみて下さい。プログラムの動作は全く変わらないはずです。C++では、各関数ごとにオブジェクトの名前は独立で、同じ名前のオブジェクトであろうが、別のなまえのオブジェクトであろうが、全く関係ない、別のオブジェクトとして扱われます。これは大事ですから、よくよく覚えておいて下さい。以前説明したとおり、このようなオブジェクトの有効範囲はスコープと呼ばれます。
複数の引数を持つ関数
前節では、引数が一つだけの関数を宣言しましたが、ここでは、応用として複数の引数を持つ関数を宣言しようと思います。以前、ふたつの引数のうち大きい方を返すmax()と小さい方を返すmin()を紹介しましたが、ここでは、引数を三つとって、まんなかの値を返す関数を作ってみようと思います。
この機能を持つ関数を普通に作ると何かと面倒なので、標準ライブラリに含まれる、ある関数を紹介しましょう。その関数は、swap() です。swap() は swap(x, y) のようにつかいます。こうすると、なんと、オブジェクト x と y の中身が入れ替わってしまうのです。(ただし、x と y の型は同じじゃないといけません) 一体どうやってこのようなことをやっているかは、また後々お話しすることとして、とりあえずこれを使っていくこととしましょう。 swap() を使うには、algorithm ヘッダーをインクルードします。
[List 2]
1 #include <iostream>
2 #include <algorithm>
3 using namespace std;
4
5 // まんなかの値を返す
6 double mid(double d1, double d2, double d3)
7 {
8 if (d1 > d2)
9 swap(d1, d2);
10 if (d2 > d3)
11 swap(d2, d3);
12 return d2;
13 }
14
15 // main()関数
16 int main()
17 {
18 double x1, x2, x3;
19 cout << "三つの小数を入力して下さい:";
20 cin >> x1 >> x2 >> x3;
21 cout << "三つの小数のうち、大きさがまんなかなのは " << mid(x1, x2, x3) << endl;
22 return 0;
23 }
何故、このmid()関数でまんなかの値が取り出せるかは、皆さんでよく考えてみてくださいね。
ともかく、複数の引数をもつ関数を宣言するには、引数を , (コンマ)で区切れば良いわけです。
補足:本稿では区別しませんが、本来は、関数の宣言の中に書く引数を仮引数(parameter)、関数を呼び出すときに書く引数を実引数 (argument)と言って区別します。
返値の無い関数
今まで説明して来た関数はmain()、min()、max()、ad_to_heisei()すべて、返値を返していました。つまり、関数とは、「0個以上の引数をとり、1つの返値を返すもの」だったわけです。しかしC++では返値を返さない、つまり結果の値を返さない関数を作ることが出来ます。実際の例で見てみましょう。サンプルプログラム1の三択ゲームが題材です。(6-2)節の[List 2]の、do~whileループの中を別の関数にしてみましょう。
[List 3]
1 #include <iostream>
2 #include <cstdlib>
3 using namespace std;
4
5 void do_game()
6 {
7 cout << "箱が三つあります。一つだけがあたりです。"
8 << "あたりはどれでしょう。(1~3の整数で)" << endl;
9 cin >> x;
10
11 if (x == rand() % 3 + 1) {
12 cout << "あたり" << endl;
13 cout << "やったね" << endl;
14 } else if (x < 1 || 3 < x) {
15 cout << "そんな箱ないがな!" << endl;
16 } else {
17 cout << "はずれ" << endl;
18 cout << "残念…" << endl;
19 }
20 }
21
22 int main()
23 {
24 // 乱数の初期化
25 srand(time(NULL));
26
27 int x;
28 string str;
29
30 do {
31 // ゲームをする
32 do_game();
33
34 cout << "まだ続けますか?"
35 << "終了するときは q と入力してください:";
36 cin >> str;
37 } while (str != "q");
38
39 return 0;
40 }
これですこしはプログラムが見やすくなったのではないでしょうか? 注目すべきはdo_game()関数です。do_game()関数は返値を返していません。実際、return文がありませんよね? 「do_game()関数は返値を返しませんよ」と言っているのが、本来は返値の型を書く、
void do_game()
というところです。この、void というのは「空」などという意味で、その関数が返値を返さないことを表すのです。特に結果を呼び出し元の関数に教える必要が無い関数を宣言するときには、関数をvoidにしてしまいましょう。このような関数をvoid型の関数などと呼びます。
値渡し
説明のための、特に実用的な意味は無い簡単な例を紹介しましょう。
[List 4]
1 #include <iostream>
2 using namespace std;
3
4 void func(int a)
5 {
6 a = 20;
7 }
8
9 int main()
10 {
11 int x = 10;
12
13 func(x);
14
15 cout << x << endl;
16
17 return 0;
18 }
まず、x に10を代入して、それを関数func()に渡しています。func()の中では、この引数のなまえは a ですね。func()の中で a に 20 を代入しています。そして処理はmain()関数に戻り、x の値を表示しています。では、画面には何と表示されるでしょう? 20でしょうか?
正解は 10 です。何故でしょうか? 以前、別の関数のオブジェクトは同じなまえであろうと、別のなまえであろうと関係ない、別のオブジェクトだと言いました。まさにこれを地で行くプログラムなのです。つまり、func()の中のオブジェクト a には、あくまで x に入っていた値のコピー(つまり 10)が入っているだけで、a の値をいくら変更しても、コピーの値を変更しているだけで、元のオブジェクト(つまり x)は変わらないのです。このような引数の渡し方を値渡し(pass by value)と呼びます。
プロトタイプ宣言
ところで、今まで、main()関数から呼び出す別の関数は全て、main()関数より上に書いてきました。こうしないといけないのでしょうか? 試しに、下の例をコンパイルしてみて下さい。
[List 5]
1 #include <iostream>
2 using namespace std;
3
4 int main()
5 {
6 int x = 10;
7
8 func(x);
9
10 cout << x << endl;
11
12 return 0;
13 }
14
15 void func(int a)
16 {
17 a = 20;
18 }
エラーが出たはずです。何度も行ったように、C++のコンパイラは頭がよくありません。ソースコードを上から順番に見ていって、main()の中の
func(x);
のところまでいって、「え? func()? そんな関数は見たことも聞いたこともないぞ!」となってしまいます。ですから、上のプログラムではダメで、呼び出される関数は、呼び出し元の関数よりも先に書かないといけないのです。
ですが、それではさすがに自由度が低すぎます。他の方法は無いのでしょうか? 次のプログラムを実行してみて下さい。
[List 6]
1 #include <iostream>
2 using namespace std;
3
4 void func(int a);
5
6 int main()
7 {
8 int x = 10;
9
10 func(x);
11
12 cout << x << endl;
13
14 return 0;
15 }
16
17 void func(int a)
18 {
19 a = 20;
20 }
今度はうまくいったはずです。ミソは、main()関数の上の
void func(int a);
です。このように、関数の宣言の部分だけをあらかじめ書いておくことで、定義は後回しに出来るのです。このような宣言を関数のプロトタイプ宣言と呼びます。
