昨天花了一天的时间弄计算器。也算是做出来了,还是简易的(怀疑猿生!!)。在此先感谢昨天被我骚扰的朋友。

先贴一张界面看看

  其实健壮性还是挺差的,用户体验也是极差的。比如说用户输入了不合理运算式子,我就直接抛出一个异常完事了,因为要在原来的算法里加判断实在晕乱。所以趁热打铁,希望在写博客的时候再把思路理理,完善不足。


思路一:

  因为计算的是四则混合运算,比如2*6-4/(2+3)。我们最开始得到的是一个表达式字符串,计算机是不会帮你计算的。而四则混合运算有优先等级的计算,那么该怎么计算呢?于是问了问度娘,度娘说你可以用逆波兰式计算。于是我二话不说看了看逆波兰式子,果然高明。下面是贴一下逆波兰式计算步骤:

/// <summary>
/// 使用逆波兰表示法求四则混合运算
/// 首先,需要两个栈
/// 栈s1用于临时存储运算符(含一个结束符号),此运算符在栈内遵循越往栈顶优先级越高的原则;
/// 栈s2用于输入逆波兰式;
/// 为方便起见,栈s1需要放入一个优先级最低的运算符,在这里假定为“#”;
/// 读取运算式入栈的步骤
/// 1.若x是操作数,则分析出完整的运算数,压入栈s2;
/// 2.若x是运算符,则分情况而定:
/// 若x是‘(’,则直接压入栈s1
/// 若x是‘)’,则将距离栈s1栈顶的最近的‘(’之间的运算符,逐个出栈,依次压入栈s2,此时抛弃‘(’
/// 若x是除了‘(’和‘)’以外的运算符,则再分如下情况
/// 若当前的栈顶元素是‘(’,则直接将x压入栈s1
/// 若当前的栈顶元素不是‘(’,则将x与栈s1的栈顶元素进行对比,如果优先级比较高,则压入栈,如果优先级低,则把栈s1的栈顶元素弹出压入栈s2,直到栈s1的栈顶元素优先级低于x,
/// 或则栈s2的栈顶运算符为‘(’,此时再将x压入栈s1
/// 3.进行完以上操作之后,检查栈s1是否为空,若不为空,则将栈中元素依次弹出并压入栈s2中。
/// </summary>

  把上面的2*6-4/(2+3)转成逆波兰式是这样的:-/+324*62。注意,转换完之后的逆波兰式是没有括号的。


思路二:

  首先想到的是写一个函数,实现这一转换。但是在写的时候会发现,①会用到判断是否是操作数还是操作符,②以及符号的优先级。因为符号很多,写在一个判断里,代码看起来会很长,所以就先把这两个判断写成函数,以方便使用,增强代码可读性。

  ①判断是数字还是符号函数如下:

  

        //
//判断是操作数还是操作符
//
static bool IsNumber(string str)
{
if (str == "(" || str == ")" || str == "*" || str == "/" || str == "-" || str == "+")
return false;
else
return true;
}

  ②定义优先等级。在这里,我用到的是泛型集合Dictionary。(我上一篇博文提到过这个集合)

        //
//定义优先等级,数字越大,优先等级越高
//
static void DefinePriority()
{
Dictionary<string, int> dic = new Dictionary<string, int>();
dic.Add("(", );
dic.Add(")", );
dic.Add("*", );
dic.Add("/", );
dic.Add("-", );
dic.Add("+", );
dic.Add("#", );
}

  然后接着写转换逆波兰式的函数:

  

        //
