用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_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 循环
②定义表达式缓冲区
③提示用户输入
④读取用户输入
⑤去除换行符
⑥检查输入是否为空
⑦处理表达式
⑧检查是否继续
三、完整代码
相关文章:
用C语言实现——一个中缀表达式的计算器。支持用户输入和动画演示过程。
一、思路概要和知识回顾 1.思路概要 ①中缀表达式计算: 需要处理运算符的优先级,可能需要用到栈结构。 ❗❗如何将中缀表达式转换为后缀表达式?或者直接计算? 通常,中缀转后缀(逆波兰式)再…...
使用 Pandas 进行多格式数据整合:从 Excel、JSON 到 HTML 的处理实战
前言 在数据处理与分析的实际场景中,我们经常需要整合不同格式的数据,例如 Excel 表格、JSON 配置文件、HTML 报表等。本文以一个具体任务(蓝桥杯模拟练习题)为例,详细讲解如何使用 Python 的 Pandas 库结合其他工具&…...
常见游戏引擎介绍与对比
Unreal Engine (UE4/UE5) 主语言:C Unreal Engine 主要使用 C 作为开发语言。C 提供了高性能的底层控制,适用于需要精细调优的 AAA 级游戏。C 在 Unreal 中用于开发核心游戏逻辑、物理引擎等性能要求较高的部分。 脚本语言:蓝图(B…...
第十一天 主菜单/设置界面 过场动画(Timeline) 成就系统(Steam/本地) 多语言支持
前言 对于刚接触Unity的新手开发者来说,构建完整的游戏系统往往充满挑战。本文将手把手教你实现游戏开发中最常见的四大核心系统:主菜单界面、过场动画、成就系统和多语言支持。每个模块都将结合完整代码示例,使用Unity 2022 LTS版本进行演示…...
vue3 使用 vite 管理多个项目,实现各子项目独立运行,独立打包
场景: 之前写过一篇 vite vue2 的配置,但是现在项目使用 vue3 较多,再更新一下 vue脚手架初始化之后的项目,每个项目都是独立的,导致项目多了之后,node依赖包过多,占用内存较多。想实现的效果…...
k8s(9) — zookeeper集群部署(亲和性、污点与容忍测试)
一、部署思路 1、前期设想 zookeeper集群至少需要运行3个pod集群才能够正常运行,考虑到节点会有故障的风险这个3个pod最好分别运行在3个不同的节点上(为了实现这一需要用到亲和性和反亲和性概念),在部署的时候对zookeeper运行的pod打标签加…...
Linux操作系统复习
Linux操作系统复习 一. Linux的权限和shell原理1. Linux从广义上讲是什么 从狭义上讲是什么?2. shell是什么?3. 为什么要设置一个shell外壳而不是直接和linux 内核沟通4. shell的原理是什么5. Linux中权限的概念6. 如何提升当前操作的权限7. 文件访问者的…...
深入解析 Linux 中动静态库的加载机制:从原理到实践
引言 在 Linux 开发中,动静态库是代码复用的核心工具。静态库(.a)和动态库(.so)的加载方式差异显著,直接影响程序的性能、灵活性和维护性。本文将深入剖析两者的加载机制,结合实例演示和底层原…...
总账主数据——Part 2 科目-1
本文主要介绍在S4 HANA OP中 总账主数据的后台配置及前台操作。 目录 1. 准备 1.1 科目表的定义(OB13) 1.2 给公司代码分配科目表(OB62) 1.3 定义科目组(OBD4) 1.4 定义留存收益科目(OB53) 1.5 维护科目表层“文本标识” (OBT6) 1.6 维护公司代码层“文本标识” (OBT…...
借助内核逻辑锁pagecache到内存
一、背景 内存管理是一个永恒的主题,尤其在内存紧张触发内存回收的时候。系统在通过磁盘获取磁盘上的文件的内容时,若不开启O_DIRECT方式进行读写,磁盘上的任何东西都会被缓存到系统里,我们称之为page cache。可以想象࿰…...
✨ Apifox:这玩意儿是接口界的“瑞士军刀”吧![特殊字符][特殊字符]
——全网最皮最全测评,打工人看了直呼“真香” 📢 友情提醒 还在用 Postman 测接口、Swagger 写文档、Mock.js 造假数据、脑细胞搞团队协作? 停! 你仿佛在玩《工具人环游记》,而隔壁同事已经用 Apifox 「一杆清台」了…...
《普通逻辑》学习记录——性质命题及其推理
目录 一、性质命题概述 二、性质命题的种类 2.1、性质命题按质的分类 2.2、性质命题按量的分类 2.3、性质命题按质和量结合的分类 2.4、性质命题的基本形式归纳 三、四种命题的真假关系 3.1、性质命题与对象关系 3.2、四种命题的真假判定 3.3、四种命题的对当关系 四、四种命题…...
设备接入与APP(应用程序)接入华为云iotDA平台的路径元素有哪些不同?
目录 壹、设备接入华为云iotDA 🏢 形象比喻:设备 员工,IoTDA 平台 安保森严的总部大楼 一、📍 平台接入地址 总部大楼地址 二、🧾 接入凭证 出入证 / 门禁卡 / 工牌 1. 设备密钥或证书 2. 预置接入凭证密钥&a…...
【git#4】分支管理 -- 知识补充
一、bug 分支 假如我们现在正在 dev2 分支上进行开发,开发到一半,突然发现 master 分支上面有 bug,需要解决。 在Git中,每个 bug 都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临…...
AXOP34062: 40V双通道运算放大器
AXOP34062是一款通用型高压双通道运算放大器,产品的工作电压为2.5V至40V,具有25MHz的带宽,压摆率为10V/μs,静态电流为650A。较高的耐压和带宽使其可以胜任绝大多数的高压应用场景。 主要特性 轨到轨的输入输出范围低输入失调电…...
OpenCv高阶(十)——光流估计
文章目录 前言一、光流估计二、使用步骤1、导库读取视频、随机初始化颜色2、初始化光流跟踪3、视频帧处理循环4、光流计算与可视化5、循环控制与资源释放完整代码 总结 前言 在计算机视觉领域,光流估计是捕捉图像序列中像素点运动信息的核心技术。它描述了图像中每…...
BS客户端的单点登录
1、参数类似于“XXXXX://?userIdsystem&time1696830378038&token38a8ea526537766f01ded33a6cdfa5bd” 2、在config里加一个LoginSecret参数可随意指定一个字符串 3、BS登录代码里会对“LoginSecret的参数值用户ID时间戳”进行MD5加密形成token,与传过来的…...
通讯录完善版本(详细讲解+源码)
目录 前言 一、使通讯可以动态更新内存 1、contact.h 2、contact.c 存信息: 删除联系人,并试一个不存在的人的信息,看看会不会把其他人删了 编辑 修改: 编辑 排序: 编辑 销毁: 编辑 …...
第3讲:ggplot2完美入门与美化细节打磨——从基础绘制到专业级润色
目录 1. 为什么选择ggplot2? 2. 快速了解ggplot2绘图核心逻辑 3. 基础绘图示范:柱状图、折线图、散点图 (1)简单柱状图 (2)折线图示范 (3)高级散点图 + 拟合线 4. 精细美化:细节打磨决定专业感 5. 推荐的美化小插件(可选进阶) 6. 小练习:快速上手一幅美化…...
带宽?增益带宽积?压摆率?
一、带宽(Bandwidth) 1.科学定义: 带宽指信号或系统能够有效通过的频率范围,通常定义为信号功率下降到中频值的一半(即 - 3dB)时的最高频率与最低频率之差。对于运算放大器(Op-Amp)…...
为什么栈内存比堆内存速度快?
博主介绍:程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章,首发gzh,见文末👇…...
什么是非关系型数据库
什么是非关系型数据库? 引言 随着互联网应用的快速发展,传统的基于表格的关系型数据库(如 MySQL、Oracle 等)已经不能完全满足现代应用程序的需求。在这种背景下,非关系型数据库(NoSQL 数据库)…...
制作一个简单的操作系统9
自定义 myprintf 函数实现解析 探索如何实现一个自定义的 printf 函数来处理任意 %d 和 %s 组合 (说实话,想不用任何库函数和头文件,纯C实现太难了,我放弃了,弄了一个简陋版本 对付用) 运行效果: Hello 123 World 456 Coding这样参数传递:(最多支持5个参数,按顺序…...
华为Pura X的智控键:让折叠机体验更上一层楼的设计
还记得Mate 70系列刚出那会,我体验了下智控键,那时候就觉得这个“把快捷方式做进电源键”的交互方式非常惊艳,没想到在Pura X上,这种便捷体验感更上了一层楼。 智控键:折叠屏手机的天选快捷方式? 传统折叠…...
打造高功率、高电流和高可靠性电路板的厚铜PCB生产
厚铜PCB生产是指制作一种具有较厚铜层的PCB(Printed Circuit Board,印刷电路板)。这种PCB通常用于高功率、高电流和高可靠性的电子设备中。厚铜PCB的生产过程包括以下几个 主要步骤: 1. 基材准备 厚铜PCB的基材通常采用FR4或CEM-…...
AI超级智能体教程(三)---程序调用AI大模型的四种方式(SpringAI+LangChain4j+SDK+HTTP)
文章目录 1.安装SDK(查看文档)2.创建API-key3.项目引入灵积大模型4.HTTP接入的方式5.SpringAI引入5.1添加依赖5.2添加配置5.3测试代码 6.LangChain4j引入6.1依赖引入6.2测试提问 1.安装SDK(查看文档) 安装阿里云百炼SDK_大模型服…...
JDBC连接数据库
一、查询 sqlserver数据库 private List<Map<String, String>> getPathList(String id) throws Exception {String driverName "com.microsoft.sqlserver.jdbc.SQLServerDriver";String dataBaseurl "jdbc:sqlserver://localhost:1433;SelectMeth…...
常见缓存淘汰算法(LRU、LFU、FIFO)的区别与实现
一、前言 缓存淘汰算法主要用于在内存资源有限的情况下,优化缓存空间的使用效率。以确保缓存系统在容量不足时能够智能地选择需要移除的数据。 二、LRU(Least Recently Used) 核心思想:淘汰最久未被访问的数据。实现方式&#x…...
深度学习--循环神经网络RNN
文章目录 前言一、RNN介绍1、传统神经网络存在的问题2、RNN的核心思想3、 RNN的局限性 二、RNN基本结构1、RNN基本结构2、推导3、注意4、循环的由来5、再谈RNN的局限 总结 前言 循环神经网络(RNN)的起源可以追溯到1982年,由Saratha Sathasiv…...
大学IP广播系统解决方案:构建数字化智慧化大学校园IP广播平台
大学IP广播系统解决方案:构建数字化智慧化大学校园IP广播平台 北京海特伟业科技有限公司任洪卓于2025年4月24日发布 随着教育信息化建设的深入推进,传统的模拟广播系统已无法满足现代化校园对智能化、场景化、融合化的管理需求。为此,海特伟业提出构建…...
#ifndef #else #endif条件编译
目录 一、#ifdef 1. 基本用法 2. 查看头文件 3. 目的 4. 常见用途 4. 取消定义 5.小结 二、#ifndef和#ifdef区别 1. #ifdef 2. #ifndef 3.结论 一、#ifdef 宏定义 #define H_PWM_L_ON 的作用是创建一个名为 H_PWM_L_ON 的宏。以下是这个宏定义的一些关键点ÿ…...
SystemVerilog语法之typedef与自定义结构
1.7 使用typedef创建新的类型 在Verilog中,你可以为操作数的位宽或者类型分别定义一个宏,但是你并没有创建新的数据类型,而是进行了文本的替换。在SystemVerilog中,可以使用typedef创建新的类型。可以将parameter和typedef语句放…...
【防火墙 pfsense】2配置
(1)接口配置和接口 IP 地址分配 ->配置广域网(WAN)和局域网(LAN)接口,分配设备标识符,如 eth0、eth1 等; ->如将WAN 接口将被分配到 eth0,而 LAN 接口将…...
数据结构之排序
排序 一.比较排序1.插入排序基本思想1.1直接插入排序1.2希尔排序 2.选择排序直接选择排序堆排序 3.交换排序冒泡排序快速排序hoare版本挖坑法lomuto前后指针非递归版本 4.归并排序非递归的归并排序 非比较排序1.计数排序 排序算法复杂度及稳定性分析 一.比较排序 1.插入排序 …...
cgroup sched_cfs_bandwidth_slice参数的作用及效果
一、背景 cgroup是一个非常重要的功能,其中cgroup cpu这块有不少功能,在之前的博客 CFS及RT调度整体介绍_rt调度器-CSDN博客 里,我们分析了cfs的组调度也就是cgroup cpu的这块内核逻辑的细节侧重于调度逻辑这块,在之前的博客 cgr…...
【C++指南】告别C字符串陷阱:如何实现封装string?
🌟 各位看官好,我是egoist2023! 🌍 种一棵树最好是十年前,其次是现在! 💬 注意:本章节只详讲string中常用接口及实现,有其他需求查阅文档介绍。 🚀 今天通过了…...
液体神经网络LNN-Attention创新结合——基于液体神经网络的时间序列预测(PyTorch框架)
1.数据集介绍 ETT(电变压器温度):由两个小时级数据集(ETTh)和两个 15 分钟级数据集(ETTm)组成。它们中的每一个都包含 2016 年 7 月至 2018 年 7 月的七种石油和电力变压器的负载特征。 traffic(交通) :描…...
kafka和Spark-Streaming2
Kafka 工作流程及文件存储机制 Kafka 中消息是以topic 进行分类的,生产者生产消息,消费者消费消息,都是面向topic 的。 “.log”文件存储大量的数据,“.index”文件存储偏移量索引信息,“.timeindex”存储时间戳索引文…...
MySQL日期函数的详细教程(包含常用函数及其示例)
概述 以下是一个关于MySQL日期函数的详细教程,包含常用函数及其示例内容以转换为PDF电子书,喜欢的朋友可以转存慢慢享用:https://pan.quark.cn/s/57d2e491bbbe 1. 获取当前日期和时间 • CURDATE() / CURRENT_DATE() 返回当前日期…...
P4017 最大食物链计数-拓扑排序
P4017 最大食物链计数 题目来源-洛谷 题意 要求最长食物链的数量。按照题意,最长食物链就是指有向无环图DAG中入度为0到出度为0的不同路径的数量(链数) 思路 在计算时,明显:一个被捕食者所…...
C语言——字串处理
C语言——字串处理 一、问题描述二、格式要求1.输入形式2.输出形式3.样例 三、实验代码 一、问题描述 现有两个字符串s1和s2,它们最多都只能包含255个字符。编写程序,将字符串s1中所有出现在字符串s2中的字符删去,然后输出s1。 二、格式要求…...
工业排风轴流风机:强劲动力与节能设计的完美融合
在工业生产中,通风换气是保障作业环境安全、维持设备正常运行的关键环节。工业排风轴流风机凭借其独特的设计,将强劲动力与节能特性完美融合,成为众多工业场景的首选通风设备,为企业高效生产与绿色发展提供了可靠支持。 工业排风…...
【Test】单例模式❗
文章目录 1. 单例模式2. 单例模式简单示例3. 懒汉模式4. 饿汉模式5. 懒汉式和饿汉式的区别 1. 单例模式 🐧定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式是一种常用的软件设计模式,在它的核心结构中只包…...
3.3 Spring Boot文件上传
在 Spring Boot 项目中实现文件上传功能,首先创建项目并添加依赖,包括 Commons IO 用于文件操作。接着,创建文件上传控制器 FileUploadController,定义上传目录并实现文件上传逻辑,通过生成唯一文件名避免文件冲突。创…...
【玩泰山派】7、玩linux桌面环境xfce - (4)使用gstreamer
文章目录 前言gstreamergstreamer概述基本概念主要功能应用场景开发方式 安装gstreamer使用gstreamer使用gstreamer播放视频 前言 玩一下gstreamer,使用gstreamer去播放下音视频 gstreamer gstreamer概述 GStreamer是一个用于构建多媒体应用程序的开源库和框架&…...
cpu性能统计
cpu负载 top中avg,/proc/loadavg, 包括cpu密集型任务io型任务 统计流程 每cpu scheduler_tick ----calc_global_load_tick : 当前瞬时 cpu::this_rq:: nr_runningnr_inunterrupt->calc_load_tasks(全局变量) 全局 do_timer ----calc_global_load&a…...
Java对接企业微信实战笔记
Java对接企业微信实战笔记 微信开发文档 有关企业微信的服务商的一些配置参考企业微信创建的服务商配置信息 一 流程图 只要企业安装应用后,就可以获取到企业的信息 二 创建应用获取suite_ticket 1.创建应用 微信开发平台得是服务商角色才能进入服务商后台创建一…...
HashMap的源码解析
HashMap基于哈希表的Map接口实现,是以key-value存储形式存在,即主要用来存放键值对。HashMap的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。 JDK1.8 之前 HashMap由数…...
【金仓数据库征文】金仓数据库KingbaseES:在技术与人文交织中开拓信创未来
🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 【引言】 在信息技术应用创新(信创)的浪潮下,数据库作为数字经济的基石…...
【AI】[特殊字符]生产规模的向量数据库 Pinecone 使用指南
一、Pinecone 的介绍 Pinecone是一个完全托管的向量数据库服务,专为大规模机器学习应用设计。它允许开发者轻松存储、搜索和管理高维向量数据,为推荐系统、语义搜索、异常检测等应用提供强大的基础设施支持。 1.1 Pinecone的核心特性 1. 高性能向量搜…...