这个项目更加着重之前学的基础命令、文件IO、进程、线程的知识,重在对Linux操作系统的理解与掌握。
第一部分
第一步:创建你的第一个项目目录
# 先看看你在哪里(当前目录)
pwd
# 应该显示 /home/你的用户名
# 创建一个工作文件夹(所有项目都放这里)
mkdir my_workspace
# 进入这个文件夹
cd my_workspace
# 再创建今天的项目文件夹
mkdir diary_project
# 进入项目文件夹
cd diary_project
# 看看现在在哪里
pwd
# 应该显示 /home/你的用户名/my_workspace/diary_project
第二步:创建第一个C文件
# 创建一个空文件叫 main.c
touch main.c
# 看看文件是不是创建成功了
ls
# 应该显示 main.c
第三步:写你的第一个程序框架
现在我们要编辑main.c
vim main.c
逐字输入以下代码(不要复制,手动敲才能记住):
cpp
#include <stdio.h>
int main()
{
printf("你好,这是我的第一个Linux项目!\n");
return 0;
}
这里我注意到Linux中文输入法要进行配置,为了后续代码辨识,详情见帖子:Ubuntu中文设置与安装中文输入法(超详细)_ubuntu中文输入法安装-CSDN博客
第四步:编译和运行你的第一个程序
# 编译:把人类能读懂的C代码变成计算机能执行的机器码
gcc main.c -o diary
# 解释:gcc是编译器,main.c是输入,-o diary指定输出文件名叫diary
# 看看编译后多了什么
ls
# 应该看到 main.c 和 diary(diary是绿色或白色的可执行文件)
# 运行你的程序
./diary
# 应该看到输出:你好,这是我的第一个Linux项目!
以上这些操作的细节可以看我前面介绍Linux命令的帖子,学过的跟着打就好,先完成一整个项目。
第五步:让程序能接收命令
现在我们要让程序更智能,能根据你输入的命令做不同的事。
再次打开main.c。
删除所有内容,输入以下新代码:
cpp
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
printf("====== 命令行日记本 ======\n");
// 如果没有输入任何命令,显示帮助信息
if (argc == 1) {
printf("用法: %s <命令>\n", argv[0]);
printf("可用命令:\n");
printf(" write - 写日记\n");
printf(" read - 读日记\n");
printf(" help - 显示帮助\n");
return 0;
}
// 根据输入的命令执行不同操作
if (strcmp(argv[1], "write") == 0) {
printf("你选择了:写日记\n");
printf("(这个功能还没实现)\n");
}
else if (strcmp(argv[1], "read") == 0) {
printf("你选择了:读日记\n");
printf("(这个功能还没实现)\n");
}
else if (strcmp(argv[1], "help") == 0) {
printf("你选择了:显示帮助\n");
printf("用法: %s <命令>\n", argv[0]);
printf(" write - 写日记\n");
printf(" read - 读日记\n");
printf(" help - 显示帮助\n");
}
else {
printf("错误:未知命令 '%s'\n", argv[1]);
printf("试试 '%s help' 查看帮助\n", argv[0]);
}
return 0;
}
gcc main.c -o diary
# 测试各种命令
./diary
./diary write
./diary read
./diary help
./diary abc
出现对应指令即可;
至此,我们完成了这个项目基础框架的搭建,并且其能够运行起来,
接下来我将补充git的使用,并将其远程同步到guthub上,这对我们写代码和项目的完备性都有益,并且良好的代码习惯,对于找工作肯定是个加分项
git入门及其代码同步到GitHub上参考我这篇帖子:
Git入门教程及代码同步到Github(Linux嵌入式)-CSDN博客
这篇帖子的项目背景就是这个项目;
第二部分
我们继续。现在来实现真正的写日记功能。你会学到:
-
如何创建和写入文件
-
如何获取当前时间
-
如何处理用户输入
第一步:理解我们要做什么
写日记功能需要:
-
获取当前日期(用来生成文件名,比如
日记_20260310.txt) -
让用户输入日记内容
-
把内容保存到文件
第二步:打开文件并编辑
cpp
# 进入你的项目目录
cd ~/my_workspace/diary_project
# 打开main.c准备编辑
nano main.c
删除所有内容,输入以下完整代码
cpp
#include <stdio.h>
#include <string.h>
#include <time.h> // 新增:时间函数
// 函数声明
void show_help(void);
void write_diary(void);
void read_diary(void);
int main(int argc, char *argv[])
{
printf("====== 命令行日记本 v0.2 ======\n");
if (argc == 1) {
show_help();
return 0;
}
if (strcmp(argv[1], "write") == 0) {
write_diary(); // 调用写日记函数
}
else if (strcmp(argv[1], "read") == 0) {
read_diary();
printf("(读日记功能还没实现)\n");
}
else if (strcmp(argv[1], "help") == 0) {
show_help();
}
else {
printf("错误:未知命令 '%s'\n", argv[1]);
show_help();
}
return 0;
}
void show_help(void)
{
printf("\n使用方法:\n");
printf(" ./diary write - 写日记\n");
printf(" ./diary read - 读日记\n");
printf(" ./diary help - 显示帮助\n");
printf("\n");
}
void write_diary(void)
{
// ---- 第1步:准备变量 ----
char filename[100]; // 文件名缓冲区
char content[1024]; // 日记内容缓冲区
time_t now; // 当前时间(秒数)
struct tm *timeinfo; // 时间结构体
// ---- 第2步:获取当前时间并生成文件名 ----
time(&now); // 获取当前时间(秒数)
timeinfo = localtime(&now); // 转换成当地时间
// 生成文件名:日记_20260310.txt
// %Y = 年,%m = 月,%d = 日
strftime(filename, sizeof(filename), "日记_%Y%m%d.txt", timeinfo);
printf("正在写入: %s\n", filename);
printf("请输入你的日记内容(输入空行结束):\n");
printf("------------------------------\n");
// ---- 第3步:打开文件准备写入 ----
// "w" 表示写入模式(write)
// 如果文件不存在就创建,如果存在就覆盖
FILE *fp = fopen(filename, "w");
// 检查文件是否成功打开
if (fp == NULL) {
printf("错误:无法创建文件!\n");
printf("可能原因:磁盘已满或没有写入权限\n");
return;
}
// ---- 第4步:写入日记标题(日期时间) ----
char datetime[100];
strftime(datetime, sizeof(datetime), "%Y年%m月%d日 %H:%M:%S", timeinfo);
fprintf(fp, "=== %s ===\n\n", datetime);
// ---- 第5步:读取用户输入并写入文件 ----
printf("> "); // 提示符
// fgets:从键盘读取一行,存入content,最多读sizeof(content)字节
while (fgets(content, sizeof(content), stdin) != NULL) {
// 如果用户只输入了回车(空行),就结束输入
if (strcmp(content, "\n") == 0) {
break;
}
// 把这一行写入文件
fputs(content, fp);
// 显示下一个提示符
printf("> ");
}
// ---- 第6步:关闭文件并显示完成信息 ----
fclose(fp);
printf("------------------------------\n");
printf(" 日记已保存到: %s\n", filename);
}
void read_diary(void)
{
// 读日记功能暂时为空,下节课实现
printf(" 读日记功能正在开发中...\n");
}
第三步:逐行理解新代码
新引入的头文件:
#include <time.h>:提供时间相关的函数,比如获取当前日期时间
时间处理相关:
-
time_t now:一种数据类型,用来存储时间(从1970年1月1日到现在的秒数) -
time(&now):获取当前时间,存入now变量 -
localtime(&now):把秒数转换成我们能看懂的年月日时分秒 -
strftime():把时间格式化成字符串,比如把时间变成 "2026-03-10"
文件操作相关:
-
FILE *fp:文件指针,代表一个打开的文件 -
fopen(filename, "w"):打开文件,"w"表示写入模式 -
fprintf(fp, "..."):向文件写入格式化内容(类似printf,但输出到文件) -
fgets(content, size, stdin):从键盘(stdin)读取一行 -
fputs(content, fp):向文件写入一行字符串 -
fclose(fp):关闭文件,必须做!
用户输入处理:
-
stdin:标准输入,默认就是键盘 -
空行判断:
strcmp(content, "\n") == 0检查用户是否只按了回车
这里相关操作在我前面Linux系统编程的帖子里有,感兴趣可以去看看:
嵌入式学习找工作第八天(Linux系统编程-常用基础命令-1)-CSDN博客
第四步:编译和测试
cpp
# 编译程序
gcc main.c -o diary
# 如果编译出错,仔细看错误信息
# 常见错误:忘记分号、括号不匹配、拼写错误
# 运行写日记功能
./diary write
第五步:用git提交你的代码:
cpp
# 1. 查看修改了哪些文件
git status
# 2. 添加修改的文件到暂存区
git add main.c
# 3. 提交到本地仓库
git commit -m "实现写日记功能:可以保存内容到文件"
# 4. 推送到GitHub
git push
# 5. 查看提交历史
git log --oneline -3
这里显示无法连接远程仓库,可能是虚拟机自身的一些问题,解决办法如下:
cpp
# 1. 请求新的IP地址
sudo dhclient -v
# 2. 测试网络
ping -c 3 8.8.8.8

