C++之類型安全格式化format
C++20 引入的 std::format 是基于開源 fmt 庫(kù)標(biāo)準(zhǔn)化的新式格式化接口,解決了傳統(tǒng) printf 類型不安全、stringstream 代碼冗余等問題,成為當(dāng)前 C++ 首選的格式化方案。
與 printf 和 stringstream 對(duì)比:
| 特性 | printf | stringstream | std::format |
|---|---|---|---|
| 類型安全 | 無編譯檢查,類型錯(cuò)配導(dǎo)致未定義行為 (UB) | 類型安全,通過 operator<< 重載 | 編譯期檢查格式串與參數(shù)類型,錯(cuò)配即編譯報(bào)錯(cuò) |
| 自定義對(duì)象 | 不支持 | 需重載 operator<< | 特化 std::formatter<T>,邏輯集中 |
| 格式語(yǔ)法 | %d/%f/%s 符號(hào)零散 | 操控符堆砌(如 setw/hex),代碼冗長(zhǎng) | 統(tǒng)一 {} 占位符 + {:格式符} 語(yǔ)法 |
| 動(dòng)態(tài)寬/精度 | 需 * 參數(shù),易用性差 | 代碼繁瑣 | 原生支持 {:{width}} 和 {:.{prec}} 動(dòng)態(tài)傳參 |
| 性能 | 中等 | 差(大量臨時(shí)對(duì)象、堆分配) | 優(yōu)秀(format_to 零臨時(shí)字符串) |
關(guān)鍵差異:
printf使用裸可變參數(shù)(C-style varargs),編譯器在編譯階段無法有效對(duì)參數(shù)類型進(jìn)行強(qiáng)校驗(yàn),極易在運(yùn)行時(shí)因類型不匹配導(dǎo)致棧破壞或未定義行為。std::format則是基于 C++ 模板變參(Variadic Templates)與編譯期常量表達(dá)式(constexpr)技術(shù),在編譯期直接解析字符串語(yǔ)法,對(duì)參數(shù)數(shù)量、參數(shù)類型進(jìn)行全方位的靜態(tài)強(qiáng)類型檢查。
核心 API
std::format
生成格式化后的字符串并返回。適用于大部分需要便捷獲取 std::string 的通用場(chǎng)景。
std::string s1 = std::format("name: {}, age: {}", "Jack", 20); // 返回 "name: Jack, age: 20"
std::format_to
高性能流式接口,直接將數(shù)據(jù)寫入指定的輸出迭代器(如 std::vector<char>、字符數(shù)組、std::ostream_iterator 等),完美避免了生成中間臨時(shí) std::string 對(duì)象的內(nèi)存分配開銷。
// 寫入字符容器(生產(chǎn)環(huán)境建議提前 reserve 以保證性能)
std::vector<char> buf;
buf.reserve(32);
std::format_to(std::back_inserter(buf), "num = {}", 314);
// 直接零拷貝輸出到控制臺(tái)
std::format_to(std::ostream_iterator<char>(std::cout), "Hello {}\n", 666);
std::vformat
接收打包后的動(dòng)態(tài)參數(shù)包 std::format_args。該 API 主要用于封裝自定義的可變參數(shù)函數(shù),是構(gòu)建企業(yè)級(jí)通用日志系統(tǒng)或格式化包裝器的核心基石。
void log_message(std::string_view fmt, auto... args) {
// 運(yùn)行時(shí)或編譯期將變參打包
std::format_args pack = std::make_format_args(args...);
// 傳遞給 vformat 執(zhí)行實(shí)際的格式化轉(zhuǎn)換
std::cout << std::vformat(fmt, pack) << '\n';
}
int main() {
log_message("val:{}", 123); // 輸出 "val:123"
}
占位符
通用格式:{[參數(shù)索引][:格式說明符]}
無索引{}
按照參數(shù)傳入的先后順序,從左到右依次填充。
std::format("{} + {} = {}", 1, 2, 3); // "1 + 2 = 3"
數(shù)字索引{N}
從 0 開始指定參數(shù)索引,支持顯式調(diào)換輸出順序以及同一個(gè)參數(shù)的多次復(fù)用。
std::format("{1}-{0}={1}", 5, 9); // "9-5=9"
注意: 在同一個(gè)格式化字符串中,顯式數(shù)字索引 {N} 與自動(dòng)無索引 {} 不能混用,否則會(huì)導(dǎo)致編譯期語(yǔ)法錯(cuò)誤。另外,C++ 標(biāo)準(zhǔn)庫(kù)并未原生支持如 {name} 形式的具名關(guān)鍵參數(shù)(該語(yǔ)法目前僅在開源 fmt 庫(kù)中支持)。
格式說明符
格式說明符緊跟在冒號(hào)后面,各控制分段的相對(duì)順序嚴(yán)格固定:
[填充字符][對(duì)齊][符號(hào)][#][0][寬度][.精度][類型碼]
對(duì)齊 + 填充
<:左對(duì)齊>:右對(duì)齊(非數(shù)值默認(rèn))^:居中對(duì)齊- 填充字符:緊鄰對(duì)齊符號(hào)左側(cè)的任意單個(gè)字符(默認(rèn)是空格)
int n = 123;
std::format("{:*>6}", n); // "***123"
std::format("{:*<6}", n); // "123***"
std::format("{:*^6}", n); // "*123**"
符號(hào)規(guī)則(數(shù)值類型)
+:正數(shù)輸出+,負(fù)數(shù)輸出--:僅負(fù)數(shù)輸出-(標(biāo)準(zhǔn)默認(rèn)行為)- (空格):正數(shù)前置補(bǔ)空格,負(fù)數(shù)輸出
-
std::format("{:+d}", 20); // "+20"
std::format("{: d}", 20); // " 20"
std::format("{: d}", -20); // "-20"
#進(jìn)制前綴開關(guān)
自動(dòng)為整型數(shù)據(jù)啟用前綴標(biāo)識(shí)符:十六進(jìn)制引入 0x/0X,二進(jìn)制引入 0b,八進(jìn)制引入 0。
int val = 15;
std::format("{:#x}", val); // "0xf"
std::format("{:#X}", val); // "0XF"
std::format("{:#b}", val); // "0b1111"
std::format("{:#o}", val); // "017"
0前導(dǎo)補(bǔ)零
在寬度控制前加 0,空余位用字符 0 填充(本質(zhì)上等價(jià)于右對(duì)齊且用 0 填充)。
std::format("{:06d}", 123); // "000123"
最小寬度與動(dòng)態(tài)寬度
- 固定寬度:
{:6d}表示目標(biāo)輸出至少占 6 個(gè)字符位。 - 動(dòng)態(tài)寬度:
{:{w}}運(yùn)行時(shí)通過額外的參數(shù)動(dòng)態(tài)指定寬度。
int w = 8;
std::format("{:*>{}}", 123, w); // "*****123"
精度.prec
- 浮點(diǎn)數(shù):指定小數(shù)點(diǎn)后的保留位數(shù)。
- 字符串:指定最大截取字符數(shù)。
- 動(dòng)態(tài)精度:使用
{:.{prec}}由后續(xù)參數(shù)動(dòng)態(tài)控制。
double pi = 3.1415926;
std::format("{:.3f}", pi); // "3.142"
std::string s = "abcdef";
std::format("{:.3}", s); // "abc"
int prec = 2;
std::format("{:.{}}", pi, prec); // "3.14"
類型碼
不指定時(shí)將自動(dòng)根據(jù)泛型推導(dǎo)
| 分類 | 標(biāo)識(shí) | 說明 |
|---|---|---|
| 整數(shù) | d/o/x/X/b | 十進(jìn)制 / 八進(jìn)制 / 小寫十六進(jìn)制 / 大寫十六進(jìn)制 / 二進(jìn)制 |
| 浮點(diǎn) | f/e/g/a | 定點(diǎn)小數(shù) / 科學(xué)計(jì)數(shù)法 / 自動(dòng)精簡(jiǎn) / 十六進(jìn)制浮點(diǎn) |
| 布爾 | s/d | 輸出 true/false 或文本化的 1/0 |
| 指針 | p | 格式化輸出內(nèi)存物理地址 |
| 字符 | c | 將整型數(shù)值轉(zhuǎn)換為對(duì)應(yīng)的 ASCII 字符輸出 |
bool b = true;
std::format("{}", b); // "true"
std::format("{:d}", b); // "1"
自定義類型格式化
讓自定義類型支持 std::format 的核心在于顯式特化 std::formatter<T> 模板。

標(biāo)準(zhǔn)規(guī)格實(shí)現(xiàn)示例
#include <format>
#include <string>
#include <iostream>
// --- 1. 自定義數(shù)據(jù)類型 ---
struct User {
std::string name;
int age;
};
// --- 2. 在 std 命名空間內(nèi)為 User 類型特化 formatter ---
namespace std {
// 特化版本 1: 處理 char 類型的格式字符串 (e.g., std::format)
template <>
struct formatter<User, char> { // 顯式指定第二個(gè)模板參數(shù)為 char
// 必須定義 char_type,告訴格式化庫(kù)我們處理的是哪種字符
using char_type = char;
// 解析格式說明符的函數(shù)
constexpr auto parse(basic_format_parse_context<char>& ctx) const {
auto it = ctx.begin();
auto end = ctx.end();
// 檢查格式說明符是否為空 (即 {})
if (it == end) {
// 空格式說明符,解析成功,直接返回
return it;
}
// 如果不為空,我們目前不支持任何格式化選項(xiàng),
// 所以期望下一個(gè)字符必須是結(jié)束符 '}'
if (*it != '}') {
throw format_error("Invalid format specifier for User (char).");
}
// 解析成功,返回指向 '}' 的迭代器
return it;
}
// 執(zhí)行實(shí)際格式化的函數(shù)
auto format(const User& u, format_context& ctx) const {
// 使用 format_to 將數(shù)據(jù)寫入輸出迭代器
return format_to(ctx.out(), "User[name={}, age={}]", u.name, u.age);
}
};
// 特化版本 2: 處理 wchar_t 類型的格式字符串 (e.g., std::wformat)
template <>
struct formatter<User, wchar_t> { // 顯式指定第二個(gè)模板參數(shù)為 wchar_t
using char_type = wchar_t;
constexpr auto parse(basic_format_parse_context<wchar_t>& ctx) const {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end) {
return it;
}
if (*it != L'}') { // 注意寬字符的 '}'
throw format_error(L"Invalid format specifier for User (wchar_t).");
}
return it;
}
auto format(const User& u, wformat_context& ctx) const {
// 注意使用 L"..." 寬字符串字面量
return format_to(ctx.out(), L"User[name={}, age={}]", u.name, u.age);
}
};
}
// --- 3. 主函數(shù),測(cè)試我們的自定義格式化 ---
int main() {
User u{"Tom", 25};
// 測(cè)試 1: 使用 char 版本的 std::format
std::string res = std::format("{}", u);
std::cout << "std::format result: " << res << '\n';
// 測(cè)試 2: 使用 wchar_t 版本的 std::wformat
std::wstring wres = std::wformat(L"{}", u);
std::wcout << L"std::wformat result: " << wres << L'\n';
return 0;
}
時(shí)間格式化
C++20 將 <chrono> 時(shí)間庫(kù)與 std::format 進(jìn)行了深度融合??芍苯訉?duì)系統(tǒng)時(shí)間、持續(xù)時(shí)間進(jìn)行高級(jí)格式化:
#include <chrono>
#include <format>
#include <iostream>
int main() {
auto now = std::chrono::system_clock::now();
// 原生支持時(shí)間軸格式化輸出
std::cout << std::format("{:%Y-%m-%d %H:%M:%S}", now);
return 0;
}
// 示例輸出:2026-06-03 23:37:18
時(shí)間格式控制符規(guī)則與傳統(tǒng)標(biāo)準(zhǔn) C 函數(shù) strftime 完全一致(如 %Y 代表四位數(shù)年份,%m 代表月份,%d 代表日期),詳情可參考《C++之時(shí)間日期庫(kù)chrono》。
異常與校驗(yàn)
編譯期嚴(yán)格校驗(yàn)
在代碼中傳入字面量格式串(Literal string)時(shí),現(xiàn)代編譯器會(huì)在編譯階段通過 constexpr 機(jī)制提前運(yùn)行格式串解析器。一旦發(fā)現(xiàn)占位符數(shù)量與參數(shù)列表不匹配,或者類型對(duì)應(yīng)的格式符錯(cuò)誤(例如對(duì) std::string 使用了 {:d}),將在編譯期直接攔截并拋出編譯錯(cuò)誤。
運(yùn)行時(shí)異常攔截
如果是動(dòng)態(tài)組裝、或運(yùn)行時(shí)從配置文件讀入的非常量格式化字符串,編譯器無法做靜態(tài)前置檢查,錯(cuò)誤將被推遲到運(yùn)行期。此時(shí)格式化引擎會(huì)拋出 std::format_error 異常。
#include <iostream>
#include <format>
#include <string>
int main() {
try {
std::string dynamic_fmt = "{:z}"; // 'z' 是針對(duì)整型完全非法的未知格式符
std::format(dynamic_fmt, 1);
} catch (const std::format_error& e) {
// 優(yōu)雅捕獲運(yùn)行期格式化異常,防止服務(wù)崩潰
std::cerr << "Format error caught: " << e.what() << std::endl;
}
}
總結(jié)
命名空間限制
自定義類型的 formatter<T> 特化必須顯式置于 namespace std 空間內(nèi),否則格式化引擎在進(jìn)行 ADL 關(guān)聯(lián)模板特化查找時(shí)會(huì)直接宣告失敗并引發(fā)編譯錯(cuò)誤。
窄整型符號(hào)擴(kuò)展
當(dāng)格式化 char 或 unsigned char 變參并指定 {:d} 打印數(shù)值時(shí),容易因?yàn)殡[式符號(hào)擴(kuò)展導(dǎo)致輸出不符合預(yù)期的負(fù)數(shù)數(shù)值。在嚴(yán)謹(jǐn)?shù)母咝阅軋?chǎng)景下,顯式通過 static_cast<int> 進(jìn)行強(qiáng)轉(zhuǎn)切分。
std::format("{:d}", static_cast<int>(ch));
高頻使用示例
// 1. 固定高位補(bǔ)零的十六進(jìn)制大寫輸出
std::string hex_str = std::format("0x{:04X}", 255); // "0x00FF"
// 2. 浮點(diǎn)數(shù)四舍五入保留兩位小數(shù)
std::string fp_str = std::format("{:.2f}", 2.71828); // "2.72"
// 3. 基于動(dòng)態(tài)寬度的靠右填充邊界對(duì)齊
int padding_width = 10;
std::string align_str = std::format("{:*>{}}", 99, padding_width); // "*******99"
到此這篇關(guān)于C++之類型安全格式化format的文章就介紹到這了,更多相關(guān)C++ 類型格式化format內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ new/delete相關(guān)知識(shí)點(diǎn)詳細(xì)解析
C語(yǔ)言用一堆標(biāo)準(zhǔn)庫(kù)函數(shù)malloc和free在自由存儲(chǔ)區(qū)中分配存儲(chǔ)空間,而C++則用new和delete表達(dá)式實(shí)現(xiàn)相同的功能2013-09-09
C語(yǔ)言模擬實(shí)現(xiàn)atoi函數(shù)的實(shí)例詳解
這篇文章主要介紹了C語(yǔ)言模擬實(shí)現(xiàn)atoi函數(shù)的實(shí)例詳解的相關(guān)資料,atoi函數(shù),主要功能是將一個(gè)字符串轉(zhuǎn)變?yōu)檎麛?shù),這里就實(shí)現(xiàn)這樣的函數(shù),需要的朋友可以參考下2017-08-08
C語(yǔ)言超詳細(xì)講解getchar函數(shù)的使用
C 庫(kù)函數(shù) int getchar(void) 從標(biāo)準(zhǔn)輸入 stdin 獲取一個(gè)字符(一個(gè)無符號(hào)字符)。這等同于 getc 帶有 stdin 作為參數(shù),下面讓我們?cè)敿?xì)來看看2022-05-05
C語(yǔ)言?深入理解動(dòng)態(tài)規(guī)劃之計(jì)數(shù)類DP
動(dòng)態(tài)規(guī)劃可謂是大名鼎鼎,筆試面試中的高頻考點(diǎn),也是重點(diǎn)難點(diǎn),動(dòng)態(tài)規(guī)劃類型題目靈活多變,難度系數(shù)也相對(duì)較高,往往我們做不好動(dòng)態(tài)規(guī)劃的題目就會(huì)與心儀的offer失之交臂,本篇文章我們就一起來研究一下動(dòng)態(tài)規(guī)劃的計(jì)數(shù)類DP2022-04-04

