C++递归遍历文件夹内的所有文件夹与文件的详细实现,基于VC++环境,使用Windows API(FindFirstFile和FindNextFile)递归遍历指定文件夹内的所有子文件夹和文件。本文提供完整源码、功能介绍、实现细节、示例用法、注意事项以及扩展建议,适用于Windows平台的C++开发。结合您之前提到的VC++开发背景(如分层窗口、UAC等),代码注重健壮性、错误处理和调试支持。


一、功能介绍

在C++开发中,遍历文件夹是常见需求,例如文件搜索、批量处理或资源管理。本程序的核心功能包括:

  1. 递归遍历:

    • 遍历指定文件夹及其所有子文件夹,列出每个文件夹和文件的路径。

    • 支持深度优先的递归方式,确保遍历所有层级。

  2. 文件和文件夹区分:

    • 通过WIN32_FIND_DATA的属性区分文件夹和文件。

    • 可扩展为过滤特定文件类型(如.txt)或文件夹。

  3. 错误处理:

    • 处理无效路径、权限不足等异常。

    • 提供日志记录,便于调试。

  4. 源码与实战:

    • 提供完整VC++代码,支持Windows XP及以上版本。

    • 示例展示如何遍历C:\Test目录并输出结果。


二、实现细节

1. Windows API简介

Windows提供FindFirstFile和FindNextFile用于遍历文件夹内容:

  • FindFirstFile:

    • 原型:

      cpp

      HANDLE FindFirstFileW(
        LPCWSTR lpFileName,            // 搜索路径(如L"C:\\Test\\*")
        LPWIN32_FIND_DATAW lpFindFileData // 文件信息
      );
    • 返回第一个匹配文件的句柄,填充WIN32_FIND_DATA结构。

  • FindNextFile:

    • 继续查找下一个文件。

  • WIN32_FIND_DATA:

    • 包含文件名(cFileName)、属性(dwFileAttributes)等。

    • FILE_ATTRIBUTE_DIRECTORY标志区分文件夹。

2. 递归遍历逻辑

  1. 使用FindFirstFile查找指定路径下的第一个文件/文件夹。

  2. 循环调用FindNextFile获取后续项。

  3. 对每个文件夹(排除.和..),递归调用遍历函数。

  4. 对每个文件,记录路径或执行处理。

  5. 关闭查找句柄(FindClose)。

3. VC++环境配置

  • 头文件:<windows.h>、<string>、<iostream>。

  • 字符集:建议使用Unicode(宽字符,wchar_t),支持中文路径。

  • 链接库:无需额外库,Windows API由kernel32.lib提供。

  • 调试工具:Visual Studio、Process Monitor(监控文件访问)。


三、完整源码

以下是VC++实现的递归遍历文件夹的完整代码,包含错误处理、日志记录和注释。

cpp

#include <windows.h>
#include <string>
#include <iostream>
#include <fstream>

// 日志记录
void LogMessage(const std::wstring& message) {
    std::wofstream log("traverse_log.txt", std::ios::app);
    log << L"[" << std::time(nullptr) << L"] " << message << std::endl;
}

