6日でできる 新C言語入門|プロトタイプ宣言で関数を整理する

関数の中身はあとで書いても、先に使い方を知らせておけば大丈夫。
プロトタイプ宣言を理解して、関数をすっきり整理できるC言語プログラムを書こう。

C言語で関数を使えるようになると、プログラムを小さな部品に分けて書けるようになります。計算をする関数、表示をする関数、値を判定する関数のように、処理ごとに関数を分けると、main関数の中が読みやすくなります。

ただし、関数が増えてくると、次のような悩みが出てきます。

どの順番で関数を書けばよいのか。
main関数より前に、すべての関数本体を書かなければならないのか。
関数同士が呼び出し合う場合、どちらを先に書けばよいのか。

C言語では、基本的にソースコードを上から順番に読み取ります。そのため、まだ情報を知らない関数を先に呼び出そうとすると、コンパイラはその関数の戻り値の型や引数の型を判断できません。

この問題を解決するために使うのが、プロトタイプ宣言です。

プロトタイプ宣言は、関数の本体を書く前に、その関数の基本情報をコンパイラへ知らせるための宣言です。具体的には、戻り値の型、関数名、引数の型を先に書いておきます。

プロトタイプ宣言を使うと、関数本体をmain関数のあとに書いても、main関数の中からその関数を呼び出せます。これにより、プログラム全体の構成を整理しやすくなります。

この記事では、プロトタイプ宣言の基本的な書き方、なぜ必要になるのか、関数定義との違い、複数の関数を扱う場合の整理方法まで、サンプルプログラムを使いながらやさしく解説していきます。

プロトタイプ宣言は関数の予告を書くためのもの

プロトタイプ宣言は、関数の中身を書く前に、関数の形だけを先に伝えるための宣言です。

たとえば、次のような関数があるとします。

int add(int a, int b) {
    return a + b;
}

この関数は、int型の値を2つ受け取り、int型の結果を返す関数です。

この関数のプロトタイプ宣言は、次のように書けます。

int add(int, int);

関数本体と違い、プロトタイプ宣言には処理内容を書きません。最後にはセミコロンを付けます。

項目内容
戻り値の型関数が返す値の型
関数名呼び出すときに使う名前
引数の型関数に渡す値の型
セミコロンプロトタイプ宣言の終わりを表す

プロトタイプ宣言は、コンパイラに対して、このような関数があとで出てきますと知らせる役割を持ちます。

プロトタイプ宣言の基本形

プロトタイプ宣言の基本形は、次のようになります。

戻り値の型 関数名(引数の型);

引数が複数ある場合は、カンマで区切って書きます。

int add(int, int);
double calcAverage(int, int);
void printMessage(void);

それぞれの意味を整理すると、次のようになります。

プロトタイプ宣言意味
int add(int, int);int型の値を2つ受け取り、int型を返す
double calcAverage(int, int);int型の値を2つ受け取り、double型を返す
void printMessage(void);引数なし、戻り値なしの関数

引数がない関数では、引数の部分にvoidを書くと、値を受け取らない関数であることが明確になります。

また、プロトタイプ宣言では、引数名を省略できます。

int add(int, int);

次のように引数名を書いても構いません。

int add(int a, int b);

どちらも、関数の型情報をコンパイラへ知らせるという意味では同じです。学習段階では、引数名を書いたほうが意味を理解しやすい場合もあります。

図:プロトタイプ宣言は関数の使い方を先に知らせる

この図から分かること

この図から分かるのは、プロトタイプ宣言が関数本体そのものではなく、関数の使い方を先に知らせるためのものだということです。

コンパイラは、関数の戻り値の型、関数名、引数の型を先に知っていれば、関数本体が後ろに書かれていても呼び出しを理解できます。そのため、main関数を先に書き、そのあとに自作関数をまとめて書くような構成が可能になります。

関数定義とプロトタイプ宣言の違い

プロトタイプ宣言と関数定義は、見た目が似ていますが役割が違います。

プロトタイプ宣言は、関数の情報を先に伝えるためのものです。処理内容は書きません。

int calcPrice(int, int);

一方、関数定義は、実際に関数が何をするのかを書く部分です。

int calcPrice(int price, int rate) {
    return price + price * rate / 100;
}

違いを表にすると、次のようになります。

種類役割処理内容最後の書き方
プロトタイプ宣言関数の情報を先に知らせる書かないセミコロンを付ける
関数定義関数の実際の処理を書く書く波かっこで本体を書く

