【献给过去的自己】栈实现计算器(C语言)

背景

        记得在刚学C语言时,写了一篇栈实现计算器-CSDN博客文章。偶然间看到了文章的阅读量以及评论,居然有1.7w的展现和多条博友的点评,反馈。

        现在回过头来看,的确有许多不严谨的地方,毕竟当时分享文章时,还未毕业。理论和思维还不够严谨。但是我还依稀记得,班级上当时写出这个程序的同学,稀疏可数。所以在当时,还是有骄傲的资本的。本着对技术精益求精的态度,再通过本篇文章希望能够帮助刚接触C语言的朋友,也是给过去的自己一个满意的答复~

规则

        对于一个表达式,我们应该如何去识别它呢?当时,老师和我们说,按照如下规则进行解析即可。

        当时我们并不懂这个规则的由来,只知道按照这个规则去编程即可。再后来的工作中,因为考《软件设计师》资格证,了解到上述的规则,其实就是后缀表达式。同理还有前缀表达式中缀表达式

中缀表达式

        中缀表达式就是我们常用的一种算数表示方式。它的特点是操作符以中缀的方式处于操作数中间。但是中缀表达比较适合人类计算,对于计算机而言过于复杂。前缀表达式和后缀表达式对于计算机而言,更加友好。

        因此,我们想用程序实现计算器功能,有两种方式:

中缀表达式--> 前缀表达式-->计算

中缀表达式--> 后缀表达式-->计算

前缀表达式

        前缀表达式的运算符位于两个操作数之前,又称为前缀记法或波兰式。比如表达式(中缀)5+4,前缀表达式+ 5 4。因此使用前缀表达式进行计算,需要两个步骤。

  1. 如何将中缀表达式转换为前缀表达式

  2. 计算机如何识别前缀表达式并计算