至此我们完成了日记本中写日记的部分(但实际运行有问题:同一天第二次日记会覆盖第一次日记的内容,我们的日记中不能有空格等小细节)
完善代码
找到 write_diary 函数,删除原来的内容,替换成下面的新代码:
cpp
void write_diary(void)
{
// ---- 第1步:准备变量 ----
char filename[100]; // 文件名缓冲区
char content[1024]; // 日记内容缓冲区
char answer[10]; // 用户回答
time_t now; // 当前时间(秒数)
struct tm *timeinfo; // 时间结构体
// ---- 第2步:获取当前时间并生成文件名 ----
time(&now);
timeinfo = localtime(&now);
strftime(filename, sizeof(filename), "日记_%Y%m%d.txt", timeinfo);
printf("日记文件: %s\n", filename);
// ---- 第3步:检查文件是否已存在 ----
FILE *check_fp = fopen(filename, "r"); // "r" 只读模式打开
if (check_fp != NULL) {
// 文件已存在
fclose(check_fp); // 记得关闭检查用的文件指针
printf("今天已经写过日记了。\n");
printf("是否追加到文件末尾?(y/n): ");
fgets(answer, sizeof(answer), stdin);
// 去掉回答末尾的换行符
answer[strcspn(answer, "\n")] = 0;
if (answer[0] == 'y' || answer[0] == 'Y') {
// 用户选择追加 - 使用追加模式
printf("选择追加模式\n");
} else {
printf(" 操作取消\n");
return; // 退出函数
}
}
// ---- 第4步:打开文件(根据用户选择决定模式) ----
FILE *fp;
if (check_fp == NULL) {
// 文件不存在,用写入模式创建新文件
fp = fopen(filename, "w");
printf("📋 创建新日记文件\n");
} else {
// 文件存在且用户选择追加,用追加模式
fp = fopen(filename, "a");
}
// ---- 第5步:检查文件是否成功打开 ----
if (fp == NULL) {
printf("错误:无法打开文件 %s\n", filename);
printf("可能原因:\n");
printf(" - 磁盘已满\n");
printf(" - 没有写入权限\n");
printf(" - 文件名包含非法字符\n");
return; // 退出函数
}
// ---- 第6步:写入时间标记 ----
char datetime[100];
strftime(datetime, sizeof(datetime), "\n--- %Y年%m月%d日 %H:%M:%S ---\n", timeinfo);
// 如果是新文件,写入标题;如果是追加,写入分隔线
if (check_fp == NULL) {
// 新文件,写入漂亮标题
fprintf(fp, "========== 我的日记 ==========\n");
fprintf(fp, "开始日期: %s", datetime + 1); // +1去掉开头的换行
} else {
// 追加模式,写入时间标记
fputs(datetime, fp);
}
// ---- 第7步:读取用户输入并写入文件 ----
printf("请输入你的日记内容(输入空行结束):\n");
printf("------------------------------\n");
printf("> ");
while (fgets(content, sizeof(content), stdin) != NULL) {
// 检查是否空行(只包含换行符)
if (strcmp(content, "\n") == 0) {
break; // 结束输入
}
// 写入文件
fputs(content, fp);
// 下一个提示符
printf("> ");
}
// ---- 第8步:写入结束标记并关闭文件 ----
fprintf(fp, "\n"); // 最后加一个空行
fclose(fp);
printf("------------------------------\n");
printf("日记已成功保存到: %s\n", filename);
// ---- 第9步:显示文件信息 ----
// 使用 stat 命令获取文件大小(需要 #include <sys/stat.h>)
// 这里我们用简单的方法
printf("文件信息:\n");
printf(" - 位置:%s\n", filename);
printf(" - 时间:%s", datetime + 1);
// 简单的文件大小估算
FILE *size_fp = fopen(filename, "r");
if (size_fp != NULL) {
fseek(size_fp, 0, SEEK_END); // 跳到文件末尾
long size = ftell(size_fp); // 获取当前位置(即文件大小)
fclose(size_fp);
printf(" - 大小:%ld 字节\n", size);
}
}
在文件开头的 #include 部分,添加两个新的头文件:
cpp
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h> // 新增:字符处理函数
#include <sys/stat.h> // 新增:文件状态函数
1. 查看修改
git diff
2. 添加到暂存区
git add main.c
3. 提交
git commit -m "改进写日记功能:支持追加模式,添加错误处理"
4. 推送到GitHub
git push
5. 查看提交历史
git log --oneline -3
第三部分
现在我们的日记本能写能存,但还看不到以前写的日记。我们来实现 读日记功能,学会如何读取文件并显示内容。
第一步:理解我们要实现什么
读日记功能需要:
-
列出所有日记文件
-
让用户选择要看哪一天的日记
-
读取文件内容并显示
-
添加简单的翻页功能(内容太多时一页页看)
第二步:打开文件并编辑
在 show_help 函数上面,添加一个新的辅助函数(用来列出所有日记文件):
cpp
// 新增:列出所有日记文件
void list_diary_files(void)
{
DIR *dir; // 目录指针
struct dirent *entry; // 目录条目
int count = 0; // 文件计数器
// 打开当前目录
dir = opendir(".");
if (dir == NULL) {
printf("无法打开当前目录\n");
return;
}
printf("\n找到的日记文件:\n");
printf("------------------------------\n");
// 遍历目录中的所有文件
while ((entry = readdir(dir)) != NULL) {
// 检查文件名是否以"日记_"开头并以".txt"结尾
if (strncmp(entry->d_name, "日记_", 4) == 0) {
// 检查文件扩展名
char *ext = strrchr(entry->d_name, '.');
if (ext != NULL && strcmp(ext, ".txt") == 0) {
count++;
// 从文件名中提取日期(去掉"日记_"和".txt")
char date_str[20];
strncpy(date_str, entry->d_name + 4, 8); // 取8位日期
date_str[8] = '\0'; // 字符串结束符
// 格式化显示日期(YYYY-MM-DD)
printf("%d. %s-%s-%s\n", count,
date_str, // 年
date_str + 4, // 月
date_str + 6); // 日
}
}
}
closedir(dir); // 关闭目录
if (count == 0) {
printf("还没有写过日记。\n");
}
printf("------------------------------\n");
}
现在,把原来的 read_diary 函数替换成新的实现:
cpp
void read_diary(void)
{
char filename[100];
char choice[10];
int file_number;
int line_count = 0;
int page_size = 20; // 每页显示20行
int current_line = 0;
char buffer[1024];
printf("读日记功能\n");
printf("==============================\n");
// ---- 第1步:列出所有日记文件 ----
list_diary_files();
// ---- 第2步:让用户选择 ----
printf("请选择:\n");
printf("1. 查看今天的日记\n");
printf("2. 查看指定日期的日记\n");
printf("3. 按编号选择日记\n");
printf("请输入数字 (1-3): ");
fgets(choice, sizeof(choice), stdin);
// ---- 第3步:根据用户选择确定要查看的文件 ----
if (choice[0] == '1') {
// 查看今天的日记
time_t now;
struct tm *timeinfo;
time(&now);
timeinfo = localtime(&now);
strftime(filename, sizeof(filename), "日记_%Y%m%d.txt", timeinfo);
} else if (choice[0] == '2') {
// 查看指定日期的日记
printf("请输入日期 (格式: YYYYMMDD,例如 20260311): ");
char date_str[20];
fgets(date_str, sizeof(date_str), stdin);
date_str[strcspn(date_str, "\n")] = 0; // 去掉换行符
snprintf(filename, sizeof(filename), "日记_%s.txt", date_str);
} else if (choice[0] == '3') {
// 按编号选择(需要重新扫描目录)
printf("请输入日记编号: ");
char num_str[10];
fgets(num_str, sizeof(num_str), stdin);
file_number = atoi(num_str); // 字符串转整数
// 重新扫描目录,找到对应编号的文件
DIR *dir = opendir(".");
struct dirent *entry;
int current = 0;
int found = 0;
if (dir != NULL) {
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "日记_", 4) == 0) {
char *ext = strrchr(entry->d_name, '.');
if (ext != NULL && strcmp(ext, ".txt") == 0) {
current++;
if (current == file_number) {
strcpy(filename, entry->d_name);
found = 1;
break;
}
}
}
}
closedir(dir);
}
if (!found) {
printf("无效的编号\n");
return;
}
} else {
printf("无效的选择\n");
return;
}
// ---- 第4步:打开并读取文件 ----
printf("\n正在查看: %s\n", filename);
printf("==============================\n");
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
printf("文件不存在或无法读取!\n");
return;
}
// 先统计文件总行数
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
line_count++;
}
// 回到文件开头
rewind(fp);
// ---- 第5步:分页显示文件内容 ----
printf("总共有 %d 行,每页显示 %d 行\n", line_count, page_size);
printf("按 Enter 键继续,输入 q 退出\n");
printf("------------------------------\n");
int lines_shown = 0;
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
// 显示当前行
printf("%s", buffer);
lines_shown++;
// 如果达到页大小,暂停等待用户输入
if (lines_shown % page_size == 0 && lines_shown < line_count) {
printf("------------------------------\n");
printf("-- 按 Enter 继续,输入 q 退出 --\n");
char cmd[10];
fgets(cmd, sizeof(cmd), stdin);
if (cmd[0] == 'q' || cmd[0] == 'Q') {
printf("退出查看\n");
break;
}
}
}
fclose(fp);
printf("------------------------------\n");
printf("阅读完成,共显示 %d 行\n", lines_shown);
}
第三步:添加一些辅助函数
在 read_diary 函数上面,添加一个格式化显示日记内容的函数:
cpp
// 新增:美化显示日记内容
void display_diary_entry(char *filename)
{
FILE *fp = fopen(filename, "r");
if (fp == NULL) return;
char buffer[1024];
int in_content = 0;
int line_num = 0;
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
line_num++;
// 检查是否是标题行(包含"===")
if (strstr(buffer, "===") != NULL) {
printf("\n\033[1;36m%s\033[0m", buffer); // 青色粗体
}
// 检查是否是时间标记行(包含"---")
else if (strstr(buffer, "---") != NULL) {
printf("\n\033[1;33m%s\033[0m", buffer); // 黄色粗体
}
// 普通内容
else {
printf(" %s", buffer);
}
}
fclose(fp);
}
第四步:编译和测试新功能
编译程序
gcc main.c -o diary
如果编译出错,检查是否包含了 dirent.h
如果警告,可以用 -Wall
先写几篇日记来测试
./diary write # 写第一篇
./diary write # 写第二篇(追加)
测试读日记功能
./diary read
结果展示

