C+栈实现中缀转前、后缀并计算(四则计算器)+源码

前、中、后缀表达式概念介绍

在这里插入图片描述

中缀转前、后缀方法

在这里插入图片描述

用栈实现表达式转换与计算原理

转换

在这里插入图片描述

gif来源在这里插入图片描述

计算

在这里插入图片描述

gif来源在这里插入图片描述

在这里插入图片描述

c代码实现

在这里插入图片描述

九层之台,起于累土,需要先搭建好底层结构,再实现逻辑业务。
(涉及:联合体,结构体,链栈,链表,指针等)
对于栈结构实现如下。该栈能同时入栈两种不同类型的数据,每个栈节点可以是运算数或者运算符

1.1. 头文件LStack.h

本BLOG上原创文章未经本人许可,不得用于商业用途。转载请注明出处,否则保留追究法律责任的权利。
https://blog.csdn.net/Ijerome/article/details/111773298
#ifndef _LSTACK_H_
#define _LSTACK_H_

/************************************************************
  Copyright (C), 1988-1999
  FileName: LStack.h     2021.01.02
  Author:hzp        Version :1.0  
  Description:   	//链表存用于存放操作符或者操作数
  Version:          // v1.0

  History:          // 历史修改记录
      <author>     <time>      <version >     <desc>

***********************************************************/

#include "stdio.h"
#include "stdlib.h"

//定义bool类型
#ifndef BOOL  
#define BOOL int
#define TRUE 1
#define FALSE 0
#endif 

//定义运算符优先级,同时也起到标注作用
#define ADD_PRIORITY (0)//加
#define SUB_PRIORITY (1)//减
#define MUL_PRIORITY (3)//乘
#define DIV_PRIORITY (4)//除
#define L_BRACKET_PRIORITY (6)//左括号
#define R_BRACKET_PRIORITY (7)//右括号
#define OPREATOR (1)  //操作符类型
#define NUMBER (2)  //操作数类型
typedef int operatorKind; //运算符类型数据
typedef double numberData; //操作数类型数据
typedef char elementKind;//标志元素类型

//联合体类型数据,存放操作符或者操作数
typedef union {
   numberData num; 
   operatorKind kind;
}elementData;

//栈节点
typedef struct lNode *pNode;
typedef struct lNode {
    elementData data;
    elementKind elementKind; 
    pNode  next;
}lNode;


typedef struct {
    pNode top; //栈顶
    int count; //元素数量
}linkStack;

//初始化链栈
void initStack(linkStack *lStack);

//入栈
BOOL pushStack(linkStack *lStack , pNode pushNode);

//出栈
void popStack(linkStack *lStack, pNode popNode);

//查看栈顶元素(不出栈)
void readStack(linkStack *lStack, pNode popNode);

//释放栈
void freeStack(linkStack *lStack);

//打印节点数据
void printfStackNode(pNode node);
#endif

1.2. 源文件LStack.c

本BLOG上原创文章未经本人许可,不得用于商业用途。转载请注明出处,否则保留追究法律责任的权利。
https://blog.csdn.net/Ijerome/article/details/111773298
#include"LStack.h"
#include <stdlib.h>

/************************************************************
  Copyright (C), 1988-1999
  FileName: LStack.c     2021.01.02
  Author:hzp        Version :1.0
  Description:   	//链表实现栈
  Version:          // v1.0

  History:          // 历史修改记录
      <author>     <time>      <version >     <desc>

***********************************************************/

//函数定义
//初始化链栈
void initStack(linkStack *lStack) 
{
    lStack->top = NULL;
    lStack->count = 0;
}

/*************************************************
 @ Function:  pushStack
 @ Description:
    入栈操作,头插法
 @ Input:
    lStack        //栈指针
    elemt         //入栈元素指针
    size          //入栈元素大小
 @ Output:
 @ Return:
    入栈成功/失败 
 @ Others:
*************************************************/
BOOL pushStack(linkStack *lStack, pNode pushNode)
{
    pNode Node = NULL;//栈节点
    pNode temp = NULL;//临时节点
    if (!pushNode||!lStack)return FALSE;//空指针

    Node = (pNode)malloc(sizeof(lNode));
    if (!Node) //申请空间失败
    {
        return FALSE;
    }
    int size = sizeof(elementData);
    memcpy(&Node->data,&pushNode->data,size);//数据拷贝到自己节点空间,如果不做数据类型大小修改,这里可以直接赋值
    Node->elementKind = pushNode->elementKind;
    Node->next = NULL;
    //头插法,插入节点
    temp= lStack->top;
    lStack->top = Node;
    Node->next = temp;
    lStack->count += 1;
    return TRUE;
}

