第 18.17 節
std::format / std::print
0瀏覽次數0訪問次數--跳出率--平均停留
本節解決什麼問題
C++ 中輸出格式化的字符串一直比較麻煩:
printf:快但類型不安全,格式字符串錯誤會導致崩潰。std::cout:類型安全但寫起來冗長,格式控制不方便。std::stringstream:功能全但非常囉嗦。
C++20/23 引入了 std::format(格式化字符串)和 std::print(直接輸出),結合了 Python 風格的簡潔和 C++ 的類型安全 + 高性能。
這個特性是什麼
std::format(C++20):類似 Python 的f"{name}: {score}",返回格式化後的std::string。std::print(C++23):類似 Python 的print(),直接把格式化結果輸出到 stdout。
底層基於 {fmt} 庫,性能極高——接近甚至超過 printf。
C++ 標準版本
std::format:C++20std::print:C++23
需要的頭文件
#include <format> // for std::format (C++20)
#include <print> // for std::print, std::println (C++23)
基本語法
// std::format:返回 string
std::string s = std::format("Hello, {}!", name);
std::string s2 = std::format("{0} + {1} = {2}", a, b, a + b);
// std::print:直接输出(不自动换行)
std::print("x = {}, y = {}", x, y);
// std::println:输出并换行
std::println("Hello, {}!", name);
// 指定输出目标:stdout / stderr 是 FILE*
std::println(stdout, "normal message");
std::println(stderr, "error message");
// 格式控制
std::format("{:.2f}", 3.14159); // "3.14" —— 保留 2 位小数
std::format("{:>10}", 42); // " 42" —— 右对齐,宽度 10
std::format("{:<10}", 42); // "42 " —— 左对齐
std::format("{:^10}", 42); // " 42 " —— 居中
std::format("{:#x}", 255); // "0xff" —— 十六进制带前缀
std::format("{:04d}", 7); // "0007" —— 前导零填充
輸出方式對比
| 方式 | 性能 | 類型安全 | 簡潔 | 出現版本 |
|---|---|---|---|---|
printf | 快 | ❌ | ✅ | C |
std::cout | 慢 | ✅ | ❌ | C++98 |
std::format | 很快 | ✅ | ✅ | C++20 |
std::print | 很快 | ✅ | ✅ | C++23 |
std::print 輸出到哪裏
C++23 標準庫裏的 std::print / std::println 常用兩類寫法:
| 寫法 | 輸出目標 |
|---|---|
std::println("x = {}", x) | 默認輸出到標準輸出,也就是 stdout |
std::println(stdout, "x = {}", x) | 明確輸出到 stdout |
std::println(stderr, "error = {}", code) | 輸出到標準錯誤 stderr |
這裏的 stdout 和 stderr 是 C 標準庫裏的 FILE*,需要包含 <cstdio>。
注意:標準 C++23 的 std::println 第一個參數不是 std::cout。std::cout 是 C++ 的 std::ostream 對象,屬於 <iostream>。你可能在 {fmt} 庫或某些擴展裏見過類似 fmt::print(std::cout, "...") 的寫法,那和標準庫 std::println 不是同一個接口。
示例代碼
示例 1:std::format 基本用法
#include <iostream>
#include <format>
#include <string>
int main()
{
std::string name = "Alice";
int age = 25;
double score = 92.5;
// 基本占位符
std::string s1 = std::format("Name: {}, Age: {}, Score: {}", name, age, score);
std::cout << s1 << "\n";
// 指定顺序
std::string s2 = std::format("{1} is {0} years old", age, name);
std::cout << s2 << "\n";
// 保留 2 位小数
std::string s3 = std::format("Score: {:.2f}", score);
std::cout << s3 << "\n";
return 0;
}
運行結果:
Name: Alice, Age: 25, Score: 92.5
Alice is 25 years old
Score: 92.50
示例 2:在示例 1 基礎上,格式控制和對齊
#include <iostream>
#include <format>
int main()
{
// 右对齐,宽度 10
std::cout << std::format("[{:>10}]\n", 42);
// 左对齐,宽度 10
std::cout << std::format("[{:<10}]\n", 42);
// 居中,宽度 10
std::cout << std::format("[{:^10}]\n", 42);
// 前导零
std::cout << std::format("{:05d}\n", 7);
// 十六进制
std::cout << std::format("hex = {:#x}, oct = {:#o}\n", 255, 255);
// 打印一个简单的表格
std::cout << std::format("{:<10} {:>5} {:>7}\n", "Name", "Age", "Score");
std::cout << std::format("{:<10} {:>5} {:>7.1f}\n", "Alice", 25, 92.5);
std::cout << std::format("{:<10} {:>5} {:>7.1f}\n", "Bob", 22, 88.0);
std::cout << std::format("{:<10} {:>5} {:>7.1f}\n", "Charlie", 24, 78.5);
return 0;
}
運行結果:
[ 42]
[42 ]
[ 42 ]
00007
hex = 0xff, oct = 0377
Name Age Score
Alice 25 92.5
Bob 22 88.0
Charlie 24 78.5
示例 3:std::print 直接輸出(C++23)
#include <cstdio>
#include <print>
#include <string>
int main()
{
std::string name = "World";
int value = 42;
// 直接输出,不换行
std::print("Hello, ");
std::print("{}", name);
std::print("!\n");
// 输出到 stdout 并换行
std::println("The answer is {}", value);
// 第一个参数也可以显式写 stdout
std::println(stdout, "stdout: {}", name);
// 带格式输出
std::println("pi = {:.3f}", 3.1415926);
// 输出到 stderr
std::println(stderr, "Error: something went wrong!");
return 0;
}
運行結果:
Hello, World!
The answer is 42
stdout: World
pi = 3.142
Error: something went wrong!
運行結果
見上方每個示例的"運行結果"。
示例中的關鍵語法解釋
| 示例 | 講了什麼 | 新出現的語法 | 為什麼這樣寫 | 注意事項 |
|---|---|---|---|---|
| 示例 1 | format 基本用法 | std::format("...", arg1, arg2)、{} 佔位符 | Python 風格 + C++ 類型安全 | 參數個數要和 {} 個數匹配 |
| 示例 2 | 格式控制和表格 | {:>10}、{:.2f}、{:#x} | 內置格式説明符比 printf 更豐富 | 格式控制符在 {} 內,: 後面 |
| 示例 3 | print/println 直接輸出 | std::println()、std::print()、stdout、stderr | 不需要 std::cout,直接格式化輸出 | 標準接口接收的是 FILE*,不是 std::cout |
常見錯誤
錯誤 1:格式説明符 : 前忘了寫索引
std::format("{.2f}", 3.14); // ❌ 缺少 : 前的占位符
正確做法:std::format("{:.2f}", 3.14); 或 std::format("{}", 3.14);
錯誤 2:參數個數不匹配
std::format("{}, {}", 1); // ❌ 有 2 个占位符,只有 1 个参数
編譯時報錯(這是 format 比 printf 安全的地方)。
錯誤 3:用 format 輸出的字符串中包含 { 或 }
std::format("Set = {1, 2, 3}"); // ❌ {1, 2, 3} 会被误解为格式说明
正確做法:用 {{ 表示字面量 {,}} 表示字面量 }:
std::format("Set = {{1, 2, 3}}"); // 输出:Set = {1, 2, 3}
使用建議
- C++20 項目用
std::format代替stringstream和sprintf:代碼簡潔、類型安全、性能高。 - C++23 項目用
std::print/std::println簡化格式化輸出:默認輸出到stdout,也可以傳stderr。 - 格式説明符和 Python 很像:如果你熟悉 Python 的格式化語法,上手很快。
- 如果你的編譯器還不支持 C++20/23,可以使用
{fmt}庫(https://github.com/fmtlib/fmt),它是标准化的基础。
小結
std::format("{} + {} = {}", a, b, c)返回格式化字符串。std::print("...")/std::println("...")直接輸出到stdout。std::println(stderr, "...")可以輸出到標準錯誤。- 類型安全 + 格式強大 + 性能高:結合了 printf 的速度和 cout 的安全性。
- 格式控制:
{:.2f}小數位數、{:>10}對齊、{:#x}進制、{:04d}補零。