跳转到内容

文件系统库`std::filesystem`的使用

目录文件操作

例子 目标路径存在性及类型

#include <filesystem>
#include <iostream>
int main() {
std::filesystem::directory_entry directory{R"(C:\Users\wenidc\Desktop\test)"};
if (directory.exists()) {
std::cout << "The location exists.\n";
}
if (directory.is_directory()) {
std::cout << "It is a directory\n";
}
if (!directory.is_regular_file()) {
std::cout << "It is not a file\n";
}
return 0;
}

为什么要把文件称为“常规文件”(regular file)?

硬盘驱动器上几乎所有我们熟悉的文件 —— 文档、图像、可执行文件等,都被视为常规文件(regular files)。

除了目录和常规文件之外,我们的文件系统中还可以存在其他一些东西,例如块文件(block files)和符号链接(symbolic links)。

例子 获取文件属性如大小、最后修改时间

#include <filesystem>
#include <iostream>
#include <chrono>
int main() {
std::filesystem::directory_entry file{R"(C:\Users\wenidc\Desktop\test\Teach_Youself_CPP_21days.jpg)"};
std::cout << "File Size: " << file.file_size() << " bytes\n";
std::cout << "Last Write Time: " << file.last_write_time() << "\n";
return 0;
}

例子 创建目录

创建一个目录,mkdir path

#include <filesystem>
#include <iostream>
int main() {
auto result {std::filesystem::create_directory(R"(C:\Users\wenidc\Desktop\test\test1)")};
if (result) {
std::cout << "Directory created\n";
} else {
std::cout << "Directory not created\n";
/*
* 可能的原因有:
* 1. 文件夹已存在;
* 2. 操作系统报告了一个错误。
*/
}
return 0;
}

抛出异常的情况

#include <filesystem>
#include <iostream>
int main() {
try {
auto result {std::filesystem::create_directory(R"(f:\test1)")};
} catch (const std::filesystem::filesystem_error& e) {
std::cout << e.code() << std::endl;
std::cout << e.what() << std::endl;
if (e.code().value() == 2) {
std::cout << "Path doesn't exist";
}
}
return 0;
}

错误代码的含义取决于程序运行的操作系统。对于Windows系统,错误代码的含义可以看这里:Tutorial - Debug system error codes - Win32 apps | Microsoft Learn

创建目录,递归创建所有目录层级,mkdir -p path

注意这里用的是create_directories(),不是create_directory()

#include <filesystem>
#include <iostream>
int main() {
auto result {std::filesystem::create_directories(R"(C:\Users\wenidc\Desktop\test\test2\test2-1)")};
if (result) {
std::cout << "Directory created\n";
} else {
std::cout << "Directory not created\n";
}
return 0;
}

例子 复制文件

#include <filesystem>
int main() {
// 第一个参数是复制的文件的路径。
// 第二个参数是要将其复制到的路径
std::filesystem::copy_file(R"(C:\Users\wenidc\Desktop\test\Teach_Youself_CPP_21days.jpg)", R"(C:\Users\wenidc\Desktop\test\test2\test2-1\1.jpg)");
return 0;
}

如果copy_file()第二个参数指定的路径已经存在,怎么办?可以为copy_file()指定第三个参数,指示遇到这种情况如何处理。

是一个枚举,包含的值有:

  • 保留现有文件并抛出异常(默认)
  • 保留现有文件但不引发异常
  • 覆盖现有文件
  • 如果现有文件比新文件旧(按照),则覆盖现有文件
#include <filesystem>
int main() {
// 第一个参数是复制的文件的路径。
// 第二个参数是要将其复制到的路径
std::filesystem::copy_file(R"(C:\Users\wenidc\Desktop\test\Teach_Youself_CPP_21days.jpg)",
R"(C:\Users\wenidc\Desktop\test\test2\test2-1\1.jpg)",
std::filesystem::copy_options::skip_existing);
return 0;
}

例子 复制目录

这里使用的是copy(),上面使用的是copy_file()

copy()不仅可以复制目录,也可以复制文件,但复制文件最好是用copy_file(),不仅让我们的意图更清晰,而且copy_file()具有内置的错误检查功能,以确保目标确实是一个文件。

#include <filesystem>
int main() {
std::filesystem::copy(R"(C:\Users\wenidc\Desktop\test\hello)", R"(C:\Users\wenidc\Desktop\test\test3)", std::filesystem::copy_options::skip_existing | std::filesystem::copy_options::recursive | std::filesystem::copy_options::directories_only);
return 0;
}

可以传递的比较有用的附加选项有:

  • recursive 递归复制子目录
  • directories_only 只复制目录结构 - 忽略文件

可以用|运算符来组合多个选项。

例子 删除文件

#include <filesystem>
int main() {
std::filesystem::remove(R"(C:\Users\wenidc\Desktop\test\test3)"); // test3 是一个空目录
std::filesystem::remove(R"(C:\Users\wenidc\Desktop\test\test2\test2-1\1.jpg)");
}

