首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

VC++ 遞歸遍歷目錄下所有文件與子目錄完整實現

     編輯:VC 教學

核心 API:FindFirstFile / FindNextFile

Windows API 提供 FindFirstFile / FindNextFile / FindClose 三件套實現目錄枚舉。關鍵結構 WIN32_FIND_DATA 包含文件名、屬性、時間戳、大小等信息。

標準遞歸遍歷代碼


#include 
#include 
#include 
#include 

void TraverseDirectory(const std::string& rootPath, std::vector& files, std::vector& dirs)
{
    // 構建搜索模式:目錄路徑 + "\\*"
    std::string searchPath = rootPath;
    if (searchPath.back() != '\\') searchPath += '\\';
    searchPath += '*';

    WIN32_FIND_DATAA findData;
    HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData);
    
    if (hFind == INVALID_HANDLE_VALUE)
    {
        DWORD err = GetLastError();
        if (err != ERROR_FILE_NOT_FOUND)
            std::cerr << "FindFirstFile failed: " << err << " for " << searchPath << std::endl;
        return;
    }

    do
    {
        // 跳過 "." 和 ".."
        if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0)
            continue;

        std::string fullPath = rootPath + "\\" + findData.cFileName;

        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            // 子目錄:記錄並遞歸
            dirs.push_back(fullPath);
            TraverseDirectory(fullPath, files, dirs);
        }
        else
        {
            // 文件:記錄
            files.push_back(fullPath);
        }
    }
    while (FindNextFileA(hFind, &findData));

    DWORD err = GetLastError();
    FindClose(hFind);
    
    if (err != ERROR_NO_MORE_FILES)
        std::cerr << "FindNextFile error: " << err << std::endl;
}

// 使用示例
int main()
{
    std::vector files, dirs;
    TraverseDirectory("C:\\MyProject", files, dirs);
    
    std::cout << "Directories (" << dirs.size() << "):\n";
    for (auto& d : dirs) std::cout << "  DIR: " << d << "\n";
    
    std::cout << "\nFiles (" << files.size() << "):\n";
    for (auto& f : files) std::cout << "  FILE: " << f << "\n";
    
    return 0;
}

MFC / CString 版本


void TraverseDirectoryMFC(const CString& strRoot, CStringArray& arrFiles, CStringArray& arrDirs)
{
    CString strFind = strRoot;
    if (strFind.Right(1) != _T("\\")) strFind += _T("\\");
    strFind += _T("*.*");

    WIN32_FIND_DATA findData;
    HANDLE hFind = FindFirstFile(strFind, &findData);
    if (hFind == INVALID_HANDLE_VALUE) return;

    do
    {
        if (_tcscmp(findData.cFileName, _T(".")) == 0 || _tcscmp(findData.cFileName, _T("..")) == 0)
            continue;

        CString strFull = strRoot + _T("\\") + findData.cFileName;

        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            arrDirs.Add(strFull);
            TraverseDirectoryMFC(strFull, arrFiles, arrDirs);
        }
        else
        {
            arrFiles.Add(strFull);
        }
    }
    while (FindNextFile(hFind, &findData));

    FindClose(hFind);
}

關鍵細節與避坑指南

1. 長路徑支持(>260 字符)

Windows 默認限制 MAX_PATH (260)。解決方案:

  • 路徑前綴 \\\\?\\,如 \\\\?\\C:\\VeryLongPath
  • 使用 Unicode 版 API(FindFirstFileW)配合 \\\\?\\ 前綴
  • Windows 10 1607+ 可通過組策略/注冊表啟用長路徑支持

2. 符號鏈接/接合點處理

  • FILE_ATTRIBUTE_REPARSE_POINT 標識重解析點
  • 遞歸進入可能導致無限循環,需記錄已訪問路徑(std::set/CMap)去重
  • 或使用 GetFinalPathNameByHandle 獲取真實路徑比對

3. 權限與錯誤處理

  • FindFirstFile 失敗可能因權限不足(ERROR_ACCESS_DENIED),應捕獲繼續而非中止
  • 網絡路徑可能超時,需設置超時機制或異步處理

4. 性能優化

  • 大量文件時避免頻繁內存分配:預分配 vector::reserve 或使用 CStringArray
  • 僅需文件名時,WIN32_FIND_DATA 已包含,無需額外 GetFileAttributes
  • 多線程並行遍歷:目錄樹分支獨立,可用線程池加速(注意同步輸出)

5. 替代方案:CFileFind (MFC)


void TraverseWithCFileFind(const CString& root, CStringArray& files, CStringArray& dirs)
{
    CFileFind finder;
    CString strWild = root + _T("\\*.*");
    BOOL bFound = finder.FindFile(strWild);
    
    while (bFound)
    {
        bFound = finder.FindNextFile();
        if (finder.IsDots()) continue;
        
        CString full = finder.GetFilePath();
        if (finder.IsDirectory())
        {
            dirs.Add(full);
            TraverseWithCFileFind(full, files, dirs);
        }
        else
        {
            files.Add(full);
        }
    }
    finder.Close();
}

CFileFind 封裝了 Unicode/ANSI、錯誤處理,代碼更簡潔但性能略低於原生 API。

實際應用場景擴展

場景關鍵邏輯
文件搜索工具遍歷時匹配文件名/內容(StrStrI/正則/全文索引)
磁盤空間分析累加文件大小(findData.nFileSizeLow/High),按目錄/類型統計
批量重命名/修改屬性遍歷收集列表 → 用戶確認 → 批量 MoveFile/SetFileAttributes
備份/同步工具對比源目標 FILETIME/大小/哈希,增量複製
惡意軟件掃描計算文件哈希(MD5/SHA256)匹配特徵庫

現代 C++17 替代方案:std::filesystem

Visual Studio 2017 15.7+ 完整支持 <filesystem>:


#include 
#include 

namespace fs = std::filesystem;

void ModernTraverse(const fs::path& root)
{
    try
    {
        for (auto& entry : fs::recursive_directory_iterator(root, 
            fs::directory_options::skip_permission_denied))
        {
            if (entry.is_directory())
                std::cout << "DIR:  " << entry.path() << "\n";
            else
                std::cout << "FILE: " << entry.path() << " (" << entry.file_size() << " bytes)\n";
        }
    }
    catch (const fs::filesystem_error& e)
    {
        std::cerr << "FS Error: " << e.what() << "\n";
    }
}

優勢:跨平台、異常安全、範圍-based for 循環、自動處理符號鏈接循環、支持 UTF-8 路徑。建議新項目優先使用。

總結

目錄遞歸遍歷是系統級編程基礎技能。掌握 FindFirstFile/FindNextFile 底層 API 細節(屬性判斷、錯誤碼處理、長路徑、重解析點),並了解 MFC CFileFind 封裝與現代 std::filesystem 替代方案,可根據項目約束(C++ 版本、跨平台需求、性能敏感度)選擇最佳實現。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved