在C++17之前,想要用C++操作文件系统,开发者要么依赖Boost.Filesystem这个第三方库,要么针对不同平台分别调用Windows的Win32 API和Linux的POSIX接口,代码维护成本相当高。C++17标准正式将filesystem库纳入std命名空间,从此一份代码就能在Windows、Linux、macOS上完成路径处理、目录遍历、文件复制等几乎所有常见的文件系统操作。本文将从路径对象讲起,逐步覆盖目录迭代、文件属性、错误处理等核心内容。

path路径类:一切操作的起点
std::filesystem::path是整个库的基础,它负责表示和解析路径。与传统的字符串路径不同,path类会自动处理平台差异。在Windows上,路径分隔符是反斜杠\,例如C:\Users\test\file.txt;而在Linux和macOS上是正斜杠/。path类内部会根据编译目标平台自动选择正确的格式,你甚至可以用/运算符来拼接路径,这可能是整个库最优雅的设计。
路径拼接的写法非常直观。通过重载的operator/,可以把目录和文件名一层层连接起来,不需要手动判断分隔符:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
// 使用 / 运算符拼接路径
fs::path dir = "C:\\projects";
fs::path file = dir / "demo" / "test.txt";
// 在Windows上输出 C:\projects\demo\test.txt
// 在Linux上输出 C:/projects/demo/test.txt 或按字面处理
// 拼接操作会返回新对象,也可以用 /= 就地拼接
fs::path p = "data";
p /= "config.ini";
// 常用成员函数
std::string stem = file.stem().string(); // 文件名去掉扩展名: test
std::string ext = file.extension().string(); // 扩展名: .txt
std::string name = file.filename().string(); // 完整文件名: test.txt
fs::path parent = file.parent_path(); // 父目录路径
return 0;
}需要特别注意的是,path对象在构造时只做语法层面的解析,不会去访问磁盘。也就是说fs::path("C:\\not_exist\\a.txt")是合法的,即使这个路径根本不存在。path还提供了is_absolute()和is_relative()来判断绝对路径与相对路径,用lexically_normal()可以做纯语法的路径规范化,比如消除多余的.和..。如果要把相对路径转成绝对路径,则需要调用fs::absolute()或fs::canonical(),后者要求路径必须真实存在。
目录遍历与文件操作
遍历目录是文件系统操作中最常见的需求。filesystem提供了directory_iterator用于单层遍历,以及recursive_directory_iterator用于递归遍历所有子目录。配合fs::is_regular_file、fs::is_directory等判断函数,可以很轻松地筛选出目标文件:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path root = "./src";
// 单层遍历
for (const auto& entry : fs::directory_iterator(root)) {
if (entry.is_regular_file()) {
std::cout << "文件: " << entry.path().string() << "\n";
}
}
// 递归遍历所有子目录,找出全部.cpp文件
for (const auto& entry : fs::recursive_directory_iterator(root)) {
if (entry.path().extension() == ".cpp") {
std::cout << entry.path().string() << "\n";
}
}
return 0;
}文件层面的操作同样齐全。fs::copy可以复制文件或整个目录树,通过传入fs::copy_options::recursive等标志控制覆盖行为;fs::rename同时承担重命名和移动的职责;fs::remove删除单个文件,fs::remove_all递归删除整个目录并返回删除的条目数;fs::create_directories可以一次创建多级嵌套目录,比POSIX的mkdir好用得多。下面是一个完整的目录复制示例:
namespace fs = std::filesystem;
// 递归复制目录,已存在的文件跳过
void backup(const fs::path& src, const fs::path& dst) {
if (!fs::exists(dst)) {
fs::create_directories(dst);
}
for (const auto& entry : fs::directory_iterator(src)) {
const fs::path target = dst / entry.path().filename();
if (entry.is_directory()) {
backup(entry.path(), target);
} else {
fs::copy(entry.path(), target,
fs::copy_options::skip_existing);
}
}
}使用copy时要留意默认行为:如果目标文件已存在,默认会抛出异常。可以通过fs::copy_options::overwrite_existing强制覆盖,或者skip_existing跳过,避免程序意外中断。另外fs::copy默认只复制目录的直接子项,想复制整棵目录树记得加上recursive选项。
文件属性查询与错误处理
fs::file_size返回文件字节数,fs::last_write_time返回最后修改时间点,fs::space可以查询磁盘剩余空间。fs::status返回的file_status对象能判断文件类型,比如是否是符号链接、是否是套接字。这些信息在实现资源管理、增量备份等功能时非常实用。
namespace fs = std::filesystem;
void inspect(const fs::path& p) {
if (fs::exists(p)) {
std::cout << "大小: " << fs::file_size(p) << " 字节\n";
auto space = fs::space(p);
std::cout << "可用空间: " << space.available << "\n";
}
}filesystem的错误处理有两种模式。第一种是异常模式,所有函数在失败时默认抛出fs::filesystem_error异常,可以通过e.what()和e.code()获取详细信息。第二种是错误码模式,给函数额外传入一个std::error_code参数,失败时函数不抛异常而是填充错误码。在性能敏感或不能让异常扩散的代码中,推荐使用错误码版本;在工具类脚本代码中,异常版本配合try-catch更简洁:
namespace fs = std::filesystem;
void safeRemove(const fs::path& p) {
std::error_code ec;
fs::remove_all(p, ec);
if (ec) {
std::cerr << "删除失败: " << ec.message() << "\n";
}
}编译配置与常见坑
使用filesystem库时编译配置经常踩坑。GCC 9之前的版本需要手动链接stdc++fs库,即在编译命令后加上-lstdc++fs;GCC 9及以上以及Clang的新版本已经不需要额外链接。MSVC则只要C++语言标准设置为/std:c++17即可直接使用。如果你的项目需要兼容老编译器,CMake中可以这样处理:
# CMakeLists.txt 片段
cmake_minimum_required(VERSION 3.10)
project(fs_demo)
set(CMAKE_CXX_STANDARD 17)
add_executable(fs_demo main.cpp)
# GCC 8及更早版本需要额外链接stdc++fs
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9)
target_link_libraries(fs_demo PRIVATE stdc++fs)
endif()还有几个实际开发中容易遇到的问题值得提醒。其一,路径中包含中文时,path::string()在Windows上可能因编码问题显示乱码,此时建议用path::u8string()或直接用宽字符版本path::wstring()处理。其二,recursive_directory_iterator遍历过程中如果目录结构发生变化,行为是未定义的,不要在遍历的同时删除或移动文件。其三,符号链接默认会被copy跟随,如果只想复制链接本身,需要显式指定copy_options::copy_symlinks。掌握这些细节后,filesystem库基本可以完全替代平台相关的文件API,让代码真正实现一次编写、处处编译运行。
C++17 filesystem跨平台文件操作std::filesystem修改时间:2026-09-05 15:06:37