在C语言中操作Excel文件是一项常见且重要的任务,特别是在数据处理和科学计算领域。尽管C语言本身并不直接支持Excel文件的读写操作,但通过多种方法和技术,我们仍然可以高效地完成这项任务。本文将详细介绍几种常用的C语言操作Excel文件的方法,包括使用CSV格式、调用COM接口、使用第三方库等,并对每种方法的优缺点进行深入分析。
1. 使用CSV格式
CSV(Comma-Separated Values)是一种简单的文本格式,用于存储表格数据。通过将Excel文件另存为CSV格式,我们可以使用标准的文件操作函数来读写Excel数据。
1.1 写操作
#include <stdio.h>
void writeExcel() {
char chy[4] = {'x', 'a', 'h', 'w'};
int data[4] = {1, 3, 6, 9};
int i;
FILE *fp = NULL;
fp = fopen("test.csv", "w");
for (i = 0; i < 4; i++) {
fprintf(fp, "%c,%d\n", chy[i], data[i]);
}
fclose(fp);
}
int main() {
writeExcel();
return 0;
}
1.2 读操作
#include <stdio.h>
int main() {
FILE *fp;
char filename[40];
int i, j;
float da[6][5] = {0};
printf("输入文件名: ");
scanf("%s", filename);
fp = fopen(filename, "r");
for (i = 0; i < 6; i++) {
for (j = 0; j < 5; j++) {
fscanf(fp, "%f,", &da[i][j]);
}
}
for (i = 0; i < 6; i++) {
for (j = 0; j < 5; j++) {
printf("%f\t", da[i][j]);
}
printf("\n");
}
fclose(fp);
return 0;
}
优点:
- 简单易用:不需要额外的库或工具,只需使用标准的文件操作函数。
- 兼容性强:可以在多种平台上使用,不受操作系统的限制。
缺点:
- 功能有限:不支持复杂的Excel功能,如公式、图表等。
- 数据格式单一:无法保留原始Excel文件的样式和格式。
适用场景:
- 简单数据导入导出:适用于只需要导入导出简单数据的场景。
- 数据预处理:在数据预处理阶段,可以将Excel数据转换为CSV格式,以便后续处理。
2. 调用COM接口
COM(Component Object Model)接口允许C语言程序与Excel应用程序进行交互。通过COM接口,我们可以直接操作Excel文件,执行复杂的操作。
2.1 示例代码
#include <windows.h>
#include <ole2.h>
#include <stdio.h>
void writeExcel(const char *filename) {
HRESULT hr;
CLSID clsid;
IDispatch *pXlApp = NULL;
IDispatch *pXlBooks = NULL;
IDispatch *pXlBook = NULL;
IDispatch *pXlSheet = NULL;
// 初始化COM库
CoInitialize(NULL);
// 获取Excel应用程序的CLSID
CLSIDFromProgID(L"Excel.Application", &clsid);
// 创建Excel应用程序对象
hr = CoCreateInstance(&clsid, NULL, CLSCTX_LOCAL_SERVER, &IID_IDispatch, (void **)&pXlApp);
if (FAILED(hr)) {
printf("Failed to create Excel application\n");
return;
}
// 获取工作簿集合
OLECHAR *szBooks = L"Workbooks";
DISPID dispidBooks;
hr = pXlApp->lpVtbl->GetIDsOfNames(pXlApp, &IID_NULL, &szBooks, 1, LOCALE_USER_DEFAULT, &dispidBooks);
if (FAILED(hr)) {
printf("Failed to get Workbooks ID\n");
return;
}
DISPPARAMS paramsNoArgs = {NULL, NULL, 0, 0};
VARIANT resultBooks;
hr = pXlApp->lpVtbl->Invoke(pXlApp, dispidBooks, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶msNoArgs, &resultBooks, NULL, NULL);
if (FAILED(hr)) {
printf("Failed to get Workbooks\n");
return;
}
pXlBooks = resultBooks.pdispVal;
// 创建一个新的工作簿
OLECHAR *szAdd = L"Add";
DISPID dispidAdd;
hr = pXlBooks->lpVtbl->GetIDsOfNames(pXlBooks, &IID_NULL, &szAdd, 1, LOCALE_USER_DEFAULT, &dispidAdd);
if (FAILED(hr)) {
printf("Failed to get Add ID\n");
return;
}
VARIANT resultBook;
hr = pXlBooks->lpVtbl->Invoke(pXlBooks, dispidAdd, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶msNoArgs, &resultBook, NULL, NULL);
if (FAILED(hr)) {
printf("Failed to add Workbook\n");
return;
}
pXlBook = resultBook.pdispVal;
// 获取第一个工作表
OLECHAR *szSheets = L"Worksheets";
DISPID dispidSheets;
hr = pXlBook->lpVtbl->GetIDsOfNames(pXlBook, &IID_NULL, &szSheets, 1, LOCALE_USER_DEFAULT, &dispidSheets);
if (FAILED(hr)) {
printf("Failed to get Worksheets ID\n");
return;
}
VARIANT resultSheets;
hr = pXlBook->lpVtbl->Invoke(pXlBook, dispidSheets, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶msNoArgs, &resultSheets, NULL, NULL);
if (FAILED(hr)) {
printf("Failed to get Worksheets\n");
return;
}
IDispatch *pXlSheets = resultSheets.pdispVal;
OLECHAR *szItem = L"Item";
DISPID dispidItem;
hr = pXlSheets->lpVtbl->GetIDsOfNames(pXlSheets, &IID_NULL, &szItem, 1, LOCALE_USER_DEFAULT, &dispidItem);
if (FAILED(hr)) {
printf("Failed to get Item ID\n");
return;
}
VARIANT index;
index.vt = VT_I4;
index.lVal = 1;
DISPPARAMS paramsItem = {&index, NULL, 1, 0};
VARIANT resultSheet;
hr = pXlSheets->lpVtbl->Invoke(pXlSheets, dispidItem, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶msItem, &resultSheet, NULL, NULL);
if (FAILED(hr)) {
printf("Failed to get Worksheet\n");
return;
}
pXlSheet = resultSheet.pdispVal;
// 写入数据
OLECHAR *szCells = L"Cells";
DISPID dispidCells;
hr = pXlSheet->lpVtbl->GetIDsOfNames(pXlSheet, &IID_NULL, &szCells, 1, LOCALE_USER_DEFAULT, &dispidCells);
if (FAILED(hr)) {
printf("Failed to get Cells ID\n");
return;
}
VARIANT row, col;
row.vt = VT_I4;
col.vt = VT_I4;
row.lVal = 1;
col.lVal = 1;
DISPPARAMS paramsCell = {&row, &col, 2, 0};
VARIANT resultCell;
hr = pXlSheet->lpVtbl->Invoke(pXlSheet, dispidCells, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶msCell, &resultCell, NULL, NULL);
if (FAILED(hr)) {
printf("Failed to get Cell\n");
return;
}
IDispatch *pXlCell = resultCell.pdispVal;
OLECHAR *szValue = L"Value";
DISPID dispidValue;
hr = pXlCell->lpVtbl->GetIDsOfNames(pXlCell, &IID_NULL, &szValue, 1, LOCALE_USER_DEFAULT, &dispidValue);
if (FAILED(hr)) {
printf("Failed to get Value ID\n");
return;
}
VARIANT value;
value.vt = VT_BSTR;
value.bstrVal = SysAllocString(L"Hello, World!");
DISPPARAMS paramsValue = {&value, NULL, 1, 0};
hr = pXlCell->lpVtbl->Invoke(pXlCell, dispidValue, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, ¶msValue, NULL, NULL, NULL);
if (FAILED(hr)) {
printf("Failed to set Cell Value\n");
return;
}
// 保存并关闭
OLECHAR *szSaveAs = L"SaveAs";
DISPID dispidSaveAs;
hr = pXlBook->lpVtbl->GetIDsOfNames(pXlBook, &IID_NULL, &szSaveAs, 1, LOCALE_USER_DEFAULT, &dispidSaveAs);
if (FAILED(hr)) {
printf("Failed to get SaveAs ID\n");
return;
}
VARIANT filename;
filename.vt = VT_BSTR;
filename.bstrVal = SysAllocString(L"output.xlsx");
DISPPARAMS paramsSaveAs = {&filename, NULL, 1, 0};
hr = pXlBook->lpVtbl->Invoke(pXlBook, dispidSaveAs, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶msSaveAs, NULL, NULL, NULL);
if (FAILED(hr)) {
printf("Failed to save Workbook\n");
return;
}
OLECHAR *szQuit = L"Quit";
DISPID dispidQuit;
hr = pXlApp->lpVtbl->GetIDsOfNames(pXlApp, &IID_NULL, &szQuit, 1, LOCALE_USER_DEFAULT, &dispidQuit);
if (FAILED(hr)) {
printf("Failed to get Quit ID\n");
return;
}
hr = pXlApp->lpVtbl->Invoke(pXlApp, dispidQuit, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶msNoArgs, NULL, NULL, NULL);
if (FAILED(hr)) {
printf("Failed to quit Excel application\n");
return;
}
// 释放资源
VariantClear(&value);
SysFreeString(szValue);
VariantClear(&row);
VariantClear(&col);
VariantClear(&resultCell);
VariantClear(&resultSheet);
VariantClear(&resultSheets);
VariantClear(&resultBook);
VariantClear(&resultBooks);
VariantClear(&index);
VariantClear(¶msCell);
VariantClear(¶msItem);
VariantClear(¶msValue);
VariantClear(¶msNoArgs);
pXlCell->lpVtbl->Release(pXlCell);
pXlSheet->lpVtbl->Release(pXlSheet);
pXlSheets->lpVtbl->Release(pXlSheets);
pXlBook->lpVtbl->Release(pXlBook);
pXlBooks->lpVtbl->Release(pXlBooks);
pXlApp->lpVtbl->Release(pXlApp);
CoUninitialize();
}
int main() {
writeExcel("output.xlsx");
return 0;
}
优点:
- 功能强大:支持复杂的Excel功能,如公式、图表等。
- 直接操作:可以直接操作Excel应用程序,实现高级功能。
缺点:
- 代码复杂:需要处理大量的COM接口调用,代码复杂度较高。
- 依赖性强:依赖于Windows操作系统和Excel应用程序,不适用于其他平台。
适用场景:
- 高级操作:需要直接操作Excel应用程序的场景,如生成图表、执行宏等。
- 复杂需求:需要实现复杂功能,如数据验证、条件格式等。
3. 使用第三方库
使用第三方库是另一种高效的操作Excel文件的方法。这些库提供了丰富的API,简化了操作步骤。
3.1 使用libxlsxwriter库
libxlsxwriter
是一个开源库,用于创建Excel XLSX文件。
3.1.1 安装
sudo apt-get install libxlsxwriter-dev
3.1.2 示例代码
#include "xlsxwriter.h"
void writeExcel(const char *filename) {
lxw_workbook *workbook = workbook_new(filename);
lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
// 写入数据
worksheet_write_string(worksheet, 0, 0, "Hello, World!", NULL);
worksheet_write_number(worksheet, 1, 0, 123.45, NULL);
// 关闭工作簿
workbook_close(workbook);
}
int main() {
writeExcel("output.xlsx");
return 0;
}
3.2 使用libxls库
libxls
是一个用于读取Excel XLS文件的库。
3.2.1 安装
sudo apt-get install libxls-dev
3.2.2 示例代码
#include <stdio.h>
#include <libxls/xls.h>
void readExcel(const char *filename) {
xlsWorkBook *workbook = xls_open(filename, "UTF-8");
if (workbook == NULL) {
printf("Failed to open the file.\n");
return;
}
for (int i = 0; i < workbook->sheets.count; ++i) {
xlsWorkSheet *sheet = xls_getWorkSheet(workbook, i);
xls_parseWorkSheet(sheet);
for (int row = 0; row <= sheet->rows.lastrow; ++row) {
for (int col = 0; col <= sheet->rows.lastcol; ++col) {
xlsCell *cell = xls_cell(sheet, row, col);
if (cell && cell->str) {
printf("Row %d, Col %d: %s\n", row, col, cell->str);
}
}
}
xls_close_WS(sheet);
}
xls_close_WB(workbook);
}
int main() {
readExcel("input.xls");
return 0;
}
3.3 使用libxlsxreader库
libxlsxreader
是一个用于读取Excel XLSX文件的库。
3.3.1 安装
sudo apt-get install libxlsxreader-dev
3.3.2 示例代码
#include <stdio.h>
#include <xlsxreader/xlsxreader.h>
void readExcel(const char *filename) {
lxw_reader *reader = reader_new(filename);
if (reader == NULL) {
printf("Failed to open the file.\n");
return;
}
int num_sheets = reader_get_num_sheets(reader);
printf("Number of sheets: %d\n", num_sheets);
for (int i = 0; i < num_sheets; i++) {
lxw_worksheet *worksheet = reader_get_worksheet(reader, i);
int rows = worksheet_get_row_count(worksheet);
int cols = worksheet_get_col_count(worksheet);
printf("Sheet %d: %d rows, %d columns\n", i + 1, rows, cols);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
lxw_cell *cell = worksheet_get_cell(worksheet, row, col);
if (cell) {
const char *value = cell_get_value(cell);
printf("Row %d, Column %d: %s\n", row, col, value);
}
}
}
}
reader_free(reader);
}
int main() {
readExcel("input.xlsx");
return 0;
}
优点:
- API丰富:提供了丰富的API,简化了操作步骤。
- 支持多种格式:支持多种Excel文件格式,包括XLS和XLSX。
缺点:
- 依赖库:需要安装额外的库,增加了项目的依赖。
- 兼容性问题:可能存在兼容性问题,特别是对于某些特殊的Excel文件。
适用场景:
- 多格式支持:需要处理多种Excel文件格式的场景。
- 高效读写:需要高效读写大量数据的场景。
4. 使用API接口
除了上述方法,还可以通过API接口(如Microsoft Excel API、Google Sheets API等)来操作Excel文件。
4.1 使用Microsoft Excel API
4.1.1 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#define ACCESS_TOKEN "YOUR_ACCESS_TOKEN"
#define EXCEL_FILE_ID "YOUR_EXCEL_FILE_ID"
void readExcel() {
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if (curl) {
char url[256];
snprintf(url, sizeof(url), "https://graph.microsoft.com/v1.0/me/drive/items/%s/workbook/worksheets", EXCEL_FILE_ID);
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Authorization: Bearer " ACCESS_TOKEN);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
}
int main() {
readExcel();
return 0;
}
优点:
- 远程操作:支持远程操作Excel文件,适用于分布式系统。
- 功能丰富:提供丰富的接口,可以实现复杂的功能。
缺点:
- 网络依赖:需要网络连接,依赖于API服务的可用性。
- 开发难度:需要进行身份验证和授权,增加了开发难度。
适用场景:
- 远程操作:需要远程操作Excel文件的场景。
- 分布式系统:分布式系统中需要协同操作Excel文件的场景。
5. 直接解析Excel文件格式
直接解析Excel文件格式是一项复杂的任务,因为Excel文件格式(尤其是XLSX)非常复杂,并且包含了大量的元数据和压缩数据。不过,理解基本的文件格式对于某些特殊需求可能是必要的。
5.1 解析XLS文件
XLS文件格式是二进制格式,解析起来相对复杂。一般来说,不建议手动解析XLS文件,除非有非常特殊的需求。
5.2 解析XLSX文件
XLSX文件实际上是一个压缩包,里面包含了多个XML文件。这些XML文件描述了工作表的数据、样式、关系等信息。
5.2.1 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zip.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
void parseXLSX(const char *filename) {
struct zip *z = zip_open(filename, 0, NULL);
if (!z) {
fprintf(stderr, "Failed to open zip file\n");
return;
}
struct zip_stat st;
zip_stat_init(&st);
zip_stat(z, "xl/worksheets/sheet1.xml", 0, &st);
struct zip_file *zf = zip_fopen(z, "xl/worksheets/sheet1.xml", 0);
if (!zf) {
fprintf(stderr, "Failed to open sheet1.xml\n");
zip_close(z);
return;
}
char *contents = malloc(st.size);
zip_fread(zf, contents, st.size);
zip_fclose(zf);
xmlDocPtr doc = xmlReadMemory(contents, st.size, "sheet1.xml", NULL, 0);
if (doc == NULL) {
fprintf(stderr, "Failed to parse XML\n");
free(contents);
zip_close(z);
return;
}
xmlNodePtr root = xmlDocGetRootElement(doc);
if (root == NULL) {
fprintf(stderr, "Empty document\n");
xmlFreeDoc(doc);
free(contents);
zip_close(z);
return;
}
xmlNodePtr node = root->children;
while (node != NULL) {
if (xmlStrcmp(node->name, (const xmlChar *)"sheetData") == 0) {
xmlNodePtr rowNode = node->children;
while (rowNode != NULL) {
if (xmlStrcmp(rowNode->name, (const xmlChar *)"row") == 0) {
xmlNodePtr cellNode = rowNode->children;
while (cellNode != NULL) {
if (xmlStrcmp(cellNode->name, (const xmlChar *)"c") == 0) {
xmlChar *value = xmlGetProp(cellNode, (const xmlChar *)"v");
if (value) {
printf("%s\t", value);
xmlFree(value);
}
}
cellNode = cellNode->next;
}
printf("\n");
}
rowNode = rowNode->next;
}
}
node = node->next;
}
xmlFreeDoc(doc);
free(contents);
zip_close(z);
}
int main() {
parseXLSX("input.xlsx");
return 0;
}
优点:
- 自主控制:不依赖于第三方库,完全自主控制。
- 高度定制:可以处理复杂的文件格式和元数据。
缺点:
- 实现复杂:需要深入了解Excel文件格式,实现复杂。
- 开发成本高:开发和维护成本高,不适合简单的项目需求。
适用场景:
- 特殊需求:需要高度定制的解析逻辑,适用于特殊需求。
- 高性能要求:避免依赖第三方库的性能瓶颈,适用于高性能要求的场景。
6. 结论
通过上述介绍,我们可以看到C语言操作Excel文件有多种方法,每种方法都有其适用场景和优缺点。选择合适的方法取决于具体的项目需求和开发环境:
- CSV格式:适用于简单的需求,不需要复杂的Excel功能。
- COM接口:适用于需要直接操作Excel应用程序的场景,但依赖于Windows操作系统。
- 第三方库:提供了丰富的API,简化了操作步骤,适用于大多数场景。
- API接口:支持远程操作Excel文件,适用于分布式系统。
- 直接解析Excel文件格式:适用于特殊需求,需要高度定制的解析逻辑。
希望本文能帮助你在C语言中高效地操作Excel文件,满足不同的开发需求。无论你是初学者还是经验丰富的开发者,都能找到适合自己的方法,提高开发效率和代码质量。