嵌入式学习找工作第十七天--第二个项目(命令行日记本)

这个项目更加着重之前学的基础命令、文件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博客

这篇帖子的项目背景就是这个项目;

第二部分

我们继续。现在来实现真正的写日记功能。你会学到:

  • 如何创建和写入文件

  • 如何获取当前时间

  • 如何处理用户输入

第一步:理解我们要做什么

写日记功能需要:

  1. 获取当前日期(用来生成文件名,比如 日记_20260310.txt

  2. 让用户输入日记内容

  3. 把内容保存到文件

第二步:打开文件并编辑

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

第三部分

现在我们的日记本能写能存,但还看不到以前写的日记。我们来实现 读日记功能,学会如何读取文件并显示内容。

第一步:理解我们要实现什么

读日记功能需要:

  1. 列出所有日记文件

  2. 让用户选择要看哪一天的日记

  3. 读取文件内容并显示

  4. 添加简单的翻页功能(内容太多时一页页看)

第二步:打开文件并编辑

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

第四部分

第一步:理解我们要实现什么

当用户写日记时:

  1. 主线程:负责读取用户输入

  2. 后台线程:每隔几秒自动保存当前已输入的内容

  3. 共享缓冲区:两个线程共同访问的日记内容

  4. 互斥锁:防止两个线程同时访问造成数据混乱

第二步:添加必要的头文件

在文件开头添加:

复制代码
#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系统编程在这里暂时告一段落,我没有学清楚的地方我也会在后面的学习中慢慢补充,平时多回来看看复习也很重要。

相关推荐
71-32 小时前
Android studio中真机操作
android·笔记·学习·其他·android studio
·中年程序渣·2 小时前
Spring AI Alibaba入门学习(五)
人工智能·学习
AnalogElectronic3 小时前
RP2040学习4,LED点亮,OLED显示,DHT11温湿度传感器数据读取
单片机·嵌入式硬件·学习
adore.9683 小时前
3.15 复试学习
学习
不光头强3 小时前
jwt学习
java·大数据·学习
952363 小时前
MySQL - 集群架构与实践
数据库·学习·mysql·架构
客卿1234 小时前
岛屿问题--bfs的应用--二维网络题目学习
学习·算法·宽度优先
北岛寒沫4 小时前
北京大学国家发展研究院 中国经济专题 课程笔记(第一课 绪论)
经验分享·笔记·学习
王的宝库4 小时前
Go 语言:结构体:定义、初始化、方法、组合、Tag、对齐
开发语言·后端·学习·golang