C言語で スケジュール管理アプリ(ミニ)を作ろう!第3回 ~予定の編集と削除~

スポンサーリンク
C言語

はじめに

前回のVer.2では、予定をファイルに保存し、次回起動時に読み込めるようにした。
今回のVer.3では、登録済みの予定を編集・削除できるようにしよう。

編集・削除はどちらも「一覧の中から対象を1つ選ぶ」という操作が共通しているため、今回はその部分を関数として切り出し、追加・編集の入力処理もあわせて共通化する。

対応するGitHubリポジトリは以下の通りである。

Ver.2からの変更点

  • メニューに 3: 予定を編集4: 予定を削除 を追加した。
  • 編集・削除は、一覧表示の番号(1始まり)で対象を指定する方式にした。
  • 「年月日と予定の内容を入力してもらう」処理を read_schedule_input 関数として切り出し、追加・編集の両方から使うようにした。
  • 「一覧表示→番号を入力してもらう→検証する」処理を select_schedule_index 関数として切り出し、編集・削除の両方から使うようにした。

入力処理の共通化

Ver.1・Ver.2では、年月日と予定の内容を読み取る処理は add_schedule の中に直接書いていた。Ver.3では編集でも同じ入力が必要になるため、Schedule 構造体を受け取って値を埋める関数として独立させた。

/*
 * 年月日と予定の内容を標準入力から読み取り、sに格納する。
 * 追加(add_schedule)・編集(edit_schedule)の両方から共通で使う。
 * 読み取りに成功すれば1、形式が不正であれば0を返す。
 */
int read_schedule_input(Schedule *s) {
    char date_line[MAX_DATE_LINE];

    printf("年月日を入力してください(例: 2026 8 1 または 20260801): ");
    fgets(date_line, sizeof(date_line), stdin);

    int result = sscanf(date_line, "%d %d %d", &s->year, &s->month, &s->day);
    if (result != 3) {
        result = sscanf(date_line, "%4d%2d%2d", &s->year, &s->month, &s->day);
    }

    if (result != 3 || s->month < 1 || s->month > 12 || s->day < 1 || s->day > 31) {
        printf("入力形式が正しくありません。「2026 8 1」または「20260801」の形式で入力してください。\n");
        return 0;
    }

    printf("予定を入力してください: ");
    fgets(s->title, MAX_TITLE, stdin);
    s->title[strcspn(s->title, "\n")] = '\0';

    if (s->title[0] == '\0') {
        printf("予定の内容が入力されていません。処理を中止します。\n");
        return 0;
    }

    return 1;
}

中身自体はVer.1・Ver.2の add_schedule にあったものと同じで、年月日の入力形式と、月・日の範囲チェック・ハイフン誤認識対策・空タイトルのチェックもそのまま引き継いでいる。add_schedule 側は、この関数を呼び出して結果を配列に反映するだけになった。

void add_schedule(void) {
    if (schedule_count >= MAX_SCHEDULES) {
        printf("これ以上予定を追加できません。\n");
        return;
    }

    Schedule temp;
    if (!read_schedule_input(&temp)) {
        return;
    }

    schedules[schedule_count] = temp;
    schedule_count++;
    printf("予定を追加しました。\n");
}

一度 temp という一時的な Schedule に読み取ってから配列に代入しているのは、入力が不正だった場合に配列の中身を変更せずに済ませるためである。

対象を選ぶ処理の共通化

編集・削除はどちらも「一覧を表示し、番号を入力してもらい、その番号が有効かどうかを確認する」という手順が同じである。この部分を select_schedule_index としてまとめた。

/*
 * 一覧表示したうえで番号を入力してもらい、対応する配列の添字(0始まり)を返す。
 * 予定が1件も無い場合、数値でない場合、範囲外の番号の場合は-1を返す。
 * edit_schedule・delete_scheduleの両方から共通で使う。
 */
int select_schedule_index(const char *prompt) {
    if (schedule_count == 0) {
        printf("登録されている予定はありません。\n");
        return -1;
    }

    list_schedules();

    printf("%s", prompt);
    int number;
    if (scanf("%d", &number) != 1) {
        clear_stdin();
        printf("正しい番号を入力してください。\n");
        return -1;
    }
    clear_stdin();

    if (number < 1 || number > schedule_count) {
        printf("該当する予定がありません。\n");
        return -1;
    }

    return number - 1;
}

一覧に表示する番号は1始まりだが、配列の添字は0始まりなので、戻り値では number - 1 として添字に変換している。呼び出し側はこの関数が返す値だけを見ればよく、「一覧が空かどうか」「番号が数値かどうか」「範囲内かどうか」を個別に気にする必要が無くなる。

予定の編集

select_schedule_index で対象の添字を受け取り、read_schedule_input で新しい内容を読み取って、そのまま代入するだけである。

void edit_schedule(void) {
    int index = select_schedule_index("編集する予定の番号を入力してください: ");
    if (index < 0) {
        return;
    }

    Schedule temp;
    if (!read_schedule_input(&temp)) {
        printf("編集を中止しました。\n");
        return;
    }

    schedules[index] = temp;
    printf("予定を更新しました。\n");
}

Ver.3では日付・予定内容のどちらか一方だけを変更する、といった細かい編集はできず、両方をあらためて入力し直す仕様にしている。入力の手間は増えるが、実装がシンプルになるという判断である。

予定の削除

削除は、対象の添字より後ろの要素を1つずつ前に詰めることで実現している。

void delete_schedule(void) {
    int index = select_schedule_index("削除する予定の番号を入力してください: ");
    if (index < 0) {
        return;
    }

    /* 削除した分だけ、後ろの要素を1つずつ前に詰める。 */
    for (int i = index; i < schedule_count - 1; i++) {
        schedules[i] = schedules[i + 1];
    }
    schedule_count--;

    printf("予定を削除しました。\n");
}

例えば3件のうち1件目(添字0)を削除する場合、2件目(添字1)の内容を1件目の位置に、3件目(添字2)の内容を2件目の位置にコピーし、最後に schedule_count を1減らす。これにより、配列の後ろに「削除済みだが値が残っているだけの領域」ができても、schedule_count の範囲外なので一覧表示やファイル保存には影響しない。

動作確認

3件の予定を登録した状態から、2件目を編集し、その後1件目を削除する例で確認する。

--- 予定一覧 ---
1: 2026-08-01  A予定
2: 2026-08-02  B予定
3: 2026-08-03  C予定
編集する予定の番号を入力してください: 2
年月日を入力してください(例: 2026 8 1 または 20260801): 2026 8 20
予定を入力してください: B予定(更新)
予定を更新しました。
--- 予定一覧 ---
1: 2026-08-01  A予定
2: 2026-08-20  B予定(更新)
3: 2026-08-03  C予定
削除する予定の番号を入力してください: 1
予定を削除しました。

削除後に一覧表示すると、残りの2件が1番から詰めて表示される。

--- 予定一覧 ---
1: 2026-08-20  B予定(更新)
2: 2026-08-03  C予定

存在しない番号や、整数として読み取れない入力をした場合は、それぞれ「該当する予定がありません。」「正しい番号を入力してください。」と表示され、何も変更されない。また、予定が1件も無い状態で編集・削除を選んだ場合は、番号の入力を求める前に「登録されている予定はありません。」と表示して処理を中止する。

次回予告

次回のVer.4では、一覧表示の順序を日付順に並び替える。基本情報技術者試験アルゴリズムシリーズで扱ったバブルソートを、Schedule の配列に対して応用する予定である。

コメント

タイトルとURLをコピーしました