C++17 中 std::filesystem 的实战应用

在 C++17 标准中,std::filesystem 库为文件和目录的操作提供了统一、跨平台的接口。本文将通过一个完整的示例,展示如何使用 std::filesystem 对文件系统进行遍历、复制、删除、以及获取文件属性等常见操作,并结合异常处理和现代 C++ 的语法特性,让代码既简洁又易维护。

1. 关键头文件与命名空间

#include <iostream>
#include <filesystem>
#include <fstream>
#include <vector>
#include <string>

namespace fs = std::filesystem;

使用 namespace fs = std::filesystem; 可以简化后续代码中对库的引用。

2. 基本文件与目录操作

2.1 判断路径是否存在

fs::path dir{"./example_dir"};
if (!fs::exists(dir)) {
    std::cout << "目录不存在,正在创建...\n";
    fs::create_directory(dir);
}

2.2 创建多级目录

fs::path nested{"./example_dir/sub1/sub2"};
fs::create_directories(nested); // 若父级不存在则一并创建

2.3 创建临时文件

fs::path temp_file = fs::temp_directory_path() / "demo.tmp";
std::ofstream ofs(temp_file);
ofs << "临时数据\n";
ofs.close();

3. 文件遍历与筛选

使用 recursive_directory_iterator 可以递归遍历目录。

std::vector<fs::path> txt_files;
for (const auto& entry : fs::recursive_directory_iterator(dir)) {
    if (entry.is_regular_file() && entry.path().extension() == ".txt") {
        txt_files.push_back(entry.path());
        std::cout << "找到文本文件: " << entry.path() << '\n';
    }
}

4. 复制与移动

fs::path src = dir / "sample.txt";
fs::path dst = nested / "sample_copy.txt";

try {
    fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
    std::cout << "文件复制成功!\n";
} catch (const fs::filesystem_error& e) {
    std::cerr << "复制失败: " << e.what() << '\n';
}

移动(rename)操作同样简便:

fs::rename(src, src.parent_path() / "moved_sample.txt");

5. 删除文件与目录

// 删除单个文件
fs::remove(src);

// 删除非空目录,需使用 recursive_remove_all
fs::remove_all(dir); // 递归删除

6. 文件属性查询

fs::path file = nested / "sample_copy.txt";

if (fs::exists(file)) {
    auto perms = fs::status(file).permissions();
    std::cout << "权限: " << std::oct << static_cast<unsigned>(perms) << '\n';

    auto size = fs::file_size(file);
    std::cout << "文件大小: " << size << " 字节\n";

    auto last_write = fs::last_write_time(file);
    std::time_t cftime = decltype(last_write)::clock::to_time_t(last_write);
    std::cout << "上次修改时间: " << std::asctime(std::localtime(&cftime));
}

7. 异常处理与错误码

std::filesystem 的多数函数会在遇到错误时抛出 std::filesystem_error。可以捕获异常,也可以使用 std::error_code 作为第二个参数,来获得错误码而不抛异常。

fs::error_code ec;
fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
if (ec) {
    std::cerr << "复制错误: " << ec.message() << '\n';
}

8. 完整示例代码

#include <iostream>
#include <filesystem>
#include <fstream>
#include <vector>
#include <string>
#include <chrono>

namespace fs = std::filesystem;

int main() {
    try {
        // 1. 准备目录
        fs::path base_dir{"./demo_fs"};
        fs::create_directories(base_dir / "subdir");

        // 2. 创建文件
        std::vector<fs::path> files = {
            base_dir / "file1.txt",
            base_dir / "file2.log",
            base_dir / "subdir" / "file3.txt"
        };
        for (auto& p : files) {
            std::ofstream ofs(p);
            ofs << "内容示例: " << p.filename() << '\n';
        }

        // 3. 遍历并复制 .txt 文件
        for (const auto& entry : fs::recursive_directory_iterator(base_dir)) {
            if (entry.is_regular_file() && entry.path().extension() == ".txt") {
                fs::path target = base_dir / "txt_copy" / entry.path().filename();
                fs::create_directories(target.parent_path());
                fs::copy_file(entry.path(), target, fs::copy_options::overwrite_existing);
                std::cout << "复制 " << entry.path() << " 到 " << target << '\n';
            }
        }

        // 4. 删除日志文件
        for (const auto& entry : fs::directory_iterator(base_dir)) {
            if (entry.is_regular_file() && entry.path().extension() == ".log") {
                fs::remove(entry.path());
                std::cout << "已删除日志文件: " << entry.path() << '\n';
            }
        }

        // 5. 输出文件属性
        for (const auto& entry : fs::recursive_directory_iterator(base_dir)) {
            if (entry.is_regular_file()) {
                std::cout << entry.path() << " 大小: " << fs::file_size(entry.path()) << " 字节, 修改时间: " << std::chrono::system_clock::to_time_t(
                                 fs::last_write_time(entry.path()).time_since_epoch()) << '\n';
            }
        }

    } catch (const fs::filesystem_error& e) {
        std::cerr << "文件系统错误: " << e.what() << '\n';
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

9. 小结

  • std::filesystem 提供了对文件系统的统一、高效操作,避免了平台差异导致的繁琐代码。
  • 通过异常或错误码两种方式可以优雅地处理错误。
  • 与 C++17 的其他特性(如 autostructured bindingsrange-for 等)结合使用,能写出既简洁又易读的文件系统代码。

下一步,你可以尝试在此基础上实现更复杂的功能,例如:

  • 递归删除某一类型的文件;
  • 监控目录变更(C++20 开始可用 std::filesystem::file_time_type 与系统事件结合);
  • 编写跨平台的压缩/解压工具,使用 std::filesystem 与第三方压缩库配合。

祝你编码愉快!

发表评论