C言語のきほん|関数を組み合わせて使う

小さな関数をつなげるだけで、プログラムはぐっと分かりやすくなる

C言語で関数を学び始めると、最初は main 関数から1つの関数を呼び出す書き方を練習することが多いです。
でも、実際のプログラムでは、1つの関数の中からさらに別の関数を呼び出して処理を組み立てる場面がたくさんあります。

これはとても大切な考え方です。
なぜなら、1つの大きな処理を無理に1個の関数へ詰め込むよりも、役割ごとに小さく分けた関数を組み合わせたほうが、読みやすく、直しやすく、再利用しやすいプログラムになるからです。

たとえば、

  • 判定する役割の関数
  • 計算する役割の関数
  • 表示する役割の関数

のように分けておくと、どこで何をしているのかがはっきり見えてきます。

今回の「関数を組み合わせて使う」では、関数の中から関数を呼び出す流れに注目しながら、処理を部品のように組み立てる考え方をていねいに見ていきます。
C言語らしいプログラムの作り方に一歩近づく、とても大事な内容なので、流れをイメージしながらじっくり押さえていきましょう。

関数を組み合わせて使うとは

関数を組み合わせて使うとは、ある関数の中で別の関数を呼び出し、その結果を利用してさらに処理を進めることです。

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

  • main 関数が計算用の関数を呼び出す
  • その計算用の関数が、さらに判定用の関数を呼び出す
  • 判定用の関数が値を返す
  • その値を使って計算用の関数が結果を作る
  • 最後に main 関数へ結果が戻る

この形にすると、1つ1つの関数の役割がはっきりします。

たとえば、全部を1つの関数に書くと、

  • 判定
  • 計算
  • 表示

が混ざってしまい、少し長くなっただけでも読みにくくなります。
一方で関数を分けると、どの関数が何を担当しているのかが見えやすくなります。

なぜ関数を分けるのか

関数を組み合わせる最大のメリットは、役割を分担できることです。

分け方の考え方
判定する関数条件に応じて値を選ぶ
計算する関数判定結果を使って計算する
表示する部分main 関数で結果を見せる

このように役割を分けると、プログラムに次のようなよさが出てきます。

メリット内容
読みやすい1つの関数が短くなり、何をしているか分かりやすい
修正しやすい変更したい部分だけ直しやすい
再利用しやすい別のプログラムでも同じ関数を使いやすい
テストしやすい関数ごとに動作確認しやすい

特に学習の初期段階では、1つの関数に1つの役割を持たせることを意識すると、とても整理しやすくなります。

サンプルプログラム

テストの点数に応じて評価記号を決める関数と、その評価を使ってメッセージを作る関数を組み合わせるプログラム例です。

  • get_grade 関数
    点数から評価記号を返す
  • print_result_message 関数
    get_grade を呼び出して、結果のメッセージを表示する

こうすると、評価を決める役割表示する役割を分けて考えられます。

ファイル名:13_5_1.c

// 点数に応じて評価メッセージを表示するプログラム
#include <stdio.h>

char get_grade(int score);
void print_result_message(int score);

int main(void)
{
    int score = 78;   // テストの点数

    // 結果メッセージを表示する
    print_result_message(score);

    return 0;
}

// 点数に応じた評価記号を返す関数
char get_grade(int score)
{
    char grade;

    if (score >= 80) {
        grade = 'A';
    }
    else if (score >= 60) {
        grade = 'B';
    }
    else {
        grade = 'C';
    }

    return grade;
}

// 評価記号を使って結果メッセージを表示する関数
void print_result_message(int score)
{
    char grade = get_grade(score);

    printf("点数は%d点です。\n", score);
    printf("評価は%cです。\n", grade);
}

実行結果例

点数は78点です。
評価はBです。

このプログラムの流れ

このプログラムでは、main 関数が直接評価を決めていません。
まず main 関数は print_result_message を呼び出します。

すると print_result_message の中で、今度は get_grade が呼び出されます。
そして get_grade が評価記号を返し、その結果を受けて print_result_message が表示を行います。

流れを整理すると次のようになります。

順番行われること
1main 関数が始まる
2print_result_message(score) を呼び出す
3print_result_message 関数の処理が始まる
4get_grade(score) を呼び出す
5get_grade 関数が評価記号を求める
6get_grade 関数が評価記号を返す
7print_result_message 関数が表示を行う
8print_result_message 関数が終了する
9main 関数に戻る

この流れが、まさに関数の中から関数を呼び出すという形です。

どこが「関数を組み合わせている」のか

このプログラムでいちばん大事なのは、次の部分です。

char grade = get_grade(score);

これは print_result_message 関数の中に書かれています。
つまり、関数 print_result_message の中から、別の関数 get_grade を呼び出しているわけです。

ここで役割を見てみると、とても整理されています。

