快速通道
让我们不再拖延,直接开始探索 fmt 库 API 的奥秘吧!如果您还不熟悉 fmt 库的语法规则,不用担心,可以参考 c++ {fmt}库使用指南一来了解详细信息
Core API 在 fmt/core.h中
-
根据fmt中的规范格式化args,并将结果作为字符串返回。
template<typename ...T> auto fmt::format(format_string<T...> fmt, T&&... args) -> std::string
#include <fmt/core.h> // message == The answer is 42. std::string message = fmt::format("The answer is {}.", 42);
-
根据fmt中的规范格式化args, 格式化到迭代器中,返回迭代器末尾迭代器,并且不附加终止空字符。
template<typename OutputIt, typename ...T> auto fmt::format_to(OutputIt out, format_string<T...> fmt, T&&... args) -> OutputIt
#include <fmt/core.h> auto out = std::vector<char>(); // out vector 中有两个元素,4、2 fmt::format_to(std::back_inserter(out), "{}", 42);
-
根据fmt中的规范格式化args, 格式化n个字符到迭代器中,(返回 开始迭代器 + n 那个迭代器),并且不附加终止空字符。
template<typename OutputIt, typename ...T> auto fmt::format_to_n(OutputIt out, size_t n, format_string<T...> fmt, T&&... args) -> format_to_n_result<OutputIt>
#define FMT_HEADER_ONLY #include "fmt/core.h" #include <string> #include <vector> // 迭代器开始位置是容器末尾位置,crash // n 数操作容器最大数,crash // 返回迭代器是格式化的总个数字符 // 如果n < out.size && 小于123456的个数,那么后面会格式化'\0'字符。 int main() { auto out = std::vector<char>(10); // nit n代表个数,it代表迭代器^_^ auto nit = fmt::format_to_n(out.begin(), 3, "{}", 123456); // 返回的迭代器为 out.begin() + 3 std::vector<char>::iterator it = nit.out; // 返回格式化总的个数 int size = nit.size; return 0; }
-
格式化到控制台,类似于c语言printf (函数名字为print 不还f, f为format缩写)
template<typename ...T> void fmt::print(format_string<T...> fmt, T&&... args)
#define FMT_HEADER_ONLY #include "fmt/core.h" #include <string> #include <vector> int main() { // 输出到stdout fmt::print("Elapsed time: {} seconds\n", 1.23); // 输出到stdout fmt::print(stdout, "Elapsed time: {} seconds\n", 1.23); // 输出到stderr fmt::print(stderr, "Elapsed time: {} seconds\n", 1.23); return 0; }
-
输出到文件
template<typename ...T> void fmt::print(std::FILE *f, format_string<T...> fmt, T&&... args)
#define FMT_HEADER_ONLY #include "fmt/core.h" #include <stdio.h> int main() { FILE* file; file = fopen("d:/demo/test.txt", "w"); // 打开d盘文件,即可看到输出内容 fmt::print(file, "Elapsed time: {} seconds\n", 1.23); return 0; }
命名参数
template<typename Char, typename T>
auto fmt::arg(const Char *name, const T &arg) -> detail::named_arg<Char, T>
- 返回一个命名参数给格式化函数使用, 只能使用在格式化函数里面或者dynamic_format_arg_store::push_back
- 该函数不支持编译期检查
// 命名参数的名字是agr_id fmt::print("my name is {name:}", fmt::arg("name", "knox")); // 输出: my name is knox
参数列表
这是一系列的模板类类和模板方法,使用这些类可以构造属于我们自己的格式换函数。列如日志前部分是由日期,线程id,函数名等等组成。
-
fmt::make_format_args 函数
template<typename Context = format_context, typename ...Args> constexpr auto fmt::make_format_args(Args&&... args) -> format_arg_store<Context, remove_cvref_t<Args>...> // 构造一个包含参数引用的format_arg_store 对象,并且可以隐式转换为format_args, Context使用默认的即可 // 由于是引用,不建议单独写一个变量来存储变量,变为悬空引用。 fmt::format_args args = fmt::make_format_args(42);
-
format_arg_store 类
template<typename Context, typename ...Args> class format_arg_store { // 该类是存储参数引用的数组,并且可以隐式转换为basic_format_args };
-
basic_format_args 类
template<typename Context> class basic_format_args { public: // 从format_arg_store 构造basic_format_args template<typename ...Args> constexpr basic_format_args(const format_arg_store<Context, Args...> &store); // 从dynamic_format_arg_store 构造basic_format_args template<typename ...Args> constexpr basic_format_args(const dynamic_format_arg_store<Context> &store); // 从动态参数中 构造basic_format_args template<typename ...Args> constexpr basic_format_args(const format_arg *args, int count); // 使用id获取一个format_arg auto get(int id) const -> format_arg; }; // format_args 是basic_format_args<format_context> 的别名 using format_args = basic_format_args<format_context>;
-
basic_format_parse_context 类
template<typename Char, typename ErrorHandler = detail::error_handler> class fmt::basic_format_parse_context : private fmt::detail::error_handler { public: constexpr auto begin() const noexcept->iterator; constexpr auto end() const noexcept->iterator; void advance_to(iterator it); auto next_arg_id() -> int; void check_arg_id(int id); }; using format_parse_context = basic_format_parse_context<char>; using wformat_parse_context = basic_format_parse_context<wchar_t>;
-
basic_format_context 类
template<typename OutputIt, typename Char> class basic_format_context { public: // 构造 constexpr basic_format_context(OutputIt out, basic_format_args<basic_format_context> ctx_args, detail::locale_ref loc = detail::locale_ref()); } ; using format_context = buffer_context<char>;
-
使用以上接口实现一个日志打印版本
#include <chrono> #include <iostream> #include <string> #include <thread> #include <sstream> #include "fmt/printf.h" #include "fmt/chrono.h" #undef __MODULE__ #define __MODULE__ "Test" void vlog(const char *module, int line, fmt::string_view format, fmt::format_args args) { std::string log; // 时间 打印行数 线程id auto now = std::chrono::system_clock::now(); auto now_c = std::chrono::system_clock::to_time_t(now); std::stringstream ss; // 获取线程整数真难,没有公开方法 ss << std::this_thread::get_id(); fmt::format_to(std::back_inserter<std::string>(log), "[{0:%m%d %H\:%m\:%S}:{1:}][{2:}:{3:}][tid:{4:}] ", fmt::localtime(now_c), (now.time_since_epoch() % 1000).count(), module, line, ss.str()); fmt::vformat_to(std::back_inserter<std::string>(log), format, args); // 此处可以写文件 或者打印到控制台 简单起见打印到控制台 fmt::print("{}", log); } template <typename S, typename... Args> void log(const char *file, int line, const S &format, Args&&... args) { vlog(file, line, format, fmt::make_format_args(args...)); } #define log_v_notic(format, ...) \ log(__MODULE__, __LINE__, FMT_STRING(format), __VA_ARGS__) int main() { while (true) { log_v_notic("hello {} {}, hi {}\n", "v", "log", "knox"); std::this_thread::sleep_for(std::chrono::milliseconds(3)); } return 0; }
fmt::string_view
template<typename Char>
class basic_string_view
{
public:
// 使用c 风格字符串和大小构造一个字符串引用对象。
constexpr basic_string_view(const Char *s, size_t count) noexcept;
// 使用c风格字符串,大小由 std::char_traits<Char>::length 计算构造一个字符串引用对象。
basic_string_view(const Char *s)
// 从std::string or std::wstring 构造一个字符串引用对象。
template<typename Traits, typename Alloc>
basic_string_view(const std::basic_string<Char, Traits, Alloc> &s) noexcept
// 返回字符对象字符的内容
constexpr auto data() const noexcept -> const Char*
// 返回字符对象的字符长度
constexpr auto size() const noexcept -> size_t
};
using string_view = basic_string_view<char>;
格式化自定义类型,包括类和结构体
- 特例化formatter ,并且实现 parse 和 format 方法
#include <fmt/format.h>
struct Human {
std::string name;
int age;
std::string address;
std::string number; // 电话号码
};
// 特例化formatter模板
template <> struct fmt::formatter<Human> {
// 自定义格式化风格,比如brief 简单介绍
// detail详细介绍
// 默认风格是简单介绍
char style[10]{0};
// 这个函数是解析格式化字符串的函数:{arg_id: brief | detail}
// 解析是从:后面开始的 这里可以实现自己的逻辑,想怎么实现都可以
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin())
{
auto it = ctx.begin(), end = ctx.end() - 1;
int index = 0;
for (; it != end; it++)
{
style[index++] = *it;
if (index > 9)
{
style[9] = '\0';
break;
}
}
// 合规性检查,是不是针对该自定义类型乱输入,比如输入{:1234343343} 这种就是错误的格式
if (it != end && *(it) != '}')
{
throw format_error("invalid format");
}
return it;
}
// 2.使用指定的风格(简单风格,详细风格)实现格式化函数
template <typename FormatContext>
auto format(const Human & human, FormatContext &ctx) const -> decltype(ctx.out())
{
// 将内容都格式化到ctx的尾指针中,ctx.out();
if (strcmp(style, "brief") == 0)
{
fmt::format_to(ctx.out(), "name:{}, age:{}, address:{}, number:{} \n", human.name, human.age, human.address, human.number);
}
else if(strcmp(style, "detail") == 0)
{
fmt::format_to(ctx.out(), "Hello, my name is {}. "
"I am {} years old and currently reside in {}. "
"If you need to contact me, you can reach me at{}.Thank you!\n",
human.name, human.age, human.address, human.number);
}
else
{
// 都不是我们需要的格式,直接报错
throw format_error("invalid format");
}
return ctx.out();
}
};
int main()
{
Human knox;
knox.name = "Knox";
knox.age = 18;
knox.number = "1234567";
knox.address = "tu chuan";
// 简单版本
fmt::print("{:brief}", knox);
// 详细版本
fmt::print("{:detail}", knox);
try
{
// 错误版本
fmt::print("{:2222}", knox);
fmt::print("{:detail22222222}", knox);
}
catch (const fmt::format_error &excption)
{
fmt::print("{}", excption.what());
}
}
输出
name:Knox, age:18, address:tu chuan, number:1234567
Hello, my name is Knox. I am 18 years old and currently reside in tu chuan. If you need to contact me, you can reach me at1234567.Thank you!
invalid format
- fmt::formatter::parse 函数一定要返回解析格式化字符串的尾指针
- 由于fmt::formatter::parse 函数是运行期间的,我们肯定不能有太多耗时的运算,所以可以把以上版本的风格简写为一个。尽量一个字符代表一种格式,减少运算
#include <fmt/format.h>
struct Human {
std::string name;
int age;
std::string address;
std::string number; // 电话号码
};
// 特例化formatter模板
template <> struct fmt::formatter<Human> {
// 自定义格式化风格,比如b->brief 简单介绍
// d->detail详细介绍
// 默认风格是简单介绍
char style = 'b';
// 这个函数是解析格式化字符串的函数:{arg_id: brief | detail}
// 解析是从:后面开始的 这里可以实现自己的逻辑,想怎么实现都可以
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin())
{
auto it = ctx.begin(), end = ctx.end();
char ch = *it++;
if (ch == 'b' || ch == 'd')
{
style = ch;
}
// 合规性检查
if (it != end && *it != '}')
{
throw format_error("invalid format");
}
return it;
}
// 2.使用指定的风格(简单风格,详细风格)实现格式化函数
template <typename FormatContext>
auto format(const Human & human, FormatContext &ctx) const -> decltype(ctx.out())
{
// 将内容都格式化到ctx的尾指针中,ctx.out();
if (style == 'b')
{
fmt::format_to(ctx.out(), "name:{}, age:{}, address:{}, number:{} \n", human.name, human.age, human.address, human.number);
}
else if(style == 'd')
{
fmt::format_to(ctx.out(), "Hello, my name is {}. "
"I am {} years old and currently reside in {}. "
"If you need to contact me, you can reach me at{}.Thank you!\n",
human.name, human.age, human.address, human.number);
}
else
{
// 都不是我们需要的格式,直接报错
throw format_error("invalid format");
}
return ctx.out();
}
};
int main()
{
Human knox;
knox.name = "Knox";
knox.age = 18;
knox.number = "1234567";
knox.address = "tu chuan";
// 简单版本
fmt::print("{:b}", knox);
fmt::print("{:d}", knox);
}
- 继续fmt 原来格式化类,使用fmt库的格式化语法。
#include <fmt/format.h>
enum class color { red, green, blue };
template <> struct fmt::formatter<color> : formatter<string_view>
{
//从 formatter<string_view> 继续parse 函数
template <typename FormatContext>
auto format(color c, FormatContext &ctx) const
{
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);
}
};
int main()
{
fmt::print("{:<10} |\n", color::red);
fmt::print("{:^10} |\n", color::green);
fmt::print("{:>10} |\n", color::blue);
return 0;
}
输出:
red |
green |
blue |
fmt::underlying
template
constexpr auto fmt::underlying(Enum e) noexcept -> underlying_t
转换为最底层的数据类型,观看源码:using underlying_t = typename std::underlying_type::type;\
fmt::to_string
template
auto fmt::to_string(const T &value) -> std::string
和std::to_string 差不多, 但并不会保留末尾的0
fmt::join
template<typename Range>
auto fmt::join(Range &&range, string_view sep) -> join_view<detail::iterator_t, detail::sentinel_t>
返回一个字符view ,使用指定的字符连接起来
std::vector<int> v = {1, 2, 3};
fmt::print("{}", fmt::join(v, ", "));
// Output: "1, 2, 3"
fmt::print("{:02}", fmt::join(v, ", "));
// Output: "01, 02, 03"
template<typename It, typename Sentinel>
auto fmt::join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel>
返回迭代器之间的格式化字符
std::vector<int> v = { 1, 2, 3, 6, 7 };
fmt::print("{:02}\n", fmt::join(v.begin(), v.end(), ", "));
fmt::print("{:02}\n", fmt::join(v.begin() +2,v.end(), ", "));
fmt::print("{:02}\n", fmt::join(v.begin(), v.end() -2, ", "));
输出
01, 02, 03, 06, 07
03, 06, 07
01, 02, 03
对标c语言的printf、sprintf 该函数位于fmt/printf.h中
template<typename S, typename …T>
auto fmt::printf(const S &fmt, const T&… args) -> int
格式化到标准输出,并返回格式化字符的个数
template<typename S, typename …T, typename Char = char_t<S>>
auto fmt::fprintf(std::FILE *f, const S &fmt, const T&… args) -> int
格式化到文件中
template<typename S, typename …T, typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
auto fmt::sprintf(const S &fmt, const T&… args) -> std::basic_string
格式化到string中
std::ostream 支持
#include <fmt/format.h>
#include <fmt/ostream.h>
struct Human {
std::string name;
int age;
friend std::ostream &operator<<(std::ostream &os, const Human &human) {
return os << human.name << " " <<
human.age << " years .";
}
};
template <> struct fmt::formatter<Human> : ostream_formatter {};
int main()
{
fmt::print("the human is {}", Human{ "Knox", 18 });
// 输出:the human is Knox 18 years .
return 0;
}
控制台颜色 在头文件fmt/color.h中
// 这里多了text_style
template<typename S, typename ...Args>
void fmt::print(const text_style &ts, const S &format_str, const Args&... args);
// 文字的前景色,就是文字本身颜色 fg = foreground
text_style fmt::fg(detail::color_type foreground) noexcept;
// 文字的背景色 bg = background
text_style fmt::bg(detail::color_type background) noexcept;
// color_type 查看fmt::color
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23); // 所有的字体都被加粗,并且字体颜色为红色
// 将指定参数指定为特定的颜色
template<typename T>
auto fmt::styled(const T &value, text_style ts) -> detail::styled_arg<remove_cvref_t<T>>;
// 如
fmt::print("Elapsed time: {0:.2f} seconds",
fmt::styled(1.23, fmt::fg(fmt::color::green) | // 仅仅1.23 被定义为绿色的字,蓝色的背景
fmt::bg(fmt::color::blue)));
关于标准库类型格式化 在头文件fmt/std.h中
fmt::print("{}\n", std::this_thread::get_id());
std::filesystem::path p("/home/data/zhh");
fmt::print("{}", p);
输出
21412
/home/data/zhh
data 和 time 在头文件 fmt/chrono.h
std::tm fmt::localtime(std::time_t time)
和std::localtime类似,把时间转换为本地时间日历时间,在大多数平台上都是线程安全的\
std::tm fmt::gmtime(std::time_t time)
和std::gmtime 类似 把时间转换为utc时间,在大多数平台上都是线程安全的\
std::time_t t = std::time(nullptr);
// Prints "The date is 2020-11-07." (with the current date):
fmt::print("The date is {:%Y-%m-%d %H\:%M:%S}.\n", fmt::localtime(t));
fmt::print("The date is {:%Y-%m-%d %H\:%M:%S}.\n", fmt::gmtime(t));
using namespace std::literals::chrono_literals;
// Prints "Default format: 42s 100ms":
fmt::print("Default format: {} {}\n", 42s, 100ms);
// Prints "strftime-like format: 03:15:30":
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
输出
The date is 2023-04-13 20:25:13.
The date is 2023-04-13 12:25:13.
Default format: 42s 100ms
strftime-like format: 03:15:30