Skip to content

Latest commit

 

History

History
239 lines (189 loc) · 7.77 KB

File metadata and controls

239 lines (189 loc) · 7.77 KB

ConvertFile 程序报告

概述

程序的目的是读取指定目录下的 .txt 文件名,构造一个字符串数组,然后按照文件名中的数字顺序排序并打印结果。整个实现采用了更接近 C 的风格:

  • 使用 char*char**
  • 使用 malloc / realloc / free
  • 使用 qsort 排序
  • 使用目录读取 API opendir, readdir, closedir

main 函数

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;
}

main 实现了什么

  • 定义 nameschar **,用于保存动态扩展的文件名数组。
  • 定义 n 为文件数量。
  • 调用 GetAllFileNames 递归读取目录,获取所有 .txt 文件相对于 TXT_DIR 的路径,并统计数量。
  • 调用 qsort 对字符串数组进行排序。
  • 遍历排序结果并打印每个文件名。
  • 调用 FreeNames 释放所有动态分配的内存。

为什么这样实现

  • 使用 char ** 可以与 C 风格字符串数组兼容,而且适合 qsort 的元素类型。
  • GetAllFileNames 返回数组和数量,避免了全局变量,增强函数可重用性。
  • qsort 是标准 C 的排序接口,和 char* 数组直接配合。
  • 遍历打印后释放内存,避免内存泄漏。

GetAllFileNames 函数

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;
}

GetAllFileNames 实现了什么

  • 打开传入目录 dirpath
  • 遍历目录项 readdir(dir)
  • 过滤掉 ... 目录。
  • 计算当前目录项的完整路径 full_path
  • 通过 stat 判断该目录项是目录还是普通文件。
  • 如果是目录,则递归调用自身继续读取子目录。
  • 如果是普通文件,则检查是否以 .txt 结尾。
  • 将符合条件的文件路径转换为相对路径并动态拷贝到内存。
  • 使用 realloc 扩展 names 数组并追加新字符串。

GetAllFileNames 为什么这样实现

  • 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 *));
    • 作用:扩大指针数组以保存新文件路径。
    • 为什么:事先不知道文件数量,动态扩展是最直接的方式。

IsTxtFile 函数

int IsTxtFile(const char *name) {
    size_t len = strlen(name);
    return len > 4 && strcmp(name + len - 4, ".txt") == 0;
}

IsTxtFile 实现了什么

  • 先计算文件名长度。
  • 判断长度是否大于 4,以防越界。
  • 比较文件名最后 4 个字符是否等于 .txt

为什么这么实现

  • 直接使用 strlenstrcmp,避免复杂字符串处理。
  • 这个判断足够简单且高效,适合只检查后缀的需求。

FreeNames 函数

void FreeNames(char **names, int count) {
    for (int i = 0; i < count; i++) {
        free(names[i]);
    }
    free(names);
}

FreeNames 实现了什么

  • 遍历 names 数组的每个字符串指针并释放对应内存。
  • 最后释放 names 数组本身。

为什么这么实现

  • GetAllFileNames 中对每个文件名都使用了 malloc 分配,因此必须逐个释放。
  • names 数组本身也由 realloc 分配,需要额外释放。
  • 这是标准 C 风格的内存管理方式。

CompareByFrameNumber 函数

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);
}

CompareByFrameNumber 实现了什么

  • 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 和指针数组完成排序。
  • 通过显式内存分配与释放控制生命周期。