c++{fmt} API 详解

文章介绍了fmt库的核心API,如`fmt::format`用于格式化字符串,`fmt::print`向控制台输出,以及如何处理命名参数和自定义类型。fmt库还支持对C++标准库类型如`std::ostream`的格式化,并提供了控制台颜色功能和时间日期处理。此外,文章展示了如何特例化`fmt::formatter`以自定义类型格式化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


让我们不再拖延,直接开始探索 fmt 库 API 的奥秘吧!如果您还不熟悉 fmt 库的语法规则,不用担心,可以参考 c++ {fmt}库使用指南一来了解详细信息

Core API 在 fmt/core.h中
  1. 根据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);
    
  2. 根据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);
    
  3. 根据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;
    }
    
  4. 格式化到控制台,类似于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;
        }
    
  5. 输出到文件

    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>

  1. 返回一个命名参数给格式化函数使用, 只能使用在格式化函数里面或者dynamic_format_arg_store::push_back
  2. 该函数不支持编译期检查
    // 命名参数的名字是agr_id 
    fmt::print("my name is {name:}", fmt::arg("name", "knox"));
    // 输出: my name is knox
    
参数列表

这是一系列的模板类类和模板方法,使用这些类可以构造属于我们自己的格式换函数。列如日志前部分是由日期,线程id,函数名等等组成。

  1. 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); 
    
  2. format_arg_store 类

    template<typename Context, typename ...Args>
    class format_arg_store
    {
        // 该类是存储参数引用的数组,并且可以隐式转换为basic_format_args
    };
    
  3. 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>;
    
  4. 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>;
    
  5. 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>
  6. 使用以上接口实现一个日志打印版本

    #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>;
格式化自定义类型,包括类和结构体
  1. 特例化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
  1. fmt::formatter::parse 函数一定要返回解析格式化字符串的尾指针
  2. 由于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);
}
  1. 继续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语言的printfsprintf 该函数位于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
03-10
### C++ 中 `fmt` 库的使用方法与实例 #### 简介 `fmt` 是一个现代 C++ 的格式化库,旨在提供安全、快速且易于使用的字符串格式化功能。相比传统的 printf 或 iostreams, `fmt` 提供了更简洁和强大的接口。 #### 安装方式 可以通过包管理器安装或者直接下载源码编译。对于大多数项目来说,推荐的方式是将其作为子模块加入 Git 仓库或通过 Conan/CMake 外部依赖项引入[^1]。 #### 基本用法 最简单的例子就是替换掉 std::cout 和 printf 函数: ```cpp #include <fmt/core.h> int main() { fmt::print("Hello, {}!\n", "world"); } ``` 这段代码会打印出 `"Hello, world!"`. 使用花括号 `{}` 来标记参数位置,并传递相应的变量给 `fmt::print()`. #### 高级特性 除了基本的功能外,`fmt` 还支持更多高级特性和自定义格式说明符: - **命名参数**: 可以为占位符指定名称而不是仅靠顺序来决定其含义. ```cpp auto message = fmt::format("{first} {last}", fmt::arg("first", "John"), fmt::arg("last", "Doe")); ``` - **宽度调整** : 控制字段最小宽度 ```cpp fmt::print("{:>10}\n", "hello"); // 输出右对齐并填充空格至长度为10 ``` - **精度设置** :用于浮点数显示的小数位数控制 ```cpp double pi = 3.14159; fmt::print("Pi is approximately {:.2f}\n", pi); // 显示两位小数 ``` #### 性能优势 相比于其他流行的替代方案如 Boost.Format 或者 absl::StrFormat ,`fmt` 在运行效率上具有明显的优势,在某些情况下甚至可以达到两倍以上的速度提升. #### 错误处理机制 当遇到非法输入时(比如不匹配的数量),`fmt` 不会引起未定义行为而是抛出异常,这有助于开发者更容易定位错误所在之处.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值