基于 C++11 标准实现 Python 风格的 print 函数,打印任意数量任意类型
一个适配标准库容器及其嵌套支持任意类型、任意数量输入并且极简新增自定义类型适配的 print目录先看效果Print: 起源强迫症要做就做个各项目通用的 print 函数第一步解决任意数量参数——可变参数模板第二步类型分流——对标准库容器及数据结构的处理第三步类型分流——未知的类型怎么办细节控的自我修养那些专门处理的边角第四步性能优化——写都写了不如写快点1. 自定义缓冲流 AutoOStream2. 整数查表绕开 ostream3. 浮点格式化用上了 Dragonbox妥协与遗憾结尾大梦初醒先看效果我想要的 print大概长这样当然实现的效果也是这样std::vectorintnums{1,2,3};intarr[3]{4,5,6};std::vectorstd::mapint,std::stringnested{{{1,a},{2,b}},{{3,c}}};std::tupleint,float,std::stringtp{7,8.12345,Hello};MyStruct s;// 假设有个没适配 operator 的自定义类型glily::io::print(nums:,nums,arr:,arr,nested:,nested,tp:,tp,pi:,3.14,Unknown:,s);输出nums: {1, 2, 3} arr: {4, 5, 6} nested: {{1:a, 2:b}, {3:c}} tp: {7, 8.12345, Hello} pi: 3.14 Unknown: MyStruct:0x7ffe06db1967一次调用基本类型、容器、嵌套容器、连没适配过的自定义类型都能打。这就是我想要的体验。由于实现会考虑所有标准库可遍历容器、C数组、tuple、C字符串、函数指针及其相互嵌套的全覆盖并且针对性优化了性能。这些能力的实现代码量不小因此难以一次性展示源码详见BokuMeidoCpp。Print: 起源我大学期间只用 Python 做深度学习工作后因为实际部署需要才开始学 C。刚上手时最让我难受的不是指针、不是内存管理而是——没有 print。在 Python 里print(anything)就完事了list、dict 嵌套多少层都能打。而到了 C我想看一眼std::vector的内容得写循环for(constautox:vec)std::coutx ;std::coutstd::endl;写一次两次还行可调试时经常遇到类似的情况甚至vector 里套 map、map 里套 vector每次都要写循环、想格式写多了快要张口翻白眼。当时我只有一个念头想把我常用的打印操作整合成一个 print 函数像 Python 那样什么东西都能往里扔——这就是开头先看效果里的样子。强迫症要做就做个各项目通用的 print 函数其实给特定项目写个打印函数很容易但我有个习惯——我只想写好一次然后所有项目都能用并且行为一致。所以给自己定了三个目标——这些目标前后花了两年才在功能层面实现支持任意类型、任意数量的参数像 Python 一样标准库容器直接打印嵌套容器也能打实在打印不了的输出ClassName: Address而不是编译报错第 3 点是很重要的。Python 的 print 就是这样——什么都能打打不了也不崩。我希望 C 也能给我这种感觉调试的时候不用担心有某个类型导致编译错误。第一步解决任意数量参数——可变参数模板C 要怎么实现输入不固定数量的参数这是让 print 的输入方式开始像 Python 的地方也是我接触模板编程的开始。任意数量的参数如何依次处理呢我的方法是用一个初始化列表展开参数包让每个参数进入专门的处理函数osInputtemplateclassT,class...Argsvoidprint(constTarg,constArgs...args){_priv::osInputFloatPrecision()-1;_priv::StringOutBufsbuf_priv::getPrintSbuf();// 线程本地的字符串缓冲sbuf.clear();std::ostreamos_priv::getPrintOStream();os.clear();// 复位状态位避免异常残留的 badbit 导致后续输出静默丢失_priv::AutoOStreamaos(os);_priv::osInput(aos,arg);// 第一个参数单独打印inttmp[]{0,(aos.put( ),_priv::osInput(aos,args),0)...};// 展开剩余参数参数间以空格分隔(void)tmp;aos.put(\n);aos.flush();conststd::stringssbuf.str();std::cout.write(s.data(),static_caststd::streamsize(s.size()));// 整条输出一次性写入 coutstd::cout.flush();}int tmp[] {0, (aos.put( ), _priv::osInput(aos, args), 0)...};这种写法我第一次遇到时觉得很难理解。其实它的作用就是把剩余参数args…展开每个都调用一次osInput并写入分隔空格第一个参数arg在展开之前单独处理。展开全部发生在编译期。AutoOStream是我对ostream的一层封装1.0.1 里它接的是线程本地的 ostream其底层是StringOutBuf字符串缓冲至于为什么要封装涉及更后面的性能优化问题先把它当成cout理解即可。另外注意这个实现没有锁。1.0.0 版本用一把全局 mutex 保护输出1.0.1 改用thread_local缓冲每个线程先把整条输出拼进自己的字符串最后std::cout.write()一次性写入。这样既消除了锁竞争又不会和其他线程的cout输出交错。第二步类型分流——对标准库容器及数据结构的处理参数数量解决了现在的问题是怎么让标准库容器等分别打印最初的想法很直接把标准库里所有容器的osInput重载都写一遍在函数体内挨个打印元素。但这又有个问题嵌套容器怎么办而且你不知道它嵌套了多深。遇到这种情况我自然想到了递归——不直接用cout打印元素而是递归调用osInput递归下去总会到达一个基础类型。以标准 STL 容器的重载为例// 添加对 STL 标准容器的支持// 虽然用了双层模板 CTer但单层也可以达成目的// 这里的 enable_if 是编译期判断类型是否具备标准的 begin()/end()templatetemplateclassU,class...UsclassCTer,classT,class...Ts,typenamestd::enable_if_priv::StdBeginEndCheckerconstCTerT,Ts...::value,int::typeinlinevoidosInput(AutoOStreamaos,constCTerT,Ts...cter){aos.put({);for(autoitcter.begin();it!cter.end();){_priv::osInput(aos,*it);it;if(it!cter.end())aos.write(, ,2);}aos.put(});}StdBeginEndChecker是什么暂时不用细讲只要知道它可以判断一个类型是否具有标准的 begin() 和 end() 方法。现在对于基本类型、标准库容器及其他数据结构对应的打印逻辑如下算术类型→ 走整数和浮点数各自的快速转字符串算法字符类型直接打印指针→ 除了字符串指针外直接输出指针地址0x12345678函数→ 输出函数类型名如void (*)(int)标准库容器等数据结构→ 输出{1, 2, 3}格式递归处理元素数组→ 类似标准库容器递归打印map 系列→ 输出{key:value, key:value}格式键值对分别递归打印第三步类型分流——未知的类型怎么办就算我能重载所有标准库的数据类型也不可能穷举所有第三方类型。难道遇到未知的类型我就报错吗这样的话print 也只是勉强能用功能并不完备。怎么处理未知类型呢仔细想想这些类型无非就两种可以被cout打印的和不能被cout打印的。于是我用一个模板来区分这两种情况// 支持 operator 的类型 → 直接打印// 这一长串 enable_if 条件是在编译期做类型分流细节不用深究templateclassT,typenamestd::enable_iftype::StdCoutEachCheckerconstT::value!_priv::IsFuncOrMemFuncCheckerT::value!std::is_pointerT::value!std::is_arithmeticT::value,int::typeinlinevoidosInput(AutoOStreamaos,constTarg){aosarg;}// 不支持 operator 的类型 → 打印 ClassName: AddresstemplateclassT,typenamestd::enable_if!type::StdCoutEachCheckerconstT::value,int::typeinlinevoidosInput(AutoOStreamaos,constTarg){aos.put();conststd::stringtype_nametype::getTypeNameT();aos.write(type_name.c_str(),type_name.size());aos.put(:);aosstd::showbasestd::hexuintptr_t(arg)std::dec;aos.put();}同样的StdCoutEachChecker及后续复杂的模板判断细节暂时不用深究只要知道这些模板区分了类型T是否支持cout 操作。现在对于其他类型就只有这两种情况了支持operator的类型→ 直接打印都不支持的→ 打印ClassName: Address顺理成章地想要适配其他类型无需看懂osInput的实现无需自行添加模板特化无需学习任何新概念——只需要为你想要打印的类型重载operator即可。这也是我选择cout路径的原因这套设计适配新类型极为方便能降低心智成本。顺带一提这个适配会在我的format和toStr函数中同步生效——它们的底层与print共用osInput。细节控的自我修养那些专门处理的边角写完主体后我花了很多时间处理边角情况。这些细节没啥技术含量但决定了一个工具好不好用char系列char、signed char、unsigned char本质是整数但打印时应该当字符不能打出数字char*字符串指针打印字符串本身而不是地址volatile char*也要逐字符打出来其他指针打印十六进制地址带0x前缀C 数组char数组当字符串打其他数组打{...}tuple需要编译期索引递归取值用std::integral_constantbool在编译期判断递归终止函数指针 / 成员函数没法打印值那就打印函数类型名——总比报错强。第四步性能优化——写都写了不如写快点功能齐全之后强迫症又犯了性能。虽然 print 主要是调试用的但性能能省则省更何况format等函数对性能也有要求。于是做了三件事1. 自定义缓冲流 AutoOStream直接往std::cout打每次都是一次虚函数调用。我包了一层 512 字节的栈缓冲小片段先攒着填满了才一次性写入底层流classAutoOStream{public:voidwrite(constchar*s,size_t len){if(pos_lensizeof(buf_))// 缓冲放不下{flush();if(lensizeof(buf_)/2)// 长字符串直接写避免拷贝{os_-rdbuf()-sputn(s,len);return;}}memcpy(buf_pos_,s,len);pos_len;}private:charbuf_[512];// 栈上缓冲size_t pos_0;std::ostream*os_;};1.0.1 里print给AutoOStream接的是线程本地的ostream底层StringOutBuf拼完一整行后一次性写入cout——顺带解决了多线程输出交错的问题。2. 整数查表绕开 ostream整数不经过ostream自己写转换——一次处理两位数字查00~99的静态表while(uval100){size_t pairstatic_castsize_t(uval%100);uvalstatic_castUT(uval/100);constchar*ddigits2(pair);// 两位数字查表buf[--pos]d[1];buf[--pos]d[0];}3. 浮点格式化用上了 Dragonbox浮点格式化是个深坑。标准库的to_chars是 C17 才有的C11 下只能用snprintf慢。后来我找到了 Dragonbox——目前浮点转字符串最快的算法之一把它嵌了进来。满足 IEEE 754 的浮点走 Dragonbox不满足的比如某些平台上的 long double回退到snprintf输出结果超出缓冲区的再走ostream的operator——总之不遗漏特殊情况。妥协与遗憾有几个地方是我没处理或刻意没处理的写出来给大家避坑宽字符 / 宽字符串直接按整型打印。cout 本身不支持宽字符我也没有打算造一套自己的宽字符体系。print 不可重入自定义类型的operator里不能调用 print 本身会出问题。冗余的 CTer 双层模板早期为了匹配 STL 容器写的template template class U, class... Us class CTer, ...后来发现单层模板也能做到。但因为它是编译期逻辑、没有运行时开销就一直没简化——既然没问题还看着很厉害就不动了。结尾大梦初醒费了千辛万苦随着对 C 的理解越来越深我实现的 print 函数也越来越接近 Python 的体验——那我可一定要狠狠使用它然而等我写完这个 print我发现已经完全习惯了 C反而不太需要 print 函数了。悲但这个结果也不赖至少我技术是学会了实现 print 时积累的osInput类型分流体系后来成了我实现format、toStr的核心。现在我的库里这些都能用autos1glily::str::toStr(std::vectorint{1,-2,3});// {1, -2, 3}autos2glily::str::toStr(std::mapint,std::string{{1,a},{2,b}});// {1:a, 2:b}autos3glily::str::format({} × {} {},6,7,6*7);// 6 × 7 42而且浮点格式化实测与 {fmt} 在同一量级、部分场景还略快一点Dragonbox 的功劳。这段经历让我明白了一件事想偷懒可能是最好的学习动力。如果不是为了不写循环我可能到现在还停留在能实现项目需求就行的阶段不会去碰模板编程也不会理解 SFINAE、类型约束这些东西。如果你也刚学 C或者也想要一个能打任何东西的 print——完整实现就是我开头提到的那个轻量纯头文件库BokuMeidoCpp零依赖#include bokumeido/core.hpp就能用format、log、线程池都在里面。后续文章format/toStr 如何与 print 共用一套类型分流、log 的同步/异步设计取舍、以及如何让模板错误出现在调用处——感兴趣可以关注我我们下篇见。如果这篇文章对你有帮助欢迎点赞、收藏、关注、star你的支持是我继续分享的动力

