← Back to Blog
C++C++20

聊聊 C++20 的 std::format

排版字串時,比起 printf% 或 iostream 的 <<std::format 的格式跟參數分離得比較清楚, 也比較好讀。這篇先當速查筆記,整理幾個最常需要查的點:浮點數的 precision / type,以及 precision 用在字串上會截斷的坑。

基本用法

#include <format>,用 {} 當佔位符。 它是 type-safe 的,不用像 printf 那樣手動對 %d%f

cpp
#include <format>
#include <print> // C++23
std::string s = std::format("{} + {} = {}", 1, 2, 3);
// "1 + 2 = 3"
std::println("{} + {} = {}", 1, 2, 3); // C++23,直接印

想重複用同一個參數,就標位置 {0}

cpp
std::format("{0}{1}{0}", "ab", "cd"); // "abcdab"

對齊、寬度、符號

format spec 看起來很長,但日常大多只會用到 width、precision、type 這幾個:

text
{:[fill][align][sign][#][0][width][.precision][type]}

< 靠左、> 靠右、^ 置中:

cpp
std::format("{:>8}", "hi"); // " hi"
std::format("{:<8}", "hi"); // "hi "
std::format("{:^8}", "hi"); // " hi "
std::format("{:*^8}", "hi"); // "***hi***"
std::format("{:08}", 42); // "00000042" 0 + width 補零
std::format("{:+}", 42); // "+42" 正數也加號
std::format("{: }", 42); // " 42" 正數留空位,負數照樣 -

浮點數:precision 跟 type

浮點數先記兩件事就夠:precision.N) 決定精度、type(最後那個字母)決定顯示形式。

cpp
double pi = 3.14159265;
std::format("{:.2f}", pi); // "3.14" 固定小數 2 位
std::format("{:.3e}", pi); // "3.142e+00" 科學記號
std::format("{:.4g}", pi); // "3.142" g:自動選 f/e,緊湊顯示

金額、平均值、比例這類輸出,{:.2f} 通常就很合適:

cpp
for (double price : {12.5, 8.99, 159.0, 68.75})
std::println("${:>8.2f}", price);
// $ 12.50
// $ 8.99
// $ 159.00
// $ 68.75

坑:precision 在字串上是「截斷長度」

同一個 .N,放在浮點數跟字串上不是同一件事:

  • 浮點數:精度,可能是小數位或有效位數,取決於 type
  • 字串:截斷長度,最多取幾個字元
cpp
std::format("{:.3}", 3.14159); // "3.14" 3 位有效數字
std::format("{:.3}", "formatting"); // "for" 截到 3 個字
std::format("{:.3f}", 3.14159); // "3.142" f 才是 3 位小數

所以 {:.3} 套在字串上不會報錯。 這種 bug 特別煩:不會炸,只是輸出悄悄變短。

整數:進制與前綴

cpp
std::format("{:b}", 42); // "101010"
std::format("{:o}", 42); // "52"
std::format("{:x}", 255); // "ff"
std::format("{:#x}", 255); // "0xff" # 加上進制前綴
std::format("{:#010b}", 42); // "0b00101010"

寬度、精度也能動態傳

不想寫死,就用巢狀的 {} 把寬度或精度當參數傳進去:

cpp
int w = 8;
std::format("{:>{}}", x, w); // 寬度由 w 決定
int n = 3;
std::format("{:.{}f}", pi, n); // 小數位數由 n 決定

這招很適合用在 log / table output:欄寬固定, 但寬度或小數位可以從 config 來。