P - 1.15.関数 Editorial /

Time Limit: 0 msec / Memory Limit: 0 KB

前のページ | 次のページ

キーポイント

  • 関数を作成することを関数を定義すると言う
返り値の型 関数名(引数1の型 引数1の名前, 引数2の型 引数2の名前, ...) {
  処理
}
  • 関数の返り値はreturn文を使ってreturn 返り値;で指定する
  • 関数の返り値が無い場合は返り値の型にvoidを指定し、return文はreturn;と書く
  • 関数の引数が不要な場合は定義と呼び出しで()だけを書く
  • 処理がreturn文の行に到達した時点で関数の処理は終了する
  • 返り値がある関数で返り値の指定を忘れた場合、どんな値が返ってくるかは決まっていない
  • 引数に渡された値は基本的にコピーされる

関数

関数を作成することを関数を定義すると言います。

次の例では、STLのmin関数と同じ機能を持つ関数をmy_minを定義しています。

#include <bits/stdc++.h>
using namespace std;

// 関数定義
int my_min(int x, int y) {

  if (x < y) {
    return x;
  }
  else {
    return y;
  }

}

int main() {
  int answer = my_min(10, 5);
  cout << answer << endl; // 5
}
実行結果
5

関数の動作

次のスライドは上のプログラム全体の動作を説明したものです。

関数の定義

関数の定義はmain関数より前で行います。

関数定義の記法は次のとおりです。

返り値の型 関数名(引数1の型 引数1の名前, 引数2の型 引数2の名前, ...) {
  処理
}

前の節で見たとおり、引数は「関数に渡す値」、返り値は「関数の結果の値」のことです。
my_min関数はint型の引数を2つ受け取り、計算結果としてint型の値を返すので、定義は次のようになります。

int my_min(int x, int y) {
}

呼び出し方はSTLの関数と同様です。
次のように呼び出した場合、引数x10が代入され、引数y5が代入されます。

my_min(10, 5);

返り値の指定

関数の返り値は、return文で指定します。

return 返り値;

my_min関数では2つの引数x,yのうち小さい方を返すので、次のように書きます。

if (x < y) {
  return x;
}
else {
  return y;
}

返り値が無い関数

関数の返り値は無いこともあります。その場合は返り値の型にvoidを指定します。

次のプログラムのhello関数は、引数の文字列の始めに"Hello, "をつけて出力します。
返り値は必要ないのでvoid型を指定しています。

#include <bits/stdc++.h>
using namespace std;

// 返り値が無いのでvoidを指定
void hello(string text) {
  cout << "Hello, " << text << endl;
  return;
}

int main() {
  hello("Tom");
  hello("C++");
}
実行結果
Hello, Tom
Hello, C++

返り値がvoid型である関数のreturn文は次のように書きます。

return;

引数が無い関数

関数の引数が不要な場合は、定義と呼び出しでは()だけを書きます。

#include <bits/stdc++.h>
using namespace std;

// 整数の入力を受け取って返す関数
// 引数なし
int input() {
  int x;
  cin >> x;
  return x;
}

int main() {
  int num = input(); // 引数なし
  cout << num + 5 << endl;
}
入力
10
実行結果
15

関数を自分で定義する理由

関数を自分で定義する理由はいくつかありますが、その中から3つを紹介します。

  1. プログラムの重複が避けられる
  2. 処理のかたまりに名前をつけられる
  3. 再帰関数が書ける

大規模なプログラムを書く際は1.と2.による恩恵が大きいです。
APG4bでは主に3.のために関数の紹介をしています。詳しくは「2.04.再帰」で説明します。


注意点

return文の動作

処理がreturn文の行に到達した時点で関数の処理は終了します。

一つの関数の中にreturn文が複数あっても問題ありませんが、return文より後に書いた処理は実行されないことに注意しましょう。

次のプログラムでは、"Hello, "を出力した次の行でreturn;と書いているため、その後の処理は実行されません。

#include <bits/stdc++.h>
using namespace std;

void hello() {
  cout << "Hello, ";
  return; // この行までしか実行されない

  cout << "world!" << endl;
  return;
}

int main() {
  hello();
}
実行結果
Hello, 

返り値の指定忘れ

返り値がある関数でreturn文を忘れると、どんな値が返ってくるかは決まっていません。

次のプログラムのmy_min関数は、if文の中が実行されない場合に返り値を指定していないので、0が返ってきています。

#include <bits/stdc++.h>
using namespace std;

int my_min(int x, int y) {

  if (x < y) {
    return x;
  }

  // x >= y のときのreturn忘れ
}

int main() {
  int answer = my_min(10, 5);
  cout << answer << endl;
}
実行結果
0

このプログラムでは0が返ってきますが、0でない値が返ってくることもあります。

返り値がある関数では、どのような引数のパターンでも必ずreturnするように注意しましょう。

引数はコピーされる

他の変数に値を代入したとき同様に、基本的に引数に渡した値はコピーされます。

次のプログラムのadd5関数は関数内で引数に5を足していますが、呼び出し元の変数numの値は変化しません。

#include <bits/stdc++.h>
using namespace std;

// 引数の値に5を足して出力する関数
void add5(int x) {
  x += 5;
  cout << x << endl;
  return;
}

int main() {
  int num = 10;
  add5(num); // numの値は引数xにコピーされる

  cout << num << endl; // numは10のまま
}

実行結果
15
10

関数add5を呼び出す処理は次のプログラムと同じだと考えれば良いです。よくわからない人は1.04.変数と型の「変数はコピーされる」を読んでください。

