用C语言实现——一个中缀表达式的计算器。支持用户输入和动画演示过程。

一、思路概要和知识回顾

1.思路概要

①中缀表达式计算:

需要处理运算符的优先级,可能需要用到栈结构。

❗❗如何将中缀表达式转换为后缀表达式?或者直接计算?

通常,中缀转后缀(逆波兰式)再用后缀计算比较方便。可以直接用两个栈,一个操作数栈,一个运算符栈,边转换边计算。比如Shunting-yard算法。可能后者更适合,因为可以边处理边显示步骤,方便动画演示。

❗❗数据结构的选择: 栈➡可以用简单的数组实现

操作数栈:存放double类型.

运算符栈:存放字符。

然后需要处理运算符的优先级,比如加减是1,乘除是2,可能需要一个函数来返回优先级值。

②用户输入:

需要读取表达式,处理可能的错误输入。

用fgets读取输入的表达式,可能包含空格,需要处理掉空格。同时,要考虑多位数和小数点的处理,比如遇到连续的数字字符需要合并成一个数。

③动画演示:

需要逐步显示计算过程,比如显示栈的状态,当前处理的字符等。

动画演示的话,需要在每一步处理时输出当前的状态,比如显示操作数栈和运算符栈的内容,当前处理的字符,以及进行的操作,比如入栈、出栈计算等。可能需要用一些控制台输出技巧,比如清屏、延迟,让用户能看到过程。

可能还需要处理括号的情况,因为括号会影响运算顺序。所以在处理运算符的时候,遇到左括号入栈,遇到右括号则弹出直到左括号,同时处理运算.

具体实现步骤:

定义栈结构。操作数栈和运算符栈。每个栈需要有数组,栈顶指针。然后实现栈的基本操作:初始化、入栈、出栈、查看栈顶等。

然后,处理输入的表达式。可能需要过滤空格,将表达式中的空格去掉。

接下来,逐个字符处理:

如果是数字或者小数点,开始收集整个数字,直到遇到非数字或小数点,然后将该数字字符串转换为double,压入操作数栈。同时,在动画中显示这一步。

如果是运算符或括号,则比较当前运算符与栈顶运算符的优先级。如果当前运算符优先级小于等于栈顶的,则弹出栈顶运算符,从操作数栈弹出两个操作数进行计算,结果压回操作数栈。重复直到栈顶优先级低于当前运算符,然后将当前运算符压栈。左括号直接压栈,遇到右括号则弹出直到左括号,并处理运算。

在每一步操作时,显示当前处理的位置,栈的状态,以及进行的操作。例如,显示“当前字符:+”,操作数栈的内容,运算符栈的内容,然后说明正在将+入栈,或者进行计算等。

关于动画的步骤显示,可以在每次处理一个字符后,暂停一下,输出当前状态。例如,每次处理完一个操作符或数字后,打印两个栈的状态,然后等待用户按回车继续,或者自动延迟。

代码结构的大致框架:

- 主函数读取输入,并处理每个字符。

- 处理数字的函数,收集数字字符串。

- 处理运算符的函数,处理栈的操作。

- 计算函数,执行具体的运算,并处理错误如除以零。

- 显示栈状态的函数,用于动画输出。

可能的错误处理:

- 除以零的情况,需要提示错误并退出。

- 括号不匹配的情况。

- 无效字符的输入。

2.知识回顾

二、具体实现步骤

1.定义栈结构

操作数栈和运算符栈。每个栈需要有数组,栈顶指针。然后实现栈的基本操作:初始化、入栈、出栈、查看栈顶等。

操作数栈:用于存储计算过程中涉及的数值,当遇到运算符时,会从操作数栈中弹出相应数量的操作数进行计算,并将计算结果重新压入操作数栈中。

运算符栈:用于存储遇到的运算符,依据运算符的优先级来决定何时执行相应的计算操作。当遇到优先级低或相等的运算符时,会弹出栈中的运算符进行计算,直到栈顶运算符的优先级低于当前运算符。

①操作数栈:


数据存储 :一个固定大小的数组(data):用于存放栈中的元素。

                   数组类型为 double:可存储浮点数操作数

栈顶指针top 用于指示当前栈顶的位置。初始时 top 设为 -1,表示栈为空;每当有元素入栈                          时,top 增 1,指向新的栈顶;弹出元素时,top 减 1,指向新的栈顶。  

②运算符栈:

数据存储 :一个固定大小的数组(data),用于存放栈中的元素。

                   运算符栈的数组类型为 char,用于存储运算符字符。

栈顶指针top 用于指示当前栈顶的位置。初始时 top 设为 -1,表示栈为空;每当有元素入栈                         时,top 增 1,指向新的栈顶;弹出元素时,top 减 1,指向新的栈顶。