中缀表达式转换前缀表达式

        根据文中描述,中缀表达式转换为前缀表达式的规则如下:

  1. 初始化两个栈:运算符栈S1和存储中间结果的栈S2;

  2. 从右至左扫描中缀表达式;

  3. 遇到操作数时,将其压入S2;

  4. 遇到运算符时,比较其与S1栈顶运算符的优先级;

    1. 如果S1为空,或栈顶运算符为右括号),则将此运算符入栈;

    2. 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入S1;

    3. 否则,将S1栈顶的运算符弹出并压入到S2,再次转到4.1与S1中新的栈顶运算符相比较;

  5. 遇到括号时:

    1. 如果是右括号),则直接压入S1;

    2. 如果是左括号(,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;

  6. 重复步骤25,直到表达式的最左边;

  7. 将S1中剩余的运算符依次弹出并压入S2;

  8. 依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。

        虽然规则很复杂,但是编码难度并不是很大,大家可以按照自己的技术能力尝试一下。

分析思路

        我们以表达式1+(2+3)*4-5举例。

        1. 因为输入表达式是字符串,后续我们需要从右往左扫描表达式,因此首先需要将字符串表达式中的运算符和操作数进行区分,可以用整型数组如下图:

        2. 根据25规则,进行分析。

       

        3. 弹出S2中的数据元素:- + 1 * + 2 3 4 5;

代码示例

我的代码示例如下:

#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<stdint.h>
#include<stdbool.h>#define STACK_LEN 1024/** 中缀表达式栈*/
static int32_t g_infix_expression[1024] = {0};/** 前缀表达式栈*/
static int32_t g_prefix_expression[1024] = {0};/** 后缀表达式栈*/
static int32_t g_suffix_expression[1024] = {0};/*** @brief 将输入的字符串表达式转换为中缀表达式** @param [in] expression 字符串表达式* @return int 0 成功 non-0 失败* */
int expression2infix(const char* expression)
{if(expression == NULL){printf("input error\n");return -1;}int dataTmp = 0; //表达式中的操作数bool dataFlag = false; // 操作数标识,表示当前是否有数据需要入栈const char* ptr = expression;int32_t* infix_index = g_infix_expression;printf("expression = %s\n",expression);while(*ptr != '\0'){/** 字符为数字*/if('0' <= *ptr  && *ptr <= '9'){dataTmp = dataTmp*10 +(*ptr - '0');dataFlag = true;}/**字符为操作符或括号*/else if(*ptr == '+' || *ptr == '-' || *ptr == '*' || *ptr == '/' || *ptr == '(' || *ptr == ')'){if(dataFlag == true){*(infix_index++) = dataTmp;dataFlag = false;dataTmp = 0;}*(infix_index++) = *ptr;}else{printf("wrong exptrssion\n");return -1;}ptr++;}/**将最后一个操作数,入栈*/if(dataFlag == true){*(infix_index++) = dataTmp;dataFlag = false;dataTmp = 0;}return 0;
}/*** @brief 将中缀表达式转换为前缀表达式** @return int 0 成功 non-0 失败* */
int infix2prefixExpression()
{/**初始化运算符栈和中间结果栈*/int32_t stack_s1[STACK_LEN] = {0};int32_t stack_s1_top = 0;int32_t stack_s2[STACK_LEN] = {0};int32_t stack_s2_top = 0; int32_t * index = g_infix_expression;/**获取中缀表达式最右侧操作数*/while(*(index+1) != 0){index++;}while(index != g_infix_expression){/** 操作符*/if(*index == '+' || *index == '-' || *index == '*' || *index == '/'){while(true){/**S1为空,或栈顶运算符为右括号),则将此运算符入栈*/if(stack_s1_top == 0 || stack_s1[stack_s1_top-1] == ')' || stack_s1[stack_s1_top-1] == '-'|| stack_s1[stack_s1_top-1] == '+'){stack_s1[stack_s1_top++] = *index;break;}stack_s2[stack_s2_top++] = stack_s1[stack_s1_top-1];stack_s1[stack_s1_top-1] = 0;stack_s1_top = stack_s1_top -1;}}/**左括号* 则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止*/else if(*index == '('){while(true){/**异常*/if(stack_s1_top == 0){printf("infix experssion worong\n");return -1;}/**遇到右括号,丢弃括号*/if(stack_s1[stack_s1_top-1] == ')'){stack_s1[stack_s1_top-1] = 0;stack_s1_top = stack_s1_top -1;break;}/**其它符号需要入栈S2*/else{stack_s2[stack_s2_top++] = stack_s1[stack_s1_top-1];stack_s1[stack_s1_top-1] = 0;stack_s1_top--;}}}/**右括号* 直接入运算符栈s1*/else if(*index == ')'){stack_s1[stack_s1_top++] = *index;}/** 操作数* 直接加入栈s2*/else{stack_s2[stack_s2_top++] = *index;}index--;
#if 0printf("==============\n");printf("stack_s1=");for(int i = 0 ; i < stack_s1_top; i++){(stack_s1[i] > 9) ? (printf("%c ",stack_s1[i])):(printf("%d ",stack_s1[i]));}printf("\n");printf("stack_s2=");for(int i = 0 ; i < stack_s2_top; i++){(stack_s2[i] > 9) ? (printf("%c ",stack_s2[i])):(printf("%d ",stack_s2[i]));}printf("\n");
#endif  }/**将最左侧操作数压入s2*/stack_s2[stack_s2_top++] = *index;/**将s1中的符号压入s2*/for(int i = stack_s1_top - 1; i >= 0; i-- ){stack_s2[stack_s2_top++] = stack_s1[i];stack_s1[i] = 0;    }/**将s2中的数据弹出,放入前缀表达式栈中*/for(int i = 0 ; stack_s2_top > 0; i++,stack_s2_top--){g_prefix_expression[i] = stack_s2[stack_s2_top-1];}return 0;
}int main(int argc,char* argv[])
{if(argc != 2){printf("please input experssion\n");return -1;}int32_t iRet = 0;iRet = expression2infix(argv[1]);if(iRet == 0){for(int i = 0 ; i < STACK_LEN && g_infix_expression[i] != 0; i++){if(g_infix_expression[i] == '+' || g_infix_expression[i] == '-' || g_infix_expression[i] == '*' || g_infix_expression[i] == '/'){printf("%c ",g_infix_expression[i]);}else{printf("%d ",g_infix_expression[i]);}}printf("\n");}iRet = infix2prefixExpression();if(iRet == 0){for(int i = 0 ; i < STACK_LEN && g_prefix_expression[i] != 0; i++){if(g_infix_expression[i] == '+' || g_infix_expression[i] == '-' || g_infix_expression[i] == '*' || g_infix_expression[i] == '/'){printf("%c ",g_infix_expression[i]);}else{printf("%d ",g_infix_expression[i]);}}printf("\n");}prefixExpressionCaculate();return 0;
}

前缀表达式计算

前缀表达式的计算规则如下:

  1. 从右至左扫描表达式;

  2. 遇到数字,压入栈中;

  3. 遇到运算符,弹出栈顶的两个数,并用运算符对这两个数做相应的计算,并将结果入栈;

  4. 重复上述2,3步骤,直到表达式最左端,最后的值为表达式的结果。

分析思路

        以上述后缀表达式举例:- + 1 * + 2 3 4 5

        得出结果为16。

代码示例

新增prefixExpressionCaculate接口。代码如下:

/*** @brief 将前缀表达式进行计算** @return int 0 成功 non-0 失败* */
int prefixExpressionCaculate()
{/**结果栈*/int32_t stack[1024] = {0};int32_t stack_len = 0;/**临时结果*/int32_t tmpResult = 0;int32_t data1 = 0;int32_t data2 = 0;/**获取后缀表达式的最右侧操作数*/int32_t* index = g_prefix_expression;while(*(index+1) != 0){index++;}while(index >= g_prefix_expression){/**弹出栈顶的两个数,并用运算符对这两个数做相应的计算,并将结果入栈*/if(*index == '+' || *index == '-' || *index == '*' || *index == '/'){data1 = stack[stack_len-1];data2 = stack[stack_len-2];if(*index == '+'){tmpResult = data1 + data2;}else if(*index == '-'){tmpResult = data1 - data2;}else if(*index == '*'){tmpResult = data1 * data2;}else if(*index == '/'){tmpResult = data1 / data2;}else{printf("worng prefixExperssion\n");return -1;}stack[stack_len-1] = 0;stack[stack_len-2] = tmpResult;stack_len --;}/**遇到数字,压栈*/else{stack[stack_len] = *index;stack_len ++;}index --;}printf("result = %d\n",stack[0]);return 0;
}

演示

后缀表达式

        后缀表达式与前缀表达式类似,只是运算符位于两个相应操作数之后,后缀表达式也称为后缀记法或逆波兰式。同样,我们需要解决两个问题。

  1. 如何将中缀表达式转换为后缀表达式

  2. 后缀表达式的计算规则

中缀表达式转后缀表达式

根据文中描述,中缀表达式转换为后缀表达式的规则如下:

  1. 初始化两个栈:运算符栈S1和存储中间结果的栈S2;

  2. 从左至右扫描中缀表达式

  3. 遇到操作数时,将其压入S2;

  4. 遇到运算符时,比较其与S1栈顶运算符的优先级;

    1. 如果S1为空,或栈顶运算符为左括号(,则将此运算符入栈;

    2. 否则,若优先级比栈顶运算符的高,也将运算符压入S1;(注意是必须为高,相同或低于都不行)

    3. 否则,将S1栈顶的运算符弹出并压入到S2,再次转到4.2与S1中新的栈顶运算符相比较;

  5. 遇到括号时:

    1. 如果是左括号(,则直接压入S1;

    2. 如果是右括号),则依次弹出S1栈顶的运算符,并压入S2,直到遇到左括号为止,此时将这一对括号丢弃;

  6. 重复步骤25,直到表达式的最右边;

  7. 将S1中剩余的运算符依次弹出并压入S2;

  8. 依次弹出S2中的元素并输出,结果即为中缀表达式对应的后缀表达式。

后缀表达式计算规则

后缀表达式的计算规则如下:

  1. 从左至右扫描表达式;

  2. 遇到数字,压入栈中;

  3. 遇到运算符,弹出栈顶的两个数,并用运算符对这两个数做相应的计算,并将结果入栈;

  4. 重复上述2,3步骤,直到表达式最右端,最后的值为表达式的结果。

        后缀表达式的代码示例可以参考前缀表达式的分析思路和代码,大家可以尝试编写。

总结

        时间流逝,在竞争激烈的社会背景下,我们的身处IT行业,不断逼迫自己去学习,去成长。但是总会觉得自己做的还不够。为什么总是赶不上别人的脚步,陷入怀疑自我的处境。

        朋友们,偶尔回头看看来时路上的自己,你会发现,你一直在成长,你的努力一直是正向反馈着你,不要轻视自己的努力。感谢csdn给予记录成长的平台,也感谢一直努力的自己。共勉~

参考文档

前缀表达式、中缀表达式和后缀表达式 - 乘月归 - 博客园

数据结构和算法(六):前缀、中缀、后缀表达式

栈实现计算器-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/150998.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【日常】爬虫技巧进阶:textarea的value修改与提交问题(以智谱清言为例)

序言 记录一个近期困扰了一些时间的问题。 我很喜欢在爬虫中遇到问题&#xff0c;因为这意味着在这个看似简单的事情里还是有很多值得去探索的新东西。其实本身爬虫也是随着前后端技术的不断更新在进步的。 文章目录 序言Preliminary1 问题缘起1.1 Selenium长文本输入阻塞1.2…

激光雕刻机的雕刻操作

今天小编带大家来学习一下在实践当中学习到的基础操作&#xff0c;激光雕刻机的雕刻玻璃操作&#xff0c;欢迎评论指正。 目录 什么是激光雕刻机 怎么操作激光雕刻机操作 打开电源 打开电脑&#xff0c;选择需要的软件 在CAD上进行绘图&#xff0c;绘图完成进行保存 在RD…

Ubuntu 20.04 调整交换分区大小

Ubuntu 调整交换分区大小 一、系统情况二、去除旧的交换分区文件三、配置并启用交换分区四、查看swap文件大小 一、系统情况 Ubuntu &#xff1a;Ubuntu 20.04.6 LTS 交换分区位置&#xff1a; cat /proc/swaps二、去除旧的交换分区文件 去掉旧的交换分区有两个步骤&#x…

广州一母婴店因设置0元购导致关店

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 广州的一家母婴用品网店Minitutu因双十一优惠券设置错误&#xff0c;导致所有商品变成0元购买&#xff0c;引发消费者疯狂抢购&#xff0c;15万多单订单中有800多万元的损失。店家无奈之下只能暂停营…

css animation 动画如何保留动画结束后的状态 animation-fill-mode: forwards

css animation 动画如何保留动画结束后的状态 animation-fill-mode: forwards 一、问题描述 在做一个弹窗动画提示的时候遇到了一个问题&#xff1a; 在动画结束的时候&#xff0c;移除元素时会有闪一下的问题&#xff0c;像这样&#xff1a; 我的动画结尾是这样的&#xff…

LaTex生成引文(参考文献)时出现乱序,想把引文按顺序显示的解决方法

LaTex生成pdf时文献应用会乱序&#xff1a; 引用bib格式的参考文献时&#xff0c;会这么写&#xff1a; \bibliographystyle{plain} \bibliography{%filename%.bib} 而plain的意思是用作者的姓名排序&#xff0c;而不是按照引用顺序&#xff0c; 解决方案&#xff1a; 所以…

redis高级案列case

案列一 双写一致性 案例二 双锁策略 package com.redis.redis01.service;import com.redis.redis01.bean.RedisBs; import com.redis.redis01.mapper.RedisBsMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; imp…

流体的压力

压力是流体力学中很重要的物理量&#xff0c;国际标准单位为 Pa&#xff08;帕斯卡&#xff09;&#xff0c;其他常用单位包括 MPa&#xff08;兆帕&#xff09;、atm&#xff08;标准大气压&#xff09;、Torr&#xff08;托&#xff09; 等。 在流体内部&#xff0c;压力是标…

数据结构树与二叉树的实现

目录 一、普通树的存储结构 1、双亲表示法 2.孩子表示法 二、二叉树 1.二叉树的顺序存储&#xff08;必须是完全二叉树&#xff0c;否则很浪费空间&#xff09; 1&#xff09;结构体 2.二叉树的链式存储 1&#xff09;结构体 2&#xff09;操作 1.创建一颗二叉树 2.创…

Linux内核mmap内存映射详解及例子实现

mmap在linux哪里&#xff1f; 什么是mmap&#xff1f; 上图说了&#xff0c;mmap是操作这些设备的一种方法&#xff0c;所谓操作设备&#xff0c;比如IO端口&#xff08;点亮一个LED&#xff09;、LCD控制器、磁盘控制器&#xff0c;实际上就是往设备的物理地址读写数据。 但…

【Zabbix监控二】之zabbix自定义监控内容案例(自动发现、自动注册)

一、自定义监控内容 案例&#xff1a;自定义监控客户端服务器登录的人数 需求&#xff1a;限制登录人数不超过3个人&#xff0c;超过5个人就发出报警 1、在客户端创建自定义key 明确需要执行的linux命令 创建zabbix监控项配置文件&#xff0c;用于自定义Key #在zabbix的配…

SMART PLC数值积分器功能块(矩形+梯形积分法完整源代码)

PLC的数值积分器算法也可以参考下面文章链接: PLC算法系列之数值积分器(Integrator)-CSDN博客文章浏览阅读1.5k次,点赞3次,收藏3次。数值积分和微分在工程上的重要意义不用多说,闭环控制的PID控制器就是积分和微分信号的应用。流量累加也会用到。有关积分运算在流量累加上…

算法-二叉树-简单-二叉树的遍历

记录一下算法题的学习6 首先我们要回忆一下怎么样遍历一个树&#xff1a; 三种遍历概念 先序遍历&#xff1a;先访问根节点&#xff0c;再访问左子树&#xff0c;最后访问右子树。 后序遍历&#xff1a;先左子树&#xff0c;再右子树&#xff0c;最后根节点。 中序遍历&…

kafka原理看这一篇就够了

为何使用消息队列 异步。接口方式实现多个系统协作&#xff0c;如图A系统作为用户请求接收方&#xff0c;需要调用多个系统的接口&#xff0c;这些接口还有可能是在A系统里同步调用&#xff0c;所以最后的接口耗时是多个系统接口耗时的总和&#xff1b;mq方式则可以异步发送消…

解决ubuntu23.10 wifi不能使用的问题

解决ubuntu23.10 wifi不能使用的问题 今天升级到了ubuntu23.10之后&#xff0c;wifi不能使用。 参考此视频解决了问题&#xff1a; https://www.youtube.com/watch?appdesktop&vn92O8rNKVb0 sudo lshw -class networkcd /etc/pm/sleep.dlssudo touch configsudo gedit co…

Java制作俄罗斯方块

Java俄罗斯方块小游戏 import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List; imp…

C#,怎么修改(VS)Visual Studio 2022支持的C#版本

一些文字来自于 Microsoft . &#xff08;只需要读下面的红色文字即可&#xff01;&#xff09; 1 C# 语言版本控制 最新的 C# 编译器根据项目的一个或多个目标框架确定默认语言版本。 Visual Studio 不提供用于更改值的 UI&#xff0c;但可以通过编辑 .csproj 文件来更改值。…

1688阿里巴巴官方开放平台API接口获取商品详情、商品规格信息列表、价格、宝贝详情数据调用示例说明

商品详情API接口在电商平台和购物应用中的作用非常重要。它提供了获取商品详细信息的能力&#xff0c;帮助用户了解和选择合适的商品&#xff0c;同时也支持开发者进行竞品分析、市场研究和推广营销等工作&#xff0c;以提高用户体验和促进销售增长。 1688.item_get-获得1688商…

单链表的实现(Single Linked List)---直接拿下!

单链表的实现&#xff08;Single Linked List&#xff09;—直接拿下&#xff01; 文章目录 单链表的实现&#xff08;Single Linked List&#xff09;---直接拿下&#xff01;一、单链表的模型二、代码实现&#xff0c;接口函数实现①初始化②打印链表③创建一个结点④尾插⑤尾…

Unity 场景烘培 ——unity Post-Processing后处理1(四)

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、Post-Processing是什么&#xff1f;二、安装使用Post-Processing1.安装Post-Processing2.使用Post-Processing&#xff08;1&#xff09;.添加Post-process Volume&#xff08…