在 C++17 之后,<filesystem> 标头为文件系统操作提供了强大的工具。本文将演示如何使用 std::filesystem::path 以及相关 API 进行目录遍历,并针对特定文件后缀(如 .cpp、.h)进行过滤。目标是编写一个简洁、可移植且易维护的代码片段,并说明关键点和常见陷阱。
1. 环境准备
首先确保编译器支持 C++17 或更高版本,并已启用 `
` 标头。对于 GCC 9+、Clang 10+ 和 MSVC 19.14+,只需添加 `-std=c++17` 或 `-std=c++20` 即可。 “`bash g++ -std=c++20 -O2 -Wall -Wextra main.cpp -o scan_dir “` — ## 2. 代码结构概览 “`cpp #include #include #include #include namespace fs = std::filesystem; // 1. 递归遍历目录 std::vector find_files(const fs::path& root, const std::vector& suffixes); // 2. 主函数演示 int main() { std::vector extensions = {“.cpp”, “.h”, “.hpp”}; auto files = find_files(“src”, extensions); std::cout << "Found " << files.size() << " source files:\n"; for (const auto& f : files) { std::cout << " " << f << '\n'; } } “` — ## 3. 递归遍历实现 ### 3.1 `std::filesystem::recursive_directory_iterator` 该迭代器自动递归遍历目录,返回 `directory_entry` 对象。使用 `if (entry.is_regular_file())` 可筛选文件。 ### 3.2 过滤后缀 – `path.extension()` 返回文件后缀(包括点号),如 `.cpp`。 – 与预定义后缀向量做比较。可以使用 `std::unordered_set` 进行 O(1) 查找。 ### 3.3 完整实现 “`cpp #include std::vector find_files(const fs::path& root, const std::vector& suffixes) { std::vector result; if (!fs::exists(root) || !fs::is_directory(root)) return result; std::unordered_set suffix_set(suffixes.begin(), suffixes.end()); try { for (const auto& entry : fs::recursive_directory_iterator(root, fs::directory_options::skip_permission_denied)) { if (entry.is_regular_file()) { auto ext = entry.path().extension().string(); // std::filesystem uses case-sensitive comparison by default. // Convert to lower-case if needed for case-insensitive matching. if (suffix_set.count(ext)) { result.push_back(entry.path()); } } } } catch (const fs::filesystem_error& e) { std::cerr << "Filesystem error: " << e.what() << '\n'; } return result; } “` **说明:** 1. **错误处理** `directory_iterator` 可能因权限不足或符号链接导致异常。使用 `try/catch` 并 `fs::directory_options::skip_permission_denied` 可以忽略无权限文件夹。 2. **性能** 对于非常大的目录树,最好使用 `std::deque` 或 `std::vector` 预留容量,减少分配次数。这里我们使用 `vector`,并在遍历前 `reserve` 大致估计容量(可选)。 3. **大小写问题** Windows 文件系统不区分大小写;Linux 区分。若想统一,可把后缀转换为小写后再比较。 — ## 4. 示例:大小写忽略 “`cpp #include #include std::string to_lower(const std::string& s) { std::string r = s; std::transform(r.begin(), r.end(), r.begin(), [](unsigned char c){ return std::tolower(c); }); return r; } std::vector find_files_ci(const fs::path& root, const std::vector& suffixes) { std::vector result; if (!fs::exists(root) || !fs::is_directory(root)) return result; std::unordered_set suffix_set; for (auto s : suffixes) suffix_set.insert(to_lower(s)); try { for (const auto& entry : fs::recursive_directory_iterator(root, fs::directory_options::skip_permission_denied)) { if (entry.is_regular_file()) { auto ext = to_lower(entry.path().extension().string()); if (suffix_set.count(ext)) result.push_back(entry.path()); } } } catch (const fs::filesystem_error& e) { std::cerr << "Filesystem error: " << e.what() << '\n'; } return result; } “` — ## 5. 进一步扩展 | 需求 | 方案 | |——|——| | 只列出最近 N 天修改的文件 | 在遍历时检查 `last_write_time(entry)` 并与 `system_clock::now()` 做比较 | | 跳过隐藏文件 | 在 Linux/macOS 通过 `path.filename().string()[0] != '.'`;Windows 通过 `FILE_ATTRIBUTE_HIDDEN` | | 对结果按文件大小排序 | 使用 `std::sort` 并查询 `fs::file_size(path)` | | 支持多种匹配模式(正则) | 在遍历时使用 `std::regex` 过滤 `entry.path()` 或 `extension()` | — ## 6. 常见陷阱 1. **符号链接导致循环** `recursive_directory_iterator` 默认跟随符号链接,可能出现无限递归。使用 `fs::directory_options::follow_directory_symlink` 或 `fs::directory_options::none` 并手动检测 `entry.is_symlink()` 来避免。 2. **权限错误** 某些目录无法访问会抛出异常。`skip_permission_denied` 选项可忽略这些错误。 3. **跨平台兼容性** – Windows 目录分隔符为 `\`,Linux 为 `/`,`std::filesystem` 自动处理。 – 文件名大小写敏感性差异需要自行处理。 4. **大型项目时内存占用** 收集所有文件路径会占用内存。若仅需处理一次,可以直接在遍历中调用处理函数,而不是先收集到容器。 — ## 7. 结语 使用 `std::filesystem`,C++ 代码可以轻松实现跨平台的目录遍历与文件筛选。上述实现演示了递归遍历、后缀过滤、错误处理、大小写统一以及常见扩展需求。只需几行代码,你就能快速构建高效、可维护的文件系统工具。祝你编码愉快!