先回は、「ミニ日記アプリ」に曜日が表示(日本語対応)できるように修正版を作成した。
今回は、以下のような機能を追加してみよう。
追加する機能
機能 | 内容 |
① ファイル読み込み | 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
関数のみ- 実行の流れ
- 曜日配列定義
- 現在日時取得・フォーマット
- ユーザー入力受付
- 空文字チェック
diary.txt
に追記
ver.3(過去2件表示付き)
DiaryEntry
構造体追加(日時+本文)load_entries
関数追加(ファイル読み込み&分割処理)- 実行の流れ
load_entries
で diary.txt を読み込み- 最新2件の内容を表示(件数不足時はある分だけ or 「なし」)
- 現在日時取得・フォーマット
- ユーザー入力受付
- 空文字チェック
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件表示」の機能を追加し、構造体+関数化で安全性も強化した改良版である。
コメント