関数名役割
main全体の開始地点
print_result_message結果を表示する
get_grade点数から評価を決める

このように役割が分かれているので、コードを読むときにも「今は評価を決めている部分だな」「今は表示している部分だな」と判断しやすくなります。

この図では、処理が下へ進み、結果が上へ戻ってくる流れを表しています。

main 関数が print_result_message を呼び出すと、その時点で main の処理はいったん中断されます。
次に print_result_message の処理が始まり、その中でさらに get_grade が呼び出されます。
すると今度は print_result_message の処理がいったん止まり、get_grade の処理へ移ります。

get_grade が評価記号を return で返すと、print_result_message に処理が戻ります。
そして表示を終えたら、最後に main 関数へ戻ります。

このように、呼び出した側は、呼び出された関数の処理が終わるまで待つというのが基本の流れです。

呼び出しの流れをコードで追ってみる

次の部分を順番に見てみましょう。

まず main 関数です。

int main(void)
{
    int score = 78;

    print_result_message(score);

    return 0;
}

ここで print_result_message(score); が呼び出されます。
すると処理は次の関数へ移ります。

void print_result_message(int score)
{
    char grade = get_grade(score);

    printf("点数は%d点です。\n", score);
    printf("評価は%cです。\n", grade);
}

この最初の行で、さらに get_grade(score) が呼び出されます。

char get_grade(int score)
{
    char grade;

    if (score >= 80) {
        grade = 'A';
    }
    else if (score >= 60) {
        grade = 'B';
    }
    else {
        grade = 'C';
    }

    return grade;
}

ここで条件に応じて A、B、C のいずれかが決まり、その値が返されます。
返された値は print_result_message の中の変数 grade に入ります。
そのあとで printf によって結果が表示されます。

この流れをしっかり追えるようになると、関数が増えても混乱しにくくなります。

1つの関数に全部書く場合との違い

同じ処理を、関数を分けずに全部 main 関数へ書くこともできます。
でも、その場合は判定と表示が1か所に集まってしまいます。

たとえば次のようなイメージです。

ファイル名:13_5_2.c

#include <stdio.h>

int main(void)
{
    int score = 78;
    char grade;

    if (score >= 80) {
        grade = 'A';
    }
    else if (score >= 60) {
        grade = 'B';
    }
    else {
        grade = 'C';
    }

    printf("点数は%d点です。\n", score);
    printf("評価は%cです。\n", grade);

    return 0;
}

もちろんこれでも動きます。
ただ、今は短いから読めるだけで、処理がもっと複雑になると見通しが悪くなります。

そこで、

  • 評価を決める部分は get_grade
  • 表示する部分は print_result_message

と分けておくと、コードの意味がずっと見えやすくなります。

関数を組み合わせるときの考え方

関数を組み合わせるときは、次のように考えると作りやすいです。

考え方内容
何を求めるか最終的に何を表示・計算したいか
途中で必要な情報は何か先に別の関数で求めたほうがよい値は何か
役割を分けられるか判定、計算、表示に分けられないか

今回の例なら、

  • 最終的に表示したいのは評価メッセージ
  • そのために必要なのは評価記号
  • ならば評価記号を求める関数を別に作ろう

という流れで考えています。

この考え方は、今後もっと複雑なプログラムでもとても役立ちます。

return で返ってくる場所に注目しよう

関数を組み合わせるときに大事なのが、return でどこへ戻るのかを意識することです。

たとえば、

char grade = get_grade(score);

では、get_grade が返した値はこの右辺に戻ってきます。
そしてその結果が左辺の grade に代入されます。

つまり return は、「とりあえず呼び出し元へ戻る」ではなく、呼び出された場所へ結果を返すイメージです。

この感覚をつかむと、式の中で関数を使うことが自然に理解できるようになります。

関数の組み合わせは再利用しやすい

たとえば get_grade 関数は、今回の print_result_message の中だけでなく、別の場面でも使えます。

  • 評価だけを表示したい
  • 評価ごとの人数を集計したい
  • A評価かどうかで別の処理をしたい

このようなとき、評価を決める処理が get_grade にまとまっていれば、同じロジックを何度も書かずに済みます。

これが再利用しやすさです。

同じ処理を何度もコピペして書くのではなく、必要な処理を関数としてまとめておけば、プログラム全体が整いやすくなります。

補足:マクロを使うか const を使うか

元の例では、税率のような変わらない値を扱う話が出てきました。
C言語では、こうした固定値を表す方法として、主に const#define があります。

const を使う方法

たとえば次のように書く方法です。

const double TAX_RATE = 0.10;

この書き方のよいところは、値を変更してはいけない変数として扱えることです。
間違って書き換えようとすると、コンパイル時に問題が見つかりやすくなります。

また、型を持っているので、学習段階では意味を理解しやすいです。

