6日でできる 新C言語入門|関数の演習プログラム①

関数は、使って練習すると一気に身につく。
平均計算と素数判定の演習を通して、引数・戻り値・プロトタイプ宣言を実践的に理解しよう。

C言語で関数の基本を学んだら、次に大切なのは、実際に関数を使ってプログラムを作ってみることです。

関数は、説明だけを読むよりも、引数に値を渡す、戻り値を受け取る、別の関数から呼び出す、といった流れを手を動かして確認することで理解しやすくなります。

今回のテーマでは、関数を使った2つの演習プログラムを扱います。

1つ目は、3つの実数の平均値を求めるプログラムです。平均値を計算する処理を関数として分けることで、計算部分と入力・表示部分を整理します。

2つ目は、指定した数以下の素数を表示するプログラムです。素数かどうかを調べる処理と、素数の一覧を表示する処理を関数に分けることで、複数の関数を組み合わせる考え方を学びます。

関数を使うと、main関数の中にすべての処理を書き込む必要がなくなります。処理ごとに名前をつけて分けられるため、プログラムの見通しがよくなり、あとから修正しやすくなります。

この記事では、平均値を求める関数、素数判定に使う関数、素数一覧を表示する関数を通して、プロトタイプ宣言、引数、戻り値、void関数、return文の使い方をやさしく解説していきます。

関数演習で意識したいこと

関数の演習では、単にプログラムを動かすだけでなく、次の点を意識すると理解が深まります。

見るポイント内容
関数に渡す値引数として何を渡しているか
関数から返る値戻り値として何を受け取っているか
関数の役割その関数が何を担当しているか
main関数の流れ全体の処理が読みやすくなっているか
プロトタイプ宣言main関数より前に関数の情報を伝えているか

関数は、処理を小さな部品に分けるための仕組みです。

たとえば、平均値を計算する関数は、平均を求めることだけに集中します。入力を受け取る処理や結果を表示する処理はmain関数側に置くことで、役割が分かりやすくなります。

平均値を求める関数を作る

まずは、3つの実数を入力し、その平均値を求めるプログラムを作ります。

平均値は、3つの数値を足して3.0で割ることで求められます。

(a + b + c) / 3.0

この計算をmain関数の中に直接書くこともできますが、今回はavg3という関数に分けます。

avg3関数は、3つのdouble型の値を受け取り、その平均値をdouble型で返す関数です。

項目内容
関数名avg3
引数double型を3つ
戻り値double型
役割3つの実数の平均値を返す

平均値の計算を関数に分けることで、main関数では入力と表示に集中できます。

図:平均値を求める関数の流れ

この図から分かること

この図から分かるのは、avg3関数が3つの値を受け取り、平均値を計算して返す流れです。

引数は関数へ渡す入力、戻り値は関数から返ってくる結果です。平均値を求めるような計算処理は、引数あり・戻り値ありの関数として作ると分かりやすくなります。

3つの実数の平均値を求めるプログラム

次のプログラムでは、3つの気温を入力し、その平均値をavg3関数で計算します。

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

#include <stdio.h>

// 3つの実数の平均値を返す関数のプロトタイプ宣言
double avg3(double, double, double);

int main(void) {
    double temperature[3];
    double average;
    int i;

    // 3日分の気温を入力する
    for (i = 0; i < 3; i++) {
        printf("%d日目の気温を入力してください: ", i + 1);
        scanf("%lf", &temperature[i]);
    }

    // avg3関数を呼び出して平均値を求める
    average = avg3(temperature[0], temperature[1], temperature[2]);

    printf("3日間の平均気温: %.2f 度\n", average);

    return 0;
}

// 3つの実数の平均値を計算する関数
double avg3(double a, double b, double c) {
    return (a + b + c) / 3.0;
}

実行結果

1日目の気温を入力してください: 22.5
2日目の気温を入力してください: 24.0
3日目の気温を入力してください: 23.5
3日間の平均気温: 23.33 度

