C言語で ミニ日記アプリを作ろう!(3)

スポンサーリンク
C言語

先回は、「ミニ日記アプリ」に曜日が表示(日本語対応)できるように修正版を作成した。

今回は、以下のような機能を追加してみよう。

追加する機能

機能内容
① ファイル読み込みdiary.txt を読み込み、日記エントリ([日時] 区切り)ごとに分割
② 過去2件の表示最後の2件を表示(なければ「なし」)
③ 入力受付日本語で新しい日記を入力受付(曜日付き日時)
④ ファイル追記UTF-8(BOMなし)で追記
// ミニ日記アプリ ver.3(曜日付き+過去2件表示)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <time.h>

#define MAX_INPUT 1024
#define MAX_ENTRIES 1000   // 読み込み可能な最大エントリ数

// 過去エントリ構造体
typedef struct {
    char timestamp[128];
    char content[MAX_INPUT];
} DiaryEntry;

// diary.txtを読み込み、エントリ数を返す
int load_entries(const char *filename, DiaryEntry entries[], int max_entries) {
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        return 0; // ファイルなし
    }

    char line[MAX_INPUT];
    int count = 0;
    DiaryEntry temp = {"", ""};

    while (fgets(line, sizeof(line), fp)) {
        // タイムスタンプ行([YYYY-... で始まる)
        if (line[0] == '[') {
            if (strlen(temp.timestamp) > 0) {
                // 直前のエントリを保存
                if (count < max_entries) {
                    entries[count++] = temp;
                }
                temp.timestamp[0] = '\0';
                temp.content[0] = '\0';
            }
            line[strcspn(line, "\n")] = '\0';
            strncpy(temp.timestamp, line + 1, sizeof(temp.timestamp) - 1); // [ を除く
        }
        // 内容行
        else if (strlen(temp.timestamp) > 0 && line[0] != '\n') {
            line[strcspn(line, "\n")] = '\0';
            strncpy(temp.content, line, sizeof(temp.content) - 1);
        }
    }

    // 最後のエントリ保存
    if (strlen(temp.timestamp) > 0) {
        if (count < max_entries) {
            entries[count++] = temp;
        }
    }

    fclose(fp);
    return count;
}

int main(void) {
    setlocale(LC_ALL, "ja_JP.UTF-8");

    DiaryEntry entries[MAX_ENTRIES];
    int entry_count = load_entries("diary.txt", entries, MAX_ENTRIES);

    // --- 過去2件の表示 ---
    printf("【過去2件の記録】\n");
    if (entry_count == 0) {
        printf("なし\n\n");
    } else {
        int start = (entry_count >= 2) ? entry_count - 2 : 0;
        for (int i = start; i < entry_count; i++) {
            printf("[%s]\n%s\n\n", entries[i].timestamp, entries[i].content);
        }
    }

    // --- 新規入力 ---
    char diary[MAX_INPUT];
    time_t now = time(NULL);
    struct tm *t = localtime(&now);
    const char *weekdays_jp[] = {"日", "月", "火", "水", "木", "金", "土"};

    char timestamp[128];
    snprintf(timestamp, sizeof(timestamp),
             "%04d-%02d-%02d(%s) %02d:%02d:%02d",
             t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
             weekdays_jp[t->tm_wday],
             t->tm_hour, t->tm_min, t->tm_sec);

    printf("今日の日記を入力してください(Enterで終了):\n");
    if (!fgets(diary, sizeof(diary), stdin)) {
        fprintf(stderr, "入力に失敗しました。\n");
        return 1;
    }

    size_t len = strlen(diary);
    if (len > 0 && diary[len - 1] == '\n') {
        diary[len - 1] = '\0';
    }

    if (strlen(diary) == 0) {
        printf("入力が空です。保存しません。\n");
        return 0;
    }

    FILE *fp = fopen("diary.txt", "a");
    if (!fp) {
        perror("ファイルを開けませんでした");
        return 1;
    }
    fprintf(fp, "[%s]\n%s\n\n", timestamp, diary);
    fclose(fp);

    printf("日記を保存しました。\n");
    return 0;
}

ミニ日記アプリ ver.2(曜日付き) と、 ミニ日記アプリ ver.3(過去2件表示付き) のコードの、主な違いを比較して確認しよう。


1. 機能面の違い

機能ver.2(曜日付き)ver.3(過去2件表示付き)
曜日付き日時生成〇(同じ)
新規日記入力〇(同じだが過去表示後に入力)
ファイル追記〇(UTF-8 BOMなし)〇(UTF-8 BOMなし)
過去2件の表示×〇(ファイル読み込み後に最新2件表示)
エントリ分割処理×〇([日時] 区切りで構造体に格納)
空ファイルや未作成時の対応×(エラーになる)〇(「なし」と表示)
最大エントリ数管理×〇(MAX_ENTRIES制限付き)

2. 構造の違い

ver.2(曜日付き)

  • main 関数のみ
  • 実行の流れ
    1. 曜日配列定義
    2. 現在日時取得・フォーマット
    3. ユーザー入力受付
    4. 空文字チェック
    5. diary.txt に追記

ver.3(過去2件表示付き)

  • DiaryEntry 構造体追加(日時+本文)
  • load_entries 関数追加(ファイル読み込み&分割処理)
  • 実行の流れ
    1. load_entries で diary.txt を読み込み
    2. 最新2件の内容を表示(件数不足時はある分だけ or 「なし」)
    3. 現在日時取得・フォーマット
    4. ユーザー入力受付
    5. 空文字チェック
    6. diary.txt に追記

3. コード上の追加点

新規定義・変更

#define MAX_ENTRIES 1000
typedef struct {
    char timestamp[128];
    char content[MAX_INPUT];
} DiaryEntry;
  • エントリを安全に管理するための構造体を導入した。
  • 最大件数制限でメモリ安全性を確保した。

ファイル読み込み部分(ver.3で追加)

int load_entries(const char *filename, DiaryEntry entries[], int max_entries) {
    FILE *fp = fopen(filename, "r");
    if (!fp) return 0;
    ...
}

[日時] 行を見つけると新しいエントリとして格納する。

  • 最後の1件も忘れず保存
  • ファイルが無いときは 0 件で返す

過去2件表示処理(ver.3で追加)

printf("【過去2件の記録】\n");
if (entry_count == 0) {
    printf("なし\n\n");
} else {
    int start = (entry_count >= 2) ? entry_count - 2 : 0;
    for (int i = start; i < entry_count; i++) {
        printf("[%s]\n%s\n\n", entries[i].timestamp, entries[i].content);
    }
}
  • 件数が1件や0件のときでも落ちないよう条件分岐

4. 安全性の違い

  • ver.2
    • ファイル読み込み機能がないため、表示機能もなし
    • diary.txt が存在しなくても問題はないが、過去表示はできない。
  • ver.3
    • ファイルがない場合でも安全に「なし」と表示する。
    • メモリサイズ(MAX_ENTRIES)で読み込み上限を設定し、オーバーフロー防止する。

5. 実行イメージの違い

ver.2 実行例

今日の日記を入力してください(Enterで終了):
今日はカレーを食べました。
日記を保存しました。

ver.3 実行例

【過去2件の記録】
[2025-08-08(金) 22:03:37]
映画「ジュラシックワールド」を見ました。

[2025-08-09(土) 07:49:27]
今日は暑さも少し和らいだ気がします。

今日の日記を入力してください(Enterで終了):
本を読みました。
日記を保存しました。

ver.3 は ver.2 に「ファイル読み込み+過去2件表示」の機能を追加し、構造体+関数化で安全性も強化した改良版である。

コメント

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