在C++17标准之前,想要遍历一个目录下的所有子目录和文件,Windows下要用FindFirstFile、Linux下要用opendir和readdir,不仅接口完全不同,连路径拼接方式都不一样。C++17把filesystem正式纳入标准库,让我们可以用同一套代码在主流操作系统上完成文件夹的递归遍历。其中最关键的工具就是std::filesystem::recursive_directory_iterator,它能够自动向下深入子目录,避免手动维护栈或递归函数。

一、filesystem基础与环境准备
要使用filesystem,需要包含头文件<filesystem>,并将其命名空间简写为fs。在大部分现代编译器中,C++17是默认支持该库的,但如果使用较旧的GCC(如GCC 8以下),可能需要显式链接-lstdc++fs。Clang和MSVC通常无需额外操作。
下面的代码展示了最基本的命名空间引入方式,以及编译时如何确认标准版本。注意filesystem中的路径path类会自动处理不同平台的路径分隔符,比如Windows的反斜杠和Linux的正斜杠,在构造path对象时都能正确解析。
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
// 构造一个路径,跨平台可用
fs::path dir_path = "./test_folder";
std::cout << "路径是否存在: " << fs::exists(dir_path) << std::endl;
return 0;
}
二、使用recursive_directory_iterator递归遍历
recursive_directory_iterator是输入迭代器,当它被递增时会自动进入子目录。我们只需要用范围for循环就能拿到目录树中的每一个条目。每个条目都是directory_entry对象,可以调用path()获取路径,调用is_regular_file()或is_directory()判断类型。
与手动写递归函数相比,这种方式代码量更少,也不容易在深层目录上出现栈溢出。下面的例子递归打印某个目录下的所有文件和文件夹路径:
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path root = "./sample";
if (!fs::exists(root) || !fs::is_directory(root)) {
std::cerr << "目录不存在或不是文件夹" << std::endl;
return 1;
}
// 递归迭代器自动深入子目录
for (const auto& entry : fs::recursive_directory_iterator(root)) {
if (entry.is_regular_file()) {
std::cout << "[文件] " << entry.path().string() << std::endl;
} else if (entry.is_directory()) {
std::cout << "[目录] " << entry.path().string() << std::endl;
}
}
return 0;
}
上面的代码在Windows、Linux和macOS上均无需修改即可编译运行。如果希望只统计某种后缀的文件,比如.txt,可以在循环内用entry.path().extension()做判断。这种过滤方式同样跨平台,因为extension()会按点号拆分,不受路径分隔符影响。
三、处理符号链接与跳过目录
默认情况下,recursive_directory_iterator会跟随符号链接进入指向的目录,这可能造成循环遍历。如果不希望跟随,可以构造迭代器时传入directory_options::skip_permission_denied和nofollow_directory_symlink选项。这样遇到无权限目录会跳过,遇到符号链接目录也不会深入。
另外,有时我们想在遍历中动态跳过某些目录,比如名为node_modules的文件夹。可以拿到recursive_directory_iterator的引用,调用disable_recursion_pending()来阻止进入当前条目对应的子目录。下面的示例演示了这种控制:
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path root = "./project";
// 不跟随符号链接,跳过无权限目录
auto options = fs::directory_options::skip_permission_denied
| fs::directory_options::nofollow_directory_symlink;
for (auto it = fs::recursive_directory_iterator(root, options);
it != fs::recursive_directory_iterator(); ++it) {
const auto& entry = *it;
if (entry.is_directory() && entry.path().filename() == "node_modules") {
it.disable_recursion_pending(); // 不进入该目录
std::cout << "跳过: " << entry.path().string() << std::endl;
continue;
}
std::cout << entry.path().string() << std::endl;
}
return 0;
}
这种细粒度控制在处理大型工程目录时非常实用。比如构建系统扫描源码时,临时目录和依赖目录往往不需要索引,用disable_recursion_pending能显著减少无谓的磁盘访问。
四、异常与安全遍历
递归遍历过程中可能遇到权限不足、路径过长或文件在遍历时被删除等情况,直接访问entry的方法可能抛出filesystem_error异常。为了保证程序健壮,应该用try-catch包裹遍历逻辑,或者改用不抛异常的path方法,例如entry.path().string()本身不会抛异常,但is_regular_file()在底层stat失败时可能抛错。
一种更安全的写法是捕获filesystem_error并打印错误码,而不是让程序崩溃。下面给出一个带异常处理的完整模板:
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path root = "./data";
try {
for (const auto& entry : fs::recursive_directory_iterator(root)) {
try {
if (entry.is_regular_file()) {
auto size = fs::file_size(entry);
std::cout << entry.path().string()
<< " 大小: " << size << "字节" << std::endl;
}
} catch (const fs::filesystem_error& e) {
std::cerr << "访问失败: " << e.what() << std::endl;
}
}
} catch (const fs::filesystem_error& e) {
std::cerr << "遍历初始化失败: " << e.what() << std::endl;
return 1;
}
return 0;
}
通过双层try-catch,即使个别文件无法访问,整体遍历依然可以继续。对于服务端程序或批量处理工具,这种容错能力几乎是必要的。
五、性能与跨平台注意事项
recursive_directory_iterator在底层通常调用操作系统的目录读取接口,性能与原生API接近。但在网络文件系统或包含海量小文件的目录下,频繁调用file_size或status会增加系统调用次数。如果只需要路径而不需要属性,应尽量减少这类查询。
在Windows上,路径长度默认限制为260字符,filesystem可以通过前缀\?来突破,但需保证使用宽字符接口;在Linux上则几乎没有长度限制。因此跨平台发布时,建议用path::string()或path::u8string()统一获取字符串,并在日志中避免过长路径截断导致歧义。
| 平台 | 默认路径分隔符 | 符号链接支持 | 长路径处理 |
|---|---|---|---|
| Windows | 反斜杠 | 需特权或开发者模式 | 需\?前缀 |
| Linux | 正斜杠 | 原生支持 | 几乎无限制 |
| macOS | 正斜杠 | 原生支持 | 受限但较宽松 |
总的来说,C++17的filesystem接口让递归遍历文件夹变成一件简单且可移植的事。只要理解迭代器的选项控制和异常模型,就能写出干净、安全、跨平台的目录扫描代码,而不必再为不同系统维护多套实现。
C++filesystemrecursive_directory_iterator修改时间:2026-08-03 14:18:35