概述

C++23 引入了 std::printstd::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++ 标准库在用户体验方面的重大进步:

  1. 类型安全: 编译时检查,避免运行时错误
  2. 性能优异: 接近 printf 的性能,同时提供更好的类型安全
  3. 易于使用: 现代格式化语法,无需记忆复杂的格式说明符
  4. 可扩展性: 支持自定义类型的格式化
  5. 向后兼容: 可以与现有代码无缝集成

这些特性使得 std::print 成为现代 C++ 开发中格式化输出的首选方案。建议在新项目中优先使用 std::printstd::println,并在适当时候将现有代码迁移到新的格式化方案。

记住 Linus 的教诲:“好品味意味着消除特殊情况”std::print 正是这一理念的完美体现 - 它将原本复杂的格式化输出变成了简单、直观且类型安全的操作。