C++17 std::filesystem:从路径操作到文件系统迭代的全攻略

在 C++17 标准中, 库被正式引入,为跨平台的文件系统交互提供了统一且强大的 API。本文将从最常见的路径处理、文件读写,到递归遍历、权限检查等方面展开,帮助你快速掌握 std::filesystem 的使用技巧,并结合实际示例让你在项目中能直接上手。


1. 引入与编译

#include <filesystem>
namespace fs = std::filesystem;

在大多数编译器(gcc 8+、clang 7+、MSVC 2017+)中,只需链接 -lstdc++fs(gcc 8 以前需要),或直接编译即可。

g++ -std=c++17 -Wall -Wextra main.cpp -lstdc++fs

2. 路径(path)操作

2.1 基本构造

fs::path p1 = "C:/Users/John";
fs::path p2 = "/usr/local/bin";
fs::path p3 = fs::path("foo") / "bar" / "baz.txt"; // 组合路径

fs::path 自动识别平台分隔符,内部使用 std::string 存储。

2.2 访问路径属性

std::cout << "Full path: " << p3 << '\n';          // 输出完整路径
std::cout << "Parent: "   << p3.parent_path() << '\n'; // 上级目录
std::cout << "Filename: " << p3.filename() << '\n';   // 文件名
std::cout << "Extension: "<< p3.extension() << '\n';  // 扩展名

2.3 路径比较与相对化

fs::path a = "/home/user/project/src";
fs::path b = "/home/user/project/docs";

auto rel = fs::relative(a, b); // 生成相对路径
// 结果:../src

注意fs::relative 只在两个路径共享同一根时才有意义,否则抛出 std::invalid_argument


3. 文件与目录的基本操作

3.1 创建与删除

fs::create_directory("/tmp/hello");          // 单级目录
fs::create_directories("/tmp/hello/world"); // 多级目录
fs::remove("/tmp/hello/world");              // 删除文件或空目录
fs::remove_all("/tmp/hello");                // 删除非空目录

3.2 复制与移动

fs::copy("/tmp/hello/world/file.txt", "/tmp/copy.txt", 
         fs::copy_options::overwrite_existing);
fs::rename("/tmp/old.txt", "/tmp/new.txt");

3.3 文件信息

fs::file_size("/etc/passwd");                // 文件大小
fs::last_write_time("/etc/passwd");          // 最后修改时间

last_write_time 返回 std::filesystem::file_time_type,可通过 std::chrono 转为可读时间。


4. 迭代器(Directory Iteration)

4.1 简单遍历

for (const auto& entry : fs::directory_iterator("/tmp/hello")) {
    std::cout << entry.path() << '\n';
}

4.2 递归遍历

for (const auto& entry : fs::recursive_directory_iterator("/tmp/hello")) {
    if (entry.is_regular_file()) {
        std::cout << "File: " << entry.path() << '\n';
    } else if (entry.is_directory()) {
        std::cout << "Dir : " << entry.path() << '\n';
    }
}

recursive_directory_iterator 默认会跟随符号链接,若不想跟随可使用 fs::directory_options::follow_directory_symlink

4.3 排序与过滤

std::vector<fs::directory_entry> files;

for (const auto& entry : fs::directory_iterator("/tmp/hello")) {
    if (entry.is_regular_file() && entry.path().extension() == ".txt") {
        files.push_back(entry);
    }
}

std::sort(files.begin(), files.end(), 
          [](auto& a, auto& b){ return a.path() < b.path(); });

for (auto& e : files) std::cout << e.path() << '\n';

5. 文件内容的读取与写入

虽然 std::filesystem 主要关注路径与元数据,实际文件 I/O 仍使用传统 `

` 或 “ 相关的 `std::fstream`。 “`cpp #include #include fs::path file = “/tmp/hello/world/hello.txt”; { std::ofstream ofs(file); ofs << "Hello, 世界!\n"; } { std::ifstream ifs(file); std::string line; while (std::getline(ifs, line)) std::cout << line < `std::ofstream`、`std::ifstream` 在打开文件时会自动根据路径字符串创建 `fs::path`,无须额外转换。 — ## 6. 文件权限与属性 “`cpp fs::permissions p = fs::status(file).permissions(); std::cout << "Owner read: " << ((p & fs::perms::owner_read) != fs::perms::none) < `fs::permissions` 提供了跨平台的权限掩码,可使用 `fs::permissions(path, perms, change)` 修改权限。 — ## 7. 错误处理 大多数 `std::filesystem` 函数都有两种调用方式: 1. **异常模式**(默认)——遇到错误抛 `std::filesystem::filesystem_error`。 2. **错误码模式**——传递 `std::error_code&`,函数返回值不抛异常。 “`cpp std::error_code ec; fs::remove(“/non/existent/file”, ec); if (ec) { std::cerr << "删除失败: " << ec.message() << '\n'; } “` — ## 8. 常见坑与最佳实践 1. **路径分隔符不统一**:`fs::path` 在内部使用 `path::preferred_separator`,但在字符串拼接时需小心。 2. **符号链接**:递归遍历默认会跟随 symlink,可能导致无限循环。使用 `fs::directory_options::skip_permission_denied` 可跳过无权限的目录。 3. **文件大小**:`fs::file_size` 对符号链接会返回链接本身大小,若需获取目标文件大小需先 `fs::canonical`。 4. **多线程**:`std::filesystem` 本身不是线程安全的,若在多线程环境下操作同一目录,需自行加锁。 5. **跨平台路径拼接**:不要手动添加 `/`,始终使用 `operator/` 或 `push_back`。 — ## 9. 结语 C++17 的 ` ` 极大简化了跨平台文件系统编程,让路径操作、文件/目录管理、权限检查等功能不再需要繁琐的第三方库或手写平台特定代码。掌握其核心 API 后,你可以像处理容器那样轻松处理文件系统,写出更安全、更可维护、更可移植的代码。 祝你编码愉快,文件系统无所不通!

发表评论