printf 类型不安全,iostream 又冗又慢;C++20 的 std::format 把 {fmt} 收进标准库,Python 风格占位符 + 编译期类型检查。同一时代还有 constexpr 进阶constevalstd::span——把格式化与计算都推向「更安全、更编译期」。

这一篇对应三个 demo:format_democonstexpr_demospan_demo

这是「现代 C++ 实战」系列的第 9 篇。建议先读 第 08 篇:错误处理策略

一、格式化三时代

方式 类型安全 语法 性能
printf ❌ 格式与参数不匹配 → UB C 风格 %d
iostream cout << x << y 冗长 较慢
std::format "{} + {} = {}" 简洁 接近 printf

C++23 再加 std::print / std::println——直接输出,不必先拼 stringcout

编译器要求:GCC 13+ / Clang 17+ 支持 std::formatstd::print 需 GCC 14+ / Clang 18+。

二、std::format 基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <format>
#include <string>

std::string s = std::format("Hello, {}!", "world");
std::cout << std::format("{0} + {0} = {1}\n", 21, 42);

// 对齐与填充
std::format("[{:>10}]", "hi"); // 右对齐
std::format("[{:*^10}]", "hi"); // 居中,* 填充

// 数值格式
std::format("{:x}", 255); // ff
std::format("{:.2f}", 3.14159); // 3.14
std::format("{:#b}", 5); // 0b101
占位符 含义
{} 自动格式化
{0}, {1} 按位置索引
{:d} / {:f} / {:s} 整数 / 浮点 / 字符串
{:>10} / {:<10} / {:^10} 右 / 左 / 居中
{:.2f} 精度

自定义类型:实现 formatter<T> 特化,即可用 {} 格式化你的 struct——demo 里有完整示例。

三、std::print(C++23)

1
2
3
4
#include <print>

std::print("value = {}\n", 42);
std::println("done"); // 自动换行

等价于 std::format + 写入 stdout,代码更短。不支持时可用 {fmt} 库或继续 format + cout

四、constexpr 演进

核心思想:参数为编译期常量时,函数在编译期求值——零运行时开销。

标准 constexpr 能力
C++11 函数体只能有一条 return
C++14 允许循环、局部变量、分支
C++17 if constexpr、lambda 可 constexpr
C++20 更多标准库算法/容器可 constexpr
1
2
3
4
5
6
7
8
9
10
11
constexpr int factorial(int n) {
int r = 1;
for (int i = 2; i <= n; ++i) r *= i;
return r;
}

constexpr int n = factorial(5); // 编译期:120
static_assert(factorial(5) == 120);

int x = read();
int y = factorial(x); // 运行期:x 非常量

用途:数组大小、模板非类型参数、static_assert 校验、查表预计算。

五、constevalconstinit(C++20)

consteval:必须在编译期求值

1
2
3
4
consteval int sq(int n) { return n * n; }

constexpr int a = sq(10); // OK
// int b = sq(runtime_val); // 错误:consteval 禁止运行期调用

constexpr 的区别:constexpr「可以」编译期求值;consteval「必须」编译期求值。

constinit:静态变量编译期初始化

1
constinit int global_counter = compute_at_compile_time();

解决静态初始化顺序问题——保证在动态初始化之前已有确定值(仍可能运行期赋值,但首次初始化在编译期完成)。

if consteval(C++23)

1
2
3
4
5
6
7
constexpr int f(int n) {
if consteval {
return n * n; // 编译期路径
} else {
return expensive(n); // 运行期路径
}
}

同一函数内区分编译期 / 运行期实现。

六、std::span:连续内存的非拥有视图

类似 string_view 之于 stringspan 不拥有内存,只持有指针 + 长度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <span>
#include <vector>
#include <array>

void sum_print(std::span<const int> data) {
long s = 0;
for (int x : data) s += x;
std::cout << s << '\n';
}

int arr[] = {1, 2, 3};
std::array<int, 3> a = {1, 2, 3};
std::vector<int> v = {1, 2, 3};

sum_print(arr); // 同一函数
sum_print(a);
sum_print(v);
类型 大小
span<T> 动态长度,指针 + size
span<T, N> 固定长度 N,仅指针

替代裸指针 + length 参数,边界更清晰;与 Ranges 配合良好(第 10 篇)。

注意:span 生命周期不能长于底层容器——不延长对象寿命。

七、三个 demo 怎么跑

1
2
3
cd ref/cpp_demo/basics/format_demo && ./build.sh --run
cd ref/cpp_demo/basics/constexpr_demo && ./build.sh --run
cd ref/cpp_demo/basics/span_demo && ./build.sh --run

CMake 建议 -std=c++20;需要 C++23 特性时设 -std=c++23

八、小结

特性 要点
std::format 类型安全、 {} 占位符,替代 printf/iostream
std::print C++23 一行输出
constexpr 编译期计算,C++14 起可写循环
consteval 强制编译期
constinit 静态变量编译期首初始化
span 非拥有连续内存视图

现代 C++ 实战系列第 9 篇完。下一篇 Ranges 与函数式风格——filter | transform | take 管道。

系列导航

篇号 标题 状态
08 错误处理策略
09 C++20 格式化与编译期计算(本篇)
10 Ranges 与函数式风格 下一篇

完整大纲见工作区 docs/CPP_SERIES_OUTLINE.md