在 C++17 标准中,<filesystem> 库被正式纳入标准库,提供了一套统一、跨平台的文件系统操作接口。相比传统的 dirent.h、windows.h 等平台特定的 API,std::filesystem 极大地简化了路径操作、文件和目录的创建、遍历、属性查询以及移动、复制等功能。本文将从基本概念、常用类型和函数入手,帮助你快速掌握 C++17 的文件系统操作。
1. 基本概念与命名空间
#include <filesystem>
namespace fs = std::filesystem;
- 路径(path):
fs::path表示一个文件系统路径,可以是相对路径、绝对路径、UNC 路径等。其内部使用std::string或std::wstring存储,提供了大量成员函数用于解析、拼接、获取文件名、扩展名等。 - 文件系统句柄:C++ 标准并未直接定义句柄,但提供了
fs::file_status(文件状态信息)和fs::directory_entry(目录条目)等结构体,用于描述文件属性。
2. 路径操作
fs::path p1("/home/user/documents");
fs::path p2("report.txt");
// 拼接路径
fs::path full = p1 / p2; // /home/user/documents/report.txt
// 访问路径元素
std::cout << "Filename: " << full.filename() << '\n'; // report.txt
std::cout << "Stem: " << full.stem() << '\n'; // report
std::cout << "Extension: " << full.extension() << '\n'; // .txt
// 转为字符串
std::string str = full.string();
路径类支持运算符重载,使用 /、+= 等可以方便地进行路径拼接,兼容 Windows 与 POSIX 语义。
3. 文件和目录属性
fs::file_status status = fs::status(full); // 查询文件状态
bool exists = fs::exists(full); // 是否存在
bool is_regular = fs::is_regular_file(full); // 是否普通文件
bool is_dir = fs::is_directory(full); // 是否目录
// 获取文件大小
auto file_size = fs::file_size(full);
注意:
fs::status可能抛出异常,如果你只想检查是否存在,使用fs::exists更安全。
4. 创建、移动、删除
// 创建目录(递归)
fs::create_directories("/tmp/a/b/c");
// 创建单个文件夹
fs::create_directory("/tmp/new_folder");
// 复制文件
fs::copy(full, "/tmp/backup/report.txt", fs::copy_options::overwrite_existing);
// 移动文件
fs::rename(full, "/tmp/new_location/report.txt");
// 删除文件
fs::remove("/tmp/old_file.txt");
// 删除目录(若为空)
fs::remove("/tmp/empty_dir");
// 删除目录(递归)
fs::remove_all("/tmp/old_folder");
fs::copy_options 枚举提供了多种复制策略,例如 skip_existing、overwrite_existing、recursive 等,能满足不同需求。
5. 遍历目录
C++17 提供了两种主要遍历方式:
5.1 基础遍历
for (const auto& entry : fs::directory_iterator("/home/user")) {
std::cout << entry.path() << '\n';
}
directory_iterator:非递归遍历,只列出指定目录下的直接子项。- 异常:若遇到不可读目录,迭代器会抛出
std::filesystem::filesystem_error。
5.2 递归遍历
for (const auto& entry : fs::recursive_directory_iterator("/home/user")) {
std::cout << entry.path() << '\n';
}
recursive_directory_iterator:递归遍历整个目录树。- 过滤器:可以在构造函数中传入
fs::directory_options::skip_permission_denied,忽略权限不足的文件夹,避免异常中断。
6. 文件权限和时间戳
// 设置权限
fs::permissions(full, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace);
// 获取权限
auto perms = fs::status(full).permissions();
std::cout << std::bitset<16>(static_cast<unsigned>(perms)) << '\n';
// 访问时间戳
auto ftime = fs::last_write_time(full);
std::time_t cftime = decltype(ftime)::clock::to_time_t(ftime);
std::cout << std::asctime(std::localtime(&cftime));
文件权限的位掩码与 POSIX 或 Windows 的权限概念保持一致,兼容多平台。
7. 常见错误与调试
- 路径非法:Windows 中路径包含冒号
:、问号?等非法字符,fs::path在构造时不会检查,但在访问文件系统时会抛异常。 - 异常处理:所有
std::filesystem函数会在错误时抛std::filesystem::filesystem_error,因此建议使用try/catch捕获或在代码中使用fs::exists预检查。 - 跨平台差异:
fs::path::native()返回平台原生字符串,注意编码问题(Windows 上使用 UTF-16)。
8. 代码实例:复制项目目录
下面给出一个完整示例,演示如何递归复制一个目录到另一个位置,同时忽略权限不足的子目录。
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
void copy_directory(const fs::path& src, const fs::path& dst) {
if (!fs::exists(src) || !fs::is_directory(src))
throw std::runtime_error("Source directory does not exist or is not a directory.");
fs::create_directories(dst);
for (const auto& entry : fs::recursive_directory_iterator(src,
fs::directory_options::skip_permission_denied)) {
try {
const auto& path = entry.path();
auto relative = fs::relative(path, src);
auto target = dst / relative;
if (entry.is_directory())
fs::create_directories(target);
else if (entry.is_regular_file())
fs::copy_file(path, target, fs::copy_options::overwrite_existing);
} catch (const fs::filesystem_error& ex) {
std::cerr << "Failed to copy " << entry.path() << ": " << ex.what() << '\n';
}
}
}
int main() {
try {
fs::path source = "/home/user/project";
fs::path destination = "/tmp/project_backup";
copy_directory(source, destination);
std::cout << "复制完成!\n";
} catch (const std::exception& ex) {
std::cerr << "错误: " << ex.what() << '\n';
}
}
运行后,/tmp/project_backup 将包含与原目录结构相同的所有文件。
9. 小结
std::filesystem提供了跨平台的文件系统操作接口,减少了平台特定代码。- 路径、属性、权限、时间戳、遍历等功能都以对象化方式暴露,语法简洁。
- 异常机制帮助捕获错误,但也需要在高性能场景下注意性能开销。
通过掌握上述基本使用方法,你可以在 C++17 项目中轻松完成文件系统相关的需求,提升代码可维护性与可移植性。祝你编码愉快!