/*************************************************
 @ Function:  popStack
 @ Description:
    出栈操作
 @ Input:
    lStack        //栈指针
 @ Output:
    popNode  //将出栈的元素赋值到该指针上
 @ Return:
 @ Others:
 popNode指针空间需要开辟,以保证达到空间需求
*************************************************/
void popStack(linkStack *lStack, pNode popNode)
{
    pNode tempNode = NULL;//栈节点
    if (lStack->count <= 0||!lStack||!popNode)return;//栈空
    //赋值节点元素数据
    memcpy(&popNode->data, &lStack->top->data, sizeof(elementData));
    popNode->elementKind = lStack->top->elementKind;
    //释放节点空间
    tempNode = lStack->top;
    lStack->top = lStack->top->next;
    free(tempNode);//
    tempNode = NULL;
    lStack->count -= 1;//计数
    return ;
}

/*************************************************
 @ Function:  readStack
 @ Description:
    查看栈顶元素(不出栈)
 @ Input:
    lStack        //栈指针
 @ Output:
    popNode  //将栈顶的元素赋值到该指针上
 @ Return:
 @ Others:
 popNode指针空间需要开辟,以保证达到空间需求
*************************************************/
void readStack(linkStack *lStack,pNode popNode)
{
    pNode tempNode = NULL;//栈节点
    if (lStack->count <= 0 || !lStack || !popNode)return;//栈空
    //赋值节点元素数据
    memcpy(&popNode->data, &lStack->top->data, sizeof(elementData));
    popNode->elementKind = lStack->top->elementKind;
    return;
}

//销毁栈
void freeStack(linkStack *lStack)
{
    pNode temp = NULL;
    temp = lStack->top;
    while (temp)//栈内还有元素
    {
        lStack->top = lStack->top->next;
        free(temp);
        lStack->count--; //测试用
        temp = lStack->top;
    }
    lStack->count = 0;
}

//打印节点数据
void printfStackNode(pNode node )
{
    if (!node)return;
    if (node->elementKind == NUMBER)//数字
    {
        printf("%0.2lf ", node->data.num);
    }
    else
    {
        switch (node->data.kind)
        {
        case ADD_PRIORITY:printf("+ "); break;
        case SUB_PRIORITY:printf("- "); break;
        case MUL_PRIORITY:printf("* "); break;
        case DIV_PRIORITY:printf("/ "); break;
        case L_BRACKET_PRIORITY:printf("( "); break;
        case R_BRACKET_PRIORITY:printf(") "); break;
        default:printf("? ");
        }
    }
}

入栈顺序:1+2+3+4+5+ 出栈结果如下,测试通过)
在这里插入图片描述
2. 前面已经实现了栈的结构,用来存放运算符以及运算数,但是对于用户输入的字符串,并不知道哪些是运算符,那些是运算数,哪些字符是整数以及小数部分,因此,需要一种方法来将用户输入转化为能识别的数据存放,目的是将运算数以及运算符分开。

在此,因为前面的链表节点已经能够存放操作数和操作符,所以利用前面的节点类型生成链表,单纯用来存放用户输入的中缀表达式,这样在计算阶段,就可以根据节点类型方便地转换和计算结果。链表文件如下

2.1 头文件 turnRPN.h

/************************************************************
  Copyright (C), 1988-1999
  FileName: turnRPN.h     2021.01.02
  Author:hzp        Version :1.0
  Description:   	//获取用户输入,并且转换、计算后缀表达式的模块
  Version:          // v1.0

  History:          // 历史修改记录
      <author>     <time>      <version >     <desc>
本BLOG上原创文章未经本人许可,不得用于商业用途。转载请注明出处,否则保留追究法律责任的权利。
https://blog.csdn.net/Ijerome/article/details/111773298
***********************************************************/
#ifndef _TURN_RPN_H_
#define _TURN_RPN_H_
#include "LStack.h"
#include<stdio.h>

//定义扫描标记,用于扫描中缀表达式字符串并且转换成中缀表达式链
#define PRE_NULL (0)//前面没有字符
#define PRE_SYMBOL (1)//前面字符为运算符类型
#define PRE_INT (2)//前面字符为整形
#define PRE_DOUBLE (3) //前面字符为浮点类型

