1. 项目概述为什么C程序员需要操作Excel在数据处理、自动化报告生成或者与业务系统交互的日常开发中C程序员常常会遇到一个看似“跨界”的需求读写Excel文件。你可能会想C不是更擅长底层性能、游戏引擎或者高频交易吗没错但现实是Excel作为全球最普及的数据交换和展示工具之一其.xlsx或.xls文件往往是业务数据的最终载体。无论是从工业软件中导出计算结果还是为财务系统生成复杂的报表亦或是处理大量科学实验数据能够直接操作Excel文件意味着你的C程序可以无缝融入现有的办公自动化流程极大地提升工具的实用性和易用性。然而C标准库并没有提供原生的Excel文件支持。这不像Python有pandas和openpyxl那样开箱即用。在C的世界里我们需要借助第三方库来“翻译”和“构建”Excel文件复杂的内部结构它本质上是一个遵循Open Packaging Conventions的ZIP压缩包里面包含了XML格式的工作表、样式、公式等。这个过程涉及到库的选择、编译环境的配置、API的学习以及一系列内存管理和错误处理的坑。网上资料虽然多但往往零散要么只讲某个库的简单示例要么环境配置步骤缺失让新手望而却步。这篇指南的目的就是为你梳理出一条清晰、可复现的路径。我将以一个从业超过十年的C开发者的视角分享我处理这个需求时积累的经验、踩过的坑和最终验证过的稳定方案。我们将从最核心的库选型开始一步步搭建环境完成从读取、写入到处理样式和公式的完整操作并附上实际项目中总结出的避坑指南。无论你是需要快速实现一个数据导出功能还是打算构建一个复杂的报表引擎这篇文章都能为你提供扎实的起点和可靠的参考。2. 核心库选型与优劣深度解析面对“C操作Excel”这个需求第一步也是最重要的一步就是选择合适的库。选错了库可能会陷入无尽的编译错误、链接失败或者运行时崩溃。下面我详细分析几个主流和备选方案并给出我的最终推荐。2.1 主流方案对比LibXL vs. OpenXLSX目前在纯C环境下有两个库是经过大量项目验证相对成熟稳定的LibXL和OpenXLSX。它们的设计哲学和适用场景有显著区别。LibXL是一个商业库但也提供免费功能受限版本。它的最大优点是接口简单直观、文档齐全、性能出色。它将自己定位为一个轻量级的读写器API设计非常接近你对Excel操作的直觉思维比如book-addSheet(Sheet1)、sheet-writeNum(2, 3, 42.0)。它直接处理.xls和.xlsx格式无需依赖其他大型库如Boost。对于需要快速上手、实现基础读写功能尤其是数值和字符串并且对性能有要求的项目LibXL是首选。其商业授权版本支持公式、图片、图表等高级功能。注意LibXL的免费版本会在生成的Excel文件顶部添加一个提示横幅并且不支持读取/写入公式。这在很多生产环境是不可接受的务必在评估初期就确认授权需求。OpenXLSX是一个新兴的、纯头文件的、MIT协议的开源库。它的最大卖点是现代CC17、零依赖、仅头文件。它完全专注于.xlsx格式底层直接操作ZIP压缩和XML解析。因为其现代C的封装代码风格非常优雅例如使用XLWorkbook wb; wb.load(file.xlsx); auto ws wb.workbook().sheet(Sheet1);。对于追求现代C实践、希望将库直接包含进项目而无需编译链接、且项目主要处理.xlsx格式的开发者来说OpenXLSX极具吸引力。我的选择与理由 对于大多数以功能实现和稳定性优先的工业级或商业项目我倾向于推荐LibXL假设已获得合法授权。原因如下成熟度与稳定性LibXL经过更长时间的市场检验在复杂文件处理和内存管理上更稳健我曾在处理超过10万行数据的文件时LibXL表现出了更好的鲁棒性。调试与支持作为商业库它通常有更明确的错误信息和付费的技术支持渠道。当遇到一个损坏的Excel文件时LibXL提供的错误信息往往比开源库更清晰。功能全面性商业版支持的公式、单元格合并、样式调整等功能非常完善API统一学习曲线平滑。编译与部署简便虽然需要链接库文件但它的依赖干净最终生成的二进制文件部署到客户环境时通常不会引发额外的运行时库问题。当然如果你的项目是开源项目、对授权敏感、或者你个人想学习现代C库设计OpenXLSX是一个绝佳的学习和轻量级应用对象。本指南后续的实操部分将主要以LibXL为例进行讲解因为它的应用范围更广但核心思路和遇到的坑是相通的。2.2 其他方案简析与避坑除了上述两个你可能会搜索到其他方案这里快速点评帮你避坑使用COM组件Windows专属通过#import指令导入Excel的类型库直接调用Excel的COM接口。这是最强大、最原生的方式能调用Excel的所有功能。但强烈不推荐用于新项目原因1) 仅限Windows2) 要求目标机器安装特定版本的Excel3) 代码繁琐异常处理复杂4) 会实际启动Excel进程性能差且可能弹出可见窗口。使用ODBC/ADO读取Excel将Excel文件当作数据库来查询。这仅适用于简单的数据读取且对文件格式如工作表名称有严格要求无法进行写入或格式操作功能非常有限。使用第三方库如xlntxlnt也是一个开源库目标是提供完整的Excel文件操作。但其开发活跃度一度不高社区维护可能不如OpenXLSX在尝试时我遇到过一些边缘Case的崩溃问题对于生产环境需要更谨慎的测试。结论除非你的应用场景被严格限定在Windows且必须与Excel进程深度交互例如控制图表生成动画否则应优先选择独立的库如LibXL或OpenXLSX。3. 开发环境搭建与项目配置实战选定了LibXL接下来就是把它集成到你的项目中。这里以Visual Studio 2022和CMake项目为例展示两种最常用的集成方式。我会详细说明每一步的意图和可能遇到的问题。3.1 方案一Visual Studio IDE内集成适合快速原型下载与解压从LibXL官网下载最新版本例如libxl-4.2.0.zip。解压后你会看到include、lib和bin文件夹。include里是头文件lib里是静态库或导入库bin里是运行时所需的DLL针对Windows。项目属性配置C/C - 常规 - 附加包含目录添加LibXL的include文件夹路径。这一步是让编译器能找到libxl.h等头文件。链接器 - 常规 - 附加库目录添加LibXL的lib文件夹路径。这一步是让链接器知道去哪找库文件。链接器 - 输入 - 附加依赖项根据你的配置添加库文件名。通常Debug模式:libxl.lib(静态库) 或libxl_d.libRelease模式:libxl.lib关键点务必区分Debug和Release的库文件。如果库提供了Debug版本通常带_d后缀一定要在对应配置下链接正确的版本否则可能导致运行时内存分配错误或崩溃。处理运行时依赖DLL将bin文件夹下对应版本的libxl.dll例如libxl.dllfor Release,libxl_d.dllfor Debug复制到你的可执行文件.exe所在的输出目录。否则程序运行时将因找不到DLL而失败。更规范的做法是在项目生成后事件中添加复制命令。3.2 方案二CMake集成推荐便于跨平台与团队协作对于现代C项目使用CMake管理依赖是更优雅的方式。你可以将LibXL作为项目的子模块submodule或通过FetchContent获取。这里展示一种将下载的LibXL包放在项目thirdparty目录下的常见方式# 假设你将 libxl-4.2.0 文件夹解压到了项目根目录的 thirdparty/libxl 下 set(LIBXL_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/libxl) # 添加头文件路径 include_directories(${LIBXL_ROOT_DIR}/include_cpp) # 注意LibXL 有一个 include_cpp 文件夹是对C接口的C封装更易用 # 根据平台和配置选择库文件 if(WIN32) if(CMAKE_BUILD_TYPE STREQUAL Debug) set(LIBXL_LIB ${LIBXL_ROOT_DIR}/lib/libxl_d.lib) else() set(LIBXL_LIB ${LIBXL_ROOT_DIR}/lib/libxl.lib) endif() elseif(APPLE) set(LIBXL_LIB ${LIBXL_ROOT_DIR}/lib/libxl.dylib) # macOS else() set(LIBXL_LIB ${LIBXL_ROOT_DIR}/lib/libxl.so) # Linux endif() # 将库链接到你的目标 add_executable(YourExcelApp main.cpp) target_link_libraries(YourExcelApp ${LIBXL_LIB}) # 在构建后将DLLWindows或SOLinux复制到可执行文件目录 if(WIN32) add_custom_command(TARGET YourExcelApp POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${LIBXL_ROOT_DIR}/bin/libxl.dll $TARGET_FILE_DIR:YourExcelApp ) endif()配置心得路径问题CMake中路径最好使用绝对路径或相对于CMAKE_CURRENT_SOURCE_DIR的路径避免在不同机器上构建失败。区分C/C接口LibXL提供了C接口的头文件在include而C封装接口在include_cpp。我强烈建议使用include_cpp下的头文件如libxl.hpp因为它使用了RAII管理资源用起来更安全、更“C”例如通过Book* book xlCreateBook();创建的对象在C封装中会用std::unique_ptr管理减少内存泄漏风险。跨平台注意虽然LibXL主要支持Windows和Linux/macOS但在非Windows平台使用前务必仔细阅读其自带的readme.txt可能需要安装额外的依赖如libzip、libxml2。4. 核心API详解与基础读写操作环境配好了让我们开始真正的编码。我会按照一个典型的数据处理流程创建/加载工作簿 - 获取工作表 - 读写单元格 - 保存文件来讲解核心API。4.1 工作簿与工作表一切的起点所有操作都始于一个Book对象它代表整个Excel文件。#include libxl.h #include iostream #include memory // 为了使用std::unique_ptr using namespace libxl; int main() { // 1. 创建或加载工作簿 // 使用C封装利用智能指针自动管理生命周期 std::unique_ptrBook book(xlCreateBook()); if (!book) { std::cerr Failed to create Excel book object! std::endl; return -1; } // 尝试加载一个已存在的文件 // bool loaded book-load(existing_data.xlsx); // if (!loaded) { // std::cerr Failed to load file: book-errorMessage() std::endl; // // 可以选择在这里创建一个新的 // } // 2. 添加或获取工作表 Sheet* sheet book-addSheet(月度报告); // 添加新工作表 // Sheet* sheet book-getSheet(0); // 通过索引获取第一个工作表 // Sheet* sheet book-getSheet(Sheet1); // 通过名称获取工作表 if (!sheet) { std::cerr Failed to create or get sheet! std::endl; return -1; } // ... 后续操作 // 3. 保存工作簿 if (book-save(output_report.xlsx)) { std::cout Excel file saved successfully. std::endl; } else { std::cerr Failed to save file: book-errorMessage() std::endl; return -1; } // unique_ptr 会在退出作用域时自动调用 xlBookRelease(book.get())无需手动释放 return 0; }关键点解析错误处理LibXL的许多函数返回布尔值表示成功与否。务必检查返回值并通过book-errorMessage()获取错误信息这在调试时至关重要。资源管理使用C接口时必须手动调用xlBookRelease()来释放Book对象。而使用C封装libxl.hpp并配合std::unique_ptr可以借助RAII自动释放安全又省心。这是避免内存泄漏的最佳实践。工作表指针addSheet和getSheet返回的是Sheet*这个指针的生命周期由Book对象管理我们不需要也不应该手动删除它。4.2 单元格操作读写数据与格式有了工作表指针我们就可以像操作二维数组一样操作单元格了。行和列的索引都是从0开始。// 假设 sheet 是有效的 Sheet* 指针 // 1. 写入不同类型的数据 sheet-writeStr(2, 1, 产品名称); // 第3行第2列 (C3单元格)写入字符串 sheet-writeNum(3, 1, 1001); // 第4行第2列写入整数 sheet-writeNum(4, 1, 299.99); // 写入浮点数 sheet-writeBool(5, 1, true); // 写入布尔值 sheet-writeFormula(6, 1, SUM(C4:C5)); // 写入公式商业版支持 // 写入一个日期/时间 (需要转换为Excel内部的日期序列值) // LibXL 提供了辅助函数 double dateTime book-datePack(2023, 10, 27, 14, 30, 0); // 2023-10-27 14:30:00 sheet-writeNum(7, 1, dateTime); // 为了正确显示为日期格式通常需要同时设置单元格格式见下文 // 2. 读取单元格数据 // 首先判断单元格类型 CellType cellType sheet-cellType(2, 1); switch(cellType) { case CELLTYPE_STRING: { const char* s sheet-readStr(2, 1); if(s) std::cout String: s std::endl; break; } case CELLTYPE_NUMBER: { double d sheet-readNum(2, 1); std::cout Number: d std::endl; break; } case CELLTYPE_BOOLEAN: { bool b sheet-readBool(2, 1); std::cout Boolean: std::boolalpha b std::endl; break; } case CELLTYPE_EMPTY: std::cout Cell is empty. std::endl; break; case CELLTYPE_ERROR: std::cout Cell contains an error. std::endl; break; default: break; } // 3. 设置单元格格式样式 Format* boldFormat book-addFormat(); boldFormat-setBold(); // 设置为粗体 boldFormat-setBorder(BORDERSTYLE_THIN); // 设置细边框 boldFormat-setAlignH(ALIGNH_CENTER); // 水平居中 sheet-writeStr(0, 0, 标题, boldFormat); // 写入内容并应用格式 // 设置数字格式例如货币、百分比、日期 Format* dateFormat book-addFormat(); dateFormat-setNumFormat(NUMFORMAT_DATE); // 设置为日期格式 // 更精确的格式代码例如 yyyy-mm-dd hh:mm // dateFormat-setNumFormat(yyyy-mm-dd hh:mm); sheet-writeNum(7, 1, dateTime, dateFormat); // 写入日期值并应用日期格式 // 设置列宽和行高 sheet-setCol(1, 1, 15.0); // 设置第2列索引1宽度为15个字符单位 sheet-setRow(0, 25.0); // 设置第1行索引0高度为25个点操作心得与陷阱索引从0开始这是很多从Excel GUI操作转过来的开发者容易混淆的地方。sheet-writeStr(0, 0, ...)对应的是A1单元格。读取前检查类型直接调用readStr读取一个数字单元格会得到nullptr或空字符串。务必先用cellType检查类型这是避免程序崩溃或数据错误的关键。格式对象的管理Format对象也是由Book创建和管理的。一个Format可以被多个单元格共享这比为每个单元格创建新格式要高效得多。日期处理Excel内部将日期存储为浮点数整数部分代表自1900年1月0日以来的天数小数部分代表一天中的时间。book-datePack()和book-dateUnpack()是进行转换的得力助手。写入日期数值后必须设置对应的数字格式否则它只会显示为一个普通的数字。性能考虑在写入大量数据如上万行时频繁调用writeStr等函数可能会有性能开销。如果遇到性能瓶颈可以考虑使用sheet-writeRow或sheet-writeCol如果库支持进行批量写入或者评估是否需要在内存中构建好数据再一次性写入。5. 高级功能与实战技巧掌握了基础读写我们来看看一些更高级但同样常见的需求。5.1 处理公式与函数LibXL商业版支持公式的读写。写入公式就像写入一个以开头的特殊字符串。// 假设在C列索引2有价格D列索引3有数量我们在E列索引4计算总价 for(int row 1; row 10; row) { // 假设数据从第2行开始 // 写入公式例如 E2 C2 * D2 // 注意公式中的单元格引用是Excel风格的A1表示法不是行列索引。 std::string formula C std::to_string(row1) *D std::to_string(row1); sheet-writeFormula(row, 4, formula.c_str()); // 第4列是E列 // 更复杂的公式例如带SUM // sheet-writeFormula(11, 4, SUM(E2:E10)); } // 读取公式单元格的结果 // 注意LibXL是一个文件读写库不是Excel计算引擎。 // 它只能写入公式字符串或者读取文件中已存在的公式及其缓存的计算结果。 // 如果你用LibXL创建了一个带公式的文件用Excel打开时会自动计算。 // 用LibXL读取一个由Excel保存的、包含公式的文件时可以读到公式字符串和缓存值。 CellType type sheet-cellType(1, 4); if(type CELLTYPE_FORMULA) { const char* formulaStr sheet-readFormula(1, 4); if(formulaStr) std::cout Formula: formulaStr std::endl; // 尝试读取缓存的计算结果如果有的话 if(sheet-isFormula(1, 4)) { double result; if(sheet-readNum(1, 4, result)) { std::cout Cached result: result std::endl; } } }重要限制LibXL不计算公式。它只负责存储和检索公式字符串。公式的计算依赖于Excel、LibreOffice等客户端软件。这意味着如果你用LibXL生成一个包含A1B1公式的文件然后用LibXL直接读取这个文件未用Excel打开过cellType可能仍然是CELLTYPE_FORMULA但你可能读不到计算结果缓存值为空。只有被Excel计算并保存后结果才会被缓存并可用readNum等读取。5.2 合并单元格与样式进阶制作报表时合并单元格和复杂样式是家常便饭。// 1. 合并单元格 // 合并 A1 到 D1 sheet-setMerge(0, 0, 0, 3); // (startRow, startCol, endRow, endCol) Format* headerFormat book-addFormat(); headerFormat-setBold(); headerFormat-setAlignH(ALIGNH_CENTER); headerFormat-setFillPattern(FILLPATTERN_SOLID); headerFormat-setPatternForegroundColor(COLOR_GRAY25); // 设置背景色 sheet-writeStr(0, 0, 2023年第四季度销售报告, headerFormat); // 内容写在合并区域的左上角单元格 // 2. 设置字体、颜色、边框 Format* highlightFormat book-addFormat(); Font* font book-addFont(); font-setName(微软雅黑); // 设置字体 font-setSize(12); font-setColor(COLOR_RED); font-setBold(); highlightFormat-setFont(font); highlightFormat-setBorder(BORDERSTYLE_MEDIUM, COLOR_BLACK); // 设置所有边框为中粗线、黑色 // 也可以单独设置各边边框 // highlightFormat-setBorderLeft(BORDERSTYLE_THIN, COLOR_BLUE); highlightFormat-setFillPattern(FILLPATTERN_SOLID); highlightFormat-setPatternForegroundColor(COLOR_YELLOW); sheet-writeStr(2, 0, 重点数据, highlightFormat); // 3. 设置数字格式会计格式、百分比等 Format* accountingFormat book-addFormat(); accountingFormat-setNumFormat(NUMFORMAT_ACCOUNTING); // 会计格式如 $#,##0.00 // 自定义格式代码 Format* customNumFormat book-addFormat(); customNumFormat-setNumFormat(0.00%); // 显示为百分比两位小数 sheet-writeNum(3, 2, 0.856, customNumFormat); // 会显示为 85.60%样式设置经验顺序问题通常先创建Font或Format对象设置好属性再将其应用到单元格。一个Format可以包含字体、对齐、边框、填充等所有样式信息。颜色常量LibXL定义了一系列颜色常量如COLOR_RED,COLOR_BLUE,COLOR_GRAY25等。对于更自定义的颜色商业版可能支持RGB设置。性能提示创建大量唯一的Format对象尤其是每个单元格都不同会增加文件大小和内存占用。尽量复用格式对象。5.3 遍历与批量数据处理当需要读取或处理整个工作表的数据时我们需要遍历。// 获取工作表已使用的范围 int firstRow 0, lastRow -1, firstCol 0, lastCol -1; if (sheet-getPrintArea(firstRow, lastRow, firstCol, lastCol)) { // getPrintArea 获取的是打印区域有时能反映数据区域 } else { // 更通用的方法是使用 firstFilledRow/Col 和 lastFilledRow/Col (LibXL 4.0) // 或者自己遍历判断 lastRow sheet-lastRow(); lastCol sheet-lastCol(); } std::cout Data range: Row firstRow to lastRow , Col firstCol to lastCol std::endl; // 遍历所有单元格 for (int row firstRow; row lastRow; row) { for (int col firstCol; col lastCol; col) { CellType ct sheet-cellType(row, col); if (ct CELLTYPE_EMPTY) continue; // 根据类型处理数据... // 可以将数据读取到 std::vectorstd::vectorstd::string 或自定义数据结构中 } } // 批量写入数据示例写入一个二维向量 std::vectorstd::vectorstd::string data { {Name, Age, City}, {Alice, 30, New York}, {Bob, 25, London} }; int startRow 5; for (size_t i 0; i data.size(); i) { for (size_t j 0; j data[i].size(); j) { sheet-writeStr(startRow i, j, data[i][j].c_str()); } }遍历注意事项lastRow()和lastCol()返回的是有记录的行列索引的最大值但不一定是连续的数据区域。中间可能有空行空列。遍历大量数据时注意I/O和函数调用开销。如果性能是关键可以考虑将数据先读入内存结构如std::vector再进行处理而不是在嵌套循环中频繁进行业务逻辑判断。6. 常见问题、错误排查与性能优化即使按照指南操作在实际项目中你还是会遇到各种问题。下面是我总结的一些典型坑和解决方案。6.1 编译与链接错误错误LNK2019: 无法解析的外部符号这是最常见的问题。检查库版本确保链接的库文件.lib的位数32/64与你的项目配置完全一致。Debug/Release模式也要对应。检查运行时库在VS项目属性中C/C - 代码生成 - 运行时库的设置如/MDd,/MD也需要与库的编译选项匹配。通常下载的预编译库是/MD或/MDd的。如果不匹配尝试在项目属性中切换运行时库设置。检查函数签名确保你包含的是正确的头文件libxl.hvslibxl.hpp并且调用函数的方式与库的版本匹配。有时库的版本升级会导致API微调。错误找不到 libxl.dll程序编译成功但运行时崩溃。将libxl.dll或libxl_d.dll放置到可执行文件同级目录或者放到系统PATH包含的目录中。最可靠的方式是在项目生成后事件中复制。6.2 运行时错误与数据异常写入文件失败(book-save返回false)检查文件路径确保目标目录存在且有写入权限。路径中避免使用中文或特殊字符进行测试。检查文件是否被占用如果尝试覆盖一个已被其他程序如Excel打开的文件保存会失败。在保存前可以先尝试删除旧文件如果存在但要注意数据安全。获取错误信息立即使用book-errorMessage()打印错误描述这是最直接的线索。读取的数据乱码或为空字符编码Excel文件内部可能使用不同的编码。LibXL默认使用UTF-8。如果你写入的中文在Excel中显示乱码尝试在创建Book时设置编码如果API支持。对于读取确保你的程序能正确处理UTF-8字符串。在Windows控制台直接输出UTF-8可能会乱码这是控制台的问题不是库的问题。单元格类型判断再次强调读取前必须用cellType判断类型。一个看起来是数字的单元格可能是以字符串形式存储的例如以开头的数字。程序在处理大文件时崩溃或内存泄漏使用RAII坚持使用C封装libxl.hpp和智能指针。分块处理对于极大的文件如几十万行不要试图一次性将所有数据读入内存。可以设计流式读取一次处理一部分。检查循环中的资源创建避免在循环内部频繁创建Format或Font对象。应在循环外创建并复用。6.3 性能优化建议减少API调用次数批量操作比单点操作快。例如先在一个内存数据结构中构建好一整行的数据然后考虑是否能用一次调用写入如果库支持批量接口。如果不支持至少确保循环内部逻辑高效。复用格式对象如前所述创建成千上万个仅细微差别的Format对象是巨大的开销。定义几个常用的格式如标题格式、数据格式、高亮格式然后在整个工作表中复用它们。禁用自动计算如适用如果你使用商业版并写入大量公式在写入前可以尝试调用book-setCalcMode(CALCMODE_MANUAL)之类的函数如果存在来禁止库进行任何可能的预处理计算写入完成后再设为CALCMODE_AUTO。明智地使用getPrintArea和lastRow/Col在遍历前准确获取数据区域避免遍历成千上万个空单元格。6.4 一个完整的调试案例读取文件并打印内容下面是一个健壮的、包含错误处理的完整示例它演示了如何安全地读取一个Excel文件并打印其内容。#include libxl.h #include iostream #include memory #include iomanip using namespace libxl; bool readAndPrintExcel(const std::string filename) { // 使用unique_ptr管理Book资源 std::unique_ptrBook book(xlCreateBook()); if (!book) { std::cerr Error: Could not create Book object. std::endl; return false; } // 1. 加载文件 if (!book-load(filename.c_str())) { std::cerr Error loading file: book-errorMessage() std::endl; return false; } // 2. 遍历所有工作表 int sheetCount book-sheetCount(); std::cout File contains sheetCount sheet(s). std::endl; for (int s 0; s sheetCount; s) { Sheet* sheet book-getSheet(s); if (!sheet) { std::cerr Warning: Could not access sheet at index s std::endl; continue; } std::cout \n--- Sheet: sheet-name() --- std::endl; // 3. 获取大致的数据范围简化处理 int lastRow sheet-lastRow(); int lastCol sheet-lastCol(); if (lastRow 0 || lastCol 0) { std::cout (Sheet is empty) std::endl; continue; } // 4. 遍历并打印单元格内容 for (int r 0; r lastRow; r) { for (int c 0; c lastCol; c) { CellType ct sheet-cellType(r, c); switch (ct) { case CELLTYPE_STRING: { const char* val sheet-readStr(r, c); std::cout std::setw(20) std::left (val ? val : (null)); break; } case CELLTYPE_NUMBER: { double val sheet-readNum(r, c); std::cout std::setw(20) std::left val; break; } case CELLTYPE_BOOLEAN: { bool val sheet-readBool(r, c); std::cout std::setw(20) std::left std::boolalpha val; break; } case CELLTYPE_EMPTY: std::cout std::setw(20) std::left [Empty]; break; case CELLTYPE_ERROR: std::cout std::setw(20) std::left [Error]; break; case CELLTYPE_BLANK: // 通常与EMPTY类似 std::cout std::setw(20) std::left [Blank]; break; case CELLTYPE_FORMULA: { const char* formula sheet-readFormula(r, c); std::cout std::setw(20) std::left (formula ? std::string()formula : [Formula]); // 可以尝试读取缓存值 // double result; // if(sheet-readNum(r, c, result)) { std::cout ( result ); } break; } default: std::cout std::setw(20) std::left [Unknown]; break; } if (c lastCol) std::cout | ; } std::cout std::endl; // 换行 } } std::cout \n--- End of file --- std::endl; return true; } int main(int argc, char* argv[]) { std::string fileToRead test_input.xlsx; // 默认文件 if (argc 1) { fileToRead argv[1]; } if (!readAndPrintExcel(fileToRead)) { std::cerr Failed to process the Excel file. std::endl; return 1; } return 0; }这个示例展示了错误检查、资源管理、类型安全读取和格式化输出的完整流程你可以直接以此为基础进行修改和扩展。记住处理外部文件和数据时稳健的错误处理不是可选项而是必选项。