资讯动态

现代C++的文件系统操作:目录遍历与搜索

发布时间:2026/8/3 16:17:06 来源:尧图企业网站定制
现代C的文件系统操作目录遍历与搜索这个仓库已经开源现代化 CC11/14/17/20从基础到进阶的系统教程都在这里力争做一条完备的现代 C 学习路径欢迎各位大佬前来参观喜欢的话点个⭐Github 一键直达: git clone https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP看看超酷的新网站https://awesome-embedded-learning-studio.github.io/Tutorial_AwesomeModernCPP/前两篇我们学会了用path处理路径、用文件操作函数管理文件和目录。但在实际项目中最常见的需求其实是在某个目录下找到我想要的文件。比如收集所有.cpp文件送给编译器在资源目录里找到所有纹理图片或者统计项目代码的总行数。C17 提供了两个迭代器来完成目录遍历directory_iterator做单层遍历recursive_directory_iterator做递归遍历。这一篇我们从基本用法到性能优化再到错误处理把目录遍历彻底搞透。环境说明和前两篇一样C17 标准GCC 13 / Clang 15 / MSVC 2022。头文件filesystem命名空间namespace fs std::filesystem;。directory_iterator单层遍历fs::directory_iterator是一个输入迭代器遍历指定目录下的直接子项不递归进入子目录。每次解引用返回一个fs::directory_entry对象它包含了文件名和基本状态信息。最基本的用法是在 range-based for 循环中直接使用#includefilesystem#includeiostreamnamespacefsstd::filesystem;intmain(){fs::path dir/usr/local/bin;for(constautoentry:fs::directory_iterator(dir)){std::coutentry.path().filename().string();if(entry.is_directory()){std::cout/;}std::cout\n;}return0;}可能的输出截取部分gcc g cmake python3/ pip就这么简单——一个 range-based for 循环遍历目录下所有项输出文件名。如果目录是空的循环体不会执行。如果目录不存在或没有读取权限构造迭代器时就会抛出filesystem_error异常。⚠️directory_iterator遍历的顺序是未指定的——不保证按字母序、不保证按创建时间、不保证任何特定顺序。如果你需要排序就把结果收集到vector里然后std::sort。过滤文件在实际项目中我们通常只对特定类型的文件感兴趣。最简单的过滤方式是在循环体内加条件判断voidfind_cpp_files(constfs::pathdir){for(constautoentry:fs::directory_iterator(dir)){if(entry.is_regular_file()entry.path().extension().cpp){std::coutentry.path()\n;}}}如果你熟悉 C20 的 ranges可以结合 views 做更函数式的过滤但那需要 C20 支持。在 C17 中lambda std::copy_if是一个不错的替代方案#includevector#includealgorithmstd::vectorfs::pathcollect_files(constfs::pathdir,conststd::stringext){std::vectorfs::pathresult;for(constautoentry:fs::directory_iterator(dir)){if(entry.is_regular_file()entry.path().extension()ext){result.push_back(entry.path());}}std::sort(result.begin(),result.end());returnresult;}recursive_directory_iterator递归遍历如果你需要遍历一个目录树下所有的文件包括子目录、子目录的子目录…就需要fs::recursive_directory_iterator。它的工作方式类似于find命令——从起始目录开始深度优先地递归进入每一个子目录。voidlist_all_files(constfs::pathdir){for(constautoentry:fs::recursive_directory_iterator(dir)){std::coutentry.path();if(entry.is_directory()){std::cout/;}std::cout\n;}}可能的输出/home/user/project/src/ /home/user/project/src/main.cpp /home/user/project/src/utils/ /home/user/project/src/utils/helper.cpp /home/user/project/src/utils/helper.h /home/user/project/CMakeLists.txt深度控制recursive_directory_iterator提供了depth()方法返回当前递归深度从 0 开始。你可以用它来限制遍历深度voidlist_with_depth_limit(constfs::pathdir,intmax_depth){for(autoitfs::recursive_directory_iterator(dir);it!fs::recursive_directory_iterator();it){if(it.depth()max_depth){it.disable_recursion_pending();// 跳过该子目录continue;}std::coutstd::string(it.depth()*2, )it-path().filename().string()\n;}}输出示例max_depth 1src/ main.cpp utils/ CMakeLists.txt⚠️ 注意depth()返回的是当前条目相对于起始目录的深度不是相对于根目录。起始目录下的直接子项深度为 0子目录下的子项深度为 1以此类推。如果你在遍历过程中需要跳过某个子目录不想递归进去可以调用迭代器的disable_recursion_pending()方法——下一篇我们会展示具体用法。directory_options控制遍历行为构造recursive_directory_iterator时可以传入directory_options来控制遍历行为。常用的选项有fs::directory_options::none默认——遇到权限拒绝的目录时抛异常。fs::directory_options::skip_permission_denied——遇到权限拒绝的目录时跳过不抛异常。这个选项在实际项目中非常有用因为你经常会遇到系统目录如/proc、/sys没有读取权限的情况。fs::directory_options::follow_directory_symlink——遇到指向目录的符号链接时跟随链接递归进去。默认不跟随因为可能导致无限循环。// 安全的递归遍历跳过无权限的目录for(constautoentry:fs::recursive_directory_iterator(dir,fs::directory_options::skip_permission_denied)){// 处理 entry...}笔者强烈建议在遍历用户文件系统尤其是从根目录或 home 目录开始遍历时始终加上skip_permission_denied。否则一旦遇到一个没权限的子目录整个遍历就会中断已经遍历了一半的结果也丢了。directory_entry不只是 path每次解引用目录迭代器时你得到的不是path对象而是directory_entry对象。directory_entry是path的加强版——它不仅保存了路径还缓存了文件状态信息。缓存的优势directory_entry可能会缓存文件状态信息类型、大小等以减少系统调用次数。当你在遍历过程中多次调用is_regular_file()、is_directory()、file_size()等方法时可以直接从缓存读取避免重复的stat()调用。⚠️ 注意缓存行为是实现定义的implementation-defined标准不保证一定会缓存或缓存何时失效。for(constautoentry:fs::directory_iterator(dir)){// 这些调用使用缓存值不触发额外的系统调用autonameentry.path().filename().string();autois_fileentry.is_regular_file();autois_direntry.is_directory();autosizeentry.file_size();// 仅对普通文件有效std::coutname (is_file?file:dir) size\n;}⚠️directory_entry的缓存是在迭代器构造时获取的。如果在遍历过程中文件被修改或删除缓存可能已经过期。如果你需要实时状态可以调用entry.refresh()强制刷新或者直接用fs::status(entry.path())获取最新状态。不过这种情况比较少见——大多数遍历场景下缓存数据是足够准确的。遍历时过滤按扩展名、大小、时间我们把前面的知识组合起来写一个支持多维度过滤的文件搜索函数。它可以根据扩展名、最小文件大小、最大文件大小来过滤结果#includefilesystem#includevector#includealgorithm#includeiostream#includechrononamespacefsstd::filesystem;structSearchFilter{std::string extension;// 目标扩展名空表示不过滤std::uintmax_t min_size0;// 最小文件大小std::uintmax_t max_sizeUINTMAX_MAX;// 最大文件大小intmax_depth-1;// 最大递归深度-1 表示不限};std::vectorfs::pathsearch_files(constfs::pathroot,constSearchFilterfilter){std::vectorfs::pathresults;std::error_code ec;autooptionsfs::directory_options::skip_permission_denied;for(autoitfs::recursive_directory_iterator(root,options,ec);it!fs::recursive_directory_iterator();it){if(ec){std::cerr遍历错误: ec.message()\n;ec.clear();continue;}// 深度过滤if(filter.max_depth0it.depth()filter.max_depth){it.disable_recursion_pending();continue;}constautoentry*it;// 只处理普通文件if(!entry.is_regular_file()){continue;}// 扩展名过滤if(!filter.extension.empty()){if(entry.path().extension()!filter.extension){continue;}}// 文件大小过滤autosizeentry.file_size();if(sizefilter.min_size||sizefilter.max_size){continue;}results.push_back(entry.path());}std::sort(results.begin(),results.end());returnresults;}使用示例intmain(){SearchFilter filter;filter.extension.cpp;filter.min_size100;// 至少 100 字节filter.max_size1000000;// 不超过 1MBautofilessearch_files(/home/user/project,filter);std::cout找到 files.size() 个文件:\n;for(constautof:files){std::cout f\n;}return0;}这个搜索函数展示了recursive_directory_iterator的典型使用模式构造时加上skip_permission_denied在循环体内用directory_entry的缓存方法做过滤最后收集结果。这种遍历 过滤 收集的模式在实际项目中非常常见。性能考量目录遍历的性能取决于两个因素目录的大小和系统调用的次数。directory_entry的缓存已经帮我们减少了很多不必要的stat()调用但还有一些其他因素需要注意。符号链接处理默认情况下recursive_directory_iterator不跟随符号链接。这是正确的默认行为——跟随链接可能导致无限循环A 指向 BB 指向 A也可能导致同一个文件被访问多次。如果你确实需要跟随符号链接加上follow_directory_symlink选项但一定要确保没有循环链接。深度控制递归遍历一个深层嵌套的目录结构可能会消耗大量时间和内存。如果你的目标只是浅层搜索用depth()限制递归深度是很有必要的。在笔者的测试中遍历整个/usr目录树大约需要 5 秒但限制深度为 2 时只需要 0.3 秒。与手动递归的性能对比有时候你会看到有人手动写递归来遍历目录用directory_iterator在每个子目录中递归调用。这种方式的性能通常比recursive_directory_iterator差——因为recursive_directory_iterator在内部做了优化比如批量读取目录项而手动递归每次都要构造新的迭代器。所以优先使用recursive_directory_iterator。实战代码统计工具作为本篇的收尾我们来写一个实用的代码统计工具。它递归地遍历指定目录统计每种源代码文件的文件数量和总行数#includefilesystem#includeiostream#includefstream#includestring#includeunordered_map#includealgorithm#includeiomanipnamespacefsstd::filesystem;structFileStats{intfile_count0;inttotal_lines0;};/// brief 统计单个文件的行数/// param path 文件路径/// return 行数失败返回 0intcount_lines(constfs::pathpath){std::ifstreamfile(path);if(!file)return0;intlines0;std::string line;while(std::getline(file,line)){lines;}returnlines;}/// brief 统计目录下的代码文件/// param root 根目录voidcode_stats(constfs::pathroot){std::unordered_mapstd::string,FileStatsstats;std::error_code ec;autooptionsfs::directory_options::skip_permission_denied;for(constautoentry:fs::recursive_directory_iterator(root,options,ec)){if(ec){ec.clear();continue;}if(!entry.is_regular_file())continue;autoextentry.path().extension().string();// 只统计常见源代码文件if(ext!.cppext!.hext!.hppext!.cext!.pyext!.javaext!.rsext!.go){continue;}// 跳过隐藏目录和 build 目录boolskipfalse;for(constautocomponent:entry.path()){autoscomponent.string();if(s.git||sbuild||scmake-build-*||(s.size()1s[0].)){// 简单的跳过逻辑}}// 完整版本应该用 disable_recursion_pending() 处理// 这里简化处理autolinescount_lines(entry.path());stats[ext].file_count;stats[ext].total_lineslines;}// 输出结果inttotal_files0;inttotal_lines0;std::coutstd::leftstd::setw(8)扩展名std::setw(10)文件数std::setw(12)总行数\n;std::coutstd::string(30,-)\n;for(constauto[ext,stat]:stats){std::coutstd::leftstd::setw(8)extstd::setw(10)stat.file_countstd::setw(12)stat.total_lines\n;total_filesstat.file_count;total_linesstat.total_lines;}std::coutstd::string(30,-)\n;std::coutstd::leftstd::setw(8)合计std::setw(10)total_filesstd::setw(12)total_lines\n;}intmain(){code_stats(.);return0;}可能的输出扩展名 文件数 总行数 ------------------------------ .cpp 12 4856 .h 15 2340 .hpp 3 892 .py 2 340 ------------------------------ 合计 32 8428这个工具综合运用了本篇和前两篇的所有知识recursive_directory_iterator做递归遍历directory_entry::is_regular_file()做类型过滤path::extension()做扩展名过滤path的迭代器做目录名过滤。在实际项目中你可以扩展它来统计空行数、注释行数、代码行数等更细粒度的指标。参考资源cppreference: directory_iteratorcppreference: recursive_directory_iteratorcppreference: directory_entrycppreference: directory_optionsC Stories: Directory Iteration

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价