//单链表结构
typedef struct calLink {
    lNode* front;//链头
    lNode* rear;//链尾
    int count;
}calLink;

//函数
//初始化并且获取中缀表达式链表 
BOOL initCalLink(calLink* link);

//释放中缀表达式链表
void freeCalLink(calLink* link);

//尾插法添加链表
void addCalLink(calLink* link, pNode addNode);

//根据中缀表达式链转换并计算结果
BOOL  calMiddle(calLink* link, numberData *sumData);

//右括号处理
BOOL popBracket(calLink* link, linkStack* lStack);

//栈内的四则运算(括号不参与)
BOOL calFun(linkStack* opStack, linkStack* numStack, operatorKind kind);

//单个操作符计算(括号不参与)
BOOL singleFun(linkStack* numStack, operatorKind kind);

#endif // !_TURN_RPN_H_

2.2 源文件turnRPN.c

/************************************************************
  Copyright (C), 1988-1999
  FileName: turnRPN.c    2021.01.02
  Author:hzp        Version :1.0
  Description:   	//获取用户输入,并且转换、计算后缀表达式的模块
  Version:          // v1.0

  History:          // 历史修改记录
      <author>     <time>      <version >     <desc>
本BLOG上原创文章未经本人许可,不得用于商业用途。转载请注明出处,否则保留追究法律责任的权利。
https://blog.csdn.net/Ijerome/article/details/111773298
***********************************************************/
#include "turnRPN.h"

//函数
//初始化并且获取中缀表达式链表 
BOOL initCalLink(calLink* link)
{
    char getc=1;  //用于接收输入
    int preKind = PRE_NULL;  //用于标记当前字符前的数据类型,以便当前字符的转换
    int doubleLevel = 0;//小数点位数
    //初始化链表
    lNode node;
    node.next = NULL;
    link->count = 0;
    link->front = NULL;
    link->rear = NULL;
    //接收输入的表达式并转换链表
    while ((getc = getchar()) != '\n')
    {

        //分析每个字符
        switch (getc)
        {
        case '+':
        {
            node.elementKind = OPREATOR;//链表节点为运算符
            node.data.kind = ADD_PRIORITY;
            addCalLink(link, &node);//添加链表
            preKind = PRE_SYMBOL;//链表前节点为运算符类型
        }break;
        case '-':
        {
            if (preKind == PRE_SYMBOL)//-1=0-1,为后续后缀计算实现负数计算,在纯负号前添加0
            {
                node.elementKind = NUMBER;//链表节点为运算符
                node.data.num = 0;
                addCalLink(link, &node);//添加链表
            }
            node.elementKind = OPREATOR;//链表节点为运算符
            node.data.kind = SUB_PRIORITY;
            addCalLink(link, &node);//添加链表
            preKind = PRE_SYMBOL;//链表前节点为运算符类型
        }break;
        case '*':
        {
            node.elementKind = OPREATOR;//链表节点为运算符
            node.data.kind = MUL_PRIORITY;
            addCalLink(link, &node);//添加链表
            preKind = PRE_SYMBOL;//链表前节点为运算符类型
        }break;
        case '/':
        {
            node.elementKind = OPREATOR;//链表节点为运算符
            node.data.kind = DIV_PRIORITY;
            addCalLink(link, &node);//添加链表
            preKind = PRE_SYMBOL;//链表前节点为运算符类型
        }break;
        case '(':
        {
            node.elementKind = OPREATOR;//链表节点为运算符
            node.data.kind =L_BRACKET_PRIORITY;
            addCalLink(link, &node);//添加链表
            preKind = PRE_SYMBOL;//链表前节点为运算符类型
        }break;
        case ')':
        {
            node.elementKind = OPREATOR;//链表节点为运算符
            node.data.kind = R_BRACKET_PRIORITY;
            addCalLink(link, &node);//添加链表
            preKind = PRE_SYMBOL;//链表前节点为运算符类型
        }break;
        case '.':  //小数点情况需要做标记,说明后面字符为小数
        {
            if (preKind != PRE_INT) //小数点前必须为整数
            {
                freeCalLink(link);//释放已经建立的链表
                return FALSE;
            }
            else 
            {
                preKind = PRE_DOUBLE;//链表前节点为浮点类型
            }
        }break;
        case ' ':break;  //空格不管
        default:  //数字情况
        {
            if (getc >= 48 && getc <= 57) //数字
            {
                numberData numChar = (numberData)getc - 48;//取当前字符对应的数字
                if (preKind == PRE_SYMBOL|| preKind == PRE_NULL)//如果当前表达式字符前方是空或运算符,那么当前需要新建一个数字节点
                {
                    node.elementKind = NUMBER;//链表节点为运算符
                    node.data.num = 0;//数值初始化0
                    node.data.num += numChar;
                    addCalLink(link, &node);//添加链表
                    preKind = PRE_INT;//链表前节点为整形类型
                    doubleLevel = 0;//目前小数点保留0位
                }
                else if (preKind == PRE_DOUBLE)  //如果当前表达式字符前方是浮点,就取链尾浮点运算
                {
                    doubleLevel++;//小数点保留加1位
                    for (int i = 0; i < doubleLevel; i++)numChar /= 10;
                    link->rear->data.num += numChar;
                }
                else //如果当前表达式前方是整数,就取链尾进行整数运算
                {
                    link->rear->data.num = link->rear->data.num * 10 + numChar;
                }
            }
            else //非法的符号
            {
                freeCalLink(link);//释放已经建立的链表
                return FALSE;
            }
        }
        }
    }
    return TRUE;
}