このプログラムでは、temperatureというdouble型の配列を使って、3日分の気温を保存しています。

double temperature[3];

for文を使って、3回入力を受け取ります。

for (i = 0; i < 3; i++)

入力された値は、temperature[0]、temperature[1]、temperature[2]に順番に格納されます。

平均値の計算は、main関数の中ではなくavg3関数に任せています。

average = avg3(temperature[0], temperature[1], temperature[2]);

avg3関数では、3つの値を受け取り、合計を3.0で割った結果を返します。

double avg3(double a, double b, double c) {
    return (a + b + c) / 3.0;
}

3.0で割っているのは、小数を含む計算にするためです。3と書いてもこの場合はdouble型の値と計算されますが、平均値のように小数を意識したい場面では3.0と書くと意図が分かりやすくなります。

Visual Studioでscanfを使う場合、環境設定によって警告が表示されることがあります。学習用として動作確認する場合は、必要に応じてSDLチェックの設定も確認しておきましょう。

Lesson42_1で使っている関数のポイント

Lesson42_1では、avg3関数が中心になります。

項目内容
プロトタイプ宣言double avg3(double, double, double);
関数定義double avg3(double a, double b, double c)
引数a、b、cの3つの実数
戻り値平均値
呼び出し場所main関数の中

プロトタイプ宣言は、main関数より前に書いています。

double avg3(double, double, double);

これにより、avg3関数の本体がmain関数のあとに書かれていても、main関数の中から呼び出せます。

このように、プロトタイプ宣言を使うと、main関数を先に書き、細かい関数定義を後ろにまとめる構成にできます。

配列と関数を組み合わせる考え方

Lesson42_1では、入力された値を配列に保存してから、関数へ渡しています。

average = avg3(temperature[0], temperature[1], temperature[2]);

配列を使うと、複数の値をまとめて管理できます。関数を使うと、計算処理を別の部品として切り出せます。

仕組み役割
配列複数の入力値をまとめて保存する
for文入力処理を繰り返す
関数平均値の計算を担当する
戻り値計算結果をmain関数へ返す

この組み合わせは、C言語の実用的なプログラムでよく使います。

たとえば、点数の平均、売上の平均、気温の平均など、複数の数値を扱う処理に応用できます。

素数判定を関数で分ける

次に、指定した数以下の素数を表示するプログラムを考えます。

素数とは、1より大きい自然数のうち、約数が1と自分自身だけの数です。

たとえば、2、3、5、7、11、13などは素数です。

約数素数かどうか
21, 2素数
31, 3素数
41, 2, 4素数ではない
51, 5素数
61, 2, 3, 6素数ではない

素数かどうかを判定するには、その数の約数の個数を調べる方法があります。

約数の個数が2個なら、その数は素数です。

今回の演習では、次の2つの関数を使います。

関数名役割
countDivisors指定した整数の約数の個数を返す
showPrimes2から上限値までの素数を表示する

このように処理を分けることで、1つの関数に多くの役割を詰め込まずに済みます。

図:素数表示を2つの関数で分担する

この図から分かること

この図から分かるのは、素数を表示する処理を複数の関数で分担していることです。

main関数は入力を担当し、showPrimes関数は範囲内の数を順番に調べます。そして、countDivisors関数が約数の個数を調べます。関数ごとに役割を分けることで、プログラムの流れが読みやすくなります。

指定した数以下の素数を表示するプログラム

次のプログラムでは、入力された上限値以下の素数を表示します。

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

#include <stdio.h>

// 約数の個数を求める関数のプロトタイプ宣言
int countDivisors(int);

// 素数を表示する関数のプロトタイプ宣言
void showPrimes(int);

int main(void) {
    int limit;

    printf("調べたい上限の自然数を入力してください: ");
    scanf("%d", &limit);

    // 入力された上限値までの素数を表示する
    showPrimes(limit);

    return 0;
}

