C++文件操作

在C++17之前,C++并没有跨平台的统一文件操作解决方案,不同的平台需要依赖不同的API或者第三方库来处理文件操作。C++17引入的std::filesystem就解决了这个痛点。

常见函数

以下按与你之前相似的格式,列出并详解 <filesystem> 中日常最常见的函数及其关键点:

  • fs::exists(p)

    • 判断路径 p 是否存在(文件、目录或符号链接)。
    • 返回truefalse
    • 不抛异常
    • 比起捕获异常更轻量
    • 若无权限访问,也返回 false
  • fs::status(p) / fs::symlink_status(p)

    • 获取路径的类型和权限信息。
    • 返回file_status,可用 status.type() 判断类型。
    • 两个函数的区别在于symlink_status 不会跟随符号链接。
    • 调用前无需 exists()
    • 若出错会抛 filesystem_error
  • fs::is_directory(p) / fs::is_regular_file(p) / fs::is_symlink(p) / is_block_file() / is_fifo()

    • 基于 status() 返回值判断是否为目录、普通文件、符号链接、块设备(例如/dev/sda)和命名管道。分别对应下面的file_type枚举值
      • file_type::regular
      • file_type::directory
      • file_type::symlink
      • file_type::block
      • file_type::fifo
    • p 不存在或权限不足会抛异常,推荐先 exists() 再判类型。
  • fs::create_directory(p)

    • 创建单级目录。
    • 返回创建成功返回 true,已存在则 false
    • 失败抛异常。
    • 不会递归
    • 父目录缺失时也会抛异常。
  • fs::create_directories(p)

    • 递归创建所有缺失的目录层级。
    • 返回如有新目录则 true,否则 false
    • 失败抛异常。
    • 避免先 exists() 检查
    • 有带 std::error_code& 重载可免异常。
  • fs::remove(p)

    • 删除单个文件或空目录。
    • 返回删除成功返回 true,否则 false(如文件不存在)
    • 失败抛异常。
    • 不递归;非空目录用 remove_all()
  • fs::remove_all(p)

    • 递归删除路径及其所有内容。
    • 返回删除的条目总数(含自身)
    • 失败抛异常。
    • 风险较高,调用前请确保路径正确
    • 支持带 error_code 重载。
  • fs::copy(src, dst, opts)

    • 复制文件或目录
    • 无返回值
    • options可以取
      • fs::copy_options::recursive:递归复制目录。
      • update_existing:只覆盖目标中已有的条目且源更新时才替换。
      • skip_existingoverwrite_existingcopy_symlinks 等。
    • 失败抛异常。
    • 默认不递归,若复制目录必须加 fs::copy_options::recursive
    • 对目录结构复杂时留意符号链接选项。
  • fs::copy_file(src, dst, opts)

    • 仅复制单个文件。
    • 返回true(复制)或 false(跳过,如目标不存在或相同时间戳且加了 update_existing
    • 失败抛异常。
    • 相比 copy() 更精细,推荐文件级操作时使用。
  • fs::rename(old_p, new_p)

    • 原子化地移动或重命名文件/目录。等同于 POSIX rename()
    • 无返回值
    • 失败抛异常。
    • 跨设备会失败,需改用 copy() + remove();目标存在时覆盖行为依平台而异。
  • fs::file_size(p)

    • 返回普通文件大小(字节)。
    • 返回uintmax_t,失败抛异常(非文件或不存在)。
    • 对大文件很快
    • 若需安全接口,可用带 error_code 的重载。
  • fs::last_write_time(p)

    • 获取最后修改时间。
    • 返回file_time_type,可转换成 std::chrono 时间点
    • 失败抛异常。
    • 可与 system_clocksteady_clock 互转,判断文件新旧。
  • fs::current_path() / fs::current_path(p)

    • 获取或设置当前工作目录。
    • 返回获取时返回 path
    • 设置失败抛异常。
    • 设置前确保目录存在;影响进程内所有相对路径解析。
  • fs::absolute(p) / fs::canonical(p)

    • 将路径转换为绝对形式。
      • absolute:不解析符号链接;
      • canonical:解析所有符号链接,去除“..”、“.”。
    • 返回转换后的绝对路径
    • 失败抛异常。
    • canonicalabsolute 更“真实”,但更耗时。
  • fs::relative(p, base)

    • 计算从 basep 的相对路径
    • 如果 p 无法由 base 表达,则返回未经修改的 p
    • 返回值:一个 path,表示相对路径。
    • 两个路径最好都使用同一风格(绝对或相对)并归一化(canonicalabsolute)后调用,以避免不可预期的“原样返回”
    • base 并非 p 的父层级,结果路径会包含“..”以回退;
    • 不抛异常,但若路径权限有问题,可能在内部调用 canonical 时抛 filesystem_error
  • fs::permissions(p, perms, opt)

    • 修改目标的权限位。
    • 参数:perms 是欲设置/增加/移除的权限掩码,opt 指明操作模式(addremovereplace)。
    • Windows/Unix 表现略有差异;请先查询当前权限再修改。
  • fs::space(p)

    • 获取路径所在文件系统的容量信息。
    • 返回space_info{ capacity, free, available }
    • 失败抛异常。
    • 区分 free(总空闲)和 available(给当前用户的空闲)。
  • fs::equivalent(p1, p2)

    • 判断两个路径是否指向同一个文件或目录。
    • 返回true/false
    • 失败抛异常。
    • 可用来检查硬链接或跨路径比较。
  • fs::create_symlink(target, link) / fs::create_directory_symlink(target, link) / fs::create_hard_link(target, link)

    • 分别创建符号链接(文件或目录)或硬链接。
    • 无返回值
    • 权限不足或目标不存在时抛异常。
  • fs::read_symlink(p)

    • 读取符号链接的目标路径(不跟随)。
    • 返回指向目标的 path
    • 失败抛异常。
    • p 不是符号链接会抛异常,可先 is_symlink() 再读。
  • fs::directory_iterator(dir) / fs::recursive_directory_iterator(dir)

    • 迭代遍历目录项。
      • 非递归:仅遍历第一层;
      • 递归:深入所有子目录。
    • recursive_directory_iterator 支持 disable_recursion_pending() 跳过特定子目录
    • 遇到权限问题可设置 options::skip_permission_denied

std::filesystem::path

以下是基于你给出的结构,使用统一格式,详细介绍 std::filesystem::path 类的常见操作,包括构造、拼接、分解、遍历、比较等功能:

类的构造和成员函数

  • path p1("dir/file.txt") / path p2 = u8"目录/文件.txt"

    • 使用字符串、std::string, std::wstring, UTF-8 字面量等构造路径对象。
    • 跨平台封装路径表示,自动处理分隔符(如 Windows 用 \,Unix 用 /)。
    • 构造仅仅是对字符串的封装,不会检查路径是否存在。
  • p.string(), p.u8string()

    • 将路径对象转换为 std::string 或 UTF-8 编码的字符串。

路径的拼接

  • p1 / p2

    • 拼接路径,自动加上或去掉分隔符。例如:path("dir") / "file.txt""dir/file.txt"
    • 右侧参数若为绝对路径则会“替换”左侧路径
  • p.append("subdir")

    • / 类似,但是是path类的成员函数。
    • 等价于 p /= "subdir",使用场景偏向链式操作。
  • p.concat("file.txt")

    • 直接字符串拼接,不添加路径分隔符
    • path("dir/").concat("file.txt")"dir/file.txt"
    • 关键点:用于修改文件名或扩展名部分,不用于路径层级拼接。

路径的分解

  • p.parent_path()

    • 获取父目录路径。例如path("a/b/c.txt").parent_path()"a/b"
    • 若路径为根目录或文件名,则返回空路径
  • p.filename()

    • 获取最后一级(文件名或最后目录名)。例如path("a/b/c.txt").filename()"c.txt"
    • 路径以分隔符结尾时,返回空路径。
  • p.extension()

    • 获取文件扩展名(含“.”)。例如path("c.txt").extension()".txt"
    • 若文件名无扩展名,返回空路径。
  • p.stem()

    • 去除扩展名后的主文件名。例如"c.txt""c"
    • 可结合 stem() + extension() 修改或重构文件名。

路径的遍历

  • for (auto& part : path("a/b/c.txt"))

    • 遍历路径的每一级组件(如 a, b, c.txt)。
    • 根目录和空路径也会被视为组件;
    • 可以用 *begin() 判断路径是否以 / 开头。
  • path::iterator

    • 双向迭代器

    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      path p("a/b/c.txt");
      for (auto it = p.begin(); it != p.end(); ++it)
      std::cout << *it << '\n';
      /*
      输出:
      "a"
      "b"
      "c.txt"
      */
    • 用于逐层分析路径结构

路径的比较

  • ==, !=

    • 比较路径字符串是否完全相等(字节级别,不解析符号链接)。
    • 对路径的格式敏感,"a/b""a//b" 不等。
  • <, >, <=, >=

    • 按字典序比较路径,常用于排序或存入有序容器(如 std::set<path>)。
    • 不是基于路径深度或文件存在与否,仅按字面比较。
  • p.lexically_normal()

    • 返回语义上等价但简化的路径(处理“.”、“..”、“重复分隔符”等)。例如"a/./b/../c""a/c"
    • 不访问文件系统,仅字符串逻辑变换
    • 用于规范化路径前的预处理。

常用操作

路径处理

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <filesystem>

namespace fs = std::filesystem;​

fs::path p1 = "data"; // 当前平台路径风格​
fs::path p2 = R"(D:\logs\)"; // 原生字符串处理特殊字符​
fs::path full_path = p1 / "2023" / "report.csv"; // 自动处理分隔符​
std::cout << full_path << "\n"; // 输出:data/2023/report.csv​

std::cout << full_path.extension() << "\n"; // 获取文件扩展名,输出:.csv​
// 路径标准化​
fs::path messy = "data//./tmp/../config.ini";​
std::cout << messy.lexically_normal(); // 输出:data/config.ini

获取文件状态与元数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
auto check_file = [](const fs::path& p) {​
if (!fs::exists(p)) return;​

fs::file_status s = fs::status(p);​//获取文件状态
switch(s.type()) {​
case fs::file_type::regular: ​
std::cout << "文件大小:" << fs::file_size(p) << "字节\n";​
break;​
case fs::file_type::directory:​
std::cout << "目录最后修改时间:"
<< fs::last_write_time(p).time_since_epoch().count();​
break;​
}​
};​

目录遍历

1
2
3
4
5
6
7
uintmax_t total_size = 0;​
for (const auto& entry : fs::recursive_directory_iterator("logs")) {​
if (entry.is_regular_file()) {​
total_size += entry.file_size();​
}​
}​
std::cout << "总日志大小:" << total_size << "字节";​

C++文件操作
https://guts.homes/2025/06/27/cpp-filesystem/
作者
guts
发布于
2025年6月27日
许可协议