张银峰的编程课堂

遍历文件夹

为了遍历一个文件夹下的所有文件,我们可以配对使用findfirst/findnext系列函数。在Windows平台下,它们定义于<io.h>,并由 _findfirst/_findnext 宏统一了不同平台下的名称差异性。这个宏根据当前编译环境的配置,映射到32位或64位的实现,比如在32位环境下,使用_findfirst实际调用的是_findfirst32的实现。

#ifdef _USE_32BIT_TIME_T
    #define _findfirst      _findfirst32
    #define _findnext       _findnext32
    #define _findfirsti64   _findfirst32i64
    #define _findnexti64    _findnext32i64
#else
    #define _findfirst      _findfirst64i32
    #define _findnext       _findnext64i32
    #define _findfirsti64   _findfirst64
    #define _findnexti64    _findnext64
#endif

findfirst系列函数的基础原型都是一个待查找的文件名和finddata指针。

intptr_t _findfirst(char const *filename, struct _finddata_t *finddata);

第一个参数为文件名,可以用"*.*"来匹配查找所有文件,也可以用"*.c"来查找反缀名为.c的文件。第二个参数是_finddata_t结构体指针。函数返回一个intptr_t型的变量,这是整型类型的一个别名(typedef),32位环境下它是int,64位下则是__int64,函数执行成功时返回非-1值。

对于 _finddata_t 结构体:

struct _finddata_t
{
    unsigned    attrib;
    time_t      time_create;
    time_t      time_access;
    time_t      time_write;
    _fsize_t    size;
    char        name[260];
};

time_t通常是long或者__int64,这与被选取的findfirst函数相关,_fsize_t则是unsigned long。

结构体的各个成员解释如下:

字段 解释
attrib 所查找文件的属性,一个文件可具有一个或多个属性的组合:
_A_ARCH(存档)
_A_HIDDEN(隐藏)
_A_NORMAL(正常)
_A_RDONLY(只读)
A_SUBDIR(文件夹)
_A_SYSTEM(系统)
time_create 文件的创建时间
time_access 最后一次访问文件的时间
time_write 文件最后被修改的时间
size 文件大小
name 文件名

执行_findfirst函数后,接下来我们需要使用_findnext循环查找文件,其原型如下:

int _findnext(intptr_t handle, struct _finddata_t *finddata);

第一个参数为文件句柄,它是_findfirst函数执行成功时的返回值;第二个参数同样为_finddata_t结构体指针;若查找成功,返回0,失败返回-1。

当遍历完成后,我们需要使用findclose结束本次查找,函数原型如下:

int _findclose(intptr_t handle);

函数只有文件句柄一个参数,它是_findfirst函数执行成功时的返回值,若关闭成功返回0,失败返回-1。

可以看到,整个文件查找过程,是通过一个文件句柄(实则就是一个整数)关联起来的:findfirst用于启动一次查询操作,它返回一个称为句柄的整数,findnext利用这个句柄,得到自己目前的工作环境,继而访问文件,最后通过findclose结束本次查找。

使用流程

#include <stdio.h>
#include <io.h>

int main()
{
    struct _finddata_t fd;
    intptr_t handle;

    // 查找当前目录下的所有文件
    handle = _findfirst("*.*", &fd);

    // 查找成功
    if (handle != -1)
    {
        // 输出找到的第一个文件名
        printf("%s\n", fd.name);

        // 循环查找下一个文件
        while (_findnext(handle, &fd) == 0)
            printf("%s\n", fd.name);
    }

    _findclose(handle);
    return 0;
}

glimix.com

实现dir命令

命令提示符提供了一个dir命令,作用是显示目录中的文件和子目录列表,如:

glimix.com

现在,我们就利用文件查找函数,仿制一个dir命令。

glimix.com

#include <stdio.h>
#include <io.h>
#include <time.h>

void print_fileinfo(struct _finddata_t const *fd)
{
    struct tm t;

    _localtime64_s(&t, &fd->time_create);

    printf("%d/%02d/%02d  %02d:%02d", t.tm_year + 1900, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_sec);

    if (fd->attrib & _A_SUBDIR)
    {
        printf("%9s", "<DIR>");
        printf("%8s", " ");
    }
    else
    {
        printf("%9s", " ");
        printf("%8ld", fd->size);
    }

    printf("  %s", fd->name);
    printf("\n");
}

int main()
{
    int                 files = 0;
    int                 dirs = 0;
    unsigned long       bytes = 0;
    struct _finddata_t  fd;
    intptr_t            handle;

    handle = _findfirst("./*.*", &fd);

    if (handle != -1)
    {
        do
        {
            if (fd.attrib & _A_SUBDIR)
            {
                dirs += 1;
            }
            else
            {
                files += 1;
                bytes += fd.size;
            }

            print_fileinfo(&fd);
        }
        while (_findnext(handle, &fd) == 0);

        _findclose(handle);

        printf("\n");
        printf("\t\t%d 个文件 \t %u 字节\n", files, bytes);
        printf("\t\t%d 个目录\n", dirs);
    }

    return 0;
}

在示例程序环境下,_finddata_t的time_create字段是一个__int64类型的数值,故而通过使用_localtime64_s函数,从它构造出一个时间结体变量,然后格式化输出文件的创建日期。在输出<DIR>标志时,"%9s"限定字符串输出最少9个字符宽并右对齐;"%8ld"指出整数的输出宽度为8个字符并右对齐输出。