首页 元宇宙

C语言小白逆袭:多功能计算器实现背后的血泪史

分类:元宇宙
字数: (1338)
阅读: (5526)
内容摘要:C语言小白逆袭:多功能计算器实现背后的血泪史,

作为一名从 Java 转行到 Go 的后端工程师,最近为了复习一下 C 语言的基础,我尝试用 C 语言写一个多功能计算器。理想很丰满,现实很骨感,这个过程简直是“血泪史”。想当初,看到printf和scanf就觉得C语言简单,现在却被各种指针、内存泄漏搞得焦头烂额。正好趁这次机会,记录下我踩过的坑,希望能给 C 语言小白一些启发。

问题场景重现:一个看似简单的计算器

需求很简单,实现加、减、乘、除、平方根、幂运算等基本功能,支持连续运算,有简单的错误处理。听起来是不是很简单?然而,魔鬼都在细节里。首先,如何处理用户输入?如何正确解析表达式?如何避免内存泄漏?这些都是拦路虎。

C语言小白逆袭:多功能计算器实现背后的血泪史

底层原理深度剖析:C 语言的“坑”

C 语言的“坑”主要集中在以下几个方面:

C语言小白逆袭:多功能计算器实现背后的血泪史
  • 内存管理: C 语言需要手动管理内存,这很容易导致内存泄漏。比如,使用 malloc 分配内存后,忘记使用 free 释放,程序运行一段时间后,可用内存越来越少,最终崩溃。这和 Java 的 JVM 内存回收机制简直是天壤之别。Java 开发者可能对这块感知不强,但在 C 语言里,这是基本功。
  • 指针: 指针是 C 语言的灵魂,但也让很多人望而却步。理解指针的概念、指针运算、指针与数组的关系,需要花费大量时间和精力。
  • 字符串处理: C 语言的字符串本质是字符数组,没有像 Java 或 Python 那样方便的字符串类。字符串的复制、比较、拼接都需要手动实现,稍不注意就会出现缓冲区溢出等安全问题。很多Web服务器的安全漏洞都与C语言字符串处理有关,像早期Apache服务器。
  • 类型转换: C 语言的类型转换规则比较复杂,如果不熟悉这些规则,很容易出现意想不到的错误。

代码解决方案:一步一步实现计算器

下面是一些关键代码片段,展示了我是如何一步一步实现计算器的:

C语言小白逆袭:多功能计算器实现背后的血泪史
  1. 表达式解析: 为了简化,我只支持简单的四则运算和括号。使用了栈来解析表达式,类似于编译器中的语法分析。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

// 定义栈结构体
typedef struct {
    double *data;
    int top;
    int capacity;
} Stack;

// 初始化栈
Stack* initStack(int capacity) {
    Stack* stack = (Stack*)malloc(sizeof(Stack));
    stack->data = (double*)malloc(sizeof(double) * capacity);
    stack->top = -1;
    stack->capacity = capacity;
    return stack;
}

// 压栈
void push(Stack* stack, double value) {
    if (stack->top >= stack->capacity - 1) {
        printf("Stack overflow!\n");
        return;
    }
    stack->data[++stack->top] = value;
}

// 弹栈
double pop(Stack* stack) {
    if (stack->top < 0) {
        printf("Stack is empty!\n");
        return NAN; // 返回 NaN 表示出错
    }
    return stack->data[stack->top--];
}

// 获取栈顶元素
double peek(Stack* stack) {
    if (stack->top < 0) {
        printf("Stack is empty!\n");
        return NAN; // 返回 NaN 表示出错
    }
    return stack->data[stack->top];
}

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

// 释放栈内存
void freeStack(Stack* stack) {
    free(stack->data);
    free(stack);
}

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

// 计算
double calculate(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("Division by zero!\n");
                return NAN; // 返回 NaN 表示出错
            }
            return a / b;
        default: return NAN; // 返回 NaN 表示出错
    }
}

