一、综合项目实战:实现一个简易的文件管理器

说实话,学完前面那些文件操作函数,你是不是觉得有点散?

fopen、fread、fwrite、remove、rename……每个都会用,但就是不知道它们怎么组合起来干点实事。

嗯,我当年学到这里也有同感。直到我动手写了一个小项目——一个能在命令行里跑的文件管理器。

今天咱们就把这个项目拆开来讲。你跟着做一遍,前面那些零散的知识点就全串起来了。

1.1 项目目标:我们要做什么?

这个文件管理器不需要图形界面,纯命令行操作。它要支持四个基本功能:

  • 浏览:列出当前目录下的文件和子目录
  • 复制:把文件从A处复制到B处
  • 移动:把文件从A处移动到B处(其实就是复制+删除原文件)
  • 删除:删除指定文件

说白了,就是一个简化版的cp、mv、rm、ls命令。但自己写一遍,你对底层原理的理解会深很多。

核心知识点覆盖:

  • fopen / fclose —— 打开和关闭文件
  • fread / fwrite —— 读写文件数据
  • remove —— 删除文件
  • rename —— 移动/重命名文件
  • 目录操作(opendir / readdir)—— 遍历目录

1.2 整体架构:先画个图

动手写代码之前,我习惯先画个结构图。你想想看,一个文件管理器最核心的是什么?

是用户输入命令,程序解析,然后调用对应的功能模块。

简易文件管理器架构图 main() 主循环 解析用户输入 浏览目录 list_dir() 复制文件 copy_file() 移动文件 move_file() 删除文件 delete_file() 用户输入命令 → 解析 → 调用对应函数 → 返回结果

你看,结构其实很简单。主循环等着用户输入,然后根据命令类型,跳到对应的函数去执行。

1.3 核心代码实现

1.3.1 主循环与命令解析

先写主函数。我个人习惯把命令解析放在一个单独的函数里,这样主循环看起来清爽。

#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>

void show_menu() {
    printf("\n===== 简易文件管理器 =====\n");
    printf("1. 浏览当前目录\n");
    printf("2. 复制文件\n");
    printf("3. 移动文件\n");
    printf("4. 删除文件\n");
    printf("5. 退出\n");
    printf("请选择操作: ");
}

int main() {
    int choice;
    char src[256], dst[256];
    
    while (1) {
        show_menu();
        scanf("%d", &choice);
        getchar();  // 吃掉换行符
        
        switch (choice) {
            case 1:
                list_dir(".");
                break;
            case 2:
                printf("源文件路径: ");
                fgets(src, 256, stdin);
                src[strcspn(src, "\n")] = 0;
                printf("目标路径: ");
                fgets(dst, 256, stdin);
                dst[strcspn(dst, "\n")] = 0;
                copy_file(src, dst);
                break;
            case 3:
                printf("源文件路径: ");
                fgets(src, 256, stdin);
                src[strcspn(src, "\n")] = 0;
                printf("目标路径: ");
                fgets(dst, 256, stdin);
                dst[strcspn(dst, "\n")] = 0;
                move_file(src, dst);
                break;
            case 4:
                printf("要删除的文件路径: ");
                fgets(src, 256, stdin);
                src[strcspn(src, "\n")] = 0;
                delete_file(src);
                break;
            case 5:
                printf("再见!\n");
                return 0;
            default:
                printf("无效选项,请重新选择。\n");
        }
    }
    return 0;
}

小技巧: 用 fgets 代替 scanf 读字符串,可以避免缓冲区溢出。再用 strcspn 去掉末尾的换行符,这是我踩过坑之后养成的习惯。

1.3.2 浏览目录:list_dir()

浏览目录要用到 opendir 和 readdir。我记得第一次用这两个函数时,还奇怪为什么目录也要像文件一样打开和关闭。

