• 【献给过去的自己】栈实现计算器(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;

    代码示例

    我的代码示例如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #define STACK_LEN 1024
    7. /** 中缀表达式栈*/
    8. static int32_t g_infix_expression[1024] = {0};
    9. /** 前缀表达式栈*/
    10. static int32_t g_prefix_expression[1024] = {0};
    11. /** 后缀表达式栈*/
    12. static int32_t g_suffix_expression[1024] = {0};
    13. /**
    14. * @brief 将输入的字符串表达式转换为中缀表达式
    15. *
    16. * @param [in] expression 字符串表达式
    17. * @return int 0 成功 non-0 失败
    18. * */
    19. int expression2infix(const char* expression)
    20. {
    21. if(expression == NULL)
    22. {
    23. printf("input error\n");
    24. return -1;
    25. }
    26. int dataTmp = 0; //表达式中的操作数
    27. bool dataFlag = false; // 操作数标识,表示当前是否有数据需要入栈
    28. const char* ptr = expression;
    29. int32_t* infix_index = g_infix_expression;
    30. printf("expression = %s\n",expression);
    31. while(*ptr != '\0')
    32. {
    33. /** 字符为数字*/
    34. if('0' <= *ptr && *ptr <= '9')
    35. {
    36. dataTmp = dataTmp*10 +(*ptr - '0');
    37. dataFlag = true;
    38. }
    39. /**字符为操作符或括号*/
    40. else if(*ptr == '+' || *ptr == '-' || *ptr == '*' || *ptr == '/'
    41. || *ptr == '(' || *ptr == ')')
    42. {
    43. if(dataFlag == true)
    44. {
    45. *(infix_index++) = dataTmp;
    46. dataFlag = false;
    47. dataTmp = 0;
    48. }
    49. *(infix_index++) = *ptr;
    50. }
    51. else
    52. {
    53. printf("wrong exptrssion\n");
    54. return -1;
    55. }
    56. ptr++;
    57. }
    58. /**将最后一个操作数,入栈*/
    59. if(dataFlag == true)
    60. {
    61. *(infix_index++) = dataTmp;
    62. dataFlag = false;
    63. dataTmp = 0;
    64. }
    65. return 0;
    66. }
    67. /**
    68. * @brief 将中缀表达式转换为前缀表达式
    69. *
    70. * @return int 0 成功 non-0 失败
    71. * */
    72. int infix2prefixExpression()
    73. {
    74. /**初始化运算符栈和中间结果栈*/
    75. int32_t stack_s1[STACK_LEN] = {0};
    76. int32_t stack_s1_top = 0;
    77. int32_t stack_s2[STACK_LEN] = {0};
    78. int32_t stack_s2_top = 0;
    79. int32_t * index = g_infix_expression;
    80. /**获取中缀表达式最右侧操作数*/
    81. while(*(index+1) != 0)
    82. {
    83. index++;
    84. }
    85. while(index != g_infix_expression)
    86. {
    87. /** 操作符*/
    88. if(*index == '+' || *index == '-' || *index == '*' || *index == '/')
    89. {
    90. while(true)
    91. {
    92. /**S1为空,或栈顶运算符为右括号),则将此运算符入栈*/
    93. if(stack_s1_top == 0 || stack_s1[stack_s1_top-1] == ')' || stack_s1[stack_s1_top-1] == '-'
    94. || stack_s1[stack_s1_top-1] == '+')
    95. {
    96. stack_s1[stack_s1_top++] = *index;
    97. break;
    98. }
    99. stack_s2[stack_s2_top++] = stack_s1[stack_s1_top-1];
    100. stack_s1[stack_s1_top-1] = 0;
    101. stack_s1_top = stack_s1_top -1;
    102. }
    103. }
    104. /**左括号
    105. * 则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止
    106. */
    107. else if(*index == '(')
    108. {
    109. while(true)
    110. {
    111. /**异常*/
    112. if(stack_s1_top == 0)
    113. {
    114. printf("infix experssion worong\n");
    115. return -1;
    116. }
    117. /**遇到右括号,丢弃括号*/
    118. if(stack_s1[stack_s1_top-1] == ')')
    119. {
    120. stack_s1[stack_s1_top-1] = 0;
    121. stack_s1_top = stack_s1_top -1;
    122. break;
    123. }
    124. /**其它符号需要入栈S2*/
    125. else
    126. {
    127. stack_s2[stack_s2_top++] = stack_s1[stack_s1_top-1];
    128. stack_s1[stack_s1_top-1] = 0;
    129. stack_s1_top--;
    130. }
    131. }
    132. }
    133. /**右括号
    134. * 直接入运算符栈s1
    135. */
    136. else if(*index == ')')
    137. {
    138. stack_s1[stack_s1_top++] = *index;
    139. }
    140. /** 操作数
    141. * 直接加入栈s2*/
    142. else
    143. {
    144. stack_s2[stack_s2_top++] = *index;
    145. }
    146. index--;
    147. #if 0
    148. printf("==============\n");
    149. printf("stack_s1=");
    150. for(int i = 0 ; i < stack_s1_top; i++)
    151. {
    152. (stack_s1[i] > 9) ? (printf("%c ",stack_s1[i])):(printf("%d ",stack_s1[i]));
    153. }
    154. printf("\n");
    155. printf("stack_s2=");
    156. for(int i = 0 ; i < stack_s2_top; i++)
    157. {
    158. (stack_s2[i] > 9) ? (printf("%c ",stack_s2[i])):(printf("%d ",stack_s2[i]));
    159. }
    160. printf("\n");
    161. #endif
    162. }
    163. /**将最左侧操作数压入s2*/
    164. stack_s2[stack_s2_top++] = *index;
    165. /**将s1中的符号压入s2*/
    166. for(int i = stack_s1_top - 1; i >= 0; i-- )
    167. {
    168. stack_s2[stack_s2_top++] = stack_s1[i];
    169. stack_s1[i] = 0;
    170. }
    171. /**将s2中的数据弹出,放入前缀表达式栈中*/
    172. for(int i = 0 ; stack_s2_top > 0; i++,stack_s2_top--)
    173. {
    174. g_prefix_expression[i] = stack_s2[stack_s2_top-1];
    175. }
    176. return 0;
    177. }
    178. int main(int argc,char* argv[])
    179. {
    180. if(argc != 2)
    181. {
    182. printf("please input experssion\n");
    183. return -1;
    184. }
    185. int32_t iRet = 0;
    186. iRet = expression2infix(argv[1]);
    187. if(iRet == 0)
    188. {
    189. for(int i = 0 ; i < STACK_LEN && g_infix_expression[i] != 0; i++)
    190. {
    191. if(g_infix_expression[i] == '+' || g_infix_expression[i] == '-' || g_infix_expression[i] == '*' || g_infix_expression[i] == '/')
    192. {
    193. printf("%c ",g_infix_expression[i]);
    194. }
    195. else
    196. {
    197. printf("%d ",g_infix_expression[i]);
    198. }
    199. }
    200. printf("\n");
    201. }
    202. iRet = infix2prefixExpression();
    203. if(iRet == 0)
    204. {
    205. for(int i = 0 ; i < STACK_LEN && g_prefix_expression[i] != 0; i++)
    206. {
    207. if(g_infix_expression[i] == '+' || g_infix_expression[i] == '-' || g_infix_expression[i] == '*' || g_infix_expression[i] == '/')
    208. {
    209. printf("%c ",g_infix_expression[i]);
    210. }
    211. else
    212. {
    213. printf("%d ",g_infix_expression[i]);
    214. }
    215. }
    216. printf("\n");
    217. }
    218. prefixExpressionCaculate();
    219. return 0;
    220. }

    前缀表达式计算

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

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

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

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

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

    分析思路

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

            得出结果为16。

    代码示例

    新增prefixExpressionCaculate接口。代码如下:

    1. /**
    2. * @brief 将前缀表达式进行计算
    3. *
    4. * @return int 0 成功 non-0 失败
    5. * */
    6. int prefixExpressionCaculate()
    7. {
    8. /**结果栈*/
    9. int32_t stack[1024] = {0};
    10. int32_t stack_len = 0;
    11. /**临时结果*/
    12. int32_t tmpResult = 0;
    13. int32_t data1 = 0;
    14. int32_t data2 = 0;
    15. /**获取后缀表达式的最右侧操作数*/
    16. int32_t* index = g_prefix_expression;
    17. while(*(index+1) != 0)
    18. {
    19. index++;
    20. }
    21. while(index >= g_prefix_expression)
    22. {
    23. /**弹出栈顶的两个数,并用运算符对这两个数做相应的计算,并将结果入栈*/
    24. if(*index == '+' || *index == '-' || *index == '*' || *index == '/')
    25. {
    26. data1 = stack[stack_len-1];
    27. data2 = stack[stack_len-2];
    28. if(*index == '+')
    29. {
    30. tmpResult = data1 + data2;
    31. }
    32. else if(*index == '-')
    33. {
    34. tmpResult = data1 - data2;
    35. }
    36. else if(*index == '*')
    37. {
    38. tmpResult = data1 * data2;
    39. }
    40. else if(*index == '/')
    41. {
    42. tmpResult = data1 / data2;
    43. }
    44. else
    45. {
    46. printf("worng prefixExperssion\n");
    47. return -1;
    48. }
    49. stack[stack_len-1] = 0;
    50. stack[stack_len-2] = tmpResult;
    51. stack_len --;
    52. }
    53. /**遇到数字,压栈*/
    54. else
    55. {
    56. stack[stack_len] = *index;
    57. stack_len ++;
    58. }
    59. index --;
    60. }
    61. printf("result = %d\n",stack[0]);
    62. return 0;
    63. }

    演示

    后缀表达式

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

    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博客

  • 相关阅读:
    基于springboot高校学生健康打卡系统毕业设计源码021009
    三、部署kafka
    机器学习_个人笔记_周志华(停更中......)
    2 JavaScript in HTML
    基于Java+SpringBoot+Vue前后端分离多媒体素材库设计和实现
    146.LRU缓存
    osqp的原理ADMM(交替方向乘子法)理解
    R语言ggplot2包绘制网络地图
    【R语言】概率密度图
    C++用锁实现线程安全的stack容器
  • 原文地址:https://blog.csdn.net/xieyihua1994/article/details/134448197