相关新闻

RISC-V code model(medlow/medany)原理与混用实战

RISC-V code model(medlow/medany)原理与混用实战

排查过一个怪问题:某 RISC-V 工程的中间件静态库和主目标用了不同的 code model,我想确认库到底是 medlow 还是 medany。随手数了一下反汇编里 lui 和 auipc 的指令比例,auipc 远多于 lui,于是判断"这是 medany"。结论是…

2026/8/26 19:10:54 阅读更多 →
C# WinForm TCP-Socket

C# WinForm TCP-Socket

一、TCP 基础理论知识点 对应代码1.1 TCP 四大核心特点(背诵知识点)面向连接:通信前三次握手建立连接流式无边界传输:数据连续流传输,需要固定缓冲区接收可靠传输:自带确认重传,无丢包全双工&a…

2026/8/26 19:10:54 阅读更多 →
Istio 服务网格入门:流量管理、安全与可观测性

Istio 服务网格入门:流量管理、安全与可观测性

Istio 服务网格入门:流量管理、安全与可观测性工具地址:https://www.speedce.com 社区论坛:https://bbs.speedce.com 联系:speedceadsgmail.com写在前面 服务网格增加了复杂度,外部可达性验收更重要。 本文是一份围绕「…

2026/8/26 19:10:54 阅读更多 →