プロトタイプ宣言では、最後にセミコロンが必要です。
関数定義では、処理内容を波かっこで囲みます。

ここを混同しないようにしましょう。

プロトタイプ宣言を使ったプログラム

次のプログラムでは、割引後の価格を計算する関数をmain関数のあとに定義しています。

main関数より前には、関数本体ではなくプロトタイプ宣言だけを書いています。

プロジェクト/ファイル名: Lesson38_1/main.c

#include <stdio.h>

// calcDiscountPrice関数のプロトタイプ宣言
int calcDiscountPrice(int price, int discountRate);

int main(void) {
    int price = 3000;
    int discountRate = 20;
    int result;

    // プロトタイプ宣言があるため、関数本体が後ろにあっても呼び出せる
    result = calcDiscountPrice(price, discountRate);

    printf("割引後の価格は %d 円です。\n", result);

    return 0;
}

// 商品価格と割引率から、割引後の価格を返す関数
int calcDiscountPrice(int price, int discountRate) {
    int discount;
    int result;

    discount = price * discountRate / 100;
    result = price - discount;

    return result;
}

実行結果

割引後の価格は 2400 円です。

このプログラムでは、最初に次のプロトタイプ宣言を書いています。

int calcDiscountPrice(int price, int discountRate);

この宣言によって、コンパイラはcalcDiscountPrice関数について、次の情報を先に知ることができます。

情報内容
戻り値の型int
関数名calcDiscountPrice
引数int型を2つ受け取る

そのため、main関数の中で次のように呼び出しても問題ありません。

result = calcDiscountPrice(price, discountRate);

関数本体はmain関数のあとにありますが、プロトタイプ宣言によって先に関数の情報が伝わっているため、プログラムを整理した形で書けます。

なぜプロトタイプ宣言が必要になるのか

C言語では、基本的にソースコードを上から順番に読み取ります。

そのため、関数を呼び出す時点で、その関数の戻り値の型や引数の型が分かっていないと、コンパイラは正しく判断できません。

たとえば、main関数の中でcalcTotal関数を呼び出しているのに、calcTotal関数の情報がまだどこにも書かれていない場合、コンパイラは次のようなことが分かりません。

コンパイラが知りたいこと
関数は本当に存在するのかcalcTotalという関数があるか
戻り値の型は何かintを返すのか、doubleを返すのか
引数はいくつ必要か1つなのか、2つなのか
引数の型は何かintなのか、doubleなのか

この情報がない状態で関数を呼び出すと、コンパイル時にエラーや警告の原因になります。

プロトタイプ宣言は、この問題を防ぐために使います。関数本体より前に、関数の情報だけを先に書いておくことで、コンパイラは安心して関数呼び出しを解釈できます。

プロトタイプ宣言がない場合に起こりやすい問題

次のような構成では、main関数の中で関数を呼び出している時点で、calcTotal関数の情報がまだ出てきていません。

#include <stdio.h>

int main(void) {
    int total;

    total = calcTotal(120, 3);

    printf("合計金額は %d 円です。\n", total);

    return 0;
}

int calcTotal(int price, int count) {
    return price * count;
}

このような書き方では、環境や設定によって、関数が未宣言であることに関するエラーや警告が出る場合があります。

特に学習段階では、関数を呼び出す前に、関数定義を書くか、プロトタイプ宣言を書くと覚えておくと安全です。

ただし、開発環境やC言語の規格、コンパイラ設定によって、表示されるメッセージや扱いが異なる場合があります。大切なのは、関数を呼び出す前に、コンパイラへ関数の情報を知らせておくという考え方です。

図:プロトタイプ宣言がある場合とない場合の違い

この図から分かること

この図から分かるのは、プロトタイプ宣言があるかどうかで、コンパイラが関数呼び出しを理解できるかが変わるということです。

プロトタイプ宣言がない場合、main関数の中で呼び出した関数の情報がまだ分からないため、エラーや警告につながることがあります。プロトタイプ宣言がある場合は、関数本体が後ろに書かれていても、戻り値の型や引数の型が先に分かるため、安心して呼び出せます。

プロトタイプ宣言で関数定義を後ろに置く

次のプログラムでは、商品の単価と個数から合計金額を計算する関数を使います。関数本体はmain関数のあとに書いていますが、先頭にプロトタイプ宣言を置いているため、main関数の中から呼び出せます。

