用 C++20 打造一个实用的十六进制对比工具
文章目录用 C20 打造一个实用的十六进制对比工具完整代码一、整体设计1.1 基本常量与数据结构1.2 核心函数二、单文件转储的实现三、双文件对比的实现3.1 差异检测与着色3.2 并排布局3.3 读取控制四、使用示例4.1 编译4.2 命令行用法4.3 输出效果五、代码亮点与改进方向5.1 现代 C 特性运用5.2 可扩展性5.3 潜在改进六、总结用 C20 打造一个实用的十六进制对比工具完整代码#includeiostream#includeiomanip#includefstream#includeformat#includearray#includespan#includecctype#includestring_view#includestring// ANSI 颜色代码constexprconstchar*COL_RESET\033[0m;constexprconstchar*COL_RED\033[31m;constexprstd::size_t BYTES_PER_LINE4;// 每行显示的字节数// 单文件打印一行4字节十六进制 ASCIIvoidHexAscii(std::spanconststd::bytedata,std::uint32_toffset){std::arraychar,BYTES_PER_LINE1ascii{};// ASCII部分 \0std::coutstd::format({:08X} | ,offset);// 十六进制部分for(std::size_t i0;iBYTES_PER_LINE;i){if(idata.size()){unsignedcharcstatic_castunsignedchar(data[i]);std::coutstd::format({:02X} ,c);ascii[i]std::isprint(c)?static_castchar(c):.;}else{std::cout ;// 对齐缺失字节ascii[i] ;}}ascii[BYTES_PER_LINE]\0;std::coutstd::format( |{}|\n,ascii.data());}// 对比模式打印一行两个文件各4字节十六进制 ASCII并排voidCompareHexLine(std::spanconststd::bytedataA,std::spanconststd::bytedataB,std::uint32_toffset){// 输出偏移量std::coutstd::format({:08X} | ,offset);// ---- 文件 A 的十六进制部分 ----for(std::size_t i0;iBYTES_PER_LINE;i){booldifffalse;if(idataA.size()idataB.size()){diff(dataA[i]!dataB[i]);}elseif(idataA.size()||idataB.size()){difftrue;// 一方有数据另一方无}// 双方都无数据时不标记差异if(diff)std::coutCOL_RED;if(idataA.size()){unsignedcharcstatic_castunsignedchar(dataA[i]);std::coutstd::format({:02X} ,c);}else{std::cout-- ;}if(diff)std::coutCOL_RESET;}// 文件 A 的 ASCII 部分std::cout| ;for(std::size_t i0;iBYTES_PER_LINE;i){booldifffalse;if(idataA.size()idataB.size()){diff(dataA[i]!dataB[i]);}elseif(idataA.size()||idataB.size()){difftrue;}if(diff)std::coutCOL_RED;if(idataA.size()){unsignedcharcstatic_castunsignedchar(dataA[i]);std::cout(std::isprint(c)?static_castchar(c):.);}else{std::cout ;}if(diff)std::coutCOL_RESET;}// 分隔符准备文件 Bstd::cout | ;// ---- 文件 B 的十六进制部分 ----for(std::size_t i0;iBYTES_PER_LINE;i){booldifffalse;if(idataA.size()idataB.size()){diff(dataA[i]!dataB[i]);}elseif(idataA.size()||idataB.size()){difftrue;}if(diff)std::coutCOL_RED;if(idataB.size()){unsignedcharcstatic_castunsignedchar(dataB[i]);std::coutstd::format({:02X} ,c);}else{std::cout-- ;}if(diff)std::coutCOL_RESET;}// 文件 B 的 ASCII 部分std::cout| ;for(std::size_t i0;iBYTES_PER_LINE;i){booldifffalse;if(idataA.size()idataB.size()){diff(dataA[i]!dataB[i]);}elseif(idataA.size()||idataB.size()){difftrue;}if(diff)std::coutCOL_RED;if(idataB.size()){unsignedcharcstatic_castunsignedchar(dataB[i]);std::cout(std::isprint(c)?static_castchar(c):.);}else{std::cout ;}if(diff)std::coutCOL_RESET;}std::cout |\n;}// 单文件十六进制转储voidHexDump(std::string_view filename){std::ifstreamfile(filename.data(),std::ios::binary);if(!file){std::cerrstd::format(Failed to open file: {}\n,filename);return;}std::arraystd::byte,BYTES_PER_LINEbuffer{};std::uint32_toffset0;while(file.read(reinterpret_castchar*(buffer.data()),buffer.size())){HexAscii(std::spanconststd::byte(buffer),offset);offsetstatic_caststd::uint32_t(buffer.size());}if(file.gcount()0){autolastSpanstd::spanconststd::byte(buffer.data(),file.gcount());HexAscii(lastSpan,offset);}}// 两文件十六进制对比voidHexCompare(std::string_view file1,std::string_view file2){std::ifstreamf1(file1.data(),std::ios::binary);std::ifstreamf2(file2.data(),std::ios::binary);if(!f1){std::cerrstd::format(Failed to open file: {}\n,file1);return;}if(!f2){std::cerrstd::format(Failed to open file: {}\n,file2);return;}std::arraystd::byte,BYTES_PER_LINEbuf1{},buf2{};std::uint32_toffset0;while(true){std::streamsize count10,count20;if(!f1.eof()){f1.read(reinterpret_castchar*(buf1.data()),buf1.size());count1f1.gcount();}if(!f2.eof()){f2.read(reinterpret_castchar*(buf2.data()),buf2.size());count2f2.gcount();}if(count10count20)break;CompareHexLine(std::spanconststd::byte(buf1.data(),count1),std::spanconststd::byte(buf2.data(),count2),offset);offsetBYTES_PER_LINE;}}// 帮助信息voidPrintUsage(constchar*progName){std::coutUsage:\n progName file # 十六进制转储每行4字节ASCII\n progName file1 file2 # 对比两个文件每行4字节并排ASCII\n;}////int main(int argc, char* argv[]) {// if (argc 2) {// HexDump(argv[1]);// }// else if (argc 3) {// HexCompare(argv[1], argv[2]);// }// else {// PrintUsage(argv[0]);// return 1;// }// return 0;//}intmain(intargc,char*argv[]){HexCompare(C:\\Code\\test.png,C:\\Code\\test2.png);return0;}在日常开发中我们经常需要查看文件的原始二进制内容或者对比两个文件是否完全相同——甚至精确到每个字节的差异。虽然市面上有hexdump、xxd、Beyond Compare等工具但自己动手实现一个轻量级的十六进制查看器兼对比器不仅有趣还能加深对 C 文件流、std::span、格式化输出等特性的理解。本文介绍的代码实现了一个命令行工具支持两种模式单文件模式以每行 4 字节的格式输出十六进制和对应的 ASCII 字符。双文件对比模式并排显示两个文件的十六进制和 ASCII并用红色高亮标记差异字节。完整代码基于 C20 标准库编写format、span等不依赖任何第三方库。下面我们来详细拆解它的设计思路和实现细节。一、整体设计1.1 基本常量与数据结构constexprconstchar*COL_RESET\033[0m;constexprconstchar*COL_RED\033[31m;constexprstd::size_t BYTES_PER_LINE4;使用 ANSI 转义序列控制终端颜色差异部分用红色突出。BYTES_PER_LINE控制每行显示的字节数这里设为 4方便排版也可根据需要调整。1.2 核心函数HexAscii()打印单行数据4 字节十六进制 ASCII。CompareHexLine()并排打印两个文件的同一行并标出不同之处。HexDump()单文件转储主逻辑。HexCompare()双文件对比主逻辑。PrintUsage()帮助信息。所有函数均接受std::spanconst std::byte作为数据视图避免了不必要的拷贝。二、单文件转储的实现voidHexAscii(std::spanconststd::bytedata,std::uint32_toffset){std::arraychar,BYTES_PER_LINE1ascii{};std::coutstd::format({:08X} | ,offset);// 十六进制部分for(std::size_t i0;iBYTES_PER_LINE;i){if(idata.size()){unsignedcharcstatic_castunsignedchar(data[i]);std::coutstd::format({:02X} ,c);ascii[i]std::isprint(c)?static_castchar(c):.;}else{std::cout ;// 对齐缺失字节ascii[i] ;}}ascii[BYTES_PER_LINE]\0;std::coutstd::format( |{}|\n,ascii.data());}偏移量使用{:08X}输出 8 位十六进制地址。十六进制列每个字节两位十六进制数加一个空格不足 4 字节时用三个空格补齐。ASCII 列可打印字符原样显示不可打印字符显示为.最后用|包裹。每行恰好处理BYTES_PER_LINE个字节最后一行可能不足。主循环利用std::ifstream::read()读取固定大小的缓冲区最后用gcount()处理剩余字节std::arraystd::byte,BYTES_PER_LINEbuffer{};while(file.read(reinterpret_castchar*(buffer.data()),buffer.size())){HexAscii(std::spanconststd::byte(buffer),offset);offsetBYTES_PER_LINE;}if(file.gcount()0){autolastSpanstd::spanconststd::byte(buffer.data(),file.gcount());HexAscii(lastSpan,offset);}三、双文件对比的实现对比模式的核心挑战是两个文件可能长度不同。需要并排显示且差异字节要红色高亮。同一行内既要显示文件 A 的十六进制 ASCII又要显示文件 B 的十六进制 ASCII。3.1 差异检测与着色在CompareHexLine()中我们为每个字节位置计算一个diff标志如果两个文件在该位置都有数据但值不同 →diff true如果一个文件有数据而另一个没有即文件长度不一致→diff true然后根据diff决定是否包裹红色 ANSI 码if(diff)std::coutCOL_RED;// 输出字节或占位符if(diff)std::coutCOL_RESET;3.2 并排布局输出格式为偏移量 | 文件A十六进制 | 文件A的ASCII | 文件B十六进制 | 文件B的ASCII |示例00000000 | 89 50 4E 47 | .PNG | 89 50 4E 47 | .PNG | 00000004 | 0D 0A 1A 0A | .... | 0D 0A 1A 0A | .... | ...实际代码中通过多次循环分别输出文件 A 的 hex、A 的 ASCII、B 的 hex、B 的 ASCII每个部分独立判断差异并着色。3.3 读取控制由于两个文件长度可能不同我们分别读取各自的一块数据然后取有效长度调用CompareHexLine()std::streamsize count10,count20;if(!f1.eof()){f1.read(...);count1f1.gcount();}if(!f2.eof()){f2.read(...);count2f2.gcount();}if(count10count20)break;CompareHexLine(std::span(buf1.data(),count1),std::span(buf2.data(),count2),offset);offsetBYTES_PER_LINE;这样即使一个文件先读完另一文件剩余的部分也会被正常显示缺失处用--和空格表示。四、使用示例4.1 编译使用支持 C20 的编译器如 GCC 11、Clang 14、MSVC 2022g-stdc20-ohexdiff hexdiff.cpp4.2 命令行用法单文件转储./hexdiff file.bin双文件对比./hexdiff file1.bin file2.bin如果你不想每次手动输入参数也可以在main()中直接硬编码路径如原代码最后的示例intmain(){HexCompare(C:\\Code\\test.png,C:\\Code\\test2.png);return0;}4.3 输出效果单文件输出00000000 | 89 50 4E 47 | .PNG | 00000004 | 0D 0A 1A 0A | .... |对比模式差异部分红色显示00000000 | 89 50 4E 47 | .PNG | 89 50 4F 47 | .POG | ^^^^ ^^^^注实际红色由终端支持这里用^^^示意五、代码亮点与改进方向5.1 现代 C 特性运用std::span避免指针长度传递安全且语义清晰。std::format类型安全的格式化输出比printf更现代。std::byte明确表示原始内存避免字符类型的歧义。std::array固定大小缓冲区性能优于vector。5.2 可扩展性调整BYTES_PER_LINE即可改变每行字节数例如 8 或 16。可以增加命令行参数解析如-c开启对比、-w设置列宽目前简单但够用。5.3 潜在改进支持大文件当前逐行读取内存占用极小已适合大文件。跳过相同区域对比时可以检测连续相同的块只输出差异部分类似diff的压缩模式。Windows 兼容性ANSI 颜色码在 Windows 10 的终端中默认支持旧系统可能需要启用 VT 模式。宽字符处理目前 ASCII 显示仅支持单字节字符对于 UTF-8 文本可进一步优化。六、总结不到 150 行代码我们就实现了一个功能完整的十六进制查看与对比工具。它展示了如何利用 C20 的标准库组件高效地处理二进制文件同时提供了友好的视觉反馈。无论你是需要快速检查文件内容还是想学习现代 C 的 IO、格式化、内存视图等知识这个例子都是一个不错的起点。完整的代码已经贴在上面你可以直接复制保存为.cpp文件编译运行。动手试试用红色高亮抓住那些“狡猾”的字节差异吧

