精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
服务方向
联系方式
锐英源精品原创,禁止转载和任何形式的非法内容使用,违者必究。点名“简易百科”和“闲暇巴”盗用锐英源原创内容。
C++17文件系统要点有两个,A目录遍历容易了,B获取磁盘空间和剩余空间方便了。文件系统用系统API也可以实现,不过显的C++不完整,所以C++标准组织就完成了文件系统部分,有了文件系统部分容易移植和兼容多操作系统平台代码。另外有些IO的性能,会比自己调用API好一些,大家还是要多尝试用下。
C++17 添加了一个新的库,旨在大大简化文件系统及其组件(如路径、文件和目录)的使用。由于每两个程序都以某种方式与文件系统一起工作,因此我们有一个新功能,可以为我们节省文件系统中文件路径的繁琐工作。毕竟,有些文件路径是绝对的,而另一些是相对的,也许它们甚至不是直接的,因为它们包含间接地址:.(当前目录)和 .. (父目录)。
为了分隔目录,Windows 操作系统使用反斜杠 (\),而 Linux、MacOS 和各种类 Unix 操作系统使用斜杠 (/)。
C++17 中引入的新功能对不同的操作系统支持相同的操作原理,因此我们不需要为支持不同操作系统的可移植程序编写不同的代码片段。
注: 如果您使用 Visual Studio 2017 版本 15.7 及更高版本,则支持新的 C++17 <filesystem> 标准。这是一个全新的实现,与之前的 std::experimental 版本不兼容。通过符号链接支持、错误修复和标准要求行为的更改,这成为可能。目前,包括 <filesystem> 提供了新的 std::filesystem 和之前的 std::experimental::filesystem。包括 <experimental/filesystem> 仅提供旧的实验性实现。实验性实现将在下一个 ABI (Application Binary Interface) 突破性版本的库中删除。
以下示例说明了如何使用 filesystem::path 和 filesystem::exists当我们使用与 Filesystem 关联的库时,std::filesystempath类至关重要,因为大多数函数和类都与它相关联。该filesystem::exists函数允许您检查指定的文件路径是否确实存在。
让我们选择属性以在 Visual Studio 中打开项目 Property Pages,然后选择 Configuration properties > Debugging > Command Arguments,并定义命令行参数:"C:\Test1":
然后运行以下代码:
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main(int argc, char *argv[]) {
if (argc != 2) {
std::cout << "Usage: " << argv[0] << " <path>\n";
return 1;
}
fs::path path_CmdParam { argv[1] };
if (fs::exists(path_CmdParam)) {
std::cout << "The std::filesystem::path " << path_CmdParam << " is exist.\n";
}
else {
std::cout << "The std::filesystem::path " << path_CmdParam << " does not exist.\n";
return 1;
}
return 0;
}
我的计算机上不存在路径 'C:\Test1',因此程序会打印以下输出:
在 main 函数中,我们检查用户是否提供了命令行参数。如果得到否定的响应,我们会发出错误消息并在屏幕上显示如何正确使用该程序。如果提供了文件的路径,我们将基于它创建filesystem::path对象的实例。我们基于包含文件路径描述的字符串初始化了 path 类的对象。该filesystem::exists函数允许您检查指定的文件路径是否确实存在。到目前为止,我们无法确定,因为可以创建不属于真实文件系统对象的 path 类对象。该exists函数仅接受 path 类的实例,如果它实际存在,则返回true。这个函数能够确定我们传递给它的路径(绝对或相对),这使得它非常有用。
除了该filesystem::exists功能之外,该filesystem模块还提供了许多用于创建、删除、重命名和复制的有用功能:
filesystem::create_directories 创建一个目录(即使我们需要创建多个子目录)
filesystem::remove - 删除文件或空目录
filesystem:: remove_all - 删除文件或递归删除目录
filesystem::copy、filesystem::copy_file 和 filesystem::rename 用于使用操作系统工具复制和重命名目录和文件
filesystem::resize_file 截断或增加文件大小(如果文件之前小于新文件大小,则文件大小会增加,并且新区域看起来像是零填充的)
在下一个示例中,我们将说明如何使用绝对和相对文件路径,以了解 path 类及其关联的帮助程序函数的优势。path 类自动执行所有必要的字符串转换。它接受宽字符数组和窄字符数组的参数,即std::string和std::wstring,格式为 UTF8 或 UTF16 类型。
C++
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main(int argc, char *argv[]) {
fs::path pathToFile { L"C:/Test/Test.txt" };
std::cout << "fs::current_path() = "
<< fs::current_path() << std::endl
<< "fs::absolute (C:\\Test\\Test.txt) = "
<< fs::absolute(pathToFile) << std::endl
<< std::endl;
std::cout << "(C:\\Test\\Test.txt).root_name() = "
<< pathToFile.root_name() << std::endl
<< "(C:\\Test\\Test.txt).root_path() = "
<< pathToFile.root_path() << std::endl
<< "(C:\\Test\\Test.txt).relative_path() = "
<< pathToFile.relative_path() << std::endl
<< "(C:\\Test\\Test.txt).parent_path() = "
<< pathToFile.parent_path() << std::endl
<< "(C:\\Test\\Test.txt).filename() = "
<< pathToFile.filename() << std::endl
<< "(C:\\Test\\Test.txt).stem() = "
<< pathToFile.stem() << std::endl
<< "(C:\\Test\\Test.txt).extension() = "
<< pathToFile.extension() << std::endl;
fs::path concatenateTwoPaths{ L"C:/" };
concatenateTwoPaths /= fs::path("Test/Test.txt");
std::cout << "\nfs::absolute (concatenateTwoPaths) = "
<< fs::absolute(concatenateTwoPaths) << std::endl;
return 0;
}
current_path() 函数返回当前工作目录的绝对路径(在上面的示例中,该函数返回我笔记本电脑上的主目录,因为我从那里启动了应用程序)。接下来,我们使用 fs::path pathToFile执行各种操作。
该类具有多个方法,这些方法返回有关路径本身不同部分的信息,而不是它可以引用的文件系统对象。path
pathToFile 的root_name() 返回 generic-format 路径的根名称
pathToFile 的root_path() 返回路径的根路径
pathToFile 的relative_path() 返回相对于 root-path 的 path
pathToFile 的parent_path() 返回父目录的路径
pathToFile 的filename() 返回路径的通用格式 filename 组件
pathToFile 的stem() 返回由去掉其扩展名的通用格式路径标识的文件名
pathToFile 的extension() 返回文件名的扩展名
在最后三行(return语句之前),我们可以看到filesystem::path如何自动规范化路径分隔符。
我们可以在构造函数参数中使用 '/' 作为目录分隔符。这允许我们使用相同的字符串在 Unix 和 WINDOWS 环境中存储路径。
现在让我们看看如何使用<filesystem>实现目录的递归和非递归遍历来列出目录中的所有文件。
获取以下目录:
The TestFolder
并运行以下代码:
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main(int argc, char *argv[]) {
fs::path dir{ "C:\\Test\\" };
if (!exists(dir)) {
std::cout << "Path " << dir << " does not exist.\n";
return 1;
}
for (const auto & entry : fs::directory_iterator(dir))
std::cout << entry.path() << std::endl;
return 0;
}
请注意,该循环使用 C++17 中引入的 std::filesystem::directory_iterator,它可以用作forLegacyInput迭代directory_entry目录元素(但不访问子目录)。尽管如此,我们的目录 "C:\Test" 包含子目录:
如果我们将上面的代码更改为 std::filesystem::recursive_directory_iterator代替fs::directory_iterator
for (const auto & entry : fs::recursive_directory_iterator(dir))
std::cout << entry.path() << std::endl;
std::filesystem::recursive_directory_iterator 是一个LegacyInputIterator迭代目录元素的函数,并递归地迭代所有子目录的directory_entry条目。
此外,与前一种情况一样,迭代顺序未指定,但每个目录条目仅访问一次。
我们还可以使用 std::copy_if 算法获取子目录列表,该算法将[begin,end) 返回的范围内的元素中满足 [](const fs::path& path)返回为true的元素复制到 subdirs向量的范围,并使用构造的std::back_inserter在容器 'subdirs' 的末尾插入新元素(见下面的代码)。
std::copy显示 'subdirs' 向量的结果:
#include <iostream>
#include <filesystem>
#include <vector>
#include <iterator>
namespace fs = std::filesystem;
int main(int argc, char *argv[]) {
fs::path dir{ "C:\\Test\\" };
if (!exists(dir)) {
std::cout << "Path " << dir << " does not exist.\n";
return 1;
}
std::cout << "\nLet's show all the subdirectories " << dir << "\n";
std::vector<fs::path> paths;
for (const auto & entry : fs::recursive_directory_iterator(dir))
paths.push_back(entry.path());
fs::recursive_directory_iterator begin("C:\\Test");
fs::recursive_directory_iterator end;
std::vector<fs::path> subdirs;
std::copy_if(begin, end, std::back_inserter(subdirs), [](const fs::path& path) {
return fs::is_directory(path);
});
std::copy(subdirs.begin(), subdirs.end(), std::ostream_iterator<fs::path>(std::cout, "\n"));
return 0;
}
注:里面的copy的最后一个参数是函数形式。
在 C++17 中,<filesystem>有许多工具用于获取有关文件/目录的元信息和对文件系统执行操作。
例如,我们有机会获取文件大小、读取或设置进程上次将数据写入给定文件的时间、读取或设置文件权限等。
让我们再次看一下上一个示例中的目录:
#include <iostream>
#include <fstream>
#include <filesystem>
namespace fs = std::filesystem;
void demo_perms(fs::perms p)
{
std::cout << ((p & fs::perms::owner_read) != fs::perms::none ? "r" : "-")
<< ((p & fs::perms::owner_write) != fs::perms::none ? "w" : "-")
<< ((p & fs::perms::owner_exec) != fs::perms::none ? "x" : "-")
<< ((p & fs::perms::group_read) != fs::perms::none ? "r" : "-")
<< ((p & fs::perms::group_write) != fs::perms::none ? "w" : "-")
<< ((p & fs::perms::group_exec) != fs::perms::none ? "x" : "-")
<< ((p & fs::perms::others_read) != fs::perms::none ? "r" : "-")
<< ((p & fs::perms::others_write)!= fs::perms::none ? "w" : "-")
<< ((p & fs::perms::others_exec) != fs::perms::none ? "x" : "-")
<< '\n';
}
std::time_t getFileWriteTime(const std::filesystem::path& filename) {
#if defined ( _WIN32 )
{
struct _stat64 fileInfo;
if (_wstati64(filename.wstring().c_str(), &fileInfo) != 0)
{
throw std::runtime_error("Failed to get last write time.");
}
return fileInfo.st_mtime;
}
#else
{
auto fsTime = std::filesystem::last_write_time(filename);
return decltype (fsTime)::clock::to_time_t(fsTime);
}
#endif
}
int main(int argc, char *argv[]) {
fs::path file_Test1 { "C:\\Test\\Test1.txt" };
std::string line;
std::fstream myfile(file_Test1.u8string());
std::cout << "The " << file_Test1 << "contains the following value : ";
if (myfile.is_open())
{
while (getline(myfile, line)) {
std::cout << line << '\n';
}
myfile.close();
}
else {
std::cout << "Unable to open " << file_Test1 ;
return 1;
}
std::cout << "File size = " << fs::file_size(file_Test1) << std::endl;
std::cout << "File permissions = ";
demo_perms(fs::status(file_Test1).permissions());
std::time_t t = getFileWriteTime(file_Test1);
std::cout << file_Test1 << " write time is : "
<< std::put_time(std::localtime(&t), "%c %Z") << '\n';
return 0;
}
在上面的代码中,我们可以看到,使用 fs::file_size,我们可以在不读取其内容的情况下确定文件的大小(如程序输出所示,文件包含 5 个字符:TEST1,而这正是 fs::file_size 函数计算得出的。要读取或设置文件权限,我们使用 fs::status("C:\Test\Test1.txt".permissions() 和demo_perms。为了演示文件 "C:\Test\Test1.txt" 的最后修改,我们使用了该getFileWriteTime函数。正如这里所写的:clock::to_time_t (ftime)不适用于 MSVC,因此该函数使用适用于 _WIN32(非 POSIX Windows)和其他操作系统的可移植解决方案。
在下一个示例中,我们将显示有关文件系统上可用空间的信息。fs::space 全局函数返回 fs::space_info 类型的对象,该对象描述指定路径所在的媒体上的可用空间量。如果我们传输位于同一媒体上的多个路径,结果将是相同的。
#include <iostream>
#include <fstream>
namespace fs = std::filesystem;
int main(int argc, char *argv[]) {
fs::space_info diskC = fs::space("C:\\");
std::cout << "Let's show information about disk C : \n";
std::cout << std::setw(15) << "Capacity"
<< std::setw(15) << "Free"
<< std::setw(15) << "Available"
<< "\n"
<< std::setw(15) << diskC.capacity
<< std::setw(15) << diskC.free
<< std::setw(15) << diskC.available
<< "\n";
return 0;
}
上述代码的输出显示有关可用磁盘空间的信息:
返回的 fs::space_info 对象包含三个指示符(均以字节为单位):
Capacity 是文件系统的总大小,以字节为单位
Free 是文件系统上的可用空间,以字节为单位
Available 是非特权进程可用的可用空间(可能等于或小于 free)