前回は、「過去2件の表示 (最後の2件を表示)」機能を追加したVer.3を作成した。
さらに、構造体+関数化で安全性を強化した。
今回は、このVer.3 をベースにして、メニュー方式で「過去2件表示」「新規日記入力」「終了」の3機能を選べる Ver.4を作成してみよう。
// ミニ日記アプリ ver.4(メニュー付き)
#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;
// --- メニュー表示 ---
void show_menu(void) {
printf("\n********** MENU **********\n");
printf("1. 過去2件の日記を表示する\n");
printf("2. 新しい日記を書く\n");
printf("9. 終了する\n");
printf("***************************\n");
printf("選択肢を入力してください:");
}
// --- ファイル読み込み ---
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)) {
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;
}
// --- 過去2件表示 ---
void show_last_two_entries(void) {
DiaryEntry entries[MAX_ENTRIES];
int entry_count = load_entries("diary.txt", entries, MAX_ENTRIES);
printf("\n【過去2件の記録】\n");
if (entry_count == 0) {
printf("なし\n");
return;
}
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);
}
}
// --- 新規日記作成 ---
void write_new_entry(void) {
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;
}
size_t len = strlen(diary);
if (len > 0 && diary[len - 1] == '\n') {
diary[len - 1] = '\0';
}
if (strlen(diary) == 0) {
printf("入力が空です。保存しません。\n");
return;
}
FILE *fp = fopen("diary.txt", "a");
if (!fp) {
perror("ファイルを開けませんでした");
return;
}
fprintf(fp, "[%s]\n%s\n\n", timestamp, diary);
fclose(fp);
printf("日記を保存しました。\n");
}
// --- メイン ---
int main(void) {
setlocale(LC_ALL, "ja_JP.UTF-8");
while (1) {
show_menu();
char choice[10];
if (!fgets(choice, sizeof(choice), stdin)) {
printf("入力エラーです。\n");
continue;
}
int opt = atoi(choice);
switch (opt) {
case 1:
show_last_two_entries();
break;
case 2:
write_new_entry();
break;
case 9:
printf("終了します。\n");
return 0;
default:
printf("無効な選択肢です。\n");
}
}
}
追加・修正点(Ver.3 → Ver.4)
項目 | Ver.3 | Ver.4 |
---|---|---|
メニュー機能 | なし(起動時に自動で最新2件表示+新規入力) | あり(1:過去2件表示 / 2:新規日記 / 9:終了) |
処理構造 | main 関数内に全処理 | 機能ごとに関数化(show_menu , show_last_two_entries , write_new_entry ) |
過去表示タイミング | 起動直後に自動表示 | ユーザーが 1 を選択したときだけ表示 |
新規入力タイミング | 自動で表示後に入力 | ユーザーが 2 を選択したときだけ入力 |
入力の安全性 | fgets 利用(OK) | 同じく fgets + メニュー入力時の文字列処理 |
繰り返し動作 | 起動 → 1回動作して終了 | メニュー選択後も終了選ぶまでループする |
Ver.4 の特徴
- 起動後すぐ終了したいときにも対応した。(Ver.3 は必ず入力が必要だった。)
- 過去表示・新規入力を必要なときだけ実行できる。
- 関数を分割して、読みやすく拡張しやすい構造にした。
- 将来的の機能追加が容易である。
この Ver.4 を使うと、例えば「過去を見返してから入力する」や「今日は書かないけど過去だけ見る」などが可能になる。
コメント