相关新闻

开篇词 | 如何让 Linux 内核更好地服务应用程序?

开篇词 | 如何让 Linux 内核更好地服务应用程序?

开篇词 | 如何让 Linux 内核更好地服务应用程序? 一、写在前面 作为一名应用开发者或运维 / SRE 工程师,你大概率遇到过这样的场景: 线上服务突然 TCP 重传率飙升,业务端到端延迟翻了好几倍,你盯着 ss -tin 的输出&…

2026/10/8 9:45:07 阅读更多 →
JPEXS Free Flash Decompiler:终极免费SWF反编译工具完整使用指南

JPEXS Free Flash Decompiler:终极免费SWF反编译工具完整使用指南

JPEXS Free Flash Decompiler:终极免费SWF反编译工具完整使用指南 【免费下载链接】jpexs-decompiler JPEXS Free Flash Decompiler 项目地址: https://gitcode.com/gh_mirrors/jp/jpexs-decompiler 在Flash技术逐渐退出历史舞台的今天,如何保存和…

2026/10/6 7:35:10 阅读更多 →
ETS2LA自动驾驶插件:如何在《欧洲卡车模拟2》中实现真正的智能驾驶体验

ETS2LA自动驾驶插件:如何在《欧洲卡车模拟2》中实现真正的智能驾驶体验