第五步:用Git提交
1. 查看修改
git diff
2. 添加到暂存区
git add main.c
3. 提交
git commit -m "实现读日记功能:文件列表、分页显示、搜索功能"
4. 推送到GitHub
git push
5. 查看提交历史
git log --oneline -3
第四部分
第一步:理解我们要实现什么
当用户写日记时:
-
主线程:负责读取用户输入
-
后台线程:每隔几秒自动保存当前已输入的内容
-
共享缓冲区:两个线程共同访问的日记内容
-
互斥锁:防止两个线程同时访问造成数据混乱
第二步:添加必要的头文件
在文件开头添加:
#include <pthread.h> // 线程相关
#include <unistd.h> // sleep函数
第三步:定义全局变量(在线程间共享)
在函数声明之后,添加全局变量:
cpp
// ========== 全局变量(线程间共享)==========
char g_draft_buffer[1024 * 1024]; // 1MB的草稿缓冲区
int g_draft_size = 0; // 当前草稿大小
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥锁
int g_auto_save_enabled = 1; // 自动保存开关
char g_current_filename[256]; // 当前正在写的文件名
第四步:编写自动保存线程函数
在 write_diary 函数之前添加:
cpp
// ========== 自动保存线程函数 ==========
void* auto_save_thread(void *arg)
{
char auto_save_file[300];
printf("自动保存线程已启动\n");
while (g_auto_save_enabled) {
sleep(5); // 每5秒保存一次
// 加锁,防止访问g_draft_buffer时main线程正在修改它
pthread_mutex_lock(&g_mutex);
if (g_draft_size > 0) {
// 生成自动保存文件名
snprintf(auto_save_file, sizeof(auto_save_file),
"%s.autosave", g_current_filename);
FILE *fp = fopen(auto_save_file, "w");
if (fp != NULL) {
fwrite(g_draft_buffer, 1, g_draft_size, fp);
fclose(fp);
// 计算已保存的字符数(粗略估计)
int char_count = 0;
for (int i = 0; i < g_draft_size; i++) {
if (g_draft_buffer[i] == '\n') char_count++;
}
printf("\n[自动保存] 已保存 %d 字节 (%d 行) 到 %s\n",
g_draft_size, char_count, auto_save_file);
printf("> "); // 恢复提示符
fflush(stdout);
}
}
pthread_mutex_unlock(&g_mutex);
}
printf("自动保存线程结束\n");
return NULL;
}
第五步:修改 write_diary 函数(添加多线程)
找到 write_diary 函数,完全替换为这个新版本:
cpp
void write_diary(void)
{
char filename[256] = {0};
char content[1024];
char answer[10] = {0};
time_t now;
struct tm *timeinfo;
pthread_t save_thread;
int c;
// 清空输入缓冲区
while ((c = getchar()) != '\n' && c != EOF);
// 获取当前时间并生成文件名
time(&now);
timeinfo = localtime(&now);
strftime(filename, sizeof(filename), "日记_%Y%m%d.txt", timeinfo);
// 保存文件名到全局变量(供线程使用)
strcpy(g_current_filename, filename);
printf("\n日记文件: %s\n", filename);
printf("自动保存已开启(每5秒保存一次草稿)\n");
// 检查文件是否已存在
FILE *check_fp = fopen(filename, "r");
FILE *fp;
if (check_fp != NULL) {
fclose(check_fp);
printf("今天已经写过日记了。\n");
printf("是否追加到文件末尾?(y/n): ");
if (fgets(answer, sizeof(answer), stdin) == NULL) {
printf("读取输入失败\n");
return;
}
answer[strcspn(answer, "\n")] = 0;
if (answer[0] != 'y' && answer[0] != 'Y') {
printf("操作取消\n");
return;
}
printf("选择追加模式\n");
fp = fopen(filename, "a");
} else {
printf("创建新日记文件\n");
fp = fopen(filename, "w");
}
if (fp == NULL) {
printf("无法打开文件 %s\n", filename);
return;
}
// ========== 新代码:启动自动保存线程 ==========
// 清空草稿缓冲区
memset(g_draft_buffer, 0, sizeof(g_draft_buffer));
g_draft_size = 0;
g_auto_save_enabled = 1;
// 创建自动保存线程
if (pthread_create(&save_thread, NULL, auto_save_thread, NULL) != 0) {
printf("无法创建自动保存线程\n");
g_auto_save_enabled = 0;
} else {
printf("自动保存线程已创建\n");
}
// 写入时间标记
char datetime[100];
strftime(datetime, sizeof(datetime), "\n--- %Y年%m月%d日 %H:%M:%S ---\n", timeinfo);
if (check_fp == NULL) {
fprintf(fp, "========== 我的日记 ==========\n");
fprintf(fp, "开始日期: %s", datetime + 1);
} else {
fputs(datetime, fp);
}
// 读取用户输入
printf("\n请输入你的日记内容(输入空行结束):\n");
printf("------------------------------\n");
printf("> ");
fflush(stdout);
while (fgets(content, sizeof(content), stdin) != NULL) {
// 检查是否空行
if (strcmp(content, "\n") == 0) {
break;
}
// ========== 新代码:加锁更新共享缓冲区 ==========
pthread_mutex_lock(&g_mutex);
// 将新内容添加到草稿缓冲区
int content_len = strlen(content);
if (g_draft_size + content_len < sizeof(g_draft_buffer)) {
strcpy(g_draft_buffer + g_draft_size, content);
g_draft_size += content_len;
}
pthread_mutex_unlock(&g_mutex);
// 写入文件
fputs(content, fp);
printf("> ");
fflush(stdout);
}
// ========== 新代码:停止自动保存线程 ==========
printf("\n\n⏳ 正在停止自动保存线程...\n");
g_auto_save_enabled = 0;
// 等待线程结束
pthread_join(save_thread, NULL);
// ========== 新代码:最后一次保存所有内容 ==========
pthread_mutex_lock(&g_mutex);
if (g_draft_size > 0) {
// 保存完整内容到.autosave文件(备份)
char auto_save_file[300];
snprintf(auto_save_file, sizeof(auto_save_file), "%s.autosave", filename);
FILE *backup_fp = fopen(auto_save_file, "w");
if (backup_fp != NULL) {
fwrite(g_draft_buffer, 1, g_draft_size, backup_fp);
fclose(backup_fp);
printf("最终草稿已备份到 %s\n", auto_save_file);
}
}
pthread_mutex_unlock(&g_mutex);
// 关闭文件
fprintf(fp, "\n");
fclose(fp);
printf("------------------------------\n");
printf("日记已成功保存到: %s\n", filename);
// 显示文件信息
FILE *size_fp = fopen(filename, "r");
if (size_fp != NULL) {
fseek(size_fp, 0, SEEK_END);
long size = ftell(size_fp);
fclose(size_fp);
printf("文件大小:%ld 字节\n", size);
}
}
第六步:编译(注意链接线程库)
cpp
# 编译时必须加上 -lpthread 链接线程库
gcc -g main.c -o diary -lpthread
# 如果有很多警告,可以用 -Wall
gcc -Wall -g main.c -o diary -lpthread
cpp
# 运行写日记
./diary write
# 测试步骤:
1. 开始输入一些文字
2. 等待5秒,看到自动保存提示
3. 继续输入更多文字
4. 再等5秒,看到第二次自动保存
5. 输入空行结束
# 查看自动保存的文件
ls -la *.autosave
cat 日记_20260312.txt.autosave
# 测试恢复草稿
./diary write # 应该提示发现草稿
第七步:提交到Git
cpp
# 查看修改
git diff
# 添加所有文件
git add main.c
# 提交
git commit -m "添加多线程自动保存功能"
# 推送到GitHub
git push
其实还有网络编程,将日记本可以通过网络访问,但暂时还用不到,到这里这个项目暂时结束,后续有补充再来添加。
Linux系统编程在这里暂时告一段落,我没有学清楚的地方我也会在后面的学习中慢慢补充,平时多回来看看复习也很重要。