C言語で成績管理システムを作る【第3回】【Switch文でメニュー作成】

目次

この記事について

「C言語で成績管理システムを作る」シリーズの第3回です。今回が最終回になります。
過去のシリーズは以下の通りです。

成績管理システムはC言語の課題としてもよく出される定番のお題です。
もし課題で調べに来た方は、コードをそのまま写すのではなく、「なぜこの設計にしたか」の部分を読んでから自分で書いてみることをおすすめします。

このステップでは、以下の範囲を実装します。

  • 前提:第2回で作成したソースコードに追加する形で作成
  • switch 文を使ってメニュー画面を作成
  • for文を使って無限ループで入力を確認

これまでは「起動したら入力→即座に一覧と統計を表示して終了」という一直線の流れでした。

今回は 、メニュー画面を作成して「入力する」「一覧を見る」「統計を見る」を好きな順番・好きな回数だけ実行できるようにします。

あわせて、for の無限ループとメニュー選択の組み合わせという、C言語で対話的なプログラムを作る際の基本パターンにも触れます。

実行結果とソースコード

先に、完成したプログラムと実行した結果を載せておきます。

実行結果

赤枠内がキーボードで入力した値、それ以外はプロブラム実行時に表示される値です。

まだ何も入力していない状態で「2: 一覧を表示する」を選ぶと、こうなります⇩

親切なガードが1つ入っているのが分かります。

ソースコード

/*
 * 成績管理システム(パターン3: メニュー方式版)
 * Visual Studio 2026 で動作確認想定
 * ファイルは「UTF-8 シグネチャ(BOM)付き」で保存すること
 */
#define _CRT_SECURE_NO_WARNINGS  /* VSでのscanf使用時のC4996警告を抑止 */
#include <stdio.h>

#define MAX_NAME     32   /* 名前の最大文字数(終端の\0を含む) */
#define MAX_STUDENTS 10   /* 受け付ける最大人数 */

 /* 学生1人分のデータ */
struct Student {
    char name[MAX_NAME];
    int  score;           /* 0〜100点 */
};

/* --- 関数プロトタイプ宣言 --- */
int    input_students(struct Student s[], int max);
void   print_all(const struct Student s[], int n);
void   print_stats(const struct Student s[], int n);
double calc_average(const struct Student s[], int n);
int    find_max_index(const struct Student s[], int n);
int    find_min_index(const struct Student s[], int n);
void   clear_input(void);
int    read_menu_choice(void);

int main(void)
{
    struct Student students[MAX_STUDENTS];
    int n = 0;          /* まだ何も入力されていない状態を0で表す */
    int choice;

    printf("===== 成績管理システム =====\n");

    for (;;) {
        printf("\n1: 成績を入力する\n");
        printf("2: 一覧を表示する\n");
        printf("3: 統計を表示する\n");
        printf("0: 終了する\n");
        printf("選択してください: ");

        choice = read_menu_choice();

        switch (choice) {
        case 1:
            n = input_students(students, MAX_STUDENTS);
            printf("\n%d人分のデータを入力しました。\n", n);
            break;

        case 2:
            if (n == 0) {
                printf("\nまだデータが入力されていません。先に「1」で入力してください。\n");
            }
            else {
                printf("\n");
                print_all(students, n);
            }
            break;

        case 3:
            if (n == 0) {
                printf("\nまだデータが入力されていません。先に「1」で入力してください。\n");
            }
            else {
                printf("\n");
                print_stats(students, n);
            }
            break;

        case 0:
            printf("\n終了します。\n");
            return 0;

        default:
            printf("\n1・2・3・0のいずれかを入力してください。\n");
            break;
        }
    }
}

/* 入力バッファに残った文字を読み捨てる */
void clear_input(void)
{
    int c;
    while ((c = getchar()) != '\n' && c != EOF) {
        /* 何もしない(読み捨てるだけ) */
    }
}

/*
 * メニューの選択番号を1つ読み取る
 * 数字以外が入力された場合は -1 を返す(switch のdefaultで弾かれる)
 */
int read_menu_choice(void)
{
    int choice;

    if (scanf("%d", &choice) != 1) {
        clear_input();
        return -1;
    }
    clear_input();
    return choice;
}