当参数是不为空的目录时,会抛出异常:

#include <filesystem>
#include <iostream>
int main() {
try {
std::filesystem::remove(R"(C:\Users\wenidc\Desktop\test\test2)");
} catch (const std::filesystem::filesystem_error& e) {
std::cout << "Code: " << e.code() << std::endl;
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}

例子 删除目录

当我们想删除一个目录,并且想删除其中的所有内容时,可以使用 remove_all()。它返回一个整数,表示删除了多少文件和目录。

返回值是一个整数,类型为uintmax_t,一个固定的整数,可以像任何其他整数类型一样使用。

#include <filesystem>
#include <iostream>
int main() {
auto countFileDeleted{std::filesystem::remove_all(R"(C:\Users\wenidc\Desktop\test\test2)")};
std::cout << "Deleted " << countFileDeleted << " files or directories.\n";
return 0;
}

例子 移动和重命名文件或目录

可以使用 rename() 函数移动或重命名文件或目录。

第一个参数是要移动的条目的位置(路径)。第二个参数是想要将其移动到的位置。

#include <filesystem>
int main() {
std::filesystem::rename(R"(C:\Users\wenidc\Desktop\test\hello)", R"(C:\Users\wenidc\Desktop\test\world)");
return 0;
}

例子 获取磁盘空间

space() 返回一个 space_info 结构,该结构具有下面三个数据成员,所有三个值均以字节(bytes)为单位:

  • capacity - 该位置的可用总容量
  • free - 该位置的可用空间容量
  • available - 可供程序写入的空间容量。这将小于或等于 free
#include <filesystem>
#include <iostream>
int main() {
auto [capacity, free, available] {std::filesystem::space(R"(c:\)")};
constexpr int bytesInGB{1024 * 1024 * 1024};
std::cout << "Capacity: " << capacity / bytesInGB << "GB\n";
std::cout << "Free: " << free / bytesInGB << "GB\n";
std::cout << "Available: " << available / bytesInGB << "GB\n";
return 0;
}

路径操作

例子 路径对象和字符串互转

#include <filesystem>
#include <iostream>
int main() {
// 从字符串创建 path
std::filesystem::path location{R"(C:\Users\wenidc\Desktop\test)"};
std::cout << location.string() << std::endl;
// 从 path 创建 directory_entry
std::filesystem::directory_entry entry{location};
// 使用 path() 获取与 directory_entry 关联的 path
std::cout << entry.path().string() << std::endl;
return 0;
}

例子 获取路径的特定部分

#include <filesystem>
#include <iostream>
int main() {
// 从字符串创建 path
std::filesystem::path location{R"(C:\Users\wenidc\Desktop\test\111.jpg)"};
std::cout << "File Name: " << location.filename().string() << std::endl; // 111.jpg
std::cout << "File Stem: " << location.stem().string() << std::endl; // 111
std::cout << "File Extension: " << location.extension().string() << std::endl; // .jpg
std::cout << "Parent Path: " << location.parent_path().string() << std::endl; // C:\Users\wenidc\Desktop\test
std::cout << "Root Path: " << location.root_name().string() << std::endl; // C:
return 0;
}

例子 判断路径的特定部分是否存在

.filename()用于获取文件名,与之对应的.has_filename()返回一个布尔值用于判断文件名是否存在。其他的函数也类似,在前面加has_就变为了与之等同的布尔函数。

#include <filesystem>
#include <iostream>
int main() {
// 从字符串创建 path
std::filesystem::path location{R"(C:\Users\wenidc\Desktop\test\111.jpg)"};
if (location.has_filename()) {
std::cout << location.string() << " has file name: " << location.filename().string() << std::endl;
} else {
std::cout << location.string() << " has no file name\n";
}
std::filesystem::path file{R"(c:\hi.txt)"};
if (file.has_extension()) {
std::cout << file.string() << " has a file extension: " << file.extension() << std::endl;
}
return 0;
}

例子 路径文件名部分的操作

#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path file{R"(C:\Users\wenidc\Desktop\test\hello.txt)"};
std::cout << file.string() << std::endl;
file.replace_filename("world.txt");
std::cout << file.string() << std::endl; // C:\Users\wenidc\Desktop\test\world.txt
file.replace_extension("doc");
std::cout << file.string() << std::endl; // C:\Users\wenidc\Desktop\test\world.doc
file.remove_filename();
std::cout << file.string() << std::endl; // C:\Users\wenidc\Desktop\test\
return 0;
}

例子 路径拼接

#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path file{R"(C:\Users\wenidc\Desktop\test\)"};
std::cout << file.string() << std::endl;
file /= "test1";
std::cout << file.string() << std::endl; // C:\Users\wenidc\Desktop\test\test1
file /= "hello.txt";
std::cout << file.string() << std::endl; // C:\Users\wenidc\Desktop\test\test1\hello.txt
file.remove_filename() /= "subdirectory";
std::cout << file.string() << std::endl; // C:\Users\wenidc\Desktop\test\test1\subdirectory
(file /= "nested") /= "directory";
std::cout << file.string() << std::endl; // C:\Users\wenidc\Desktop\test\test1\subdirectory\nested\directory
return 0;
}

