C++17引入的std::filesystem
库为文件和目录操作提供了跨平台的标准解决方案,极大简化了文件系统操作的复杂性。本文详细介绍核心功能与使用技巧。
1. 基础概念
1.1 核心术语
// 关键概念
file // 可读写的数据对象,包含内容与属性
directory // 文件容器,包含其他文件的条目
path // 标识文件位置的元素序列,可包含根名称、根目录与文件名序列
1.2 路径类型
// 三种主要路径类型
absolute_path // 完整路径,无歧义标识文件位置(如"/home/user/file.txt")
canonical_path // 不含符号链接、"."或".."元素的绝对路径
relative_path // 相对于某位置的路径(如"../docs/file.txt")
2. 核心功能类
2.1 path类
namespace fs = std::filesystem;
// 创建和操作路径
fs::path filepath{"dir/file.txt"};
std::cout << "文件名: " << filepath.filename() << '\n'; // "file.txt"
std::cout << "扩展名: " << filepath.extension() << '\n'; // ".txt"
std::cout << "父目录: " << filepath.parent_path() << '\n'; // "dir"
// 迭代路径组件
for(const auto& component : filepath) {
std::cout << "<" << component.string() << "> "; // <dir> <file.txt>
}
2.2 目录操作与迭代
// 目录迭代器:遍历单层目录内容
for(auto& entry : fs::directory_iterator{"/path/to/dir"}) {
std::cout << entry.path() << '\n';
}
// 递归目录迭代器:深度优先遍历整个目录树
for(auto& entry : fs::recursive_directory_iterator{"/path/to/dir"}) {
std::cout << "深度: " << entry.depth() << " - " << entry.path() << '\n';
}
// 目录条目:提供文件属性的缓存访问
fs::directory_entry entry{"/path/to/file.txt"};
if(entry.exists() && entry.is_regular_file()) {
std::cout << "文件大小: " << entry.file_size() << '\n';
}
3. 常用操作函数
3.1 文件操作
// 文件检测
bool exists = fs::exists("file.txt"); // 检查文件是否存在
bool is_dir = fs::is_directory("dir"); // 是否目录
bool is_empty = fs::is_empty("file.txt"); // 文件或目录是否为空
// 文件创建与修改
fs::create_directory("new_dir"); // 创建单个目录
fs::create_directories("nested/dirs/structure"); // 创建嵌套目录结构
fs::copy("source.txt", "dest.txt"); // 复制文件
fs::copy("src_dir", "dest_dir",
fs::copy_options::recursive); // 递归复制目录
fs::rename("old_name.txt", "new_name.txt"); // 重命名/移动文件
fs::remove("file.txt"); // 删除文件或空目录
uintmax_t count = fs::remove_all("dir"); // 递归删除,返回删除的文件数
// 文件属性
uintmax_t size = fs::file_size("large_file.dat"); // 获取文件大小
fs::file_time_type time = fs::last_write_time("log.txt"); // 获取最后修改时间
3.2 路径处理
// 路径转换
fs::path abs_p = fs::absolute("../relative/path"); // 转换为绝对路径
fs::path canon_p = fs::canonical("/home/../user/./dir"); // 转换为规范路径 (/user/dir)
fs::path rel_p = fs::relative("C:/data/file.txt", "C:/"); // 获取相对路径 (data/file.txt)
// 路径比较
bool equiv = fs::equivalent("file.txt", "./file.txt"); // 检查是否指向同一文件
// 文件系统信息
fs::path temp = fs::temp_directory_path(); // 获取临时目录
fs::path curr = fs::current_path(); // 获取当前工作目录
4. 实用示例
4.1 递归计算目录大小
uintmax_t get_directory_size(const fs::path& dir_path) {
uintmax_t size = 0;
// 检查路径是否存在且是目录
if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
return size;
}
// 递归遍历目录
for (const auto& entry : fs::recursive_directory_iterator(dir_path)) {
if (fs::is_regular_file(entry)) {
// 累加文件大小
size += fs::file_size(entry);
}
}
return size;
}
// 使用示例
void print_dir_size() {
fs::path dir = "/path/to/directory";
try {
uintmax_t size = get_directory_size(dir);
std::cout << "目录大小: " << size << " 字节" << '\n';
std::cout << " " << (size / (1024.0 * 1024.0)) << " MB" << '\n';
} catch (const fs::filesystem_error& e) {
std::cerr << "错误: " << e.what() << '\n';
}
}
4.2 简易文件备份工具
void backup_directory(const fs::path& source, const fs::path& backup_root) {
// 创建带时间戳的备份目录
auto now = std::chrono::system_clock::now();
auto time_t_now = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time_t_now), "%Y%m%d_%H%M%S");
fs::path backup_dir = backup_root / (source.filename().string() + "_" + ss.str());
try {
// 创建备份目录
fs::create_directories(backup_dir);
// 复制内容
fs::copy(source, backup_dir,
fs::copy_options::recursive |
fs::copy_options::update_existing);
std::cout << "备份完成! 大小: "
<< (get_directory_size(backup_dir) / (1024.0 * 1024.0))
<< " MB" << '\n';
} catch (const fs::filesystem_error& e) {
std::cerr << "备份错误: " << e.what() << '\n';
}
}
5. 最佳实践
5.1 异常处理
try {
fs::rename("source.txt", "destination.txt");
} catch(const fs::filesystem_error& e) {
std::cerr << "错误: " << e.what() << '\n';
std::cerr << "源路径: " << e.path1() << '\n';
std::cerr << "目标路径: " << e.path2() << '\n';
}
5.2 性能优化技巧
// 低效方式: 每次调用status()都会查询文件系统
if (fs::is_regular_file(path) && fs::file_size(path) > 1024) { /* ... */ }
// 高效方式: 使用缓存的directory_entry
fs::directory_entry entry(path);
if (entry.is_regular_file() && entry.file_size() > 1024) { /* ... */ }
// 控制递归行为,避免不必要的遍历
fs::recursive_directory_iterator it(dir_path, fs::directory_options::skip_permission_denied);
for (auto it = fs::recursive_directory_iterator(dir_path);
it != fs::recursive_directory_iterator(); ++it) {
if (should_skip_this_dir(*it)) {
it.disable_recursion_pending(); // 不递归进入当前目录
}
}
5.3 跨平台注意事项
// 不推荐:手动拼接路径
fs::path p = dir_path + "/" + filename; // 在Windows上可能有问题
// 推荐:使用路径连接操作符
fs::path p = dir_path / filename; // 在所有平台上都能正确工作
// 检查特定文件系统功能支持
try {
fs::create_symlink("target_file", "link_to_file");
} catch (const fs::filesystem_error& e) {
if (e.code() == std::errc::operation_not_supported) {
std::cout << "当前文件系统不支持符号链接\n";
}
}
6. 编译注意事项
# GNU实现(9.1之前)需要链接选项
g++ -std=c++17 main.cpp -lstdc++fs
# LLVM实现(9.0之前)需要链接选项
clang++ -std=c++17 main.cpp -lc++fs
# 较新版本通常不需要额外链接选项
g++ -std=c++17 main.cpp # GNU 9.1+
clang++ -std=c++17 main.cpp # LLVM 9.0+
总结
std::filesystem
库极大简化了跨平台文件操作。掌握path
、directory_entry
和各种迭代器的用法,加上正确的异常处理,可以使文件操作代码更加健壮与可移植。