/*
 * 学生データをキーボードから入力する
 * 戻り値: 実際に入力された人数
 */
int input_students(struct Student s[], int max)
{
    int n = 0;
    int i;

    /* 人数の入力(1〜maxの範囲になるまで繰り返す) */
    for (;;) {
        printf("学生の人数を入力してください (1~%d人): ", max);
        if (scanf("%d", &n) == 1 && n >= 1 && n <= max) {
            clear_input();
            break;
        }
        printf("入力が正しくありません。\n");
        clear_input();
    }

    /* 各学生の名前と点数の入力 */
    for (i = 0; i < n; i++) {
        printf("\n%d人目の名前: ", i + 1);
        scanf("%31s", s[i].name);   /* 31文字まで読み込み(バッファあふれ防止) */
        clear_input();

        /* 点数の入力(0〜100の範囲になるまで繰り返す) */
        for (;;) {
            printf("%d人目の点数 (0~100): ", i + 1);
            if (scanf("%d", &s[i].score) == 1
                && s[i].score >= 0 && s[i].score <= 100) {
                clear_input();
                break;
            }
            printf("入力が正しくありません。\n");
            clear_input();
        }
    }

    return n;
}

/* 全員分の成績を一覧表示する */
void print_all(const struct Student s[], int n)
{
    int i;

    printf("--- 成績一覧 ---\n");
    printf("番号  名前            点数\n");
    for (i = 0; i < n; i++) {
        printf("%2d    %-15s %4d\n", i + 1, s[i].name, s[i].score);
    }
}

/* 平均点・最高点・最低点をまとめて表示する */
void print_stats(const struct Student s[], int n)
{
    int max_i = find_max_index(s, n);
    int min_i = find_min_index(s, n);

    printf("--- 統計情報 ---\n");
    printf("平均点: %.1f 点\n", calc_average(s, n));
    printf("最高点: %d 点 (%s)\n", s[max_i].score, s[max_i].name);
    printf("最低点: %d 点 (%s)\n", s[min_i].score, s[min_i].name);
}

/* 平均点を計算して返す(人数0のときは0.0を返す) */
double calc_average(const struct Student s[], int n)
{
    int    i;
    double sum = 0.0;

    if (n <= 0) {
        return 0.0;
    }
    for (i = 0; i < n; i++) {
        sum += s[i].score;
    }
    return sum / n;
}

/* 最高点の学生の添字を返す(同点なら先に見つかった方) */
int find_max_index(const struct Student s[], int n)
{
    int i;
    int max_i = 0;

    for (i = 1; i < n; i++) {
        if (s[i].score > s[max_i].score) {
            max_i = i;
        }
    }
    return max_i;
}

/* 最低点の学生の添字を返す(同点なら先に見つかった方) */
int find_min_index(const struct Student s[], int n)
{
    int i;
    int min_i = 0;

    for (i = 1; i < n; i++) {
        if (s[i].score < s[min_i].score) {
            min_i = i;
        }
    }
    return min_i;
}

今回使う環境

  • Windows 11
  • Visual Studio 2026(コードを書いて実行するツール)
  • Claude(AI)

VS2026のインストール方法は以下の記事で解説しています。

Visual Studio 2026 でのビルド/実行手順

以前Claudeにソースコード生成を依頼した際、Visual Studioでの実行手順を教えてくれたので、その手順に従って進めます。

新しいプロジェクトの作成

.cファイルを作成

「デバッグなしで開始(Ctrl + F5)」で実行

補足:デバッグコンソールで日本語が文字化けする場合

以下のように、デバッグコンソールで日本語が文字化けする場合は「UTF-8 シグネチャ(署名)付き」で保存してください。

保存形式が原因の文字化けについてはこちらの記事で解説しています。

コードの解説

前回までとの変更点を中心に解説します。scanf の検証や入力バッファのクリアについては第2回の記事をご覧ください。

for(無限ループ)とswitchの組み合わせ

対話的なメニュー画面の基本形は、「無限ループでメニューを表示し続け、選ばれた番号に応じて処理を分岐する」という構造です。今回は無限ループを for (;;) で書いています(while (1) でも同じ意味です)。