#define を使う方法

もう1つは、オブジェクト形式マクロを使う方法です。

#define TAX_RATE 0.10

こちらもC言語ではよく使われます。
特に、複数の関数や複数のファイルで共通して使う値をまとめたいときに便利です。

どちらを使えばよいか

結論としては、どちらでも使えます。
ただ、使い分けの目安を持っておくと分かりやすいです。

場面向いている方法
その関数の中だけで使う定数const
複数の場所で共通して使う定数#define
型を意識して分かりやすく書きたいconst
共通ヘッダなどでまとめて管理したい#define

学習中は、まず const を使って「変更しない値」を表現することに慣れると理解しやすいです。

実践問題

次の手順に従って2つの関数を作成し、main 関数から呼び出して結果を確認してください。

① 判定用の関数を作成してください。

関数宣言

char get_rank(int score);

機能
score の値に応じて成績ランクを返す。

返却値

  • A:80点以上
  • B:60点以上80点未満
  • C:60点未満

② 表示用の関数を作成してください。

関数宣言

void print_rank_message(int score);

機能
①で作成した get_rank を呼び出して、点数と成績ランクを表示する。

返却値
なし

実行結果例

点数を入力してください > 72

点数:72
成績ランク:B

main 関数で入力
print_rank_message 関数で表示

解答例

// 点数に応じた成績ランクを表示するプログラム
#include <stdio.h>

char get_rank(int score);
void print_rank_message(int score);

int main(void)
{
    int score;

    printf("点数を入力してください > ");
    scanf("%d", &score);

    printf("\n");

    print_rank_message(score);

    return 0;
}

// 点数に応じた成績ランクを返す関数
char get_rank(int score)
{
    if (score >= 80) {
        return 'A';
    }
    else if (score >= 60) {
        return 'B';
    }
    else {
        return 'C';
    }
}

// 点数と成績ランクを表示する関数
void print_rank_message(int score)
{
    char rank = get_rank(score);

    printf("点数:%d\n", score);
    printf("成績ランク:%c\n", rank);
}

解説

この問題のポイントは、表示用の関数の中で判定用の関数を呼び出していることです。

まず main 関数では、点数を入力して print_rank_message(score) を呼び出しています。
すると print_rank_message 関数の中で、さらに get_rank(score) が呼び出されます。

char rank = get_rank(score);

この1行が、関数を組み合わせて使っている部分です。

get_rank は点数を見て A、B、C のいずれかを返すだけの関数です。
つまり、判定に専念している関数です。

一方の print_rank_message は、get_rank の返却値を受け取って、点数とランクを表示します。
つまり、表示に専念している関数です。

このように分けておくと、

  • ランクの判定方法を変えたいときは get_rank を直す
  • 表示の形を変えたいときは print_rank_message を直す

というように、修正箇所がはっきりします。

さらに理解を深める見方

今回の内容では、関数どうしのつながりを意識することが大切です。
とくに次の3点を押さえておくと理解しやすいです。

役割ごとに分ける

1つの関数で全部やろうとせず、
「何を判定するか」「何を計算するか」「何を表示するか」で分けると見通しがよくなります。

必要な値を別の関数から受け取る

途中で必要になる値を、別の関数に求めてもらうことで、メインの処理をすっきり書けます。

呼び出しの順番と戻り先を意識する

main から呼び出す

その関数の中でさらに別の関数を呼び出す

return で呼び出し元へ戻る

この流れをイメージできるようになると、複数の関数を使うプログラムも読みやすくなります。

学習の段階でおすすめの見方

関数が入れ子のように呼び出されると、最初は少しややこしく感じることがあります。
そんなときは、次のように追いかけると分かりやすいです。

見るポイント確認する内容
どこから呼ばれたかmain からか、別の関数からか
何を受け取るか引数は何か
何を返すかreturn の値は何か
戻ったあと何をするか呼び出し元でその値をどう使うか

これを1つずつ確認していくと、複数の関数がつながっていても整理しやすくなります。

関数の役割分担を強調した図も、とても効果的です。

  • main 関数は全体の入口
  • print_rank_message は表示担当
  • get_rank は判定担当

という分担が見えるので、関数を分ける意味が直感的に理解しやすくなります。

関数を組み合わせる考え方は今後ずっと使う

今回の内容は、単に「関数の中から関数を呼び出せる」という知識だけではありません。
この考え方は、これから先に学ぶさまざまな処理につながっていきます。

たとえば、

  • 料金計算の前に割引率を求める
  • メニュー表示の前に入力チェックを行う
  • 結果表示の前に判定や変換を行う

といった場面では、どれも今回と同じように小さな関数を組み合わせて処理を作ることになります。

その意味で、「関数を組み合わせて使う」は、C言語で読みやすいプログラムを書くためのとても重要な一歩です。