C++17 详解 23 —文件系统
本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。
翻译太特么累人了……剩余部分还是只做摘要翻译吧。
12. 文件系统
12.1 总览
头文件:
1 |
std::filesystem 是一个模块,同时也是一个命名空间。核心元素包括:
- std::filesystem::path 对象允许你操作代表存在或不存在的文件和目录的路径。
- std::filesystem::directory_entry 表示一个存在的路径,并附带额外状态信息,比如最后修改时间、文件尺寸以及其它属性。
- 目录迭代器允许你遍历一个给定的目录。库提供了递归和非递归版本。
- 大量支持函数比如获取路径信息、文件操作、权限、创建目录等等。
12.2 综合应用实例
1 |
|
Windows 下输出:
1 | .\ListFiles.exe D:\testlist\ |
所有类型、函数和名字都存在于 std::filesystem 命名空间内,为了方便用到了命名空间别名(namespace alias):
1 | namespace fs = std::filesystem; |
main 函数中:
程序接受一个来自命令行的可选参数,如果为空则使用当前系统路径:
1 | const fs::path pathToShow{ argc >= 2 ? argv[1] : fs::current_path() }; |
fs::absolute() 函数将输入路径转换成绝对路径。
DisplayDirectoryTree 函数中:
使用 directory_iterator 检查目录并查找(子)文件或(子)目录:
1 | for (const auto& entry : fs::directory_iterator(pathToShow)) |
每次迭代都返回一个新的 fs::directory_entry 对象。
通过 entry.is_directory() 判断当前项是否是子目录,如果是则递归调用 DisplayDirectoryTree。
12.3 path 对象
std::filesystem::path 是库的核心构成。
path 由下列元素组成:
root-name root-directory relative-path
+(可选)root-name:POSIX 系统没有根名称。Windows 上它通常是驱动器名称,像“C:”
- (可选)root-directory:区分相对路径和绝对路径
- relative-path:
- filename
- (可选)directory separator
- (可选)relative-path
path 类实现了许多函数以提取路径中的各个部分:
- path::root_name()
- path::root_directory()
- path::root_path():上边两部分的组合
- path::relative_path()
- path::parent_path()
- path::filename()
- path::stem():filename() 的前半部分
- path::extension():filename() 的扩展名部分
如果某部分在路径中不存在,上述函数会返回一个空 path 对象(path::empty() = true)。
同时,上述函数都配有一个 bool has_xxx() 的查询函数。
示例:
1 | const filesystem::path testPath{ ... }; |
输入为 “C:\Windows\system.ini” 时的输出:
1 | root_name() = "C:" |
POSIX 系统上,输入为 “/usr/temp/abc.txt” 时的输出:
1 | no root-name |
std::filesystem::path 同时还实现了 begin() 和 end() 函数,所以你可以在 range based for 循环里使用 path:
1 |
|
输出:
1 | Examining the path "C:\users\abcdef\AppData\Local\Temp\" through iterators gives |
译注:我以一个多层子目录的路径示例替换了原书示例。上边代码来自 cppreference。
12.3.1 path 操作
其它重要函数:
- path::append() 把一个 path 和目录分隔符 append 到另一个 path 上
- path::concat() 拼接 path,不带目录分隔符
- path::clear() 使 path 为空
- path::remove_filename() 删除路径的 filename 部分
- path::replace_filename() 替换 filename 组件
- path::replace_extension() 替换扩展名
- path::swap() 交换两个路径
- path::compare() 比较路径,返回 int
- path::empty() 是否为空
比较
除 path::compare() 函数外,还支持操作符比较:
1 | == != < > <= >= |
所有方法均以路径的原生格式逐元素比较。
1 | fs::path p1 { "/usr/a/b/c" }; |
Windows 上还能测试带 root path 的路径:
1 | p1 = "C:/test"; |
同样能处理 Windows 上以不同格式表示的 path:
1 | fs::path p3 { "/usr/a/b/c" }; // Windows 上它被转换为原生格式 |
路径组合
- path::append()
添加一个目录分隔符和路径。
同样支持操作符:/
/=
。
1 | fs::path p1{"C:\\temp"}; |
- path::concat()
只添加 path 的字符串而不带目录分隔符。
操作符形式:+
+=
1 | fs::path p2("C:\\temp\\"); |
如果传入 path 带有 root-name,且 root-name 跟当前 path 的 root-name 不同,append 操作会替换当前路径:
1 | auto resW = fs::path{"foo"} / "D:\"; // Windows |
流操作符
path 类实现了 operator >> 和 operator <<。
操作符里用到了 std::quoted 以保存正确的格式。这会导致在 Windows 上以原生格式输出 path 中的 “\”。
比如 POSIX 上:
1 | fs::path p1 { "/usr/test/temp.xyz" }; |
会输出 "/usr/test/temp.xyz"
(译注:带引号输出),
Windows 上:
1 | fs::path p2{ "usr\\test\\temp.xyz" }; |
会输出:"usr\\test\\temp.xyz"
(译注:带引号,同时保留了 “\”)。
路径格式和转换
路径格式有两种:
- 通用(generic)格式,标准格式(基于 POSIX 格式)
- 原生(native)格式,某些特别的实现所用
路径格式在 Windows 和 POSIX 系统上的表现是不一样的。
POSIX 系统上两种格式是一样的;Windows 上不一样。
Windows 上使用反斜杠(\),且 Windows 上有 root-name,比如 C: D: 等。
POSIX 系统上以 char 和 std::string 存储路径字符;Windows 上用的是 wchar_t 和 std::wstring。
原生格式接口:
- path::c_str():返回 value_type*,等同于 path::native().c_str()
- path::native():返回 string_type&
从原生格式转换接口:
- path::string():转换成 string
- path::wstring():转换成 wstring
- path::u8string():转换成 u8string
- path::u16string():转换成 u16string
- path::u32string():转换成 u32string
12.4 directory_entry 和 directory_iterator
path 可以表示一个存在或不存在的文件或路径,directory_entry 和 directory_iterator 指向存在的文件或目录。
使用 directory_iterators 遍历路径
- directory_iterator:迭代单个目录,前向迭代器。
- recursive_directory_iterator:递归迭代,前向迭代器。
遍历顺序都是不确定的。
如果迭代器被创建后,迭代器指向的目录树中有文件或目录被添加或删除,标准没有规范迭代器是否已知这种变化(译注:即未定义行为)。
“.” 和 “..” 这两个特殊目录会被迭代器跳过。
directory_entry (常用)方法
- directory_entry::assign():替换对象中的路径并调用 refresh() 更新缓存的属性
- directory_entry::replace_filename():替换对象中路径的 filename 并调用 refresh() 更新缓存的属性,效果等同于 path::replace_filename()
- directory_entry::refresh():更新缓存的文件属性
- directory_entry::exists():检查对象中路径是否指向一个存在的文件/目录
- directory_entry::is_directory()
- directory_entry::is_regular_file()
- directory_entry::file_size()
- directory_entry::last_write_time()
- directory_entry::status()
12.5 (常用)支持函数
查询类:
- filesystem::is_directory()
- filesystem::is_empty()
- filesystem::is_regular_file()
- filesystem::exists()
- filesystem::file_size()
- filesystem::last_write_time()
路径相关:
- filesystem::absolute():合成一个绝对路径
- filesystem::canonical():合成一个规范的绝对路径,即在其通用格式表示中无点(.)、双点(..)元素或符号链接的绝对路径;路径不存在时抛出异常
- filesystem::weakly_canonical():同 canonical(),但是不检查路径是否存在
- filesystem::relative():返回路径相对于指定路径(默认为 current_path())的相对路径;如果两者无关联,返回空
relative (“c”,”/a/b”) == “”
- filesystem::proximate():同 relative(),但是两个路径无关时返回源路径
proximate(“c”,”/a/b”) == “c”
- filesystem::current_path():返回或设置当前目录
- filesystem::equivalent():检查两个路径是否指向同一个文件系统对象
目录和文件管理:
- filesystem::copy():复制指定文件或目录到目标位置,可以通过 std::filesystem::copy_options 枚举控制具体的复制行为,比如是否递归、是否覆盖等
- filesystem::copy_file():复制单个文件
- filesystem::create_directory():创建新目录
- filesystem::create_directories():递归创建指定路径上不存在的目录
- filesystem::permissions():修改路径访问权限
- filesystem::remove():删除单个文件或空目录
- filesystem::remove_all():递归删除指定目录及其内容
- filesystem::rename():移动或重命名文件或目录
- filesystem::resize_file():修改文件大小,小于当前大小截断内容;大于当前以 0 填充
- filesystem::space():返回指定文件系统空间信息,比如容量、未用、可用
- filesystem::status():返回指定文件或目录的状态信息
- filesystem::temp_directory_path():返回系统临时文件目录
12.6 错误处理 & 文件竞争(race)
所有 filesystem 库函数均可以用异常处理错误,同时所有函数都有一个带 error_code 参数的重载版本,可以通过 error_code 判断错误。
所有 filesystem 操作都是非线程安全的。