for (;;) {
    /* メニュー表示 */
    choice = read_menu_choice();

    switch (choice) {
    case 1: /* ... */ break;
    case 2: /* ... */ break;
    case 3: /* ... */ break;
    case 0: return 0;   /* ループを抜けて終了 */
    default: /* ... */ break;
    }
}

ループを抜ける方法は break ではなく return 0; にしています。

switch の中の breakswitch 自体を抜けるだけで、外側の for ループはそのまま続いてしまうためです。「0を選んだら終了する」は、関数自体を終わらせてしまうのが確実です。

状態を変数で管理する(nの初期値を0にする理由)

第2回までは、入力してすぐに表示・集計まで行っていたので、「まだ何も入力されていない」状態を考える必要がありませんでした。

今回はメニュー形式になったことで、「一覧を表示する」が選ばれた時点でデータが入力済みとは限らなくなります。

そこで n の初期値を 0 にし、「n == 0 ならまだ未入力」という目印として使っています。

int n = 0;          /* まだ何も入力されていない状態を0で表す */
case 2:
    if (n == 0) {
        printf("\nまだデータが入力されていません。先に「1」で入力してください。\n");
    } else {
        print_all(students, n);
    }
    break;

「何もしていない状態を、特定の値(今回は0)で表現する」という考え方は、今後いろいろな場面で応用が効きます

メニュー選択の入力チェック(read_menu_choice関数)

メニュー番号の入力にも、第2回で学んだ「scanf の戻り値チェック」をそのまま応用しています。

int read_menu_choice(void)
{
    int choice;

    if (scanf("%d", &choice) != 1) {
        clear_input();
        return -1;
    }
    clear_input();
    return choice;
}

数字以外が入力された場合は -1 を返すようにし、switchdefault ケースで「1・2・3・0のいずれかを入力してください」と案内する形に寄せています。

case の値と一致しない -1 を返すことで、わざわざ専用のエラー処理を書かずに switch の仕組みだけで弾けるのがポイントです。

統計表示を関数にまとめる(print_stats)

第2回では、平均・最高・最低の計算と表示を main 関数の中に直接書いていました。

今回はメニューの case 3 から呼び出す形にするため、print_stats という関数にまとめています。

void print_stats(const struct Student s[], int n)
{
    int max_i = find_max_index(s, n);
    int min_i = find_min_index(s, n);

    printf("--- 統計情報 ---\n");
    printf("平均点: %.1f 点\n", calc_average(s, n));
    printf("最高点: %d 点 (%s)\n", s[max_i].score, s[max_i].name);
    printf("最低点: %d 点 (%s)\n", s[min_i].score, s[min_i].name);
}

中身は第2回で main に書いていた処理をそのまま移動しただけで、calc_averagefind_max_index といった計算部分は今回も無変更です。

気づいたこと

第2回との差分から見えること

今回も第2回のコードと 差分(diff)を取ってみたところ、各関数の変更有無は以下の通りでした。

  • 第2回コードと差分あり
    • main 関数(メニューループの導入)
    • 新規関数2つ(read_menu_choiceprint_stats)
  • 第2回コードと差分なし:
    • calc_average関数
    • find_max_index関数
    • find_min_index関数
    • input_students 関数

第1回から一貫して「配列と人数を渡せば動く」設計にしてきたことで、プログラムの構造(一直線の実行→メニュー方式)がこれだけ大きく変わっても、計算・入力のロジック自体には手を入れずに済んでいます。

まとめ

  • for (;;) の無限ループと switch を組み合わせると、対話的なメニュー画面の基本形が作れる
  • switch 内の break はループを抜けない。ループごと終了させたいときは return を使う
  • 「まだ何も入力されていない」状態を、変数の初期値(今回は0)で表現できる
  • 3回分の改修を通じて、calc_averagefind_max_index などの計算ロジックは一度も変更していない。関数分割の設計が効いていた

これで「C言語で成績管理システムを作る」シリーズは、当初決めたお題の範囲(構造体・配列・集計・入力・メニュー)を一通り実装できたことになります。

最後まで読んでいただき、ありがとうございました!

シリーズの他の記事もあわせてどうぞ。

「C言語で成績管理システムを作る」シリーズの第3回です。今回が最終回になります。
過去のシリーズは以下の通りです。

    よかったらシェアしてね!
    • URLをコピーしました!
    • URLをコピーしました!

    コメント

    コメントする

    目次