# C++ std::filesystem 完整用法教程:从基础到进阶 # C++ std::filesystem 详细用法教程 你想要系统学习 C++ 标准库中 `std::filesystem` 的完整用法,这个库从 C++17 开始正式纳入标准,提供了跨平台的文件系统操作能力,能替代传统的 `fopen`/`stat` 等底层 API,让文件/目录操作更简洁、安全、易读。下面我会从基础到进阶,带你全面掌握它的用法。 ## 核心前置知识 1. **编译要求**:必须使用支持 C++17 及以上的编译器(GCC 8+/Clang 7+/MSVC 2017+),编译时需指定标准: 1. ```Bash # GCC/Clang g++ your_code.cpp -o output -std=c++17 -lstdc++fs # GCC 8 及以下需链接 -lstdc++fs,GCC 9+ 无需 # MSVC cl.exe your_code.cpp /std:c++17 ``` 2. **核心头文件**:`#include ` 3. **命名空间**:核心功能在 `std::filesystem` 命名空间下(可简写为 `namespace fs = std::filesystem;` 简化代码)。 ## 核心概念:path 类(路径操作) `std::filesystem::path` 是所有操作的基础,用于表示文件/目录路径,支持跨平台(自动处理 Windows 的 `\` 和 Linux 的 `/`)。 ### 1. 路径创建与初始化 ```C++ #include #include namespace fs = std::filesystem; int main() { // 1. 直接构造(自动适配平台分隔符) fs::path p1 = "test.txt"; // 相对路径 fs::path p2 = "/home/user/test.txt"; // Linux 绝对路径 fs::path p3 = "C:\\Users\\test.txt"; // Windows 绝对路径(转义反斜杠) fs::path p4 = u8"中文路径.txt"; // 支持 UTF-8 中文路径 // 2. 拼接路径(推荐用 / 运算符,跨平台) fs::path p5 = fs::path("/home") / "user" / "docs"; std::cout << "拼接路径: " << p5 << std::endl; // Linux: /home/user/docs; Windows: /home\user\docs // 3. 从字符串/字符串视图构造 std::string str_path = "data/log.txt"; fs::path p6(str_path); std::string_view sv_path = "config.ini"; fs::path p7(sv_path); return 0; } ``` ### 2. 路径常用操作(解析/修改) ```C++ int main() { fs::path p = "/home/user/docs/report.pdf"; // 1. 路径解析 std::cout << "完整路径: " << p << std::endl; // /home/user/docs/report.pdf std::cout << "文件名: " << p.filename() << std::endl; // report.pdf std::cout << "文件名(无后缀): " << p.stem() << std::endl; // report std::cout << "文件后缀: " << p.extension() << std::endl; // .pdf std::cout << "父目录: " << p.parent_path() << std::endl; // /home/user/docs std::cout << "根目录: " << p.root_path() << std::endl; // / // 2. 路径修改 fs::path p2 = p; p2.replace_extension(".docx"); // 修改后缀 std::cout << "修改后缀后: " << p2 << std::endl; // /home/user/docs/report.docx // 3. 路径判断 std::cout << "是否绝对路径: " << std::boolalpha << p.is_absolute() << std::endl; // true std::cout << "是否相对路径: " << p.is_relative() << std::endl; // false std::cout << "路径是否为空: " << p.empty() << std::endl; // false // 4. 转换为字符串 std::string str_p = p.string(); // 转换为 std::string const char* cstr_p = p.c_str(); // 转换为 const char* return 0; } ``` ## 文件/目录状态查询(status/stat) 用于判断路径是文件/目录/不存在,或获取文件属性(大小、权限、修改时间等)。 ### 1. 基础状态判断 ```C++ int main() { fs::path p = "test.txt"; // 1. 获取路径状态(若路径不存在,抛出异常) try { fs::file_status s = fs::status(p); std::cout << "是否存在: " << fs::exists(s) << std::endl; std::cout << "是否是普通文件: " << fs::is_regular_file(s) << std::endl; std::cout << "是否是目录: " << fs::is_directory(s) << std::endl; std::cout << "是否是符号链接: " << fs::is_symlink(s) << std::endl; std::cout << "是否是字符设备: " << fs::is_character_file(s) << std::endl; } catch (const fs::filesystem_error& e) { std::cerr << "错误: " << e.what() << std::endl; } // 2. 无异常版本(推荐,避免频繁 try-catch) fs::file_status s2 = fs::status_known(fs::symlink_status(p)) ? fs::status(p) : fs::file_status{}; if (fs::exists(s2)) { std::cout << "路径存在" << std::endl; } else { std::cout << "路径不存在" << std::endl; } return 0; } ``` ### 2. 文件属性获取 ```C++ int main() { fs::path p = "test.txt"; if (!fs::exists(p)) { std::cerr << "文件不存在" << std::endl; return 1; } // 1. 文件大小(字节) uintmax_t file_size = fs::file_size(p); std::cout << "文件大小: " << file_size << " 字节" << std::endl; // 2. 最后修改时间(time_t 类型,可转换为本地时间) fs::file_time_type last_write = fs::last_write_time(p); // C++20 及以上可直接转换:std::chrono::system_clock::to_time_t(last_write) // 兼容写法(GCC/Clang/MSVC): auto sctp = std::chrono::time_point_cast( last_write - fs::file_time_type::clock::now() + std::chrono::system_clock::now() ); std::time_t last_write_t = std::chrono::system_clock::to_time_t(sctp); std::cout << "最后修改时间: " << std::ctime(&last_write_t); // 3. 文件权限(跨平台需注意,Windows 权限逻辑不同) fs::perms perm = fs::status(p).permissions(); std::cout << "是否可读: " << ((perm & fs::perms::owner_read) != fs::perms::none) << std::endl; std::cout << "是否可写: " << ((perm & fs::perms::owner_write) != fs::perms::none) << std::endl; return 0; } ``` ## 文件/目录操作(创建/删除/复制/移动) ### 1. 创建目录/文件 ```C++ int main() { // 1. 创建单个目录(若已存在,抛出异常) fs::path dir1 = "new_dir"; try { fs::create_directory(dir1); std::cout << "目录创建成功: " << dir1 << std::endl; } catch (const fs::filesystem_error& e) { std::cerr << "创建目录失败: " << e.what() << std::endl; } // 2. 创建多级目录(如 a/b/c,自动创建父目录) fs::path dir2 = "parent/child/grandchild"; fs::create_directories(dir2); // 无异常版本:create_directories(dir2) 直接返回 bool std::cout << "多级目录创建成功: " << dir2 << std::endl; // 3. 创建空文件(简单方式:用 std::ofstream,或结合 touch 逻辑) fs::path file = "new_file.txt"; std::ofstream(file).close(); // 创建空文件 if (fs::exists(file)) { std::cout << "空文件创建成功: " << file << std::endl; } return 0; } ``` ### 2. 删除文件/目录 ```C++ int main() { // 1. 删除文件(若不存在,抛出异常;无异常版本:remove_if_exists) fs::path file = "new_file.txt"; if (fs::remove(file)) { std::cout << "文件删除成功: " << file << std::endl; } // 2. 删除空目录(若目录非空,抛出异常) fs::path empty_dir = "new_dir"; fs::remove(empty_dir); // 3. 删除目录及所有内容(递归删除,慎用!) fs::path dir = "parent"; fs::remove_all(dir); // 返回删除的文件/目录数量 std::cout << "递归删除目录成功: " << dir << std::endl; return 0; } ``` ### 3. 复制/移动文件/目录 ```C++ int main() { // 1. 复制文件 fs::path src_file = "source.txt"; fs::path dst_file = "dest.txt"; std::ofstream(src_file) << "test content"; // 创建源文件 fs::copy_file(src_file, dst_file, fs::copy_options::overwrite_existing); // 覆盖已存在的目标文件 std::cout << "文件复制成功" << std::endl; // 2. 复制目录(递归复制所有内容) fs::path src_dir = "src_dir"; fs::path dst_dir = "dst_dir"; fs::create_directories(src_dir / "subdir"); std::ofstream(src_dir / "subdir" / "file.txt") << "dir content"; fs::copy(src_dir, dst_dir, fs::copy_options::recursive | fs::copy_options::overwrite_existing); std::cout << "目录递归复制成功" << std::endl; // 3. 移动/重命名文件/目录(类似 mv 命令) fs::path old_path = "dest.txt"; fs::path new_path = "renamed.txt"; fs::rename(old_path, new_path); // 若 new_path 已存在,抛出异常 std::cout << "文件重命名成功" << std::endl; // 移动目录(跨路径移动) fs::rename(dst_dir, "moved_dir"); return 0; } ``` ## 遍历目录(迭代器) `std::filesystem::directory_iterator` 遍历目录(非递归),`recursive_directory_iterator` 递归遍历子目录。 ### 1. 非递归遍历目录 ```C++ int main() { fs::path dir = "."; // 当前目录 for (const fs::directory_entry& entry : fs::directory_iterator(dir)) { std::cout << "路径: " << entry.path() << std::endl; std::cout << " 是否是文件: " << entry.is_regular_file() << std::endl; std::cout << " 是否是目录: " << entry.is_directory() << std::endl; if (entry.is_regular_file()) { std::cout << " 文件大小: " << entry.file_size() << " 字节" << std::endl; } } return 0; } ``` ### 2. 递归遍历目录(含子目录) ```C++ int main() { fs::path dir = "."; // 递归遍历,跳过符号链接(避免循环) for (const fs::directory_entry& entry : fs::recursive_directory_iterator( dir, fs::directory_options::skip_permission_denied)) { std::cout << "层级: " << fs::recursive_directory_iterator::depth() << " | 路径: " << entry.path() << std::endl; } return 0; } ``` ## 异常处理与错误码 `std::filesystem` 操作默认抛出 `filesystem_error` 异常,也可使用带错误码的重载版本(避免 try-catch)。 ```C++ int main() { fs::path p = "non_exist.txt"; std::error_code ec; // 错误码对象,用于接收错误信息 // 带错误码的操作:不抛出异常,错误信息存入 ec bool exists = fs::exists(p, ec); if (ec) { std::cerr << "错误(exists): " << ec.message() << std::endl; } else { std::cout << "文件是否存在: " << exists << std::endl; } // 复制文件(带错误码) fs::copy_file("src.txt", "dst.txt", fs::copy_options::overwrite_existing, ec); if (ec) { std::cerr << "复制文件失败: " << ec.message() << std::endl; } return 0; } ``` ## 实用示例:批量处理文件 需求:遍历指定目录,找出所有 `.txt` 文件,统计总大小并输出路径。 ```C++ #include #include #include namespace fs = std::filesystem; int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "用法: " << argv[0] << " <目录路径>" << std::endl; return 1; } fs::path dir = argv[1]; if (!fs::is_directory(dir)) { std::cerr << "错误: " << dir << " 不是有效目录" << std::endl; return 1; } uintmax_t total_size = 0; int txt_count = 0; // 递归遍历所有 .txt 文件 for (const fs::directory_entry& entry : fs::recursive_directory_iterator(dir)) { if (entry.is_regular_file() && entry.path().extension() == ".txt") { uintmax_t size = entry.file_size(); total_size += size; txt_count++; std::cout << "TXT 文件: " << entry.path() << " | 大小: " << size << " 字节" << std::endl; } } std::cout << "\n统计结果:" << std::endl; std::cout << "TXT 文件总数: " << txt_count << std::endl; std::cout << "总大小: " << total_size << " 字节 (" << total_size / 1024.0 << " KB)" << std::endl; return 0; } ``` ## 总结 ### 核心要点 1. **path 类**:是文件系统操作的基础,支持跨平台路径拼接、解析,优先用 `/` 运算符拼接路径。 2. **状态查询**:用 `status()`/`symlink_status()` 判断文件类型,`file_size()`/`last_write_time()` 获取属性,优先使用带错误码的重载版本避免异常。 3. **目录操作**:`create_directories()` 创多级目录,`remove_all()` 递归删除目录,`copy()` 支持文件/目录复制(需指定递归选项)。 4. **目录遍历**:`directory_iterator` 非递归遍历,`recursive_directory_iterator` 递归遍历,可跳过权限不足的目录。 5. **异常处理**:核心操作有“抛异常”和“返回错误码”两种版本,生产环境优先用错误码版本提升稳定性。 ### 最佳实践 - 始终检查路径/文件是否存在后再操作; - 用 `std::error_code` 替代 try-catch 处理非致命错误; - 递归操作时跳过符号链接,避免循环; - 跨平台开发时,避免硬编码路径分隔符(用 `path` 的 `/` 运算符); - 操作完成后验证结果(如复制文件后检查目标文件是否存在)。