CSpreadsheet,C语言如何实现电子表格功能?

99ANYc3cd6
预计阅读时长 39 分钟
位置: 首页 C语言 正文

项目概述与设计思路

一个简单的电子表格程序需要具备以下核心功能:

c语言 cspreadsheet
(图片来源网络,侵删)
  • 单元格网格:一个由行和列组成的二维网格,每个格子可以存储数据。
  • 数据类型:单元格不仅能存储文本(字符串),还能存储数字,并能进行简单的数学运算。
  • 公式计算:支持以 开头的公式,=A1+B2
  • 用户交互:允许用户通过命令行或简单的图形界面选择单元格、输入和编辑内容。
  • 显示:能将整个表格清晰地展示在屏幕上。

设计思路

  1. 数据结构是核心:如何表示一个单元格是关键,一个单元格需要存储它的(可能是字符串、数字或公式)和显示值(计算后的结果)。
  2. 解析与计算:需要能够解析用户输入的公式(如 =A1+B2),提取出单元格引用,获取这些单元格的值,并执行计算。
  3. 交互与渲染:需要一个循环来持续接收用户输入,并根据用户指令更新或显示表格。

核心数据结构

我们使用一个二维结构体数组来表示整个表格。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
// 定义最大行数和列数
#define MAX_ROWS 20
#define MAX_COLS 10
// 定义单元格的数据类型
typedef enum {
    CELL_TYPE_EMPTY,
    CELL_TYPE_STRING,
    CELL_TYPE_NUMBER,
    CELL_TYPE_FORMULA
} CellType;
// 单元格结构体
typedef struct {
    CellType type;
    char raw_input[256]; // 存储用户原始输入,如 "Hello", "123", "=A1+B2"
    double value;        // 存储计算后的数值,用于显示和公式计算
    char display_text[256]; // 存储要显示的文本,如 "Hello", "123", "15"
} Cell;
// 电子表格结构体
typedef struct {
    Cell cells[MAX_ROWS][MAX_COLS];
} Spreadsheet;

结构体解析

  • CellType:枚举类型,明确单元格的类型,方便后续处理。
  • Cell:这是核心,它包含了:
    • type:单元格的类型。
    • raw_input:保留用户输入的原始字符串,这对于重新计算公式至关重要。
    • value:一个 double 类型的值,无论原始输入是数字还是公式的结果,都将其转换为数值存储,这使得公式计算非常方便。
    • display_text:最终显示给用户的文本,对于文本和数字,它和 raw_input 一样;对于公式,它是计算结果。
  • Spreadsheet:一个 Cell 的二维数组,构成了整个表格。

关键功能实现

1 初始化表格

创建一个函数,将所有单元格初始化为空。

void init_spreadsheet(Spreadsheet *sheet) {
    for (int i = 0; i < MAX_ROWS; i++) {
        for (int j = 0; j < MAX_COLS; j++) {
            sheet->cells[i][j].type = CELL_TYPE_EMPTY;
            sheet->cells[i][j].raw_input[0] = '\0';
            sheet->cells[i][j].value = 0;
            sheet->cells[i][j].display_text[0] = '\0';
        }
    }
}

2 解析单元格引用 (如 "A1")

这是实现公式功能的基础,我们需要一个函数,将类似 "A1" 的字符串转换为行和列的索引。

c语言 cspreadsheet
(图片来源网络,侵删)
// 将单元格引用 (如 "A1") 转换为 (row, col) 索引
// 返回 1 表示成功,0 表示失败
int parse_cell_reference(const char *ref, int *row, int *col) {
    if (strlen(ref) < 2 || !isalpha(ref[0])) {
        return 0; // 无效格式
    }
    // 解析列 (A, B, C, ...)
    *col = toupper(ref[0]) - 'A';
    // 解析行 (1, 2, 3, ...)
    char *end_ptr;
    long r = strtol(ref + 1, &end_ptr, 10);
    if (*end_ptr != '\0' || r < 1 || r > MAX_ROWS) {
        return 0; // 行号无效或格式错误
    }
    *row = (int)r - 1; // 转换为 0-based 索引
    return 1;
}

3 公式计算引擎

这是最复杂的部分,当用户输入一个公式时,我们需要:

  1. 识别出公式中的单元格引用(如 A1, B2)。
  2. 递归或迭代地获取这些引用单元格的值。
  3. 执行计算。

为了简化,我们先实现一个不处理括号和运算符优先级的版本。