//释放中缀表达式链表
void freeCalLink(calLink* link)
{
    lNode* temp=NULL;
    if (!link)return;
    while (link->count > 0)
    {
        temp = link->front;
        link->front = temp->next;
        free(temp);
        link->count--;
    }
    link->rear = NULL;
}

//尾插法添加链表
void addCalLink(calLink* link, pNode addNode)
{
    if (!link || !addNode)return;
    lNode* node = NULL;//链表节点
    node = (pNode)malloc(sizeof(lNode));
    memcpy(node, addNode, sizeof(lNode)); //复制数据
    if (link->count > 0)
    {
        link->rear->next = node;
        link->rear = link->rear->next;
        link->rear->next = NULL;
    }
    else
    {
        link->front = node;
        link->rear = link->front;
        link->rear->next = NULL;
    }
    link->count++;
}

//右括号处理
BOOL popBracket( linkStack* opStack, linkStack* numStack)
{
    int isFind = 0;//是否弹出了当前对应的左括号
    lNode tempNode;
    while (!isFind&&opStack->count>0) 
    {
        readStack(opStack, &tempNode); // 查看栈顶元素是否为左括号
        if (tempNode.data.kind == L_BRACKET_PRIORITY)
        {
            popStack(opStack, &tempNode);
            isFind = 1; break;  //结束循环
        }
        else
        {
            if((calFun(opStack, numStack, tempNode.data.kind)==FALSE))return FALSE; //表达式有误
        }
    }
    if (!isFind)return FALSE; //没找到最近匹配的左括号
    return TRUE;
}

//根据当前操作符进行出栈的四则运算(括号不参与)操作符栈没有入栈操作
BOOL calFun(linkStack* opStack, linkStack* numStack, operatorKind kind)
{
    lNode opNode;   //运算符栈节点
    if (opStack->count <= 0)return TRUE;//操作数栈空就返回,上层外部函数入栈
    readStack(opStack, &opNode);//查看栈顶元素,不弹出
    while ((opNode.data.kind+1) >= kind&& (opNode.data.kind + 1)<L_BRACKET_PRIORITY) //弹出优先级不低于当前的运算符,计算
    {
        popStack(opStack, &opNode);//弹出栈
        if (singleFun(numStack, opNode.data.kind) == FALSE)
        {
            return FALSE; //当前符号计算失败(没有操作数或者分母0)
        }
        if (opStack->count <= 0)return TRUE;//操作数栈空就返回,上层外部函数入栈
        readStack(opStack, &opNode);//查看栈顶元素,不弹出
    }
    return TRUE;
}

