C语言手写栈、手写计算器

17 篇文章 0 订阅
17 篇文章 0 订阅

文章结构

本文尝试通过纯C语言写一个计算器,在Visual Studio和VScode运行都可以通过,大家可以下载到自己的编译器试一下。其中主要涉及的知识点是C语言手写栈,中缀表达式与后缀表达式的转换等

C语言手写栈Struct Stack

stack.h
本文通过顺序结构写出栈的结构体,并实现了相应的入栈、出栈、初始化等操作

// An highlighted block
#include<stdlib.h>
typedef double ElemType;	//这里比较重要,关乎栈中存储的是什么元素
typedef struct stack
{
    int top;    //表示栈顶元素标识
    int maxSize;
    ElemType *element;
}Stack;
//创建一个能容纳mSize和单元的空栈
void Create(Stack *S, int mSize) {
    S->maxSize = mSize;
    S->element = (ElemType*)malloc(sizeof(ElemType) * mSize);   //申请了一段double型的内存当数组用了
    S->top = -1;
}
//销毁栈,释放空间
void Destroy(Stack *S) {
    S->maxSize = -1;
    free(S->element);
    S->top = -1;
}
//清楚栈中元素,但不释放空间
void Clear(Stack *S) {
    S->top = -1;
}
//判断堆栈是否已满,若已满返回true,否则返回false
bool IsFull(Stack *S) {
    return S->top == S->maxSize - 1;
}
//判断堆栈是否为空,空返回true, 否则返回false
bool IsEmpty(Stack *S) {
    return S->top == -1;
}
//获取栈顶元素,通过指针x返回,操作成功返回true, 否则返回false
bool Top(Stack *S, ElemType *x) {
    if(IsEmpty(S)) 
        return false;
    *x = S->element[S->top];
    return true;
}
//栈顶位置插入元素x入栈
bool Push(Stack *S, ElemType x) {
    if(IsFull(S)) 
        return false;
    S->top++;
    S->element[S->top] = x;
    return true;
}
//出栈,删除栈顶元素
bool Pop(Stack *S) {
    if(IsEmpty(S)) 
        return false;
    S->top--;
    return true;
}

后缀表达式求值

如果大家会写后缀表达式的话,直接调用前面写的“stack.h”就可以求值了。
中缀表达式是我们常见的“a + b”这种格式,转换后缀表达式就是“a b +”的形式了。
我们数学中理解表达式通常都是中缀表达式的格式,但计算机理解表达式需要转换为后缀表达式的形式(如果是通过栈来实现的话)。
后缀表达式计算思想:从左至右扫描后缀表达式,遇到操作数进栈;遇到操作符,则从栈中弹出前两个元素执行相应计算,将计算结果再进栈,直到后缀表达式扫描结束。注意这里只考虑双目操作符。

C语言部分操作符优先级:

操作符优先级
-, !7
*, /, %6
+, -5
<, <=, >, >=4
==, !=3
&&2
双竖“或”(这里敲不出来)1

中缀表达式、后缀表达式转换:

中缀表达式后缀表达式
a*b+cab*c+
a*b/cab*c/
abcde*fabcdef*
a+(b*c+d)/eabc*d+e/+
a*((b+c)/(d-e)-f)abc+de-/f-*
a/(b-c)+d*eabc-/de*+
// An highlighted block
#include<iostream>
#include "stack.h"
#include<math.h>
#include<string.h>
using namespace std;
#define STACKSIZE 20
#define ITEMSIZE 20
#define POSTFIXSIZE 200
//判断表达式中是否有非法字符(合法字符包括0~9 . + - * / ^ )
bool IsLegal(char *postfix) {
    int i;
    char c;
    for(i = 0; i < strlen(postfix); i++) {
        c = postfix[i];
        if(!((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^' || c == ' '))
            return false;
    }
    return true;
}
//从表达式当前位置curPos获取元素,完成后移动到下一元素首字符位置
//操作数返回0,操作符返回1,异常返回-1
int GetItem(char *postfix, int *curPos, char *item) {
    int i = 0, k = *curPos, flag;
    if(postfix[k] == '.') { //元素首字符不能是小数点
        flag = -1;
    } else if(postfix[k] >= '0' && postfix[k] <= '9') {     //元素首字符是数字,是正确的操作数
        while ((postfix[k] >= '0' && postfix[k] <= '9') || postfix[k] == '.') {
            item[i++] = postfix[k++];
        }
        item[i] = '\0';     //将数字截取为字符串形式保存
        flag = 0;
    } else {        //该元素为操作符
        item[0] = postfix[k++];
        item[1] = '\0';
        flag = 1;
    }
    while(postfix[k] == ' ') {  //跳过当前元素后面的空格,在下一次趣元素的起始位置为非空格字符
        k++;
    }
    *curPos = k;
    return flag;
}
//根据操作符执行计算,包括两个操作数和一个操作符(例如a+b, a左操作数,+操作符,b右操作数)
void DoOperator(Stack *S, char oper) {
    double oper1, oper2;
    if(!Top(S, &oper1)) {   //从栈中弹出右操作数
        printf("异常:后缀表达式格式出错,存在多余操作符\n");
        exit(0);
    }
    Pop(S);
    if(!Top(S, &oper2)) {   //从栈中弹出左操作数
        printf("异常:后缀表达式格式出错,存在多余操作符\n");
        exit(0);
    }
    Pop(S);
    switch (oper)   //根据操作符执行相应运算
    {
    case '+' :
        Push(S, oper2 + oper1);
        break;
    case '-' :
        Push(S, oper2 - oper1);
        break;
    case '*' :
        Push(S, oper2 * oper1);
        break;
    case '/' :
        if(fabs(oper1) < 1e-6) {    //判断分母是否为0
            printf("异常:除数不可以为0!\n");
            exit(0);
        } else {
            Push(S, oper2 / oper1);
        }
        break;
    case '^' :
        Push(S, pow(oper2, oper1));
        break;
    }
}
//对后缀表达式postfix求值
double Calculating(char *postfix) {
    Stack S;
    char item[ITEMSIZE];    //储存后缀表达式中的元素
    double data;
    int flag = -1;          //标记当前扫描元素的类型,1表示操作数,0表示操作符,-1表示异常
    int curPos = 0;         //记录当前扫描元素首字符下标位置
    while(postfix[curPos] == ' ')   //过滤postfix前面所有空格
        curPos++;
    Create(&S, STACKSIZE);  //创建栈,申请内存
    while (curPos < strlen(postfix))
    {
        flag = GetItem(postfix, &curPos, item);     //获取当前扫描的表达式的元素
        if(flag == -1) {
            printf("异常:后缀表达式元素不合法!\n");
            exit(0);
        } else if(flag == 1) {  //执行相应的操作符运算
            switch (item[0])
            {
            case '+':
            case '-':
            case '*':
            case '/':
            case '^':
                DoOperator(&S, item[0]);
                break;
            }
        } else {            //操作数进栈
            data = atof(item);  //将字符串转换为浮点数输出
            Push(&S, data);
        }
    }
    if(S.top == 0) {    //如果栈中只剩下唯一元素,则栈顶元素即为计算结果
        Top(&S, &data);
    } else {
        printf("异常:后缀表达式格式出错,存在多余操作数\n");
        exit(0);
    }
    Destroy(&S);    //释放空间
    return data;
}
int main() {
    char postfix[POSTFIXSIZE];      //存储表达式中的扫描元素
    printf("请输入后缀表达式格式(连续操作数之间用空格隔开):\n");
    gets(postfix);                  //输入流读取后缀表达式,cmd输入计算表达式(后缀表达式形式)
    if(!IsLegal(postfix)) {
        printf("异常:中缀表达式存在非法字符\n");
    } else 
        printf("%s = %.2f\n", postfix, Calculating(postfix));   //输出计算结果,保留两位小数1
    system("pause");
    return 0;
}
输入格式(数字之间需要空格隔开)
1 2 + 
===> 3.00
6 4 2 - / 3 2 * + 
===> 9.00
计算复杂度:O(n)

中缀表达式转后缀表达式

"stack.h"

#include<stdlib.h>
typedef char ElemType;
typedef struct stack
{
    int top;    //表示栈顶元素标识
    int maxSize;
    ElemType *element;
}Stack;
//创建一个能容纳mSize和单元的空栈
void Create(Stack *S, int mSize) {
    S->maxSize = mSize;
    S->element = (ElemType*)malloc(sizeof(ElemType) * mSize);   //申请一段double型的内存当数组用
    S->top = -1;
}
//销毁栈,释放空间
void Destroy(Stack *S) {
    S->maxSize = -1;
    free(S->element);
    S->top = -1;
}
//清楚栈中元素,但不释放空间
void Clear(Stack *S) {
    S->top = -1;
}
//判断堆栈是否已满,若已满返回true,否则返回false
bool IsFull(Stack *S) {
    return S->top == S->maxSize - 1;
}
//判断堆栈是否为空,空返回true, 否则返回false
bool IsEmpty(Stack *S) {
    return S->top == -1;
}
//获取栈顶元素,通过指针x返回,操作成功返回true, 否则返回false
bool Top(Stack *S, ElemType *x) {
    if(IsEmpty(S)) 
        return false;
    *x = S->element[S->top];
    return true;
}
//栈顶位置插入元素x入栈
bool Push(Stack *S, ElemType x) {
    if(IsFull(S)) 
        return false;
    S->top++;
    S->element[S->top] = x;
    return true;
}
//出栈,删除栈顶元素
bool Pop(Stack *S) {
    if(IsEmpty(S)) 
        return false;
    S->top--;
    return true;
}

“main.cpp”

#include<iostream>
#include "stack.h"
#include<math.h>
#include<string.h>
using namespace std;
#define STACKSIZE 20    //定义栈的容量大小
#define ITEMSIZE 20     //表达式中元素最大长度
#define EXPSIZE 200     //表达式最大长度
//判断表达式中是否有非法字符(合法字符包括0~9 . + - * / ^ )
bool IsLegal(char *postfix) {
    int i;
    char c;
    for(i = 0; i < strlen(postfix); i++) {
        c = postfix[i];
        if(!((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^' || c == ' '))
            return false;
    }
    return true;
}
//从表达式当前位置curPos获取元素,完成后移动到下一元素首字符位置
//操作数返回0,操作符返回1,异常返回-1
int GetItem(char *postfix, int *curPos, char *item) {
    int i = 0, k = *curPos, flag;
    if(postfix[k] == '.') { //元素首字符不能是小数点
        flag = -1;
    } else if(postfix[k] >= '0' && postfix[k] <= '9') {     //元素首字符是数字,是正确的操作数
        while ((postfix[k] >= '0' && postfix[k] <= '9') || postfix[k] == '.') {
            item[i++] = postfix[k++];
        }
        item[i] = '\0';     //将数字截取为字符串形式保存
        flag = 0;
    } else {        //该元素为操作符
        item[0] = postfix[k++];
        item[1] = '\0';
        flag = 1;
    }
    while(postfix[k] == ' ') {  //跳过当前元素后面的空格,在下一次趣元素的起始位置为非空格字符
        k++;
    }
    *curPos = k;
    return flag;
}
//获取操作符的栈外优先级
int ICP(char c) {
    if(c == '#') {
        return 0;
    } else if(c == '(') {
        return 7;
    } else if(c == '*' || c == '/') {
        return 4;
    } else if(c == '+' || c == '-') {
        return 2;
    } else if(c == ')') {
        return 1;
    } else {
        printf("%c!\n", c); //后缀表达式不支持操作符
        exit(-1);
    }
}
//获取操作符的栈内优先级
int ISP(char c) {
    if(c == '#') {
        return 0;
    } else if(c == '(') {
        return 1;
    } else if(c == '*' || c == '/') {
        return 5;
    } else if(c == '+' || c == '-') {
        return 3;
    } else if(c == ')') {
        return 7;
    } else {
        printf("后缀表达式不支持操作符%c!\n", c);   //后缀表达式不支持操作符
        exit(-1);
    }
}
//将中缀表达式infix转换为后缀表达式postfix,后缀表达式中的相邻元素(操作符或操作数)用空格分开
void InfixToPostfix(char *infix, char *postfix) {
    Stack S;
    char item[ITEMSIZE];    //存储中缀表达式的元素,通过字符数组形式,C++中可以通过String类型,相同道理
    int flag = -1;          //标记当前扫描元素类型,1操作符,0操作数,-1异常
    int curPos = 0;         //记录当前扫描元素首字符下标位置
    int k = 0, i;
    char ch, curOP;
    while(infix[curPos] == ' ')     //过滤infix前面所有空格
        curPos++;
    Create(&S, STACKSIZE);          //创建栈,申请内存空间
    Push(&S, '#');
    while(curPos < strlen(infix)) {
        flag = GetItem(infix, &curPos, item);   //获取当前扫描表达式的元素
        if(flag == -1) {
            printf("异常:中缀表达式元素不合法!\n");   //异常:中缀表达式元素不合法!
            exit(-1);
        } else if(flag == 1) {      //当前元素为操作符或界符
            curOP = item[0];        //curOP是当前操作符,只有一个字符表示就够了
            if(curOP == ')') {      //扫描到右括号的时候进行出栈操作,实现括号闭合,直到遇到左括号
                do {
                    Top(&S, &ch);
                    Pop(&S);
                    if(ch == '#') {
                        printf("异常:中缀表达式元素不合法!\n");   //异常:中缀表达式元素不合法!
                        exit(-1);
                    }
                    if(ch != '(') {         //左括号不输出
                        postfix[k++] = ch;
                        postfix[k++] = ' '; //相邻元素用空格分开
                    }
                } while (ch != '(');
            } else {                        //扫描到其他操作符时候的处理
                Top(&S, &ch);               //获取当前栈顶操作符
                while (ICP(curOP) <= ISP(ch))
                {
                    Pop(&S);
                    postfix[k++] = ch;
                    postfix[k++] = ' ';     //相邻元素空格分开
                    Top(&S, &ch);
                }
                Push(&S, curOP);            //当前扫描到的操作符进栈
            }
        } else {                            //当前扫描到的是操作数,不做处理直接输出
            for(i = 0; i < strlen(item); i++, k++) {
                postfix[k] = item[i];
            }
            postfix[k++] = ' ';             //相邻元素空格分开
        }
    }
    while (!IsEmpty(&S))                    //输出栈中剩余操作符
    {
        Top(&S, &ch);
        Pop(&S);
        if(ch != '#') {
            postfix[k++] = ch;
            postfix[k++] = ' ';
        }
    }
    postfix[--k] = '\0';                    //去除最后多余空格
}
int main() {
    char infix[EXPSIZE];                    //存储中缀表达式
    char postfix[EXPSIZE];                  //存储后缀表达式
    printf("请输入中缀表达式:%s\n", infix);  //请输入中缀表达式:
    gets(infix);
    if(!IsLegal(infix)) {
        printf("异常:中缀表达式存在非法字符\n");      //异常:中缀表达式存在非法字符
        return -1;
    } else {
        InfixToPostfix(infix, postfix);
        printf("%s ===> %s\n", infix, postfix);     //输出转换后的后缀表达式
    }
    system("pause");
    return 0;
}
1+2
===>1 2 +
时间复杂度O(n)
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值