// 中缀表达式求值
double evaluateExpression(const char* expression) {
    Stack* values = initStack(100); // 值栈
    Stack* ops = initStack(100);    // 运算符栈

    int i;
    for (i = 0; expression[i] != '\0'; i++) {
        if (expression[i] == ' ') continue; // 忽略空格

        if (isdigit(expression[i])) {
            double num = 0;
            while (isdigit(expression[i])) {
                num = num * 10 + (expression[i] - '0');
                i++;
            }
            i--; // 回退一个字符
            push(values, num);
        } else if (expression[i] == '(') {
            push(ops, expression[i]);
        } else if (expression[i] == ')') {
            while (!isEmpty(ops) && peek(ops) != '(') {
                double val2 = pop(values);
                double val1 = pop(values);
                char op = (char)pop(ops);
                push(values, calculate(val1, val2, op));
            }
            pop(ops); // 弹出 '('
        } else if (expression[i] == '+' || expression[i] == '-' || expression[i] == '*' || expression[i] == '/') {
            while (!isEmpty(ops) && precedence(expression[i]) <= precedence((char)peek(ops))) {
                double val2 = pop(values);
                double val1 = pop(values);
                char op = (char)pop(ops);
                push(values, calculate(val1, val2, op));
            }
            push(ops, expression[i]);
        }
    }

    while (!isEmpty(ops)) {
        double val2 = pop(values);
        double val1 = pop(values);
        char op = (char)pop(ops);
        push(values, calculate(val1, val2, op));
    }

    double result = pop(values);
    freeStack(values);
    freeStack(ops);
    return result;
}

int main() {
    char expression[100];

    printf("Enter an expression: ");
    fgets(expression, sizeof(expression), stdin);

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

    double result = evaluateExpression(expression);

    if (isnan(result)) {
        printf("Error occurred during calculation.\n");
    } else {
        printf("Result: %lf\n", result);
    }

    return 0;
}
  1. 错误处理: 检查除数为零、无效输入等情况,并给出友好的提示。
  2. 界面交互: 使用 printfscanf 实现简单的命令行交互界面。

实战避坑经验总结

  1. 养成良好的内存管理习惯: 每次使用 malloc 分配内存后,一定要记得使用 free 释放。可以使用 valgrind 等工具检测内存泄漏。
  2. 仔细检查指针操作: 指针操作很容易出错,一定要仔细检查,避免出现空指针、野指针等问题。
  3. 充分测试: 编写测试用例,覆盖各种边界情况和异常情况。可以使用 gtest 等测试框架。
  4. 避免使用 gets 函数:这个函数存在安全隐患,容易导致缓冲区溢出。推荐使用 fgets 代替,并且限制读取的字符数。
  5. 善用调试工具: GDB 是 C 语言调试的利器,可以帮助你快速定位问题。

C语言多功能计算器项目的扩展方向

这个多功能计算器项目虽然简单,但是可以作为学习 C 语言的起点。可以尝试添加更多的功能,例如:

C语言小白逆袭:多功能计算器实现背后的血泪史
  • 支持更多的运算符,例如三角函数、对数函数等。
  • 支持变量和函数定义。
  • 实现图形界面,例如使用 GTK+ 或 Qt 库。
  • 将计算器移植到嵌入式设备上,例如使用 ARM 架构的单片机。

总之,学习 C 语言需要耐心和实践。只有不断地练习、不断地踩坑,才能真正掌握这门语言。

C语言小白逆袭:多功能计算器实现背后的血泪史

转载请注明出处: 加班到秃头

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

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

()
您可能对以下文章感兴趣
评论
  • 夏天的风 2 天前
    用栈解析表达式这个思路不错,之前没想过,学习了!
  • 修仙党 16 小时前
    C 语言确实是基本功,虽然现在主要用 Java,但是偶尔还是要复习一下。
  • 豆腐脑 6 天前
    作者说的没错,gets 函数真的不能用,之前就因为这个被 review 了一次。