自作関数の定義

関数は、以前に紹介した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関数を抜けるとプログラムが終了します。

8行目、「int main()」と書かれていますが、ここでmain関数を定義しています。
いままでスルーしてきましたが、今までのコードでもmain関数を自分で定義していたことになります。

逆に言えば、C言語では必ず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」とだけ記述します。
(どちらかと言えばこちらが推奨されるようです)

仮引数と実引数

関数の定義時に指定する引数を仮引数といいます。
その関数を使用する際に指定する引数を実引数といいます。

//num1とnum2が仮引数
int Add(int num1, int num2)
{
    return num1 + num2;
}

int main()
{
    //1と2が実引数
    int ret = Add(1, 2);
}

このコードの2行目の「num1」「num2」が仮引数、10行目の「1」「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文が実行されるため「かきくけこ」は表示されません。

関数を使用するメリット

サンプルコードのようなごく単純な関数は、わざわざ関数化して使用する意味はほとんどありません。
足し算程度ならばそこに直接書いた方が速いです。

しかし、ある程度コードの記述量が多くなってくると、似たような処理を何度も行うことが多くなってきます。
その都度似たようなコードを記述するのは効率が悪いですし、コードが肥大化し見た目にもわかりにくくなりますし、コンパイル後の実行ファイルのファイルサイズも大きくなります。
そこで、同じような処理があれば関数化してしまって、場合によっては引数によって具体的な処理内容を変更するようにします。
その関数を必要に応じて呼び出すようにすればプログラミングが効率的に行えます。

仮に関数内にバグがあっても、その関数ひとつを修正すれば済みます。
関数化していない場合は、同じような処理を書いた箇所すべてを修正する必要があり、バグの修正漏れや、記述ミスによる新たなバグの発生などを引き起こす可能性もあります。

関数のプロトタイプ宣言

最初のサンプルコードでは、まず最初に自作の関数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;
}

3行目に、「int add(int, int);」という一文が追加されています。
これが関数のプロトタイプ宣言です。

プロトタイプ宣言では関数の戻り値の型、関数名、引数(の数と型)、の3つの情報だけが記述されています。
これをコードの最初に書くことで、この行以降では「このプログラムには、int型の戻り値を持ち、int型の引数を二つ取る、Addという名前の関数が定義されている」ということを知ることができるようになります。

実際の自作関数Add~20行目で定義されています。
戻り値の型、関数名、引数の数と型はプロトタイプ宣言と同じにします。

main関数内で自作関数Addを使用する時点(10行目)では、まだ実際に関数Addは定義されていませんが、コードの最初にプロトタイプ宣言があるので、main関数では関数Addを使用できるようになります。