资讯动态

C++ 遍历某个文件夹下所有文件的方法步骤

发布时间:2026/10/20 11:37:49 来源:尧图企业网站定制
前言「遍历一个目录下的所有文件」看起来是个小需求真正写起来却有一堆细节要不要递归子目录、遇到没有权限的目录怎么办、符号链接要不要跟、遍历顺序是否稳定、路径里的中文在 Windows 上会不会乱码。C17 之前标准库没有目录操作只能依赖平台 APIWindows 的FindFirstFile、POSIX 的opendir/readdir或 Boost.Filesystem代码不可移植。C17 引入filesystem之后这些操作终于有了标准接口。本文按「非递归遍历、递归遍历、错误处理、路径与编码」的顺序讲最后给出一个可直接改造的完整示例。需要提醒std::filesystem是 C17 特性GCC 8 及更早版本需要额外链接-lstdcfsGCC 9 起通常不再需要Clang 搭配 libc、MSVC 的支持时间也各不相同实际以所用工具链的编译器支持表为准。本文的代码只做语义推演未在本机编译也不声称任何运行输出。示例中的路径使用相对目录或保留域名不涉及真实主机。一、最小可用非递归遍历头文件是filesystem命名空间通常简写为fs。最基本的写法是范围 for 循环// 适用于 C17#include filesystem#include iostreamnamespace fs std::filesystem;void list_once(const fs::path dir) {for (const fs::directory_entry entry : fs::directory_iterator(dir)) {if (entry.is_regular_file()) {std::cout entry.path().filename() \n;}}}directory_iterator只遍历当前这一层不进入子目录。每次迭代得到一个directory_entry它可以回答「是不是普通文件」「是不是目录」「大小多少」「最后修改时间」等问题也能取出完整路径。entry.path()返回的是带目录前缀的路径只想显示文件名时用filename()。directory_iterator的构造会抛std::filesystem::filesystem_error派生自std::system_error目录不存在或没有读权限时都会走到异常路径。如果不希望异常穿透可以使用带std::error_code的重载或在整个循环外层捕获。范围 for 的写法依赖迭代器的begin/endC17 的标准库已经为目录迭代器提供了这两者。注意迭代顺序是未指定的unspecified不同文件系统、不同实现可能给出不同顺序不能假设按文件名或创建时间排列。二、递归遍历与遍历控制需要包含子目录时换成recursive_directory_iterator// 适用于 C17#include filesystem#include iostreamnamespace fs std::filesystem;void list_recursive(const fs::path dir) {for (const fs::directory_entry entry :fs::recursive_directory_iterator(dir)) {if (entry.is_regular_file()) {std::cout entry.path() \n;}}}递归迭代器在进入子目录时自动增加深度离开时自动回退。它提供depth()查询当前深度提供disable_recursion_pending()阻止进入当前这个子目录适合跳过.git、build之类的目录也提供pop()主动结束当前层的遍历。默认不跟随目录符号链接如果需要跟随构造时传fs::directory_options::follow_directory_symlink。还有一个常用选项是fs::directory_options::skip_permission_denied它让迭代器在遇到无权限目录时跳过而不是报错。它只影响迭代器的推进不代表所有错误都被吞掉具体语义以标准与实现文档为准。// 适用于 C17fs::recursive_directory_iterator it(dir,fs::directory_options::skip_permission_denied);for (; it ! fs::recursive_directory_iterator(); it) {if (it-is_directory() it-path().filename() .git) {it.disable_recursion_pending();}}disable_recursion_pending()必须在「即将进入该目录」的那次迭代里调用也就是当前条目本身是目录时如果条目是文件调用它没有意义。这个接口容易记错使用前建议对照 cppreference 的recursive_directory_iterator页面确认签名。三、错误处理异常还是 error_codefilesystem的大多数操作都有两套接口抛异常的版本以及接受std::error_code的版本。异常版本读起来干净适合「目录必须存在否则就是程序错误」的场景error_code版本适合批处理、后台扫描这类「个别目录失败也要继续」的场景。// 适用于 C17#include filesystem#include system_errornamespace fs std::filesystem;void scan(const fs::path dir) {std::error_code ec;fs::directory_iterator it(dir, ec);if (ec) {// 目录不存在、不是目录、无权限等return;}for (const auto entry : it) {std::error_code inner;if (entry.is_regular_file(inner) !inner) {// 处理文件}}}这里要区分两种失败构造迭代器失败说明整个目录不可读单个条目查询失败例如符号链接指向不存在的目标只影响这一条。把两者混在一个error_code里很容易出现「一个坏链接导致整个扫描停止」的问题。异常版本则要注意filesystem_error携带的信息what()返回描述字符串path1()、path2()返回涉及的路径code()返回底层错误码。捕获时按const std::filesystem::filesystem_error捕获不要按值捕获否则可能切片。四、路径、扩展名与编码fs::path是路径的类型不是字符串。拼接路径要用operator/// 适用于 C17fs::path dir data;fs::path file dir / config.txt; // 自动处理分隔符不要用dir.string() / name手工拼接Windows 与 POSIX 的分隔符、根名、驱动器号规则不同手工拼接容易出错。path还提供filename()、stem()、extension()、parent_path()分别对应文件名、去扩展名的主干、扩展名和父目录。注意extension()返回的字符串带点比较时要写 .txt。编码是跨平台时最容易踩的坑。Windows 上fs::path的内部表示使用wchar_t可以直接承载 UTF-16POSIX 上使用char具体编码取决于系统区域设置。把路径输出到std::cout时非 ASCII 字符能否正确显示取决于控制台代码页与流的状态。C17 提供path::u8string()按 UTF-8 返回std::stringC20 起它的返回类型改成了std::u8string元素类型是char8_t这是一处不兼容的签名变化。写跨标准版本的代码时不要假定u8string()的返回类型必要时用auto接住再按需转换。把 UTF-8 字节直接写入std::cout也不保证终端按 UTF-8 解释Windows 控制台尤其如此这类问题属于运行环境不是文件系统库本身的错。五、完整示例收集指定扩展名的文件下面把递归遍历、错误码、排序和扩展名过滤组合起来。排序是为了让结果稳定因为标准不保证遍历顺序。// 适用于 C17#include algorithm#include filesystem#include iostream#include string#include system_error#include vectornamespace fs std::filesystem;std::vectorfs::path collect_files(const fs::path root,const std::string ext) {std::vectorfs::path result;std::error_code ec;fs::recursive_directory_iterator it(root,fs::directory_options::skip_permission_denied,ec);if (ec) {return result;}const fs::recursive_directory_iterator end;while (it ! end) {const fs::directory_entry entry *it;std::error_code file_ec;if (entry.is_regular_file(file_ec) !file_ec) {if (entry.path().extension() ext) {result.push_back(entry.path());}}it.increment(ec);if (ec) {break; // 推进失败时结束避免原地打转}}std::sort(result.begin(), result.end());return result;}int main() {const fs::path root data;std::error_code ec;if (!fs::is_directory(root, ec) || ec) {std::cerr not a directory\n;return 1;}for (const fs::path p : collect_files(root, .txt)) {std::cout p.string() \n;}return 0;}几个设计点值得说明。第一collect_files先收集再排序而不是边遍历边输出这样调用者拿到的是稳定顺序。第二推进失败时直接break因为无法保证迭代器已经前进如果继续循环可能重复处理同一条目。第三skip_permission_denied只作为选项单个条目的查询仍然单独用error_code检查两层错误分开处理。第四is_directory(root, ec)同时检查返回值和ec因为失败时返回值也可能是false只看布尔值无法区分「不是目录」和「查询失败」。如果目录很大收集全部路径会占用内存这时可以改成流式回调或者在遍历时直接处理文件但要接受顺序不稳定的现实。是否值得排序取决于后续步骤是否依赖顺序不要为了「看起来整齐」而无条件付出额外内存。常见坑点❌ 假设directory_iterator按文件名排序输出。✅ 标准不规定顺序需要稳定结果时先收集到std::vectorfs::path再std::sort。❌ 递归遍历时对每个目录都调用disable_recursion_pending()结果一个子目录都没进去。✅ 只在需要跳过的那一个目录上调用且要在当前条目是目录时调用。❌ 用dir.string() / name拼路径在 Windows 上混用分隔符或丢失根名。✅ 用fs::path的operator/由库处理平台差异。❌ 只写for (auto e : fs::directory_iterator(dir))而不处理构造异常目录不存在时程序直接终止。✅ 用error_code重载并检查或捕获fs::filesystem_error后给出可读提示。❌ 在遍历过程中删除或重命名当前目录里的文件指望迭代器继续正常工作。✅ 先收集路径结束遍历后再修改遍历期间修改目录内容的行为不应依赖。❌ 使用 GCC 8 编译时包含filesystem却忘记链接-lstdcfs得到大量未定义符号。✅ 按工具链版本补链接选项GCC 9 起通常不再需要升级前先查编译器支持表。❌ 把path::u8string()的返回值硬写成std::string升级到 C20 后编译失败。✅ C20 起返回std::u8string用auto接住或按目标标准显式转换。❌ 认为is_regular_file()与is_symlink()是互斥的两种判断。✅ 前者默认跟随符号链接看目标后者看链接本身判断链接本身要用symlink_status()或is_symlink()两者回答的是不同问题。总结需求接口要点只看当前目录fs::directory_iterator不递归顺序未指定包含子目录fs::recursive_directory_iterator可跳过指定目录、可控制深度跳过无权限目录directory_options::skip_permission_denied仅影响迭代推进不抛异常接受std::error_code的重载区分构造失败与单条目失败拼接路径fs::path::operator/不要手工拼分隔符取扩展名path::extension()返回值带点如.txt稳定顺序收集后std::sort顺序不是标准保证的遍历目录的难点不在 API而在边界权限、符号链接、编码、顺序、遍历期间修改。把「收集」和「处理」分成两个阶段把「整体失败」和「单条失败」分开处理代码就会稳很多。新项目直接用filesystem需要兼容 C14 时再考虑 Boost.Filesystem 或平台 API并接受相应的可移植性代价。

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

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

免费获取报价 →
↑