// nの約数の個数を返す関数
int countDivisors(int n) {
    int i;
    int count = 0;

    // 1からnまで割り切れる数を数える
    for (i = 1; i <= n; i++) {
        if (n % i == 0) {
            count++;
        }
    }

    return count;
}

// 2からnまでの素数を表示する関数
void showPrimes(int n) {
    int i;
    int printed = 0;

    // 2未満の値では素数を表示できない
    if (n < 2) {
        printf("2以上の自然数を入力してください。\n");
        return;
    }

    printf("2から%dまでの素数:\n", n);

    // 2からnまで順番に素数かどうかを調べる
    for (i = 2; i <= n; i++) {
        if (countDivisors(i) == 2) {
            printf("%d ", i);
            printed++;

            // 8個表示したら改行する
            if (printed % 8 == 0) {
                printf("\n");
            }
        }
    }

    // 最後の行が途中で終わった場合に改行する
    if (printed % 8 != 0) {
        printf("\n");
    }
}

実行例1

調べたい上限の自然数を入力してください: 30
2から30までの素数:
2 3 5 7 11 13 17 19 
23 29

実行例2

調べたい上限の自然数を入力してください: 1
2以上の自然数を入力してください。

このプログラムでは、main関数で上限値を入力し、その値をshowPrimes関数へ渡しています。

showPrimes(limit);

showPrimes関数は、2からnまでの整数を順番に調べます。

for (i = 2; i <= n; i++)

それぞれの数について、countDivisors関数を呼び出し、約数の個数を調べます。

if (countDivisors(i) == 2)

約数の個数が2個なら、その数は素数です。そこでprintfを使って表示しています。

約数の個数を返すcountDivisors関数

countDivisors関数は、指定された整数nの約数の個数を返します。

int countDivisors(int n) {
    int i;
    int count = 0;

    for (i = 1; i <= n; i++) {
        if (n % i == 0) {
            count++;
        }
    }

    return count;
}

この関数では、1からnまでの数でnを割り、余りが0になるものを数えています。

余りを調べるには、%演算子を使います。

if (n % i == 0)

この条件が成り立つ場合、iはnの約数です。そのたびにcountを1増やします。

最後に、約数の個数であるcountを戻り値として返します。

return count;

この関数は、素数かどうかを直接判定しているわけではありません。あくまで約数の個数を返す役割に集中しています。

このように、関数の役割を1つに絞ると、プログラムが読みやすくなります。

素数を表示するshowPrimes関数

showPrimes関数は、2からnまでの数を順番に確認し、素数だけを表示します。

void showPrimes(int n)

戻り値の型はvoidです。つまり、showPrimes関数は何かの値を返すのではなく、画面に表示することを目的としています。

関数の中では、まずnが2未満かどうかを確認しています。

if (n < 2) {
    printf("2以上の自然数を入力してください。\n");
    return;
}

2未満の値では素数を表示できないため、メッセージを表示してreturnで関数を終了します。

このreturnは、戻り値を返すためではなく、void関数を途中で終わらせるために使っています。

その後、2からnまで順番に確認し、countDivisors関数の戻り値が2なら素数として表示します。

if (countDivisors(i) == 2)

showPrimes関数は、表示全体の流れを担当しています。一方、約数の個数を調べる細かい処理はcountDivisors関数に任せています。

関数同士を組み合わせると処理を分けやすい

Lesson42_2では、showPrimes関数の中からcountDivisors関数を呼び出しています。

if (countDivisors(i) == 2)

これは、関数の中から別の関数を呼び出している例です。

このように関数同士を組み合わせると、それぞれの処理を分担できます。

関数担当する処理
main入力を受け取り、処理を開始する
showPrimes範囲内の素数を表示する
countDivisors約数の個数を調べる

main関数にすべての処理を書くと、入力、判定、表示が混ざって読みにくくなります。

