运用C#的堆栈类实现四则混合运算计算器设计(一)

《程序设计综合实践》课第一项任务,便是编写一个能实现四则混合运算的计算器窗体程序。考虑到Windows窗体界面程序制作的简单化,以及堆栈运用的方便性,选择C#作为实现代码。

图一      四则运算计算器界面图

经过查阅资料,已经明确了一种大致实现方法:将操作数和运算符,按顺序入栈,再反序一个个读出识别,加以运算,最终输出结果。

部分控件说明
(Name)说明
txtDisplay左边的文本框,展示用户输入
numDisplay右边的文本框,用于按输入顺序展示在堆栈中已存放的操作数

在具体实现时,主要有以下几个方面比较困难:

  1. 如何直接通过文本框在堆栈中存放操作数?
  2. 如何实现运算符优先级?
  3. 如何实现多项式一步计算?

下面将逐一解决这三个困难点:

  • 实现通过文本框在堆栈中存入操作数。

 针对最初的第一个操作数,我们可以直接调用stack类中Push()函数,读取文本框中数据。

st.Push(txtDisplay.Text);
本文所涉及Stack类常用的方法及属性
方法及属性名称说明
Count获取Stack中包含的元素数
Clear()从Stack中移除所有对象
Push()将对象插入Stack的顶部
Pop()移除并返回位于Stack顶部的对象
Peek()返回位于Stack顶部的对象但不将其移除

 

本文涉及全局变量说明
变量名称说明
stStack<string>型
num1运算符前的操作数,double型
num2运算符后的操作数,double型
Operatorstring类,操作符
beijianshustring类,存放前一次的文本框内容(*关键变量)

关键是,随着用户的不断输入,对于之后掺杂了操作符的文本框,怎样从中读取出一个个操作数呢?

图二        掺杂了运算符的文本框

一个很自然的想法,就是希望程序能够从最后一位往前读取至非数字。比如上图,最好直接有个函数,让程序反向读取,读到“-”,自动停止,得到数字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中可以清晰看出进入了堆栈的操作数,完美,哈哈。

图三     上图二的运算结果图

 

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值