ETS2LA自动驾驶插件:如何在《欧洲卡车模拟2》中实现真正的智能驾驶体验 【免费下载链接】ETS2LA Plugin based interface program for ETS2/ATS. 项目地址: https://gitcode.com/gh_mirrors/eur/ETS2LA 你是否曾在《欧洲卡车模拟2》的漫长旅途中感到疲惫&…

2026/10/12 20:02:48 阅读更多 →

最新新闻

商人过河问题详解:状态转移与BFS的Matlab实现

商人过河问题详解:状态转移与BFS的Matlab实现

1. 从一道经典建模题说起:商人过河到底在考什么数学建模入门阶段,几乎人人都会碰到“商人过河”这道题。题目本身并不复杂:三个商人带着三个随从,要坐一条小船过河,小船每次最多载两人,而且无论在哪一岸&am…

2026/10/13 2:36:32 阅读更多 →
机器学习加速超材料逆向设计:从电磁仿真到参数搜索的实战指南

机器学习加速超材料逆向设计:从电磁仿真到参数搜索的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 2:36:32 阅读更多 →
DeepSeek Harness插件:笔记整理与会话管理实战指南

DeepSeek Harness插件:笔记整理与会话管理实战指南

这次我们来看一个跑得很快的 AI 工具项目:围绕 DeepSeek 模型能力做的两款 Harness 插件,一款负责笔记整理,一款负责会话管理。项目上线不到一周,GitHub 接近 4000 个 star,在插件类项目里算很扎眼的起步速度。从这个热…