int x = num; // xにnumの値10をコピー
x += 5; // xとnumは別の変数なのでnumの値は変わらない
cout << x << endl;

なお、vectorstringを引数にした場合も同様にコピーされます。

関数が呼び出せる範囲

関数は宣言した行より後でしか呼び出せません。

次のプログラムでは、hello関数を定義した行より前でhello関数を呼び出しているため、コンパイルエラーが発生しています。

#include <bits/stdc++.h>
using namespace std;

int main() {
  hello();
}

void hello() {
  cout << "hello!" << endl;
  return;
}
コンパイルエラー
./Main.cpp: In function ‘int main()’:
./Main.cpp:5:9: error: ‘hello’ was not declared in this scope
   hello();
         ^

「5:11: error: ‘hello’ was not declared in this scope(5行11文字目 エラー: 'hello'はこのスコープでは宣言されていません)」というエラー文が表示されています。
このエラーが発生した場合、コンパイルエラーが起きない順番に関数の定義を並び替えるか、後述する「プロトタイプ宣言」を利用しましょう。


細かい話

細かい話なので、飛ばして問題を解いても良いです。

プロトタイプ宣言

プロトタイプ宣言をすれば関数を定義する前に呼び出すことができます。
プロトタイプ宣言とは、「関数の名前」と「引数と返り値の型」だけを先に宣言することです。

#include <bits/stdc++.h>
using namespace std;

// プロトタイプ宣言
void hello();

int main() {
  // プロトタイプ宣言をしたので呼び出せる
  hello();
}

void hello() {
  cout << "hello!" << endl;
  return;
}
実行結果
hello!

プロトタイプ宣言の記法は次のようになります。

返り値の型 関数名(引数1の型 引数1の名前, 引数2の型 引数2の名前, ...);

引数名の省略

引数の名前は省略することもできます。
例えば、my_min関数のプロトタイプ宣言は次のように書くこともできます。

int my_min(int, int);

returnの省略

返り値がない場合、関数の末尾ではreturnを省略できます。

void hello(string text) {
  cout << "Hello, " << text << "!" << endl;
}

main関数

はじめのプログラムから出てきていたmain関数も関数の一つです。
ただし、main関数は特別扱いされていて、returnを省略した場合は必ず0が返るようになっています。

#include <bits/stdc++.h>
using namespace std;

int main() {
  cout << "Hello, world!" << endl;
  // return 0; と書くのと同じ
}
実行結果
Hello, world!

関数のオーバーロード

「引数の型」または「引数の数」が異なる場合は、同じ名前の関数を定義することができます。これを関数のオーバーロードと言います。
次のプログラムでは「2つの引数を取るmy_min関数」と「3つの引数を取るmy_min関数」を定義しています。

#include <bits/stdc++.h>
using namespace std;

// 2つの引数のうち最も小さい値を返す
int my_min(int x, int y) {
  if (x < y) {
    return x;
  }
  else {
    return y;
  }
}

// 3つの引数のうち最も小さい値を返す
int my_min(int x, int y, int z) {
  if (x < y && x < z) {
    return x;
  }
  else if (y < x && y < z) {
    return y;
  }
  else {
    return z;
  }
}

int main() {
  int answer = my_min(10, 5); // 2つの引数
  cout << answer << endl; // 5

  answer = my_min(3, 2, 5); // 3つの引数
  cout << answer << endl; // 2
}

ただし、返り値の型が異なるだけではオーバーロードできないことに注意してください。

複雑な引数の条件の指定

STLのmin関数は、引数としてint型でもdouble型でも指定できます。
これと似た挙動は関数のオーバーロードでも再現できますが、STLのmin関数は関数のオーバーロードではなく後の章で説明するテンプレートという機能を使って実装されています。
テンプレートを用いると、引数の条件として「大小比較ができる型なら何でも良い」などの複雑なものを指定できます。

変数のスコープ

関数内の変数のスコープは、呼び出し元とは完全に分けられています。

次のプログラムでは、test関数とmain関数の両方でnumという名前の変数を定義していますが、その2つは完全に別の変数として扱われます。

#include <bits/stdc++.h>
using namespace std;

void test() {

  // test関数のスコープで変数numを宣言
  int num = 5;
  cout << num << endl; // 5

  return;
}

int main() {
  // main関数のスコープで変数numを宣言
  int num = 10; 
  test();
  cout << num << endl; // 10
}

実行結果

5
10

2章の内容の紹介

2章で扱う内容で、関数と関わりが深いものについて2つだけ簡単に紹介します。

飛ばして問題を解いても良いです。読む場合も、今はしっかりと理解する必要はありません。

参照

引数の値をコピーさせないようにすることもできます。
その場合は参照という機能を引数で利用します。これについては2.04.参照で詳しく説明します。

#include <bits/stdc++.h>
using namespace std;

// 型に&をつけると参照になる
void change(int &n, vector<int> &vec) {
  n = 1;
  vec.at(0) = 1;
  return;
}

int main() {
  int x = 5;
  vector<int> v = {10, 10, 10};

  change(x, v);

  cout << x << endl;
  cout << v.at(0) << endl;
}
実行結果
1
1

再帰関数

関数の中で同じ関数を呼び出すこともできます。
このような関数を再帰関数と言います。これについては2.05.再帰で詳しく説明します。

#include <bits/stdc++.h>
using namespace std;

// 0からnまでの和を求める関数sum
int sum(int n) {
  if (n == 0) {
    return 0;
  }
  return n + sum(n - 1);
}

int main() {
  cout << sum(3) << endl;
}
実行結果
6

問題

リンク先の問題を解いてください。