C++标准 — C++17特性 — 文件系统 — path 路径处理
类型 path 的对象表示文件系统上的路径。只有路径的语法外观得到处理: 路径名可能表示不存在的路径,或甚至不允许存在于当前文件系统或操作系统的路径。
路径名拥有下列语法:
- root-name(可选):标识具有多个根目录的文件系统(如 “C:” 或 “//myserver”)。有歧义的情况下,将组成合法根名的最长序列当做根名 。标准库可以在 OS API 所了解的根名外,定义额外的根名。
- root-directory(可选):目录分隔符,若存在,则标记此路径为绝对。若缺失(且异于根名的首元素是文件名),则路径为相对且要求另一路径作为解决此文件名的起始位置。
- 包括0个或多个下列语法:
- file-name:不由目录分隔符或偏好目录分隔符组成的字符序列(操作系统或文件系统可能加上附加限制)。此名称可能标识一个文件、硬链接或目录。有两种特殊的文件名需要被识别: .(当前目录),. .(上层目录)。
- directory-separators:正斜杠字符 / 或作为 path::preferred_separator 提供的另一种字符。若重复此字符,则它被处理成单个目录分隔符:/usr///lib 与 /usr/lib 相同。
可以通过下列方法将路径标准化:
- 若路径为空,则停止(空路径的正常形式是空路径);
- 替换每个目录分隔符(可以由多重斜杠组成)为单个 path::preferred_separator;
- 替换根目录名中的每个斜杠字符为 path::preferred_separator;
- 移除每个 . 和紧随其后的目录分隔符;
- 移除每个非 . . 和其后紧随的目录分隔符和 . .;
- 若存在根目录 ,则移除紧随其后的所有 . . 及任何目录分隔符 ;
- 如果最后的文件名是 . .,则移除尾部的所有目录分隔符;
- 如果目录为空,需要加上一个 .。
一、分解
#include <iostream>
#include <filesystem>
using namespace std;
using namespace std::filesystem;
int main()
{
path p("E:/Project/c++/main.cpp");
cout << "path root name is : " << p.root_name() << endl;
cout << "path root directory is : " << p.root_directory() << endl;
cout << "path root path is : " << p.root_path() << endl;
cout << "path relative path is : " << p.relative_path() << endl;
cout << "path parent path is : " << p.parent_path() << endl;
cout << "path filename is : " << p.filename() << endl;
cout << "path stem is : " << p.stem() << p.stem() << endl;
cout << "path extension is : " << p.extension() << endl;
}
其中,stem 返回的字符串为文件名开头到其最后一个 .。
二、查询
三、拼接
1、append、/=
以第一种形式为例:
- 若 p.is_absolute() || (p.has_root_name() && p.root_name() != root_name()) ,则以 p 替换当前路径,如同用 operator=(p) 并终止;
- 否则,若 p.has_root_directory() ,则移除 *this 的通用格式路径名的任何根目录及整个相对路径;
- 否则,若 has_filename() || (!has_root_directory() && is_absolute()) ,则附加 path::preferred_separator 到 this 的通用格式;
- 用以上之一处理后,附加 p 的原生格式路径名到 this 的原生格式,从 p 的通用格式忽略任何 root-name 。
针对根目录直接拼接目录或文件名,其输出可能看着比较怪异,但是运行环境是可以确定相应文件或目录的:
int main()
{
path newPath1 = path("E:Project") / "test.cpp";
path newPath2 = path("E:") / "test.cpp";
cout << "newPath1 = " << newPath1 << " newPath2 = " << newPath2 << endl;
copy(newPath1, newPath2);
}
2、concat、+=
与前面的相比,concat 和 += 无论如何都不会插入目录分隔符。
四、修改
int main()
{
path p("E:/Project/c++/main.cpp");
path other("E:Project");
cout << "original path : " << p << endl;
cout << "preffered path : " << p.make_preferred() << endl;
cout << "remove filename : " << p.remove_filename() << endl;
cout << "replace filename : " << p.replace_filename("test") << endl;
cout << "replace extension : " << p.replace_extension("cc") << endl;
p.swap(other);
cout << "after swap : " << p << endl;
p.clear();
cout << "after clear : " << p << endl;
}
五、遍历
path 支持了 begin 和 end 接口,使我们可以像访问容器一样访问它们:
int main()
{
path dir("E:/Project/c++/");
path file("E:/Project/c++/main.cpp");
cout << "visit dir:" << endl;
for (path sub : dir)
{
cout << sub << " ";
}
cout << endl << "visit path:" << endl;
for (path sub : file)
{
cout << sub << " ";
}
}
六、比较
path 类支持通过 compare 方法和各种比较操作符进行路径比较。
路径对象之间的排序方式为:
- 若 root_name().native().compare(p.root_name().native()) 非零,则返回该值(实际上为字符串比较) 。
- 否则若 has_root_directory() != p.has_root_directory() ,则若 has_root_directory() 为 false 则返回小于零的值,否则返回大于零的值。
- 否则返回比较小于、等于或大于 0 的值,若 path 的相对部分 (relative_path()) 分别按字典序小于、等于或大于 p 的相对部分 (p.relative_path()) 。逐元素进行比较,如同从 begin() 到 end() 迭代 path 。
template<class T>
void compare(T lhs, T rhs)
{
auto result = lhs.compare(rhs);
cout << lhs << " " << (result > 0 ? ">" : (result < 0 ? "<" : "==")) << " " << rhs << endl;
}
int main()
{
path path1("E:/Project/c++/");
path path2("E:/Project/");
path path3("E:/Project/c++/main.cpp");
path path4("D:/Project/c++");
path path5("F:/Project/c++");
path path6("Project/c++");
compare(path1, path1);
compare(path1, path2);
compare(path1, path3);
compare(path1, path4);
compare(path1, path5);
compare(path1, path6);
}