【C语言小型项目实践】:初学者到中级水平的计算器项目

目录

一、计算器项目

1.1. 功能描述

1.2. 技术要点

 二、代码实现

2.1. 代码说明

2.2. 注意事项

 三、测试用例

3.1. 基本运算

3.2. 括号与优先级

3.3. 多个运算符

3.4. 更复杂的表达式

3.5. 错误情况

2.6. 带负数的运算

2.7. 混合情况


一、计算器项目

1.1. 功能描述

  • 此计算器项目旨在实现加、减、乘、除等基本数学运算,并支持括号以处理运算的优先级。用户可以通过命令行界面输入表达式,程序将解析并计算结果。

1.2. 技术要点

1. 用户输入读取

  • 使用scanffgets等函数从标准输入读取用户输入的表达式。
  • 注意处理输入中的空格和非法字符。

2. 表达式解析

  • 编写解析器,使用栈结构处理括号和运算符的优先级。
  • 可以考虑使用两个栈,一个用于存储操作数,另一个用于存储运算符。

3. 运算处理

  • 使用if-elseswitch语句根据运算符执行相应的数学运算。
  • 确保处理除数为零的情况,避免程序崩溃。

4. 连续计算

  • 使用循环结构,允许用户输入多个表达式并连续计算结果。
  • 提供退出选项,让用户能够随时结束程序。

5. 错误处理

  • 添加错误处理逻辑,处理无效输入和运算错误。
  • 向用户显示清晰的错误信息。

6. 界面设计(可选):

  • 对于更高级的用户,可以考虑设计一个简单的命令行界面,提供用户友好的提示和选项。

通过实现这个计算器项目,C语言初学者可以巩固基础知识,如变量声明、循环结构、条件语句和函数调用。同时,这个项目也提供了学习数据结构(如栈)和算法(如表达式解析)的机会,有助于提升编程能力和问题解决能力。

 二、代码实现

实现一个完整的支持括号和运算符优先级的计算器项目,包括表达式解析和栈处理,是一个相对复杂的任务。以下是一个简化的C语言代码框架。请注意,这个框架不会提供完整的错误处理和所有细节,但它将提供一个起点。

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>

#define MAX_EXPR_SIZE 100

// 定义栈结构
typedef struct {
    double data[MAX_EXPR_SIZE];
    int top;
} Stack;

// 初始化栈
void initStack(Stack *stack) {
    stack->top = -1;
}

// 判断栈是否为空
int isEmpty(Stack *stack) {
    return stack->top == -1;
}

// 入栈
void push(Stack *stack, double value) {
    if (stack->top < MAX_EXPR_SIZE - 1) {
        stack->data[++stack->top] = value;
    } else {
        printf("Stack overflow\n");
        exit(EXIT_FAILURE);
    }
}

// 出栈
double pop(Stack *stack) {
    if (!isEmpty(stack)) {
        return stack->data[stack->top--];
    } else {
        printf("Stack underflow\n");
        exit(EXIT_FAILURE);
    }
}

// 获取栈顶元素
double peek(Stack *stack) {
    if (!isEmpty(stack)) {
        return stack->data[stack->top];
    } else {
        printf("Stack is empty\n");
        exit(EXIT_FAILURE);
    }
}

// 运算符优先级
int precedence(char op) {
    switch (op) {
        case '+':
        case '-':
            return 1;
        case '*':
        case '/':
            return 2;
        case '(':
        case ')':
            return 0;
    }
    return -1;
}

// 执行运算
double applyOp(double a, double b, char op) {
    switch (op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/':
            if (b == 0) {
                printf("Error: Division by zero\n");
                exit(EXIT_FAILURE);
            }
            return a / b;
    }
    return 0;
}

// 解析并计算表达式
double evaluate(char *expr) {
    Stack values;
    Stack ops;
    initStack(&values);
    initStack(&ops);

    for (int i = 0; expr[i] != '\0'; i++) {
        // 跳过空格
        if (isspace(expr[i])) {
            continue;
        }

        // 当前字符是数字或小数点
        if (isdigit(expr[i]) || expr[i] == '.') {
            double val = 0;
            // 处理多位数和小数
            while (isdigit(expr[i]) || expr[i] == '.') {
                val = val * 10 + (expr[i] - '0');
                if (expr[i] == '.') {
                    val /= 10;
                    while (isdigit(expr[++i])) {
                        val = val * 10 + (expr[i] - '0') / pow(10, (i - (strrchr(expr, '.') - expr + 1)));
                    }
                    i--; // 回到当前处理的字符位置
                }
                i++;
            }
            i--; // 回到当前处理的字符位置的前一个位置(为了for循环的i++)
            push(&values, val);
        }
        // 当前字符是左括号
        else if (expr[i] == '(') {
            push(&ops, expr[i]);
        }
        // 当前字符是右括号
        else if (expr[i] == ')') {
            while (!isEmpty(&ops) && peek(&ops) != '(') {
                double val2 = pop(&values);
                double val1 = pop(&values);
                char op = pop(&ops);
                push(&values, applyOp(val1, val2, op));
            }
            // 弹出左括号
            if (!isEmpty(&ops)) {
                pop(&ops);
            } else {
                printf("Error: Mismatched parentheses\n");
                exit(EXIT_FAILURE);
            }
        }
        // 当前字符是运算符
        else if (expr[i] == '+' || expr[i] == '-' || expr[i] == '*' || expr[i] == '/') {
            while (!isEmpty(&ops) && precedence(peek(&ops)) >= precedence(expr[i])) {
                double val2 = pop(&values);
                double val1 = pop(&values);
                char op = pop(&ops);
                push(&values, applyOp(val1, val2, op));
            }
            push(&ops, expr[i]);
        } else {
            printf("Error: Invalid character '%c' in expression\n", expr[i]);
            exit(EXIT_FAILURE);
        }
    }

    // 弹出所有剩余的操作符
    while (!isEmpty(&ops)) {
        double val2 = pop(&values);
        double val1 = pop(&values);
        char op = pop(&ops);
        push(&values, applyOp(val1, val2, op));
    }

    // 返回栈中唯一的值
    return pop(&values);
}