関数を分けると、どこで何をしているのかが分かりやすくなります。あとから素数判定の方法を変えたい場合も、countDivisors関数を中心に見直せばよくなります。

図3:main関数・表示関数・判定用関数の役割分担

この図から分かること

この図から分かるのは、複数の関数を使うことで、処理を役割ごとに分けられるということです。

main関数は全体の入口を担当し、showPrimes関数は素数一覧の表示を担当します。countDivisors関数は、約数の個数を数える処理に集中します。関数ごとに役割を分けると、プログラム全体の見通しがよくなります。

プロトタイプ宣言で関数の情報を先に伝える

Lesson42_1とLesson42_2では、どちらもプロトタイプ宣言を使っています。

Lesson42_1では、次のようにavg3関数の情報を先に書いています。

double avg3(double, double, double);

Lesson42_2では、countDivisors関数とshowPrimes関数の情報を先に書いています。

int countDivisors(int);
void showPrimes(int);

プロトタイプ宣言があることで、main関数より後ろに関数本体を書いても、main関数の中から呼び出せます。

プロトタイプ宣言では、次の情報をコンパイラへ伝えます。

情報
戻り値の型double、int、void
関数名avg3、countDivisors、showPrimes
引数の型double、intなど

関数が増えてきたら、プログラムの先頭にプロトタイプ宣言をまとめておくと、どのような関数があるのか分かりやすくなります。

戻り値ありの関数とvoid関数の違い

今回の演習では、戻り値ありの関数とvoid関数の両方を使っています。

関数名戻り値目的
avg3double型平均値を返す
countDivisorsint型約数の個数を返す
showPrimesなし素数を表示する

avg3関数やcountDivisors関数は、計算結果を呼び出し元で使いたいので戻り値があります。

一方、showPrimes関数は、素数を画面に表示することが目的です。計算結果を呼び出し元へ返す必要がないため、戻り値の型はvoidです。

関数を作るときは、次のように考えると整理しやすくなります。

考えること判断
関数から結果を受け取りたいか受け取りたいなら戻り値あり
表示するだけでよいか表示だけならvoid関数
関数へ値を渡す必要があるか必要なら引数を使う

関数の目的を先に考えると、戻り値の型や引数の形を決めやすくなります。

return文の使い方も確認する

return文は、関数から値を返すときに使います。

avg3関数では、平均値を返しています。

return (a + b + c) / 3.0;

countDivisors関数では、約数の個数を返しています。

return count;

一方、showPrimes関数では、入力値が2未満の場合にreturnを使っています。

if (n < 2) {
    printf("2以上の自然数を入力してください。\n");
    return;
}

このreturnは値を返していません。showPrimes関数はvoid関数だからです。この場合のreturnは、関数を途中で終了するために使っています。

関数の種類returnの使い方
戻り値ありの関数return 値;で結果を返す
void関数return;で途中終了できる
void関数の最後return;は省略できる

戻り値がある関数では、必要な値を返すことが大切です。void関数では、値を返さずに処理だけを行います。

関数演習で身につけたい考え方

関数の演習では、1つのプログラムをただ完成させるだけでなく、処理をどう分けるかを考えることが大切です。

平均値を求めるプログラムでは、入力と表示をmain関数に置き、平均値の計算をavg3関数に分けました。

素数を表示するプログラムでは、入力をmain関数、表示の流れをshowPrimes関数、約数の個数を調べる処理をcountDivisors関数に分けました。

このように役割ごとに関数を作ると、プログラムの構造が分かりやすくなります。

また、プロトタイプ宣言を使うことで、main関数を上のほうに書き、その後に関数定義をまとめられます。これにより、最初にプログラム全体の流れを読み、そのあとで細かい処理を確認できます。

関数を使いこなすためには、引数、戻り値、void、return、プロトタイプ宣言をセットで理解することが大切です。今回のような演習を通して、関数を部品として組み合わせる感覚を少しずつ身につけていきましょう。