fmt的API介绍(版本: 7.0.1)
!!版权声明:本文为博主原创文章,版权归原文作者和博客园共有,谢绝任何形式的 转载!!
作者:mohist
本文翻译: https://fmt.dev/latest/api.html
水平有限,仅供参考,欢迎指正。 有兴趣的,看看原文。
fmt版本: 7.0.1
--------------------------------------------------------------------------
A、介绍
fmt库由一下模块组成:
fmt/core.h: 提供参数处理的一系列接口 和 一个轻量级的格式化函数集合。
fmt/format.h: 提供了用于编译时的格式化字符串检查、宽字符、迭代输出 和支持用户子自定义的接口。
fmt/ranges.h: 提供了针对元组和容器ranges的额外的格式化支持
fmt/chrono.h: 提供了时间和日期的格式化处理的接口
fmt/compile.h: 格式化字符串编译
fmt/ostream.h: 标准输出流的支持
fmt/printf.h: printf格式化
fmt库提供的函数和类型都在fmt的命名空间下和加上了前缀 FMT_
注意: fmt格式化都是在大括号 {}内。
B、core api
0、包含头文件:
#include <fmt/core.h>
1、fmt::foroomat返回一个字符串。
std::string message = fmt::format("The answer is {}", 42);
2、fmt::print标准输出, 与 std::cout的输出相仿
#include <fmt/core.h>
fmt::print("Elapsed time: {0:.2f} seconds", 1.23);
3、fmt::print支持写入文件,写入的字符串是用的UNICODE编码。函数原型如下:
template <typename S, typename... Args, typename Char = char_t<S>>
void fmt::print(std::FILE *f, const S &format_str, Args&&... args)
函数的第一个参数是文件指针:
fmt::print(stderr, "Don't {}!", "panic");
4、命名参数:通过指定参数名称的方式来调用参数。方便调用参数时按调用者的需要来排列顺序,而不必死守函数声明时的顺序,同时结合默认参数值的特性,可以选择使用默认参数还是不使用默认参数。 函数原型如下:
template <typename Char, typename T>
detail::named_arg<Char, T> fmt::arg(const Char *name, const T &arg)
该函数将返回一个用于函数参数的格式化字符串。使用方法:
fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23));
注意: 命名参数不能在编译的时候检查格式化字符串的错误
5、参数列表 Argument Lists
5.1)、函数原型:
template <typename Context = format_context, typename... Args>
format_arg_store<Context, Args...> fmt::make_format_args(const Args&... args)
构造一个format_arg_store对象,该对象包含对参数的引用,并且可以隐式转换为format_args。可以省略上下文,在这种情况下,它默认为上下文。
有关生命周期的考虑,请参见arg()。
5.2)、class fmt::format_arg_store类
template <typename Context, typename... Args>
class fmt::format_arg_store
引用参数的数组。可以将其隐式转换为basic_format_args,以传递给类型擦除的格式函数,例如vformat()。
fmt::format_arg_store 的公有函数:
5.2.1)、push_back函数原型
template <typename T>
void push_back(const T &arg)
该函数将参数arg自动添加到自动存储的内存中,以便传递给需要格式化参数的函数。
请注意,如有必要,将自定义类型和字符串类型(但不包括字符串view)复制到存储中,以动态分配内存, 例如:
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(42);
store.push_back("abc");
store.push_back(1.5f);
std::string result = fmt::vformat("{} and {} and {}", store);
5.2.2)、 push_back函数原型:
template <typename T>
void push_back(std::reference_wrapper<T> arg)
该函数将参数arg自动添加到自动存储的内存中,以便传递给需要格式化参数的函数。通过std :: ref()/ std :: cref()的引用,支持包装在std :: reference_wrapper中的命名参数。例如:
fmt::dynamic_format_arg_store<fmt::format_context> store;
char str[] = "1234567890";
store.push_back(std::cref(str));
int a1_val{42};
auto a1 = fmt::arg("a1_", a1_val);
store.push_back(std::cref(a1));
// Changing str affects the output but only for string and custom types.
str[0] = 'X';
std::string result = fmt::vformat("{} and {a1_}");
assert(result == "X234567890 and 42");
5.2.3)、push_back 函数原型:
template <typename T>
void push_back(const detail::named_arg<char_type, T> &arg)
将命名参数添加到动态存储中,以便以后传递给格式化函数。std::reference_wrapper可以避免参数传递时的拷贝。
5.2.4)、clear原型:
void clear()
该函数将清除存储中的所有参数。
5.2.5)、reserve函数原型:
void reserve(size_t new_cap, size_t new_cap_named)
保留存储空间以至于能容纳new_cap参数,也包括new_cap_named 的命名参数。
5.3)、class fmt::basic_format_args类
template <typename Context>
class fmt::basic_format_args
格式化参数集合的视图。 为了避免生命周期的问题,它仅应在类型擦除的函数中用作参数类型,例如vformat函数:
void vlog(string_view format_str, format_args args); // OK
format_args args = make_format_args(42); // Error: dangling reference
其公有函数:
5.3.1)、basic_format_args重载:
template <typename... Args>
basic_format_args(const format_arg_store<Context, Args...> &store)
从format_arg_store 构造一个 basic_format_args 对象。
5.3.2)、basic_format_args重载:
basic_format_args(const dynamic_format_arg_store<Context> &store)
从 dynamic_format_arg_store 构造一个 basic_format_args 对象。
5.3.3)、basic_format_args重载:
basic_format_args(const format_arg *args, int count)
根据一组动态参数构造一个basic_format_args对象。
5.4)、format_arg 的get函数原型:
format_arg get(int id) const
返回具有指定ID的参数。
5.5)、结构体 fmt::format_args
fmt::format_args是【basic_format_args<context>】的别名,继承 fmt::basic_format_args< format_context >
5.6)、类 fmt::basic_format_arg:
template <typename Context>
class fmt::basic_format_arg
6、兼容性
类fmt::basic_string_view
template <typename Char>
class fmt::basic_string_view
c++17以前, std::basic_string_view 的实现,其提供了一个子集的API
fmt::basic_string_view 可以用来格式化字符串,即使std::string_view可以有效避免当使用与客户端代码不同的-std选项编译库时问题:
公有函数:
6.1)、basic_string_view重载
constexpr basic_string_view(const Char *s, size_t count)
根据C字符串和大小,构造一个字符串引用对象。
6.2)、basic_string_view重载
basic_string_view(const Char *s)
从C字符串构造一个字符串引用对象,该字符串使用std :: char_traits <Char> :: length计算大小。
6.3)、basic_string_view重载
template <typename Traits, typename Alloc>
basic_string_view(const std::basic_string<Char, Traits, Alloc> &s)
从std :: basic_string对象构造一个字符串引用。
6.4)、data原型:
constexpr const Char *data() const
返回指向字符串数据的指针。
6.5)、size
constexpr size_t size() const
返回字符串的大小
该类提供了对宽字符的支持
using fmt::string_view = typedef basic_string_view<char>
using fmt::wstring_view = typedef basic_string_view<wchar_t>
7、本地化
默认情况下,所有格式化与语言环境无关。使用'n'格式,从本地语言环境中插入适当的数字分隔符,例如:
#include <fmt/core.h>
#include <locale>
std::locale::global(std::locale("en_US.UTF-8"));
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
C、format API
fmt/format.h定义了完整格式化的API,提供了编译时格式字符串检查,宽字符串,输出迭代器和用户定义的类型支持。
内置格式和字符串类型以及格式定义专长中具有constexpr解析函数的用户定义类型均支持编译时检查。
1、FMT_STRING(s)
从字符串常量s构造一个编译时格式字符串。例如:
// A compile-time error because 'd' is an invalid specifier for strings.
std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
2、格式化用户定义的类型
要创建用户定义的类型格式表,请专门设置formatter <T>结构模板并实现解析和格式化方法:
#include <fmt/format.h>
struct point { double x, y; };
template <>
struct fmt::formatter<point> {
// Presentation format: 'f' - fixed, 'e' - exponential.
char presentation = 'f';
// Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context& ctx) {
// auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) // c++11
// [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed,
// e.g. in
//
// fmt::format("{:f} - point of interest", point{1, 2});
//
// the range will contain "f} - point of interest". The formatter should
// parse specifiers until '}' or the end of the range. In this example
// the formatter should parse the 'f' specifier and return an iterator
// pointing to '}'.
// Parse the presentation format and store it in the formatter:
auto it = ctx.begin(), end = ctx.end();
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
// Check if reached the end of the range:
if (it != end && *it != '}')
throw format_error("invalid format");
// Return an iterator past the end of the parsed range:
return it;
}
// Formats the point p using the parsed format specification (presentation)
// stored in this formatter.
template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) {
// auto format(const point &p, FormatContext &ctx) -> decltype(ctx.out()) // c++11
// ctx.out() is an output iterator to write to.
return format_to(
ctx.out(),
presentation == 'f' ? "({:.1f}, {:.1f})" : "({:.1e}, {:.1e})",
p.x, p.y);
}
};
然后,传递指针对象给任何函数:
point p = {1, 2};
std::string s = fmt::format("{:f}", p);
// s == "(1.0, 2.0)"
还可以通过继承或组合来重用现有的格式化器,例如:
enum class color {red, green, blue};
template <> struct fmt::formatter<color>: formatter<string_view>
{
// parse is inherited from formatter<string_view>.
template <typename FormatContext>
auto format(color c, FormatContext& ctx)
{
string_view name = "unknown";
switch (c)
{
case color::red: name = "red"; break;
case color::green: name = "green"; break;
case color::blue: name = "blue"; break;
}
return formatter<string_view>::format(name, ctx);
}
};
由于解析是从formatter <string_view>继承的,因此它将识别所有字符串格式规范,例如:
fmt::format("{:>10}", color::blue)
这行代码将会返回一个字符串:" blue"。
可以为类的层次结构编写格式化器:
#include <type_traits>
#include <fmt/format.h>
struct A
{
virtual ~A() {}
virtual std::string name() const { return "A"; }
};
struct B : A
{
virtual std::string name() const { return "B"; }
};
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
fmt::formatter<std::string>
{
template <typename FormatCtx>
auto format(const A& a, FormatCtx& ctx)
{
return fmt::formatter<std::string>::format(a.name(), ctx);
}
};
int main(int argc, char * argv[])
{
B b;
A& a = b;
fmt::print("{}", a); // prints "B"
return 0;
}
3、basic_format_parse_context类
template <typename Char, typename ErrorHandler = detail::error_handler>
class fmt::basic_format_parse_context
解析上下文,由解析的格式字符串范围和用于自动索引的参数计数器组成。
可以将一下类型取别名,用于常见字符类型
Type Definition
format_parse_context basic_format_parse_context<char>
wformat_parse_context basic_format_parse_context<wchar_t>
basic_format_parse_context类类继承: fmt::detail::error_handler
公有函数:
3.1)、begin
constexpr iterator begin() const
返回一个迭代器,指向要解析的格式字符串范围的开头
3.2)、end
constexpr iterator end() const
返回一个迭代器,指向要解析的格式字符串范围的结束
3.3)、advance_to
void advance_to(iterator it)
将迭代器前进到it
3.4)、next_arg_id
int next_arg_id()
如果使用手动参数索引,则报告错误;否则返回下一个参数索引并切换到自动索引。
3.5)、check_arg_id
void check_arg_id(int)
如果使用自动参数索引,则报告错误;否则切换到手动索引。
D、支持输出迭代器
1、
template <typename OutputIt, typename S, typename... Args>
OutputIt fmt::format_to(OutputIt out, const S &format_str, Args&&... args)
格式化参数,将结果写入输出迭代器,并返回超出输出范围末尾的迭代器。 例如:
std::vector<char> out;
fmt::format_to(std::back_inserter(out), "{}", 42);
2、
template <typename OutputIt, typename S, typename... Args>
format_to_n_result<OutputIt> fmt::format_to_n(OutputIt out, size_t n, const S &format_str, const Args&... args)
格式化参数,将结果的n个字符写到输出迭代器中,并返回总输出大小和迭代器。
3、结构体:fmt::format_to_n_result
template <typename OutputIt>
struct fmt::format_to_n_result
公有成员:
3.1)、OutputIt out
迭代器指向末尾
3.2)、size_t size
总(未截断)输出大小。
E、Literal-based API
fmt/format.h中定义了以下用户定义的Literal。
1、
detail::udl_formatter<char> fmt::literals::operator""_format(const char *s, size_t n)
用户定义的Litera等效于fmt :: format()。例如:
using namespace fmt::literals;
std::string message = "The answer is {}"_format(42);
2、
detail::udl_arg<char> fmt::literals::operator""_a(const char *s, size_t)
用户定义的Litera等效于fmt :: argv()。 例如:
using namespace fmt::literals;
fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23);
F、实用工具(Utilities)
1、fmt::is_char
template <typename T>
struct fmt::is_cha
判断T是否为字符类型。 继承false_type
using fmt::char_t = typedef typename detail::char_t_impl<S>::type
字符串的字符类型。
2、 fmt::formatted_size
template <typename... Args>
size_t fmt::formatted_size(string_view format_str, const Args&... args)
返回格式(format_str,args ...)的输出中的字符数。
3、fmt::to_string
template <typename T>
std::string fmt::to_string(const T &value)
使用类型T的默认格式将value转换为std :: string。例子:
#include <fmt/format.h>
std::string answer = fmt::to_string(42);
4、 fmt::to_wstring
template <typename T>
std::wstring fmt::to_wstring(const T &value)
使用类型T的默认格式将value转换为std :: wstring。
5、 fmt::to_string_view
template <typename Char>
basic_string_view<Char> fmt::to_string_view(const Char *s)
返回参数S的字符串view.为了向{fmt}添加自定义字符串类型支持,重载了函数:to_string_view。例如:
namespace my_ns
{
inline string_view to_string_view(const my_string& s)
{
return {s.data(), s.length()};
}
}
std::string message = fmt::format(my_string("The answer is {}"), 42);
6、fmt::join
template <typename Range>
arg_join<detail::iterator_t<const Range>, detail::sentinel_t<const Range>, char> fmt::join(const Range &range, string_view sep)
返回一个对象,该对象的格式设置range用sep分隔的元素。例如:
std::vector<int> v = {1, 2, 3};
fmt::print("{}", fmt::join(v, ", "));
// Output: "1, 2, 3"
fmt :: join将传递的格式说明符应用于range元素:
fmt::print("{:02}", fmt::join(v, ", "));
// Output: "01, 02, 03"
7、fmt::join
template <typename It, typename Sentinel>
arg_join<It, Sentinel, char> fmt::join(It begin, Sentinel end, string_view sep
返回一个对象,该对象使用由sep分隔,格式化迭代器范围[begin,end)。
8、类 fmt::detail::buffer
具有可选增长能力的连续内存缓冲区。它是一个内部类,不应该直接使用,通过 basic_memory_buffer 访问
公有函数:
8.1)、size
size_t size() const
返回缓冲区的大小
8.2)、capacity
size_t capacity() const
返回此缓冲区的容量
8.3)、data
T *data()
返回一个指向该缓冲区的指针,指针类型与模板T有关
8.4)、resize
void resize(size_t new_size)
调整缓冲区大小。如果T是POD类型,则可能不会初始化新元素。
8.5)、clear
void cLear()
清空buffer
8.6)、reserve
void reserve(size_t new_capacity)
保留至少能够存储 new_capacity的元素空间
8.7)、append
template <typename U>
void append(const U *begin, const U *end)
向buffer的末尾追加数据
9、fmt::basic_memory_buffer类
动态增长的内存缓冲区,用于带有可存储在对象本身中的第一个SIZE元素的类型。
将以下类型别名,用于常见字符类型:
Type Definition
memory_buffer basic_memory_buffer<char>
wmemory_buffer basic_memory_buffer<wchar_t>
例如:
fmt::memory_buffer out;
format_to(out, "The answer is {}.", 42);
字符串将输出到out:
The answer is 42.
可以使用to_string(out)将输出转换为std :: string。
该类继承 fmt::detail::buffer< T >
公有函数:
9.1)、basic_memory_buffer
basic_memory_buffer(basic_memory_buffer &&other)
构造一个fmt :: basic_memory_buffer对象,将另一个对象的内容移动到该对象。
9.2)、basic_memory_buffer
basic_memory_buffer &operator=(basic_memory_buffer &&other)
重载运算符,将对象other的值拷贝到当前对象
受保护函数:
9.3)、grow
void grow(size_t capacity)
扩张buffer的容量以便能容纳当前元素数量。
G、系统错误
fmt不使用errno向用户传达错误。但它可调用设置errno的系统函数errno。用户不应对库函数保留的errno值做任何臆测。
1、类fmt::system_error
class fmt::system_error
操作系统或语言运行时返回的错误,例如:文件打开的错误。该类继承runtime_error。
公有函数:
1.1)、system_error
template <typename... Args>
system_error(int error_code, string_view message, const Args&... args)
构造一个fmt :: system_error对象,其格式为fmt :: format_system_error(),消息和传递给构造函数的参数将会自动被格式化,类似:fmt::format()。例如:
// This throws a system_error with the description
// cannot open file 'madeup': No such file or directory
// or similar (system message may vary).
const char *filename = "madeup";
std::FILE *file = std::fopen(filename, "r");
if (!file)
throw fmt::system_error(errno, "cannot open file '{}'", filename);
2、fmt::format_system_error函数
void fmt::format_system_error(detail::buffer<char> &out, int error_code, string_view message)
格式化由操作系统或语言运行时返回的错误,例如文件打开错误,例如,以下形式输出:
<message>: <system-message>
其中,<message>是传递的消息,而<system-message>是与错误代码相对应的系统消息。error_code是errno给出的系统错误代码。如果error_code不是有效的错误代码(例如-1),则系统消息可能看起来像“未知错误-1”,并且取决于平台因素。
3、类fmt::windows_error
Windows错误类,继承fmt::system_error
3.1)、public构造函数
template <typename... Args>
windows_error(int error_code, string_view message, const Args&... args)
创建一个 fmt::system_error的对象,对象的格式如下:
<message>: <system-message>
其中,<message>是格式化的消息,而<system-message>是与错误代码相对应的系统消息。error_code是GetLastError给出的Windows错误代码。如果error_code不是有效的错误代码(例如-1),则系统消息将看起来像“错误-1”。例如:
// This throws a windows_error with the description
// cannot open file 'madeup': The system cannot find the file specified.
// or similar (system message may vary).
const char *filename = "madeup";
LPOFSTRUCT of = LPOFSTRUCT();
HFILE file = OpenFile(filename, &of, OF_READ);
if (file == HFILE_ERROR)
{
throw fmt::windows_error(GetLastError(),
"cannot open file '{}'",
filename);
}
H、定制分配器
fmt库支持自定义动态内存分配。可以将自定义分配器类指定为fmt :: basic_memory_buffer的模板参数:
using custom_memory_buffer = fmt::basic_memory_buffer<char, fmt::inline_buffer_size, custom_allocator>;
也可以编写使用自定义分配器的格式化函数:
using custom_string =
std::basic_string<char, std::char_traits<char>, custom_allocator>;
custom_string vformat( custom_allocator alloc,
fmt::string_view format_str,
fmt::format_args args)
{
custom_memory_buffer buf(alloc);
fmt::vformat_to(buf, format_str, args);
return custom_string(buf.data(), buf.size(), alloc);
}
template <typename ...Args>
inline custom_string format(custom_allocator alloc,
fmt::string_view format_str,
const Args& ... args)
{
return vformat(alloc, format_str, fmt::make_format_args(args...));
}
分配器将仅用于输出容器。如果使用命名参数,则将使用默认分配器分配存储指向它们的指针的容器。浮点格式还取决于可以进行分配的sprintf。
I、范围和元组的格式化
该库还支持范围和元组的便捷格式化, 例如:
#include <fmt/ranges.h>
std::tuple<char, int, float> t{'a', 1, 2.0f};
// Prints "('a', 1, 2.0)"
fmt::print("{}", t);
注意: 当前的使用的是头文件format.h中的fmt::join函数。
当使用 fmt::join时,可以使用自定义的分隔符将元组的元素分开:
#include <fmt/ranges.h>
std::tuple<int, char> t = {1, 'a'};
// Prints "1, a"
fmt::print("{}", fmt::join(t, ", "));
J、时间和日期的格式化
fmt还支持对时间和日期的格式化处理。
#include <fmt/chrono.h>
std::time_t t = std::time(nullptr);
// Prints "The date is 2016-04-29." (with the current date)
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
格式化字符串的语法,具体可以 查看后面的链接: https://en.cppreference.com/w/cpp/chrono/c/strftime
K、编译时格式化字符串
fmt/compile.h提供格式字符串编译支持。格式字符串在编译时进行解析,并转换为有效的格式代码。支持内置和字符串类型的参数,以及格式设置专长中具有constexpr解析函数的用户定义类型的参数。与默认API相比,格式字符串编译可以生成更多的二进制代码,仅在格式化是性能瓶颈的地方才建议使用。
FMT_COMPILE(s)
将字符串文字s转换为格式字符串,该格式字符串将在编译时进行解析,并转换为有效的格式代码。如果编译器支持,则需要C ++ 17 constexpr。例如:
// Converts 42 into std::string using the most efficient method and no
// runtime format string processing.
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
L、标准输出流std::ostream 的支持
fmt/ostream.h提供std :: ostream支持,包括格式化运算符<<的用户定义类型的格式。例如:
include <fmt/ostream.h>
class date
{
int year_, month_, day_;
public:
date(int year, int month, int day): year_(year), month_(month), day_(day) {}
friend std::ostream& operator<<(std::ostream& os, const date& d)
{
return os << d.year_ << '-' << d.month_ << '-' << d.day_;
}
};
std::string s = fmt::format("The date is {}", date(2012, 12, 9));
// s == "The date is 2012-12-9"
1、 fmt::print
template <typename S, typename... Args, typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
void fmt::print(std::basic_ostream<Char> &os, const S &format_str, Args&&... args)
将格式化的数据输出到标准输出流os。例如:
fmt::print(cerr, "Don't {}!", "panic");
M、printf的格式化
头文件fmt / printf.h提供类似printf的格式化功能。以下函数使用带有POSIX扩展名的printf格式字符串语法来表示位置参数。与标准对等函数不同,fmt函数具有类型安全性,如果参数类型与其格式规范不匹配,则抛出异常。
1、fmt::printf
template <typename S, typename... Args>
int fmt::printf(const S &format_str, const Args&... args)
将格式化的数据输出到stdout。例如:
fmt::printf("Elapsed time: %.2f seconds", 1.23);
2、fmt::fprintf
template <typename S, typename... Args, typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
int fmt::fprintf(std::FILE *f, const S &format, const Args&... args)
将格式化的数据输出到文件。例如:
fmt::fprintf(stderr, "Don't %s!", "panic");
3、fmt::fprintf
template <typename S, typename... Args, typename Char = char_t<S>>
int fmt::fprintf(std::basic_ostream<Char> &os, const S &format_str, const Args&... args)
将数据输出到os。 例如:
fmt::fprintf(cerr, "Don't %s!", "panic");
4、fmt::sprintf
template <typename S, typename... Args, typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
std::basic_string<Char> fmt::sprintf(const S &format, const Args&... args)
格式化参数,并返回一个已经格式化的字符串。例如:
std::string message = fmt::sprintf("The answer is %d", 42);
N、兼容c++20的std::format
fmt几乎实现了所有c++20中格式化库,但有几点不同:
1、命名空间的不同,fmt的函数全部都在fmt的命名空间下,避免了与std命名空间下的函数同名冲突。
2、当前的fmt库中,'L'仍然不能用做格式化符连接使用。
3、宽度计算不使用字素聚类。后者已在单独的分支中实施,但尚未集成。
4、计时格式不支持C ++ 20日期类型,因为它们不是标准库实现提供的。
--------------------------------------完--------------------------------------------------
githu是个好地方,人富水也甜。
欢迎指正