C语言实现计算器

计算器支持括号嵌套,小数输入。

思路:

        中缀表达式转后缀表达式,然后使用逆波兰计算法计算。

具体实现:

        输入一个表达式,扫描该表达式,遇到数直接压入数字栈,遇到符号压入符号栈(判断优先级)。符号栈弹出符号的时候,拿数字栈上边两个元素做该符号的运算,计算出的结果再次压入数字栈。直到scanf 读取到 '#' 时候,扫描结束,此时把栈内剩余的符号弹出,再次进行计算,最后放在数字栈栈底的元素就是该表达式的结果。

注:如果想看中缀表达式转后缀表达式的具体实现,请看上一章内容。

数字拼接问题:

        先说一下我使用的方法吧,定义一个字符型变量,记录上一个字符,如果上一个字符是数字的话那就说明这是一个二位数,因为上一个数字已经入数字栈了(此时这个数是一个double类型的数),把上一个数字 * 10 + 当前数字,就完成了两位数的拼接。

小数判断问题:

        初始化两个个静态变量 flag(用来判断是否为小数)、cnt(用来计算目前这个数是小数后的第几位)。如过扫描到的字符是一个小数点,则 令 flag = 1,说明这个数是一个小数,扫描到下一个数字的时候 cnt ++; 说明这是小数后的第一位,然后进行数字拼接,数字栈的栈顶元素 + 当前数字/pow(10, cnt); 依次类推。直到扫描到符号的时候,令 flag = cnt = 0;

具体代码实现:

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

#define OK              1
#define DEFEAT          0
#define STACK_INIT_SIZE 15

typedef int Status;

// 这个栈存放运算符
typedef struct sqStack
{
    char *top;
    char *base;
    int stack_size;
} sqStack;

// 这个栈存放待运算的数
typedef struct resStack
{
    double *top;
    double *base;
    int stack_size;
} resStack;

void initSqStack(sqStack *stack);
void initResStack(resStack *stack);
void ePush(sqStack *symbol_stack, char e);
void resStackCalc(resStack *res_stack, char e);
void point_cnn(resStack *res_stack, char e, int cnt);
void highPriority(sqStack *symbol_stack, resStack *res_stack);
void destroyStack(sqStack *symbol_stack, resStack *res_stack);
void res_stack_push(resStack *res_stack, char e, char previous);
void divideStack(sqStack *symbolStack, resStack *res_stack, char e);
void lowPriority(sqStack *symbol_stack, resStack *res_stack,char e);
void mediumPriority(sqStack *symbol_stack, resStack *res_stack, char e);
void symbolPriority(sqStack *symbol_stack, resStack *res_stack, char e);
void symbol_stack_push(sqStack *symbol_stack, resStack *res_stack, char e);

// 初始化符号栈
void initSqStack(sqStack *stack)
{
    stack->base = (char *)malloc(STACK_INIT_SIZE * sizeof(char));
    if (!stack->base)
    {
        printf("sqStack初始化失败!\n");
        exit(1);
    }
    stack->top = stack->base;
    stack->stack_size = STACK_INIT_SIZE;
}

// 初始化数字栈
void initResStack(resStack *stack)
{
    stack->base = (double *)malloc(STACK_INIT_SIZE * sizeof(double));
    if (!stack->base)
    {
        printf("resStack初始化失败!\n");
        exit(1);
    }
    stack->top = stack->base;
    stack->stack_size = STACK_INIT_SIZE;
}

// 区分符号与数字
void divideStack(sqStack *symbol_stack, resStack *res_stack, char e)
{
    static Status flag = 0;     // 记录前边有没有小数点
    static int cnt = 0;         // 记录小数点后数字的个数
    static char previous;       // 记录上一个字符
    if (e == 46)
    {
        flag = 1;
        return;
    }
    if (e >= 48 && e <= 57)
    {
        if (flag)   // 如果为 1 则前面有小数点。
        {
            cnt ++;
            point_cnn(res_stack, e, cnt);
        }
        else
        {
            res_stack_push(res_stack, e, previous);
        }
    }
    else
    {
        flag = cnt = 0;
        symbol_stack_push(symbol_stack, res_stack, e);
    }
    previous = e;
}

// 把能直接压栈的情况全写在这里
void symbol_stack_push(sqStack *symbol_stack, resStack *res_stack, char e)
{
    // 如果栈为空或者当前符号为左括号,则直接压栈
    if (symbol_stack->top == symbol_stack->base || e == '(')
    {
        *(symbol_stack->top) = e;
        symbol_stack->top ++;
    }
    // 如果栈顶符号为左括号,则直接压栈
    else if (*(--symbol_stack->top) == '(')
    {
        symbol_stack->top ++;
        *(symbol_stack->top) = e;
        symbol_stack->top ++;
    }
    else
    {
        symbol_stack->top ++;
        symbolPriority(symbol_stack, res_stack, e);
    }
}

// 判断符号优先级
void symbolPriority(sqStack *symbol_stack, resStack *res_stack, char e)
{
    /* 
        既然已经进到 symbolPriority 函数了,
        那就说明栈不为空,栈顶符号不为左括号, 
        当前符号不是左括号
    */
    if (e == '+' || e == '-')
    {
        lowPriority(symbol_stack, res_stack, e);
    }
    else if (e == '*' || e == '/')
    {
        mediumPriority(symbol_stack, res_stack, e);
    }
    else    // e 为 右括号的情况
    {
        highPriority(symbol_stack, res_stack);
    }
}

