一、思路概要和知识回顾
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_num
和pop_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.执行计算函数
-
如果运算符是
'+'
,则返回两个操作数a
和b
的和。
如果运算符是 '-'
,则返回两个操作数 a
和 b
的差。
如果运算符是 '*'
,则返回两个操作数 a
和 b
的积。
-
如果运算符是
'/'
,则首先检查除数b
是否为零。如果是零,打印错误信息并退出程序;否则,返回两个操作数a
和b
的商。
如果传入的运算符不属于上述四种基本运算符之一,则返回 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
函数 :之前定义的函数,用于弹出运算符栈的栈顶元素并返回该元素。
弹出操作数 :依次弹出操作数栈的栈顶元素,分别存储在变量 b
和 a
中。这里假设 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
函数 :对 a
和 b
执行 op
运算,并返回结果。
push_num
函数 :将计算结果压入操作数栈。
显示栈状态:
display_stacks
函数 :显示当前操作数栈和运算符栈的状态。
压入当前运算符:
push_op
函数 :将当前运算符压入运算符栈。
15.清理输入缓冲区函数
-
声明变量
c
: 用于存储从标准输入读取的字符。 -
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 循环
②定义表达式缓冲区
③提示用户输入
④读取用户输入
⑤去除换行符
⑥检查输入是否为空
⑦处理表达式
⑧检查是否继续
三、完整代码