プロジェクト/ファイル名: Lesson38_2/main.c

#include <stdio.h>

// calcTotal関数のプロトタイプ宣言
int calcTotal(int price, int count);

int main(void) {
    int price = 450;
    int count = 6;
    int total;

    // 単価と個数を渡して合計金額を計算する
    total = calcTotal(price, count);

    printf("合計金額は %d 円です。\n", total);

    return 0;
}

// 単価と個数から合計金額を返す関数
int calcTotal(int price, int count) {
    return price * count;
}

実行結果

合計金額は 2700 円です。

このプログラムでは、次のプロトタイプ宣言があります。

int calcTotal(int price, int count);

この宣言によって、main関数より前にcalcTotal関数の情報が伝わります。

そのため、関数本体がmain関数のあとにあっても、main関数では問題なく関数を呼び出せます。

total = calcTotal(price, count);

プロトタイプ宣言を使うことで、main関数をプログラムの上のほうに置き、細かい関数定義を下のほうにまとめる構成ができます。これにより、プログラム全体の流れを先に見せ、そのあとで各処理の中身を確認できるようになります。

プロトタイプ宣言に引数名を書くか省略するか

プロトタイプ宣言では、引数名は省略できます。

int calcTotal(int, int);

このように型だけを書いても、コンパイラには必要な情報が伝わります。

一方で、次のように引数名を書いても問題ありません。

int calcTotal(int price, int count);

どちらも同じ関数のプロトタイプ宣言として扱えます。

書き方特徴
int calcTotal(int, int);型だけを書くため短い
int calcTotal(int price, int count);引数の意味が分かりやすい

学習段階では、引数名を書いておくと、関数がどのような値を受け取るのか理解しやすくなります。実務では、読みやすさを重視して引数名を書くこともよくあります。

ただし、プロトタイプ宣言に書いた引数名と、関数定義で使う引数名は必ず同じでなければならないわけではありません。大切なのは、型と順番が一致していることです。

プロトタイプ宣言と関数定義は型を合わせる

プロトタイプ宣言と関数定義では、戻り値の型と引数の型を合わせる必要があります。

たとえば、次のプロトタイプ宣言を書いた場合を考えます。

int calcTotal(int price, int count);

この場合、関数定義もint型を2つ受け取り、int型を返す形にします。

int calcTotal(int price, int count) {
    return price * count;
}

もしプロトタイプ宣言と関数定義で型が違っていると、コンパイラが矛盾を検出します。

確認する部分合わせる内容
戻り値の型int、double、voidなど
関数名同じ名前にする
引数の数同じ数にする
引数の型同じ順番で指定する

プロトタイプ宣言は関数の仕様を先に知らせるものなので、あとから書く関数定義と内容が一致していることが大切です。

複数の関数を使うときのプロトタイプ宣言

プログラムが少し大きくなると、関数が1つだけではなく、複数登場します。

たとえば、次のように処理を分けることができます。

関数役割
showLine区切り線を表示する
showTitleタイトルを表示する
showMenuメニューを表示する

このように複数の関数を使う場合、プログラムの先頭にプロトタイプ宣言をまとめて書いておくと整理しやすくなります。

void showLine(void);
void showTitle(void);
void showMenu(void);

こうしておけば、関数の定義順序にあまり悩まずに、main関数や各関数の中から呼び出せます。

複数の関数をプロトタイプ宣言で整理する

次のプログラムでは、画面に簡単なメニューを表示します。複数の表示用関数を使い、それぞれのプロトタイプ宣言を先に書いています。

プロジェクト/ファイル名: Lesson38_3/main.c

#include <stdio.h>

// 複数の関数のプロトタイプ宣言
void showLine(void);
void showTitle(void);
void showMenu(void);

int main(void) {
    // メニュー画面を表示する
    showTitle();
    showMenu();
    showLine();

    return 0;
}

// 区切り線を表示する関数
void showLine(void) {
    printf("--------------------\n");
}

// タイトルを表示する関数
void showTitle(void) {
    showLine();
    printf("学習メニュー\n");
    showLine();
}

// メニュー項目を表示する関数
void showMenu(void) {
    printf("1. 関数の基本\n");
    printf("2. 引数と戻り値\n");
    printf("3. プロトタイプ宣言\n");
}

実行結果

