如何在 C++17 中使用 std::filesystem 进行跨平台文件操作

在现代 C++ 开发中,文件系统操作往往是不可或缺的一环。传统上,程序员需要依赖平台特定的 API(如 Windows 的 FindFirstFile / POSIX 的 opendir)或者第三方库(如 Boost.Filesystem)来完成文件读写、遍历、复制等任务。随着 C++17 标准的发布,标准库新增了 <filesystem> 头文件,提供了一套统一、跨平台、类型安全的文件系统接口,极大简化了这类任务。

下面我们通过一个完整示例来演示如何利用 std::filesystem 完成以下操作:

  1. 递归遍历指定目录,打印所有文件路径;
  2. 复制文件或目录到目标位置;
  3. 检查文件是否存在、获取文件大小、修改时间等元数据;
  4. 创建、删除文件与目录;
  5. 处理错误与异常。

代码示例均以 cpp 语法高亮,并可直接在支持 C++17 的编译器(如 GCC 8+、Clang 7+、MSVC 2017+)中编译运行。

#include <iostream>
#include <filesystem>
#include <fstream>
#include <chrono>
#include <iomanip>

namespace fs = std::filesystem;

// 1. 递归遍历目录
void print_all_files(const fs::path& dir) {
    if (!fs::exists(dir) || !fs::is_directory(dir)) {
        std::cerr << "错误: 目录不存在或不是目录: " << dir << '\n';
        return;
    }

    for (const auto& entry : fs::recursive_directory_iterator(dir)) {
        try {
            std::cout << (entry.is_directory() ? "[DIR ] " : "[FILE] ") << entry.path() << '\n';
        } catch (const fs::filesystem_error& e) {
            std::cerr << "访问错误: " << e.what() << '\n';
        }
    }
}

// 2. 复制文件或目录
void copy_item(const fs::path& src, const fs::path& dst) {
    try {
        if (fs::is_directory(src)) {
            fs::copy(src, dst, fs::copy_options::recursive | fs::copy_options::overwrite_existing);
        } else if (fs::is_regular_file(src)) {
            fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
        } else {
            std::cerr << "未知文件类型: " << src << '\n';
            return;
        }
        std::cout << "复制成功: " << src << " -> " << dst << '\n';
    } catch (const fs::filesystem_error& e) {
        std::cerr << "复制失败: " << e.what() << '\n';
    }
}

// 3. 获取文件元数据
void print_file_info(const fs::path& p) {
    try {
        if (!fs::exists(p)) {
            std::cout << "文件不存在: " << p << '\n';
            return;
        }

        std::cout << "路径:      " << p << '\n';
        std::cout << "类型:      ";
        if (fs::is_regular_file(p))   std::cout << "普通文件\n";
        else if (fs::is_directory(p)) std::cout << "目录\n";
        else if (fs::is_symlink(p))   std::cout << "符号链接\n";
        else                          std::cout << "其他\n";

        std::cout << "大小:      " << fs::file_size(p) << " 字节\n";

        auto ftime = fs::last_write_time(p);
        auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
            ftime - fs::file_time_type::clock::now()
            + std::chrono::system_clock::now());
        std::time_t cftime = std::chrono::system_clock::to_time_t(sctp);
        std::cout << "修改时间:  " << std::put_time(std::localtime(&cftime), "%F %T") << '\n';
    } catch (const fs::filesystem_error& e) {
        std::cerr << "获取信息失败: " << e.what() << '\n';
    }
}

// 4. 创建与删除
void create_and_remove_demo() {
    fs::path tmp_dir = "demo_dir";
    fs::path tmp_file = tmp_dir / "example.txt";

    // 创建目录
    try {
        if (fs::create_directory(tmp_dir)) {
            std::cout << "创建目录: " << tmp_dir << '\n';
        } else {
            std::cout << "目录已存在: " << tmp_dir << '\n';
        }

        // 写文件
        std::ofstream ofs(tmp_file);
        ofs << "Hello, std::filesystem!\n";
        ofs.close();
        std::cout << "写入文件: " << tmp_file << '\n';
    } catch (const fs::filesystem_error& e) {
        std::cerr << "创建失败: " << e.what() << '\n';
    }

    // 删除文件与目录
    try {
        if (fs::remove(tmp_file)) {
            std::cout << "删除文件: " << tmp_file << '\n';
        }
        if (fs::remove(tmp_dir)) {
            std::cout << "删除目录: " << tmp_dir << '\n';
        }
    } catch (const fs::filesystem_error& e) {
        std::cerr << "删除失败: " << e.what() << '\n';
    }
}

int main() {
    std::cout << "=== 文件系统演示 ===\n\n";

    // 1. 遍历当前目录
    std::cout << "1. 递归遍历当前目录:\n";
    print_all_files(fs::current_path());
    std::cout << '\n';

    // 2. 复制示例
    std::cout << "2. 复制示例 (如果目录存在):\n";
    if (fs::exists("src") && fs::is_directory("src")) {
        copy_item("src", "dst");
    } else {
        std::cout << "源目录 'src' 不存在,跳过复制。\n";
    }
    std::cout << '\n';

    // 3. 获取文件信息
    std::cout << "3. 文件信息:\n";
    print_file_info("demo.cpp"); // 替换为你自己的文件名
    std::cout << '\n';

    // 4. 创建与删除
    std::cout << "4. 创建与删除演示:\n";
    create_and_remove_demo();

    return 0;
}

关键点说明

  1. 命名空间简化
    namespace fs = std::filesystem; 让代码更简洁。

  2. 递归遍历
    fs::recursive_directory_iterator 能自动遍历子目录;可以通过 options 参数控制是否遵循符号链接等。

  3. 复制
    fs::copyfs::copy_filecopy_options 允许你覆盖已有文件、递归复制目录、保持权限等。

  4. 元数据
    fs::file_sizefs::last_write_timefs::is_regular_file 等函数提供了文件属性的安全访问。注意 last_write_time 返回的是一个抽象时间点,需要转换为系统时间以便打印。

  5. 错误处理
    std::filesystem 抛出的 filesystem_error 包含了系统错误码,可通过 e.code() 获取 std::error_code,进一步分析错误原因。

  6. 兼容性
    只要编译器支持 C++17 并开启 -std=c++17(或更高),上述代码即可在 Windows、Linux、macOS 等平台上编译运行。

小结

C++17 的 std::filesystem 提供了一套丰富、类型安全且跨平台的文件系统 API。无论是简单的文件读取、写入,还是复杂的目录遍历、复制、移动、权限管理,使用 std::filesystem 都能让代码更简洁、更可靠。熟练掌握它,能够显著提高日常项目的开发效率与代码可维护性。

发表评论