C++高效读取Excel文件:基于libxlsxreader的跨平台解决方案

发布时间:2026/7/24 12:32:09
C++高效读取Excel文件:基于libxlsxreader的跨平台解决方案 1. 项目概述为什么我们需要一个C读取Excel的轮子在数据处理这个行当里Excel文件就像空气一样无处不在。无论是财务对账、实验数据分析还是游戏配置表、设备日志导出.xlsx或.xls后缀的文件总是绕不开的一环。作为一名长期在C领域摸爬滚打的开发者我无数次面临这样的场景一个核心的后台服务或高性能计算模块需要直接消费业务方提供的Excel数据。这时常见的做法是让业务方先手动导出为CSV或者写个Python脚本做转换。但流程一复杂沟通成本和出错率就上来了更别提在那些对执行环境有严格限制比如不能安装Python解释器的嵌入式或客户端应用中。市面上成熟的库比如libxlsxwriter只擅长写xlnt虽然读写都支持但文档略显晦涩而直接使用微软的COM接口通过#import则严重依赖Windows环境和Office安装移植性几乎为零。所以打造一个轻量、高效、跨平台且易于集成的C Excel读取工具不是“重复造轮子”而是“打造一个更适合自己车子的轮胎”。这个项目的核心价值在于将数据处理链条中最前端的“数据接入”环节用最贴近系统核心语言的方式固化下来减少外部依赖和中间转换提升整个系统的健壮性和效率。接下来我将分享一个我经过多个项目迭代、踩过无数坑之后总结出的一个相对完整的C读取Excel程序方案。它不追求大而全而是聚焦于“读取”这一核心需求力求代码清晰、依赖明确、拿来即用。我们会从方案选型开始深入到每一个核心模块的源码解析最后分享如何集成到你的项目中以及避坑指南。2. 方案选型与核心库解析面对“C读取Excel”这个问题摆在面前的路径主要有三条每一条的风景和坑洼都大不相同。2.1 路径对比COM、纯解析库与间接转换2.1.1 微软COM自动化接口这是最“原生”也是最“重”的方式。其原理是通过Windows的组件对象模型COM来调用本地安装的Microsoft Excel应用程序的功能。// 伪代码示例展示其繁琐性 #import C:\\Program Files\\Microsoft Office\\Office16\\EXCEL.EXE no_namespace CoInitialize(NULL); _ApplicationPtr pApp; pApp.CreateInstance(__uuidof(Application)); WorkbooksPtr pBooks pApp-Workbooks; _WorkbookPtr pBook pBooks-Open(data.xlsx); // ... 一系列繁琐的VARIANT操作读取单元格优点功能最全能利用Excel所有特性公式、图表、宏等。缺点严重依赖环境必须安装特定版本的Microsoft Office。仅限Windows跨平台是奢望。性能与稳定性需要启动完整的Excel进程开销大在服务器无界面环境下运行极易出错崩溃。编程复杂大量VARIANT、SAFEARRAY和BSTR类型操作代码冗长易错。注意在自动化部署的服务器环境或Docker容器中此方案基本不可行强烈不推荐用于生产级后台服务。2.1.2 使用纯C解析库直接解析.xlsx文件本质是一个ZIP压缩包的XML组件文件或解析二进制的.xls格式。代表库有libxlsxreader轻量级只读专注于.xlsxAPI简洁。xlnt功能更全面的读写库现代C风格但体积和复杂度稍高。OpenXLSX另一个活跃的读写库API设计类似xlnt。BasicExcel一个非常老旧的库仅支持.xls已基本无人维护。2.1.3 通过中间件或命令行工具间接转换例如在C程序中调用system()或创建进程来执行python -c “import pandas; ...或libreoffice --headless --convert-to csv。这本质上将C程序变成了一个“壳”稳定性、性能和安全性都受制于外部工具仅作为临时救急方案。结论对于需要集成到C主程序、要求跨平台、高稳定性和性能的数据读取场景选用纯C解析库是唯一务实的选择。在本项目中我选择libxlsxreader作为核心因为它目标单一只读、代码精炼、跨平台纯C依赖zlib非常适合作为示例和项目基础。2.2 为什么是libxlsxreader专注且轻量它的唯一目标就是高效读取.xlsx文件。核心文件就几个.c/.h不引入复杂的C运行时或模板依赖。零外部运行时依赖除了标准的C库和zlib用于解压没有其他依赖。这意味着你可以轻松地将其源码直接编译进你的项目部署时没有任何额外负担。低内存开销它采用流式解析并非一次性将整个工作表加载到内存这对于处理大文件非常友好。宽松的许可证基于BSD许可证允许商业闭源使用法律风险低。当然它也有局限仅支持.xlsxOffice 2007格式不支持.xls只读对于复杂单元格格式如富文本的支持有限。但对于90%以上的“读取表格数据”需求这已经完全足够。3. 程序架构与核心模块设计一个健壮的Excel读取器不应该只是一个简单的函数调用封装。我们需要考虑错误处理、内存管理、数据转换和易用的接口。下面是我设计的核心类图文字描述和模块职责。3.1 整体架构与类职责整个程序可以划分为三个层次底层封装层ExcelReader直接封装libxlsxreader的C API负责文件的打开关闭、工作表遍历、单元格原始数据读取。这一层处理所有底层错误并将C风格的数据转换为更安全的C类型如std::string。数据表示层Sheet, Row, Cell定义用于在内存中表示表格数据的类。Sheet对应一个工作表包含多个Row每个Row包含多个Cell。Cell类需要能存储不同类型的数据字符串、数字、布尔值、错误、空值。应用接口层提供便捷的接口例如按工作表名获取数据、将整个工作表读取到std::vectorstd::vectorCell中或者提供类似forEachRow的迭代接口。这样的分层使得核心的读取逻辑底层封装与具体的数据使用方式应用接口解耦。未来如果想替换底层库比如换成xlnt只需要重写ExcelReader类上层业务代码几乎不用改动。3.2 核心数据结构Cell的设计Cell类是数据承载的核心设计的好坏直接影响易用性。我采用了std::variant来存储多种可能的数据类型这是C17带来的非常优雅的解决方案。#include variant #include string enum class CellType { Empty, String, Number, Boolean, Error }; class Cell { public: Cell() : type(CellType::Empty), data(std::monostate{}) {} // ... 构造函数、赋值运算符等 CellType getType() const { return type; } std::string toString() const; double toDouble() const; // 转换时需做类型检查和默认值处理 int toInt() const; bool toBool() const; // 示例内部数据存储 private: CellType type; std::variantstd::monostate, std::string, double, bool data; };使用std::variant比传统的联合体union或继承体系更安全、更现代。std::monostate用于表示Empty状态。toString(),toDouble()等成员函数内部需要根据type和data的实际类型进行安全的转换并在类型不匹配时提供合理的默认值或抛出异常根据项目需求决定。3.3 错误处理与资源管理C项目 robustness 的关键。我们必须确保即使Excel文件损坏、路径错误或内存不足程序也不会崩溃而是能给出清晰的错误信息。RAII封装文件句柄ExcelReader类的构造函数尝试打开文件析构函数确保关闭。使用std::unique_ptr管理libxlsxreader的xlsxreader指针。class ExcelReader { public: explicit ExcelReader(const std::string filepath); ~ExcelReader(); bool isOpen() const { return workbook_ ! nullptr; } std::string getLastError() const { return lastError_; } private: xlsxreader::workbook* workbook_ nullptr; std::string lastError_; };统一的错误码与异常可选对于可预见的错误如文件不存在、工作表名无效可以定义枚举错误码并通过getLastError查询。对于不可恢复的错误如内存分配失败可以考虑抛出标准异常如std::runtime_error。在本示例中为求简洁我们采用错误码布尔返回值的方式。读取过程中的错误容忍例如当读取某个单元格失败时是跳过该单元格、填入空值还是立即终止整个工作表的读取这需要根据业务逻辑决定。一个常见的策略是记录错误日志并将该单元格标记为Error类型让上层业务决定如何处理。4. 核心源码实现与逐步解析现在让我们深入到代码的肌肉与骨骼中。我将分步骤构建ExcelReader核心类。4.1 步骤一集成libxlsxreader并封装基础API首先你需要获取libxlsxreader的源码通常是一个xlsxreader.h和xlsxreader.c。将其加入你的项目编译。接着创建excel_reader.h开始我们的封装// excel_reader.h #pragma once #include string #include memory #include vector // 前向声明避免直接暴露libxlsxreader头文件 struct xlsxioreader_struct; typedef struct xlsxioreader_struct xlsxioreader; namespace excel { class Sheet; // 前向声明 class ExcelReader { public: // 构造函数尝试打开文件 explicit ExcelReader(const std::string filepath); // 禁止拷贝 ExcelReader(const ExcelReader) delete; ExcelReader operator(const ExcelReader) delete; // 支持移动 ExcelReader(ExcelReader other) noexcept; ExcelReader operator(ExcelReader other) noexcept; // 析构函数确保资源释放 ~ExcelReader(); // 状态查询 bool isOpen() const { return workbook_ ! nullptr; } const std::string getLastError() const { return lastError_; } // 获取所有工作表名 std::vectorstd::string getSheetNames() const; // 按索引或名称获取工作表对象 std::shared_ptrSheet getSheet(size_t index) const; std::shared_ptrSheet getSheet(const std::string name) const; private: xlsxioreader* workbook_ nullptr; std::string lastError_; // 内部初始化函数 bool openWorkbook(const std::string filepath); }; }在excel_reader.cpp中实现构造函数和析构函数// excel_reader.cpp #include excel_reader.h #include xlsxreader.h // libxlsxreader的头文件 #include cassert namespace excel { ExcelReader::ExcelReader(const std::string filepath) { if (!openWorkbook(filepath)) { // 打开失败workbook_为nullptrlastError_已设置 } } bool ExcelReader::openWorkbook(const std::string filepath) { // libxlsxreader的打开函数 workbook_ xlsxioread_open(filepath.c_str()); if (!workbook_) { lastError_ Failed to open file: filepath; return false; } lastError_.clear(); return true; } ExcelReader::~ExcelReader() { if (workbook_) { xlsxioread_close(workbook_); workbook_ nullptr; } } // 移动构造/赋值实现管理资源所有权的转移 ExcelReader::ExcelReader(ExcelReader other) noexcept : workbook_(other.workbook_), lastError_(std::move(other.lastError_)) { other.workbook_ nullptr; } ExcelReader ExcelReader::operator(ExcelReader other) noexcept { if (this ! other) { if (workbook_) xlsxioread_close(workbook_); workbook_ other.workbook_; lastError_ std::move(other.lastError_); other.workbook_ nullptr; } return *this; } std::vectorstd::string ExcelReader::getSheetNames() const { std::vectorstd::string names; if (!workbook_) return names; // 使用libxlsxreader的迭代器API遍历工作表 const char* sheet_name; xlsxioreadersheetlist sheetlist xlsxioread_sheetlist_open(workbook_); while ((sheet_name xlsxioread_sheetlist_next(sheetlist)) ! nullptr) { names.emplace_back(sheet_name); } xlsxioread_sheetlist_close(sheetlist); return names; } }这里的关键点资源管理使用原始指针xlsxioreader*但在析构函数和移动操作中严格管理其生命周期。错误信息lastError_在打开失败时记录原因方便上层查询。const正确性getSheetNames等不修改状态的函数标记为const。移动语义实现了移动构造和移动赋值允许高效地转移文件句柄所有权这在返回对象或放入容器时很有用。4.2 步骤二实现Sheet与Cell数据模型接下来定义Cell和Sheet类。Cell类的实现如前文所述。这里重点看Sheet类它需要利用libxlsxreader的sheet读取API。sheet.h:// sheet.h #pragma once #include cell.h #include memory #include functional namespace excel { class Sheet { public: // 使用工厂模式由ExcelReader创建 static std::shared_ptrSheet create(xlsxioreader* workbook, const std::string name); ~Sheet(); const std::string getName() const { return name_; } // 迭代读取对每一行调用回调函数参数为行号从0开始和该行的单元格数据向量 bool forEachRow(const std::functionvoid(int, const std::vectorCell) callback) const; // 一次性读取整个工作表慎用于大文件 std::vectorstd::vectorCell readAll() const; private: // 私有构造函数强制使用create工厂函数 Sheet(xlsxioreader* workbook, const std::string name); xlsxioreader* workbook_; // 不拥有所有权由ExcelReader管理 xlsxioreadersheet sheet_handle_ nullptr; std::string name_; }; }sheet.cpp的核心在于forEachRow的实现它展示了如何流式读取数据// sheet.cpp #include sheet.h #include xlsxreader.h #include cstring namespace excel { Sheet::Sheet(xlsxioreader* workbook, const std::string name) : workbook_(workbook), name_(name) { sheet_handle_ xlsxioread_sheet_open(workbook_, name.c_str(), XLSXIOREAD_SKIP_EMPTY_ROWS); // 注意如果工作表名不存在sheet_handle_可能为nullptr } Sheet::~Sheet() { if (sheet_handle_) { xlsxioread_sheet_close(sheet_handle_); } } std::shared_ptrSheet Sheet::create(xlsxioreader* workbook, const std::string name) { auto sheet std::make_sharedSheet(workbook, name); if (!sheet-sheet_handle_) { return nullptr; // 创建工作表失败 } return sheet; } bool Sheet::forEachRow(const std::functionvoid(int, const std::vectorCell) callback) const { if (!sheet_handle_ || !callback) return false; char* value; int row_num -1; std::vectorCell current_row_cells; // libxlsxreader按单元格迭代需要我们自己组装成行 while (xlsxioread_sheet_next_cell(sheet_handle_)) { int this_row xlsxioread_sheet_get_row(sheet_handle_); if (row_num ! this_row) { // 遇到新的一行先处理上一行如果有的话 if (row_num ! -1) { callback(row_num, current_row_cells); current_row_cells.clear(); } row_num this_row; } // 读取当前单元格的值 value xlsxioread_sheet_get_value(sheet_handle_); // 这里需要根据value的内容和类型可通过其他API判断来创建合适的Cell对象 // 例如判断是否为数字 Cell cell; if (value) { // 简单演示尝试转换为数字 char* endptr; double num strtod(value, endptr); if (*endptr \0) { // 整个字符串成功转换为数字 cell Cell(num); } else { // 作为字符串处理 cell Cell(std::string(value)); } // 注意需要根据xlsxioread_sheet_get_cell_type等API更精确判断类型 } else { // 空单元格 cell Cell(); // 默认为Empty类型 } current_row_cells.push_back(std::move(cell)); } // 处理最后一行 if (row_num ! -1 !current_row_cells.empty()) { callback(row_num, current_row_cells); } // 重置sheet读取指针以便下次读取 xlsxioread_sheet_close(sheet_handle_); sheet_handle_ xlsxioread_sheet_open(workbook_, name_.c_str(), XLSXIOREAD_SKIP_EMPTY_ROWS); return true; } }实操心得libxlsxreader的xlsxioread_sheet_next_cell是按单元格顺序迭代的而不是按行。因此我们需要在代码中维护当前行号并手动将单元格组装成行。这是使用该库时的一个关键细节处理不好会导致数据错乱。4.3 步骤三完善ExcelReader并连接Sheet现在在ExcelReader类中实现getSheet方法// 在excel_reader.cpp中补充 #include sheet.h namespace excel { std::shared_ptrSheet ExcelReader::getSheet(size_t index) const { auto names getSheetNames(); if (index names.size()) { lastError_ Sheet index out of range.; return nullptr; } return getSheet(names[index]); } std::shared_ptrSheet ExcelReader::getSheet(const std::string name) const { if (!workbook_) { lastError_ Workbook is not open.; return nullptr; } auto sheet Sheet::create(workbook_, name); if (!sheet) { lastError_ Sheet \ name \ not found or cannot be opened.; } return sheet; } }4.4 步骤四编写使用示例与测试最后我们编写一个main.cpp来演示如何使用这个封装好的库// main.cpp #include excel_reader.h #include iostream #include iomanip int main(int argc, char* argv[]) { if (argc 2) { std::cerr Usage: argv[0] path_to_excel.xlsx std::endl; return 1; } std::string filepath argv[1]; excel::ExcelReader reader(filepath); if (!reader.isOpen()) { std::cerr Error opening file: reader.getLastError() std::endl; return 1; } std::cout File opened successfully.\n; auto sheetNames reader.getSheetNames(); std::cout Sheets in file: ; for (const auto name : sheetNames) { std::cout name ; } std::cout \n\n; // 读取第一个工作表 if (!sheetNames.empty()) { auto sheet reader.getSheet(sheetNames[0]); if (sheet) { std::cout Reading sheet: \ sheet-getName() \\n; std::cout ----------------------------------------\n; sheet-forEachRow([](int rowIndex, const std::vectorexcel::Cell rowCells) { std::cout Row std::setw(3) rowIndex : ; for (const auto cell : rowCells) { std::cout [ cell.toString() ] ; } std::cout \n; }); std::cout ----------------------------------------\n; } else { std::cerr Failed to open sheet: reader.getLastError() std::endl; } } // 示例读取特定单元格区域通过扩展Sheet类实现 // auto data sheet-readRange(A1:C10); return 0; }编译并运行假设使用gg -stdc17 -I. -o excel_reader_demo main.cpp excel_reader.cpp sheet.cpp cell.cpp xlsxreader.c -lz ./excel_reader_demo ./test_data.xlsx如果一切顺利你将看到终端打印出Excel文件中第一个工作表的所有数据。5. 高级话题、性能优化与避坑指南一个基础可用的读取器已经完成但要投入生产环境还需要考虑更多。5.1 处理大文件与内存优化libxlsxreader本身是流式解析内存友好。但我们的readAll()接口一次性读取所有数据在面对数万行的数据时会导致巨大的std::vector内存分配。策略一坚持使用迭代接口始终使用forEachRow并在回调函数中即时处理每一行数据如插入数据库、进行实时计算处理完即释放这样内存中最多只保留一行的数据。策略二分块读取可以扩展Sheet类增加readChunk(int startRow, int numRows)方法每次只读取一部分。策略三智能缓存对于需要频繁随机访问的数据可以实现一个LRU缓存只将最近访问的行留在内存中。5.2 数据类型精确判断与转换前面的示例中我们简单地用strtod判断数字这很不精确。Excel单元格有明确的类型属性。libxlsxreader提供了xlsxioread_sheet_get_cell_type函数应优先使用它XLSXIOREAD_CELLTYPE cell_type xlsxioread_sheet_get_cell_type(sheet_handle_); switch (cell_type) { case XLSXIOREAD_CELLTYPE_TEXT: // 文本即使是“123” cell Cell(std::string(value)); break; case XLSXIOREAD_CELLTYPE_NUMBER: // 数字 cell Cell(strtod(value, nullptr)); break; case XLSXIOREAD_CELLTYPE_BOOLEAN: // 布尔值value可能是“TRUE”或“FALSE” cell Cell(strcmp(value, TRUE) 0); break; case XLSXIOREAD_CELLTYPE_DATE: // 日期value是ISO格式字符串如“2023-10-27” // 需要自己解析为time_t或std::chrono::system_clock::time_point // cell Cell(parseDate(value)); break; case XLSXIOREAD_CELLTYPE_ERROR: case XLSXIOREAD_CELLTYPE_EMPTY: default: cell Cell(); // Empty break; }重要提示Excel内部存储的数字和日期都是浮点数OLE Automation Datelibxlsxreader已经帮我们转换成了字符串。对于日期你需要将类似“45005.5”的字符串表示从1899-12-30开始的天数转换为你需要的日期格式。这是一个常见的坑。5.3 编码与特殊字符处理Excel文件可能包含UTF-8编码的文本。libxlsxreader返回的字符串是UTF-8编码的。如果你的程序运行在Windows默认编码可能是本地代码页如GBK上直接输出到控制台可能会出现乱码。你需要进行转换或者确保你的终端和支持UTF-8。// Windows下控制台输出UTF-8示例需要设置控制台代码页 #ifdef _WIN32 #include windows.h SetConsoleOutputCP(CP_UTF8); #endif对于字符串内部处理建议在程序内部统一使用std::string并视其内容为UTF-8仅在需要与特定API交互时进行转换。5.4 线程安全考虑libxlsxreader的文档并未明确声明其线程安全性。通常一个xlsxioreader对象不应被多个线程同时操作。安全的做法是每个线程使用独立的ExcelReader实例即使读取同一个文件也各自打开。操作系统会缓存文件开销可接受。外部加锁如果必须共享一个ExcelReader实例则在调用其方法特别是getSheet和forEachRow时使用互斥锁std::mutex进行保护。注意锁的粒度要合理。5.5 编译与跨平台部署依赖确保目标系统安装了zlib开发库。在Ubuntu上sudo apt-get install zlib1g-dev在CentOS上sudo yum install zlib-devel。Windows上可以下载预编译的zlib或使用vcpkg/Conan管理。编译命令如前所示需要链接-lz。静态链接为了部署简便可以将xlsxreader.c和zlib的源码一起静态编译进你的程序生成一个无额外依赖的可执行文件。CMake集成创建一个CMakeLists.txt来管理项目是更专业的选择它能自动查找zlib并方便地集成到更大的项目中。cmake_minimum_required(VERSION 3.10) project(ExcelReaderDemo LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(ZLIB REQUIRED) # 查找zlib add_library(libxlsxreader STATIC xlsxreader.c) target_link_libraries(libxlsxreader PRIVATE ZLIB::ZLIB) target_include_directories(libxlsxreader PUBLIC .) add_executable(excel_reader_demo main.cpp excel_reader.cpp sheet.cpp cell.cpp) target_link_libraries(excel_reader_demo PRIVATE libxlsxreader)6. 常见问题排查与实战技巧在实际使用中你肯定会遇到各种各样的问题。下面是我总结的一些典型场景和解决方法。6.1 问题速查表问题现象可能原因排查步骤与解决方案编译时链接错误提示undefined reference to inflate等zlib函数未正确链接zlib库1. 检查是否安装了zlib开发包。2. 确保编译命令末尾加了-lz。3. 检查库路径-L是否正确。程序运行崩溃在xlsxioread_open处段错误1. 文件路径错误或无权访问。2. 文件不是有效的.xlsx格式。3. 内存不足。1. 打印filepath确认路径正确检查文件权限。2. 用解压软件尝试打开.xlsx文件应能解压出一堆XML。3. 在打开文件前和后进行简单内存日志记录。读取到的中文或其他非ASCII字符是乱码终端或程序输出编码不匹配1. 确认libxlsxreader返回的是UTF-8。2. 在Windows控制台使用chcp 65001并设置合适的字体。3. 在代码中将UTF-8字符串转换为本地编码后再输出不推荐建议内部统一UTF-8。forEachRow回调中行数据错位如第一列数据跑到了第二列未正确处理空单元格libxlsxreader默认会跳过空单元格。你需要根据xlsxioread_sheet_get_col返回的列索引来将单元格放入rowCells向量的正确位置而不是简单地push_back。读取数字时小数精度丢失或格式不对使用了简单的字符串转换使用std::stod或strtod_l进行更精确的转换。注意本地化设置如小数点可能是‘,’最好使用C本地化环境setlocale(LC_NUMERIC, C);。程序能打开.xlsx但读取不到任何数据工作表名称可能有空格或特殊字符或文件内部结构特殊1. 打印getSheetNames()看返回的名称是否与你预期一致。2. 尝试用索引getSheet(0)读取。3. 使用其他工具如Python pandas读取同一文件对比结果。在多线程环境下使用数据读取不稳定libxlsxreader对象非线程安全为每个线程创建独立的ExcelReader实例或者在对共享实例进行操作时加锁。6.2 实战技巧读取特定区域与性能基准测试技巧一实现readRange功能libxlsxreader没有直接读取单元格区域的API。我们可以基于forEachRow来实现std::vectorstd::vectorCell Sheet::readRange(const std::string rangeStr) const { // 解析rangeStr例如 A1:C10 // 将A1, C10转换为行号列号0-based int startRow, startCol, endRow, endCol; if (!parseRange(rangeStr, startRow, startCol, endRow, endCol)) { return {}; } std::vectorstd::vectorCell result(endRow - startRow 1); // 遍历所有行但只收集指定区域内的单元格 this-forEachRow([](int rowIdx, const std::vectorCell rowCells) { if (rowIdx startRow rowIdx endRow) { // 这里需要根据单元格的列索引进行筛选和填充 // 注意处理行内空单元格导致的列索引不连续问题 } }); return result; }技巧二进行简单的性能基准测试在处理大型文件前了解性能瓶颈很重要。#include chrono auto start std::chrono::high_resolution_clock::now(); sheet-forEachRow([](int, const std::vectorCell row) { // 模拟处理比如计数 totalRows; for (const auto cell : row) { totalCells; } }); auto end std::chrono::high_resolution_clock::now(); auto duration std::chrono::duration_caststd::chrono::milliseconds(end - start); std::cout Processed totalRows rows, totalCells cells in duration.count() ms.\n;在我的测试中读取一个包含10万行*10列100万单元格纯文本的.xlsx文件libxlsxreader迭代一遍大约在2-3秒SSD硬盘内存占用极低。性能瓶颈通常从“流式处理”转向你自己的业务逻辑。6.3 扩展方向添加写入功能与更丰富的API如果你还需要写入功能这个项目可以作为一个很好的起点。你有两个选择引入libxlsxwriter它是一个专门用于写入.xlsx文件的C库与libxlsxreader风格类似。你可以在你的项目中同时包含这两个库分别负责读和写。切换到xlnt它是一个功能更全面的C14库同时支持读写。将本项目底层的ExcelReader替换为对xlnt的封装上层的Sheet/Cell接口可以尽量保持不变减少对业务代码的影响。此外你还可以考虑为Cell类添加更多实用方法如getFormula()获取公式、getStyle()获取字体、颜色等样式信息不过这需要底层库提供相应支持libxlsxreader在这方面的能力比较有限。这个C Excel读取程序源代码从最初的简单封装到如今能稳定处理各种生产环境中的数据导入任务其价值不在于代码本身有多复杂而在于它精准地解决了一个特定场景下的痛点。它就像一把精心打磨的瑞士军刀虽然功能单一但在需要它的时候总是那么顺手和可靠。希望这份详细的拆解和源码能帮助你构建起自己的数据处理工具链让C程序与Excel世界无缝对接。