// 递归遍历文件夹
void TraverseDirectory(const std::wstring& dirPath, int depth = 0) {
    // 构造搜索路径(如L"C:\\Test\\*")
    std::wstring searchPath = dirPath + L"\\*";
    WIN32_FIND_DATAW findData;
    HANDLE hFind = FindFirstFileW(searchPath.c_str(), &findData);

    if (hFind == INVALID_HANDLE_VALUE) {
        DWORD error = GetLastError();
        LogMessage(L"FindFirstFile failed for " + dirPath + L": Error " + std::to_wstring(error));
        return;
    }

    // 缩进表示层级
    std::wstring indent(depth * 2, L' ');

    do {
        // 跳过 . 和 ..
        if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0) {
            continue;
        }

        // 构造完整路径
        std::wstring fullPath = dirPath + L"\\" + findData.cFileName;

        // 判断是文件夹还是文件
        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            // 文件夹
            std::wcout << indent << L"[DIR] " << fullPath << std::endl;
            LogMessage(indent + L"[DIR] " + fullPath);
            // 递归遍历子文件夹
            TraverseDirectory(fullPath, depth + 1);
        }
        else {
            // 文件
            std::wcout << indent << L"[FILE] " << fullPath << std::endl;
            LogMessage(indent + L"[FILE] " + fullPath);
        }
    } while (FindNextFileW(hFind, &findData));

    // 检查FindNextFile失败原因
    DWORD error = GetLastError();
    if (error != ERROR_NO_MORE_FILES) {
        LogMessage(L"FindNextFile failed for " + dirPath + L": Error " + std::to_wstring(error));
    }

    // 关闭查找句柄
    FindClose(hFind);
}

int wmain() {
    // 测试目录(替换为实际路径)
    std::wstring rootDir = L"C:\\Test";

    // 检查目录是否存在
    DWORD attr = GetFileAttributesW(rootDir.c_str());
    if (attr == INVALID_FILE_ATTRIBUTES || !(attr & FILE_ATTRIBUTE_DIRECTORY)) {
        std::wcerr << L"Invalid directory: " << rootDir << std::endl;
        LogMessage(L"Invalid directory: " + rootDir);
        return 1;
    }

    std::wcout << L"Traversing directory: " << rootDir << std::endl;
    LogMessage(L"Start traversing: " + rootDir);

    // 开始遍历
    TraverseDirectory(rootDir);

    std::wcout << L"Traversal completed." << std::endl;
    LogMessage(L"Traversal completed");

    return 0;
}

四、代码说明

  1. TraverseDirectory:

    • 核心递归函数,接受目录路径和深度参数(用于缩进显示)。

    • 使用FindFirstFileW和FindNextFileW遍历目录内容。

    • 排除.和..,避免无限递归。

    • 对文件夹递归调用,对文件直接输出。

  2. LogMessage:

    • 记录操作日志到traverse_log.txt,包括路径、错误码等。

    • 便于调试和问题定位。

  3. 错误处理:

    • 检查FindFirstFile和FindNextFile的返回值。

    • 记录GetLastError返回的错误码(如ERROR_ACCESS_DENIED)。

    • 验证根目录有效性。

  4. 输出格式:

    • 使用缩进表示层级,格式如:

      [DIR] C:\Test
        [DIR] C:\Test\SubFolder
          [FILE] C:\Test\SubFolder\file.txt
        [FILE] C:\Test\document.pdf
  5. 运行效果:

    • 遍历C:\Test及其子目录,控制台输出路径。

    • 生成traverse_log.txt记录日志。

    • 若路径无效或无权限,输出错误并记录。

  6. 编译与运行:

    • Visual Studio:新建Win32控制台应用,设置字符集为Unicode。

    • 替换rootDir为实际路径(如C:\Test需存在)。

    • 可能需管理员权限访问受限目录。


五、实战分析

案例背景

某C++程序需遍历项目文件夹,统计所有.cpp和.h文件,用于代码分析。但在遍历C:\Program Files时,部分子目录因权限不足失败,且未发现某些隐藏文件。

问题与解决方案

  1. 问题1:权限不足(ERROR_ACCESS_DENIED)

    • 分析:使用Process Monitor监控文件访问,确认C:\Program Files需要管理员权限。

    • 解决:

      • 以管理员身份运行程序(参考UAC处理)。

      • 添加Manifest请求权限(见第10篇):

        xml

        <requestedExecutionLevel level="requireAdministrator" />
  2. 问题2:未发现隐藏文件

    • 分析:默认不遍历隐藏文件/文件夹。

    • 解决:

      • 检查dwFileAttributes的FILE_ATTRIBUTE_HIDDEN:

        cpp

        if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)) {
            continue; // 跳过隐藏文件/文件夹
        }
      • 移除此判断以包含隐藏项。

  3. 问题3:仅需.cpp和.h文件

    • 解决:

      • 添加文件扩展名过滤:

        cpp

        std::wstring ext = std::wstring(findData.cFileName).substr(4);
        std::transform(ext.begin(), ext.end(), ext.begin(), ::towlower);
        if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
            (ext == L".cpp" || ext == L".h")) {
            std::wcout << indent << L"[FILE] " << fullPath << std::endl;
            LogMessage(indent + L"[FILE] " + fullPath);
        }