// 清空括号内的符号
void highPriority(sqStack *symbol_stack, resStack *res_stack)
{
    do
    {
        symbol_stack->top --;
        if (*(symbol_stack->top) == '(')
        {
            break;
        }
        resStackCalc(res_stack, *(symbol_stack->top));
    } while (symbol_stack->top != symbol_stack->base);
}

// 高优先级符号入栈
void mediumPriority(sqStack *symbol_stack, resStack *res_stack, char e)
{
    do
    {
        symbol_stack->top --;
        if (*(symbol_stack->top) == '(')
        {
            break;
        }
        // 防止栈底符号被覆盖
        if (*(symbol_stack->top) == '+' || *(symbol_stack->top) == '-')
        {
            symbol_stack->top ++;
            *(symbol_stack->top) = e;
            symbol_stack->top ++;
            return;
        }
        resStackCalc(res_stack, *(symbol_stack->top));
    } while (symbol_stack->top != symbol_stack->base);

    // 将优先级大于等于 e 的弹出之后压入栈中。
    ePush(symbol_stack, e);
}

// 低优先级符号入栈
void lowPriority(sqStack *symbol_stack, resStack *res_stack, char e)
{
    do
    {
        symbol_stack->top --;
        if (*(symbol_stack->top) == '(')
        {
            break;
        }
        resStackCalc(res_stack, *(symbol_stack->top));
    } while (symbol_stack->top != symbol_stack->base);

    // 将优先级大于等于 e 的弹出之后压入栈中。
    ePush(symbol_stack, e);
}

/* 
    把栈内优先级大于等于e的符号弹完后,进入这个函数只有两种情况,
    1. 遇到左括号  (需要top指针先++ 再压栈)
    2. 到栈底了    (直接压栈)
*/
void ePush(sqStack *symbol_stack, char e)
{
    if (symbol_stack->top == symbol_stack->base)    // 栈底
    {
        *(symbol_stack->top) = e;
        symbol_stack->top ++;
    }
    else                                            // 左括号
    {
        symbol_stack->top ++;
        *(symbol_stack->top) = e;
        symbol_stack->top ++;
    }
}

// 执行运算
void resStackCalc(resStack *res_stack, char e)
{
    // 计算的时候是用 下边的数 运算符 上边的数
    double b = *(--res_stack->top);
    double a = *(--res_stack->top);
    switch (e)
    {
        case '+': *(res_stack->top) = a+b; break;
        case '-': *(res_stack->top) = a-b; break;
        case '*': *(res_stack->top) = a*b; break;
        case '/': *(res_stack->top) = a/b; break;
    }
    // 压栈完毕之后, top指针需要往上走一步
    res_stack->top ++;
}

// 把数字压入栈中
void res_stack_push(resStack *res_stack, char e, char previous)
{
    if (res_stack->top == res_stack->base)
    {
        *(res_stack->top) = e - 48;
        res_stack->top ++;
        return;
    }
    if (previous >= 48 && previous <= 57)   // 上一个如果是个数,则进行拼接
    {
        double sum = *(--res_stack->top) * 10 + (e - 48);
        *(res_stack->top) = sum;
        res_stack->top ++;
    }
    else
    {
        // 如果上一个不是数字则直接压栈
        *(res_stack->top) = (e - 48);
        res_stack->top ++;
    }
}

// 拼接小数点后边的数字
void point_cnn(resStack *res_stack, char e, int cnt)
{
    double n = e - 48;
    double cnnt = *(--res_stack->top) + n/pow(10, cnt);
    *(res_stack->top) = cnnt;
    res_stack->top ++;
}

// 把符号栈内剩余的符号弹出, 进行计算
void clearSymbolStack(sqStack *symbol_stack, resStack *res_stack)
{
    do
    {
        symbol_stack->top --;
        resStackCalc(res_stack, *(symbol_stack->top));
    } while (symbol_stack->top != symbol_stack->base);
}

// 销毁栈
void destroyStack(sqStack *symbol_stack, resStack *res_stack)
{
    free(symbol_stack->base);
    free(res_stack->base);
    // 让指针指向NULL, 防止野指针出现
    symbol_stack->base = symbol_stack->top = NULL;
    res_stack->base = res_stack->top = NULL;
    symbol_stack->stack_size = res_stack->stack_size = 0;
}

int main(void)
{
    char input;
    sqStack symbol_stack;
    resStack res_stack;

    initResStack(&res_stack);
    initSqStack(&symbol_stack);

    printf("输入中缀表达式:");
    do
    {
        scanf("%c", &input);
        if (input != '#')
        {
            divideStack(&symbol_stack, &res_stack, input);
        }
    } while (input != '#');

    getchar();  // 清空缓冲区
    clearSymbolStack(&symbol_stack, &res_stack);

    printf(">> %f", *(res_stack.base));    // 此时数字栈栈底就是结果

    destroyStack(&symbol_stack, &res_stack);
    return 0;
}

注:输入表达式时候不要有空格,表达式以符号 '#' 结尾!

运行结果: 

        输入中缀表达式:1*(9/2-1+0.3-8/0.2)+0.002#
        >> -36.198000

        输入中缀表达式:0.002*0.1+3*((9-6)/2-0.5)#
        >> 3.000200

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值