C++操作Excel文件:从LibXL库选型到实战报表生成

C++操作Excel文件:从LibXL库选型到实战报表生成
1. 项目概述为什么要在C里操作Excel在数据处理和分析的日常工作中Excel文件几乎是绕不开的格式。无论是生成报表、导出日志还是作为不同系统间的数据交换媒介Excel的.xls和.xlsx文件都扮演着重要角色。作为一名C开发者你可能会遇到这样的场景你的高性能计算程序产生了几十万行结果需要输出给业务人员查看或者你需要从一个复杂的仿真系统中将结构化的配置参数写入一个标准的Excel模板。这时候如果只能生成晦涩的.txt或.csv文件再手动导入Excel不仅效率低下也容易出错。直接使用C进行Excel文件写入意味着你可以将数据生成、格式编排、文件输出在一个流程内自动化完成。这不仅仅是“写入数据”更是实现程序与通用办公软件无缝对接的关键能力。想象一下你的程序能直接生成一个带有彩色表头、自动计算公式、甚至内嵌图表的专业报表这比交付一堆原始数据要直观和强大得多。然而C标准库并没有提供直接操作Excel文件的功能。Excel文件本质上是一种复杂的、结构化的压缩包对于.xlsx格式内部包含了XML描述文件、样式定义、共享字符串表等。因此我们需要借助第三方库来解析和生成这种特定格式。市面上有多种选择比如功能强大但需要Office环境的COM组件或者像LibXL、OpenXLSX这样轻量、跨平台的纯库方案。选择哪一个取决于你的项目对部署环境、性能、功能以及许可协议的具体要求。2. 核心库选型与对比找到你的“瑞士军刀”在动手之前选对工具至关重要。不同的库在易用性、功能、性能和许可上差异巨大。这里我们深入对比几个主流选择帮你做出最适合的决策。2.1 主流第三方库横向评测LibXL这是我个人在商业项目中用得最多、也最推荐给新手的库。它是一个商业库但也提供免费功能受限版本。它的最大优点是接口极其简洁直观不需要任何额外的依赖如Excel软件或.NET框架一个头文件加一个静态库或动态库就能工作。优点零依赖真正的跨平台Windows、Linux、macOS上编译即用。API友好读写操作就像在操作一个二维数组学习成本极低。功能全面支持单元格格式字体、颜色、边框、对齐、公式、合并单元格、图片插入商业版、图表商业版等。性能优秀直接操作文件速度很快。缺点商业许可用于商业项目需要购买授权免费版会在生成的Excel文件顶部添加一行提示文本。高级功能需付费如图表、图片等功能仅在付费版中提供。OpenXLSX这是一个新兴的、纯头文件的C库专门用于处理.xlsx格式。它完全开源采用现代CC17编写利用了RAII等特性内存管理更安全。优点纯头文件集成简单直接包含头文件即可无需链接库。现代C风格代码清晰利用了智能指针等更安全。开源免费采用MIT许可证商业项目可自由使用。缺点相对年轻社区和生态可能不如LibXL成熟遇到极端复杂场景的解决方案可能较少。仅支持.xlsx不支持旧的.xls格式。功能正在完善某些高级格式功能可能还在开发中。使用COMComponent Object Model这是Windows平台上最“原生”的方式通过调用Microsoft Excel自身的COM接口来操作。功能是最强大的Excel能做的它几乎都能做。优点功能无限凡是Excel GUI能实现的操作通过COM都能以编程方式实现。无需关心文件格式直接操作Excel应用。缺点重度依赖目标机器必须安装对应版本的Microsoft Excel。速度慢需要启动Excel进程进程间通信开销大。稳定性挑战需要处理Excel进程的生命周期异常退出可能导致僵尸进程。仅限Windows平台锁死。其他库如xlntxlnt也是一个跨平台的C14库模仿了Apache POI的API设计。它开源免费但在我过去的体验中其文档和社区活跃度一般对于复杂中文支持或某些格式可能会遇到问题这里不做首要推荐。选择建议对于绝大多数需要生成报表、数据导出的场景LibXL的付费版或OpenXLSX是最佳选择。如果你需要绝对的功能完整且环境可控如企业内部Windows服务器可以考虑COM方案。本指南将以LibXL为例进行讲解因为它API最稳定资料相对丰富且其核心写入逻辑与其他库相通。2.2 环境准备与库的集成假设我们选择LibXL。首先你需要从官网下载开发包。通常你会得到libxl.h、libxl.libWindows静态库或libxl.soLinux动态库等文件。在Visual Studio中集成Windows包含目录在项目属性 - C/C - 常规 - 附加包含目录中添加libxl.h所在的路径。库目录在链接器 - 常规 - 附加库目录中添加.lib文件所在的路径。附加依赖项在链接器 - 输入 - 附加依赖项中添加libxl.lib。拷贝DLL将libxl.dll如果使用动态库拷贝到你的可执行文件输出目录如Debug/或Release/下或者放到系统PATH包含的目录中。在CMake项目中集成跨平台更推荐使用CMake来管理这样跨平台编译会更方便。假设你将LibXL的库文件放在项目根目录的thirdparty/libxl下。cmake_minimum_required(VERSION 3.10) project(ExcelWriter) set(CMAKE_CXX_STANDARD 11) # 假设LibXL头文件在 thirdparty/libxl/include库文件在 thirdparty/libxl/lib include_directories(${PROJECT_SOURCE_DIR}/thirdparty/libxl/include) link_directories(${PROJECT_SOURCE_DIR}/thirdparty/libxl/lib) add_executable(ExcelWriter main.cpp) # 根据平台链接不同的库文件 if(WIN32) target_link_libraries(ExcelWriter libxl.lib) else() target_link_libraries(ExcelWriter libxl) endif()在Linux/macOS下编译你需要使用LibXL提供的Linux/macOS版本的库文件链接步骤类似通常是通过-lxl来链接。3. 基础写入操作从“Hello World”到结构化数据库集成好后让我们开始真正的写入操作。我们从创建一个最简单的Excel文件开始逐步增加复杂度。3.1 创建文件与工作表任何操作的第一步是创建一个“Book”对象它代表整个Excel工作簿。#include libxl.h using namespace libxl; int main() { // 1. 创建Book对象。xlCreateBook()用于创建.xls格式xlCreateXMLBook()用于创建.xlsx格式。 // 这里我们创建更现代的.xlsx格式工作簿。 Book* book xlCreateXMLBook(); if (!book) { std::cerr Failed to create Excel book! std::endl; return -1; } // 2. 添加一个工作表并为其命名。Sheet索引从0开始。 Sheet* sheet book-addSheet(SalesData); if (!sheet) { std::cerr Failed to add sheet! std::endl; book-release(); // 释放资源 return -1; } // 3. 最基础的写入向单元格A1写入一个字符串 // 注意LibXL的行列索引是从0开始的。所以(0, 0)对应A1单元格。 sheet-writeStr(0, 0, Hello Excel from C!); // 4. 写入不同类型的数据 sheet-writeStr(1, 0, Product Name); // 字符串 sheet-writeNum(1, 1, 42.99); // 数字浮点数 sheet-writeNum(1, 2, 100); // 数字整数 sheet-writeBool(1, 3, true); // 布尔值Excel中显示为TRUE/FALSE sheet-writeFormula(1, 4, B2*C2); // 公式计算B2和C2的乘积 // 5. 保存文件 if (book-save(example.xlsx)) { std::cout File saved successfully: example.xlsx std::endl; } else { std::cerr Failed to save file: book-errorMessage() std::endl; } // 6. 释放资源这是非常重要的一步防止内存泄漏。 book-release(); return 0; }编译并运行这个程序你会在当前目录下得到一个example.xlsx文件用Excel打开它你会看到A1单元格的问候语以及第一行的一些基础数据。实操心得book-release()至关重要。LibXL使用纯C的API风格需要手动管理内存。忘记调用release()是新手最常见的导致内存泄漏的原因。可以考虑用std::unique_ptr配合自定义删除器来包装Book*实现自动管理但这需要一些模板技巧。3.2 批量写入与性能考量当你需要写入成千上万行数据时单单元格写入的效率会成为瓶颈。虽然LibXL内部有一定优化但遵循一些最佳实践可以显著提升性能。高效写入模式按行或按列顺序写入尽量保证你的写入顺序与内存中数据的物理顺序一致减少内部缓存刷新次数。例如如果你有一个vectorvectordouble的数据外层是行内层是列那么就按行循环写入。避免频繁的格式创建如果大批量单元格使用同一种格式应该在循环外创建Format*对象在循环内重复使用。频繁创建和销毁格式对象开销很大。预分配内存对于超大文件虽然LibXL没有明确的“预分配”API但你可以先写入一个足够大的单元格比如最后一行最后一列给库一个文件大小的暗示但这并非总是有效。更可靠的方法是分批次处理例如每写入10000行就保存一次到临时文件或者考虑使用流式API如果库支持。示例批量生成数据// 假设我们有一个数据结构产品名称单价销量总价单价*销量 struct ProductRecord { std::string name; double unitPrice; int quantity; }; std::vectorProductRecord records { {Laptop, 799.99, 120}, {Mouse, 25.50, 500}, {Keyboard, 89.99, 300}, // ... 假设有10000条记录 }; // 创建格式后续会详细讲格式 Format* headerFormat book-addFormat(); headerFormat-setBorder(BORDERSTYLE_THIN); headerFormat-setFillPattern(FILLPATTERN_SOLID); headerFormat-setPatternForegroundColor(COLOR_TAN); headerFormat-setFont(book-addFont()); // 使用默认字体但加粗 headerFormat-font()-setBold(true); // 写入表头 sheet-writeStr(0, 0, Product, headerFormat); sheet-writeStr(0, 1, Unit Price, headerFormat); sheet-writeStr(0, 2, Quantity, headerFormat); sheet-writeStr(0, 3, Total, headerFormat); // 为数据行创建一个通用格式 Format* dataFormat book-addFormat(); dataFormat-setBorder(BORDERSTYLE_THIN); // 批量写入数据 for (size_t i 0; i records.size(); i) { const auto record records[i]; int row static_castint(i) 1; // 表头占第0行数据从第1行开始 sheet-writeStr(row, 0, record.name.c_str(), dataFormat); sheet-writeNum(row, 1, record.unitPrice, dataFormat); sheet-writeNum(row, 2, record.quantity, dataFormat); // 在Excel中使用公式计算总价而不是在C中计算后写入。 // 这样在Excel中修改单价或数量时总价会自动更新。 char formula[50]; sprintf(formula, B%d*C%d, row1, row1); // Excel行号从1开始所以是row1 sheet-writeFormula(row, 3, formula, dataFormat); }这种方式一次性处理所有数据逻辑清晰。对于海量数据如超过10万行你可能需要关注内存占用并考虑分页或增量写入。4. 单元格格式深度定制让你的报表“会说话”原始数据是骨架格式就是血肉和衣服。一个好的格式能让报表专业、易读。LibXL的格式系统非常强大但稍显繁琐因为每个格式属性字体、边框、填充等都需要通过Format对象来设置。4.1 字体、对齐与数字格式创建与使用格式格式不能独立存在必须依附于一个Book对象来创建。// 创建一个新的格式对象 Format* titleFormat book-addFormat(); if (!titleFormat) { /* 处理错误 */ } // 1. 字体设置 Font* titleFont book-addFont(); titleFont-setName(Arial); titleFont-setSize(16); titleFont-setBold(true); titleFont-setColor(COLOR_RED); titleFormat-setFont(titleFont); // 2. 对齐方式 titleFormat-setAlignH(ALIGNH_CENTER); // 水平居中 titleFormat-setAlignV(ALIGNV_CENTER); // 垂直居中 // 3. 数字格式 // 数字格式是一个字符串遵循Excel的格式代码。 Format* currencyFormat book-addFormat(); currencyFormat-setNumFormat(NUMFORMAT_CURRENCY); // 内置货币格式如$#,##0.00 // 或者自定义格式 Format* customNumFormat book-addFormat(); customNumFormat-setNumFormat(0.00%); // 显示为百分比两位小数 customNumFormat-setNumFormat(#,##0.000); // 千位分隔符三位小数 customNumFormat-setNumFormat(yyyy-mm-dd hh:mm:ss); // 日期时间格式 // 应用格式到单元格 sheet-writeStr(0, 0, Monthly Report, titleFormat); sheet-writeNum(5, 2, 1234567.89, currencyFormat); sheet-writeNum(6, 2, 0.8567, customNumFormat); // 会显示为85.67%4.2 边框、背景色与合并单元格边框和填充边框和填充是让表格层次分明的关键。Format* headerCellFormat book-addFormat(); // 1. 设置边框 // 可以分别设置四条边的样式和颜色 headerCellFormat-setBorder(BORDERSTYLE_MEDIUM); // 设置所有边框为中等粗细 // 或者精细控制 headerCellFormat-setBorder(BORDERSTYLE_THIN); // 先设一个默认值 headerCellFormat-setBorder(BORDERSTYLE_THICK, BORDERDIAGONAL_DOWN); // 单独设置对角线 headerCellFormat-setBorderColor(COLOR_BLUE); // 设置所有边框颜色 // 更精细的控制LibXL高级功能 headerCellFormat-setBorder(BORDERSTYLE_DASHED, BORDERTOP); headerCellFormat-setBorder(BORDERSTYLE_DOUBLE, BORDERBOTTOM); headerCellFormat-setTopBorderColor(COLOR_DARKGREEN); headerCellFormat-setBottomBorderColor(COLOR_DARKRED); // 2. 设置背景填充 headerCellFormat-setFillPattern(FILLPATTERN_SOLID); // 纯色填充 headerCellFormat-setPatternForegroundColor(COLOR_GRAY25); // 设置前景色即填充色 // 如果是图案填充还可以设置背景色 // headerCellFormat-setPatternBackgroundColor(COLOR_WHITE); // 应用 sheet-writeStr(1, 0, Region, headerCellFormat); sheet-writeStr(1, 1, Sales, headerCellFormat);合并单元格常用于制作标题或分类。// 合并A1到D1的单元格并写入标题 sheet-writeStr(0, 0, 2024 Annual Sales Summary, titleFormat); sheet-setMerge(0, 0, 0, 3); // 参数起始行起始列结束行结束列 // 合并后只有左上角(0,0)单元格的内容有效写入其他合并区域单元格会失败或覆盖。 // 格式会应用到整个合并区域。注意事项格式对象一旦被创建并应用到单元格其生命周期就由Book对象管理。你不需要手动释放Format*或Font*。但是不要在不同的Book对象之间共享同一个Format*这会导致未定义行为。每个工作簿的格式是独立的。5. 高级功能与实战技巧掌握了基础写入和格式后我们可以探索一些更高级的功能让生成的Excel文件更具交互性和专业性。5.1 写入公式与函数在单元格中写入公式可以让Excel文件在打开后具备动态计算能力。LibXL支持大部分Excel内置函数。// 写入简单的算术公式 sheet-writeFormula(10, 5, SUM(F2:F9)); // 计算F2到F9的和 sheet-writeFormula(11, 5, AVERAGE(B2:B100)); sheet-writeFormula(12, 5, MAX(C:C)); // 引用整列 // 写入带条件的公式 // 假设D列是销售额E列是成本我们想在F列计算利润率 for (int row 1; row 100; row) { char formula[100]; // 利润率 (销售额 - 成本) / 销售额。防止除零错误。 sprintf(formula, IF(D%d0, (D%d-E%d)/D%d, \N/A\), row1, row1, row1, row1); sheet-writeFormula(row, 5, formula); // 写入F列 } // 写入查找函数如VLOOKUP // 假设在Sheet1的A:B列是产品ID和名称在Sheet2中根据ID查找名称 Sheet* sheet2 book-addSheet(Report); sheet2-writeFormula(0, 1, VLOOKUP(A2, Sheet1!$A$2:$B$100, 2, FALSE));重要提示LibXL写入的是公式字符串本身。公式的计算是在Excel打开文件时进行的而不是在C程序运行时。这意味着你的程序不负责计算结果。5.2 处理多工作表与页面设置一个工作簿包含多个工作表是很常见的。// 创建多个工作表 Sheet* summarySheet book-addSheet(Summary); Sheet* detailSheet book-addSheet(Detail); Sheet* chartSheet book-addSheet(Charts); // 在工作表间跳转写入 summarySheet-writeStr(0, 0, This is summary.); detailSheet-writeStr(0, 0, This is detail data.); // 设置工作表属性非所有版本支持 detailSheet-setHidden(false); // 是否隐藏工作表 // 保护工作表带密码 // detailSheet-protect(your_password, PROT_DEFAULT, PROT_DEFAULT); // 页面设置打印相关 detailSheet-setPaper(PAPER_A4); detailSheet-setLandscape(); // 设置为横向打印 detailSheet-setPrintRepeatRows(0, 0); // 设置顶端标题行第1行 detailSheet-setPrintRepeatCols(0, 2); // 设置左端标题列A到C列 detailSheet-setPrintArea(0, 0, 50, 10); // 设置打印区域 A1:J51这些设置能确保你生成的报表在打印时也保持美观和规整。5.3 错误处理与文件保存策略健壮的程序必须处理错误。Book* book xlCreateXMLBook(); if (!book) { // 创建失败可能是库初始化问题 std::cerr Critical: Failed to initialize LibXL. std::endl; return -1; } // 设置错误处理回调如果库支持 // book-setErrorHandler(myErrorHandler); Sheet* sheet book-addSheet(Data); if (!sheet) { std::cerr Error: Could not add sheet. Book error: book-errorMessage() std::endl; book-release(); return -1; } // 写入操作本身通常不返回错误但为了安全可以检查文件保存的结果 std::string filename report.xlsx; if (!book-save(filename.c_str())) { // 保存失败可能的原因 // 1. 文件路径不存在或没有写权限。 // 2. 磁盘空间不足。 // 3. 文件正在被其他程序如Excel打开。 std::cerr Failed to save file filename . Error: book-errorMessage() std::endl; // 尝试备用方案保存到临时文件或提示用户 std::string tempFile std::tmpnam(nullptr); tempFile .xlsx; if (book-save(tempFile.c_str())) { std::cout File saved to temporary location: tempFile std::endl; } book-release(); return -1; } book-release();文件保存策略原子性写入对于关键数据可以先保存到一个临时文件如report.tmp.xlsx保存成功后再用rename或移动操作替换旧文件。这可以防止程序崩溃导致生成一个不完整的、损坏的Excel文件。版本备份在文件名中加入时间戳如report_20240527_143022.xlsx便于追溯和回滚。用户反馈保存成功后可以输出文件完整路径甚至用系统命令尝试打开它如Windows下的system(start report.xlsx)但需谨慎使用避免安全风险。6. 常见问题排查与性能优化在实际开发中你肯定会遇到各种“坑”。这里汇总了一些典型问题及其解决方案。6.1 编译与链接问题问题现象可能原因解决方案编译错误undefined reference to xlCreateBook链接器没有找到LibXL的库文件。检查项目设置中的库目录和附加依赖项是否正确。在Linux下确保使用了-lxl参数。运行时崩溃程序启动即崩溃1. 使用了不匹配的库版本Debug/Release32位/64位。2. 没有将DLLWindows或SOLinux文件放到可执行文件能找到的位置。1. 确保你的项目配置Debug/Release平台x86/x64与下载的LibXL库完全一致。2. 将libxl.dllWindows拷贝到exe同级目录或将libxl.so所在路径加入LD_LIBRARY_PATHLinux。保存文件失败errorMessage()返回空常见的权限问题或路径问题。检查目标文件夹是否存在以及程序是否有写入权限。尝试使用绝对路径。6.2 运行时与数据问题问题现象可能原因解决方案写入的中文显示为乱码LibXL默认使用UTF-8编码。如果源字符串是其他编码如GBK就会乱码。确保你的C源代码文件保存为UTF-8编码无BOM。在Windows下如果字符串来自系统如ANSI可能需要先转换为UTF-8。可以使用std::wstring和sheet-writeStr(row, col, wstr.c_str())如果库支持宽字符。最稳妥的方式是使用LibXL的book-setKey(...)设置正确的本地化如果版本支持。生成的Excel文件用WPS打开正常用Microsoft Excel打开报错“文件已损坏”可能是LibXL生成的某些高级特性如特定图表、样式与Excel的严格校验不兼容。或者是文件结构有轻微错误。1. 尝试使用xlCreateBook()生成老旧的.xls格式看是否还有问题。如果.xls正常可能是.xlsx生成逻辑有bug。2. 更新到LibXL的最新版本。3. 简化文件内容移除复杂的格式和公式逐步定位问题所在。4.重要在保存文件后调用book-save的返回值一定要检查并打印errorMessage()。写入大量数据10万行时程序速度变慢内存占用高每次write操作都可能涉及内存分配和格式处理。所有数据在调用save前都保存在内存中。1.复用Format对象这是最重要的优化。2.分批次处理如果可能将数据分成多个文件或者分多个工作表。3.考虑其他格式对于纯数据交换CSV可能是更高效的选择。如果必须用Excel评估是否真的需要所有格式可以先用简单格式写入后期再用Excel或脚本美化。4.使用64位编译如果你的数据量极大确保你的应用程序是64位的以突破内存限制。公式不计算用LibXL写入的公式在Excel中打开时默认可能处于“手动计算”模式或者文件被设置为“不更新链接”。在Excel中按F9键手动重算。或者在保存工作簿前可以通过COM如果使用设置Calculation属性为自动。对于LibXL可以尝试在保存前设置book-calcMode()如果API支持。更常见的做法是在生成文件后如果环境允许可以调用一个简单的VBScript或PowerShell脚本用Excel打开并强制计算一次然后保存。6.3 高级调试技巧最小化复现当遇到问题时尝试创建一个最小的、能复现问题的代码片段。这能帮你快速定位是数据问题、格式问题还是库本身的bug。二进制文件对比生成一个有问题的Excel文件和一个手工创建的正确文件。将它们重命名为.zip并解压对比内部XML文件的差异。这能帮你精确找到是哪个部分如styles.xml,sharedStrings.xml出了问题。查看库的日志某些版本的LibXL可以通过环境变量或API调用来开启调试日志输出内部执行信息。社区与官方支持LibXL的官方论坛和邮件列表是解决问题的好地方。提问时务必附上你的最小复现代码、库版本、操作系统和编译器信息。7. 实战构建一个完整的销售数据导出模块让我们综合运用以上所有知识构建一个模拟的、接近真实场景的销售数据导出模块。这个模块会生成一个包含摘要、明细、简单图表通过公式模拟的报表。需求生成一个包含“摘要”、“明细”两个工作表的文件。“摘要”工作表包含月度销售总额、平均订单额、最佳销售产品。“明细”工作表包含所有订单的原始数据并带有格式。文件以当前日期时间命名。对可能出现的错误进行基本处理。核心代码结构#include libxl.h #include iostream #include vector #include ctime #include iomanip #include sstream struct Order { std::string orderId; std::string product; double amount; std::string date; std::string region; }; class ExcelReportGenerator { private: libxl::Book* book; std::vectorOrder orders; std::string generateFilename() { std::time_t t std::time(nullptr); std::tm tm *std::localtime(t); std::ostringstream oss; oss sales_report_ std::put_time(tm, %Y%m%d_%H%M%S) .xlsx; return oss.str(); } libxl::Format* createHeaderFormat() { libxl::Format* fmt book-addFormat(); fmt-setBorder(libxl::BORDERSTYLE_THIN); fmt-setFillPattern(libxl::FILLPATTERN_SOLID); fmt-setPatternForegroundColor(libxl::COLOR_GRAY25); fmt-setFont(book-addFont()); fmt-font()-setBold(true); fmt-setAlignH(libxl::ALIGNH_CENTER); return fmt; } libxl::Format* createCurrencyFormat() { libxl::Format* fmt book-addFormat(); fmt-setNumFormat(libxl::NUMFORMAT_CURRENCY); return fmt; } public: ExcelReportGenerator(const std::vectorOrder orderList) : orders(orderList), book(nullptr) { book libxl::xlCreateXMLBook(); if (!book) { throw std::runtime_error(Failed to create Excel book object.); } // 可以在这里设置License Key如果你有 // book-setKey(...); } ~ExcelReportGenerator() { if (book) { book-release(); } } bool generate() { try { createSummarySheet(); createDetailSheet(); // 可以在这里创建第三个图表工作表如果需要 std::string filename generateFilename(); if (!book-save(filename.c_str())) { std::cerr Save failed: book-errorMessage() std::endl; return false; } std::cout Report generated successfully: filename std::endl; return true; } catch (const std::exception e) { std::cerr Generation error: e.what() std::endl; return false; } } private: void createSummarySheet() { libxl::Sheet* sheet book-addSheet(Summary); if (!sheet) return; libxl::Format* headerFmt createHeaderFormat(); libxl::Format* currencyFmt createCurrencyFormat(); // 标题 sheet-writeStr(0, 0, Monthly Sales Summary, headerFmt); sheet-setMerge(0, 0, 0, 3); // 摘要数据头 sheet-writeStr(2, 0, Metric, headerFmt); sheet-writeStr(2, 1, Value, headerFmt); // 计算并写入摘要数据这里简化计算实际应从orders数据中统计 double totalSales 0.0; for (const auto order : orders) { totalSales order.amount; } double avgOrder orders.empty() ? 0 : totalSales / orders.size(); sheet-writeStr(3, 0, Total Sales); sheet-writeNum(3, 1, totalSales, currencyFmt); sheet-writeStr(4, 0, Number of Orders); sheet-writeNum(4, 1, orders.size()); sheet-writeStr(5, 0, Average Order Value); sheet-writeNum(5, 1, avgOrder, currencyFmt); // 设置列宽 sheet-setCol(0, 0, 25); // A列宽25 sheet-setCol(1, 1, 15); // B列宽15 } void createDetailSheet() { libxl::Sheet* sheet book-addSheet(Detail); if (!sheet) return; libxl::Format* headerFmt createHeaderFormat(); libxl::Format* dataFmt book-addFormat(); dataFmt-setBorder(libxl::BORDERSTYLE_THIN); // 表头 const char* headers[] {Order ID, Product, Amount, Date, Region}; for (int col 0; col 5; col) { sheet-writeStr(0, col, headers[col], headerFmt); } // 数据行 for (size_t i 0; i orders.size(); i) { const Order o orders[i]; int row i 1; // 表头在第0行 sheet-writeStr(row, 0, o.orderId.c_str(), dataFmt); sheet-writeStr(row, 1, o.product.c_str(), dataFmt); sheet-writeNum(row, 2, o.amount, dataFmt); sheet-writeStr(row, 3, o.date.c_str(), dataFmt); sheet-writeStr(row, 4, o.region.c_str(), dataFmt); } // 自动调整列宽LibXL没有原生API可以估算 // 这里简单设置一个固定宽度 for (int col 0; col 5; col) { sheet-setCol(col, col, 15); } // 冻结首行窗格方便滚动查看 sheet-setSplit(1, 0); // 在第1行索引1即第二行上方第0列左侧分割 sheet-setSplitFreeze(1, 0); // 冻结分割出的左上窗格 } }; // 主函数示例 int main() { // 模拟数据 std::vectorOrder orders { {ORD001, Laptop, 1200.50, 2024-05-01, North}, {ORD002, Mouse, 25.99, 2024-05-01, South}, {ORD003, Keyboard, 89.99, 2024-05-02, East}, {ORD004, Monitor, 350.00, 2024-05-03, West}, {ORD005, Laptop, 1100.00, 2024-05-03, North}, }; try { ExcelReportGenerator generator(orders); if (!generator.generate()) { std::cerr Report generation failed. std::endl; return 1; } } catch (const std::exception e) { std::cerr Fatal error: e.what() std::endl; return 1; } return 0; }这个示例展示了一个相对完整的结构将Excel操作封装在一个类中提高了代码的可重用性和可维护性。在实际项目中你可以进一步扩展它比如从数据库读取数据、添加更复杂的图表通过库的商业版或后续处理、或者集成到你的应用程序日志系统中。