C++17 Filesystem 实用指南

C++17 标准带来了 std::filesystem库, 提供了强大的工具来处理文件路径, 目录以及其他与文件系统相关的操作. 这篇文章适合 C++ 初学者以及希望掌握 C++17 新特性的开发者, 旨在帮助他们高效地完成文件系统相关任务.


什么是 std::filesystem?

std::filesystem 是 C++ 标准库的一部分, 位于 std::filesystem 名称空间中. 它提供了一个平台独立的方式来与文件系统交互, 支持目录导航, 查询文件属性和实现文件操作等功能.


为什么使用 std::filesystem?

在 C++17 之前, 开发者依赖于平台特定的 API 或第三方库进行文件操作, 导致可移植性的挑战. 使用 std::filesystem, 您可以获得:


std::filesystem 常用功能

1. 检查文件和目录是否存在

#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

int main() {
  fs::path filePath = "example.txt";
  if (fs::exists(filePath)) {
    std::cout << "文件存在!\n";
  } else {
    std::cout << "文件不存在.\n";
  }
  return 0;
}

为了保持代码简洁, 后续代码中将不再展示头文件和main函数体, 点击’Compiler Explorer’按钮, 即可运行, Enjoy~

2. 创建和删除目录

fs::path dirPath = "new_directory";
fs::create_directory(dirPath);  // 创建单个目录
fs::remove(dirPath);            // 删除目录

3. 遍历目录

fs::path dirPath = ".";  // 当前目录
for (const auto& entry : fs::recursive_directory_iterator(dirPath)) {
  fmt::println("Path: {}", entry.path().string());
  fmt::println("\tIs Regular File: {}", entry.is_regular_file());
  fmt::println("\tIs Directory: {}", entry.is_directory());
  fmt::println("\tIs Symlink: {}", entry.is_symlink());
  if (entry.is_regular_file()) {
    fmt::println("\tFile Size: {} bytes", entry.file_size());
  }
}

此示例会递归列出当前目录及其所有子目录中的文件和文件夹.

4. 查询文件属性

fs::path filePath = "stat";
if (fs::exists(filePath)) {
  fmt::println("文件大小: {} 字节", fs::file_size(filePath));
  fmt::println("是否为常规文件: {}", fs::is_regular_file(filePath));
}

5. 复制和重命名文件

fs::copy("source.txt", "destination.txt", fs::copy_options::overwrite_existing); // 复制文件
fs::rename("old_name.txt", "new_name.txt"); // 重命名文件

6. 处理符号链接

fs::path symlinkPath = "symbolic_link";
fs::path targetPath = "target_file";
fs::create_symlink(targetPath, symlinkPath);  // 创建符号链接
if (fs::is_symlink(symlinkPath)) {
  fmt::println("{} 是符号链接, 指向 {}", symlinkPath.string(),
               fs::read_symlink(symlinkPath).string());
}

路径相关操作

7.1 路径组合

fs::path base = "/home/user/documents";
fs::path file = "report.pdf";
fs::path fullPath = base / file;  // 使用 / 操作符组合路径
fmt::println("组合路径: {}", fullPath.string());

输出

组合路径: "/home/user/documents/report.pdf"

7.2 获取路径组件

fs::path filepath = "/home/user/documents/report.pdf";
fmt::println("文件名: {}", filepath.filename().string());
fmt::println("主文件名: {}", filepath.stem().string());
fmt::println("扩展名: {}", filepath.extension().string());
fmt::println("父路径: {}", filepath.parent_path().string());

输出

文件名: "report.pdf"
主文件名: "report"
扩展名: ".pdf"
父路径: "/home/user/documents"

7.3 相对路径计算

fs::path p1 = "/home/user/documents";
fs::path p2 = "/home/user/pictures";
fs::path rel = fs::relative(p2, p1);         // 计算从 p1 到 p2 的相对路径
fmt::println("相对路径: {}", rel.string());  // ../pictures

输出

相对路径: "../pictures"

7.4 路径分解与迭代

fs::path complex = "/home/user/documents/work/report.pdf";
fmt::println("路径分解: {}", complex.string());
for (const auto& part : complex) {
  fmt::println("  {}", part.string());
}

