文件系统 (filesystem)
C++17 引入的文件系统库,提供跨平台的路径操作、目录遍历与文件属性查询。
在此之前,这些操作要么用 C 的 <dirent.h>、<sys/stat.h>(POSIX)或 <windows.h>(Windows),要么用 Boost.Filesystem。现在标准库有了统一接口。
1. 引入
#include <filesystem>
namespace fs = std::filesystem; // 标准做法:起个短别名
2. 核心概念
1. path:路径
std::filesystem::path 表示一个路径,它不检查路径是否存在,只是字符串的封装,负责处理不同平台的路径格式差异。
fs::path p1 = "/home/user/file.txt"; // POSIX
fs::path p2 = "C:\\Users\\file.txt"; // Windows
fs::path p3 = "/home" / "user" / "file.txt"; // 用 / 拼接
路径拼接用 operator/,它会自动处理分隔符:
fs::path dir = "/home/user";
fs::path file = dir / "data" / "input.txt"; // /home/user/data/input.txt
2. 路径的组成部分
fs::path p = "/home/user/data.txt";
p.root_name(); // ""(Windows 上是 "C:")
p.root_directory(); // "/"
p.parent_path(); // "/home/user"
p.filename(); // "data.txt"
p.stem(); // "data"
p.extension(); // ".txt"
3. 文件类型
enum class file_type {
none, // 不存在或无法确定
not_found, // 不存在
regular, // 普通文件
directory, // 目录
symlink, // 符号链接
block, // 块设备
character, // 字符设备
fifo, // 命名管道
socket, // socket
unknown, // 存在但类型未知
};
3. 用法
1. 查询文件状态
fs::path p = "data.txt";
// 是否存在
if (fs::exists(p)) { }
// 类型判断
fs::is_regular_file(p); // 普通文件
fs::is_directory(p); // 目录
fs::is_symlink(p); // 符号链接
// 文件大小(字节)
auto size = fs::file_size(p);
// 最后修改时间
auto time = fs::last_write_time(p);
这些函数都有两个版本:一个是 fs::exists(p),失败时抛异常;另一个是 fs::exists(p, ec),把错误写进 std::error_code:
std::error_code ec;
if (fs::exists(p, ec)) { }
if (ec)
std::cerr << "错误: " << ec.message() << '\n';
在预期可能失败的地方(比如检查不存在的文件),用 error_code 版本,避免异常开销。
2. 目录操作
// 创建目录
fs::create_directory("newdir"); // 父目录必须存在
fs::create_directories("a/b/c"); // 递归创建,父目录不存在也行
// 删除
fs::remove("file.txt"); // 删除文件或空目录
fs::remove_all("dir"); // 递归删除
// 重命名/移动
fs::rename("old.txt", "new.txt");
// 复制
fs::copy("src.txt", "dst.txt");
fs::copy("srcdir", "dstdir", fs::copy_options::recursive); // 递归复制
3. 遍历目录
非递归遍历:
for (const auto& entry : fs::directory_iterator("."))
{
std::cout << entry.path() << '\n';
}
递归遍历:
for (const auto& entry : fs::recursive_directory_iterator("."))
{
if (entry.is_regular_file())
std::cout << entry.path() << " (" << entry.file_size() << " 字节)\n";
}
遍历顺序是未指定的。如果需要排序,先收集到容器再排:
std::vector<fs::path> files;
for (const auto& entry : fs::directory_iterator("."))
files.push_back(entry.path());
std::sort(files.begin(), files.end());
遍历过程中可以控制:
for (auto it = fs::recursive_directory_iterator("."); it != fs::recursive_directory_iterator(); ++it)
{
if (it->path().extension() == ".tmp")
it.disable_recursion_pending(); // 不进入这个目录
// 或者 it.pop() 跳过当前目录的剩余内容
}
4. 路径转换
fs::path p = "/home/user/data.txt";
// 转字符串
std::string s = p.string(); // 本地编码
std::wstring ws = p.wstring(); // 宽字符
std::u8string u8 = p.u8string(); // UTF-8(C++20)
std::u16string u16 = p.u16string(); // UTF-16
// 从字符串构造
fs::path p2 = std::string("/tmp/file");
在 Windows 上
path::string()可能因为编码问题失败或产生乱码。跨平台代码建议用u8string()(C++20 起返回std::u8string)。
5. 实用示例
递归统计目录大小:
std::uintmax_t directorySize(const fs::path& dir)
{
std::uintmax_t total = 0;
for (const auto& entry : fs::recursive_directory_iterator(dir))
{
if (entry.is_regular_file())
total += entry.file_size();
}
return total;
}
查找特定扩展名的文件:
std::vector<fs::path> findFiles(const fs::path& dir, const std::string& ext)
{
std::vector<fs::path> result;
for (const auto& entry : fs::recursive_directory_iterator(dir))
{
if (entry.is_regular_file() && entry.path().extension() == ext)
result.push_back(entry.path());
}
return result;
}
auto cppFiles = findFiles(".", ".cpp");
确保目录存在:
void ensureDirectory(const fs::path& dir)
{
if (!fs::exists(dir))
fs::create_directories(dir);
}
4. 注意事项
path 不验证存在性。构造 fs::path 只是封装字符串,不会检查路径是否有效。要检查得调用 fs::exists()。
directory_iterator 的顺序未指定。不要假设遍历顺序,需要有序就自己排序。
遍历时修改目录是未定义行为。不要在遍历过程中创建或删除文件,先收集路径再操作。
注意异常与 error_code 的选择。默认版本抛 fs::filesystem_error,带 error_code 参数的版本不抛。在「文件可能不存在」这类正常情况用后者。
file_size 对目录无效。目录的大小是平台相关的,没有可移植的含义。
符号链接的处理。默认情况下,is_directory(p) 会跟随符号链接。要判断链接本身,用 is_symlink(p);要获取链接目标,用 read_symlink(p)。
路径分隔符。写代码时用 /,path 会在 Windows 上自动转换。不要硬编码 \\。
C++17 支持情况。需要链接 stdc++fs(GCC 8)或 c++fs(Clang 7),较新版本已不需要:
# 老版本 GCC
g++ -std=c++17 main.cpp -lstdc++fs
last_write_time 的时钟。它用的是 std::filesystem::file_time_type,不是 system_clock,与 chrono 互转需要额外处理。