现代 C++ 实战(09):C++20 格式化与编译期计算
printf 类型不安全,iostream 又冗又慢;C++20 的 std::format 把 {fmt} 收进标准库,Python 风格占位符 + 编译期类型检查。同一时代还有 constexpr 进阶、consteval、std::span——把格式化与计算都推向「更安全、更编译期」。
这一篇对应三个 demo:format_demo、constexpr_demo、span_demo。
这是「现代 C++ 实战」系列的第 9 篇。建议先读 第 08 篇:错误处理策略。
一、格式化三时代
| 方式 | 类型安全 | 语法 | 性能 |
|---|---|---|---|
printf |
❌ 格式与参数不匹配 → UB | C 风格 %d |
快 |
iostream |
✅ | cout << x << y 冗长 |
较慢 |
std::format |
✅ | "{} + {} = {}" 简洁 |
接近 printf |
C++23 再加 std::print / std::println——直接输出,不必先拼 string 再 cout。
编译器要求:GCC 13+ / Clang 17+ 支持 std::format;std::print 需 GCC 14+ / Clang 18+。demo 用 __has_include(<format>) 定义 HAS_STD_FORMAT,用 __has_include(<print>) && __cplusplus >= 202302L 定义 HAS_STD_PRINT,不支持时回退到 printf / 提示文字。
二、std::format 基础
1 |
|
| 占位符 | 含义 |
|---|---|
{} |
自动格式化 |
{0}, {1} |
按位置索引 |
{:d} / {:f} / {:s} |
整数 / 浮点 / 字符串 |
{:>10} / {:<10} / {:^10} |
右 / 左 / 居中 |
{:.2f} |
精度 |
自定义类型:实现 formatter<T> 特化,即可用 {} 格式化你的 struct。demo 为 Point 实现了 parse() + format(),支持 {} / {:f} / {:e} 三种写法:
1 | struct Point { |
三、std::print(C++23)
1 |
|
等价于 std::format + 写入 stdout,代码更短;std::println(stderr, ...) 还能写到指定文件流。不支持时可用 {fmt} 库或继续 format + cout。
format_demo 的 CMake 是 set(CMAKE_CXX_STANDARD 23),默认构建时启动信息会显示 [std::print 支持: 是],第 4 段直接用 std::print / std::println 输出(macOS 本机与 Docker 中的 GCC 均已验证);编译器不支持 <print> 时才回退到提示文字和等效写法对比。
四、constexpr 演进
核心思想:参数为编译期常量时,函数在编译期求值——零运行时开销。
| 标准 | constexpr 能力 |
|---|---|
| C++11 | 函数体只能有一条 return |
| C++14 | 允许循环、局部变量、分支 |
| C++17 | if constexpr、lambda 可 constexpr |
| C++20 | 更多标准库算法/容器可 constexpr |
1 | // C++11 风格:只能有一条 return |
用途:数组大小、模板非类型参数、static_assert 校验、查表预计算。demo 第 3 段还演示了 C++20 的 constexpr 容器操作:create_squares() 在编译期生成 std::array<int, 5>,find_max() / reverse_array() 同样在编译期完成,并用 static_assert 校验结果。
五、consteval 与 constinit(C++20)
consteval:必须在编译期求值
1 | consteval int compile_time_only(int n) { |
与 constexpr 的区别:constexpr「可以」编译期求值;consteval「必须」编译期求值。
constinit:静态变量编译期初始化
1 | constinit int global_counter = 100; // 编译期初始化,之后仍可修改 |
解决静态初始化顺序问题——保证在动态初始化之前已有确定值(仍可能运行期赋值,但首次初始化在编译期完成)。
if consteval(C++23)
1 | constexpr int smart_function(int n) { |
同一函数内区分编译期 / 运行期实现(demo 故意让两条路径结果不同,便于观察)。demo 在 __cplusplus < 202302L 时改用 C++20 的 std::is_constant_evaluated() 实现同名函数作为回退;它的 CMake 统一 set(CMAKE_CXX_STANDARD 23),所以 macOS 的 Apple Clang 和 Docker 中的 GCC 都走 if consteval 分支,输出相同:
1 | smart_function(3) [编译期] = 27 |
六、std::span:连续内存的非拥有视图
类似 string_view 之于 string,span 不拥有内存,只持有指针 + 长度:
1 |
|
| 类型 | 大小 |
|---|---|
span<T> |
动态长度,指针 + size |
span<T, N> |
固定长度 N,仅指针 |
替代裸指针 + length 参数,边界更清晰;与 Ranges 配合良好(第 10 篇)。demo 里对比了 average_new(std::span<const int>) 与传统「指针 + 长度」写法,用 double_values(std::span<int>) 演示可写视图,并演示 first(n) / last(n) / subspan(offset, count) 三个 O(1) 子视图操作。
注意:span 生命周期不能长于底层容器——不延长对象寿命。
七、三个 demo 怎么跑
1 | cd ref/cpp_demo/basics/format_demo && ./build.sh --run |
format_demo 和 constexpr_demo 的 CMake 是 set(CMAKE_CXX_STANDARD 23),span_demo 仍是 set(CMAKE_CXX_STANDARD 20)。
八、小结
| 特性 | 要点 |
|---|---|
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 与函数式风格 | ✅ |











