做二手网站,建材商城网站建设,云上铺会员管理系统,重庆未来科技网站建设1. optional
在编程时#xff0c;我们经常会遇到可能会返回/传递/使用一个确定类型对象的场景。也就是说#xff0c;这个对象可能有一个确定类型的值也可能没有任何值。因此#xff0c;我们需要一种方法来模拟类似指针的语义#xff1a;通过nullptr表示指针为空。解决方法…1. optional
在编程时我们经常会遇到可能会返回/传递/使用一个确定类型对象的场景。也就是说这个对象可能有一个确定类型的值也可能没有任何值。因此我们需要一种方法来模拟类似指针的语义通过nullptr表示指针为空。解决方法就是定义该对象的同时再定义一个附加的bool类型来标志该对象有没有值。std::optional就是提供了一种类型安全的方式来实现。
1.1 使用optional
1.1.1 可选的返回值
#include optionaloptionalint aslnt(const string s)
{try{return stoi(s);}catch(...){return nullopt;}
}int main()
{for (auto s : { 42,077,hello,0x33 }){optionalint oi aslnt(s);if (oi)cout convert s to int: *oi endl;elsecout cant convert s endl;}
}我们实现一个函数用来对字符串转换为整形。这个操作可能会失败因此在失败的时候我们返回一个nullopt表示没有int值。并且通过解引用*来获取optional对象的值。除了这种方式还有has_value()和value()分别是判断是否有值和值是多少更为安全。
int main()
{for (auto s : { 42,077,hello,0x33 }){optionalint oi aslnt(s);if (oi.has_value())cout convert s to int: oi.value() endl;elsecout cant convert s endl;}
}1.1.2 可选的参数和数据成员
另一个例子是传递可选的参数或者设置可选的数据成员
class Name
{
private:string first;optionalstring middle;string last;
public:Name(string f,optionalstring m,string l): first{f},middle{m},last{l}{}friend ostream operator(ostream strm, const Name n){strm n.first ;if (n.middle){strm *n.middle ;}return strm n.last;}
};还有一个访问值的方法value_or()当没有值的时候可以制定一个备选值。比如
cout middle.value_or();1.2 optional类型和操作
1.2.1 optional类型
optional类
template class T
class optional
{};还定义了这些类型和对象
nullopt_t类型的nullopt表示可选对象无值。bad_optional_access异常类当无值的时候访问会抛出该异常。
1.2.2 optional的操作
操作符效果make_optional()创建一个用参数初始化的可选对象。emplace()给内含类型赋予一个新值。reset()销毁值使其变成无值状态。has_value()是否有值。value()访问内部值如果无值会抛出异常value_or()访问内部值如果无值将返回参数的值swap()交换两个对象的值。in_place构造函数的第一个参数如果要用多个参数初始化可选对象必须将这个当做第一个参数传递。
案例
int main()
{optionalint o1;optionalint o2(nullopt);optional o3{ 42 };optional o4{ complex{3.0, 4.0} };optional complexdouble o5{ in_place, 3.0, 4.0 };auto o5 make_optional(3.0);if (o5) // trueif (!o5) // falseif (o5.has_value()) // truecout o5; // ERRORcout *o5 endl; // 3.0cout o5.value() endl; // 3.0cout o1.value_or(1); // 1o1 3;o1.emplace(3); // 等价于上面o5.reset(); // o5nulloptoptionalint o6 move(o5); // o5nullopt
}1.3 特殊情况
一些特定的可选类型可能会导致特殊或意料之外的行为。
1.3.1 bool类型或原生指针的可选对象
optionalbool ob{false}; // false
if(!ob) // false
if(ob false) // trueoptionalint* op{nullptr};
if(!op) // false
if(op nullptr) // true1.3.2 可选对象的可选对象
理论上你可以定义可选对象的可选对象
int main()
{optionaloptionalstring oos1;optionaloptionalstring oos2 hello;optionaloptionalstring oos3{in_place,in_place,hello};oos1 hello;cout (*oos1 nullopt) endl; // 内层可选对象无值cout (oos1 nullopt) endl; // 外层可选对象无值
}2. variant
variant是C标准库提供的一个新的联合类型它最大的优势是提供了一种新的具有多态性的处理异质集合的方法。也就是说它可以帮助我们处理不同类型的数据并且不需要公共基类和指针。
2.1 variant的动机
起源于CC也提供了union的支持它的作用是持有一个值这个值的类型可能是指定的若干类型中的任意一个。然而这项语言特性有一些缺陷
对象不知道它们现在持有的值的类型。因此你不能持有非平凡类型例如string。你不能从union派生。
通过variantC标准库提供了一种可辨识的联合。
当前值的类型可知。可以持有任何类型的值。可以从它派生。
事实上variant持有的值有若干候选项这些选项通常有不同的类型。然而两个不同选项的类型也有可能相同这在多个类型相同的选项分别代表不同的含义是很有用。
variant的内存大小等于所有可能的底层类型中最大的再加上一个记录当前选项的固定内存开销。
2.2 使用variant
int main()
{variantint, string var{ hi };cout var.index() endl; // 1var 42;cout var.index() endl; // 0try{int i get0(var); // 通过索引访问string s getstring(var); // 通过类型访问}catch (const bad_variant_access e){cerr EXCEPTION: e.what() endl;}
}index()成员函数可以指出当前选项的索引。
初始化和赋值操作都会查找最匹配的选项。如果类型不能精确匹配可能会发生奇怪的事情。
注意variant不存在空、有引用成员、有C风格数组成员、有不完全类型(void)。
如果在初始化时并没有赋初值那么会将第一个参数作为选项并调用这个类型对应的默认构造函数如果没有默认构造那么会导致编译期错误。
struct A
{A(int i){cout A(int i) endl;}
};variantA,int v1; // ERROR辅助类型std::monostate提供了处理这种情况的能力还可以用来模拟空值的状态。
它的作用是可以作为variant的一个选项表示variant没有其他任何类型的值。所以可以保证variant能够默认构造。
variantmonostate,A,int v2; // OK
cout v2.index() endl; // 0你可以从variant派生
class Derived : public variantint,string
{};Derived d {{hello}};
cout d.index() endl; //1
cout get1(d) endl; // hello
d.emplace0(77); // 初始化int销毁string
cout get0(d) endl;2.3 variant的类型和操作
2.3.1 variant的类型
templatetypename... Types
class variant;此外还定义了下面的类型和对象
类模板variant_size。类模板variant_alternative。值variant_npos。类型monostate异常类bad_variant_access。
2.3.2 variant的操作
操作符效果emplace()销毁旧值并赋一个T类型选项的新值。emplace()销毁旧值并赋一个索引为Idx的选项的新值。valueless_by_exception()返回变量是否因为异常而没有值。index()返回当前选项的索引。swap()交换两个对象的值。holds_alternative()返回是否持有类型T的值。get()返回类型为T的选项的值。get()返回索引为Idx的选项的值。get_if()返回指向类型为T的选项的指针或nullptr。get_if()返回指向索引为Idx的选项的指针或nullptr。visit()对当前选项进行操作。
案例
int main()
{variantint, int, string v1; // 默认构造variantlong, int v2{ 42 }; cout v1.index() endl; // 1// 如果有两个类型同等匹配会导致歧义// variantlong, long v3{ 42 }; // ERROR// 为了传递多个值调用构造初始化variantcomplexdouble v4{ in_place_typecomplexdouble,3.0,4.0 };variantcomplexdouble v5{ in_place_index0,3.0,4.0 };// 访问值variantint, int, string var;auto a getdouble(var); // ERROR// 如果访问失败返回nullptr如果访问成功返回当前选项的指针if (auto ip get_if1(var); ip ! nullptr)cout *ip endl;// 修改值var hello;var.emplace1(42);}2.3.3 访问器
另一个处理variant对象的值的方法就是使用访问器。访问器为每一个可能的类型提供一个函数调用运算符的对象。当这些对象访问一个variant时就会调用和当前选项最匹配的函数。
struct MyVisitor
{void operator()(int i) const{cout int: i endl;}void operator()(string s) const{cout string: s endl;}void operator()(long double d) const{cout long double: d endl;}
};int main()
{variantint, string, long double var{ 42 };visit(MyVisitor(), var); // 调用operator()(int)var hello;visit(MyVisitor(), var); // 调用operator()(string)var 42.7;visit(MyVisitor(), var); // 调用operator()(long double)
}如果访问器没有某一个可能的类型的operator()重载那么visit()会导致编译期错误。如果有歧义也会导致编译器错误。
最简单的方式可以使用泛型lambda来作为访问器。
visit([](auto val)
{cout val endl;
},var);访问器的函数也可以返回值但所有返回值类型必须相同。例如
int main()
{using IntOrDouble variantint, double;vectorIntOrDouble coll{ 42,7.7,0,-0.7 };double sum{ 0 };for (const auto elem : coll){sum visit([](const auto val) - double{return val;}, elem);}cout sum endl;
}通过使用函数对象和lambda的重载器可以定义一系列的lambda其中最匹配的会被用作访问器。
假设有一个如下定义的重载器
templatetypename... Ts
struct overload : Ts...
{using Ts::operator()...;
};templatetypename... Ts
overload(Ts...)-overloadTs...;int main()
{variantint, string var(42);visit(overload{[](int i) { cout i endl; },[](const string s) { cout s endl; },}, var);
}2.3.4 异常造成的无值
如果你赋给一个variant新值时发送了异常那么这个variant可能会进入一个非常特殊的状态失去了旧值并且没有获取新的值。
如果遇到这种情况那么
var.valueless_by_exception()会返回true。var.index()会返回variant_npos。
2.4 使用variant实现多态的异质集合
2.4.1 实现几何对象
class Circle
{
private:Coord center;int rad;
public:Circle(Coord c, int r) : center{ c }, rad{ r } {}void move(const Coord c){center c;}void draw() const{cout circle at center with radius rad endl;}
};
class Rectangle {// ...
};
class Line {// ...
};
class Coord {// ...
};using GeoObj variantLine, Circle, Rectangle;// 创建并初始化一个几何体对象的集合
vectorGeoObj createFigure()
{vectorGeoObj f;f.push_back(Line{ Coord{1,2},Coord{3,4} });f.push_back(Circle{ Coord{5,5},2 });f.push_back(Rectangle{ Coord{3,3},Coord{6,4} });return f;
}int main()
{vectorGeoObj figure createFigure();for (const GeoObj geoobj : figure){visit([](const auto obj){obj.draw(); // 多态性调用draw}, geoobj);}
}如果这些实例有一个不能编译那么对visit的调用也不能编译。从效率上来讲这种行为和虚函数表的行为相同。但是draw不是虚函数。
2.4.2 使用variant实现其他异质集合
int main()
{using Var variantint, double, string;vectorVar values{ 42,0.19,hello world,0.815 };for (const Var val : values){visit([](const auto v){if constexpr (is_same_vdecltype(v), const string)cout \ v \ ;elsecout v ;}, val);}
}2.4.3 比较多态的variant
总结一下使用variant实现的异构集合的优点和缺点
优点
可以使用任意类型并且这些类型不需要公共的基类。你不需要使用指针来完成异质集合。不需要virtual成员函数。值语义不会发生内存泄漏问题。vector是连续存放的。
缺点
必须在编译器指定所有可能的类型。每个元素的大小都是所有类型中最大的。拷贝元素的开销可能很大。
2.5 variant的特殊情况
2.5.1 同时有bool和string选项
如果一个variant有bool和string选项赋予一个字符串字面量可能会导致令人惊奇的事因为字符串字面量会优先转换为bool而不是string。例如
variantbool,string v;
v hi;
cout index: v.index() endl; // 0可以使用以下的方式解决
v.emplace1(hello);
v.emplacestring(hello);3. any
一般来说C是一门类型绑定和类型安全的语言。值对象被声明为确定的类型这个类型定义了所有可能的操作、也定义了对象的行为。而且对象不能改变自身的类型。
std::any是一种在保证类型安全的基础上还能改变自身类型的值类型。也就是说它可以持有任意类型的值并且知道自己当前的值是什么类型的。
实现的关键在于std::any对象内包含了值和值的类型。
对于any对象如果你赋值一个字符串它将会分配内存并拷贝字符串并且存储记录当前的值为一个字符串。之后可以使用运行时检查来判断当前的值类型。为了将当前的值转换为真实的类型必须使用any_cast。
3.1 使用any
int main()
{any a;any b 4.3;a 42;b string{ hi };if (a.type() typeid(string)){string s any_caststring(a);useString(s);}else if (a.type() typeid(int)){useInt(any_castint(a));}
}通过使用成员type()可以检查内含值的类型与某一个类型的ID是否相同。如果对象是空的那么type() typeid(void)为了访问内部的值必须使用any_cast转换为真正的类型如果转换失败会抛出bad_any_cast异常。
除此之外还有reset()清空对象has_value()判断是否有值。
4. byte
C17引入了一个类型来代表内存的最小单位字节。byte本质上代表一个字节的值不能进行数字或字符的操作这样会更加类型安全。
4.1 使用byte
#include cstddefint main()
{byte b1{ 0x3F };byte b2{ 0b11110000 };byte b3[4]{ b1,b2,byte{1} };if (b1 b3[0])b1 1;cout to_integerint(b1) endl; // 126;
}4.2 byte的类型和操作
4.2.1 byte的类型
enum class byte : unsigned char {};// 支持位运算
template class IntType, enable_if_tis_integral_vIntType, int 0
[[nodiscard]] constexpr byte operator(const byte b, const IntType shift) noexcept {// every static_cast is intentionalreturn static_castbyte(static_castunsigned char(static_castunsigned int(b) shift));
}template class IntType, enable_if_tis_integral_vIntType, int 0
[[nodiscard]] constexpr byte operator(const byte b, const IntType shift) noexcept {// every static_cast is intentionalreturn static_castbyte(static_castunsigned char(static_castunsigned int(b) shift));
}[[nodiscard]] constexpr byte operator|(const byte _Left, const byte _Right) noexcept {// every static_cast is intentionalreturn static_castbyte(static_castunsigned char(static_castunsigned int(_Left) | static_castunsigned int(_Right)));
}[[nodiscard]] constexpr byte operator(const byte _Left, const byte _Right) noexcept {// every static_cast is intentionalreturn static_castbyte(static_castunsigned char(static_castunsigned int(_Left) static_castunsigned int(_Right)));
}[[nodiscard]] constexpr byte operator^(const byte _Left, const byte _Right) noexcept {// every static_cast is intentionalreturn static_castbyte(static_castunsigned char(static_castunsigned int(_Left) ^ static_castunsigned int(_Right)));
}[[nodiscard]] constexpr byte operator~(const byte b) noexcept {// every static_cast is intentionalreturn static_castbyte(static_castunsigned char(~static_castunsigned int(b)));
}template class IntType, enable_if_tis_integral_vIntType, int 0
constexpr byte operator(byte b, const IntType shift) noexcept {return b b shift;
}template class IntType, enable_if_tis_integral_vIntType, int 0
constexpr byte operator(byte b, const IntType shift) noexcept {return b b shift;
}constexpr byte operator|(byte _Left, const byte _Right) noexcept {return _Left _Left | _Right;
}constexpr byte operator(byte _Left, const byte _Right) noexcept {return _Left _Left _Right;
}constexpr byte operator^(byte _Left, const byte _Right) noexcept {return _Left _Left ^ _Right;
}template class IntType, enable_if_tis_integral_vIntType, int 0
[[nodiscard]] constexpr IntType to_integer(const byte b) noexcept {return static_castIntType(b);
}4.2.2 byte的操作
操作效果位运算(,,|,,^)字节的位运算比较操作字节的比较to_integer()可以把字节转换为基本类型sizeof()1
5. 字符串视图
C17中C标准库引入了一个特殊的字符串类string_view它能够让我们像处理字符串一样处理字符串序列不需要为它们分配空间。也就是说string_view类型的对象只是引用一个外部的字符串序列不需要持有它们。
5.1 和string的不同之处
和string相比string_view对象有如下特点
底层的字符序列是只读的。没有操作可以修改底层的字符。你只能赋予一个新的值、交换值、把视图缩小字符序列的子序列。字符序列不保证有空字符终止。因此字符串视图并不是一个空字符终止的字节流。data()返回的可能是nullptr。没有分配器支持。
5.2 使用字符串视图
字符串视图有两个主要的应用
你可能已经分配或者映射了字符序列或者字符串的数据并且想在不分配更多内存的情况下使用这些数据。典型的例子是内存映射文件或者处理长文本的子串。你可能想提升接收字符串为参数并以只读方式使用他们的函数/操作的性能且这些函数不需要结尾有空字符。
5.3 使用字符串视图作为参数
#include string_viewtemplatetypename T
void printElems(const T coll, string_view prefix {})
{for (const auto elem : coll){if (prefix.data())cout prefix ;cout elem endl;}
}string_view和string比起来可能会减少一次分配堆内存的调用。
5.3.1 字符串视图有害的一面
通常智能指针会比相应的语言特性更为安全。因此你可能会认为字符串视图比字符串引用更为安全然而事实上字符串视图和原生字符指针一样危险。 不要把临时字符串赋值给字符串视图。 string_view retString();string_view sv retString(); // 不延长返回值的生命周期返回值类型是字符串视图时不要返回字符串。 class Person
{string name;
public:string_view getName() const { return name;}
};函数模板应该使用auto作为返回值类型。 // 为字符串视图定义返回string
string operator(string_view sv1,string_view sv2)
{return string(sv1) string(sv2);
}// 泛型连接函数
templatetypename T
auto operator(const T sv1,const T sv2)
{return x y;
}string_view hi hi;
auto xy concat(hi,hi); 不要在调用链中使用字符串视图来初始化字符串。 class Person
{string name;
public:Person(string_view n) // 不要这样做:name{n}{}
};string s Joe;
Person p {move(s)}; // 性能开销总结
不要在那些会把参数传递给string的API使用string_view。不要用string_view形参来初始化string成员。不要把string设为string view调用链终点。不要返回string_view。函数模板永远不应该返回泛型参数的类型T。永远不要用返回值来初始化string_view。
5.4 字符串视图的类型和操作
5.4.1 字符串视图的具体类型
在头文件string_view中C为basic_string_view提供了很多特化版本
using string_view basic_string_viewchar;using u16string_view basic_string_viewchar16_t;
using u32string_view basic_string_viewchar32_t;
using wstring_view basic_string_viewwchar_t;5.4.2 字符串视图的操作
操作效果swap()交换两个字符串视图的值empty()判断字符串视图是否为空size()/length()返回字符的数量max_size()返回可能最大字符数front()/back()返回第一个字符或者最后一个字符copy()把内容拷贝或写入到字符数组data()返回nullptr或常量字符数组(没有空字符结尾)begin()/end()返回起始位置和末尾位置的下一位的迭代器substr()返回子字符串remove_prefix()移除开头的若干字符remove_suffix()移除末尾的若干字符
案例
int main()
{string_view sv;auto p sv.data(); // nullptrsv hello;cout sv endl;cout sv.size() endl; // 5using namespace literals;auto s hellosv; // string_views.swap(sv);string_view sv2 I like my kindergarten;sv2.remove_prefix(2);sv2.remove_suffix(8);cout sv2 endl; // like my kind
}5.5 在API中使用字符串视图
字符串视图开销很小并且每一个string都可以用作字符串视图。但是只有当函数按照如下约束使用参数时字符串视图才有意义
它并不需要结尾有空字符。给一个以单个const char*为参数而没有长度参数的C函数就不属于这种情况。它不会违反传入参数的生命周期。通常意味着接受函数之后在传入值的生命周期结束前使用它。调用者不应该更改底层字符的所有权。它可以处理参数为nullptr的情况。
5.5.1 使用字符串视图代替string
// 带前缀输出时间点
void print(const string prefix, const chrono::system_clock::time_point tp)
{// 转换为日历时间auto rawtime{ chrono::system_clock::to_time_t(tp) };string ts{ std::ctime(rawtime) };ts.resize(ts.size() - 1); // 跳过末尾的换行符cout prefix ts;
}// 替换成下面代码
void print(string_view prefix, const chrono::system_clock::time_point tp)
{// 转换为日历时间auto rawtime{ chrono::system_clock::to_time_t(tp) };string_view ts{ std::ctime(rawtime) };ts.remove_suffix(1); // 跳过末尾的换行符cout prefix ts;
}最先想到的就是吧只读字符串引用换成字符串视图只要我们不使用会因为没有值或者没有空字符终止而失败的操作就可以了。
同时我们也对内部ctime()的返回值使用了字符串视图。这个值只有在下一次ctime()或者asctime()调用之前有效。多线程环境下这个函数将导致问题。
如果要将结果返回
string print(string_view prefix, const chrono::system_clock::time_point tp)
{// 转换为日历时间auto rawtime{ chrono::system_clock::to_time_t(tp) };string_view ts{ std::ctime(rawtime) };ts.remove_suffix(1); // 跳过末尾的换行符return string{ prefix } string{ ts }; // 字符串视图没有重载
}6. 文件系统库
C17Boost.Filesytem终于被C标准所采纳还进行了很多调整和改进。
6.1 基本的示例
6.1.1 打印文件系统路径类的属性
#include iostream
#include filesystem
#include cstdlib
using namespace std;int main(int argc,char *argv[])
{if (argc 2){cout Usage: argv[0] path \n;return EXIT_FAILURE;}filesystem::path p{ argv[1] }; // p代表的是一个文件系统路径if (filesystem::is_regular_file(p)) // 判断p是否是一个普通路径{cout p exists with file_size(p) bytes endl;}else if (filesystem::is_directory(p)) // p是一个目录吗{cout p is a directory containing:\n;for (const auto e : filesystem::directory_iterator{ p }) // 遍历该目录下的所有文件cout e.path() endl;}else if (filesystem::exists(p)) // 路径p是否存在{cout p is a special file\n;}else{cout path: p does not exist\n;}
}在windows下处理路径
默认情况下输出路径时用双引号括起来并用反斜杠转义反斜杠会导致一个问题。
# 输入
checkpath C:\# 输出
C:\\ is a directory containing:
...
C:\\Users因此可以使用成员函数string()。
int main(int argc, char* argv[])
{if (argc 2){cout Usage: argv[0] path \n;return EXIT_FAILURE;}filesystem::path p{ argv[1] }; // p代表的是一个文件系统路径if (is_regular_file(p)) // 判断p是否是一个普通路径{cout p.string() exists with file_size(p) bytes endl;}else if (is_directory(p)) // p是一个目录吗{cout p.string() is a directory containing:\n;for (const auto e : filesystem::directory_iterator{ p }) // 遍历该目录下的所有文件cout e.path().string() endl;}else if (exists(p)) // 路径p是否存在{cout p.string() is a special file\n;}else{cout path: p.string() does not exist\n;}
}6.1.2 用switch来处理不同的文件系统类型
int main(int argc, char* argv[])
{if (argc 2){cout Usage: argv[0] path \n;return EXIT_FAILURE;}namespace fs filesystem;switch (fs::path p{ argv[1] };status(p).type()){case fs::file_type::not_found:cout path \ p.string() \ does not exist\n;break;case fs::file_type::regular:cout path \ p.string() \ exists with file_size(p) \n;break;case fs::file_type::directory:cout \ p.string() \ is a directory containing:\n;for (const auto e : filesystem::directory_iterator{ p }) // 遍历该目录下的所有文件cout e.path().string() endl;break;default:cout \ p.string() \ is a special file\n;break;}
}status().type()返回一个file_type是一个枚举类包含以下枚举值
enum class file_type
{none,not_found,regular,directory,symlink,block, // not used on Windowscharacter, // not used in this implementation; theoretically some special files like CON// might qualify, but querying for this is extremely expensive and unlikely// to be useful in practicefifo, // not used on Windows (\\.\pipe named pipes dont behave exactly like POSIX fifos)socket, // not used on Windowsunknown,junction // implementation-defined value indicating an NT junction
};6.1.3 创建不同类型的文件
#include fstreamint main()
{namespace fs filesystem;try{// 创建目录tmp/testfs::path testDir{ tmp/test };fs::create_directories(testDir); // 可以递归创建整个目录下缺少的文件或者目录 如果已经存在会抛出异常// 创建数据文件/tmp/test/data.txtauto testFile testDir / data.txt; // 重载了/ofstream dataFile{ testFile };if (!dataFile){cerr OOPS,cant open \ testFile.string() \\n;exit(EXIT_FAILURE);}dataFile The answer is 42\n;// 创建符号连接tmp/slink/指向tmp/test/:// 第一个参数是将创建的符号连接所在的目录为起点的相对路径// 第二个参数是指向的符号链接的路径fs::create_directory_symlink(test, testDir.parent_path() / slink);}catch (fs::filesystem_error e){cerr EXCEPTION: e.what() endl;cerr path1:\ e.path1().string() endl;}// 递归列出所有文件cout fs::current_path().string() :\n;auto iterOpts{ fs::directory_options::follow_directory_symlink }; // 遍历符号链接的选项for (const auto e : fs::recursive_directory_iterator(., iterOpts)){cout e.path().lexically_normal().string() endl; // lexically_normal()可以去掉绝对路径的./}
}6.2 原则和术语
6.2.1 通用的可移植的分隔符
C标准库不仅标准化了所有的操作系统的文件系统中的公共部分在很多情况下C标准还尽可能的遵循POSIX的标准的要求来实现。比如
特殊的字符不能被用作文件名。创建了文件系统不支持的元素。
不同的文件系统的差异也有个纳入考虑
大小写敏感。绝对路径和相对路径。
6.2.2 命名空间
通常文件系统是标准命名空间下的filesystem子命名空间。可以使用namespace fs std::filesystem作为缩写。
6.2.3 文件系统路径
文件系统库的一个关键元素是path。它代表文件系统中某一个文件的位置。它由可选的根名称、可选的根目录和一些以目录分隔符分隔的文件名组成。路径可以是相对的也可以是绝对的。
路径可能有不同的格式
通用格式可以移植。本地格式根据底层文件系统特定的。
一些特殊的文件名
.代表当前路径。..代表父目录。
6.2.4 正规化
路径可以正规化在正规化的路径中
文件名由单个推荐的目录分隔符分隔。除非整个路径就是.否则路径不会使用.。路径中除了开头部分以外的地方不会包含..。除非整个路径就是.或者..否则路径结尾的文件名是目录时要加上目录分隔符。
6.2.5 成员函数vs独立函数
文件系统提供了一些函数有些是成员函数有些是独立函数这么做的目的是 成员函数开销较小。不需要进行系统调用例如 mypath.is_absolute() ; // 检查路径是否是绝对的独立函数开销较大。因为会访问实际的文件系统意味着要进行系统调用。 equivalent(path1,path2); // 如果两个路径指向同一个文件返回true6.2.6 错误处理
文件系统是错误的根源。你必须考虑相应的文件是否存在、文件操作是否被允许、该操作是否会违背资源限制。另外当程序运行时其他进程可能创建、修改、或者移除了某些文件意味着事先检查并不能保证没有错误。
文件系统使用了混合的异常处理方式
默认情况下文件系统错误会作为异常处理。传递额外的输出参数时可能会得到一个错误码或者错误信息而不是异常。
filesystem_error异常
try
{if(!create_directory(p))cout p already exists\n;
}
catch(fs:filesystem_error e)
{cerr EXCEPTION: e.what() endl;cerr path1:\ e.path1().string() endl; // 获取错误相关的第一个路径
}error_code参数
error_code ec; // C11引入
create_directory(p,ec); // 发送错误设置错误码
if(ec)
{cout ec.message() endl;
}// 检查特定的错误码
if(ec errc::read_only_file_system) // 文件只能只读
{// ...
}6.2.7 文件类型
不同的操作系统支持不同的文件类型。它定义了一个枚举类型file_type标准定义了如下的值。
值含义regular普通文件directory目录文件symlink符号链接文件character字符特殊文件block块特殊文件fifoFIFO或管道文件socket套接字文件none文件类型未知unknown文件存在但推断不出类型not_found文件不存在
6.3 路径操作
有很多处理文件系统的操作。这些操作涉及到的关键类型都是std::filesystem::path它表示一个可能存在也可能不存在的文件的绝对或相对路径。
6.3.1 创建路径
调用效果path{charseq}用一个字符序列初始化路径path{beg,end}用一个范围初始化路径u8path(u8string)用一个UTF-8字符串初始化路径current_path()返回当前工作目录的路径temp_directory_path()返回临时文件的路径
6.3.2 检查路径
路径p可以调用的函数。这些操作不会访问底层的系统调用。
调用效果empty()返回路径是否为空is_absolute()返回路径是否是绝对的is_relative()返回路径是否是相对的has_filename()/has_stem()返回路径是否既不是目录也不是根名称has_extension()返回路径是否有扩展名has_root_name()返回路径是否包含根名称has_root_directory()返回路径是否包含根目录has_root_path()返回路径是否包含根名称或者根目录has_parent_path()返回路径是否包含父路径has_relative_path()返回路径是否不止包含根元素filename()返回文件名(或者空路径)stem()返回没有扩展名的文件名(或者空路径)extension()返回拓展名(或者空路径)root_name()返回根名称(或者空路径)root_directory()返回根目录(或者空路径)root_path()返回根元素(或者空路径)parent_path()返回父路径(或者空路径)relative_path()返回不带根元素的路径(或者空路径)begin()返回路径元素的起点end()返回路径元素的终点
6.3.3 遍历路径
你可以遍历一个路径这将会返回路径的所有元素根名称、根目录、所有的文件名。
路径迭代器是双向迭代器。迭代器的值的类型是path。
打印路径
void printPath(const filesystem::path p)
{cout path elements of\ p.string() \:\n;for (filesystem::path elem : p){cout \ elem.string() \;}cout endl;
}6.3.4 路径IO和转换
调用效果strm p用双引号括起来输出路径strm p读取用双引号括起来的路径string()以字符串返回路径wstring()以宽字符串返回路径u8string()以UTF-8字符串返回路径u16string()以UTF-16字符串返回路径u32string()以UTF-32字符串返回路径lexically_normal()返回正规化的路径lexically_relative(p2)返回从p2到p的相对路径如果没有则返回空路径lexically_proximate(p2)返回从p2到p的路径如果没有返回p
代码案例
int main()
{namespace fs std::filesystem;fs::path p{ /dir/./sub//sub1/../sub2 }; cout path: p endl; // /dir/./sub//sub1/../sub2cout string(): p.string() endl; // /dir/./sub//sub1/../sub2cout lexically_normal(): p.lexically_normal() endl; // \\dir\\sub\\sub2 根据系统决定// 计算相对路径fs::path{ /a/d }.lexically_relative(/a/b/c); // ../../dfs::path{ /a/b/c }.lexically_relative(/a/d); // ../b/c// windowsfs::path{ C:/a/b }.lexically_relative(c:/c/d); // fs::path{ C:/a/b }.lexically_relative(D:/c/d); // fs::path{ C:/a/b }.lexically_proximate(D:/c/d); // C:/a/b
}6.3.5 本地和通用格式的转换
通用路径格式和实际平台特定实现的格式之间转换的方法。
调用效果generic_string()返回string类型的通用路径generic_wstring()返回wstring类型的通用路径generic_u8string()返回u8string类型的通用路径generic_u16string()返回u16string类型的通用路径generic_u32string()返回u32string类型的通用路径native()返回path::string_type类型的本地路径格式c_str()返回本地字符串格式的字符序列形式的路径make_preferred()把p中的目录分隔符替换为本地格式的分隔符并返回修改后的p
这些函数在POSIX系统上没有效果因为这些系统的本地格式和通用格式没有区别。
6.3.6 修改路径
调用效果p p2赋予一个新路径p sv赋予一个字符串视图作为新路径p.assign(p2/sv)赋予一个新路径或者字符串视图作为新路径p.assign(beg,end)赋予从beg到end元素组成的路径p1/p2把p2作为子路径附加在p1之后的结果p.append(sub)相当于p1/subp.append(beg,end)把beg到end之间的元素作为子路径附加在p后面p str把str里的字符添加到路径p之后p.concat(str)等同于p strp.concat(beg,end)等同于p.append(beg,end)remove_filename()移除路径末尾的文件名replace_filename(repl)替换路径末尾的文件名remove_extension()移除路径末尾的文件的扩展名replace_extension(repl)替换路径末尾的文件的扩展名clear()清理路径swap(p2)交换两个路径make_preferred()把p中的目录分隔符替换为本地格式的分隔符并返回修改后的p
6.3.7 比较路径
调用效果compare(p2)返回是小于、等于还是大于p2p.compare(sv)返回是小于、等于还是大于字符串视图sv转换后的路径equivalent(p1,p2)访问实际文件系统的开销较大的比较操作
6.4 文件系统操作
这一节介绍开销更大的会访问实际文件系统的操作。
6.4.1 文件属性
调用效果exists§返回是否存在一个可访问到的文件is_symlink§返回是否文件p存在并且是符号链接is_regular_file§文件p存在并且是普通文件is_directory§文件p存在并且是目录is_other§文件p存在并且不是普通文件或目录或符号链接is_block_file§文件p存在并且是块特殊文件is_character_file§文件p存在并且是字符特殊文件is_fifo§文件p存在并且是FIFO或管道文件is_socket§文件p存在并且是套接字is_empty§文件是否为空file_size§返回文件大小hard_link_count§返回硬链接数量last_write_time§返回最后一次修改文件的时间
6.4.2 文件状态
有一个特殊的类型file_status可以被用来存储并修改被缓存的文件类型和权限。
调用效果status§返回文件p的file_status(解析符号链接并返回指向的文件的属性)symlink_status§返回文件p的file_status(返回符号链接自身的状态)
对file_status的操作
调用效果exists(fs)文件存在s_regular_file(fs)文件存在并且是普通文件is_directory(fs)文件存在并且是目录is_other(fs)文件存在并且不是普通文件或目录或符号链接is_block_file(fs)文件存在并且是块特殊文件is_character_file(fs)文件存在并且是字符特殊文件is_fifo(fs)文件存在并且是FIFO或管道文件is_socket(fs)文件存在并且是套接字fs.type()返回文件的file_typefs.permissions()返回文件的权限
6.4.3 权限
有一个枚举类perms表示权限可以通过fs.permissions进行返回定义如下
enum class perms
{none 0, // 没有权限集owner_read 0400, // 所属用户只读权限 owner_write 0200, // 所属用户只写权限 owner_exec 0100, // 所属用户执行权限 owner_all 0700, // 所属用户所有权限 group_read 0040, // 所属用户组只读权限 group_write 0020, // 所属用户组只写权限 group_exec 0010, // 所属用户组执行权限 group_all 0070, // 所属用户组所有权限 others_read 0004, // 所属其他人只读权限 others_write 0002, // 所属其他人只写权限 others_exec 0001, // 所属其他人执行权限 others_all 0007, // 所属其他人所有权限 all 0777, // 所有用户所有权限set_uid 04000, // 运行时设置用户IDset_gid 02000, // 运行时设置组IDsticky_bit 01000, // 操作系统特定mask 07777, // 所有可能位的掩码unknown 0xFFFF, // 未知权限_All_write owner_write | group_write | others_write, // 所有权限只写_File_attribute_readonly all ~_All_write
};6.4.4 修改文件系统
调用效果create_directory§创建目录create_directory(p,attrPath)创建属性为attrpPath的目录create_directories§创建目录和该路径下所有不存在的目录create_hard_link(to,new)为已经存在文件to创建一个硬链接create_symlink(to,new)创建指向to的符号链接newcreate_directory_symlink(to,new)创建指向目录to的符号链接newcopy(from,to)拷贝任意类型的文件copy(from,to,options)以选项options拷贝文件copy_file(from,to)拷贝文件不能是目录和符号链接copy_file(from,to,options)以选项options拷贝文件不能是目录和符号链接copy_symlink(from,to)拷贝符号链接to也指向from指向的文件remove§删除一个文件或者空目录remove_all§递归删除路径p下面的所有子文件
options会影响copy操作
copy_options效果none默认值skip_existing跳过覆盖已有文件overwrite_existing覆盖已有文件update_existing如果新文件更新的话覆盖旧文件recursive递归拷贝子目录和内容copy_symlinks拷贝符号链接为符号链接skip_symlinks忽略符号链接directories_only只拷贝目录create_hard_links创建新的硬链接而不是拷贝文件create_symlinks创建符号链接而不是拷贝文件
修改已经存在的文件
调用效果rename(old,new)重命名或移动文件last_write_time(p,newtime)修改最后修改时间permissions(p,prms)更换文件权限permissions(p,prms,mode)根据mode修改文件权限resize_file(p,newSize)修改普通文件的大小
6.4.5 符号链接和依赖文件系统的路径转换
当你想处理符号链接时这些操作尤其重要。使用纯路径转换开销更小但不会访问实际的文件系统。
调用效果read_symlink(symlink)返回符号链接指向的文件absolute§返回p的绝对路径不解析符号链接canonical§返回已存在p的绝对路径解析符号链接weakly_canonical§返回p的绝对路径解析符号链接relative§返回从当前目录到p的相对路径relative(p,base)返回从base到p的相对路径proximate§返回从当前目录到p的相对或绝对路径proximate(p,base)返回从base到p的相对或绝对路径
以上调用路径必须正规化。
6.4.6 其他文件系统操作
调用效果equivalent(p1,p2)返回p1和p2是否指向同一个文件space§返回路径p的磁盘信息current_path§将当前工作目录设置为p
6.5 遍历目录
文件系统库的一个关键作用就是遍历一个文件系统树的所有文件。
最快捷的方式是使用范围for循环。
for(const auto e : filesystem::directory_iterator(dir))cout e.path() endl;目录迭代器
directory_iteratorrecursive_directory_iterator
目录迭代器选项
directory_options效果none默认情况follow_directory_symlink解析符号链接而不是跳过skip_permission_denied当权限不足跳过目录
6.5.1 目录项
目录迭代器的元素类型是std::filesystem::directory_entry。因此如果目录迭代器有效的话使用operator*()会返回这个类型。下面是目录项的有关调用
调用效果path()返回当前目录项的文件系统路径exists()返回文件是否存在is_regular_file()返回文件是否是普通文件is_directory()返回文件是否是目录is_symlink()返回文件是否是符号链接is_other()返回文件是否不是普通文件或者目录或者符号链接is_block_file()返回文件是否是块特殊文件is_character_file()返回文件是否是字符特殊文件is_fifo()返回文件是否是FIFO或者管道文件is_socket()返回文件是否是套接字file_size()返回文件的大小hard_link_count()返回硬链接的数量last_write_time()返回最后一次修改时间status()返回文件p的状态symlink_status()返回文件p的状态(解析硬链接)replace_filename§替换文件名并更新目录项的所有属性refresh()更新该目录项所有缓存的属性assign()替换对应路径并更新目录项的所有属性
目录项缓存
鼓励使用缓存额外的文件属性来避免使用目录项时额外的文件系统访问开销。
因为所有的值都会被缓存因此这些调用开销很小
for(const auto e : filesystem::directory_iterator(.))cout e.last_write_time() endl;在多用户或者多进程的操作系统中所有这些迭代都可能返回不再有效的数据。文件的大小和内容可能改变、文件可能被删除或者替换、权限也可能发生改变。
for(const auto e : filesystem::directory_iterator(.))
{// ...e.refresh(); // 刷新文件缓存内容if(e.exists()) // 判断文件是否存在cout e.last_write_time() endl;
}
文章转载自: http://www.morning.znkls.cn.gov.cn.znkls.cn http://www.morning.jfbpf.cn.gov.cn.jfbpf.cn http://www.morning.krywy.cn.gov.cn.krywy.cn http://www.morning.wbrf.cn.gov.cn.wbrf.cn http://www.morning.gwjqq.cn.gov.cn.gwjqq.cn http://www.morning.bgpb.cn.gov.cn.bgpb.cn http://www.morning.zbhfs.cn.gov.cn.zbhfs.cn http://www.morning.zlnyk.cn.gov.cn.zlnyk.cn http://www.morning.kpcjl.cn.gov.cn.kpcjl.cn http://www.morning.qpmmg.cn.gov.cn.qpmmg.cn http://www.morning.wkmyt.cn.gov.cn.wkmyt.cn http://www.morning.yrsg.cn.gov.cn.yrsg.cn http://www.morning.zfxrx.cn.gov.cn.zfxrx.cn http://www.morning.xgbq.cn.gov.cn.xgbq.cn http://www.morning.hdzty.cn.gov.cn.hdzty.cn http://www.morning.xpzrx.cn.gov.cn.xpzrx.cn http://www.morning.dskmq.cn.gov.cn.dskmq.cn http://www.morning.bwrbm.cn.gov.cn.bwrbm.cn http://www.morning.yzzfl.cn.gov.cn.yzzfl.cn http://www.morning.xrtsx.cn.gov.cn.xrtsx.cn http://www.morning.qkdbz.cn.gov.cn.qkdbz.cn http://www.morning.ydhck.cn.gov.cn.ydhck.cn http://www.morning.dpfr.cn.gov.cn.dpfr.cn http://www.morning.mdnnz.cn.gov.cn.mdnnz.cn http://www.morning.kybjr.cn.gov.cn.kybjr.cn http://www.morning.cnqwn.cn.gov.cn.cnqwn.cn http://www.morning.ssjtr.cn.gov.cn.ssjtr.cn http://www.morning.fsbns.cn.gov.cn.fsbns.cn http://www.morning.trfrl.cn.gov.cn.trfrl.cn http://www.morning.qnqt.cn.gov.cn.qnqt.cn http://www.morning.zqkr.cn.gov.cn.zqkr.cn http://www.morning.bnbtp.cn.gov.cn.bnbtp.cn http://www.morning.rnqyy.cn.gov.cn.rnqyy.cn http://www.morning.rfxg.cn.gov.cn.rfxg.cn http://www.morning.gtjkh.cn.gov.cn.gtjkh.cn http://www.morning.zqmdn.cn.gov.cn.zqmdn.cn http://www.morning.jcpq.cn.gov.cn.jcpq.cn http://www.morning.fznj.cn.gov.cn.fznj.cn http://www.morning.rwbx.cn.gov.cn.rwbx.cn http://www.morning.splcc.cn.gov.cn.splcc.cn http://www.morning.pabxcp.com.gov.cn.pabxcp.com http://www.morning.pmjw.cn.gov.cn.pmjw.cn http://www.morning.dnqlba.cn.gov.cn.dnqlba.cn http://www.morning.qlkzl.cn.gov.cn.qlkzl.cn http://www.morning.btmwd.cn.gov.cn.btmwd.cn http://www.morning.wjdgx.cn.gov.cn.wjdgx.cn http://www.morning.pxtgf.cn.gov.cn.pxtgf.cn http://www.morning.etsaf.com.gov.cn.etsaf.com http://www.morning.dpjtn.cn.gov.cn.dpjtn.cn http://www.morning.cwwbm.cn.gov.cn.cwwbm.cn http://www.morning.ywpwg.cn.gov.cn.ywpwg.cn http://www.morning.thlr.cn.gov.cn.thlr.cn http://www.morning.ghxkm.cn.gov.cn.ghxkm.cn http://www.morning.sgmis.com.gov.cn.sgmis.com http://www.morning.xhfky.cn.gov.cn.xhfky.cn http://www.morning.rwfp.cn.gov.cn.rwfp.cn http://www.morning.kszkm.cn.gov.cn.kszkm.cn http://www.morning.mxhcf.cn.gov.cn.mxhcf.cn http://www.morning.mbmtz.cn.gov.cn.mbmtz.cn http://www.morning.ggnjq.cn.gov.cn.ggnjq.cn http://www.morning.rhqr.cn.gov.cn.rhqr.cn http://www.morning.mgmyt.cn.gov.cn.mgmyt.cn http://www.morning.rdlfk.cn.gov.cn.rdlfk.cn http://www.morning.znrlg.cn.gov.cn.znrlg.cn http://www.morning.dsxgc.cn.gov.cn.dsxgc.cn http://www.morning.ybgyz.cn.gov.cn.ybgyz.cn http://www.morning.kkwbw.cn.gov.cn.kkwbw.cn http://www.morning.dppfh.cn.gov.cn.dppfh.cn http://www.morning.zbqry.cn.gov.cn.zbqry.cn http://www.morning.ldynr.cn.gov.cn.ldynr.cn http://www.morning.rdymd.cn.gov.cn.rdymd.cn http://www.morning.hjrjr.cn.gov.cn.hjrjr.cn http://www.morning.sh-wj.com.cn.gov.cn.sh-wj.com.cn http://www.morning.yfcyh.cn.gov.cn.yfcyh.cn http://www.morning.bwqr.cn.gov.cn.bwqr.cn http://www.morning.thpzn.cn.gov.cn.thpzn.cn http://www.morning.smdnl.cn.gov.cn.smdnl.cn http://www.morning.jmmzt.cn.gov.cn.jmmzt.cn http://www.morning.brkc.cn.gov.cn.brkc.cn http://www.morning.mrfnj.cn.gov.cn.mrfnj.cn