首页 智能穿戴

C语言小白的逆袭:多功能计算器炼成记

分类:智能穿戴
字数: (9919)
阅读: (8039)
内容摘要:C语言小白的逆袭:多功能计算器炼成记,

最近有个朋友(C语言小白)找到我,说想用 C 写个多功能计算器,结果卡在了各种细节上,进度感人。这让我想起了当年自己啃《C Primer Plus》的痛苦经历。为了拯救广大 C 语言初学者,今天就来聊聊如何一步步实现一个功能完善的计算器,以及过程中可能遇到的坑。

需求分析:我们要实现哪些功能?

首先,明确一下需求。我们要实现的计算器,至少应该包含以下功能:

  • 基本算术运算: 加、减、乘、除(+, -, *, /)。
  • 优先级处理: 支持括号 () 改变运算优先级。
  • 常用函数: 例如平方根(sqrt)、正弦(sin)、余弦(cos)等。
  • 错误处理: 能够检测并处理除零错误、无效输入等。

底层原理:运算符优先级与栈

实现计算器的核心难点在于处理运算符的优先级。通常我们会使用这种数据结构来解决这个问题。简单来说,就是将操作数和运算符分别入栈,然后根据运算符的优先级进行计算。这个过程有点像编译器的语法分析。

C语言小白的逆袭:多功能计算器炼成记

中缀表达式转后缀表达式(逆波兰表达式)

