第 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++20
  • std::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" —— 前导零填充

輸出方式對比

方式性能類型安全簡潔出現版本
printfC
std::coutC++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

這裏的 stdoutstderr 是 C 標準庫裏的 FILE*,需要包含 <cstdio>

注意:標準 C++23 的 std::println 第一個參數不是 std::coutstd::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!

運行結果

見上方每個示例的"運行結果"。

示例中的關鍵語法解釋

示例講了什麼新出現的語法爲什麼這樣寫注意事項
示例 1format 基本用法std::format("...", arg1, arg2){} 佔位符Python 風格 + C++ 類型安全參數個數要和 {} 個數匹配
示例 2格式控制和表格{:>10}{:.2f}{:#x}內置格式說明符比 printf 更豐富格式控制符在 {} 內,: 後面
示例 3print/println 直接輸出std::println()std::print()stdoutstderr不需要 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}

使用建議

  1. C++20 項目用 std::format 代替 stringstreamsprintf:代碼簡潔、類型安全、性能高。
  2. C++23 項目用 std::print/std::println 簡化格式化輸出:默認輸出到 stdout,也可以傳 stderr
  3. 格式說明符和 Python 很像:如果你熟悉 Python 的格式化語法,上手很快。
  4. 如果你的編譯器還不支持 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} 補零。
音乐页