--------------------
学習メニュー
--------------------
1. 関数の基本
2. 引数と戻り値
3. プロトタイプ宣言
--------------------

このプログラムでは、showLine、showTitle、showMenuという3つの関数を使っています。

先頭には、次のようにプロトタイプ宣言をまとめて書いています。

void showLine(void);
void showTitle(void);
void showMenu(void);

showTitle関数の中では、showLine関数を呼び出しています。

void showTitle(void) {
    showLine();
    printf("学習メニュー\n");
    showLine();
}

showLine関数の本体はshowTitle関数より前に書かれていますが、もし順番を入れ替えたとしても、プロトタイプ宣言があれば関数の情報は先に伝わっています。

このように、複数の関数を使うプログラムでは、最初にプロトタイプ宣言を並べておくと、関数の構成が見やすくなります。

図:複数の関数はプロトタイプ宣言で一覧化できる

この図から分かること

この図から分かるのは、複数の関数を使う場合、プロトタイプ宣言を先頭にまとめておくと、どのような関数があるのかを一覧で確認できるということです。

main関数では全体の流れを読み、各関数の詳しい処理は後ろで確認する構成にできます。関数が増えても、プロトタイプ宣言を整理しておくことで、プログラム全体の見通しがよくなります。

プロトタイプ宣言を書く場所

プロトタイプ宣言は、通常、ソースファイルの先頭付近に書きます。

一般的には、#includeのあと、main関数より前に書くことが多いです。

#include <stdio.h>

int calcTotal(int price, int count);
void showMenu(void);

int main(void) {
    return 0;
}

この位置に書くと、main関数からも、あとに続く関数からも、その関数の情報を利用しやすくなります。

小さなプログラムでは、main関数より前に関数本体をすべて書く方法でも動きます。しかし、関数が増えてくると、main関数が下のほうに追いやられてしまい、プログラム全体の流れを確認しにくくなります。

プロトタイプ宣言を使えば、main関数を先に書き、そのあとに関数定義を並べる構成にできます。

プロトタイプ宣言を使うとmain関数が読みやすくなる

main関数は、プログラム全体の流れを表す場所として使うと読みやすくなります。

たとえば、次のような形です。

int main(void) {
    showTitle();
    showMenu();
    showLine();

    return 0;
}

このように書くと、プログラムが何をしているのかを上から順に読み取りやすくなります。

細かい表示処理や計算処理は、別の関数に分けて後ろに書いておけば、main関数は全体の流れを示す役割に集中できます。

プロトタイプ宣言は、このような読みやすい構成を作るためにも役立ちます。

プロトタイプ宣言で注意したいこと

プロトタイプ宣言を書くときは、いくつか注意点があります。

最後にセミコロンを付ける

プロトタイプ宣言は関数本体ではないため、最後にセミコロンを付けます。

int calcTotal(int price, int count);

関数定義と型を合わせる

プロトタイプ宣言と関数定義では、戻り値の型、関数名、引数の型を一致させます。

int calcTotal(int price, int count) {
    return price * count;
}

引数名は省略できる

プロトタイプ宣言では、引数の型だけを書いても構いません。

int calcTotal(int, int);

ただし、学習段階では引数名を書いたほうが意味を理解しやすいです。

戻り値がない関数はvoidを使う

値を返さない関数では、戻り値の型にvoidを書きます。

void showMenu(void);

引数がない関数もvoidで表せる

引数を受け取らない関数では、引数部分にvoidを書くと明確です。

void showLine(void);

プロトタイプ宣言を使うと関数の整理がしやすくなる

プロトタイプ宣言は、関数の戻り値の型、関数名、引数の型を先にコンパイラへ知らせるための宣言です。

C言語では、関数を呼び出す前に、その関数の情報が分かっている必要があります。関数本体をmain関数より前に書く方法もありますが、関数が増えるとプログラムの見通しが悪くなることがあります。

プロトタイプ宣言を使えば、main関数を先に書き、そのあとに自作関数をまとめて定義できます。これにより、プログラム全体の流れを先に確認し、細かい処理を後ろで読む構成にできます。

また、複数の関数を使う場合は、ソースファイルの先頭付近にプロトタイプ宣言をまとめて書いておくと、どのような関数があるのか分かりやすくなります。

プロトタイプ宣言を使いこなせるようになると、関数の定義順序に悩みにくくなり、読みやすく整理されたC言語プログラムを書きやすくなります。