//接受一个字符串数组,转逆波兰式子
//
public void ReverseToPolish(string[] str)
{ stack1.Push("#"); //栈1压入#
if (str != null) //如果字符串数组不为空,执行判断
{
//因为是处理栈堆,很容易出现内存分配读取异常,加一个try catch
try
{
for (int i = ; i < str.Length; i++)
{
if (IsNumber(str[i])) //如果是数字,直接压入栈2
stack2.Push(str[i]);
else
{
if (dic[str[i]] == ) //如果是“(”,直接压入栈1
{
stack1.Push(str[i]);
}
else if (dic[str[i]] == ) //如果是“)”,将栈顶元素依次压入栈2,直到遇到“(”
{
while (stack1.Peek() != "(")
{
stack2.Push(stack1.Pop());
}
stack1.Pop(); //移除栈顶元素“(”
}
else if (dic[str[i]] == || dic[str[i]] == ) //除了“(”和“)”的情况
{
if (stack1.Peek() == "(") //如果栈顶元素是“(”,直接压入栈1
{
stack1.Push(str[i]);
}
//若当前的栈顶元素不是‘(’,则将x与栈s1的栈顶元素进行对比,如果优先级比较高,则压入栈
//如果不是,则把栈s1的栈顶元素弹出压入栈s2,直到栈s1的栈顶元素优先级低于x
else
{
while (dic[str[i]] <= dic[stack1.Peek()]) //如果优先等级不高于栈顶
{
stack2.Push(stack1.Pop());
}
stack1.Push(str[i]);
}
}//end else if
}//end else
}//end for
//进行完以上操作,检查栈1是否为“#”,不是,则把栈顶元素依次压入栈2
while (stack1.Peek() != "#")
{
stack2.Push(stack1.Pop());
}
}
catch
{
stack2.Push("");
} }
//检查下是否正确
foreach (var item in stack2)
Console.Write(item);
Console.WriteLine(); }

  下面贴下整个类的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 简易计算器
{
class ReversePolish
{
Dictionary<string, int> dic = new Dictionary<string, int>(); //字典
Stack<string> stack1 = new Stack<string>(); //栈1
Stack<string> stack2 = new Stack<string>(); //栈2 public ReversePolish()
{
DefinePriority();
} //
//接受一个字符串数组,转逆波兰式子
//
public void ReverseToPolish(string[] str)
{ stack1.Push("#"); //栈1压入#
if (str != null) //如果字符串数组不为空,执行判断
{
//因为是处理栈堆,很容易出现内存分配读取异常,加一个try catch
try
{
for (int i = ; i < str.Length; i++)
{
if (IsNumber(str[i])) //如果是数字,直接压入栈2
stack2.Push(str[i]);
else
{
if (dic[str[i]] == ) //如果是“(”,直接压入栈1
{
stack1.Push(str[i]);
}
else if (dic[str[i]] == ) //如果是“)”,将栈顶元素依次压入栈2,直到遇到“(”
{
while (stack1.Peek() != "(")
{
stack2.Push(stack1.Pop());
}
stack1.Pop(); //移除栈顶元素“(”
}
else if (dic[str[i]] == || dic[str[i]] == ) //除了“(”和“)”的情况
{
if (stack1.Peek() == "(") //如果栈顶元素是“(”,直接压入栈1
{
stack1.Push(str[i]);
}
//若当前的栈顶元素不是‘(’,则将x与栈s1的栈顶元素进行对比,如果优先级比较高,则压入栈
//如果不是,则把栈s1的栈顶元素弹出压入栈s2,直到栈s1的栈顶元素优先级低于x
else
{
while (dic[str[i]] <= dic[stack1.Peek()]) //如果优先等级不高于栈顶
{
stack2.Push(stack1.Pop());
}
stack1.Push(str[i]);
}
}//end else if
}//end else
}//end for
//进行完以上操作,检查栈1是否为“#”,不是,则把栈顶元素依次压入栈2
while (stack1.Peek() != "#")
{
stack2.Push(stack1.Pop());
}
}
catch
{
stack2.Push("");
} }
//检查下是否正确
foreach (var item in stack2)
Console.Write(item);
Console.WriteLine(); }
//
//判断是操作数还是操作符
//
private bool IsNumber(string str)
{
if (str == "(" || str == ")" || str == "*" || str == "/" || str == "-" || str == "+")
return false;
else
return true;
}
//
//定义优先等级,数字越大,优先等级越高
//
private void DefinePriority()
{ dic.Add("(", );
dic.Add(")", );
dic.Add("*", );
dic.Add("/", );
dic.Add("-", );
dic.Add("+", );
dic.Add("#", );
}
}
}

  笔者已经检测过,逻辑是没有问题的。总之我真的是写了很久,因为在写的时候会遇到如下的问题:就是当接受了加括号的一元运算符比如:1+(-2)。转换得到的式子是不能正确计算的。

  

  下次的博文我会分享解决上述问题的方法以及笔者自己的关于如何判断用户是否输入正确的式子的方法

  Tip:关于这些逻辑性比较强的代码,可能写过一段时间到回去看就会看不懂了。所以,笔者的经验是:写的时候一定要严谨,想周到点。写完如果不确定是否正确,就进行测试,亲测几次没问题之后就不要管它了,把它缩起来,以后拿来用就好。写的时候注释一定要详细点,万一要到回去看呢!!!!

  

  