2.初始化栈

栈初始化的核心是设置栈顶指针的初始状态为 -1,这是栈为空的标志。这样,在后续的操作中,可以通过判断栈顶指针是否为 -1 来确定栈是否为空,从而避免非法的出栈操作。

3.向|操作数栈|中压入数值元素函数

参数检查 :首先判断栈顶指针 s->top 是否已到达栈的最大容量减一(MAX_SIZE - 1)。如果是,则说明栈已满,无法再压入新的元素,函数返回 false,表示压栈失败。

元素入栈 :如果栈未满,则将栈顶指针加 1(++s->top),并将传入的数值 val 存入栈顶位置(s->data[s->top])。

返回成功 :压栈成功后,函数返回 true

4.向|运算符栈|中压入元素的函数

参数检查 :首先判断栈顶指针 s->top 是否已到达栈的最大容量减一(MAX_SIZE - 1)。如果是,则说明栈已满,无法再压入新的元素,函数返回 false,表示压栈失败。

元素入栈 :如果栈未满,则将栈顶指针加 1(++s->top),并将传入的数值 val 存入栈顶位置(s->data[s->top])。

返回成功 :压栈成功后,函数返回 true

5.弹出|操作数栈|栈顶元素函数

检查栈顶指针是否为 -1。如果栈顶指针是 -1,说明栈为空,此时无法弹出元素,函数返回 0.0。

如果栈不为空,这行代码将栈顶指针减 1(s->top--),并返回原来栈顶位置的数值(s->data[s->top])。

在完整代码中的作用:

支持中缀表达式计算: 在处理中缀表达式时,需要从操作数栈中弹出数值进行计算。例如,当遇到运算符时,会从操作数栈中弹出两个数值,进行相应的计算,并将结果重新压入栈中。

动态数据管理: 允许在程序运行过程中动态地从栈中移除数值,使得栈能够根据计算的需要调整存储的内容,实现了灵活的数据管理。

6.弹出|运算符栈|中栈顶元素函数

检查栈顶指针是否为 -1。如果栈顶指针是 -1,说明栈为空,此时无法弹出元素,函数返回空字符 \0

如果栈不为空,这行代码将栈顶指针减 1(s->top--),并返回原来栈顶位置的运算符(s->data[s->top])。

在完整代码中的作用

支持中缀表达式计算: 在处理中缀表达式时,需要从运算符栈中弹出运算符进行计算。例如,当遇到优先级较低或相等的运算符时,会从运算符栈中弹出栈顶的运算符,获取该运算符后进行相应的计算操作。

动态数据管理: 允许在程序运行过程中动态地从栈中移除运算符,使得栈能够根据计算的需要调整存储的内容,实现了灵活的数据管理。

7.获取栈顶函数

①获取栈顶元素函数的作用

  • top_num 函数:

    • 功能 :返回操作数栈栈顶的数值,但不改变栈的结构(即栈顶指针不变,元素不被移除)。

    • 实现逻辑 :通过条件运算符判断栈是否为空。如果栈为空(s->top == -1),返回 0.0;否则,返回栈顶位置的数值(s->data[s->top])。

  • top_op 函数:

    • 功能 :返回运算符栈栈顶的运算符,同样不改变栈的结构。

    • 实现逻辑 :同样使用条件运算符判断栈是否为空。如果栈为空,返回空字符 \0;否则,返回栈顶位置的运算符(s->data[s->top])。

②与弹出栈顶元素函数的区别

  • pop_numpop_op 函数:

    • 功能 :不仅获取栈顶元素,还会将栈顶元素从栈中移除,并将栈顶指针减 1。

    • 应用场景 :当你需要使用栈顶元素并将其从栈中移除时,使用 pop 函数。例如,在进行数值计算时,需要从操作数栈中弹出两个数值进行计算;在处理运算符优先级时,需要从运算符栈中弹出运算符进行比较或计算。

③为什么需要两种操作?

获取栈顶元素(top 函数):
应用场景 :当你需要查看栈顶元素但不想改变栈的结构时使用。例如,在比较运算符优先级时,需要查看运算符栈栈顶的运算符,但不应将其移除。


弹出栈顶元素(pop 函数):
应用场景 :当你需要使用栈顶元素并将其从栈中移除时使用。例如,在进行数值计算时,弹出操作数栈中的数值进行计算;在处理完一个运算符后,将其从运算符栈中移除。

④协同工作实现栈顶元素的利用

