c++ std::print
概述
C++23 引入了 std::print 和 std::println,这是现代 C++ 对格式化输出的重大改进。相比传统的 printf,这些新函数提供了类型安全、性能优异且易于使用的格式化输出方案。
基本用法
简单输出
#include <print>
int main() {
// 基本输出
std::print("Hello, World!\n");
// 带换行的输出
std::println("Hello, World!");
return 0;
}格式化输出
#include <print>
#include <string>
int main() {
int age = 25;
std::string name = "Alice";
// 格式化输出
std::print("Name: {}, Age: {}\n", name, age);
// 带换行的格式化输出
std::println("Name: {}, Age: {}", name, age);
return 0;
}格式化说明符
位置参数
std::print("{0} {1} {0}\n", "hello", "world"); // 输出: hello world hello
std::print("{1} {0}\n", "world", "hello"); // 输出: hello world对齐和填充
// 右对齐(默认)
std::print("{:>10}\n", "test"); // 输出:[ test]
// 左对齐
std::print("{:<10}\n", "test"); // 输出:[test ]
// 居中对齐
std::print("{:^10}\n", "test"); // 输出:[ test ]
// 使用指定字符填充
std::print("{:0>10}\n", 42); // 输出: [0000000042]
std::print("{:*^10}\n", "test"); // 输出: [***test***]数值格式化
进制表示
int num = 255;
// 十进制(默认)
std::print("{}\n", num); // 输出: 255
// 十六进制
std::print("{x}\n", num); // 输出: ff
std::print("{X}\n", num); // 输出: FF
// 八进制
std::print("{o}\n", num); // 输出: 377
// 二进制
std::print("{b}\n", num); // 输出: 11111111
// 带前缀的进制表示
std::print("{:#x}\n", num); // 输出: 0xff
std::print("{:#o}\n", num); // 输出: 0377
std::print("{:#b}\n", num); // 输出: 0b11111111浮点数精度
double pi = 3.14159265359;
// 默认精度
std::print("{}\n", pi); // 输出: 3.14159
// 指定精度
std::print("{:.2}\n", pi); // 输出: 3.1
std::print("{:.4}\n", pi); // 输出: 3.1416
// 固定小数位数
std::print("{:.2f}\n", pi); // 输出: 3.14
// 科学技术法
std::print("{:.2e}\n", pi); // 输出: 3.14e+00符号和空格
int pos = 42;
int neg = -42;
int zero = 0;
// 始终显示符号
std::print("{:+} {:+} {:+}\n", pos, neg, zero); // 输出: +42 -42 +0
// 空格表示正数
std::print("{: } {: } {: }\n", pos, neg, zero); // 输出: 42 -42 0
// 负数显示负号(默认)
std::print("{} {} {}\n", pos, neg, zero); // 输出: 42 -42 0字符串格式化
字符串截断和填充
std::string long_text = "This is a very long text";
// 截断
std::print("{:.10}\n", long_text); // 输出: This is a v
// 指定宽度截断
std::print("{:.10}\n", long_text); // 输出: This is a v
// 填充和对齐
std::print("{:>20}\n", long_text); // 输出: This is a very lon高级格式化
百分号和特殊字符
// 转义大括号
std::print("{{}}", "hello"); // 输出: {hello}
// 百分号
std::print("{:.1%}\n", 0.75); // 输出: 75.0%分组和千位分隔符
#include <locale>
// 千位分隔符(需要locale)
auto locale = std::locale("");
std::print(std::format(locale, "{:L}\n", 1000000)); // 输出: 1,000,000日期和时间
#include <chrono>
#include <ctime>
auto now = std::chrono::system_clock::now();
std::time_t time = std::chrono::system_clock::to_time_t(now);
// 格式化时间(注意:这是概念性示例,实际可能需要额外处理)
std::print("Current time: {}\n", std::ctime(&time));类型安全特性
自动类型推导
#include <print>
int main() {
// C++23 自动推导类型,无需手动指定格式说明符
int i = 42;
double d = 3.14;
std::string s = "hello";
std::print("{} {} {}\n", i, d, s); // 类型安全!
// 传统 printf 需要这样写:
// printf("%d %f %s\n", i, d, s.c_str());
return 0;
}自定义类型支持
#include <print>
#include <vector>
// 为自定义类型提供格式化支持
struct Point {
int x, y;
};
template<>
struct std::formatter<Point> : std::formatter<std::string> {
auto format(const Point& p, std::format_context& ctx) const {
return std::formatter<std::string>::format(
std::format("({}, {})", p.x, p.y), ctx);
}
};
int main() {
Point p{10, 20};
std::print("Point: {}\n", p); // 输出: Point: (10, 20)
return 0;
}与 printf 的性能对比
| 特性 | std::print (C++23) | printf (C) |
|---|---|---|
| 格式化方式 | 基于 std::format,编译期类型检查 | 运行时解析格式字符串 |
| 中间缓冲 | 可直接写入 C 流 buffer,避免额外拷贝 | 通常涉及内部缓冲和解析 |
| 并发输出 | 保证线程安全且输出不交错 | 输出可能交错,需手动加锁 |
| 性能优化 | 提案 P3107 实现中,比 printf 快约 20% | 性能较差,尤其在复杂格式时 |
性能优势来源(std::print):
- 直接格式化到 C 流缓冲区,避免中间 std::string 构造和拷贝;
- 避免重复解析格式字符串,利用 std::format 的编译期优化;
- 线程安全输出,无需额外同步开销即可避免交错输出 。
实际应用示例
调试输出
#include <print>
class Logger {
public:
enum Level { DEBUG, INFO, WARNING, ERROR };
static void log(Level level, const std::string& message) {
std::print("[{}] {}\n", level_to_string(level), message);
}
private:
static std::string level_to_string(Level level) {
switch (level) {
case DEBUG: return "DEBUG";
case INFO: return "INFO";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
};
// 使用示例
Logger::log(Logger::INFO, "Application started");
Logger::log(Logger::ERROR, "Failed to connect to database");数据报告生成
#include <print>
#include <vector>
#include <algorithm>
#include <numeric>
void generate_report(const std::vector<double>& data) {
if (data.empty()) {
std::print("No data available\n");
return;
}
auto min_val = *std::min_element(data.begin(), data.end());
auto max_val = *std::max_element(data.begin(), data.end());
auto avg = std::accumulate(data.begin(), data.end(), 0.0) / data.size();
std::print("Data Report:\n");
std::print(" Count: {}\n", data.size());
std::print(" Min: {:.2f}\n", min_val);
std::print(" Max: {:.2f}\n", max_val);
std::print(" Avg: {:.2f}\n", avg);
}JSON 样式输出
#include <print>
#include <map>
#include <string>
void print_json_style(const std::map<std::string, int>& data) {
std::print("{{\n");
bool first = true;
for (const auto& [key, value] : data) {
if (!first) std::print(",\n");
std::print(" \"{}\": {}", key, value);
first = false;
}
std::print("\n}}\n");
}最佳实践
1. 优先使用 std::println 而不是 std::print
// 推荐
std::println("Processing item {}: {}", id, name);
// 不推荐(除非需要特定换行控制)
std::print("Processing item {}: {}\n", id, name);2. 利用类型安全,避免手动格式说明符
// 推荐:类型安全
std::print("Value: {}\n", my_value);
// 避免:容易出错
std::print("Value: %d\n", my_value); // 如果 my_value 不是 int 会出错3. 使用命名参数提高可读性
struct Config {
int port;
std::string host;
bool ssl;
};
void print_config(const Config& cfg) {
std::print("Config {{\n");
std::print(" host: {}\n", cfg.host);
std::print(" port: {}\n", cfg.port);
std::print(" ssl: {}\n", cfg.ssl ? "true" : "false");
std::print("}}\n");
}4. 自定义格式化器提升调试效率
#include <print>
// 为常用调试类型提供格式化器
template<typename T>
struct std::formatter<std::vector<T>> : std::formatter<std::string> {
auto format(const std::vector<T>& vec, std::format_context& ctx) const {
std::string result = "[";
for (size_t i = 0; i < vec.size(); ++i) {
if (i > 0) result += ", ";
result += std::format("{}", vec[i]);
}
result += "]";
return std::formatter<std::string>::format(result, ctx);
}
};
// 使用示例
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::print("Numbers: {}\n", numbers); // 输出: Numbers: [1, 2, 3, 4, 5]与旧代码的集成
逐步迁移策略
#include <print>
#include <cstdio>
void legacy_function() {
// 旧代码仍然可以使用
printf("Old style: %d\n", 42);
}
void modern_function() {
// 新代码使用现代风格
std::print("Modern style: {}\n", 42);
}
void mixed_usage() {
// 可以在同一个文件中混合使用
printf("Legacy: %s\n", "hello");
std::print("Modern: {}\n", "world");
}总结
C++23 的 std::print 代表了 C++ 标准库在用户体验方面的重大进步:
- 类型安全: 编译时检查,避免运行时错误
- 性能优异: 接近
printf的性能,同时提供更好的类型安全 - 易于使用: 现代格式化语法,无需记忆复杂的格式说明符
- 可扩展性: 支持自定义类型的格式化
- 向后兼容: 可以与现有代码无缝集成
这些特性使得 std::print 成为现代 C++ 开发中格式化输出的首选方案。建议在新项目中优先使用 std::print 和 std::println,并在适当时候将现有代码迁移到新的格式化方案。
记住 Linus 的教诲:“好品味意味着消除特殊情况”,std::print 正是这一理念的完美体现 - 它将原本复杂的格式化输出变成了简单、直观且类型安全的操作。





