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

先贴一张界面看看

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


思路一:

  因为计算的是四则混合运算,比如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. jquery 产品查看放大镜组件

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

  2. 水晶报表使用经验谈--使用sql语句直接生成dataset做为报表的数据源

    概述: 上一次自己做了直接在rpt文件里使用oledb连接使用数据库的方法 但是不是很灵活 这次做了使用sql语句直接生成dataset做为报表的数据源(即push模式),这样就可以接受参数了.当然报 ...

  3. 【转】十款让 Web 前端开发人员更轻松的实用工具

    这篇文章介绍十款让 Web 前端开发人员生活更轻松的实用工具.每个 Web 开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具, ...

  4. screen获取屏幕信息

    <script type="text/javascript" language="javascript"> document.write(" ...

  5. Ajax提交底层原型XMLHttpRequest

    相信接触过ajax的都觉得其post,get提交很方便,那么他是怎么实现的呢?基于此我们就不得不谈到js中的XMLHttpRequest对象. 其中w3c中是这样解释的: XMLHttpRequest ...

  6. javascript设计模式系列

    javascript设计模式系列   创建型: 1.抽象工厂模式(Abstract Factory) 2.构建者模式(Builder) 3.工厂方法模式(Factory Method) 4.原型模式( ...

  7. DTD

    DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块. 它使用一系列的合法元素来定义文档结构. DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用. 内部的 DOCTYPE 声明 ...

  8. jQuery中Ajax的应用

    一.Ajax介绍 1.什么是Ajax 异步的JavaScript与XML技术,是一种广泛应用在浏览器的网页开发技术. 2.Ajax的优点 a.不需要任何浏览器插件,在任何支持JavaScript的浏览 ...

  9. 从网络上获取图片并保存在sdCard上

    package com.aib.soft; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileO ...

  10. 对类sizeof的时候

    sizeof一个类的时候,都什么会被计算?静态成员会被计算进来么?如果这是一个子类,它的父类成员会被计算么? #include<stdio.h> #include<string.h& ...