获取栈顶元素和弹出栈顶元素的操作是相辅相成的:

  • 获取栈顶元素: 允许你在不破坏栈结构的情况下查看栈顶元素,用于决策(如比较运算符优先级)。

  • 弹出栈顶元素: 允许你在需要时移除并使用栈顶元素,用于实际的操作(如数值计算)。

8. 判断栈空函数

①函数功能

  • is_empty_op 函数:

    • 功能 :判断运算符栈是否为空。

    • 实现逻辑 :检查运算符栈的栈顶指针 s->top 是否等于 -1。如果等于 -1,说明栈为空,返回 true;否则,返回 false

  • is_empty_num 函数:

    • 功能 :判断操作数栈是否为空。

    • 实现逻辑 :检查操作数栈的栈顶指针 s->top 是否等于 -1。如果等于 -1,说明栈为空,返回 true;否则,返回 false

②在完整代码中的作用

  • 支持中缀表达式计算: 在处理中缀表达式时,需要频繁地检查栈的状态。例如,在弹出元素之前,需要确保栈不为空,以避免非法操作。通过调用这两个函数,可以在执行弹出操作或访问栈顶元素之前,确保栈处于有效状态。

  • 提供栈状态检查手段: 这两个函数为程序提供了简单直接的栈状态检查手段。通过返回布尔值,程序可以轻松地根据栈是否为空来决定后续的操作流程。

9.优先级函数

如果传入的运算符是加号 '+' 或减号 '-',则返回优先级 1。

如果传入的运算符是乘号 '*' 或除号 '/',则返回优先级 2。

如果传入的字符不是上述运算符,则返回优先级 0,表示该字符不具有运算符优先级。

在处理中缀表达式时,需要根据运算符的优先级来决定计算的顺序。优先级高的运算符会先进行计算。通过调用 get_priority 函数,可以获取当前运算符的优先级,并与栈顶运算符的优先级进行比较,从而决定是直接压入栈还是先进行计算

10.清屏函数

 如果代码在 Windows 系统上运行,这行代码会执行系统命令 "cls" 来清除屏幕内容。

如果代码在非 Windows 系统(如 Linux 或 macOS)上运行,这行代码会执行系统命令 "clear" 来清除屏幕内容。

在完整代码中的作用

  • 提升用户体验: 在处理中缀表达式时,每次显示栈状态之前调用 clear_screen 函数,可以清除之前的显示内容,为用户呈现一个整洁的界面,避免屏幕内容过于杂乱。

  • 保持界面整洁: 通过清屏操作,程序可以在每次显示新的栈状态时,只展示当前的相关信息,而不是在之前内容的基础上追加显示。这样可以使界面更加简洁,信息更加清晰。

11.延时函数

 

如果代码在 Windows 系统上运行,这行代码会调用 Windows API 的 Sleep 函数来实现延时,Sleep 函数的参数是以毫秒为单位的延时时间。

如果代码在非 Windows 系统(如 Linux 或 macOS)上运行,这行代码会调用 POSIX 的 usleep 函数来实现延时,usleep 函数的参数是以微秒为单位的延时时间,因此需要将传入的毫秒值乘以 1000 进行转换。

在完整代码中的作用

  • 控制显示节奏: 在处理中缀表达式时,每次显示栈状态之后调用 delay 函数,可以暂停程序的执行,使用户有时间查看当前的栈状态,从而更好地理解程序的执行过程。

  • 提升用户体验: 通过延时操作,程序可以在每次显示新的栈状态时,以合适的节奏展示信息,避免内容切换过快导致用户无法看清。

 

12.显示栈函数

 

13.执行计算函数 

 

  1. 如果运算符是 '+',则返回两个操作数 ab 的和。

 

 

如果运算符是 '-',则返回两个操作数 ab 的差。 

 

如果运算符是 '*',则返回两个操作数 ab 的积。 

 

  1. 如果运算符是 '/',则首先检查除数 b 是否为零。如果是零,打印错误信息并退出程序;否则,返回两个操作数 ab 的商。
     

 如果传入的运算符不属于上述四种基本运算符之一,则返回 0。这通常表示传入了不支持的运算符。

在完整代码中的作用

  • 执行计算: 在处理中缀表达式时,当遇到运算符需要进行计算时,会调用 calculate 函数。该函数根据运算符和弹出的操作数执行相应的计算,并返回结果,结果随后会被压入操作数栈。

  • 错误处理: 在进行除法运算时,该函数会检查除数是否为零,从而避免程序因除以零而崩溃。如果检测到除数为零,程序会打印错误信息并安全退出。

14.处理表达式函数

初始化栈 

初始化操作数栈 num_stack 和运算符栈 op_stack,将它们的栈顶指针设置为 -1,表示栈为空。 

循环处理表达式

