前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【介绍一些好用的轮子(1)】类型安全的字符串格式化输出

【介绍一些好用的轮子(1)】类型安全的字符串格式化输出

作者头像
JIFF
发布2019-08-02 15:25:23
1.7K0
发布2019-08-02 15:25:23
举报
文章被收录于专栏:Toddler的笔记Toddler的笔记
  • 前言
  • 类型安全
  • 简单用法
  • 详细用法
    • 0 padding
    • type
    • alignment and fill
    • sign
    • width and precision/truncation
    • 其他
    • 例子

前言

之前的文章有朋友反映比较难懂,这篇争取写的简单易懂一些,希望有一点参考价值。

学过 C 语言的同学可能对 printf 都不陌生,也对用 "%d" 这种格式控制符对应于打印一个 int 也不陌生。然而这种打印方式是被 C++ 唾弃的,于是有了复杂的 stream 和可怕的 std::cout << std::internal << std::setw(10) << std::showbase << std::setfill('0') << std::hex << 0x10 << std::endl; 来实现和 printf("%#010x\n", 0x10); 同样的效果。

而 python 似乎提供了一种不错的格式化输出方式:'{:#010x}'.format(0x10) [1]

于是 fmtlib.fmt[2][3] 为 C++ 提供了类似 python 的 format 的实现:fmt::format("{:#010x}", 0x10);

可能有人会问 format("{:#010x}", 0x10);printf("%#010x", 0x10); 有什么区别呢?

实际上区别很大,例如我可以用 format("{}", 16); 的形式直接打印 16 这个 int,而不需要用 "%d" 来明确指定我要打印一个 int。即 format 是通过 template 来实现类型感知的。更进一步说,format 是 类型安全 的。

类型安全

关于类型安全(type-safe)的话题网上可以搜到很多,我举一个自己碰到的实际例子。

代码语言:javascript
复制
uint32_t value = 468391957727543360UL;
char buf[256];
... // fill the buffer
snprintf(buf+56, sizeof(value), "%x", value);
... // fill the buffer

就是这样一份简单的代码,在后来一次升级的过程中,因业务需要把 uint32_t 改成了 uint64_t,把 "%x" 改成了 "%lx",初始值也改成了一个大值,于是业务发生了问题。因为 buf 最终是通过网络发送给B公司的,即使询问B公司,对方也没有义务把收到的字节反馈回来。花费了很多调试时间,绕了一大圈,通过 wireshark 抓包才得知此处的 value 写到 buf 中时,仅仅被截取了一半。

这其中包含两个问题:

  • 因为 printf 的 格式化控制符 和打印的 值的类型 必须是对应的,因此,当更新代码时,在更新值的类型的时候,需要记得同时也更新对应的格式化控制符。
    • 因此可以怼一下:a solution involving the phrase "just remember to" is seldom the best solution. Sometimes you won't remember.
  • 回到此例中,即使记得同步改了格式控制符 "%x""%lx",最后实际证明在 64位 Linux 平台中 printf("%lx", value); 是可以正确打印的,但是在 64位 Windows 平台中 printf("%lx", value); 也无法正确打印该值,需要用 printf("%**llx**", value); 才可以正确打印。这可能是因为在 64位 Linux 平台中 unsigned long 是 64 位的,但是在 64位 Windows 平台中,unsigned long 是 32 位的。
    • 因此,如果能让打印函数自动识别 value 的长度信息,而不是需要具体指出,会让代码更容易维护和减小出错的机会。

而 fmt::format 正是解决了这些问题。

简单用法

  • fmt::format("{}", value); 返回值类型为 std::string。其中"{}" 可以被看做一个占位符/placeholder
  • fmt::format_to(buf, "{}", value); buf 为 fmt::memory_buffer
代码语言:javascript
复制
std::string s = fmt::format("{} {}", "Hello", "World");
fmt::memory_buffer buf;
format_to(buf, "{}", 42);    // replaces itoa(42, buffer, 10)

详细用法

format 输出格式控制[4]。

  • fmt::format("{1} {0}", "World", "Hello"); 打印 "Hello World",此处 10 表示 positional param。
  • 和 printf 格式化控制符 "%.." 对应的,在 format 中用 "{:..}" 表示
  • 再结合 positional param,其形式为 {N:formats}N0,1,2 等自然数;formats 主要由 alignment,sign,width,type 组成,更严谨的定义如下:
  • format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision][type]
  • 和 printf 的对应关系[5]:

type

  • 打印整数:
    • d, b, B, o, x, X, n 用法较直观简单,略,可参考[4]
    • 注意,这里的 x 是不需要加 l 或者 ll 来区分位数的,而是根据变量的类型自动识别。如果是 uint32_t 则打印32位,如果是 uint64_t 则打印64位,于是再也不用操心到底用 %lx 还是 %llx 来打印hex了,从而解决上上文中的问题。
  • 打印字符:
    • c 用法较直观简单,略,可参考[4]
  • 打印字符串:
    • s 用法较直观简单,略,可参考[4]
  • 打印浮点数:
    • e 类似 std::scientific,科学计数法,例如 "1.02e1",即表示 1.02 x 10^1
    • f 类似 std::fixed,定点数,例如 "10.2"
    • g 类似 std::defaultfloat,自动选取 ef 中更短的形式打印
    • a 类似 std::hexfloat,以16进制的形式打印浮点数,一般很少用到。
  • 打印指针:
    • "{:p}", 0xffff 是不可行的,会报 type mismatch,因为 0xffff 是个 int。必须明确对 0xffff 做转换才能打印:"{}", (void*)0xffff。这也是为什么说 fmt::format 是 type-safe 的。
    • "{:p}", ptr 是没必要加 :p 的,因为 {} 会根据 ptr 的类型自动打印成指针,就相当于 "{}", ptr
    • "{:p}", str.c_str() 这个是有必要的,否则将以默认 :s 的格式打印出来