//单个操作符计算(括号不参与),*a=0,/a=0(单乘或单除都为0)
BOOL singleFun(linkStack* numStack, operatorKind kind)
{
    lNode opNode;   //运算符栈节点
    lNode numRNode;  //运算数栈节点,右操作数,用于存放第一个弹出的数
    lNode numLNode; //左操作数,用于存放第二个弹出的数
    numLNode.data.num = 0;//初始化符号
    if (numStack->count <= 0)return FALSE; //没有操作数
    else if (numStack->count >=1)  //弹出两个操作数
    {
        popStack(numStack, &numRNode);
        if(numStack->count >= 1)popStack(numStack, &numLNode);
    }
    switch (kind)
    {
    case ADD_PRIORITY://{
        numRNode.data.num = numLNode.data.num + numRNode.data.num;
        pushStack(numStack, &numRNode);
    }break;
    case SUB_PRIORITY://{
        numRNode.data.num = numLNode.data.num - numRNode.data.num;
        pushStack(numStack, &numRNode);
    }break;
    case MUL_PRIORITY://{
        numRNode.data.num = numLNode.data.num * numRNode.data.num;
        pushStack(numStack, &numRNode);
    }break;
    case DIV_PRIORITY://{
        if (numRNode.data.num == 0)return FALSE;
        numRNode.data.num = numLNode.data.num / numRNode.data.num;
        pushStack(numStack, &numRNode);
    }break;
    default:return FALSE;//存在不能运算的操作符
    }
    return TRUE;
}

//根据中缀表达式链转换并计算结果
BOOL  calMiddle(calLink* link ,numberData *sumData)
{
    //准备两个栈,一个操作数栈,一个运算符栈,中缀表达式存放在链表里
    linkStack opStack;   //运算符栈
    linkStack numStack;  //运算数栈
    lNode tempNode;      //临时栈节点
    pNode pLinkNode; //链表节点
    int count = 0;//链表节点数
    if (!link || link->count <= 0)return FALSE;
    count = link->count;
    pLinkNode = link->front;  //获取中缀链表头节点
    initStack(&opStack);//初始化栈
    initStack(&numStack);
    //根据中缀表达式链表转换和计算
    while (count-- && pLinkNode)//根据count数量从左到右遍历链表
    {
        if (pLinkNode->elementKind == OPREATOR)//运算符
        {
            if (pLinkNode->data.kind == R_BRACKET_PRIORITY)  //处理右括号
            {
                if (popBracket(&opStack, &numStack) == FALSE)
                {
                    freeStack(&opStack); freeStack(&numStack);
                    return FALSE;//表达式有误 
                }
            }
            else if (pLinkNode->data.kind < L_BRACKET_PRIORITY)// 处理栈内非括号运算符
            {
                if (calFun(&opStack, &numStack, pLinkNode->data.kind) == FALSE)
                {
                    freeStack(&opStack); freeStack(&numStack);
                    return FALSE;//表达式有误 
                }
                pushStack(&opStack, pLinkNode); //当前运算符入栈
            }
            else  pushStack(&opStack, pLinkNode);//左括号直接入栈

        }
        else if (pLinkNode->elementKind == NUMBER)//运算数
        {
            pushStack(&numStack, pLinkNode);  //入操作数栈
        }
        pLinkNode = pLinkNode->next;
    }
    while (opStack.count > 0)//遍历链表结束后,处理剩下的栈内操作符
    {
        popStack(&opStack, &tempNode);//出栈
        if (singleFun(&numStack, tempNode.data.kind) == FALSE)
        {
            freeStack(&opStack); freeStack(&numStack);
            return FALSE;//表达式有误 
        }
    }
    if (numStack.count != 1)//最终运算符栈内自身一个元素才是正确的
    {
        freeStack(&opStack); freeStack(&numStack);
        return FALSE;//栈内结果有误
    }
    else
    {
        popStack(&numStack, &tempNode);
        *sumData = tempNode.data.num; // 返回结果
        freeStack(&opStack); freeStack(&numStack);
    }
    return TRUE;
}

  1. 最后,只需要根据最初的出栈、入栈规则,便可以转换中缀表达式链并且计算出相应的四则运算结果。
#include <stdio.h>
#include <stdlib.h>
//#include"test.h"
#include "LStack.h" //栈
#include"turnRPN.h" //链表
int main()
{
	while (1)
	{
		numberData sumData = 0;
		calLink link;
		printf("输入计算的表达式:");
		if (initCalLink(&link) == FALSE)printf("输入有误!\n");
		else if (calMiddle(&link, &sumData) == FALSE)printf("表达式有误!\n");
		else printf("\n结果:%0.3f\n", sumData);

		freeCalLink(&link);
	}
	return 0;
}

实现的功能如下:

  • 四则运算支持
  • 浮点有符号运算
  • 嵌套括号以及空格的忽略
  • 可以方便地添加其他运算符如%、^ 等
    测试结果
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值