2026/10/13 2:36:32 阅读更多 →
Trae 技能配置实战:用 skills.md 让 AI 编程不再自由发挥

Trae 技能配置实战:用 skills.md 让 AI 编程不再自由发挥

你是不是也有这种体验:用 AI IDE 写代码,对话窗里聊得头头是道,真让它干活又开始自由发挥,步骤乱、输出格式乱,结果照样要自己返工。问题根源很多时候不是模型不够聪明,而是缺少一份标准操作说明书。skills…

2026/10/13 2:36:32 阅读更多 →
501B开放权重稠密大模型:部署、量化与蒸馏实操指南

501B开放权重稠密大模型:部署、量化与蒸馏实操指南

1. 从“501B 开放权重”这个数字说起第一次看到“501B 参数、开放权重”这两个词摆在一起,我的第一反应不是兴奋,而是算账。501B 这个量级,按常见的 FP16 精度算,光权重就要占掉大约 1TB 的显存空间;就算上 INT8 量化&…

2026/10/13 2:36:32 阅读更多 →
Trae实测:从对话生成到Agent执行,AI编程工具如何改变开发流程

Trae实测:从对话生成到Agent执行,AI编程工具如何改变开发流程

最近AI Coding已经热到不需要再解释背景了。从最早的代码补全,到能直接对话生成整段功能,再到现在的Agent式“你提需求、它动手改代码”,这一波工具迭代的速度,确实让很多开发者的日常工作方式都变了。我陆陆续续试过不少同类产品…

2026/10/13 2:35:32 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →