C++超入門⑤ 関数で処理を分けよう
関数、引数、戻り値、void、変数のスコープを学び、長い処理を役割ごとに分けます。
同じ計算を何か所にも書くと、1つ直すたびに「あそこも直さないと」となります。処理へ名前を付け、まとめて再利用するための道具が関数です。
今回は、2つの値を受け取って結果を返す関数を作ります。難しい設計論より、呼ぶ前・関数の中・戻った後の3段階を追うことから始めましょう。
1. 関数は処理のまとまり
#include <iostream>
int add(int a, int b) {
int result = a + b;
return result;
}
int main() {
int answer = add(3, 5);
std::cout << answer << '\n';
return 0;
}
結果は8です。
int add(int a, int b)の部分を分けると、次のようになります。
| 部分 | 役割 |
|---|---|
最初のint |
戻す値の型 |
add |
自分で付ける関数名 |
int a, int b |
受け取る値の型と名前 |
{ ... } |
関数が行う処理 |
return result |
呼び出した側へ結果を返す |
受け取る値が引数(ひきすう)、返す結果が戻り値です。関数を使うことを呼び出すといいます。
2. コードを上から書いても、実行はmainから
addの定義をmainより上に置いていますが、起動直後にaddが勝手に動くわけではありません。
mainへ入るadd(3, 5)で関数を呼ぶadd内のaを3、bを5として処理する- resultが8になり、
returnで戻る mainのanswerが8になり、表示する
addは何度でも呼べます。add(10, 2)なら12、add(-3, 5)なら2です。ただしこの簡単な加算関数は、結果がintの範囲に収まる値を渡す前提です。
定義は作り方、呼び出しはその作り方を使う場面と分けて読むと、位置関係に惑わされにくくなります。
3. 表示することと、値を返すことは違う
int answer = add(3, 5);
int doubled = answer * 2;
std::cout << doubled << '\n';
戻り値があるので、結果の8を別の計算へ使って16を表示できます。
addの中でstd::cout << a + b;と書いただけでは、呼び出し元が使える計算結果を返したことにはなりません。画面表示は出力先へ文章を送る処理、returnは呼び出した側へ値を渡す処理です。
intなどの値を返す普通の関数では、必要なすべての分岐で値を返すようにします。「たまたま値が出た」ように見える不正なコードへ頼らないことが大切です。
returnの後には進まない
int maxNumber(int a, int b) {
if (a >= b) {
return a;
}
return b;
}
最初のreturnへ来た時点で関数は終了するため、下のreturn bは実行しません。この早い終了を使うと、深いelseを増やさず書けることがあります。
4. 値を返さない関数はvoid
#include <iostream>
#include <string>
void greet(std::string name) {
std::cout << "こんにちは、" << name << "さん。\n";
}
int main() {
greet("Aoi");
greet("Ren");
return 0;
}
表示する文章だけを変えて、同じ関数を2回使っています。
voidは戻り値がない型です。int answer = greet("Aoi");のように数値として受け取ることはできません。
void関数の途中で終了したければreturn;と書けます。値を付けずに戻る形です。最後まで進んで終わる場合は、この例のようにreturnを省けます。
5. boolを返すと、条件が読みやすくなる
bool isEven(int number) {
return number % 2 == 0;
}
number % 2は2で割った余りです。0なら偶数なのでtrue、それ以外ならfalseを返します。
if (isEven(8)) {
std::cout << "偶数です。\n";
}
isEven(8)がtrueを返すので表示されます。isEven(7)なら表示しません。
isEvenのように「何を調べるか」が伝わる名前にすると、if全体を文章のように読めます。関数は、長いコードを隠すだけでなく、処理の意味を名前で表すためにも使います。
6. 関数を後ろへ置くなら、先に宣言する
#include <iostream>
int add(int a, int b);
int main() {
std::cout << add(2, 6) << '\n';
return 0;
}
int add(int a, int b) {
return a + b;
}
int add(int a, int b);は、中身を書かずに関数の形を知らせる宣言です。コンパイラは、この情報を使って呼び出し方を確認します。
add(2)は引数が足りず、定義と一致しません。呼び出しで困ったら、引数の数・型・順番を確認しましょう。
関数の構成と呼び出しはMicrosoftのC++関数の説明でも確認できます。
7. スコープと値渡しを区別する
スコープは、名前を使える範囲です。関数の中で宣言した普通の変数は、その外から直接名前を使えません。
#include <iostream>
void addOne(int number) {
number++;
std::cout << "関数の中: " << number << '\n';
}
int main() {
int count = 3;
addOne(count);
std::cout << "元の値: " << count << '\n';
return 0;
}
関数の中: 4
元の値: 3
「addOneと呼んだのに、元の値が増えない?」と思ったら、渡し方を確認します。
int numberは値のコピーを受け取る値渡しです。numberを増やしても、別の変数countは変わりません。引数に同じ名前を付けたとしても、同じ変数になるわけではありません。
元の変数を更新するなら、増やした値を戻してcount = ...とする設計か、参照の回で扱うint&を使います。
関数内の変数を外へ持ち出そうとしない
関数内の通常のローカル変数は、その関数が終わると寿命も終わります。そのアドレスや参照を外へ返して使うのは危険です。
「結果の値を返す」のと「もう消える場所を返す」の違いは、ポインタと参照で改めて説明します。今は、単純な計算結果なら値として返すと考えておけば十分です。
8. 練習:大きい方を返す関数を作る
maxNumberへ2つの整数を渡し、大きい方を返してください。同じ値なら、その値を返せば構いません。
模範解答
#include <iostream>
int maxNumber(int a, int b) {
if (a >= b) {
return a;
}
return b;
}
int main() {
std::cout << maxNumber(12, 7) << '\n';
std::cout << maxNumber(-4, -2) << '\n';
std::cout << maxNumber(5, 5) << '\n';
return 0;
}
12
-2
5
解説
12と7では最初の条件に入り、12を返します。-4と-2では最初がfalseなので、最後の-2を返します。5と5では>=に当てはまり5です。
正の数だけでなく、負の数・同じ数を試しているのは、考えていなかった条件がないか確認するためです。
関数内に表示を書かず結果を返すので、int best = maxNumber(12, 7);のように別の変数へ保存して使うこともできます。
次の一歩
関数へ分ける前に「何を受け取り、何を返すか」を短く書いてみましょう。入力・計算・表示をそれぞれ役割として分けられると、あとで点数管理のような長いプログラムも読みやすくなります。