void list_dir(const char *path) {
    DIR *dir = opendir(path);
    if (dir == NULL) {
        printf("无法打开目录: %s\n", path);
        return;
    }
    
    struct dirent *entry;
    printf("\n当前目录内容:\n");
    printf("----------------------------\n");
    
    while ((entry = readdir(dir)) != NULL) {
        // 跳过 . 和 ..
        if (strcmp(entry->d_name, ".") == 0 || 
            strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        
        // 判断是文件还是目录
        if (entry->d_type == DT_DIR) {
            printf("[目录] %s\n", entry->d_name);
        } else {
            printf("[文件] %s\n", entry->d_name);
        }
    }
    
    closedir(dir);
}

这里有个细节:d_type 字段在有些系统上可能不支持。如果你在 Windows 上编译,可能需要用 stat 函数来判断。嗯,跨平台的问题总是让人头疼。

1.3.3 复制文件:copy_file()

复制文件是核心中的核心。说白了就是:打开源文件读,打开目标文件写,一块一块地搬数据。

void copy_file(const char *src, const char *dst) {
    FILE *fsrc = fopen(src, "rb");
    if (fsrc == NULL) {
        printf("打开源文件失败: %s\n", src);
        return;
    }
    
    FILE *fdst = fopen(dst, "wb");
    if (fdst == NULL) {
        printf("创建目标文件失败: %s\n", dst);
        fclose(fsrc);
        return;
    }
    
    char buffer[4096];
    size_t bytes;
    
    while ((bytes = fread(buffer, 1, sizeof(buffer), fsrc)) > 0) {
        fwrite(buffer, 1, bytes, fdst);
    }
    
    fclose(fsrc);
    fclose(fdst);
    printf("复制完成: %s -> %s\n", src, dst);
}

注意: 缓冲区大小选 4096 字节是有原因的。这是大多数文件系统的块大小,一次读写一个块效率最高。我曾经试过用 1 字节的缓冲区,复制一个大文件慢得让人崩溃。

1.3.4 移动文件:move_file()

移动文件有两种思路:

  • 如果源文件和目标文件在同一个文件系统内,直接用 rename() 函数,速度快
  • 如果跨文件系统,rename 会失败,这时候就得先复制再删除
void move_file(const char *src, const char *dst) {
    // 先尝试 rename,如果成功就完事了
    if (rename(src, dst) == 0) {
        printf("移动完成: %s -> %s\n", src, dst);
        return;
    }
    
    // rename 失败,可能是跨文件系统
    printf("尝试跨文件系统移动...\n");
    copy_file(src, dst);
    if (remove(src) == 0) {
        printf("移动完成: %s -> %s\n", src, dst);
    } else {
        printf("移动失败,目标文件可能已创建但源文件未删除\n");
    }
}

你想想看,为什么先尝试 rename?因为 rename 只是修改目录项,不搬数据,速度极快。只有跨文件系统时才需要走复制+删除的笨办法。

1.3.5 删除文件:delete_file()

删除文件最简单,一个 remove 搞定。但我建议加个确认步骤,防止手滑。

void delete_file(const char *path) {
    char confirm;
    printf("确定要删除 %s 吗?(y/n): ", path);
    scanf("%c", &confirm);
    
    if (confirm == 'y' || confirm == 'Y') {
        if (remove(path) == 0) {
            printf("删除成功: %s\n", path);
        } else {
            printf("删除失败: %s\n", path);
        }
    } else {
        printf("取消删除\n");
    }
}

避坑指南: 我曾经在生产环境上写过一个批量删除脚本,忘了加确认步骤。结果一个路径写错了,把整个项目目录给删了……从那以后,凡是删除操作,我必加确认。

1.4 完整代码整合

把上面几个函数拼到一起,就是一个能跑的文件管理器了。我建议你亲手敲一遍,不要复制粘贴。敲代码的过程,就是理解的过程。

编译命令:

gcc file_manager.c -o file_manager

运行效果大概是这样:

===== 简易文件管理器 =====
1. 浏览当前目录
2. 复制文件
3. 移动文件
4. 删除文件
5. 退出
请选择操作: 1

当前目录内容:
----------------------------
[目录] project
[文件] main.c
[文件] readme.txt

1.5 扩展思考

这个版本只支持单文件操作。如果你想挑战自己,可以试试加上:

  • 递归复制目录(需要遍历子目录)
  • 通配符支持(比如 *.txt)
  • 进度条显示(复制大文件时很有用)

嗯,这些功能我在实际项目中都加过。等你把基础版本跑通了,咱们再聊进阶玩法。


公众号:蓝海资料掘金营,微信deep3321