一、综合项目实战:实现一个简易的文件管理器
说实话,学完前面那些文件操作函数,你是不是觉得有点散?
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 整体架构:先画个图
动手写代码之前,我习惯先画个结构图。你想想看,一个文件管理器最核心的是什么?
是用户输入命令,程序解析,然后调用对应的功能模块。
你看,结构其实很简单。主循环等着用户输入,然后根据命令类型,跳到对应的函数去执行。
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