程序的目的是读取指定目录下的 .txt 文件名,构造一个字符串数组,然后按照文件名中的数字顺序排序并打印结果。整个实现采用了更接近 C 的风格:
- 使用
char*和char** - 使用
malloc/realloc/free - 使用
qsort排序 - 使用目录读取 API
opendir,readdir,closedir
int main() {
char **names = NULL;
int n = 0;
if (GetAllFileNames(TXT_DIR, &names, &n) != 0) {
fprintf(stderr, "error opening dir: %s\n", TXT_DIR);
return 1;
}
printf("total %d files\n", n);
qsort(names, n, sizeof(char *), CompareByFrameNumber);
for (int i = 0; i < n; i++) {
printf("%s\n", names[i]);
}
FreeNames(names, n);
return 0;
}- 定义
names为char **,用于保存动态扩展的文件名数组。 - 定义
n为文件数量。 - 调用
GetAllFileNames递归读取目录,获取所有.txt文件相对于TXT_DIR的路径,并统计数量。 - 调用
qsort对字符串数组进行排序。 - 遍历排序结果并打印每个文件名。
- 调用
FreeNames释放所有动态分配的内存。
- 使用
char **可以与 C 风格字符串数组兼容,而且适合qsort的元素类型。 GetAllFileNames返回数组和数量,避免了全局变量,增强函数可重用性。qsort是标准 C 的排序接口,和char*数组直接配合。- 遍历打印后释放内存,避免内存泄漏。
int GetAllFileNames(const char *dirpath, char ***names, int *count) {
DIR *dir = opendir(dirpath);
if (dir == NULL) {
return -1;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0) {
continue;
}
char full_path[4096];
snprintf(full_path, sizeof(full_path), "%s/%s", dirpath, entry->d_name);
struct stat stat_buf;
if (stat(full_path, &stat_buf) == -1) {
continue;
}
if (S_ISDIR(stat_buf.st_mode)) {
GetAllFileNames(full_path, names, count);
} else {
if (!IsTxtFile(entry->d_name)) {
continue;
}
const char *relative = full_path + strlen(TXT_DIR);
char *copy = (char *)malloc(strlen(relative) + 1);
if (copy == NULL) {
closedir(dir);
return -1;
}
strcpy(copy, relative);
char **tmp =
(char **)realloc(*names, (*count + 1) * sizeof(char *));
if (tmp == NULL) {
free(copy);
closedir(dir);
return -1;
}
*names = tmp;
(*names)[*count] = copy;
(*count)++;
}
}
closedir(dir);
return 0;
}- 打开传入目录
dirpath。 - 遍历目录项
readdir(dir)。 - 过滤掉
.和..目录。 - 计算当前目录项的完整路径
full_path。 - 通过
stat判断该目录项是目录还是普通文件。 - 如果是目录,则递归调用自身继续读取子目录。
- 如果是普通文件,则检查是否以
.txt结尾。 - 将符合条件的文件路径转换为相对路径并动态拷贝到内存。
- 使用
realloc扩展names数组并追加新字符串。
opendir/readdir/closedir是 POSIX 读取目录的标准方式。- 过滤
./..防止无限递归。 stat用于区分目录和文件,避免误把目录名加入结果。- 递归子目录可以支持多层目录结构下查找
.txt文件。 realloc用于动态扩展names数组,逐个追加文件名,避免预先估算数量。- 通过
strcpy复制字符串,确保names中保存的每个项都是独立可释放的内存。
while ((entry = readdir(dir)) != NULL)- 作用:遍历当前目录中每个条目。
- 为什么:这是读取目录内容的标准循环模式。
if (S_ISDIR(stat_buf.st_mode)) { GetAllFileNames(full_path, names, count); }- 作用:对目录项是目录时递归处理。
- 为什么:需要递归进入子目录查找
.txt文件。
char **tmp = (char **)realloc(*names, (*count + 1) * sizeof(char *));- 作用:扩大指针数组以保存新文件路径。
- 为什么:事先不知道文件数量,动态扩展是最直接的方式。
int IsTxtFile(const char *name) {
size_t len = strlen(name);
return len > 4 && strcmp(name + len - 4, ".txt") == 0;
}- 先计算文件名长度。
- 判断长度是否大于 4,以防越界。
- 比较文件名最后 4 个字符是否等于
.txt。
- 直接使用
strlen和strcmp,避免复杂字符串处理。 - 这个判断足够简单且高效,适合只检查后缀的需求。
void FreeNames(char **names, int count) {
for (int i = 0; i < count; i++) {
free(names[i]);
}
free(names);
}- 遍历
names数组的每个字符串指针并释放对应内存。 - 最后释放
names数组本身。
GetAllFileNames中对每个文件名都使用了malloc分配,因此必须逐个释放。names数组本身也由realloc分配,需要额外释放。- 这是标准 C 风格的内存管理方式。
int CompareByFrameNumber(const void *a, const void *b) {
const char *const *pa = (const char *const *)a;
const char *const *pb = (const char *const *)b;
const char *sa = *pa;
const char *sb = *pb;
const char *dotA = strrchr(sa, '.');
const char *dotB = strrchr(sb, '.');
if (dotA == NULL || dotB == NULL || dotA - sa < 4 || dotB - sb < 4) {
return strcmp(sa, sb);
}
char bufA[5] = {0};
char bufB[5] = {0};
memcpy(bufA, dotA - 4, 4);
memcpy(bufB, dotB - 4, 4);
int numA = atoi(bufA);
int numB = atoi(bufB);
if (numA < numB)
return -1;
if (numA > numB)
return 1;
return strcmp(sa, sb);
}- 将
qsort传入的void *指针转换为char *const *,得到待比较的字符串指针。 - 查找文件名中最后一个
.位置。 - 如果没有找到后缀或文件名太短,则退回到普通字符串比较
strcmp。 - 否则提取后缀前 4 个字符作为数字字符串。
- 将这 4 个字符转换成整数,并按数字大小比较。
- 如果数字相同,则再按字符串全名做次级比较。
qsort需要int (*compar)(const void *, const void *)的签名。- 直接对
char*数组排序时,比较函数必须解引用两层指针。 - 目标排序规则是按
frame_XXXX.txt中的数字顺序,而不是纯文本顺序。 - 先比较数字可以得到期望的帧序;同数字时按完整文件名比较保证稳定性。
main是程序入口,负责初始化、获取文件名、排序、打印、清理。GetAllFileNames负责递归目录遍历和结果收集,使用动态数组扩展。IsTxtFile负责判定文件是否以.txt结尾。FreeNames负责释放所有动态内存。CompareByFrameNumber负责按照文件名里的数字排序,以保证类似frame_0010.txt排在frame_0002.txt之后。
本程序的设计思想是:
- 以 C 风格实现目录遍历和字符串操作,减少 C++ 特性依赖。
- 通过
qsort和指针数组完成排序。 - 通过显式内存分配与释放控制生命周期。