関数
概要
関数とは固有の処理を実行するための命令群のことです。
関数にある値を与えると固有の処理を実行し、その結果を返してくれます。
関数に与える値を引数、処理の結果として返ってくる値を戻り値といいます。
関数を定義する
関数の機能を実装することを関数を定義するといいます。
まず関数の基本的な形について説明したいと思います。
書式
書式例
戻り値の型 関数名(引数)
{
return 戻り値; // 戻り値の型が設定されていれば必要
}
具体例
int Add(int a, int b)
{
return (a + b);
}
上の具体例の関数は2つの引数の値の合計を返す関数を定義しており、
この関数を各パーツ毎に分解すると以下のようになります。
戻り値の型:int
関数名:Add
引数:int a, int b
戻り値:(a + b)
戻り値なし、引数なし
関数の定義は戻り値や引数がなくても行うことが可能です。
printfも戻り値がない関数になります。
戻り値、引数がない場合はvoidを使用します。
voidというのは「空の」という意味で「何もない」型として使用されています。
具体例
// 名前を表示する関数
void PrintPlayerName(void)
{
printf("Nakachi");
}
voidは戻り値がないので、return文を書く必要はありません。
また、引数に記述しているvoidは省略可能です。
省略した場合は「void PrintPlayerName();」となります。
戻り値の型は省略できないので、気をつけてください。
関数の呼び出し
定義した関数はソースコード内で使用することで価値を発揮します。
関数を使用することを「関数呼び出し」と呼びます。
書式
書式例
関数名(値);
具体例
#include <stdio.h>
int Add(int a, int b)
{
int ret = a + b;
return ret;
}
int main(void)
{
int sum = 0;
sum = Add(2, 3);
printf("戻り値は%d\n", sum);
return 0;
}
実行結果:
戻り値は5
注意点
関数を呼び出し時の「関数名と()内の値の型と値の数」は呼び出し側の関数と
一致している必要があります。
例
関数仕様
関数名:
Add
戻り値の型:
int
引数:
int val1
int val2
内容:
val1とval2の足し算の結果を返す
具体例
// 定義
int Add(int val1, int val2)
{
return (val1 + val2);
}
int main(void)
{
int x = 1;
int y = 10;
int z = 100;
// 問題なし
int sum01 = Add(x, y);
// 問題あり
int sum02 = Add(x, y, z);
return 0;
}
問題点
Add関数の引数は以下の通りです。
int val1
int val2
引数の数 => 2
第1引数の型 => int
第2引数の型 => int
具体例のコードでは2回Addを呼び出しています。
1度目:
指定している値の数 => 2
1つ目の値の型 => int
2つ目の値の型 => int
2度目:
指定している値の数 => 3
1つ目の値の型 => int
2つ目の値の型 => int
3つ目の値の型 => int
関数の引数と呼び出し側の値をすり合わせてみます。
1度目と引数の数:
引数の数 => 2
第1引数の型 => int
第2引数の型 => int
指定している値の数 => 2
1つ目の値の型 => int
2つ目の値の型 => int
2度目と引数の数
引数の数 => 2
第1引数の型 => int
第2引数の型 => int
指定している値の数 => 3
1つ目の値の型 => int
2つ目の値の型 => int
3つ目の値の型 => int
1度目の呼び出しは引数の数としている値の数が一致しており、
2度目の呼び出しは引数の数が1つ多く関数と一致していないので
エラーとなります。
関数呼び出しは引数と呼び出し側の情報を必ずあわせてください。
プロトタイプ宣言
プロトタイプ宣言は関数の戻り値の型、関数名、引数を定義の前に宣言することです。
なぜ宣言をする必要があるかというと宣言を行っていない場合、
関数を定義した場所と呼び出した場所の関係次第でエラーが発生することがあります。
エラーが発生する理由
ソースコードをオブジェクトファイルに変換する時は
コンパイラはソースの上から下へ変換していきます。
その変換途中にユーザー関数の呼び出し命令文があった場合
その関数がどういった内容(戻り値の型、関数名、引数)か分かっていないと
翻訳不能な文字列として扱われエラーになります。
エラーが出るケース
#include <stdio.h>
int main(void)
{
int num = Add(10, 20);
printf("戻り値は%d\n", num);
return 0;
}
int Add(int a, int b)
{
int ret = a + b;
return ret;
}
エラーが出ないケース
#include <stdio.h>
int Add(int a, int b)
{
int ret = a + b;
return ret;
}
int main(void)
{
int num = Add(10, 20);
printf("戻り値は%d\n", num);
return 0;
}
上の具体例の違いはユーザー関数(Add)の呼び出し箇所が
定義の前か後ろかという所だけです。
ソースコードを上から変換していく中でコンパイラは
main関数内でAddという関数があることを認識します。
この時にAddの定義または宣言を確認しているかどうかを判断します。
エラーが出るソースはAddの後ろに定義がありますので、
Addの呼び出しを確認した時はこの関数を認識していません。
逆にエラーが出ないソースはAddの前に定義がありますので、
呼び出しの際はこの関数を認識できています。
この違いがエラーという形になって現れています。
全ての関数を使用箇所の前に定義することで問題は解決しますが、
それを実行するのは現実的ではありません。
なので、先に関数の基本情報を宣言してコンパイラに関数を
認識させてエラーを回避します。
プロトタイプ宣言の書式
プロトタイプ宣言には戻り値の型、関数名、引数情報が必要です。
書式例
戻り値の型 関数名(引数情報);
具体例
int Add(int, int);
※プロトタイプ宣言の引数情報は型情報だけで問題ありません。
プロトタイプ宣言の注意点
プロトタイプ宣言の内容と定義する関数の内容は
完全に一致させる必要があります。
例えばプロトタイプ宣言でint Add(int, int)と宣言した場合は
定義でもint Add(int a, int b)とコーディングする必要があります。
戻り値の型、関数名、引数情報のひとつでも違いがあった場合は
別の関数として認識されますので、エラーの原因となります。
標準ライブラリ関数とユーザー関数
関数の種類には標準ライブラリ関数とユーザー関数があります。
標準ライブラリ関数は各言語が開発サポートのために用意している関数です。
printfはC言語が提供している標準ライブラリ関数となります。
ユーザー関数はプログラマーが独自で作成した関数で、
戻り値の型や処理内容は全て自分で決めれます。
ユーザー関数はユーザー定義関数とも呼ばれています。