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

C++ 可控遞歸深度文件搜索類實現:回調驅動、Windows API、靈活過濾

     編輯:關於C語言

設計目標

  • 遍歷目錄樹,支援最大遞歸深度限制 (防止過深/無限循環)。
  • 採用回調函數 分離「遍歷邏輯」與「業務處理」 (如刪除、複製、統計)。
  • 提供目錄完成回調,便於後序操作 (如刪除空目錄)。
  • 基於 Windows API (FindFirstFile/FindNextFile) 實現,效率高。

核心類設計


// SearchFilesUnit.h
#ifndef SEARCH_FILES_UNIT_H
#define SEARCH_FILES_UNIT_H

#include 
#include 

// 回調類型定義
typedef void (__stdcall *PSEARCHCALLBACK)(LPCTSTR);      // 發現文件/目錄
typedef void (__stdcall *PDIRCOMPLETEDCALLBACK)(LPCTSTR); // 目錄遍歷完成

class CSearchFile {
public:
    // 無深度限制構造函數
    CSearchFile() : m_depthLevel(-1) {}

    // 指定最大深度構造函數 (0=僅根目錄, 1=根+一級子目錄...)
    explicit CSearchFile(int depthLevel) : m_depthLevel(depthLevel) {}

    // 核心搜索接口
    // root: 起始目錄
    // pFoundCallback: 每個文件/目錄觸發
    // pDirCompletedCallback: 每個目錄遞歸返回觸發
    // depthLevel: 內部遞歸用,外部調用默認 0
    BOOL SearchFile(
        LPCTSTR root,
        PSEARCHCALLBACK pFoundCallback,
        PDIRCOMPLETEDCALLBACK pDirCompletedCallback,
        int depthLevel = 0
    );

private:
    int m_depthLevel; // -1 表示無限制

    // 判斷路徑是否為目錄
    BOOL DirectoryExist(LPCTSTR lpszDir);
};

#endif

關鍵實現細節


// SearchFilesUnit.cpp
#include "SearchFilesUnit.h"

BOOL CSearchFile::DirectoryExist(LPCTSTR lpszDir) {
    DWORD dwAttr = GetFileAttributes(lpszDir);
    return (dwAttr != INVALID_FILE_ATTRIBUTES) && 
           (dwAttr & FILE_ATTRIBUTE_DIRECTORY);
}

BOOL CSearchFile::SearchFile(
    LPCTSTR root,
    PSEARCHCALLBACK pFoundCallback,
    PDIRCOMPLETEDCALLBACK pDirCompletedCallback,
    int depthLevel
) {
    char szPath[MAX_PATH] = {0};
    WIN32_FIND_DATA wfd = {0};
    sprintf(szPath, "%s\\*", root); // 搜索模式

    // 深度控制:超過限制直接返回
    if (m_depthLevel > 0) {
        if (++depthLevel > m_depthLevel) return FALSE;
    }

    HANDLE hFind = FindFirstFile(szPath, &wfd);
    if (hFind == INVALID_HANDLE_VALUE) return FALSE;

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

        char szFullPath[MAX_PATH] = {0};
        sprintf(szFullPath, "%s\\%s", root, wfd.cFileName);

        // 如果是目錄,遞歸進入
        if (DirectoryExist(szFullPath)) {
            SearchFile(szFullPath, pFoundCallback, pDirCompletedCallback, depthLevel);
        }

        // 文件/目錄通用回調 (業務邏輯在這裡:過濾、刪除、複製...)
        if (pFoundCallback) pFoundCallback(szFullPath);

    } while (FindNextFile(hFind, &wfd));

    FindClose(hFind);

    // 目錄遍歷完成回調 (後序:子目錄處理完後觸發)
    if (pDirCompletedCallback) pDirCompletedCallback(root);

    return TRUE;
}

使用示例:刪除指定後綴文件


#include "SearchFilesUnit.h"
#include 

void __stdcall OnFileFound(LPCTSTR path) {
    // 只處理文件,跳過目錄
    DWORD attr = GetFileAttributes(path);
    if (attr & FILE_ATTRIBUTE_DIRECTORY) return;

    // 過濾 .tmp 後綴
    const char *ext = strrchr(path, '.');
    if (ext && _stricmp(ext, ".tmp") == 0) {
        printf("Deleting: %s\n", path);
        DeleteFile(path); // 或 MoveFile 到回收站
    }
}

void __stdcall OnDirCompleted(LPCTSTR dir) {
    // 可選:嘗試刪除空目錄
    RemoveDirectory(dir); // 非空則失敗,安全
}

int main() {
    CSearchFile searcher(3); // 最多遞歸 3 層
    searcher.SearchFile("C:\\Temp", OnFileFound, OnDirCompleted);
    return 0;
}

設計亮點分析

特性實現方式優勢
深度控制構造函數注入 + 遞歸參數傳遞靈活、無全局狀態、線程安全
關注點分離雙回調機制遍歷邏輯復用,業務邏輯外部化
後序處理目錄完成回調天然支持刪除空目錄、統計匯總
Windows 原生FindFirstFile/FindNextFile無運行時依賴,性能最優
編碼處理LPCTSTR/TCHAR支援 ANSI/Unicode 雙構建

潛在問題與改進

  1. 路徑長度限制:MAX_PATH (260) 易溢出。改用 \\\\?\\ 前綴或 GetLongPathName 支援長路徑。
  2. 符號鏈接/連接點:可能導致無限遞歸。需記錄已訪問目錄 (inode/文件ID) 去重。
  3. 錯誤處理:FindFirstFile 失敗原因未區分 (權限、不存在)。應返回錯誤碼或拋出異常。
  4. 線程安全:類本身無狀態,但回調若訪問共享數據需自行加鎖。
  5. 現代 C++ 改寫:用 std::filesystem::recursive_directory_iterator (C++17) + std::function 回調,跨平台、類型安全、RAII 管理句柄。

現代 C++17 版本對比


#include 
#include 
#include 

namespace fs = std::filesystem;

void SearchModern(
    const fs::path& root,
    int maxDepth,
    std::function onFile,
    std::function onDirComplete = nullptr
) {
    struct StackItem { fs::path path; int depth; };
    std::vector stack = {{root, 0}};

    while (!stack.empty()) {
        auto [cur, depth] = stack.back(); stack.pop_back();
        if (depth > maxDepth) continue;

        for (auto& entry : fs::directory_iterator(cur)) {
            if (entry.is_directory()) {
                stack.push_back({entry.path(), depth + 1});
            }
            onFile(entry.path());
        }
        if (onDirComplete) onDirComplete(cur);
    }
}

C++17 版本更簡潔、跨平台、異常安全,但在極深目錄樹下棧溢出風險較大 (可改用顯式棧)。

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