使用一个 while 循环遍历输入的表达式字符串,直到遇到字符串结束符 \0

跳过空格 

如果当前字符是空格,则跳过该字符,继续处理下一个字符。

显示当前栈状态 

调用 display_stacks 函数显示当前的操作数栈和运算符栈状态,以及当前处理的字符。 

⑤ 处理数字

判断是否为数字或小数点:

isdigit 是库函数isdigit 是 C 标准库 <ctype.h> 中的函数,用于判断一个字符是否是数字('0'-'9')。如果是数字字符,返回一个非零值(通常为 true);否则返回 0(false)。

判断逻辑 :这行代码检查当前字符 expr[i] 是否是数字或者是否为小数点(.)。如果是其中之一,则表示这是一个数字的开始,需要进行数字处理。

 

处理数字字符串: 

 

定义数字字符串缓冲区num_str 用于暂时存储从表达式中提取的完整数字字符串,包括整数部分和小数部分。长度设为 32,足够存储大多数常见的数字。

初始化索引变量j 用于在 num_str 中存储字符时的索引定位,初始值为 0。

 

循环提取数字字符 :这个 while 循环会一直执行,只要当前字符 expr[i] 是数字或者小数点。

存储数字字符 :在循环中,将当前字符 expr[i] 存入 num_str 的第 j 个位置,然后 j 自增 1,以便下一个字符存储在正确的位置。同时,i 自增 1,移动到下一个字符。

 

 

终止字符串 :在提取完数字的所有字符后,在 num_str 的末尾添加空字符 \0,将其转换为一个有效的 C 字符串。这一步是必要的,因为许多字符串处理函数都需要依赖空字符来确定字符串的结束位置。

 

转换为浮点数atof 是 C 标准库 <stdlib.h> 中的函数,用于将字符串转换为一个双精度浮点数(double)。这里将 num_str 转换为浮点数。

压入操作数栈 :调用 push_num 函数,将转换后的浮点数压入操作数栈 num_stack 中,以便后续进行计算。

 

 

索引纠正 :因为在 while 循环中处理完最后一个数字字符后,i 又被自增了一次,所以这里要将 i 减 1,使它指向数字的最后一个字符,以便在下一次循环中正确处理后续字符。

处理左括号

 

如果当前字符是左括号 '(',则直接压入运算符栈。

处理右括号 

弹出运算符并执行计算:

 

循环条件 :检查运算符栈的栈顶元素是否为左括号 '('。如果不是,则进入循环体。

top_op 函数 :这是之前定义的函数,用于获取运算符栈的栈顶元素,但不弹出该元素。它返回栈顶的运算符字符。

 

弹出运算符 :调用 pop_op 函数弹出运算符栈的栈顶运算符,并将其存储在变量 op 中。

pop_op 函数 :之前定义的函数,用于弹出运算符栈的栈顶元素并返回该元素。

 

 

弹出操作数 :依次弹出操作数栈的栈顶元素,分别存储在变量 ba 中。这里假设 b 是第二个操作数,a 是第一个操作数。

 pop_num 函数 :之前定义的函数,用于弹出操作数栈的栈顶元素并返回该元素。

 

执行计算并压入结果 :调用 calculate 函数对弹出的两个操作数和运算符进行计算,得到结果后,将结果压入操作数栈。

push_num 函数 :之前定义的函数,用于将数值压入操作数栈。

calculate 函数 :之前定义的函数,用于执行具体的算术运算。

 

 显示栈状态 :调用 display_stacks 函数显示当前的操作数栈和运算符栈状态,以及当前处理的运算符。

display_stacks 函数 :之前定义的函数,用于显示栈状态。

弹出左括号:

 

显示栈状态 :在处理完右括号内的所有运算符后,再次显示栈状态,以便用户可以看到当前的栈情况。

 

弹出左括号 :调用 pop_op 函数弹出运算符栈的栈顶元素(左括号 '('),但不进行任何计算。

 pop_op 函数 :之前定义的函数,用于弹出运算符栈的栈顶元素并返回该元素。

处理运算符

检查是否为运算符:

 

如果当前字符不是数字、小数点或括号,则进入此分支处理运算符。

循环处理运算符优先级:

 

is_empty_op 函数 :检查运算符栈是否为空。

get_priority 函数 :获取当前运算符和栈顶运算符的优先级。

 循环条件 :如果运算符栈不为空且当前运算符的优先级小于或等于栈顶运算符的优先级,则进入循环体。

弹出运算符和操作数并计算:

 pop_op 函数 :弹出运算符栈的栈顶运算符。

pop_num 函数 :依次弹出操作数栈的栈顶元素,b 是第二个操作数,a 是第一个操作数。

 