最新新闻

从Swift 2到4.2:SwiftyTimer版本演进史与Swifty API设计哲学

从Swift 2到4.2:SwiftyTimer版本演进史与Swifty API设计哲学

从Swift 2到4.2:SwiftyTimer版本演进史与Swifty API设计哲学 【免费下载链接】SwiftyTimer Swifty API for NSTimer 项目地址: https://gitcode.com/gh_mirrors/sw/SwiftyTimer SwiftyTimer 是一个用 Swift 编写的 NSTimer 定时器工具库,它提供 S…

2026/8/26 19:44:37 阅读更多 →
InternVL3_5-GPT-OSS-20B-A4B-Preview-HF 多模态大模型训练原理揭秘:CascadeRL 级联强化学习如何带来 16% 推理性能提升

InternVL3_5-GPT-OSS-20B-A4B-Preview-HF 多模态大模型训练原理揭秘:CascadeRL 级联强化学习如何带来 16% 推理性能提升

InternVL3_5-GPT-OSS-20B-A4B-Preview-HF 多模态大模型训练原理揭秘:CascadeRL 级联强化学习如何带来 16% 推理性能提升 【免费下载链接】InternVL3_5-GPT-OSS-20B-A4B-Preview-HF 项目地址: https://ai.gitcode.com/hf_mirrors/OpenGVLab/InternVL3_5-GPT-OSS-2…

