C++ 文件与文件系统(std::filesystem)
C++17 把"操作文件"纳入标准库:路径拼接、目录遍历、属性查询、复制删除,一套 API 跨 Windows/Linux。本篇函数与 shell 命令一一对照,全部示例在 WSL g++ 11(
-std=c++17)编译运行验证。命令行对照的 shell 用法见常用命令速查。
path:先认识路径类型
#include <filesystem>
#include <cstdio>
namespace fs = std::filesystem;
int main() {
fs::path p = "/var/log/myapp"; // 路径对象,跨平台分隔符
fs::path f = p / "app.log"; // operator/ 拼接:/var/log/myapp/app.log
std::printf("%s\n", f.c_str()); // 完整路径
std::printf("%s\n", f.parent_path().c_str()); // /var/log/myapp
std::printf("%s\n", f.filename().c_str()); // app.log
std::printf("%s\n", f.stem().c_str()); // app(不含扩展名)
std::printf("%s\n", f.extension().c_str()); // .log
}
要点:
fs::path惰性转换——它只是路径的表达,不要求文件存在;operator/是拼接运算符,比手动补/安全(Windows/Linux 通吃);- Windows 下
path内部使用wchar_t,取字符串时string()会做转换;涉及非 ASCII 文件名时留意编码。
查询:exists 一族
fs::exists(p) // 路径存在?(文件或目录都算)
fs::is_directory(p) // 是目录?
fs::is_regular_file(p) // 是普通文件?
fs::file_size(f) // 字节数(仅普通文件)
fs::last_write_time(f) // 最后修改时间(file_time_type)
fs::current_path() // 当前工作目录(无参读 / 有参改,等价 getcwd/chdir)
操作:与 shell 命令对照
| shell 命令 | std::filesystem |
|---|---|
mkdir -p a/b/c |
fs::create_directories("a/b/c") |
touch f |
std::ofstream f("f");(用流创建) |
cp src dst |
fs::copy_file(src, dst, fs::copy_options::overwrite_existing) |
cp -r src dst |
fs::copy(src, dst, fs::copy_options::recursive) |
mv a b |
fs::rename(a, b) |
rm f |
fs::remove(f) |
rm -rf dir |
fs::remove_all(dir)(返回删除的条数) |
pwd |
fs::current_path() |
#include <filesystem>
#include <fstream>
namespace fs = std::filesystem;
int main() {
fs::create_directories("demo/data"); // 递归建目录
{ std::ofstream("demo/data/a.txt") << "hello"; }
fs::copy_file("demo/data/a.txt", "demo/data/b.txt",
fs::copy_options::overwrite_existing);
fs::rename("demo/data/b.txt", "demo/data/c.txt");
std::printf("%lld bytes\n", // file_size 返回无符号,转 %lld
static_cast<long long>(fs::file_size("demo/data/a.txt")));
std::printf("%d\n", (int)fs::remove_all("demo")); // 递归删除
}
遍历:目录迭代器
#include <filesystem>
#include <cstdio>
namespace fs = std::filesystem;
int main() {
// 单层遍历(等价 ls dir)
for (const auto& entry : fs::directory_iterator("demo")) {
std::printf("%s %s\n",
entry.is_directory() ? "dir " : "file",
entry.path().filename().c_str());
}
// 递归遍历(等价 find dir -type f)
for (const auto& entry : fs::recursive_directory_iterator("demo")) {
if (entry.is_regular_file() && entry.path().extension() == ".txt")
std::printf("%s\n", entry.path().c_str());
}
}
directory_entry 自带 is_directory()/file_size() 等成员(内部缓存,比每次对 path 重新查询更高效)。
错误处理:两套重载
文件系统函数几乎都有两个版本——抛异常的默认版与接收 std::error_code 的 noexcept 版(思路见错误处理):
// 版本一:失败抛 filesystem_error
try {
fs::file_size("missing.txt");
} catch (const fs::filesystem_error& e) {
std::printf("error: %s\n", e.what()); // 含两个 path 与系统错误信息
}
// 版本二:失败填 error_code,不抛
std::error_code ec;
auto size = fs::file_size("missing.txt", ec);
if (ec) std::printf("error: %s\n", ec.message().c_str());
常驻服务、库代码建议用 error_code 版(不希望异常穿越边界);工具脚本用异常版更省事。
实战:统计目录下最大的文件
#include <filesystem>
#include <cstdio>
#include <string>
namespace fs = std::filesystem;
int main(int argc, char** argv) {
fs::path dir = argc > 1 ? argv[1] : ".";
std::string biggest;
long long biggest_size = -1;
for (const auto& entry : fs::recursive_directory_iterator(dir)) {
if (!entry.is_regular_file()) continue;
auto size = static_cast<long long>(entry.file_size());
if (size > biggest_size) {
biggest_size = size;
biggest = entry.path().string();
}
}
if (biggest_size >= 0)
std::printf("%s %lld bytes\n", biggest.c_str(), biggest_size);
}
(对应 shell:du -a dir | sort -n | tail -1,见排查工具箱。)
常见错误
| 错误 | 说明 |
|---|---|
| 相对路径依赖工作目录 | 服务由 systemd 启动时工作目录可能不同——配置里用绝对路径或以可执行文件位置为基准 |
remove 只删空目录/单文件 |
递归删除用 remove_all;误用 remove 删目录返回 false 并不报错 |
忘记 -std=c++17 |
GCC 9 之前还需显式链接 -lstdc++fs(g++ 11 不需要) |
| 遍历中修改目录结构 | 迭代器可能失效;先收集路径列表再操作 |
| Windows 与 Linux 分隔符 | 始终用 operator/ 拼接,不要手写字符串斜杠 |
| 权限与符号链接 | copy 默认不跟随目录符号链接;exists(p) 默认跟随,fs::symlink_status(p) 查链接本身 |