C#Windows Form简易计算器实现(中)的更多相关文章

  1. C#Windows Form简易计算器实现(上)

    第一次写博客,来分享一个简易计算器的代码.作为一名准程序员,就是要多写代码才能孰能生巧.重视基础知识才能飞的更快更高以及更稳. 代码可能会写的很糟糕,不完美不安全之处希望发现的越多越好 c#编写计算器 ...

  2. Windows Form简易计算器实现(下)

    陆陆续续更新这个计算器用了一个礼拜了,今天无论如何也要把它更完.笔者有点追求完美,再者每天都有课,晚上还有作业,还有每晚都会写一些其他的博文. 上一次漏了写如何实现计算的.思路如下: 之前得到一个栈2 ...

  3. Windows Form 中快捷键设置

    在Windows Form程序中使用带下划线的快捷键只需要进行设置: 就能够工作.

  4. 在WPF中添加Windows Form控件(包括 ocx控件)

      首先,需要向项目中的reference添加两个dll,一个是.NET库中的System.Windows.Forms,另外一个是WindowsFormsIntegration,它的位置一般是在C:\ ...

  5. 【译】.NET 5. 0 中 Windows Form 的新特性

    自从 Windows Form 在 2018 年底开源并移植到 .NET Core 以来,团队和我们的外部贡献者都在忙于修复旧的漏洞和添加新功能.在这篇文章中,我们将讨论 .NET 5.0 中 Win ...

  6. 自制c#简易计算器

    这是一个课堂作业,我觉得作为一个简易的计算器不需要态度复杂的东西,可能还有一些bug,有空再慢慢加强. using System;using System.Collections.Generic;us ...

  7. JavaScript简易计算器

    JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML(标 ...

  8. mini dc与简易计算器 20165235

    mini dc 任务内容 本次mini dc任务就是通过补充代码来实现整型数据的后缀表达式计算 相关知识 通过利用堆栈这一先进后出的数据结构来实现后缀表达式的计算.通过Stack<Integer ...

  9. 用js制作简易计算器及猜随机数字游戏

    <!doctype html><html><head> <meta charset="utf-8"> <title>JS ...

随机推荐

  1. Hibernate进化史-------Hibernate概要

    一个.Hibernate概要 什么是Hibernate呢?首先,Hibernate是数据持久层的一个轻量级框架.实现了ORMapping原理(Object Relational Mapping). 在 ...

  2. 处理动态SQL语句的参数

    原文:处理动态SQL语句的参数 经常对SQL进行开发,写动态的SQL语句,是少之不了的,但是在使用动态语句中,常是因为有动态的参数的出现.参考下面代码示例: 正因为有了标记1的动态条件代码,而让SQL ...

  3. 【转】Android的Merge讲解与实例

    原文:http://blog.sina.com.cn/s/blog_62f987620100sf13.html 单独将<merge />标签做个介绍,是因为它在优化UI结构时起到很重要的作 ...

  4. [转载]Android中WebView自适应屏幕

    webview中右下角的缩放按钮能不能去掉 settings.setDisplayZoomControls(false); //隐藏webview缩放按钮 让Webview加载的页面居中显示有我知道的 ...

  5. mysql主从同步配置(windows环境)

    mysql主从同步配置(mysql5.5,windows环境)   A主机(作为主服务器)环境:windows8.mysql5.5 ip:192.168.1.100(自己填) B主机(作为从服务器,由 ...

  6. ASP.NET MVC显示WebForm网页或UserControl控件

    ASP.NET MVC显示WebForm网页或UserControl控件 学习与使用ASP.NET MVC这样久,还是对asp.net念念不忘.能否在asp.net mvc去显示aspx或是user ...

  7. 浅谈DevExpress<四>:TreeList中的拖拽功能

    本篇要实现的目标,简单来说就是把一个treelist的节点用鼠标拖到另外的节点(自身或其他的listview)上,如下图: 1 

  8. ASP.NET MVC4中使用NHibernate

    ASP.NET MVC4中使用NHibernate 1:下载安装NHibernate 打开 VS 2012新建一个 MVC4项目. 在项目名称上右击选择Manage NuGet Packages.你会 ...

  9. 【IOS开发】基础

    1.Objective-C 为 ANSI C 添加了下述语法和功能: 定义新的类 类和实例方法 方法调用(称为发消息) 属性声明(以及通过它们自动合成存取方法) 静态和动态类型化 块 (block), ...

  10. QuickWebApi2:使用Lambda方式,完成对WebApi的开发和调用-文档的生成

    续 QuickWebApi:使用Lambda方式,完成对WebApi的开发和调用 上一篇完成了主要的功能,本次修订主要重构了对接口文档的生成规范,使之可读性更佳,甚至可以作为接口文档进行发布(当然,在 ...