alignment and fill

  • 格式为 [[fill]align][sign]。以<为例:"{:?<6}":其中?可指定为任意字符,取代默认的空格作为 fill/padding
  • < 左对齐,对于 <>^ 这三个align,其padding都是位于整个字符串最前面/最后面的。对string和numeric类型都适用
  • > 右对齐
  • ^ 居中对齐
  • = 这种padding是位于符号+-前缀# 和 输出的数字之间的。only valid for numeric types.
  • 这四种对齐方式只能选一种。

0 padding

  • (only valid for numerics. 结合字符串类型会运行时报错 runtime_error)
  • 0 padding 0, 紧贴在 (width)n 前面。
  • 相当于上面的对齐方式 0=
  • 如果同时存在的 ?= 指定了具体其他 padding,则此 0 覆盖之
  • 这个 0 padding 是为了兼容 printf 而设置的。
  • 这个 (width)n 前面的 0 只能是固定的 '0',不能是其他字符。如果想实现其他字符作为 这种 padding,则需要用 ?= 的语法。

sign

  • + print sign for both pos/neg
  • - print sign only for neg
  • 空格 表示前缀要么是空格,要么是-
  • 默认情况不指定 sign,表示如果是正数则忽略+号,如果是负数则写出-

width and precision/truncation

  • n.m 格式
  • 对于浮点数:
    • 6.2f 表示算上正负号,算上小数点,包括 padding,一共长6,小数点后面的位数一定是2。总长不足6补齐6,超过6忽略这个6
  • 对于字符串:"." 不是表示精度而是表示截断
    • ("{:10.5}","xylophone") 输出 "xylop(后接5个空格)"

其他

包括:

  • #,表示给 :x0x 前缀等,例如 format("{:#04x}", 0); 输出 "0x00"
  • Parameterized formats,即格式化参数本身也可以参数化,例如 format("{:.{}f}", 3.14, 1); 相当于 format("{:.1f}", 3.14);
  • Named placeholders/param,即类似 python 的 '{name}'.format(name='World')。需要结合 fmt::arg("name", "World"), 或者 "name"_a=World 来使用。
    • 结合 C++11 Literal Operator 使用:
    • using fmt::literals;
    • named param: fmt::print("{name} {number}", fmt::arg("name", "World"), "number"_a=42));
    • std::string message = "{0}{1}{0}"_format("abra", "cad"); 形式启发于python的.format(),完全identical to fmt::format
  • wide string: 控制符和变量需要一致,即format(L"{}", L'\x42e');, 而不可以 format("{}", L'\x42e');

例子

代码语言:javascript
复制
string s0 = format("{} to {}",   "a", "b"); // OK: automatic indexing
string s1 = format("{1} to {0}", "a", "b"); // OK: manual indexing
char c = 120;
string s0 = format("{:6}", 42);      // s0 == "    42"
string s1 = format("{:6}", 'x');     // s1 == "x     "
string s2 = format("{:*<6}", 'x');   // s2 == "x*****"
string s3 = format("{:*>6}", 'x');   // s3 == "*****x"
string s4 = format("{:*^6}", 'x');   // s4 == "**x***"
string s6 = format("{:6d}", c);      // s6 == "   120"
string s7 = format("{:=+6d}", c);    // s7 == "+  120" 在中间插入padding
string s7 = format("{:>+6d}", c);    // s7 == "  +120" 在前面插入padding
string s8 = format("{:?=#6x}", 0xa); // s8 == "0x???a"
string s8 = format("{:?=+#6x}", 0xa);// s8 == "+0x??a"
string s9 = format("{:6}", true);    // s9 == "true  "
string s1 = format("{0:} {0:+} {0:-} {0: }", -1);  // s1 == "-1 -1 -1 -1"
{0: }表示打印参数{0}前缀要么是空格,要么是-号
{0:}表示打印参数{0}前缀要么是-号,要么没有任何前缀
string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a"
string s2 = format("{0:#x} {0:#X}", 42);           // s2 == "0x2a 0X2A"
string s3 = format("{:n}", 1234);                  // s3 == "1,234" (depends on the locale)

  1. https://pyformat.info/
  2. https://fmt.dev/
  3. https://github.com/fmtlib/fmt
  4. https://fmt.dev/dev/syntax.html
  5. https://fmt.dev/Text Formatting.html
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Toddler的笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 类型安全
  • 简单用法
  • 详细用法
    • type
      • alignment and fill
        • 0 padding
      • sign
        • width and precision/truncation
          • 其他
            • 例子
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档