// 获取单元格的值(用于公式计算)
// 如果是公式,会尝试计算
double get_cell_value(Spreadsheet *sheet, int row, int col) {
    if (row < 0 || row >= MAX_ROWS || col < 0 || col >= MAX_COLS) {
        return 0; // 单元格越界,返回0
    }
    Cell *cell = &sheet->cells[row][col];
    if (cell->type == CELL_TYPE_NUMBER) {
        return cell->value;
    } else if (cell->type == CELL_TYPE_FORMULA) {
        // 如果是公式,重新计算它
        // 这里我们直接调用 evaluate_formula,可能会造成循环依赖
        // 在更完善的实现中,需要一个依赖图和拓扑排序来处理
        evaluate_formula(sheet, cell);
        return cell->value;
    }
    // 其他类型(字符串、空)无法参与数值计算,返回0
    return 0;
}
// 计算公式 (非常简化的版本,仅支持 + 和 -)
void evaluate_formula(Spreadsheet *sheet, Cell *cell) {
    char formula[256];
    strcpy(formula, cell->raw_input + 1); // 去掉 '='
    char *token = strtok(formula, "+-");
    double result = 0;
    int op = 1; // 1 for +, -1 for -
    char *rest = formula;
    while (token != NULL) {
        // 去除 token 前后的空格
        while (*rest == ' ') rest++;
        char *token_start = rest;
        while (*token != '+' && *token != '-' && *token != '\0') {
            rest++;
            token = strtok(NULL, "+-");
        }
        int token_len = rest - token_start;
        char current_token[256];
        strncpy(current_token, token_start, token_len);
        current_token[token_len] = '\0';
        // 检查 token 是否是单元格引用
        int ref_row, ref_col;
        if (parse_cell_reference(current_token, &ref_row, &ref_col)) {
            result += op * get_cell_value(sheet, ref_row, ref_col);
        } else {
            // 如果不是引用,尝试解析为数字
            double num = atof(current_token);
            result += op * num;
        }
        // 确定下一个运算符
        if (*rest == '+') op = 1;
        else if (*rest == '-') op = -1;
        rest++;
    }
    cell->value = result;
    snprintf(cell->display_text, sizeof(cell->display_text), "%.2f", result);
}

4 设置单元格内容

这个函数是用户输入的入口,它会根据输入的内容决定单元格的类型,并触发相应的计算。

void set_cell_content(Spreadsheet *sheet, int row, int col, const char *input) {
    if (row < 0 || row >= MAX_ROWS || col < 0 || col >= MAX_COLS) {
        return;
    }
    Cell *cell = &sheet->cells[row][col];
    strcpy(cell->raw_input, input);
    // 检查是否是公式
    if (input[0] == '=') {
        cell->type = CELL_TYPE_FORMULA;
        evaluate_formula(sheet, cell);
    } 
    // 检查是否是数字
    else if (atof(input) != 0 || strcmp(input, "0") == 0) {
        cell->type = CELL_TYPE_NUMBER;
        cell->value = atof(input);
        strcpy(cell->display_text, input);
    } 
    // 否则视为文本
    else {
        cell->type = CELL_TYPE_STRING;
        cell->value = 0; // 文本没有数值意义
        strcpy(cell->display_text, input);
    }
}

5 渲染(打印)表格

将表格以美观的格式打印到控制台。

c语言 cspreadsheet
(图片来源网络,侵删)
void print_spreadsheet(Spreadsheet *sheet) {
    // 打印列标题 (A, B, C, ...)
    printf("    ");
    for (int j = 0; j < MAX_COLS; j++) {
        printf("  %c  ", 'A' + j);
    }
    printf("\n");
    for (int i = 0; i < MAX_ROWS; i++) {
        // 打印行号
        printf("%2d |", i + 1);
        for (int j = 0; j < MAX_COLS; j++) {
            Cell *cell = &sheet->cells[i][j];
            printf(" %s ", cell->display_text);
        }
        printf("\n");
    }
}

主程序与用户交互

将以上功能组合起来,形成一个可以运行的程序。

int main() {
    Spreadsheet sheet;
    init_spreadsheet(&sheet);
    printf("C Spreadsheet (v0.1)\n");
    printf("Commands: set <cell> <content>, print, quit\n");
    printf("Example: set A1 100\n");
    printf("Example: set B1 '=A1+50'\n");
    char command[256];
    while (1) {
        printf("\n> ");
        fgets(command, sizeof(command), stdin);
        command[strcspn(command, "\n")] = 0; // 去掉换行符
        if (strncmp(command, "quit", 4) == 0) {
            break;
        } else if (strncmp(command, "print", 5) == 0) {
            print_spreadsheet(&sheet);
        } else if (strncmp(command, "set ", 4) == 0) {
            char cell_ref[10];
            char content[256];
            // 简单解析 "set A1 Hello World"
            // 注意:这个解析很脆弱,不能处理内容中带空格的情况
            if (sscanf(command, "set %s %[^\n]", cell_ref, content) == 2) {
                int row, col;
                if (parse_cell_reference(cell_ref, &row, &col)) {
                    set_cell_content(&sheet, row, col, content);
                    printf("Cell %s set to '%s'\n", cell_ref, content);
                } else {
                    printf("Invalid cell reference: %s\n", cell_ref);
                }
            } else {
                printf("Invalid set command format.\n");
            }
        } else {
            printf("Unknown command.\n");
        }
    }
    return 0;
}

