计算机软件实习1 基于逆波兰表达式的计算器制作
课前预习内容
课程要求
要求:
制作一个简单运算符计算器。
运算符包括:“+”、“-”、“*”、“/”、“(”和“)”。
可以进行多项式运算。
有一个界面展现操作过程,操作数和结果可以显示出来。
难点:
运算符带有括号,需要符号优先级判断。
需要进行多项式计算。
中缀表达式转换成后缀表达式
1.初始化两个栈:运算符栈sign和储存中间结果的栈num;
2.从左至右扫描中缀表达式;
3.遇到操作数时,将其压num;
4.遇到运算符时,比较其与sign栈顶运算符的优先级:
(1)如果sign为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
(2)否则,若优先级比栈顶运算符的高,也将运算符压入sign;
(3)否则,将sign栈顶的运算符弹出并压入到num中,再次转到(4.1)与sign中新的栈顶运算符相比较;
5.遇到括号时:
(1)如果是左括号"(",则直接压入sign;
(2)如果是右括号")",则依次弹出sign栈顶的运算符,并压入num,直到遇到左括号为止,此时将这一对括号丢弃;
6.重复步骤2至5,直到表达式的最右边;
7.将sign中剩余的运算符依次弹出并压入num;
8.依次弹出num中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式;
具体举例:
中缀表达式:a + b * c + ( d * e + f ) * g
后缀表达式:a b c * + d e * f + g * +
利用逆波兰表达式求值
首先,我们先来了解一下逆波兰表达式求值。
1、循环扫描语法单元的项目。
2、如果扫描的项目是操作数,则将其压入操作数堆栈,并扫描下一个项目。
3、如果扫描的项目是一个二元运算符,则对栈的顶上两个操作数执行该运算。
4、如果扫描的项目是一个一元运算符,则对栈的最顶上操作数执行该运算。
5、将运算结果重新压入堆栈。
6、重复步骤2-5,堆栈中即为结果值。
接着,我们了解一下运算符的优先级:
基于MFC制作计算器(环境VS2019)
利用MFC创建界面
- 创建项目:
- 计算机的界面设计
再完成界面的初步设计,我们需要完成相关的功能。
以字符“1”为例。
双击按钮“1”,出现如下界面,然后我们开始添加代码。
在头文件里定义一些需要用到的变量。
然后是相同的操作来复制代码完成其他几个数字。
重点算法实现
本计算器是利用逆波兰表达式求取数值,但是在利用逆波兰表达式之前,首先需要将中缀表达式转换成后缀表达式,并给出运算符的优先级,因此,我们先设定三个函数。
三个函数代码具体如下:
中缀表达式转后缀表达式:
/*将表达式转换成逆波兰式*/
vector<string> CMFCApplicationDlg::toPolish(string str)
{
stack<string> opstack;
vector<string> polish;
string str1;
for (int i = 0; i < str.size(); i++)
{
if (str[i] == '(') /*左括号*/
{
opstack.push("(");
continue;
}
if (str[i] >= '0' && str[i] <= '9')/*数字*/
{
str1 = "";
while (i < str.size() && str[i] >= '0' && str[i] <= '9')
{
str1 += str[i];
i++;
}
i--;
polish.push_back(str1);
continue;
}
if (str[i] == ')')/*右括号*/
{
/*出栈,直到匹配到左括号*/
while (!opstack.empty())
{
if (opstack.top() != "(")
{
polish.push_back(opstack.top());
opstack.pop();
}
else
{
opstack.pop();
break;
}
}
}
else/*加减乘除运算符*/
{
str1 = str[i];
while (!opstack.empty() && priority(opstack.top()) >= priority(str1))
{
polish.push_back(opstack.top());
opstack.pop();
}
opstack.push(str1); // 添加到符号栈中
}
}
//剩余的运算符添加到polish中
while (!opstack.empty())
{
polish.push_back(opstack.top());
opstack.pop();
}
return polish;
}
计算逆波兰式:
/*将逆波兰式进行计算*/
int CMFCApplicationDlg::calculate(vector<string> polish)
{
stack<double> data;
double a, b, c;
for (int i = 0; i < polish.size(); i++)
{
if (polish[i] == "+" || polish[i] == "-" || polish[i] == "*" || polish[i] == "/")
{
b = data.top(); data.pop();
a = data.top(); data.pop();
if (polish[i] == "+") c = a + b;
else if (polish[i] == "-") c = a - b;
else if (polish[i] == "*") c = a * b;
else if (polish[i] == "/") c = a / b;
g_string.Format(_T("%d"), c);
my_Edit.SetWindowText(g_string);
data.push(c);
}
else
{
data.push(atof(const_cast<const char*>(polish[i].c_str())));/*atof函数功能:string to double */
g_string.Format(_T("%d"), stoi(polish[i]));
my_Edit.SetWindowText(g_string);
}
}
return data.top();
}
运算符优先级:
/*返回运算符的优先级*/
int CMFCApplicationDlg::priority(string op)
{
if (op == "(") return 0;
if (op == "+" || op == "-") return 1;
else if (op == "*" || op == "/") return 2;
}
有了以上三个函数,计算器的功能则可以实现,但除了计算,计算器还需具备其他功能。
其他一些操作实现如下所示:
等于号:
void CMFCApplicationDlg::OnBnClickedButton17()//=
{
// TODO: 在此添加控件通知处理程序代码
std::string str= CT2A(g_string.GetString());//将全局变量转换成string类型,便于下面的函数操作
calculate(toPolish(str));
}
退后:
void CMFCApplicationDlg::OnBnClickedButton8()//退格
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);//将屏幕中的数据转换到变量中
if (!g_string.IsEmpty())//如果字符串不为空
{
g_string = g_string.Left(g_string.GetLength() - 1);
my_Edit.SetWindowText(g_string);
}
UpdateData(FALSE);//将变量中的信息显示到相应的框中
}
清空:
void CMFCApplicationDlg::OnBnClickedButton3()//清空
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
g_string = "";
my_Edit.SetWindowText(g_string);
UpdateData(FALSE);
}
结果输出
为了更清晰的表达计算器的运算流程,我们通过显示逻辑和处理逻辑来展示。
下面,我们选择一个带括号的式子进行测试: