C++23标准正式引入了std::print工具,它简化了格式化输出的流程,同时支持更灵活的终端样式控制,并且在多线程场景下的输出处理也有对应的适配方案。和传统的std::cout相比,std::print的格式化语法更清晰,执行效率也有一定提升,逐渐成为C++开发者输出操作的首选工具。

std::print基础用法回顾
std::print定义在<print>头文件中,支持类似Python的格式化字符串语法,不需要手动拼接输出内容。基础的字符串输出示例如下:
#include <print>
#include <string>
int main() {
std::string name = "C++23";
int version = 23;
// 基础格式化输出
std::print("欢迎使用{},版本号是{}n", name, version);
return 0;
}
std::print的高级颜色控制实现
std::print本身不直接提供颜色控制接口,但可以通过嵌入ANSI转义序列来实现终端的颜色、样式调整。ANSI转义序列以<ESC>[开头,后面跟随样式参数,最后以字母结尾标识样式类型。
常用ANSI转义序列规则
常见的样式参数和对应的效果如下:
- 前景色(文字颜色):30-37为基础颜色,90-97为高亮基础颜色
- 背景色:40-47为基础背景色,100-107为高亮背景色
- 样式控制:0为重置所有样式,1为加粗,3为斜体,4为下划线
通过std::print输出带颜色的内容时,只需要把对应的转义序列嵌入到格式化字符串中即可,示例代码如下:
#include <print>
int main() {
// 输出红色加粗文字
std::print(" 33[1;31m这是红色加粗的文字 33[0mn");
// 输出绿色背景的白色文字
std::print(" 33[47;32m白字绿底内容 33[0mn");
// 输出带下划线的蓝色文字
std::print(" 33[4;34m带下划线的蓝色文字 33[0mn");
return 0;
}
封装颜色控制工具函数
为了避免每次手动拼接转义序列,我们可以封装通用的颜色控制函数,提升代码可读性和复用性:
#include <print>
#include <string>
// 前景色枚举
enum class ForeColor {
RED = 31,
GREEN = 32,
YELLOW = 33,
BLUE = 34,
MAGENTA = 35,
CYAN = 36,
WHITE = 37
};
// 背景色枚举
enum class BackColor {
RED = 41,
GREEN = 42,
YELLOW = 43,
BLUE = 44,
MAGENTA = 45,
CYAN = 46,
WHITE = 47
};
// 样式枚举
enum class TextStyle {
RESET = 0,
BOLD = 1,
ITALIC = 3,
UNDERLINE = 4
};
// 生成带样式的字符串
std::string color_text(const std::string& text, ForeColor fore, BackColor back = (BackColor)0, TextStyle style = TextStyle::RESET) {
// 0表示不使用背景色
if (back == (BackColor)0) {
return " 33[" + std::to_string((int)style) + ";" + std::to_string((int)fore) + "m" + text + " 33[0m";
}
return " 33[" + std::to_string((int)style) + ";" + std::to_string((int)fore) + ";" + std::to_string((int)back) + "m" + text + " 33[0m";
}
int main() {
std::print("{}", color_text("红色提示信息", ForeColor::RED, (BackColor)0, TextStyle::BOLD));
std::print("n");
std::print("{}", color_text("蓝底白字内容", ForeColor::WHITE, BackColor::BLUE, TextStyle::UNDERLINE));
std::print("n");
return 0;
}
多线程输出原子性问题与std::print的机制
很多开发者认为std::print比std::cout更安全,实际上在没有额外同步措施的情况下,std::print在多线程下的输出同样不具备原子性。多个线程同时调用std::print输出内容时,不同线程的输出片段可能会交错,导致最终输出内容混乱。
多线程输出错乱示例
下面的代码启动两个线程同时输出内容,运行后会看到输出内容交错:
#include <print>
#include <thread>
#include <chrono>
void print_task(const std::string& prefix) {
for (int i = 0; i < 3; ++i) {
std::print("{}: 第{}次输出内容n", prefix, i);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main() {
std::thread t1(print_task, "线程A");
std::thread t2(print_task, "线程B");
t1.join();
t2.join();
return 0;
}
运行上述代码后,可能会出现类似"线程A: 第0次输出内容n线程B: 第0次输出内容n"的交错情况,不符合预期的输出逻辑。
多线程下std::print输出原子性保障方案
要保证多线程下std::print的输出原子性,核心思路是让同一时刻只有一个线程能执行完整的输出操作,常用的方案有以下几种。
使用互斥锁同步
通过std::mutex对输出操作加锁,是最直接的保障方式,示例代码如下:
#include <print>
#include <thread>
#include <mutex>
#include <chrono>
std::mutex print_mutex;
void safe_print_task(const std::string& prefix) {
for (int i = 0; i < 3; ++i) {
// 加锁保证输出原子性
std::lock_guard<std::mutex> lock(print_mutex);
std::print("{}: 第{}次输出内容n", prefix, i);
// 锁自动释放
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
int main() {
std::thread t1(safe_print_task, "线程A");
std::thread t2(safe_print_task, "线程B");
t1.join();
t2.join();
return 0;
}
封装原子输出函数
可以把加锁逻辑封装到统一的输出函数中,避免每个调用处都写加锁代码:
#include <print>
#include <thread>
#include <mutex>
#include <string>
#include <chrono>
std::mutex print_mutex;
// 原子输出函数
template <typename... Args>
void atomic_print(const std::string& fmt, Args&&... args) {
std::lock_guard<std::mutex> lock(print_mutex);
std::print(fmt, std::forward<Args>(args)...);
}
void task(const std::string& name) {
for (int i = 0; i < 3; ++i) {
atomic_print("{}: 执行第{}次操作n", name, i);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main() {
std::thread t1(task, "任务1");
std::thread t2(task, "任务2");
t1.join();
t2.join();
return 0;
}
使用std::osyncstream(C++23新增)
C++23同时新增了<syncstream>头文件,std::osyncstream可以自动保证输出操作的原子性,不需要手动加锁,使用方式如下:
#include <print>
#include <thread>
#include <syncstream>
#include <chrono>
void sync_print_task(const std::string& prefix) {
for (int i = 0; i < 3; ++i) {
// osyncstream自动保障输出原子性
std::osyncstream(std::cout) << prefix << ": 第" << i << "次输出n";
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main() {
std::thread t1(sync_print_task, "线程A");
std::thread t2(sync_print_task, "线程B");
t1.join();
t2.join();
return 0;
}
如果需要结合std::print的格式化能力,可以先把内容格式化到字符串,再用std::osyncstream输出,同样能保证原子性。
注意事项
- ANSI转义序列仅在支持该标准的终端生效,部分老旧终端可能无法显示颜色效果
- 使用互斥锁保障原子性时,要注意避免死锁,尽量不要在锁内执行耗时操作
- std::osyncstream的性能比手动加锁略低,在对性能要求极高的场景下需要权衡选择
std::printC++23颜色控制多线程输出原子性保障修改时间:2026-06-14 01:52:06