《程序设计综合实践》课第一项任务,便是编写一个能实现四则混合运算的计算器窗体程序。考虑到Windows窗体界面程序制作的简单化,以及堆栈运用的方便性,选择C#作为实现代码。
经过查阅资料,已经明确了一种大致实现方法:将操作数和运算符,按顺序入栈,再反序一个个读出识别,加以运算,最终输出结果。
(Name) | 说明 |
txtDisplay | 左边的文本框,展示用户输入 |
numDisplay | 右边的文本框,用于按输入顺序展示在堆栈中已存放的操作数 |
在具体实现时,主要有以下几个方面比较困难:
- 如何直接通过文本框在堆栈中存放操作数?
- 如何实现运算符优先级?
- 如何实现多项式一步计算?
下面将逐一解决这三个困难点:
- 实现通过文本框在堆栈中存入操作数。
针对最初的第一个操作数,我们可以直接调用stack类中Push()函数,读取文本框中数据。
st.Push(txtDisplay.Text);
方法及属性名称 | 说明 |
Count | 获取Stack中包含的元素数 |
Clear() | 从Stack中移除所有对象 |
Push() | 将对象插入Stack的顶部 |
Pop() | 移除并返回位于Stack顶部的对象 |
Peek() | 返回位于Stack顶部的对象但不将其移除 |
变量名称 | 说明 |
st | Stack<string>型 |
num1 | 运算符前的操作数,double型 |
num2 | 运算符后的操作数,double型 |
Operator | string类,操作符 |
beijianshu | string类,存放前一次的文本框内容(*关键变量) |
关键是,随着用户的不断输入,对于之后掺杂了操作符的文本框,怎样从中读取出一个个操作数呢?
一个很自然的想法,就是希望程序能够从最后一位往前读取至非数字。比如上图,最好直接有个函数,让程序反向读取,读到“-”,自动停止,得到数字4。顺着这个思路,一个想法便油然而生,让程序反向读取文本框中内容,按照顺序检索字符,至非数字时断开,输出。
由于对字符串操作的生疏,这个想法一时半会儿没能很好实现。于是开始了寻找其他方法。
想到堆栈是按顺序一个个存取操作数的,便想着能不能在用户每输完一个操作数,程序最好就马上读取它。顺着这个思路想,很快便从运算符上找到了切入口。因为每一个运算符的输入,意味着一个操作数的输入完成。进而想到了这样一种处理方法,在运算符控件单机事件的开头部分,加入数据入栈操作函数。
这个数据入栈函数是本程序的一大亮点。考虑到每相邻运算符中间必隔着一个操作数,于是定义一个全局string变量beijianshu,用于存放前一次的txtDisplay.text。当运算符被单机时,执行剪切txtDisplay.text中文本从beijianshu.Length处直到末尾的操作,恰巧是最新输入的一个操作数。调用st.Push(),将它入栈,同时显示在numDispaly.text中,便于观察程序运行。
protected void Ruzhan()
{
if (beijianshu == "")
st.Push(txtDisplay.Text);
else
st.Push(txtDisplay.Text.Substring(beijianshu.Length));
numDisplay.Text += st.Peek()+",";
}
下面以“+”运算符为例,给出一段具体实现代码,验证上一段落所讲的单机运算符以读取最新操作数 。
private void btn_add_Click(object sender, EventArgs e)
{
Ruzhan();
txtDisplay.Text += "+";
st.Push("+");
beijianshu = txtDisplay.Text;
}
本程序中,其他运算符与之代码均类似,执行Ruzhan()读取最新操作数,然后在用户输入文本框中显示运算符,再将运算符入栈,更新全局变量beijianshu,以待下一次执行操作。
- 实现运算符优先级
要考虑运算符的优先级,则至少涉及两个运算符。最简单的便是3操作数2运算符的一个5对象堆栈计算了。事实上,能对这个5对象进行正确处理后,外层套一个循环便可以实现多项式一步计算到位。下面先具体介绍如何实现运算符的优先级。
针对5对象的多项式a_b_c,给出这样4条:
1+2+3
1+2*3
1*2+3
1*2*3
很显然,上面4条式子,包括了我们所有可能出现的相邻运算符优先级情形 。将右边的运算符叫做Operator,左边的运算符叫做Operator2。根据乘除优先原则,若Operator优先,则先执行b_c;若Operator2优先,则先执行a_b。在程序中,选用if-else语句,分三种情况执行。若Operator位乘或除,则先执行b_c;若Operator2为乘或除,则先执行a_b;其他,则优先级相同,都可以,默认先执行b_c。
num2 = Convert.ToDouble(st.Pop());
Operator = st.Pop();
num1 = Convert.ToDouble(st.Pop());
string Operator2 = st.Peek();
//下面进行优先级判断,然后选择正确运算顺序,并完成数据正确出入栈
if (Operator == "*" || Operator == "/")
Yunsuan(num1, num2, Operator);
else if (Operator2 == "*" || Operator2 == "/")
{
st.Pop();
Yunsuan( Convert.ToDouble(st.Pop()),num1, Operator2);
st.Push(Operator);
st.Push(Convert.ToString (num2));
}
else
Yunsuan(num1, num2, Operator);
- 实现多项式一步运算
接上可知,任意长多项式都可以5对象一看一运算,化简至最终一条3对象的简单式子。这里借助Stack类的Count函数,实现While循环处理多项式,最终直接得出结果。
所有的运算顺序处理,均在“=”控件的单机事件中。下面给出实现代码。
private void btn_equal_Click(object sender, EventArgs e)
{
Ruzhan();
while (st.Count>3)
{
num2 = Convert.ToDouble(st.Pop());
Operator = st.Pop();
num1 = Convert.ToDouble(st.Pop());
string Operator2 = st.Peek();
//下面进行优先级判断,然后选择正确运算顺序,并完成数据正确出入栈
if (Operator == "*" || Operator == "/")
Yunsuan(num1, num2, Operator);
else if (Operator2 == "*" || Operator2 == "/")
{
st.Pop();//要注意对堆栈的思考,补全合适对象,避免出错
Yunsuan( Convert.ToDouble(st.Pop()),num1, Operator2);
st.Push(Operator);
st.Push(Convert.ToString (num2));
}
else
Yunsuan(num1, num2, Operator);
}
num2 = Convert.ToDouble(st.Pop());//更新3个全局变量
Operator = st.Pop();
num1 = Convert.ToDouble(st.Pop());
Yunsuan(num1, num2, Operator);//进行对最终最简式子的运算
txtDisplay.Text = st.Pop();
beijianshu = "";//关键变量回归初始状态,以待继续操作
}
三个难点已经全部突破了,下面附上Yunsuan()程序的代码,整体程序已经基本完整了。
private void Yunsuan(double num1,double num2,string Operator)
{
if (Operator == "+")
st.Push(Convert.ToString(num1 + num2));
else if (Operator == "-")
st.Push(Convert.ToString(num1 - num2));
else if (Operator == "*")
st.Push(Convert.ToString(num1 * num2));
else if (Operator == "/")
st.Push(Convert.ToString(num1 / num2));
}
经过实际的测试,程序可以完美实现四则运算的一步计算,但必须要求用户进行正确的输入。如果用户输入错误,程序将无法处理。比如:输入完“+”后,按下退格,之后运算会出错;连续输入运算符,会出错……
这也是我将标题命名为(一)的原因所在,就功能实现来说,暂时的程序已经十分完美,但在容错方面,还需要进一步改进,这将会是(二)中的内容。
最后,贴上一张上图二按下“=”后的运算结果图。numDispaly.text中可以清晰看出进入了堆栈的操作数,完美,哈哈。