为了方便计算,我们通常会将中缀表达式(例如 1 + 2 * 3)转换成后缀表达式(例如 1 2 3 * +)。转换规则如下:

  1. 遇到操作数,直接输出。
  2. 遇到运算符:
    • 如果栈为空,入栈。
    • 如果栈顶运算符优先级低于当前运算符,入栈。
    • 如果栈顶运算符优先级高于或等于当前运算符,弹出栈顶运算符并输出,直到栈为空或栈顶运算符优先级低于当前运算符,然后将当前运算符入栈。
  3. 遇到左括号 (,入栈。
  4. 遇到右括号 ),弹出栈顶运算符并输出,直到遇到左括号 (,将左括号弹出但不输出。
  5. 遍历完表达式后,将栈中剩余的运算符依次弹出并输出。

后缀表达式求值

有了后缀表达式,求值就变得简单了。从左到右扫描后缀表达式:

C语言小白的逆袭:多功能计算器炼成记
  1. 遇到操作数,入栈。
  2. 遇到运算符,从栈中弹出两个操作数进行计算,然后将结果入栈。
  3. 遍历完表达式后,栈顶元素就是计算结果。

C语言实现:代码示例

下面是一个简化的 C 语言多功能计算器实现示例,主要演示了加减乘除和括号的处理:

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

#define MAX_SIZE 100

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

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

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

// 判断栈是否已满
int isFull(Stack *stack) {
    return stack->top == MAX_SIZE - 1;
}

// 入栈
void push(Stack *stack, double value) {
    if (isFull(stack)) {
        printf("Stack Overflow!\n");
        exit(EXIT_FAILURE);
    }
    stack->data[++stack->top] = value;
}

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

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

// 运算符优先级
int getPriority(char op) {
    if (op == '+' || op == '-') {
        return 1;
    } else if (op == '*' || op == '/') {
        return 2;
    } else {
        return 0; // 左括号优先级最低
    }
}

// 中缀表达式转后缀表达式
void infixToPostfix(char *infix, char *postfix) {
    Stack operatorStack;
    initStack(&operatorStack);
    int i, j = 0;
    for (i = 0; infix[i] != '\0'; i++) {
        if (isdigit(infix[i])) {
            // 处理数字,需要考虑多位数的情况
            postfix[j++] = infix[i];
            while(isdigit(infix[i+1])) {
                postfix[j++] = infix[++i];
            }
            postfix[j++] = ' '; // 数字之间用空格分隔
        } else if (infix[i] == '(') {
            push(&operatorStack, infix[i]);
        } else if (infix[i] == ')') {
            while (!isEmpty(&operatorStack) && peek(&operatorStack) != '(') {
                postfix[j++] = pop(&operatorStack);
                postfix[j++] = ' ';
            }
            pop(&operatorStack); // 弹出左括号
        } else if (infix[i] == '+' || infix[i] == '-' || infix[i] == '*' || infix[i] == '/') {
            while (!isEmpty(&operatorStack) && getPriority(peek(&operatorStack)) >= getPriority(infix[i])) {
                postfix[j++] = pop(&operatorStack);
                postfix[j++] = ' ';
            }
            push(&operatorStack, infix[i]);
        }
    }
    while (!isEmpty(&operatorStack)) {
        postfix[j++] = pop(&operatorStack);
        postfix[j++] = ' ';
    }
    postfix[j] = '\0';
}

// 计算后缀表达式
double evaluatePostfix(char *postfix) {
    Stack operandStack;
    initStack(&operandStack);
    int i = 0;
    char *token = strtok(postfix, " "); // 使用空格分隔
    while (token != NULL) {
        if (isdigit(token[0])) {
            push(&operandStack, atof(token));
        } else {
            double operand2 = pop(&operandStack);
            double operand1 = pop(&operandStack);
            double result;
            switch (token[0]) {
                case '+':
                    result = operand1 + operand2;
                    break;
                case '-':
                    result = operand1 - operand2;
                    break;
                case '*':
                    result = operand1 * operand2;
                    break;
                case '/':
                    if (operand2 == 0) {
                        printf("Division by zero!\n");
                        exit(EXIT_FAILURE);
                    }
                    result = operand1 / operand2;
                    break;
                default:
                    printf("Invalid operator!\n");
                    exit(EXIT_FAILURE);
            }
            push(&operandStack, result);
        }
        token = strtok(NULL, " ");
    }
    return pop(&operandStack);
}

int main() {
    char infix[MAX_SIZE] = "(1 + 2) * 3"; // 示例表达式
    char postfix[MAX_SIZE];
    infixToPostfix(infix, postfix);
    printf("Infix: %s\n", infix);
    printf("Postfix: %s\n", postfix);
    double result = evaluatePostfix(postfix);
    printf("Result: %lf\n", result);
    return 0;
}

代码解释:

C语言小白的逆袭:多功能计算器炼成记
  • Stack 结构体定义了一个栈,用于存储操作数和运算符。
  • infixToPostfix 函数将中缀表达式转换为后缀表达式。
  • evaluatePostfix 函数计算后缀表达式的值。

编译运行:

使用 GCC 编译:

C语言小白的逆袭:多功能计算器炼成记
gcc calculator.c -o calculator -lm

运行程序:

./calculator

实战避坑:那些年踩过的坑

  • 内存泄漏: C 语言需要手动管理内存,稍不注意就容易出现内存泄漏。要养成良好的习惯,及时释放不再使用的内存。
  • 缓冲区溢出: 使用 scanf 等函数时,要小心缓冲区溢出。尽量使用 fgets 等更安全的函数,并进行长度检查。
  • 浮点数精度问题: 浮点数的精度有限,在进行比较时要小心。可以使用一个很小的误差范围来判断两个浮点数是否相等。
  • 运算符优先级错误: 对运算符优先级理解不透彻,容易导致计算错误。仔细分析表达式,确保优先级正确。
  • 分母为零错误: 除法运算要特别注意分母是否为零,否则程序会崩溃。

进阶之路:更多功能的探索

如果你想让你的计算器更强大,可以考虑添加以下功能:

  • 更多的函数: 例如三角函数、指数函数、对数函数等。
  • 变量: 支持使用变量,例如 x = 1 + 2; y = x * 3;
  • 自定义函数: 允许用户自定义函数。
  • 图形界面: 使用 Qt 或 GTK 等库,为计算器添加图形界面。

实现这些功能需要更深入的编程知识和算法设计能力。加油!

实现一个多功能计算器是学习 C 语言的一个很好的实践项目。通过这个项目,你可以深入理解 C 语言的语法、数据结构和算法。希望这篇文章能帮助你在 C 语言的学习道路上更进一步!

在实际项目中,我们可能还会遇到性能瓶颈,这时候可以考虑使用一些性能优化技巧,比如使用 inline 关键字减少函数调用开销,使用编译器优化选项(例如 -O2-O3)等。 另外,对于复杂的计算逻辑,可以考虑使用多线程或并发编程来提高程序的执行效率。如果需要处理大量的并发请求,可以参考 Nginx 的架构设计,使用 epoll 或 kqueue 等技术来实现高性能的事件驱动模型。 当然,在引入多线程或并发编程时,一定要注意线程安全问题,避免出现数据竞争和死锁等情况。可以使用互斥锁、信号量等同步机制来保护共享资源。 此外,代码的健壮性也非常重要。要充分考虑各种边界情况和异常情况,并进行相应的处理。例如,对于输入数据的合法性进行校验,对于可能出现错误的函数调用进行错误处理,等等。 只有不断学习和实践,才能成为一名优秀的 C 语言程序员。 希望这篇文章能帮助 C 语言小白们在实现多功能计算器的道路上少走弯路。

C语言小白的逆袭:多功能计算器炼成记

转载请注明出处: 代码一只喵

本文的链接地址: http://m.acea1.store/blog/620309.SHTML

本文最后 发布于2026-04-11 15:33:16,已经过了16天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 单身狗 3 天前
    代码注释很详细,点赞!不过感觉表达式解析部分还可以再优化一下。
  • 红豆沙 2 天前
    写得真不错,小白mark一下,周末尝试复现一下。