例子 判断是绝对路径还是相对路径

#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path fileA{R"(C:\test\hello.txt)"};
if (fileA.is_absolute()) {
// 是否是绝对路径
std::cout << fileA.string() << " is absolute.\n";
}
std::filesystem::path fileB{R"(hello.txt)"};
if (fileB.is_relative()) {
// 是否是相对路径
std::cout << fileB.string() << " is relative.\n";
}
return 0;
}

例子 更改当前路径(current path)或者说当前工作目录(current working directory)

#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path file{R"(Teach_Youself_CPP_21days.jpg)"};
std::filesystem::directory_entry entry{file};
if (!entry.exists()) {
std::cout << "The file was not found: " << entry.path().string() << std::endl;
}
std::cout << "The default working directory is: " << std::filesystem::current_path().string() << std::endl;
std::filesystem::current_path(R"(C:\Users\Aoyu\Desktop\test)");
std::cout << "The current working directory was changed to: " << std::filesystem::current_path().string() << std::endl;
// 在更改 当前工作目录(current working directory) 后应当创建一个新的 directory_entry 实例来重新检查文件的存在性。
std::filesystem::directory_entry newEntry{file};
if (newEntry.exists()) {
std::cout << "The file was found!\n";
}
return 0;
}

目录迭代器

例子 遍历某目录内所有条目,使用迭代器

#include <filesystem>
#include <iostream>
int main() {
std::filesystem::directory_iterator start{R"(C:\Users\wenidc\Desktop\test)"};
std::filesystem::directory_iterator end{};
for (auto iter{start}; iter != end; ++iter) {
std::cout << iter->path().string() << std::endl;
}
return 0;
}

例子 遍历某目录内所有条目,使用范围

#include <filesystem>
#include <iostream>
#include <ranges>
int main() {
std::filesystem::directory_iterator start{R"(C:\Users\wenidc\Desktop\test)"};
// std::filesystem::directory_iterator end{}; // 用不到
// std::ranges::subrange directoryEntries{start, end};
std::ranges::subrange directoryEntries{start};
for (const auto& entry : directoryEntries) {
std::cout << entry.path().string() << std::endl;
}
return 0;
}

配合std::ranges::for_each()使用,不用显式构建subrange

#include <filesystem>
#include <iostream>
#include <ranges>
#include <algorithm>
void log(const std::filesystem::directory_entry& entry) {
std::cout << entry.path().string();
if (entry.is_directory()) {
std::cout << " (Directory)\n";
} else if (entry.is_regular_file()) {
std::cout << " (" << entry.file_size() << " Bytes)\n";
}
}
int main() {
std::filesystem::directory_iterator start{R"(C:\Users\wenidc\Desktop\test)"};
// std::ranges::subrange directoryEntries{start};
std::ranges::for_each(start, log);
return 0;
}

输出:

C:\Users\wenidc\Desktop\test\Teach_Youself_CPP_21days.jpg (94320 Bytes)
C:\Users\wenidc\Desktop\test\test1 (Directory)
C:\Users\wenidc\Desktop\test\world (Directory)

例子 遍历目录内的所有条目,包括各级子目录内的所有条目

默认情况下,directory_iterator 仅迭代目录中的第一级条目。如果条目是子目录,不会深入进去迭代它里面的条目。

可以通过在 is_directory() 返回 true 时使用递归来自己实现此逻辑。不过标准库已经为我们提供了recursive_directory_iterator

下面的代码与前面相同,只是把directory_iterator改为了recursive_directory_iterator

#include <filesystem>
#include <iostream>
#include <ranges>
int main() {
std::filesystem::recursive_directory_iterator start{R"(C:\Users\wenidc\Desktop\test)"};
std::ranges::subrange directoryEntries{start};
for (const auto& entry : directoryEntries) {
std::cout << entry.path().string() << std::endl;
}
return 0;
}

可以用recursive_directory_iteratordepth()成员函数来访问当前的递归深度(当前条目嵌套了多少层):

#include <filesystem>
#include <iostream>
int main() {
std::filesystem::recursive_directory_iterator start{R"(C:\Users\wenidc\Desktop\test)"};
std::filesystem::recursive_directory_iterator end{};
for (auto iter{start}; iter != end; ++iter) {
std::cout << "Depth " << iter.depth() << " - " << iter->path().string() << std::endl;
}
return 0;
}

输出:

Depth 0 - C:\Users\wenidc\Desktop\test\Teach_Youself_CPP_21days.jpg
Depth 0 - C:\Users\wenidc\Desktop\test\test1
Depth 0 - C:\Users\wenidc\Desktop\test\world
Depth 1 - C:\Users\wenidc\Desktop\test\world\111.png

参考