C++标准模板库编程 – 17 文件 体系 c++模板编译
本章讲解重要的文件 体系类及函数,同时介绍可用于执行通用文件 体系操作的有用编程技巧。讨论主题包括:
类 std::filesystem::path 类 std::filesystem::recursive_directory_iterator 类 std::filesystem::directory_entry 目录创建与删除函数 目录及文件拷贝函数
文件类型函数
STL 的文件 体系类和函数定义在命名空间 std::filesystem 中。 这篇文章小编将及源代码中对该命名空间的引用均使用前缀 fs::。 时刻库类来自命名空间 std::chrono,其引用则使用前缀 chrono::。本章讨论假定 无论兄弟们已具备 Windows、Linux 或 cOS 目录、文件及路径名的基本 智慧。
文件 体系路径类 命名空间 std::filesystem 中最实用的类 其中一个是 fs::path。fs::path 类的实例包含一个表示文件 体系路径的字符串路径名。这种表示仅涉及词法和语法层面。关于 fs:path 类需要 领会的主要细节包括: 路径的组成元素包括根名称、根目录以及由分隔符划分的文件名序列。fs::path 中的根名称和根目录组件是可选的。
类 fs::path 支持多种路径名表示形式 ,包括 完全路径、相对路径和规范路径。
fs::path 对象并不一定对应存储设备上实际存在的目录或文件。
fs::path 对象包含的路径名不能保证在特定操作 体系或其支持的任何文件 体系中有效。
fs::path 路径名的最大长度由操作 体系设定,操作 体系也决定了有效的路径名字符。
示例函数 Ch17_01_ex1(),如代码清单 17-1-1 所示,引入了 fs::path 类。在其起始代码块中,Ch17_01_ex1() 使用 fs::path path1 = fs::current_path() 获取当前 职业目录。执行 fs::current_path() 会返回一个原生操作 体系格式的 完全路径(例如 X:CppSTLSourceCodeChapter17 或 /home/homer/CppSTL/Code/Chapter17)。随后的 std::println() 语句通过 path1.string() 打印 path1 的路径名。1 代码清单 17-1-1 中的下一条语句 path1.append(“test1.txt”),会将 est1.txt 或 /test1.txt 追加到 path1。注意追加的文本包含操作 体系特定的分隔符。
//------------------------------------------------------------------------- // Ch17_01_ex.cpp //------------------------------------------------------------------------- #include <filesystem> #include <fstream> #include "Ch17_01.h" #include "MF.h" namespace fs = std::filesystem; void Ch17_01_ex1() { // using fs::current_path fs::path path1 = fs::current_path(); std::println(" path1: {:s}", path1.string()); path1.append("test1.txt"); std::println("path1: {:s}", path1.string()); // using fs::temp_directory_path fs::path path2 = fs::temp_directory_path(); std::println(" path2: {:s}", path2.string()); path2 /= "test2.txt"; std::println("path2: {:s}", path2.string()); // using fs::current_path - bad path fs::path path3 = fs::current_path(); std::println(" path3: {:s}", path3.string()); path3 /= "Bad//Filename.txt"; std::println("path3: {:s}", path3.string()); std::ofstream ofs(path3); std::println(" ofs.good(): {:s} (expecting false)", ofs.good()); }
Listing 17-1-1 中的下一个代码块利用 fs::path path2 = fs::temp_directory_path() 获取当前临时目录。fs::temp_directory_path() 返回的目录路径保证存在。在同一代码块中,执行 path2 /= “test2.txt” 会将特定于操作 体系的目录分隔符附加到 path2(如果需要),后跟文本 test2.txt。
Ch17_01_ex1() 的 最后一个代码块利用 fs::path path3 = fs::current_path() 和 path3 /= “Bad//Filename.txt” 来形成一个无效的路径名,以用于演示目的。回想一下,由 fs::path 对象表示的路径名不必有效。 然而,使用 path3 创建文件将会失败,如在实例化 std::ofstream ofs(path3) 期间所示。
在 Listing 17-1-2 中,示例函数 Ch17_01_ex2() 使用 fs::path path1 = fs::current_path() / “test1.txt” 在当前 职业目录中为文件 test1.txt 创建一个 完全路径名。随后的代码块演示了各种 fs::path 分解函数的使用,这些函数提取 fs::path 的不同组成部分。
void Ch17_01_ex2() { // create test path fs::path path1 = fs::current_path() / "test1.txt"; std::println("path1: {:s}", path1.string()); // using fs::path decomposition functions fs::path path1_root_name = path1.root_name(); fs::path path1_root_dir = path1.root_directory(); fs::path path1_root_path = path1.root_path(); fs::path path1_relative_path = path1.relative_path(); fs::path path1_parent_path = path1.parent_path(); fs::path path1_filename = path1.filename(); fs::path path1_stem = path1.stem(); fs::path path1_extension = path1.extension(); std::println("path1_root_name: {:s}", path1_root_name.string()); std::println("path1_root_dir: {:s}", path1_root_dir.string()); std::println("path1_root_path: {:s}", path1_root_path.string()); std::println("path1_relative_path: {:s}", path1_relative_path.string()); std::println("path1_parent_path: {:s}", path1_parent_path.string()); std::println("path1_filename: {:s}", path1_filename.string()); std::println("path1_stem: {:s}", path1_stem.string()); std::println("path1_extension: {:s}", path1_extension.string()); }
Ch17_01_ex2() 中使用的所有 fs::path 成员函数都返回 fs::path 类型的对象。表 17-1 和表 17-2 拓展资料了在运行 Windows 和 Linux 的测试计算机上获得的 结局。请注意,在这些表中,分解函数可以正确处理驱动器盘符(即根名称)说明符的存在与否。
完全路径 |
X:CppSTLSourceCodeChapter17 est1.txt |
root_name |
X: |
root_dir |
|
root_path |
X: |
relative_path |
CppSTLSourceCodeChapter17 est1.txt |
parent_path |
X:CppSTLSourceCodeChapter17 |
filename |
test1.txt |
stem |
test1 |
extension |
.txt |
完全路径 |
/home/homer/SambaWin/CppSTL/SourceCode/Chapter17/test1.txt |
root_name |
|
root_dir |
/ |
root_path |
/ |
relative_path |
home/homer/SambaWin/CppSTL/SourceCode/Chapter17/test1.txt |
parent_path |
/home/homer/SambaWin/CppSTL/SourceCode/Chapter17 |
filename |
test1.txt |
stem |
test1 |
extension |
.txt |
下一个例子阐明了常用fs目录创建、存在和删除函数的使用。在清单17-1-3中,Ch17_01_ex3()的执行从fs::path sub_dir1 = fs::current_path() / “sub1″的初始化开始。随后的代码块利用rc = fs::exists(sub_dir1)来确定sub_dir1的存在。
void Ch17_01_ex3() { // initialize test subdirectory path fs::path sub_dir1 = fs::current_path() / "sub1"; // using fs::exists bool rc = fs::exists(sub_dir1); std::println(" fs::exists({:s}) rc = {:s}", sub_dir1.string(), rc); if (!rc) { // using fs::create_directory rc = fs::create_directory(sub_dir1); std::println(" fs::create_directory({:s}) rc = {:s}", sub_dir1.string(), rc); if (!rc) return; } // write a test file to sub_dir1 fs::path fn1 = sub_dir1 / "TestA.txt"; rc = MF::create_test_file(fn1); std::println(" write_test_file({:s}) rc = {:s}", fn1.string(), rc); // using fs::exists rc = fs::exists(fn1); std::println(" fs::exists({:s}) rc = {:s}", fn1.string(), rc); // using fs::remove to delete test file rc = fs::remove(fn1); std::println(" fs::remove({:s}) rc = {:s}", fn1.string(), rc); // using fs::remove to delete test subdirectory (must be empty) rc = fs::remove(sub_dir1); std::println(" fs::remove({:s}) rc = {:s}", sub_dir1.string(), rc); }如果 sub_dir1 不存在,Ch17_01_ex3() 会执行 rc = fs::create_directory(sub_dir1) 来创建它。文件 体系函数 fs::create_directory(sub_dir1) 如果创建了指定的目录,则返回 true;否则,返回 false。如果指定的目录已经存在,也会返回 false。这种情况将在后面更详细地讨论。
Ch17_01_ex3() 中的下一个代码块利用 fs::path fn1 = sub_dir1 / “TestA.txt” 和 MF::create_test_file(fn1)(参见清单 17-2-2-2)来创建测试文件 fn1。接下来是 fs:exists() 的另一个示例用法。Ch17_01_ex3() 的倒数第二个代码块执行 rc = fs::remove(fn1) 来删除测试文件 fn1。如果指定的文件被删除,则此函数的执行返回 true。Ch17_01_ex3() 的 最后一个代码块利用 rc = fs::remove(sub_dir1) 来删除 sub_dir1。当使用 fs::remove() 删除目录时,该目录必须为空。否则,可能会抛出 fs::filesystem_error 异常。文件 体系异常将在本章后面介绍。
清单 17-1-4 显示了示例 Ch17_01_ex4() 的源代码,该示例说明了 怎样创建和删除多层目录。开头的代码块 Ch17_01_ex4() 利用 fs::temp_directory_path() 和 几许 fs::path 追加操作来初始化 fs::path sub_tree_top 和 fs::path sub_tree_bot。在随后的代码块中,执行 fs::create_directories(sub_tree_bot) 会创建目录 sub_tree_bot,包括任何所需的中间目录。对于当前示例,这将在 fs::temp_directory_path() 中创建目录子树 d1/d2/d3/d4。
void Ch17_01_ex4() { // create fs::paths fs::path base_dir = fs::temp_directory_path(); fs::path sub_tree_top = base_dir / "d1"; fs::path sub_tree_bot = sub_tree_top / "d2/d3/d4"; // path sub_tree_bot exists? bool rc = fs::exists(sub_tree_bot); std::println(" fs::exists({:s}) rc = {:s}", sub_tree_bot.string(), rc); if (!rc) { // using fs::create_directories to create sub_tree_bot rc = fs::create_directories(sub_tree_bot); std::println(" fs::create_directories({:s}) rc = {:s}", sub_tree_bot.string(), rc); if (!rc) return; } // write test file to sub_tree_top fs::path fn1 = sub_tree_top / "TestA.txt"; rc = MF::create_test_file(fn1); std::println(" fn1.generic_string(): {:s}", fn1.generic_string()); std::println("fn1.string(): {:s}", fn1.string()); std::println("rc: {:s}", rc); // write test file to sub_tree_bot fs::path fn2 = sub_tree_bot / "TestB.txt"; rc = MF::create_test_file(fn2); std::println(" fn2.generic_string(): {:s}", fn2.generic_string()); std::println("fn2.string(): {:s}", fn2.string()); std::println("rc: {:s}", rc); // using fs::remove_all to delete sub_tree_top auto num_deletes = fs::remove_all(sub_tree_top); std::println(" fs::remove_all({:s}) num_deletes = {:d}", sub_tree_top.generic_string(), num_deletes); }Ch17_01_ex4() 中的下一个代码块利用 MF::create_test_file(fn1) 创建文件 fs::path fn1,其值等于 sub_tree_top / “TestA.txt”。随后的 std::println() 语句执行 fn1.generic_string() 以打印 fn1 的值。此函数的执行返回 fn1 的包含路径名的 std::string,格式为 fs 通用格式。这与 fn1.string() 不同,后者返回本机 OS 格式的 std::string。在调用特定于 OS 的 API 函数时,有时需要本机路径名。
Ch17_01_ex4() 的执行继续,使用 MF::create_test_file(fn2) 创建测试文件 fs::path fn2 = sub_tree_bot / “TestB.txt”。Ch17_01_ex4() 的 最后一个代码块利用 num_deletes = fs::remove_all(sub_tree_top) 完全删除目录 sub_tree_top,包括所有文件和较低级别的子目录。执行 fs::remove_all() 后,num_deletes 包含已删除的目录和文件的总数。示例 Ch17_01 的 结局如下段所示。对于函数 Ch17_01_ex4(),请注意 Windows 体系上通用和本机路径名格式之间的差异。
----- Results for example Ch17_01 ----- ----- Ch17_01_ex1() ----- path1: X:CppSTLSourceCodeChapter17 path1: X:CppSTLSourceCodeChapter17 est1.txt path2: C:UsersdanAppDataLocalTemp path2: C:UsersdanAppDataLocalTemp est2.txt path3: X:CppSTLSourceCodeChapter17 path3: X:CppSTLSourceCodeChapter17Bad//Filename.txt ofs.good(): false (expecting false) ----- Ch17_01_ex2() ----- path1: X:CppSTLSourceCodeChapter17 est1.txt path1_root_name: X: path1_root_dir: path1_root_path: X: path1_relative_path: CppSTLSourceCodeChapter17 est1.txt path1_parent_path: X:CppSTLSourceCodeChapter17 path1_filename: test1.txt path1_stem: test1 path1_extension: .txt ----- Ch17_01_ex3() ----- fs::exists(X:CppSTLSourceCodeChapter17sub1) rc = false fs::create_directory(X:CppSTLSourceCodeChapter17sub1) rc = true write_test_file(X:CppSTLSourceCodeChapter17sub1TestA.txt) rc = true fs::exists(X:CppSTLSourceCodeChapter17sub1TestA.txt) rc = true fs::remove(X:CppSTLSourceCodeChapter17sub1TestA.txt) rc = true fs::remove(X:CppSTLSourceCodeChapter17sub1) rc = true ----- Ch17_01_ex4() ----- fs::exists(C:UsersdanAppDataLocalTempd1d2/d3/d4) rc = false fs::create_directories(C:UsersdanAppDataLocalTempd1d2/d3/d4) rc = true fn1.generic_string(): C:/Users/dan/AppData/Local/Temp/d1/TestA.txt fn1.string(): C:UsersdanAppDataLocalTempd1TestA.txt rc: true fn2.generic_string(): C:/Users/dan/AppData/Local/Temp/d1/d2/d3/d4/TestB.txt fn2.string(): C:UsersdanAppDataLocalTempd1d2/d3/d4TestB.txt rc: true fs::remove_all(C:/Users/dan/AppData/Local/Temp/d1) num_deletes = 6文件 体系目录迭代器类
对于许多应用程序来说,搜索目录中的文件 一个常见的编程需求。为了满足这一需求,文件 体系库提供了一组类,这些类可用于迭代目录的元素、文件和任何子目录。清单17-2-1显示了示例Ch17_02_ex1()的源代码。此示例描述了 怎样使用类fs::recursive_directory_iterator和fs::directory_entry。它还涵盖了一些与这些类相关的辅助函数。
void Ch17_02_ex1() { // NOTE - change code_path to book's source code directory on your system #if defined(_WIN32) || defined(_WIN ) fs::path code_path = "x:\CppSTL\SourceCode"; #else fs::path code_path = "/home/homer/SambaWin/CppSTL/SourceCode"; #endif std::println("code_path: {:s}", code_path.string()); // using fs::is_directory bool is_dir = fs::is_directory(code_path); std::println("fs::is_directory({:s}): {:s}", code_path.string(), is_dir); if (!is_dir) return; // initialize recursive_directory_iterator fs::directory_options options = fs::directory_options::skip_permission_denied; fs::recursive_directory_iterator rdi = fs::recursive_directory_iterator(code_path, options); // search for .cpp and .h files size_t num_files {0}; size_t num_files_h {0}; size_t num_files_cpp {0}; for (const fs::directory_entry& dir_entry : rdi) { // using is_regular_file if (fs::is_regular_file(dir_entry)) { // extract extension fs::path file = dir_entry.path(); const std::string& file_ext = file.extension().string(); // update file counts ++num_files; if (file_ext == ".cpp") ++num_files_cpp; else if (file_ext == ".h") ++num_files_h; } } std::println(" num_files: {:d}", num_files); std::println("num_files_cpp: {:d}", num_files_cpp); std::println("num_files_h: {:d}", num_files_h); }
Ch17_02_ex1() 的起始代码块将 fs:path code_path 初始化为本书的根源代码目录。在执行此示例之前, 无论兄弟们必须将 code_path 的值更改为 体系上的正确目录。2 在下一个代码块中,执行文件 体系函数 fs::is_directory(code_path) 以确认 code_path 一个目录。
后续代码块中的第一个语句 fs::directory_options options = fs::directory_options::skip_permission_denied,设置 fs::recursive_directory_iterator 的遍历选项。选项 fs::directory_options::skip_permission_denied 指示 fs::recursive_directory_iterator 跳过任何会生成权限拒绝错误的目录。 无论兄弟们还可以指定 fs::follow_directory_symlink,它允许遍历任何符号链接。执行 rdi = fs::recursive_directory_iterator(code_path, options) 初始化一个 fs::recursive_directory_iterator,它将用于迭代 code_path 及其子目录的 fs::directory_entry 元素。在继续之前,值得一提的是,当前示例使用显式类名来加速 领会可能对 无论兄弟们来说是新的类。对于生产代码,使用关键字 auto 可能 一个更好的选择。
for (const fs::directory_entry& dir_entry : rdi) 的每次迭代都会更新 dir_entry,使其引用 [rdi.begin(), rdi.end()) 的下一个元素。第一个范围 for 循环语句 if (fs::is_regular_file(dir_entry)),测试 dir_entry 以查看它是否引用普通文件。表 17-3 列出了可以使用 fs::directory_entry 或 fs::path 对象执行的其他受支持的文件类型检查。 无论兄弟们将在本章中 进修这些函数的其他示例。
表 17-3 fs::directory_entry 和 fs::path 对象支持的文件类型检查
is_block_file |
Y |
Y |
is_character_file |
Y |
Y |
is_directory |
Y |
Y |
is_empty |
N |
Y |
is_fifo |
Y |
Y |
is_other |
Y |
Y |
is_regular_file |
Y |
Y |
is_socket |
Y |
Y |
is_symlink |
Y |
Y |
status_known |
N |
Y |
如果fs::is_regular_file(dir_entry)为真,则Ch17_02_ex1()利用fs::path file = dir_entry.path()和const std::string& file_ext = p.extension().string()来获取文件扩展名的引用。随后的代码块更新计数器num_files、num_files_cpp和num_files_h。
许多应用程序使用临时文件来维护中间 结局或其他数据。随着 时刻的推移,旧的临时文件数量可能会变得非常大,从而对整体 体系性能产生不利影响。执行定期的临时文件清理将有助于缓解这种情况。列表17-2-2-1显示了示例Ch17_02_ex2()的源代码。此示例演示了 怎样遍历临时文件目录并根据以天为单位的阈值识别旧文件。
void Ch17_02_ex2() { // set old file threshold in days constexpr chrono::days num_days {15}; // base path for old file search fs::path base_path = fs::temp_directory_path(); // get current time auto now = chrono::file_clock::now(); // create old test files (ensures that some files are found) constexpr size_t num_levels {4}; constexpr size_t num_files {3}; fs::path test_path = base_path / "Ch17_02_ex3"; std::println("test_path: {:s}", test_path.string()); MF::create_test_files(test_path, "Ch17_02_ex3", num_levels, num_files, now, -num_days, false); // set recursive directory iterator options auto options = fs::directory_options::skip_permission_denied; auto rdi = fs::recursive_directory_iterator(base_path, options); size_t num_directories {}; size_t num_old_files {}; constexpr size_t num_old_files_print_ x {25}; // search base_path for old files for (auto const& dir_entry : rdi) { // using is_directory if (fs::is_directory(dir_entry)) ++num_directories; // using is_regular_file if (fs::is_regular_file(dir_entry)) { auto lwt = dir_entry.last_write_time(); // found old file? if (now - lwt >= num_days) { if (++num_old_files <= num_old_files_print_ x) { auto s = MF::to_string(lwt); std::println("old {:2d}: {:s} {:s}", num_old_files, s, dir_entry.path().string()); } } } } // print counts and remove test_path std::println(" num_directories: {:d}", num_directories); std::println("num_old_files: {:d}", num_old_files); fs::remove_all(test_path); }
Ch17_02_ex2() 中的起始代码块将 chrono::days num_days {15} 初始化为旧文件阈值。对象 fs::path base_path(即顶层遍历路径)随后使用 fs::temp_directory_path() 进行初始化。下一个代码块利用 auto now = chrono::file_clock::now() 获取当前 时刻。示例 Ch17_02_ex2() 使用 chrono::file_clock, 由于它与其它 fs 类使用的时钟相同。
为了确保遍历代码找到一些文件,Ch17_02_ex2() 利用 MF::create_test_files() 在 base_path 下创建一个小的测试目录和文件子树。清单 17-2-2-2 显示了此函数的源代码,稍后将对此进行讨论。在调用 MF::create_test_files() 之后,Ch17_02_ex2() 利用 auto options = fs::directory_options::skip_permission_denied 和 auto rdi = fs::recursive_directory_iterator(base_path, options) 来初始化一个递归目录迭代器,就像前面的示例一样,只是使用了关键字 auto。
Ch17_02_ex2() 中的递归目录迭代器遍历代码遵循与前一个示例相同的模式。范围 for 循环中的第一个代码块执行 fs::is_directory(dir_entry) 以计算 base_path 中的目录数。如果 fs::is_regular_file(dir_entry) 为真,则 lwt = dir_entry.last_write_time() 的执行会获取 dir_entry 的上次数据修改 时刻。如果 (now – lwt >= num_days) 为真,则 dir_entry 至少有 num_days 的历史。MF::to_string(lwt)(参见 Common/MF.cpp)的执行将 lwt 转换为 std::string 以供显示。请注意,范围 for 循环中的显示代码最多打印 num_old_files_print_ x 个文件。可以在此处插入对 fs::remove(dir_entry.path()) 的调用以删除旧文件 dir_entry。
清单 17-2-2-2 显示了 MF::create_test_files() 的源代码。如前所述,此函数在目录 base_dir 下创建一个小的目录和文件子树。参数 num_levels 指定目录级别数,而 num_files 表示要在每个子目录中创建的测试文件数。参数 tp_base 和 tp_adj_days 用于设置每个创建的测试文件的上次数据修改 时刻, 无论兄弟们很快就会看到。
void MF::create_test_files(const fs::path& base_dir, const std::string& base_name, size_t num_levels, size_t num_files, const chrono::time_point<chrono::file_clock>& tp_base, chrono::days tp_adj_days, bool verbose) { // create base_dir if (fs::exists(base_dir)) { if (!fs::is_directory(base_dir)) throw std::runtime_error("fs::is_directory() failed"); } else { if (!fs::create_directory(base_dir)) throw std::runtime_error("fs::create_directory() failed"); } fs::path sub_dir = base_dir; // create subdirs and files for (size_t i = 0; i < num_levels; ++i) { sub_dir /= std::for t("D{:02d}", i); if (!fs::exists(sub_dir)) { if (!fs::create_directory(sub_dir)) throw std::runtime_error("fs::create_directory() failed"); } for (size_t j = 0; j < num_files; ++j) { fs::path fn = sub_dir / std::for t("{:s}-{:02d}-{:02d}.txt", base_name, i, j); if (!MF::create_test_file(fn.string())) throw std::runtime_error("MF::create_test_file() failed"); if (verbose) std::println("created {:s}", fn.string()); // set last write_time if (tp_adj_days != chrono::days{0}) fs::last_write_time(fn, tp_base + tp_adj_days); } } } bool MF::create_test_file(const std::string& name, bool empty) { // create test file std::ofstream ofs(name, std::ios_base::out | std::ios_base::trunc); if (!ofs.good()) return false; if (!empty) { // write sample data to test file auto tp_now = chrono::system_clock::now(); chrono::zoned_time tp_now_zt {chrono::current_zone(), tp_now}; std::println(ofs, "test file: {:s}", name); std::println(ofs, "created on: {0:%F} {0:%T} {0:%Z}", tp_now); std::println(ofs, "created on: {0:%F} {0:%T} {0:%Z}", tp_now_zt); } ofs.close(); return !ofs.fail(); } bool MF::create_test_file(const fs::path& name, bool empty) { return MF::create_test_file(name.string(), empty); }MF::create_test_files() 的起始代码块使用 fs::exists(base_dir)、fs::is_directory(base_dir) 和 fs::create_directory(base_dir) 来确保 base_dir 存在。MF::create_test_files() 的外部 for 循环中的第一个代码块创建子目录 sub_dir /= std::for t(“D{:02d}”, i)。内部 for 循环创建名为 fs::path fn = sub_dir / std::for t(“{:s}-{:02d}-{:02d}.txt”, base_name, i, j) 的测试文件。对于函数 Ch17_02_ex2(),这些 for 循环创建的目录-文件结构如下所示:
Ch17_02_ex3D00Ch17_02_ex3-00-00.txt Ch17_02_ex3D00Ch17_02_ex3-00-01.txt Ch17_02_ex3D00Ch17_02_ex3-00-02.txt Ch17_02_ex3D00D01Ch17_02_ex3-01-00.txt Ch17_02_ex3D00D01Ch17_02_ex3-01-01.txt Ch17_02_ex3D00D01Ch17_02_ex3-01-02.txt Ch17_02_ex3D00D01D02Ch17_02_ex3-02-00.txt Ch17_02_ex3D00D01D02Ch17_02_ex3-02-01.txt Ch17_02_ex3D00D01D02Ch17_02_ex3-02-02.txt Ch17_02_ex3D00D01D02D03Ch17_02_ex3-03-00.txt Ch17_02_ex3D00D01D02D03Ch17_02_ex3-03-01.txt Ch17_02_ex3D00D01D02D03Ch17_02_ex3-03-02.txt需要注意的另一个事项是,在清单 17-2-2-2 的底部附近,使用了 fs::last_write_time(fn, tp_base + tp_adj_days)。执行此重载会更改文件 fn 的 最后数据修改 时刻为 tp_base + tp_adj_days。回想一下,示例函数 Ch17_02_ex2() 使用值 now (chrono::file_clock::now()) 和 -num_days (chrono::days num_days {15}) 调用了 MF::create_test_files()。这意味着文件 fn 的新的 最后数据修改 时刻比现在早 15 天。示例 Ch17_02 的 结局如下段所示。这些 结局将因目标 体系而异。
----- Results for example Ch17_02 ----- ----- Ch17_02_ex1() ----- code_path: x:CppSTLSourceCode fs::is_directory(x:CppSTLSourceCode): true num_files: 10219 num_files_cpp: 318 num_files_h: 175 ----- Ch17_02_ex2() ----- test_path: C:UsersdanAppDataLocalTempCh17_02_ex3 old 1: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00Ch17_02_ex3-00-00.txt old 2: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00Ch17_02_ex3-00-01.txt old 3: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00Ch17_02_ex3-00-02.txt old 4: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00D01Ch17_02_ex3-01-00.txt old 5: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00D01Ch17_02_ex3-01-01.txt old 6: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00D01Ch17_02_ex3-01-02.txt old 7: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00D01D02Ch17_02_ex3-02-00.txt old 8: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00D01D02Ch17_02_ex3-02-01.txt old 9: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00D01D02Ch17_02_ex3-02-02.txt old 10: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00D01D02D03Ch17_02_ex3-03-00.txt old 11: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00D01D02D03Ch17_02_ex3-03-01.txt old 12: [2024-05-24 13:49:27] C:UsersdanAppDataLocalTempCh17_02_ex3D00D01D02D03Ch17_02_ex3-03-02.txt num_directories: 25 num_old_files: 12
文件 体系辅助函数
命名空间 std::filesystem 包含许多辅助函数,这些函数使用 fs::paths 执行有用的操作。本节介绍其中一些函数。它还讨论了异常类 fs::filesystem_error。
列表 17-3-1 显示了 Ch17_03_ex1() 的源代码,该代码突出了生成各种形式的 fs::path 对象的函数的使用。
//------------------------------------------------------------------------- // Ch17_03_ex.cpp //------------------------------------------------------------------------- #include <filesystem> #include "Ch17_03.h" #include "MF.h" namespace fs = std::filesystem; void Ch17_03_ex1() { auto print_path_info = [] (const char* msg, const fs::path& p) { // print paths std::println(" {:s}", msg); std::println("raw string: {:s}", p.string()); std::println("generic string: {:s}", p.generic_string()); // compose path forms fs::path absolute = fs::absolute(p); fs::path canonical = fs::canonical(p); fs::path relative = fs::relative(p); std::println("absolute: {:s}", absolute.string()); std::println("canonical: {:s}", canonical.string()); std::println("relative: {:s}", relative.string()); }; // print_path_info example #1 fs::path path1 = "../../test1.txt"; MF::create_test_file(path1); print_path_info("path1 infor tion:", path1); fs::remove(path1); // print_path_info example #2 fs::path path2_base = fs::current_path() / "D0"; fs::path path2 = path2_base / "D1/D2"; fs::create_directories(path2); path2 /= "test2.txt"; MF::create_test_file(path2); print_path_info("path2 infor tion:", path2); fs::remove_all(path2_base); }
函数 Ch17_03_ex1() 以 lambda 表达式 print_path_info() 的定义开始,该表达式打印与参数 const fs::path& p 相关的路径信息。此 lambda 中的前两个 std::println() 语句利用 p.string() 和 p.generic_string() 打印 p 中包含的路径名。回想一下,前者以本机操作 体系格式返回 std::string 路径名,而后者以 fs(即 std::filesystem)通用格式返回路径名。
接下来的三个语句生成各种 fs::path 格式。例如,fs::path absolute = fs::absolute(p) 构成一个指向文件 体系对象(由 p 表示)的明确路径。根据 ISO C++23 规范,强烈建议实现避免文件 体系查询和不存在的文件错误(即 fs::exists(p) 为 false)在 fs::absolute() 中,但这不能保证。
接下来是 fs::path canonical = fs::canonical(p),它以 fs 通用格式构成一个缺少符号链接、点或点点元素的 完全路径。如果 p 指定的文件不存在,则执行 fs::canonical(p) 会引发异常。
最后一个路径组合示例 fs::path relative = fs::relative(p) 构成一个相对于 fs::current_path() 的 fs::path 相对(非 完全)路径。在此使用示例中,如果 OS API 错误阻止成功组合相对路径,则执行 fs::relative(p) 会引发异常。稍后会详细介绍。
Ch17_03_ex1() 中的其余代码练习 print_path_info()。第一个示例使用 fs::path path1 = “../../test1.txt” 作为测试路径,而第二个示例使用 fs::current_path() / “D0/D1/D2/test2.txt”。 下面内容是 print_path_info() 在 Windows 体系上生成的输出示例:
path1 infor tion: raw string: ../../test1.txt generic string: ../../test1.txt absolute: X:CppSTL est1.txt canonical: \carbon2SambaShareCppSTL est1.txt relative: .... est1.txt path2 infor tion: raw string: X:CppSTLSourceCodeChapter17D0D1/D2 est2.txt generic string: X:/CppSTL/SourceCode/Chapter17/D0/D1/D2/test2.txt absolute: X:CppSTLSourceCodeChapter17D0D1D2 est2.txt canonical: \carbon2SambaShareCppSTLSourceCodeChapter17D0D1D2 est2.txt relative: D0D1D2 est2.txt在 Linux 体系中,输出如下所示:
path1 infor tion: raw string: ../../test1.txt generic string: ../../test1.txt absolute: /home/homer/SambaWin/CppSTL/SourceCode/Chapter17/../../test1.txt canonical: /home/homer/SambaWin/CppSTL/test1.txt relative: ../../test1.txt path2 infor tion: raw string: /home/homer/SambaWin/CppSTL/SourceCode/Chapter17/D0/D1/D2/test2.txt generic string: /home/homer/SambaWin/CppSTL/SourceCode/Chapter17/D0/D1/D2/test2.txt absolute: /home/homer/SambaWin/CppSTL/SourceCode/Chapter17/D0/D1/D2/test2.txt canonical: /home/homer/SambaWin/CppSTL/SourceCode/Chapter17/D0/D1/D2/test2.txt relative: D0/D1/D2/test2.txt到目前为止,本章中提供的std::filesystem示例已经使用了函数重载,如果发生底层OS API错误,这些重载会抛出一个fs::filesystem_error异常。大多数fs函数还定义了一个noexcept重载,该重载通过std::error_code返回错误信息。列表17-3-2显示了示例Ch17_03_ex2()的源代码,该示例更详细地演示了这两种错误报告 技巧。
void create_dir(const char* msg, const fs::path& dir) { std::println(" {:s} - dir: {:s}", msg, dir.string()); // using fs::create_directory (noexcept) and std::error_code std::error_code ec {}; if (fs::create_directory(dir, ec)) std::println("#1 - created directory {:s}", dir.string()); else { if (!ec) std::println("#1 - directory {:s} already exists", dir.string()); else { std::println("ec.value: {:d}", ec.value()); std::println("ec.message: {:s}", ec.message()); } } // using fs::create_directory and fs::filesystem_error try { if (fs::create_directory(dir)) std::println("#2 - created directory {:s}", dir.string()); else std::println("#2 - directory {:s} already exists", dir.string()); } catch (const fs::filesystem_error& ex) { // std::exception infor tion std::println(" caught fs::filesystem_error exception"); std::println("what(): {:s}", ex.what()); // fs::filesystem_error infor tion std::println("path1(): {:s}", ex.path1().string()); std::println("path2(): {:s}", ex.path2().string()); std::println("code().value(): {:d}", ex.code().value()); std::println("code().message(): {:s}", ex.code().message()); } } void Ch17_03_ex2() { // using create_dir with valid path fs::path dir1 = fs::temp_directory_path() / "good_dir_name"; create_dir("test case #1", dir1); create_dir("test case #2", dir1); fs::remove(dir1); // using create_dir with invalid path fs::path dir2 = fs::temp_directory_path() / "bad//_dir_name"; create_dir("test case #3", dir2); }列表 17-3-2 以名为 create_dir() 的函数的定义开始。此函数的执行会练习 fs::create_directory() 的两个不同的重载。在 create_dir() 的顶部附近,利用 fs::create_directory(dir, ec) 来创建目录 dir。如果 dir 是新创建的,则此函数返回 true;否则,返回 false。请注意,返回值为 false 包括与 dir 同名的现有目录的情况。如果在执行 fs::create_directory(dir, ec) 期间发生 OS API 错误,则会通过参数 std::error_code ec 返回特定于 OS 的错误代码。如果 fs::create_directory(dir, ec) 返回 false,则 create_dir() 测试 ec 以确定 dir 是否已存在或是否发生了 OS API 错误。如果是后者,则 create_dir() 打印 ec.value() 和 ec.message(),其中包含特定于 OS 的错误代码和消息。
create_dir() 的第二部分利用 fs::create_directory(dir) 和 try-catch 构造来创建目录 dir。与其 std::error_code 重载对应项一样,如果 dir 是新创建的,则执行 fs::create_directory(dir) 返回 true;否则,它返回 false(dir 已经存在)。如果发生阻止成功创建 dir 的 OS API,则 fs::create_directory() 会抛出 fs::filesystem_error 异常。
在 create_dir() 的 catch 块中,ex.path1() 返回与 dir 对应的 fs::path,而 ex.code().value() 和 ex.code().message() 包含特定于 OS 的错误信息。更一般地说,当 fs 函数抛出 fs::filesystem_error exception& ex 时,ex.path1() 对应于抛出异常的函数的第一个 fs::path 参数,而 ex.path2() 对应于第二个 fs::path 参数(如果有)。执行 ex.code() 返回一个 const std::error_code& 引用,其中包含有关异常的特定于 OS 的信息。
函数 Ch17_03_ex2() 中的 C++ 代码使用有效和无效的目录名称来练习 create_dir()。
示例 Ch17_03_ex3()(如列表 17-3-3 所示)重点介绍了表 17-3 中其他文件类型检查函数的使用。在列表 17-3-3 的顶部附近是 lambda 表达式 print_types() 的定义,如果参数 const fs::path& p 存在,则打印与其相关的类型信息。请注意,此函数利用 std::error_code 重载来排除抛出 std::filesystem_error 异常。 缘故是,在 Linux 体系上使用特定于 Windows 的路径(例如,C:Windows otepad.exe)调用 fs::is_ 函数将生成 fs::filesystem_error 异常。
void Ch17_03_ex3() { auto print_types = [](const fs::path& p) { std::println(" path: {:s}", p.string()); // code below uses fs std::error_code overloads, which are noexcept std::string s {"| "}; std::error_code ec {}; if (fs::exists(p, ec)) { if (fs::is_block_file(p, ec)) s += "is_block_file | "; if (fs::is_character_file(p, ec)) s += "is_character_file | "; if (fs::is_directory(p, ec)) s += "is_directory | "; if (fs::is_fifo(p, ec)) s += "is_fifo | "; if (fs::is_other(p, ec)) s += "is_other | "; if (fs::is_regular_file(p, ec)) s += "is_regular_file | "; if (fs::is_socket(p, ec)) s += "is_socket | "; if (fs::is_symlink(p, ec)) s += "is_symlink | "; if (fs::is_empty(p, ec)) s += "is_empty |"; } else s += "does not exist |"; if (s == "") s += "unknown or implementation specific |"; std::println("{:s}", s); }; // using create_directory fs::path test_path1 = fs::temp_directory_path() / "Ch17_03_ex2"; fs::create_directory(test_path1); fs::path test_path2 = test_path1 / "EmptyDir"; fs::create_directory(test_path2); // create test files fs::path test_file1 = test_path1 / "test1-data-file.txt"; MF::create_test_file(test_file1); fs::path test_file2 = test_path1 / "test2-empty-file.txt"; MF::create_test_file(test_file2, true); // print types print_types(test_path1); print_types(test_path2); print_types(test_file1); print_types(test_file2); // test paths for Windows print_types("C:\"); print_types("C:\Windows\notepad.exe"); print_types("\\carbon2\projects"); // test paths for Linux and similar operating systems print_types("/etc"); print_types("/etc/fstab"); print_types("/dev/sda"); print_types("/dev/tty0"); fs::remove_all(test_path1); }在定义了 print_types() 之后,Ch17_03_ex3() 利用 fs::create_directory() 和 MF::create_test_file() 创建了 几许测试目录和文件。随后的代码块利用 print_types() 来显示这些文件的类型信息。Ch17_03_ex3() 的 最后两个代码块使用特定于 Windows 和 Linux 的 fs::paths 来执行 print_types()。 下面内容是 Ch17_03 示例的 结局:
----- Results for example Ch17_03 ----- ----- Ch17_03_ex1() ----- path1 infor tion: raw string: ../../test1.txt generic string: ../../test1.txt absolute: X:CppSTL est1.txt canonical: \carbon2SambaShareCppSTL est1.txt relative: .... est1.txt path2 infor tion: raw string: X:CppSTLSourceCodeChapter17D0D1/D2 est2.txt generic string: X:/CppSTL/SourceCode/Chapter17/D0/D1/D2/test2.txt absolute: X:CppSTLSourceCodeChapter17D0D1D2 est2.txt canonical: \carbon2SambaShareCppSTLSourceCodeChapter17D0D1D2 est2.txt relative: D0D1D2 est2.txt ----- Ch17_03_ex2() ----- test case #1 - dir: C:UsersdanAppDataLocalTempgood_dir_name #1 - created directory C:UsersdanAppDataLocalTempgood_dir_name #2 - directory C:UsersdanAppDataLocalTempgood_dir_name already exists test case #2 - dir: C:UsersdanAppDataLocalTempgood_dir_name #1 - directory C:UsersdanAppDataLocalTempgood_dir_name already exists #2 - directory C:UsersdanAppDataLocalTempgood_dir_name already exists test case #3 - dir: C:UsersdanAppDataLocalTempad//_dir_name ec.value: 3 ec.message: The system cannot find the path specified. caught fs::filesystem_error exception what(): create_directory: The system cannot find the path specified.: "C:UsersdanAppDataLocalTempad//_dir_name" path1(): C:UsersdanAppDataLocalTempad//_dir_name path2(): code().value(): 3 code().message(): The system cannot find the path specified. ----- Ch17_03_ex3() ----- path: C:UsersdanAppDataLocalTempCh17_03_ex2 | is_directory | path: C:UsersdanAppDataLocalTempCh17_03_ex2EmptyDir | is_directory | is_empty | path: C:UsersdanAppDataLocalTempCh17_03_ex2 est1-data-file.txt | is_regular_file | path: C:UsersdanAppDataLocalTempCh17_03_ex2 est2-empty-file.txt | is_regular_file | is_empty | path: C: | is_directory | path: C:Windows otepad.exe | is_regular_file | path: \carbon2projects | is_directory | path: /etc | does not exist | path: /etc/fstab | does not exist | path: /dev/sda | does not exist | path: /dev/tty0 | does not exist |文件 体系 功能
本章的 最后一个例子解释了 怎样使用 函数fs::copy_file()和fs::copy()。在清单17-4-1中,Ch17_04_ex1()的执行首先创建一个名为fs::temp_directory_path() / “Ch17_04_ex1″的测试目录。在下一个代码块中,Ch17_04_ex1()利用fs::path test_file1 = test_path / “TestFile1.txt”和MF::create_test_file(test_file1)来创建一个源测试文件。随后的语句fs::path test_file2 = test_path / “TestFile2.txt”,建立一个测试目标文件。
//------------------------------------------------------------------------- // Ch17_04_ex.cpp //------------------------------------------------------------------------- #include <filesystem> #include "Ch17_04.h" #include "MF.h" namespace fs = std::filesystem; void Ch17_04_ex1() { // create test directory std::error_code ec {}; fs::path test_path = fs::temp_directory_path() / "Ch17_04_ex1"; if (fs::exists(test_path)) remove_all(test_path); // for debug/test if (!fs::create_directory(test_path, ec)) { std::println("ec: {:s}", ec.message()); return; } // create test files fs::path test_file1 = test_path / "TestFile1.txt"; // source file MF::create_test_file(test_file1); fs::path test_file2 = test_path / "TestFile2.txt"; // destination file for (int i = 0; i < 3; ++i) { // set copy_options (default is fs:copy_options::none) fs::copy_options copy_opt {}; if (i == 2) copy_opt = fs::copy_options::overwrite_existing; std::println(" using fs::copy_file() - test #{:d}", i); std::println("source file: {:s}", test_file1.string()); std::println("destination file: {:s}", test_file2.string()); // using copy_file (fails when i == 1 is true) bool status = fs::copy_file(test_file1, test_file2, copy_opt, ec); std::println("status: {:s}", status); std::println("ec.message(): {:s}", ec.message()); } fs::remove_all(test_path); }在 Ch17_04_ex1() 的 for 循环中,语句 fs::copy_options copy_opt {} 默认将 copy_opt 初始化为 fs::copy_options::none 以供后续使用。类 fs::copy_options 一个位掩码常量的枚举,用于指定 fs::copy_file() 和 fs::copy() 的 行为偏好。表 17-4 列出了可用的选项。
现有目标文件 fs::copy_file() |
无 |
如果文件存在则报错 |
跳过已存在文件 |
跳过覆盖现有文件 |
|
覆盖现有文件 |
覆盖现有文件 |
|
更新现有文件 |
如果替换文件较新则覆盖现有文件 |
|
子目录 fs::copy() |
无 |
不 子目录 |
递归 |
递归 子目录及其文件 |
|
符号链接 fs::copy() |
无 |
跟随符号链接 |
copy_symlinks |
将符号链接 为符号链接 |
|
跳过符号链接 |
忽略符号链接 |
|
形式 fs::copy() |
无 |
内容 |
仅目录 |
仅 目录;跳过非目录文件 |
|
创建符号链接 |
创建符号链接而非文件副本;除非目标路径在当前目录中,否则源路径必须是 完全路径 |
|
创建硬链接 |
创建硬链接而非文件副本 |
for 循环在 Ch17_04_ex1() 中执行了三次。第一次迭代时,执行 fs::copy_file(test_file1, test_file2, copy_opt, ec) 将 test_file1 到 test_file2。第二次迭代时, 由于 test_file2 已存在,fs:copy_file() 执行失败。为修改此默认行为,Ch17_04_ex1() 在第三次迭代调用 fs::copy_file() 前设置了 copy_opt = fs::copy_options::overwrite_existing。
代码清单 17-4-2 展示了示例 Ch17_04_ex2() 的源代码。该示例利用 fs::copy() 递归地将目录中的文件 到新目录。
void Ch17_04_ex2() { std::error_code ec {}; // create path names fs::path source_dir = fs::temp_directory_path() / "Ch17_04_ex2_S"; fs::path dest_dir = fs::temp_directory_path() / "Ch17_04_ex2_D"; fs::remove_all(source_dir, ec); // remove old dirs fs::remove_all(dest_dir, ec); // (for debug/test) // create test files in source_dir (remove_all delete if (!fs::create_directory(source_dir)) throw std::runtime_error("create_directory() failed"); MF::create_test_files(source_dir, "Ch17_04_ex2", 4, 3); // copy source_dir to dest_dir (fs::copy() is void) fs::copy_options copy_opt = fs::copy_options::recursive; fs::copy(source_dir, dest_dir, copy_opt, ec); if (ec) { std::println("error: {:s}", ec.message()); return; } // display files in source_dir and des_dir for (int i = 0; i < 2; ++i) { fs::path display_path = (i == 0) ? source_dir : dest_dir; std::println(" display_path: {:s}", display_path.string()); for (auto const& de : fs::recursive_directory_iterator(display_path)) { if (fs::is_directory(de) || fs::is_regular_file(de)) std::println("{:s}", de.path().string()); } } fs::remove_all(source_dir); fs::remove_all(dest_dir); }
函数 Ch17_04_ex2() 利用 MF::create_test_files(source_dir, “Ch17_04_ex2”, 4, 3) 在 source_dir 下创建一个目录树(关于 MF::create_test_files() 的信息,请参见清单 17-2-2-2)。在下一个代码块中,执行 fs::copy(source_dir, dest_dir, copy_opt, ec) 会将 source_dir 中的所有子目录和文件递归 到 des_dir。请注意,这里 copy_opt 设置为 fs::copy_options::recursive,这启用了递归 。在执行 fs::copy() 之后,会检查 std::error_code ec 以确定是否发生了任何错误。
Ch17_04_ex2() 的 最后一个代码块包含一个 for 循环,该循环利用 fs::recursive_directory_iterator() 来打印 source_dir 和 dest_dir 中的文件。 下面内容是示例 Ch17_04 的 结局:
----- Results for example Ch17_04 ----- ----- Ch17_04_ex1() ----- using fs::copy_file() - test #0 source file: C:UsersdanAppDataLocalTempCh17_04_ex1TestFile1.txt destination file: C:UsersdanAppDataLocalTempCh17_04_ex1TestFile2.txt status: true ec.message(): The operation completed successfully. using fs::copy_file() - test #1 source file: C:UsersdanAppDataLocalTempCh17_04_ex1TestFile1.txt destination file: C:UsersdanAppDataLocalTempCh17_04_ex1TestFile2.txt status: false ec.message(): The file exists. using fs::copy_file() - test #2 source file: C:UsersdanAppDataLocalTempCh17_04_ex1TestFile1.txt destination file: C:UsersdanAppDataLocalTempCh17_04_ex1TestFile2.txt status: true ec.message(): The operation completed successfully. ----- Ch17_04_ex2() ----- display_path: C:UsersdanAppDataLocalTempCh17_04_ex2_S C:UsersdanAppDataLocalTempCh17_04_ex2_SD00 C:UsersdanAppDataLocalTempCh17_04_ex2_SD00Ch17_04_ex2-00-00.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00Ch17_04_ex2-00-01.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00Ch17_04_ex2-00-02.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01 C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01Ch17_04_ex2-01-00.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01Ch17_04_ex2-01-01.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01Ch17_04_ex2-01-02.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01D02 C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01D02Ch17_04_ex2-02-00.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01D02Ch17_04_ex2-02-01.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01D02Ch17_04_ex2-02-02.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01D02D03 C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01D02D03Ch17_04_ex2-03-00.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01D02D03Ch17_04_ex2-03-01.txt C:UsersdanAppDataLocalTempCh17_04_ex2_SD00D01D02D03Ch17_04_ex2-03-02.txt display_path: C:UsersdanAppDataLocalTempCh17_04_ex2_D C:UsersdanAppDataLocalTempCh17_04_ex2_DD00 C:UsersdanAppDataLocalTempCh17_04_ex2_DD00Ch17_04_ex2-00-00.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00Ch17_04_ex2-00-01.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00Ch17_04_ex2-00-02.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01 C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01Ch17_04_ex2-01-00.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01Ch17_04_ex2-01-01.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01Ch17_04_ex2-01-02.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01D02 C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01D02Ch17_04_ex2-02-00.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01D02Ch17_04_ex2-02-01.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01D02Ch17_04_ex2-02-02.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01D02D03 C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01D02D03Ch17_04_ex2-03-00.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01D02D03Ch17_04_ex2-03-01.txt C:UsersdanAppDataLocalTempCh17_04_ex2_DD00D01D02D03Ch17_04_ex2-03-02.txt拓展资料
下面内容是本章的关键 进修要点:
fs::path 类的实例包含一个表示文件 体系路径的字符串路径名。 fs::path 类支持多种路径名表示形式,包括 完全路径、相对路径和规范路径。 fs::path 对象并不一定对应存储设备上实际存在的文件。 函数 fs::current_path() 和 fs::temp_directory_path() 返回包含当前 职业目录和临时目录的 fs::path 对象。 类 fs::recursive_directory_iterator 和 fs::directory_entry 用于遍历目录中的元素,包括其文件和所有子目录。 文件 体系函数 fs::create_directory() 和 fs::remove() 用于创建和删除单个目录。文件 体系函数 fs::create_directories() 和 fs::remove_all() 则用于创建和删除目录及其所有文件和子目录。 文件 体系函数 fs::copy_file() 和 fs::copy() 用于 文件和目录。 操作的行为偏好通过 enum class fs:copy_options 的位掩码值来指定。 大多数 fs 函数定义了两个重载版本。第一种类型包含需要显式 std::error_code 参数来返回错误 结局的函数,这些函数被声明为 noexcept。非 std::error_code 版本的重载会抛出 fs::filesystem_error 异常来报告关键错误,包括来自底层操作 体系的 API 错误。