结果

  • 成功统计.cpp和.h文件,输出示例:

    [DIR] C:\Project
      [FILE] C:\Project\main.cpp
      [FILE] C:\Project\utils.h
      [DIR] C:\Project\SubFolder
        [FILE] C:\Project\SubFolder\test.cpp
  • 日志记录权限错误和遍历路径,便于调试。


六、扩展功能

  1. 文件过滤:

    • 支持通配符(如*.txt)或正则表达式。

    • 示例:使用std::wregex匹配扩展名。

  2. 统计信息:

    • 计算文件总数、大小或修改时间:

      cpp

      LARGE_INTEGER fileSize;
      fileSize.LowPart = findData.nFileSizeLow;
      fileSize.HighPart = findData.nFileSizeHigh;
      std::wcout << indent << L"[FILE] " << fullPath << L" (" << fileSize.QuadPart << L" bytes)" << std::endl;
  3. 多线程遍历:

    • 将子文件夹遍历放入线程池,提升性能:

      cpp

      #include <thread>
      std::vector<std::thread> threads;
      if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
          threads.emplace_back(TraverseDirectory, fullPath, depth + 1);
      }
      for (auto& t : threads) {
          t.join();
      }
  4. 跨平台支持:

    • 使用std::filesystem(C++17)替代Windows API:

      cpp

      #include <filesystem>
      namespace fs = std::filesystem;
      for (const auto& entry : fs::recursive_directory_iterator(rootDir)) {
          std::wcout << (entry.is_directory() ? L"[DIR] " : L"[FILE] ") << entry.path() << std::endl;
      }

七、注意事项

  1. 字符编码:

    • 使用宽字符(wchar_t)支持中文路径。

    • 确保Visual Studio项目设置Unicode字符集。

  2. 权限问题:

    • 受限目录(如C:\Windows)需管理员权限。

    • 参考第10篇UAC处理方案。

  3. 资源管理:

    • 确保调用FindClose释放句柄,避免泄漏。

    • 使用RAII封装查找句柄:

      cpp

      struct FindHandle {
          HANDLE h;
          FindHandle(HANDLE handle) : h(handle) {}
          ~FindHandle() { if (h != INVALID_HANDLE_VALUE) FindClose(h); }
      };
  4. 错误码:

    • 常见错误:

      • ERROR_ACCESS_DENIED(5):权限不足。

      • ERROR_PATH_NOT_FOUND(3):路径无效。

    • 使用GetLastError诊断。


八、资料扩展

  • MSDN文档:

    • docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew:FindFirstFile。

    • docs.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-win32_find_dataw:WIN32_FIND_DATA。

  • 《Windows核心编程》:

    • 第12章(文件系统):讲解文件和目录操作。

  • C++17 Filesystem:en.cppreference.com/w/cpp/filesystem,跨平台替代方案。

  • CodeProject:搜索“Windows file traversal”获取更多教程。


九、总结

本篇提供了VC++递归遍历文件夹的完整源码,基于Windows API实现,包含错误处理、日志记录和扩展性设计。结合实战案例,展示了如何解决权限、隐藏文件和文件过滤问题。代码适用于文件管理、资源搜索等场景,与之前的VC++主题(如UAC、分层窗口)无缝衔接。如果你有特定的遍历需求(如过滤条件、性能优化)或调试问题,请提供更多细节,我可以进一步定制代码或提供指导!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