自作関数の定義
関数は、以前に紹介したprintf
関数のように、特定の機能を提供する命令のことです。
関数は最初からC言語に用意されているものを使用するほか、自分で作ることもできます。
以下のサンプルコードは、整数(int型)の引数を二つ受け取り、足し算をした後にその計算結果を返す関数を定義しています。
3~8行目の「int Add」から始まるブロックがそれです。
#include <stdio.h>
int Add(int num1, int num2)
{
int num;
num = num1 + num2;
return num;
}
int main()
{
int kazu1, kazu2, kekka;
kazu1 = 3;
kazu2 = 4;
kekka = Add(kazu1, kazu2);
printf("計算結果: %d", kekka);
getchar();
}
計算結果: 7
少しばかりコードが複雑になりましたね。
今までは上から順番にコードが実行されていましたが、C言語ではmain関数が一番最初に実行されるという決まりがあります。
そして、main関数を抜けるとプログラムが終了します。
10行目、int main()
と書かれていますが、ここでmain関数を定義しています。
いままでスルーしてきましたが、今までのコードでもmain関数を自分で定義していたことになります。
逆に言えば、C言語では必ずmain関数を定義しなければなりません。
正確に言えば、コンパイラの初期設定が「main関数からプログラムを開始する」というものになっています。
関数の作り方
関数は、以下のような形式で定義します。
戻り値の型 関数名(データ型 引数名, データ型, 引数名...)
{
//関数の具体的な処理
}
int main()
{
}
関数はmain関数の隣に定義します。
main関数の後ろに書くこともできますが、とりあえずmain関数の手前に記述してください。
(理由は後述)
それぞれの言葉の意味を説明します。
戻り値の型
関数は何らかの命令(処理)をひとまとめにしたものです。
そしてその命令を実行した結果を受け取ることができます。
この値を戻り値といいます。
サンプルコードの関数Add
は、戻り値の型にint型を指定しているので、処理の結果を整数値として受け取ることができます。
もし小数値を受け取りたい場合は「double」や「float」などを指定します。
関数によっては、処理の結果を受け取る必要がない場合もあります。
その場合は戻り値のデータ型にはvoid型を指定します。
void型は「空」の意味で、何もないことを示すデータ型です。
//小数値を戻り値に指定する場合
double Add(int num1, int num2)
{
}
//戻り値なしの場合
void Add(int num1, int num2)
{
}
関数名
関数名は、変数名と同じく半角英数字で、基本的に自由につけることができます。
(アンダースコア_
も使えます)
ただしこれも変数名と同じく、関数名の最初に数字を使用できないことと、特定の単語は使用できない、という決まりがあります。
引数の型と引数名の指定
関数名に続いて、丸括弧を書きます。
この括弧の中に引数を指定します。
ここで指定する引数を仮引数といいます。
引数は変数と同じくデータ型がありますから、変数の宣言と同じように引数名の前にデータ型を指定します。
引数が複数ある場合はコンマ(,)で区切ります。
引数は、関数内で必要になる値を外部から受け取るためのものです。
関数の呼び出し側では、関数に渡したい値を引数に指定して関数を実行します。
サンプルコードでは、整数の値を二つ受け取り、関数内での足し算に利用しています。
関数内では、引数は変数と同じように値を取り出して使用することができます。
引数のない関数
引数は必ず指定しなければならない、というものではありません。
外部からの値を必要としない処理なら引数は空でもかまいません。
void Message1()
{
printf("あいうえお");
}
void Message2(void)
{
printf("あいうえお");
}
このサンプルコードの関数は、実行すると画面に「あいうえお」と表示するだけの関数です。
表示したい文字は決まっているので、引数は空で問題ありません。
引数を取らないことを明確にしたい場合は、引数にvoid
とだけ記述します。
(どちらかと言えばこちらが推奨されるようです)
正確には、関数の仮引数を空にすると、引数を取らないのではなく引数の数を指定しないという意味になります。
言い換えればいくつでも引数を受け取ることができます。
これは古い規格との互換性のために残されています。
引数にvoid
を指定すると、引数を取らない関数であることを明示することができます。
仮引数と実引数
関数の定義時に指定する引数を仮引数といいます。
その関数を使用する際に、関数の呼び出し元で指定する引数を実引数といいます。
//num1とnum2が仮引数
int Add(int num1, int num2)
{
int num;
num = num1 + num2;
return num;
}
int main()
{
//1と2が実引数
int ret = Add(1, 2);
}
このコードの2行目のnum1
とnum2
が仮引数、12行目の1
と2
が実引数です。
この場合、関数Add
はnum1
に「1」が、num2
に「2」が格納された状態で実行されます。
関数の処理
戻り値のデータ型、関数名、引数を記述したら、波括弧{}
で関数の始めと終わりの範囲を指定します。
この括弧の中が関数の処理となります。
波括弧で処理をまとめること、またはまとめられた箇所をブロックといいます。
関数の処理は自由に書くことができます。
return文で戻り値を指定する
最後にreturn文を記述します。
return
というキーワードの後ろに指定された値が関数の呼び出し側に戻り値として返されます。
戻り値に指定する値のデータ型は、関数名の手前で指定したデータ型と同じものを指定します。
サンプルコードでは、二つの引数を足し算した結果をreturn文で戻り値に指定しています。
仮に、関数Add
のreturn文を以下のように書き換えると、引数にどのような数値を指定しても関数の実行の結果返ってくる数値は常に「10」になります。
int Add(int num1, int num2)
{
int num;
num = num1 + num2;
return 10;
}
return文には変数や数値の直接指定のほか、式(計算式)を指定することもできます。
サンプルコードの関数Add
は、以下のように短く書くこともできます。
int Add(int num1, int num2)
{
return num1 + num2;
}
戻り値がない場合
戻り値の型にvoid型を指定した場合は、return文は省略できます。
return文を書いてもいいですが、その場合は値を指定してはいけません。
//これはOK
void Message1()
{
printf("あいうえお");
return;
}
//return文に値を指定するとエラー
void Message2()
{
printf("あいうえお");
return 1;
}
戻り値がない関数は実行しても値を返さないので、関数の実行結果を変数に代入することはできません。
以下のような使い方はエラーとなります。
#include <stdio.h>
void Message()
{
printf("あいうえお");
return;
}
int main()
{
int kekka;
//関数Messageは戻り値がないのでエラー
kekka = Message();
}
ちなみに、戻り値のある関数であっても、必要がないなら戻り値を受け取らない(変数に代入したりしない)で使用しても構いません。
実はprintf
関数も戻り値がある関数ですが、必要がないので受け取っていません。
return文の実行は関数の終了
return文の行が実行されると、その行で関数の実行は終了します。
関数の途中でreturn文を書いば場合、その行が実行されるとそれ以降の行のコードは実行されなくなります。
void Message()
{
printf("あいうえお");
return;
printf("かきくけこ");
return;
}
この関数Messageを実行すると「あいうえお」は表示されますが、次の行でreturn文が実行されるため「かきくけこ」は表示されません。
main関数の戻り値
main関数も関数なので、return文でmain関数を終了させることができます。
main関数の戻り値はint型となっているので、return文に数値を指定して返すことになります。
int main()
{
return 0;
}
今までのサンプルコードにはmain関数にreturn文が書かれていません。
これは戻り値のある関数でもmain関数に限り、return文を省略することができるからです。
(return 0;
が指定されたものとみなされます)
ただしgccというコンパイラでは(標準では)main関数のreturn文の省略ができないため、最後にreturn 0;
を記述しなければなりません。
(古いコンパイラでも省略できないかもしれません)
なお、main関数のreturn文の数値には何を指定してもそのプログラムの動作には影響しません。
main関数の戻り値は、他のプログラムと連携する時に使用されます。
例えばプログラムAからプログラムBを起動して、プログラムBの戻り値(main関数の戻り値)によってプログラムAの動作を変える、などが可能です。
関数を使用するメリット
サンプルコードのようなごく単純な処理は、わざわざ関数化して使用する意味はほとんどありません。
足し算程度ならばそこに直接書いた方が速いです。
ある程度コードの記述量が多くなってくると、似たような処理を何度も行うことが多くなってきます。
その都度似たようなコードを記述するのは効率が悪いですし、コードが肥大化し読みづらくなりますし、コンパイル後の実行ファイルのファイルサイズも大きくなります。
そこで、共通の処理は関数化してしまうことでコードがすっきりします。
引数を変えることで関数の処理をある程度変更することができるので、効率的にプログラミングができます。
仮にバグのあるコードを書いてしまっても、関数化している場合はその関数ひとつを修正すれば済みます。
関数化していない場合は、同じような処理を書いた箇所すべてを修正する必要があり、バグの修正漏れや、記述ミスによる新たなバグの発生などを引き起こす可能性もあります。
また、まとまった処理に名前(関数名)を付けることができるのもメリットのひとつです。
関数名や変数名は、それがどういう処理にかかわる物なのかを示すので、適切な関数名を付けることで処理の意図がわかりやすくなり、コードが読みやすくなります。
関数のプロトタイプ宣言
最初のサンプルコードでは、まず最初に自作の関数Add
を定義し、その後にmain関数の定義を書いています。
これを逆にしてしまうとC言語ではエラーとなります。
以下はダメな例です。
#include <stdio.h>
int main()
{
int kazu1, kazu2, kekka;
kazu1 = 3;
kazu2 = 4;
kekka = Add(kazu1, kazu2);
printf("計算結果: %d", kekka);
getchar();
}
int Add(int num1, int num2)
{
int num;
num = num1 + num2;
return num;
}
このコードをコンパイルしようとすると、Visual Studioに以下のようなエラーが表示されます。
「識別子が見つからない」というのは、Addという名前で定義されているものをコンパイラが見つけられなかった、というエラーです。
main関数内では自作関数Addを使用していますが、自作関数Addはmain関数の後に記述されています。
C言語は、今現在の行よりも手間に宣言されているもの(変数や関数)しか使えないという制限があります。
つまり、main関数内で使用する関数はmain関数よりも前の行に記述しないと「まだ宣言されていない」とみなされてしまうのです。
この問題を解決する方法のひとつに「main関数を一番最後に定義する」というものがあります。
しかしこれは根本的な解決とはなりません。
自作関数はいつもmain関数から呼び出されるわけではなく、自作関数から別の自作関数を呼び出して使うことも普通にあります。
自作関数の定義や呼び出し順によっては、相変わらず「関数が見つからない」という事態が起こります。
正しい解決方法は、関数のプロトタイプ宣言をコードの最初で行うことです。
関数のプロトタイプ宣言とは、「このプログラムにはこういう関数が存在する」ということをコード全体に知らせる事ができるC言語の機能です。
具体的には以下のように記述します。
#include <stdio.h>
//関数のプロトタイプ宣言
int Add(int, int);
int main()
{
int kazu1, kazu2, kekka;
kazu1 = 3;
kazu2 = 4;
kekka = Add(kazu1, kazu2);
printf("計算結果: %d", kekka);
getchar();
}
int Add(int num1, int num2)
{
int num;
num = num1 + num2;
return num;
}
4行目にint Add(int, int);
という一文が追加されています。
これが関数のプロトタイプ宣言です。
関数のプロトタイプ宣言は、関数の戻り値の型、関数名、引数(の数と型)、の3つの情報だけを記述します。
これをコードの最初に書くことで、この行以降では「このプログラムには、int型の戻り値を持ち、int型の引数を二つ取る、Addという名前の関数が存在する」ということを知ることができるようになります。
実際の自作関数Add
は16~21行目で定義されています。
戻り値の型、関数名、引数の数と型はプロトタイプ宣言と同じにします。
main関数内で自作関数Add
を使用する時点(11行目)では、まだ実際に関数Add
は定義されていませんが、コードの最初にプロトタイプ宣言があるので、main関数では関数Add
を使用できるようになります。
宣言と定義
C言語では「宣言」と「定義」は区別されます。
「宣言」はコンパイラに「このプログラムには〇〇という名前のデータがある」という情報を伝えるためのものです。
宣言だけでは実際にデータがあることは保証されていません。
「定義」は、宣言に具体的な中身を与えるものです。
以下のコードは、関数Addの宣言と定義を同時に行っていることになります。
#include <stdio.h>
int Add(int num1, int num2)
{
//省略
}
int main()
{
//省略
}
以下のコードは関数のプロトタイプ宣言の後に関数の定義を行っていますが、先ほども示した通り関数の定義は宣言の役割もあります。
C言語では「宣言」は複数存在しても良いのでこのコードは問題ありませんが、「定義」はひとつしかできないので、同じ名前の関数を複数作成するとエラーになります。
#include <stdio.h>
int Add(int, int);
int main()
{
//省略
}
int Add(int num1, int num2)
{
//省略
}
//以下のコメントを解除するとエラーになる
/*
int Add()
{
//省略
}
*/
なおC++の場合、引数の数やデータ型が異なる場合は同じ名前の関数を複数定義することができます。
このページのまとめ
- プログラムはmain関数から始まり、main関数を抜けると終了する
- 関数はコードの先頭でプロトタイプ宣言をしておく