2026/8/26 19:44:37 阅读更多 →
2026年AI原生智能体招聘平台:GoHire.TOP

2026年AI原生智能体招聘平台:GoHire.TOP

GoHire 完整产品介绍 GoHire 是一款 AI 原生智能体招聘平台,帮助企业比其他人更快招到精英人才。它的 AI 招聘助手自动筛选简历、进行面试,并为每位候选人提供综合评估报告,把招聘周期从平均 42 天压缩到 3 天。 官网:GoHire - H…

2026/8/26 19:44:37 阅读更多 →
大型量产固件的工程实践(六):嵌入式错误码体系设计

大型量产固件的工程实践(六):嵌入式错误码体系设计

大型量产固件的工程实践(六):嵌入式错误码体系设计 本文是《大型量产固件的工程实践》专栏第 6 篇。 上一篇:第 5 篇《自研轻量 RTOS 内核设计》 | 下一篇:第 7 篇《嵌入式调试日志系统设计》 一、为什么需要统一的错误码 没有统一错误码的固件,错误处理是混乱的: //…

2026/8/26 19:43:36 阅读更多 →
InternVL3.5-30B-A3B流式输出实战:如何实现逐字实时输出体验

InternVL3.5-30B-A3B流式输出实战:如何实现逐字实时输出体验

InternVL3.5-30B-A3B流式输出实战:如何实现逐字实时输出体验 【免费下载链接】InternVL3_5-30B-A3B-Instruct 项目地址: https://ai.gitcode.com/hf_mirrors/OpenGVLab/InternVL3_5-30B-A3B-Instruct InternVL3.5-30B-A3B-Instruct 是书生万象(I…

2026/8/26 19:43:36 阅读更多 →
WanAndroid-Flutter项目全解:张鸿洋推荐的开源项目,为什么是Flutter入门实战的最佳选择

WanAndroid-Flutter项目全解:张鸿洋推荐的开源项目,为什么是Flutter入门实战的最佳选择

WanAndroid-Flutter项目全解:张鸿洋推荐的开源项目,为什么是Flutter入门实战的最佳选择 【免费下载链接】WanAndroid-Flutter Flutter版本 WanAndroid客户端 适合Flutter入门学习 被张鸿洋微信公众号推荐为优质Flutter开源项目啦 项目地址: https://gi…

2026/8/26 19:43:36 阅读更多 →

日新闻

Python random 模块常用函数详解:从入门到实战

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 0:00:40 阅读更多 →
《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》索引目录: 《Microsoft Sql server 2008 Internals》读书笔记--目录索引 在上篇文章中,主要介绍了创建数据库的基本语法和FileGroup的初步知识。需要注意的是: 关于FileGroup 如果你的系统是用Raid设备直接存…

2026/8/26 1:18:18 阅读更多 →
政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体已经从概念试点阶段,转入了政务服务的常态化落地应用;在实际使用过程中,它能自主理解办事需求、辅助完成填报申报、开展材料预审,并联动多个系统协同作业,真正嵌入到政务办理的全流程当中。但在落地推进过…

2026/8/26 1:18:18 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/26 14:45:33 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/26 17:46:43 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/26 14:46:37 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/26 3:50:20 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/26 17:46:39 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/26 1:24:05 阅读更多 →