calculate 函数 :对 ab 执行 op 运算,并返回结果。

push_num 函数 :将计算结果压入操作数栈。

 

显示栈状态:

display_stacks 函数 :显示当前操作数栈和运算符栈的状态。

压入当前运算符:

 

 

push_op 函数 :将当前运算符压入运算符栈。

15.清理输入缓冲区函数

  1. 声明变量 c 用于存储从标准输入读取的字符。

  2. while 循环: 使用 getchar() 函数从标准输入读取字符,并将其存储在变量 c 中。循环会一直执行,直到读取到换行符 '\n' 或文件结束标志 EOF

作用:

在程序中,当使用 fgets 或其他输入函数读取用户输入时,输入缓冲区中可能会残留一些未处理的字符(例如多余的换行符或其他输入)。这些残留字符可能会影响后续的输入操作。clean_stdin 函数的作用就是清除这些残留字符,确保后续的输入操作能够正确进行。

16.获取用户输入函数 

无限循环 

使用 while (1) 创建一个无限循环,直到用户输入有效选择(Y 或 N)为止。 

提示用户输入 

printf 函数 :输出提示信息,询问用户是否继续。

fflush(stdout) :刷新标准输出缓冲区,确保提示信息立即显示。

读取用户输入

fgets 函数 :从标准输入读取用户输入,最多读取 sizeof(input) - 1 个字符(这里为 3 个字符),并自动在末尾添加空字符 \0

输入缓冲区input 数组的大小为 4,可以存储最多 3 个字符加上空字符。

错误检查 :如果 fgets 返回 NULL,表示读取输入失败,函数返回 false

 

 ④处理过长输入

strchr 函数 :检查 input 中是否包含换行符 \n。如果不包含,说明用户输入过长,超出 input 的容量。

clean_stdin 函数 :清空标准输入缓冲区,避免多余的输入字符影响后续操作。

转换并检查用户选择 

 

tolower 函数 :将用户输入的首字符转换为小写,以便大小写不敏感地检查输入。

检查选择 :如果转换后的字符是 'y',返回 true;如果是 'n',返回 false

无效输入提示 

如果用户输入的不是 Y 或 N(大小写不敏感),输出提示信息,要求用户重新输入。

 

 17.主函数逻辑

开始 do-while 循环

使用 do-while 循环确保程序至少运行一次,允许用户多次输入表达式进行计算。

定义表达式缓冲区

 定义一个字符数组 expr,用于存储用户输入的中缀表达式,最大长度为 MAX_SIZE

提示用户输入

输出提示信息,告知用户可以输入支持 +-*/ 和括号的中缀表达式。

读取用户输入

 

fgets 函数 :从标准输入读取用户输入的字符串,最多读取 MAX_SIZE - 1 个字符,并自动在末尾添加空字符 \0

错误检查 :如果 fgets 返回 NULL,表示读取输入失败,跳出循环。

 

去除换行符

strcspn 函数 :计算 expr 中不包含换行符 \n 的字符数,返回该数作为索引。

去除换行符 :将换行符替换为空字符 \0,确保输入字符串正确结束。

 

检查输入是否为空

strlen 函数 :检查输入字符串的长度。

提示信息 :如果输入为空,输出提示信息,并跳过本次循环的剩余部分,重新开始循环。

 

处理表达式

调用 process_expression 函数处理用户输入的中缀表达式。

检查是否继续

 

get_continue_choice 函数 :调用该函数询问用户是否继续运行程序。

循环条件 :如果用户选择继续(返回 true),循环继续;否则,循环结束。