如何编译和运行

  1. 将以上所有代码片段保存为一个文件,cspreadsheet.c
  2. 使用 GCC 编译:
    gcc cspreadsheet.c -o cspreadsheet -lm
    • -lm 是必须的,因为我们使用了 math.h 中的 atof 等函数。
  3. 运行程序:
    ./cspreadsheet

示例交互

C Spreadsheet (v0.1)
Commands: set <cell> <content>, print, quit
Example: set A1 100
Example: set B1 '=A1+50'
> set A1 100
Cell A1 set to '100'
> set A2 Hello
Cell A2 set to 'Hello'
> set B1 '=A1+50'
Cell B1 set to '=A1+50'
> set B2 '=A1+A2'
Cell B2 set to '=A1+A2'
> print
    A    B    C    D    E    F    G    H    I    J  
 1 | 100      |      |      |      |      |      |      |      |      | 
 2 | Hello    |      |      |      |      |      |      |      |      | 
 3 |      |      |      |      |      |      |      |      |      | 
 4 |      |      |      |      |      |      |      |      |      | 
 5 |      |      |      |      |      |      |      |      |      | 
 6 |      |      |      |      |      |      |      |      |      | 
 7 |      |      |      |      |      |      |      |      |      | 
 8 |      |      |      |      |      |      |      |      |      | 
 9 |      |      |      |      |      |      |      |      |      | 
10 |      |      |      |      |      |      |      |      |      | 
11 |      |      |      |      |      |      |      |      |      | 
12 |      |      |      |      |      |      |      |      |      | 
13 |      |      |      |      |      |      |      |      |      | 
14 |      |      |      |      |      |      |      |      |      | 
15 |      |      |      |      |      |      |      |      |      | 
16 |      |      |      |      |      |      |      |      |      | 
17 |      |      |      |      |      |      |      |      |      | 
18 |      |      |      |      |      |      |      |      |      | 
19 |      |      |      |      |      |      |      |      |      | 
20 |      |      |      |      |      |      |      |      |      | 
> quit

(注意:上面的 print 输出中,B1 和 B2 的公式没有被正确计算和显示,因为我们之前的 evaluate_formula 实现非常简陋,这是一个需要改进的地方。)


进一步的扩展与改进方向

这个 CSpreadsheet 是一个非常好的起点,但要成为一个功能完善的程序,还有很多工作可以做:

  1. 更强大的公式引擎

    • 运算符优先级:实现 和 的优先级高于 和 ,可以使用逆波兰表达式(RPN)递归下降解析器
    • 更多函数:实现 SUM(), AVERAGE(), MAX(), MIN() 等常用函数。
    • 错误处理:处理循环引用(如 A1 的公式是 =B1B1 的公式是 =A1)、无效的单元格引用、语法错误等。
  2. 改进的用户界面

    • 光标移动:使用 ANSI 转义码实现一个可以在表格中移动的光标,并高亮显示当前选中的单元格。
    • 直接编辑:允许用户直接在单元格位置输入内容,而不是通过 set 命令。
  3. 文件 I/O

    • 保存:将表格内容保存到文件(如 CSV 或自定义二进制格式)。
    • 加载:从文件中读取表格内容。
  4. 数据类型增强

    • 支持日期/时间类型。
    • 支持布尔值 (TRUE/FALSE),用于逻辑判断(如 =IF(A1>10, "Yes", "No"))。
  5. 代码重构与健壮性

    • 内存管理:如果单元格内容可能很长,可以考虑使用 malloc 动态分配内存,而不是固定大小的字符数组。
    • 模块化:将代码拆分为多个文件(如 cell.c, formula.c, ui.c),提高可维护性。
    • 更健壮的解析:改进 set 命令的解析,使其能正确处理带空格的内容。

这个项目从零开始,每一步都能让你深入理解 C 语言的指针、结构体、字符串处理和算法设计,是一个极佳的练手项目,祝你编程愉快!

-- 展开阅读全文 --
头像
C语言如何获取鼠标光标位置?
« 上一篇 2025-12-20
织梦模板用HTML文件类型有何优势?
下一篇 » 2025-12-20

相关文章

取消
微信二维码
支付宝二维码

目录[+]