C++17新特性(三)新的标准库组件
1. optional
在编程时,我们经常会遇到可能会返回/传递/使用一个确定类型对象的场景。也就是说,这个对象可能有一个确定类型的值也可能没有任何值。因此,我们需要一种方法来模拟类似指针的语义:通过nullptr表示指针为空。解决方法就是定义该对象的同时再定义一个附加的bool类型来标志该对象有没有值。std::optional就是提供了一种类型安全的方式来实现。
1.1 使用optional
1.1.1 可选的返回值
#include <optional>optional<int> aslnt(const string& s)
{try{return stoi(s);}catch(...){return nullopt;}
}int main()
{for (auto s : { "42","077","hello","0x33" }){optional<int> oi = aslnt(s);if (oi)cout << "convert " << s << " to int: " << *oi << endl;elsecout << "can't convert " << s << endl;}
}
我们实现一个函数,用来对字符串转换为整形。这个操作可能会失败,因此,在失败的时候,我们返回一个nullopt表示没有int值。并且,通过解引用*来获取optional对象的值。除了这种方式,还有has_value()和value()分别是判断是否有值和值是多少,更为安全。
int main()
{for (auto s : { "42","077","hello","0x33" }){optional<int> oi = aslnt(s);if (oi.has_value())cout << "convert " << s << " to int: " << oi.value() << endl;elsecout << "can't convert " << s << endl;}
}
1.1.2 可选的参数和数据成员
另一个例子是传递可选的参数或者设置可选的数据成员:
class Name
{
private:string first;optional<string> middle;string last;
public:Name(string f,optional<string> 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()
{optional<int> o1;optional<int> o2(nullopt);optional o3{ 42 };optional o4{ complex{3.0, 4.0} };optional < complex<double>> 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(); // o5==nulloptoptional<int> o6 = move(o5); // o5==nullopt
}
1.3 特殊情况
一些特定的可选类型可能会导致特殊或意料之外的行为。
1.3.1 bool类型或原生指针的可选对象
optional<bool> ob{false}; // false
if(!ob) // false
if(ob == false) // trueoptional<int*> op{nullptr};
if(!op) // false
if(op == nullptr) // true
1.3.2 可选对象的可选对象
理论上,你可以定义可选对象的可选对象:
int main()
{optional<optional<string>> oos1;optional<optional<string>> oos2 = "hello";optional<optional<string>> oos3{in_place,in_place,"hello"};oos1 = "hello";cout << (*oos1 == nullopt) << endl; // 内层可选对象无值cout << (oos1 == nullopt) << endl; // 外层可选对象无值
}
2. variant
variant是C++标准库提供的一个新的联合类型,它最大的优势是提供了一种新的具有多态性的处理异质集合的方法。也就是说,它可以帮助我们处理不同类型的数据,并且不需要公共基类和指针。
2.1 variant的动机
起源于C,C++也提供了union的支持,它的作用是持有一个值,这个值的类型可能是指定的若干类型中的任意一个。然而,这项语言特性有一些缺陷:
- 对象不知道它们现在持有的值的类型。
- 因此,你不能持有非平凡类型,例如:
string。 - 你不能从
union派生。
通过variant,C++标准库提供了一种可辨识的联合。
- 当前值的类型可知。
- 可以持有任何类型的值。
- 可以从它派生。
事实上,variant持有的值有若干候选项,这些选项通常有不同的类型。然而,两个不同选项的类型也有可能相同,这在多个类型相同的选项分别代表不同的含义是很有用。
variant的内存大小等于所有可能的底层类型中最大的再加上一个记录当前选项的固定内存开销。
2.2 使用variant
int main()
{variant<int, string> var{ "hi" };cout << var.index() << endl; // 1var = 42;cout << var.index() << endl; // 0try{int i = get<0>(var); // 通过索引访问string s = get<string>(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;}
};variant<A,int> v1; // ERROR
辅助类型std::monostate提供了处理这种情况的能力,还可以用来模拟空值的状态。
它的作用是可以作为variant的一个选项,表示variant没有其他任何类型的值。所以,可以保证variant能够默认构造。
variant<monostate,A,int> v2; // OK
cout << v2.index() << endl; // 0
你可以从variant派生:
class Derived : public variant<int,string>
{};Derived d = {{"hello"}};
cout << d.index() << endl; //1
cout << get<1>(d) << endl; // hello
d.emplace<0>(77); // 初始化int,销毁string
cout << get<0>(d) << endl;
2.3 variant的类型和操作
2.3.1 variant的类型
template<typename... 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()
{variant<int, int, string> v1; // 默认构造variant<long, int> v2{ 42 }; cout << v1.index() << endl; // 1// 如果有两个类型同等匹配会导致歧义// variant<long, long> v3{ 42 }; // ERROR// 为了传递多个值调用构造初始化variant<complex<double>> v4{ in_place_type<complex<double>>,3.0,4.0 };variant<complex<double>> v5{ in_place_index<0>,3.0,4.0 };// 访问值variant<int, int, string> var;auto a = get<double>(var); // ERROR// 如果访问失败,返回nullptr,如果访问成功,返回当前选项的指针if (auto ip = get_if<1>(&var); ip != nullptr)cout << *ip << endl;// 修改值var = "hello";var.emplace<1>(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()
{variant<int, 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 = variant<int, double>;vector<IntOrDouble> 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,其中最匹配的会被用作访问器。
假设有一个如下定义的重载器:
template<typename... Ts>
struct overload : Ts...
{using Ts::operator()...;
};template<typename... Ts>
overload(Ts...)->overload<Ts...>;int main()
{variant<int, 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 = variant<Line, Circle, Rectangle>;// 创建并初始化一个几何体对象的集合
vector<GeoObj> createFigure()
{vector<GeoObj> 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()
{vector<GeoObj> 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 = variant<int, double, string>;vector<Var> values{ 42,0.19,"hello world",0.815 };for (const Var& val : values){visit([](const auto& v){if constexpr (is_same_v<decltype(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。例如:
variant<bool,string> v;
v = "hi";
cout << "index: " << v.index() << endl; // 0
可以使用以下的方式解决:
v.emplace<1>("hello");
v.emplace<string>("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_cast<string>(a);useString(s);}else if (a.type() == typeid(int)){useInt(any_cast<int>(a));}
}
通过使用成员type(),可以检查内含值的类型与某一个类型的ID是否相同。如果对象是空的,那么type() == typeid(void),为了访问内部的值,必须使用any_cast转换为真正的类型,如果转换失败,会抛出bad_any_cast异常。
除此之外,还有reset()清空对象,has_value()判断是否有值。
4. byte
C++17,引入了一个类型来代表内存的最小单位:字节。byte本质上代表一个字节的值,不能进行数字或字符的操作,这样会更加类型安全。
4.1 使用byte
#include <cstddef>int main()
{byte b1{ 0x3F };byte b2{ 0b11110000 };byte b3[4]{ b1,b2,byte{1} };if (b1 == b3[0])b1 <<= 1;cout << to_integer<int>(b1) << endl; // 126;
}
4.2 byte的类型和操作
4.2.1 byte的类型
enum class byte : unsigned char {};// 支持位运算
template <class IntType, enable_if_t<is_integral_v<IntType>, int> = 0>
[[nodiscard]] constexpr byte operator<<(const byte b, const IntType shift) noexcept {// every static_cast is intentionalreturn static_cast<byte>(static_cast<unsigned char>(static_cast<unsigned int>(b) << shift));
}template <class IntType, enable_if_t<is_integral_v<IntType>, int> = 0>
[[nodiscard]] constexpr byte operator>>(const byte b, const IntType shift) noexcept {// every static_cast is intentionalreturn static_cast<byte>(static_cast<unsigned char>(static_cast<unsigned int>(b) >> shift));
}[[nodiscard]] constexpr byte operator|(const byte _Left, const byte _Right) noexcept {// every static_cast is intentionalreturn static_cast<byte>(static_cast<unsigned char>(static_cast<unsigned int>(_Left) | static_cast<unsigned int>(_Right)));
}[[nodiscard]] constexpr byte operator&(const byte _Left, const byte _Right) noexcept {// every static_cast is intentionalreturn static_cast<byte>(static_cast<unsigned char>(static_cast<unsigned int>(_Left) & static_cast<unsigned int>(_Right)));
}[[nodiscard]] constexpr byte operator^(const byte _Left, const byte _Right) noexcept {// every static_cast is intentionalreturn static_cast<byte>(static_cast<unsigned char>(static_cast<unsigned int>(_Left) ^ static_cast<unsigned int>(_Right)));
}[[nodiscard]] constexpr byte operator~(const byte b) noexcept {// every static_cast is intentionalreturn static_cast<byte>(static_cast<unsigned char>(~static_cast<unsigned int>(b)));
}template <class IntType, enable_if_t<is_integral_v<IntType>, int> = 0>
constexpr byte& operator<<=(byte& b, const IntType shift) noexcept {return b = b << shift;
}template <class IntType, enable_if_t<is_integral_v<IntType>, 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_t<is_integral_v<IntType>, int> = 0>
[[nodiscard]] constexpr IntType to_integer(const byte b) noexcept {return static_cast<IntType>(b);
}
4.2.2 byte的操作
| 操作 | 效果 |
|---|---|
| 位运算(<<,>>,|,&,^) | 字节的位运算 |
| 比较操作 | 字节的比较 |
| to_integer() | 可以把字节转换为基本类型 |
| sizeof() | 1 |
5. 字符串视图
C++17中,C++标准库引入了一个特殊的字符串类string_view,它能够让我们像处理字符串一样处理字符串序列,不需要为它们分配空间。也就是说string_view类型的对象只是引用一个外部的字符串序列,不需要持有它们。
5.1 和string的不同之处
和string相比,string_view对象有如下特点:
- 底层的字符序列是只读的。没有操作可以修改底层的字符。你只能赋予一个新的值、交换值、把视图缩小字符序列的子序列。
- 字符序列不保证有空字符终止。因此,字符串视图并不是一个空字符终止的字节流。
data()返回的可能是nullptr。- 没有分配器支持。
5.2 使用字符串视图
字符串视图有两个主要的应用:
- 你可能已经分配或者映射了字符序列或者字符串的数据,并且想在不分配更多内存的情况下使用这些数据。典型的例子是内存映射文件或者处理长文本的子串。
- 你可能想提升接收字符串为参数并以只读方式使用他们的函数/操作的性能,且这些函数不需要结尾有空字符。
5.3 使用字符串视图作为参数
#include <string_view>template<typename 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); }// 泛型连接函数 template<typename 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_view<char>;using u16string_view = basic_string_view<char16_t>;
using u32string_view = basic_string_view<char32_t>;
using wstring_view = basic_string_view<wchar_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 = "hello"sv; // 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. 文件系统库
C++17,Boost.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 don't behave exactly like POSIX fifos)socket, // not used on Windowsunknown,junction // implementation-defined value indicating an NT junction
};
6.1.3 创建不同类型的文件
#include <fstream>int 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,can't 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); // 如果两个路径指向同一个文件返回true
6.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; // C++11引入
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 | 块特殊文件 |
| fifo | FIFO或管道文件 |
| 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/../sub2"cout << "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"); // "../../d"fs::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/sub |
| p.append(beg,end) | 把beg到end之间的元素作为子路径附加在p后面 |
| p += str | 把str里的字符添加到路径p之后 |
| p.concat(str) | 等同于p += str |
| p.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) | 返回是小于、等于还是大于p2 |
| p.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_type |
| fs.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的符号链接new |
| create_directory_symlink(to,new) | 创建指向目录to的符号链接new |
| copy(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;
}
相关文章:
C++17新特性(三)新的标准库组件
1. optional 在编程时,我们经常会遇到可能会返回/传递/使用一个确定类型对象的场景。也就是说,这个对象可能有一个确定类型的值也可能没有任何值。因此,我们需要一种方法来模拟类似指针的语义:通过nullptr表示指针为空。解决方法…...
Spring Boot入门
SpringBoot介绍 什么是SpringBoot Spring Boot是由Pivotal团队提供的全新框架,其中“Boot”的意思就是“引导”,Spring Boot 并不是对 Spring 功能上的增强,而是提供了一种快速开发 Spring应用的方式。 特点 • 嵌入的 Tomcat,…...
【LeetCode】数学精选4题
目录 1. 二进制求和(简单) 2. 两数相加(中等) 3. 两数相除(中等) 4. 字符串相乘(中等) 1. 二进制求和(简单) 从字符串的右端出发向左做加法,…...
【漏洞复现】Hikvision SPON IP网络对讲广播系统命令执行漏洞(CVE-2023-6895)
文章目录 前言声明一、系统简介二、漏洞描述三、影响版本四、漏洞复现五、修复建议 前言 Hikvision Intercom Broadcasting System是中国海康威视(Hikvision)公司的一个对讲广播系统。 声明 请勿利用文章内的相关技术从事非法测试,由于传播…...
IDEA在重启springboot项目时没有自动重新build
IDEA在重启springboot项目时没有自动重新build 问题描述 当项目里面某些依赖或者插件更新了,target的class文件没有找到,导致不是我们需要的效果。 只能手动的清理target文件,麻烦得很 , 单体项目还好说,一次清理就…...
华为设备NAT的配置
实现内网外网地址转换 静态转换 AR1: sys int g0/0/0 ip add 192.168.10.254 24 int g0/0/1 ip add 22.33.44.55 24 //静态转换 nat static global 22.33.44.56 inside 192.168.10.1 动态转换 最多有两台主机同时访问外网 AR1: sys int g0/0/0 ip add…...
48-DOM节点,innerHTML,innerText,outerHTML,outerText,静态获取,单机click,cssText
1.DOM基础 Document Object Module,文档对象模型,window对象,document文档,都可以获取和操作 1)文档节点 2)属性节点(标签内的属性href,src) 3)文本节点(标签内的文字) 4)注释节点 5)元素节点(标签) 2.获取元素节点 2.1通过标签名获取getElementsByTagName() …...
多输入多输出 | Matlab实现基于LightGBM多输入多输出预测
多输入多输出 | Matlab实现基于LightGBM多输入多输出预测 目录 多输入多输出 | Matlab实现基于LightGBM多输入多输出预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现基于LightGBM多输入多输出预测(完整源码和数据) 1.data为数据集&a…...
【欢迎您的到来】这里是开源库get_local_info作者的付费专栏
您好, 我是带剑书生,开源库get_local_info的作者,欢迎您的到来,这里是我的付费专栏,在上一个付费专栏里,用简洁的语言,通俗的话语,帮助您更好的学习了Rust,现在将用本专栏…...
Java SE入门及基础(23)
目录 方法带参 1. 构造方法带参 案例场景 思考:以上代码存在什么问题? 2. 方法带参 方法带参语法 案例场景 思考:以上代码存在什么问题? Java SE文章参考:Java SE入门及基础知识合集-CSDN博客 方法带参 1. 构造方法带参 …...
蓝桥杯回文日期判断
思想:对于回文数的判断方法,最快的就是取其中一半的字符串长度,为s,然后将其进行翻转为s’ ,再把两者进行拼接即可保证是回文数,这样子就解决了枚举所有回文数的问题。 注意点: 要求必须是有效…...
Qt文件和目录相关操作
1.相关说明 QCoreApplication类、QFile类、QDir、QTemporaryDir类、QTemporaryFile类、QFileSystemWatcher类的相关函数 2.相关界面 3.相关代码 #include "dialog.h" #include "ui_dialog.h" #include <QFileDialog> #include <QTemporaryDir>…...
递归、搜索与回溯算法(专题一:递归)
往期文章(希望小伙伴们在看这篇文章之前,看一下往期文章) (1)递归、搜索与回溯算法(专题零:解释回溯算法中涉及到的名词)【回溯算法入门必看】-CSDN博客 接下来我会用几道题&#…...
element-ui 打包流程源码解析(下)
目录 目录结构和使用1,npm 安装1.1,完整引入1.2,按需引入 2,CDN3,国际化 接上文:element-ui 打包流程源码解析(上) 文章中提到的【上文】都指它 ↑ 目录结构和使用 我们从使用方式来…...
ChatGPT给出的前端面试考点(Vue.js)
ChatGPT给出的前端面试考点(Vue.js) 答案 1. Vue.js是什么?它的主要特点是什么? Vue.js是一个渐进式JavaScript框架,用于构建用户界面。它的主要特点包括: 数据绑定:Vue.js使用双向数据绑定&…...
ChatGPT 商业提示词攻略书
原文:ChatGPT Business Prompt Playbook 译者:飞龙 协议:CC BY-NC-SA 4.0 一、书系介绍 人工智能发展迅速。非常迅速。 所以我希望你做两件事: (1) 在 Twitter 上关注我:iamkylebalmer (2) 订阅我的免费电子邮件通…...
Notepad++运行C语言输出乱码
方法一:编码-编码字符集-中文-GB2312 这时原程序中文会变成乱码,我是重新输入中文 重新编译执行即可 缺陷:重开一个程序有中文还是会显示乱码,需要重新设置编码,比较麻烦 方法二:设置-首选项-新建-右侧编…...
深入解析 Java 方法引用:Lambda 表达式的进化之路
前言 方法引用是 Java 8 提供的一种新特性,它允许我们更简洁地传递现有方法作为参数。这项特性实际上是对 Lambda 表达式的一种补充,通过方法引用,我们可以直接引用现有方法,而无需编写完整的Lambda表达式。最近在使用方法引用的…...
MySQL作业 (3)多表查询
多表查询 1.创建student和score表2.为student表和score表增加记录3.查询student表的所有记录4.查询student表的第2条到4条记录5.从student表查询所有学生的学号(id)、姓名(name)和院系(department)的信息6.…...
ConcurrentHashMap和HashMap的区别
什么是HashMap (1)HashMap 是基于 Map 接口的非同步实现,线程不安全,是为了快速存取而设计的;它采用 key-value 键值对的形式存放元素(并封装成 Node 对象),允许使用 null 键和 nul…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...
AWS vs 阿里云:功能、服务与性能对比指南
在云计算领域,Amazon Web Services (AWS) 和阿里云 (Alibaba Cloud) 是全球领先的提供商,各自在功能范围、服务生态系统、性能表现和适用场景上具有独特优势。基于提供的引用[1]-[5],我将从功能、服务和性能三个方面进行结构化对比分析&#…...