三、完整代码

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdbool.h>#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif#define MAX_SIZE 100// 操作数栈结构
typedef struct 
{double data[MAX_SIZE];int top;
} NumStack;// 运算符栈结构
typedef struct 
{char data[MAX_SIZE];int top;
} OpStack;// 初始化栈
void init_num_stack(NumStack* s) { s->top = -1; }
void init_op_stack(OpStack* s) { s->top = -1; }// 栈操作函数
bool push_num(NumStack* s, double val) 
{if (s->top >= MAX_SIZE - 1) return false;s->data[++s->top] = val;return true;
}bool push_op(OpStack* s, char op) 
{if (s->top >= MAX_SIZE - 1) return false;s->data[++s->top] = op;return true;
}double pop_num(NumStack* s) 
{if (s->top == -1) return 0.0;return s->data[s->top--];
}char pop_op(OpStack* s) 
{if (s->top == -1) return '\0';return s->data[s->top--];
}// 获取栈顶元素
double top_num(NumStack* s) 
{ return s->top == -1 ? 0.0 : s->data[s->top]; 
}
char top_op(OpStack* s)
{return s->top == -1 ? '\0' : s->data[s->top];
}// 判断栈是否为空
bool is_empty_op(OpStack* s) 
{ return s->top == -1; 
}
bool is_empty_num(NumStack* s)
{return s->top == -1;
}// 获取运算符优先级
int get_priority(char op)
{if (op == '+' || op == '-') return 1;if (op == '*' || op == '/') return 2;return 0;
}// 清屏函数
void clear_screen() 
{
#ifdef _WIN32system("cls");
#elsesystem("clear");
#endif
}// 延时函数
void delay(int ms) 
{
#ifdef _WIN32Sleep(ms);
#elseusleep(ms * 1000);
#endif
}// 显示栈状态
void display_stacks(NumStack* ns, OpStack* os, char current) 
{clear_screen();printf("当前字符: %c\n\n", current);printf("操作数栈: ");for (int i = 0; i <= ns->top; i++)printf("%.2f ", ns->data[i]);printf("\n运算符栈: ");for (int i = 0; i <= os->top; i++)printf("%c ", os->data[i]);printf("\n\n");delay(1000);
}// 执行计算
double calculate(double a, double b, char op) {switch (op) {case '+': return a + b;case '-': return a - b;case '*': return a * b;case '/':if (b == 0) {printf("错误:除数不能为零!\n");exit(EXIT_FAILURE);}return a / b;default: return 0;}
}// 处理表达式
void process_expression(const char* expr) 
{NumStack num_stack;OpStack op_stack;init_num_stack(&num_stack);init_op_stack(&op_stack);int i = 0;while (expr[i] != '\0') {if (expr[i] == ' ') {i++;continue;}display_stacks(&num_stack, &op_stack, expr[i]);if (isdigit(expr[i]) || expr[i] == '.') {// 处理数字char num_str[32];int j = 0;while (isdigit(expr[i]) || expr[i] == '.')num_str[j++] = expr[i++];num_str[j] = '\0';push_num(&num_stack, atof(num_str));i--;}else if (expr[i] == '('){push_op(&op_stack, '(');}else if (expr[i] == ')') {while (top_op(&op_stack) != '(') {char op = pop_op(&op_stack);double b = pop_num(&num_stack);double a = pop_num(&num_stack);push_num(&num_stack, calculate(a, b, op));display_stacks(&num_stack, &op_stack, expr[i]);}pop_op(&op_stack); // 弹出左括号}else {// 处理运算符while (!is_empty_op(&op_stack) &&get_priority(expr[i]) <= get_priority(top_op(&op_stack))){char op = pop_op(&op_stack);double b = pop_num(&num_stack);double a = pop_num(&num_stack);push_num(&num_stack, calculate(a, b, op));display_stacks(&num_stack, &op_stack, op);}push_op(&op_stack, expr[i]);}i++;}// 处理剩余运算符while (!is_empty_op(&op_stack)){char op = pop_op(&op_stack);double b = pop_num(&num_stack);double a = pop_num(&num_stack);push_num(&num_stack, calculate(a, b, op));display_stacks(&num_stack, &op_stack, op);}printf("最终结果: %.2f\n", pop_num(&num_stack));
}// 清空输入缓冲区
void clean_stdin() 
{int c;while ((c = getchar()) != '\n' && c != EOF);
}// 获取用户选择
bool get_continue_choice() 
{while (1){printf("是否继续?(Y/N): ");fflush(stdout);char input[4];if (fgets(input, sizeof(input), stdin) == NULL){return false;}// 处理过长输入if (strchr(input, '\n') == NULL){clean_stdin();}char choice = tolower(input[0]);if (choice == 'y') return true;if (choice == 'n') return false;printf("无效输入,请重新输入!\n");}
}int main() 
{do {char expr[MAX_SIZE];printf("请输入中缀表达式(支持+-*/和括号):>  ");if (fgets(expr, MAX_SIZE, stdin) == NULL) {break;}expr[strcspn(expr, "\n")] = '\0';if (strlen(expr) == 0) {printf("输入不能为空!\n");continue;}process_expression(expr);} while (get_continue_choice());printf("感谢使用计算器!\n");return 0;
}

 以上是基于VS2022编译器,用C语言实现——一个中缀表达式的计算器。支持用户输入和动画演示过程。

❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀

目录

一、思路概要和知识回顾

1.思路概要

①中缀表达式计算:

②用户输入:

③动画演示:

具体实现步骤:

代码结构的大致框架:

2.知识回顾

二、具体实现步骤

1.定义栈结构

①操作数栈:

②运算符栈:

2.初始化栈

3.向|操作数栈|中压入数值元素函数

4.向|运算符栈|中压入元素的函数

5.弹出|操作数栈|栈顶元素函数

6.弹出|运算符栈|中栈顶元素函数

7.获取栈顶函数

①获取栈顶元素函数的作用

②与弹出栈顶元素函数的区别

③为什么需要两种操作?

④协同工作实现栈顶元素的利用

8. 判断栈空函数

①函数功能

②在完整代码中的作用

9.优先级函数

10.清屏函数

11.延时函数

12.显示栈函数

13.执行计算函数 

14.处理表达式函数

①初始化栈 

②循环处理表达式

③跳过空格 

④显示当前栈状态 

⑤ 处理数字

⑥处理左括号

⑦处理右括号 

⑧处理运算符

16.获取用户输入函数 

①无限循环 

②提示用户输入 

③读取用户输入

 ④处理过长输入

⑤转换并检查用户选择 

⑥无效输入提示 

 17.主函数逻辑

①开始 do-while 循环

②定义表达式缓冲区

③提示用户输入

④读取用户输入

⑤去除换行符

⑥检查输入是否为空

⑦处理表达式

⑧检查是否继续

三、完整代码


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

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

相关文章

Langchain_Agent+数据库

本处使用Agent数据库&#xff0c;可以直接执行SQL语句。可以多次循环查询问题 前文通过chain去联系数据库并进行操作&#xff1b; 通过链的不断内嵌组合&#xff0c;生成SQL在执行SQL再返回。 初始化 import os from operator import itemgetterimport bs4 from langchain.ch…

Python 爬虫如何伪装 Referer?从随机生成到动态匹配

一、Referer 的作用与重要性 Referer 是 HTTP 请求头中的一个字段&#xff0c;用于标识请求的来源页面。它在网站的正常运行中扮演着重要角色&#xff0c;例如用于统计流量来源、防止恶意链接等。然而&#xff0c;对于爬虫来说&#xff0c;Referer 也可能成为被识别为爬虫的关…

Post-Processing PropertySource instance详解 和 BeanFactoryPostProcessor详解

PropertySourcesBeanFactoryPostProcessor详解 1. 核心概念 BeanFactoryPostProcessor 是 Spring 框架中用于在 BeanFactory 初始化阶段 对 Environment 中的 PropertySource 进行后处理的接口。它允许开发者在 Bean 创建之前 对属性源进行动态修改&#xff0c;例如添加、删除…

[C]基础13.深入理解指针(5)

博客主页&#xff1a;向不悔本篇专栏&#xff1a;[C]您的支持&#xff0c;是我的创作动力。 文章目录 0、总结1、sizeof和strlen的对比1.1 sizeof1.2 strlen1.3 sizeof和strlen的对比 2、数组和指针笔试题解析2.1 一维数组2.2 字符数组2.2.1 代码12.2.2 代码22.2.3 代码32.2.4 …

赛灵思 XCKU115-2FLVB2104I Xilinx Kintex UltraScale FPGA

XCKU115-2FLVB2104I 是 AMD Xilinx Kintex UltraScale FPGA&#xff0c;基于 20 nm 先进工艺&#xff0c;提供高达 1 451 100 个逻辑单元&#xff08;Logic Cells&#xff09;&#xff0c;77 721 600 bit 的片上 RAM 资源&#xff0c;以及 5 520 个 DSP 切片&#xff08;DSP48E…

CAPL编程_03

1_文件操作的相关函数&#xff1a; 读文本文件内容 读取文本文件操作的三部曲 1&#xff09;打开文件 —— openFileRead ( ) 2&#xff09;逐行读取 —— fileGetString ( ) 、fileGetStringSZ ( ) 3&#xff09;关闭文件 —— fileClose ( ) char content[100];…

2025年江西建筑安全员A证适合报考人群

江西建筑安全员A证适合报考人群 江西省建筑安全员A证&#xff08;建筑施工企业主要负责人安全生产考核合格证书&#xff09;主要面向建筑行业管理人员&#xff0c;适合以下人员报考&#xff1a; 1. 企业主要负责人 法人代表、总经理、分管安全副总&#xff1a;依法需持A证&a…

Docker安装(Ubuntu22版)

前言 你是否还在为Linux上配置Docker而感到烦恼&#xff1f; 你是否还在为docker search&#xff0c;docker pull连接不上&#xff0c;而感到沮丧&#xff1f; 本文将解决以上你的所有烦恼&#xff01;快速安装好docker&#xff01; Docker安装 首先&#xff0c;我们得先卸载…

Ubuntu18.04配置C++环境和Qt环境

Ubuntu18.04配置C环境和Qt环境 1、前言3.2 安装其他库3.3 查看有没有安装成功3.4测试C环境 4、配置Qt环境4.1 安装相关的库4.2 测试 5、总结 1、前言 记录一下Ubuntu18.04配置C环境和Qt环境的过程&#xff0c;方便自己日后回顾&#xff0c;也可以给有需要的人提供帮助。 # 2…

ACWing——算法基础课

置顶思考&#xff1a; 算法的本质是什么样的思想&#xff1f; 这种思想可以解决哪类问题&#xff1f; 有没有其他的解决思路&#xff1f; 关注数值范围&#xff0c;思考可不可以针对性解决问题&#xff1f; 目录 https://leetcode.cn/circle/discuss/RvFUtj/ 滑动窗口与双指针…

私钥连接服务器(已经有服务器私钥

前言&#xff1a;假设我们已经有了服务器的私钥&#xff0c;我们怎么配置呢&#xff1f; 下面我会从vsc的配置角度来写 ✅ 步骤一&#xff1a;准备工作 安装 VS Code&#xff08;如果还没装&#xff09; &#x1f449; https://code.visualstudio.com/ 安装插件&#xff1a;Re…

Redis LFU 策略参数配置指南

一、基础配置步骤‌ 设置内存上限‌ 在 redis.conf 配置文件中添加以下指令&#xff0c;限制 Redis 最大内存使用量&#xff08;例如设置为 4GB&#xff09;&#xff1a; maxmemory 4gb选择 LFU 淘汰策略‌ 根据键的作用域选择策略&#xff1a; # 所有键参与淘汰 maxmemory-…

嵌入式 C 语言面试核心知识点全面解析:基础语法、运算符与实战技巧

在嵌入式面试中&#xff0c;C 语言基础是重中之重。本文针对经典面试题进行详细解析&#xff0c;帮助新手系统掌握知识点&#xff0c;提升面试应对能力。 一、数据结构逻辑分类 题目 在数据结构中&#xff0c;从逻辑上可以把数据结构分为&#xff08; &#xff09;。 A、动态…

11.AOP开发

十一、AOP开发 1、Spring Boot实现 AOP 11.1.1、SpringBootAop简介 Spring Boot的AOP编程和Spring框架中AOP编程的唯一区别是&#xff1a;引入依赖的方式不同,其他内容完全一样 Spring Boot中AOP编程需要引入aop启动器&#xff1a; <!--aop启动器--> <dependency…

【网络入侵检测】基于源码分析Suricata的PCAP模式

【作者主页】只道当时是寻常 【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。 1. 概要 👋 本文聚焦于 Suricata 7.0.10 版本源码,深入剖析其 PCAP 模式的实现原理。通过系统性拆解初始化阶段的配置流程、PCAP 数据包接收线程的创建与运行机制,以及数据…

.NET 10 中的新增功能

.NET 运行时 .NET 10 运行时引入了新功能和性能改进。 关键更新包括&#xff1a; 数组接口方法反虚拟化&#xff1a;JIT 现在可以取消虚拟化和内联数组接口方法&#xff0c;从而提高数组枚举的性能。数组枚举去抽象化&#xff1a;改进功能以通过枚举器减少数组迭代的抽象开销…

盲注命令执行(Blind Command Execution)

一、核心原理 1. 无回显命令执行的本质 盲命令执行&#xff08;Blind Command Execution&#xff09;是一种攻击形式&#xff0c;攻击者通过注入系统命令到Web应用或后端系统中&#xff0c;但无法直接获取命令执行结果。盲命令执行的本质在于攻击者无法直接看到执行结果&#x…

Linux多线程技术

什么是线程 在一个程序里的多执行路线就是线程。线程是进程中的最小执行单元&#xff0c;可理解为 “进程内的一条执行流水线”。 进程和线程的区别 进程是资源分配的基本单位&#xff0c;线程是CPU调度的基本单位。 fork创建出一个新的进程&#xff0c;会创建出一个新的拷贝&…

计算机组成原理实验(1) 算术逻辑运算单元实验

实验一 算术逻辑运算单元实验 一、实验目的 1、掌握简单运算器的数据传输方式 2、掌握74LS181的功能和应用 二、实验内容 1、不带进位位逻辑或运算实验 2、不带进位位加法运算实验 3、实验指导书2.15实验思考 三、实验步骤和结果 实验内容一&#xff1a;不带进位…

Android将启动画面实现迁移到 Android 12 及更高版本

如果在 Android 11 或更低版本中实现自定义启动画面&#xff0c;请迁移应用迁移到 SplashScreen API 以获取帮助 确保其在 Android 12 及更高版本中正确显示。 从 Android 12 开始&#xff0c;在所有应用的冷启动和温启动期间&#xff0c;系统都会应用 Android 系统的默认启动…