int main() {
    char expr[MAX_EXPR_SIZE];

    printf("Simple Calculator\n");
    printf("Enter expressions (type 'exit' to quit):\n");

    while (1) {
        printf("> ");
        fgets(expr, MAX_EXPR_SIZE, stdin);

        // 移除换行符
        expr[strcspn(expr, "\n")] = 0;

        // 检查是否输入 'exit'
        if (strcmp(expr, "exit") == 0) {
            break;
        }

        // 计算表达式
        double result = evaluate(expr);

        // 输出结果
        printf("Result: %.2f\n", result);
    }

    return 0;
}

2.1. 代码说明

  • 栈结构:用于存储操作数和运算符。
  • 函数
    • initStack:初始化栈。
    • isEmpty:检查栈是否为空。
    • push:入栈操作。
    • pop:出栈操作。
    • peek:获取栈顶元素。
    • precedence:返回运算符的优先级。
    • applyOp:根据运算符执行相应的运算。
    • evaluate:解析并计算表达式。
  • 主函数
    • 提供一个简单的命令行界面。
    • 读取用户输入的表达式。
    • 调用 evaluate 函数计算结果。
    • 输出结果。
    • 支持输入 exit 退出程序。

2.2. 注意事项

  • 程序处理多位数和小数。
  • 程序处理带负数的运算。
  • 程序在发生错误时输出错误信息并退出。
  • 程序使用 fgets 读取输入,可以处理空格和非法字符。

 三、测试用例

为了确保上面的代码能够正确运行并处理各种表达式,我们可以设计一系列测试用例。这些测试用例将涵盖不同的运算符、括号使用、优先级、以及可能的错误情况。以下是一些测试用例的示例:

3.1. 基本运算

用例:

  • 3 + 5 => 结果: 8.00
  • 10 - 4 => 结果: 6.00
  • 6 * 7 => 结果: 42.00
  • 24 / 6 => 结果: 4.00

3.2. 括号与优先级

  • 3 + (2 * 4) => 结果: 11.00
  • (10 - 4) * 2 => 结果: 12.00
  • 6 / (2 + 1) => 结果: 2.00

​​​​​​​

3.3. 多个运算符

  • 2 + 3 - 5 => 结果: 0.00
  • 4 * 5 / 2 => 结果: 10.00
  • 10 - 2 + 3 * 4 => 结果: 20.00(注意运算优先级)

3.4. 更复杂的表达式

  • ((3 + 5) * 2) - 8 => 结果: 8.00
  • 10 - (2 + 3) * 2 => 结果: 0.00(注意括号内的优先级)
  • 6 * (10 - (4 + 2)) => 结果: 24.00

3.5. 错误情况

  • 10 / 0 => 错误: 除数不能为零

​​​​​​​​​​​​​​

  • 3 + => 错误: 无效的表达式(缺少操作数)

  • * 4 => 错误: 无效的表达式(缺少操作数)

  • 3 + (2 * => 错误: 无效的表达式(缺少右括号和第二个操作数)

  • => 错误: 无效的表达式(空字符串)

2.6. 带负数的运算

  • -3 + 5 => 结果: 2.00
  • 10 - -4 => 结果: 14.00
  • -6 * -7 => 结果: 42.00

2.7. 混合情况

  • 3 + 2 * (4 - 1) => 结果: 9.00
  • (3 + 5) * (2 - 8) / -3 => 结果: 4.00

注意事项

  • 在输入测试用例时,确保按照程序的提示进行输入,避免额外的空格或字符导致解析错误。
  • 对于错误情况,程序应该能够优雅地处理并输出相应的错误信息,而不是崩溃或产生未定义行为。
  • 可以考虑增加更多的边界测试用例,如最大长度的表达式、包含特殊字符的表达式等,以进一步验证程序的健壮性。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值