输出

路径分解:
  "/"
  "home"
  "user"
  "documents"
  "work"
  "report.pdf"

7.5 规范化路径

fs::path messy = "home/user/../user/./documents/report.pdf";
fmt::println("规范化路径: {}", messy.lexically_normal().string());

输出

规范化路径: "home/user/documents/report.pdf"

7.6 路径比较

fs::path p3 = "documents/report.pdf";
fs::path p4 = "documents/report.pdf";
fmt::println("路径相等: {}", p3 == p4);
// 使用 std::filesystem::equivalent 检查两个路径是否指向同一文件
fs::path linkPath =
    "link_to_report.pdf";  // 假设这是一个指向 report.pdf 的符号链接
fs::path actualPath = "documents/report.pdf";
try {
  if (fs::equivalent(linkPath, actualPath)) {
    fmt::println("路径 {} 和 {} 指向同一文件.", linkPath.string(),
                 actualPath.string());
  } else {
    fmt::println("路径 {} 和 {} 不指向同一文件.", linkPath.string(),
                 actualPath.string());
  }
} catch (const fs::filesystem_error& e) {
  fmt::println(stderr, "文件系统错误: {}", e.what());
}

7.7 路径拼接

fs::path prefix = "backup_";
fs::path filename = "report.pdf";
fs::path newPath = prefix.string() + filename.string();
fmt::println("拼接路径: {}", newPath.string());

输出

拼接路径: "backup_report.pdf"

7.8 检查路径特性

fs::path checkPath = "/home/user/documents/report.pdf";
fmt::println("是否为绝对路径: {}", checkPath.is_absolute());
fmt::println("是否有扩展名: {}", checkPath.has_extension());
fmt::println("是否有文件名: {}", checkPath.has_filename());
fmt::println("是否有父路径: {}", checkPath.has_parent_path());

输出

是否为绝对路径: 1
是否有扩展名: 1
是否有文件名: 1
是否有父路径: 1

7.9 路径替换操作

fs::path modPath = "/home/user/documents/report.pdf";
modPath.replace_filename("newreport.doc");
fmt::println("替换文件名后: {}", modPath.string());
modPath.replace_extension(".txt");
fmt::println("替换扩展名后: {}", modPath.string());

输出

替换文件名后: "/home/user/documents/newreport.doc"
替换扩展名后: "/home/user/documents/newreport.txt"

注意事项:

  1. 路径操作是纯字符串操作, 不涉及实际文件系统
  2. 这些操作在 Windows 和 Unix 系统上都能正常工作
  3. 使用 / 操作符组合路径比直接字符串拼接更安全
  4. lexically_normal() 可以清理路径中的 ...

最佳实践

  1. 错误处理:

    • 使用异常处理来捕获和管理错误情况. 许多 std::filesystem 函数会抛出 std::filesystem::filesystem_error, 例如在没有删除权限时.
  2. 路径类型:

    • 使用 fs::absolute 将相对路径转换为绝对路径, 以确保路径的完整性和清晰性.
  3. 性能优化:

    • 对于网络磁盘或涉及大量文件的操作, 尽量减少 I/O 操作以提高性能.
  4. 跨平台注意事项:

    • 路径格式: 虽然 std::filesystem 自动处理不同平台的路径分隔符差异(Windows 使用 \, 其他平台使用 /), 但建议始终使用 fs::path 进行路径操作.
    • 文件权限: 不同平台的文件权限管理可能不一致, 尤其是 fs::permissions.
  5. 路径组合:

    • 使用 / 操作符组合路径比直接字符串拼接更安全.
  6. 路径规范化:

    • 使用 lexically_normal() 清理路径中的 ..., 以获得更简洁的路径表示.

限制性


结论

std::filesystem 库是 C++ 重要的增加, 使文件和目录操作更加简单和可移植. 无论您是在写一个简单脚本, 还是构建复杂应用, 熟练该库都是值得一试的.

开始探索 std::filesystem, 你将发现它是你 C++ 程序装备中一个珍贵的工具!


资源链接

源码链接

源码链接