The 2nd part of the Calculator program

题目链接:Click Here
github链接:Click Here

诶嘿,第二部分,要开始实现计算的功能了,估计离不是黑框框的界面也不远了(离题ing...)

Part 1

话归正传,这一次的目的是在cmd(我的是windows)调用的方式来得出计算的表达式的值,所以,在函数功能基本完工之后,需要将main函数的参数进行调整,即 int main(int argc, char argv[]) ,因为我使用的是VS,所以一开始就加上main函数的参数,然后在项目属性-调试-参数直接输入要计算的表达式即可。

Part 2

然后就是Calculation类的实现啦

其实一看到这个,我第一感觉就是用前后缀表达式来做,但是仔细想想好像又有点不符合题目的要求:

“要求使用两个栈来处理(这里要使用):一个存放数值,一个存放符号。从队列中读取字符串,并分别放入对应的栈中。”

这一点似乎与后缀表达式的过程有点矛盾,后缀表达式的处理结果是某一个栈存放着顺序或逆序的后缀表达式,而不是一个栈存放着数值,另一个栈存放着符号。

拖着这么久没写的原因之一,是一直有个想法,根据后缀表达式的计算顺序,将后缀表达式拆分成数值和符号两部分,然后参照后缀表达式的计算思想扫描两个栈进行计算,但是....革命尚未成功,同志仍须努力...

所以最后还是暂时先用后缀表达式来计算。

OK,那么什么是后缀表达式呢?将将将将~

我们习惯上写的表达式,成为中缀表达式,如 (1 + 2) * 5 - 6

而前后缀表达式与之不同之处在于,运算符相对于操作数的位置不同:前缀表达式的运算符位于与其相关的操作数之后,后缀同理

如上式的前缀表达式:- * + 3 4 5 6

后缀表达式: 3 4 + 5 * 6 -

讲完了概念,接下来就是怎么转换和求值——参考博客

了解完前中后缀表达式,下面便是实现过程了,为了便于理解,version1.1.0(hah~)用了3个栈来操作

首先是Calculation.h头文件的主要部分


const int SIZE = 250;     // 表达式的最大长度(即q.size() <= SIZE)

class Calculation
{
public:
Calculation(void);
~Calculation(void);
void setPriorityLevel();
bool isOperator(string s);
bool isDigit(string s);
void toPostfixExpression(queue<string> q);
void calculatingExpression(queue<string> q, bool is_Exceed10);
public:
int priority[128]; // 存储运算符的优先级
stack<string> cacheStack; // 缓存栈
stack<string> digitStack; // 数字栈,包括处理与运算过程
stack<string> operatorStack; // 操作符栈,包括"+", "-", "*", "/", "(", ")"
};


对这一个类主要的函数 void toPostfixExpression(queue q); 以及 void calculatingExpression(queue q, bool is_Exceed10); 的解析:

toPostfixExpression函数是将传入的队列转换成后缀表达式,依照转换的思想并不难写出:解决了对数值、运算符(暂只支持+-*/)、左右括号的不同处理,其他再慢慢调整即可。


void Calculation::toPostfixExpression(queue<string>q)
{
string temp; // 记录传入的q队列的队首元素
while (!q.empty())
{
temp = q.front(); if (isDigit(temp)) // 若该队首元素是数字,则直接进栈
{
cacheStack.push(temp);
} else if (isOperator(temp)) // 若该队首元素为运算符,包括"+", "-", "*", "/"
{
if (operatorStack.empty()) // 若运算符栈为空,则直接进栈
{
operatorStack.push(temp);
}
else if (operatorStack.top() == "(") // 若运算符栈顶为左括号,则直接进栈
{
operatorStack.push(temp);
}
else if (priority[temp[0]] < priority[operatorStack.top()[0]]) // 若当前temp的优先级比栈顶运算符的优先级高,则直接进栈
{
operatorStack.push(temp);
}
else // 当不满足如上3种条件时,将操作符栈的栈顶元素push进缓存栈cacheStack之后出栈,重复操作直到操作符栈满足上述3种情况之一为止
{
cacheStack.push(operatorStack.top());
operatorStack.pop();
continue;
}
} else if (temp == "(") // 若该队首元素为"(",直接进栈
{
operatorStack.push(temp);
} else if (temp == ")") // 若该队首元素为")",弹出操作符栈栈顶元素直到遇见"(",将弹出元素push进缓存栈
{
while (!operatorStack.empty())
{
if (operatorStack.top() == "(")
{
operatorStack.pop();
break;
}
cacheStack.push(operatorStack.top());
operatorStack.pop();
}
} q.pop(); // 弹出队首元素,继续下一元素的判断
}
while (!operatorStack.empty()) // 将剩下的操作符压入缓存栈cacheStack
{
cacheStack.push(operatorStack.top());
operatorStack.pop();
}
}


calculatingExpression函数是计算部分,同样地,只要理解了计算思路:从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 op 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。 一步一步完成即可。

其中需要注意的一点是,计算时string->int,计算后int->string,应用基于的类型转换。


void Calculation::calculatingExpression(queue<string> q, bool is_Exceed10)
{
if (is_Exceed10) // 若超过10位数的数字存在为真
{
cout << "Input error ! Not exceeding 10 digits expected! " << endl;
return ;
} setPriorityLevel(); // 设置符号的优先级
toPostfixExpression(q); // 将中缀表达式转换为后缀表达式 /**********计算部分***********/
string postfixExp[SIZE]; // 由于计算时需对缓存栈从栈底到栈顶的逐一扫描,故用string数组进行遍历操作
int expLen = 0;
while (!cacheStack.empty())
{
postfixExp[expLen++] = cacheStack.top();
cacheStack.pop();
} stringstream stream; // 用stringstream流进行string和int的格式转换
for (int i = expLen - 1; i >= 0; i--)
{
if (isDigit(postfixExp[i])) // 若该元素为数字,则直接入数字栈digitStack
{
digitStack.push(postfixExp[i]);
}
else if (isOperator(postfixExp[i])) // 若该元素为运算符,则弹出数字栈的两个数进行相应的运算并将结果push进数字栈
{
int rightNum = 0; // 右操作数
if (!digitStack.empty())
{
stream << digitStack.top(); // 格式转换:string->int
stream >> rightNum;
digitStack.pop();
stream.clear(); // stringstream流的清空,以便重复利用
}
int leftNum = 0; // 左操作数
if (!digitStack.empty())
{
stream << digitStack.top(); // 格式转换:string->int
stream >> leftNum;
digitStack.pop();
stream.clear();
} string res; // res用于临时存储运算结果,push进数字栈
if (postfixExp[i] == "+") // 加法运算
{
stream << (leftNum + rightNum); // 格式转换:int->string
stream >> res;
stream.clear();
digitStack.push(res); // 运算结果进栈
res = "";
}
else if (postfixExp[i] == "-") // 减法运算
{
stream << (leftNum - rightNum); // 格式转换:int->string
stream >> res;
stream.clear();
digitStack.push(res); // 运算结果进栈
res = "";
}
else if (postfixExp[i] == "*") // 乘法运算
{
stream << (leftNum * rightNum); // 格式转换:int->string
stream >> res;
stream.clear();
digitStack.push(res); // 运算结果进栈
res = "";
}
else if (postfixExp[i] == "/") // 除法运算
{
stream << (leftNum / rightNum); // 格式转换:int->string
stream >> res;
stream.clear();
digitStack.push(res); // 运算结果进栈
res = "";
}
}
} cout << digitStack.top() << endl; // 此时,数字栈栈顶保存着最后一次运算的结果,即为表达式的值
digitStack.pop();
}


Part 3

轮到main()函数登场的时候了,没有挂在Part 1的原因,是想着把Calculation类挂完在写出main函数:


int main(int argc, char *argv[])
{
string input;
if (!strcmp(argv[1], "-a")) // 对传入的参数为"-a"的处理
{
input = argv[2];
}
else
{
input = argv[1];
} Scan *sc = new Scan();
Calculation *ca = new Calculation(); // 调用Scan类的ToStringQueue得到string队列
queue<string> qu = sc->ToStringQueue(input);
// 若传入参数"-a",则将表达式输出
if (strcmp(argv[1], "-a") == 0)
{
cout << input << "= ";
}
// 调用Calculation类的calculatingExpression得到表达式参数的运算结果
ca->calculatingExpression(qu, sc->getIsExceed10()); // 对象销毁
delete sc;
sc = NULL;
delete ca;
ca = NULL;
// system("pause");
return 0;
}


Part 4

在之前自己的代码上继续开发这种事情还是很因缺思厅的,这次的开发中,如Print类已然成为测试输出用的类,如修改Scan类使其返回队列中区分负数和'-'运算符,如修改注释...等等等等,不过最令人亦可赛艇的还是,纠结着要给自己的程序加上版本1.1呢,还是1.1.0呢(hah~)...

说的杂乱无章,指点多多的来~

参考资料:

The End

面向对象程序设计_Task4_Calculator1.1的更多相关文章

  1. 面向对象程序设计_Task5_Calculator1.5.0

    The 3rd part of the Calculator program _ FILE I/O 题目链接:第五次作业(计算器第三步) github链接:Calculator_1.5.0 第五次作业 ...

  2. [.net 面向对象程序设计深入](0) 开篇

    [.net 面向对象程序设计深入](0)开篇        [.net 面向对象编程基础]和 [.net 面向对象程序设计进阶]在15年底写完了,群里也加进来不少热爱学习的小伙伴.让我深切感受到在这个 ...

  3. [.net 面向对象程序设计进阶] (1) 开篇

    [.net 面向对象程序设计进阶] (1) 开篇 上一系列文章<.net 面向对象编程基础>写完后,很多小伙伴们希望我有时间再写一点进阶的文章,于是有了这个系列文章.这一系列的文章中, 对 ...

  4. [.net 面向对象程序设计深入](6).NET MVC 6 —— 模型、视图、控制器、路由等的基本操作

    [.net 面向对象程序设计深入](6).NET MVC 6 —— 模型.视图.控制器.路由等的基本操作 1. 使用Visual Studio 2015创建Web App (1)文件>新建> ...

  5. [.net 面向对象程序设计深入](5)MVC 6 —— 构建跨平台.NET开发环境(Windows/Mac OS X/Linux)

    [.net 面向对象程序设计深入](5)MVC 6 —— 构建跨平台.NET开发环境(Windows/Mac OS X/Linux) 1.关于跨平台 上篇中介绍了MVC的发展历程,说到ASP.NET ...

  6. [.net 面向对象程序设计深入](4)MVC 6 —— 谈谈MVC的版本变迁及新版本6.0发展方向

    [.net 面向对象程序设计深入](4)MVC 6 ——谈谈MVC的版本变迁及新版本6.0发展方向 1.关于MVC 在本篇中不再详细介绍MVC的基础概念,这些东西百度要比我写的全面多了,MVC从1.0 ...

  7. [.net 面向对象程序设计深入](3)UML——在Visual Studio 2013/2015中设计UML活动图

    [.net 面向对象程序设计深入](3)UML——在Visual Studio 2013/2015中设计UML活动图 1.活动图简介 定义:是阐明了业务用例实现的工作流程. 业务工作流程说明了业务为向 ...

  8. [.net 面向对象程序设计深入](2)UML——在Visual Studio 2013/2015中设计UML用例图

    [.net 面向对象程序设计深入](2)UML——在Visual Studio 2013/2015中设计UML用例图  1.用例图简介 定义:用例图主要用来描述“用户.需求.系统功能单元”之间的关系. ...

  9. [.net 面向对象程序设计深入](1)UML——在Visual Studio 2013/2015中设计UML类图

    [.net 面向对象程序设计深入](1)UML——在Visual Studio 2013/2015中设计UML类图 1.UML简介 Unified Modeling Language (UML)又称统 ...

随机推荐

  1. [CQOI 2018]异或序列&[Codeforces 617E]XOR and Favorite Number

    Description 题库链接1 题库链接2 已知一个长度为 \(n\) 的整数数列 \(a_1,a_2,\cdots,a_n\) ,给定查询参数 \(l,r\) ,问在 \([l,r]\) 区间内 ...

  2. python队列、线程、进程、协程

    目录: 一.queue 二.线程 基本使用 线程锁 自定义线程池 生产者消费者模型(队列) 三.进程 基本使用 进程锁 进程数据共享 默认数据不共享 queues array Manager.dict ...

  3. HTTP2 帧基础知识以及Header、CONTINUATION、DATA帧相关资料:

    HTTP2于2015年2月28日正式通过IETF组织批准发布,正式定稿.有关它的内容可以参考:  HTTP2 概述  http://www.cnblogs.com/ghj1976/p/4552583. ...

  4. 《Programming iOS 7》读书笔记 - 窗体大小与状态栏

    1.从iOS7开始,状态栏开始变为透明的,根识图占领了整个窗体,包括状态栏后面的20像素高的区域,这种样式无法被改变.iOS6的状态栏是不透明的,窗体的尺寸通常会比屏幕的尺寸要小,可以设置窗体的尺寸为 ...

  5. 郭霖 - MVVM开源项目学习

    https://blog.csdn.net/guolin_blog/article/details/87900605 https://medium.com/androiddevelopers/view ...

  6. .NET异常处理的动作策略(Action Policy)

    SQL Server 2008基于策略的管理,基于策略的管理(Policy Based Management),使DBA们可以制定管理策略,并将这些策略应用到服务器.数据库以及数据环境中的其他对象上去 ...

  7. Spring+SpringMVC+SpringDataJpa整合

    一.思路: (一) Dao层与Service层: applicationContext.xml. a) 数据库连接池 b) 整合jpa c) 配置@service文件扫描器. d) 配置事务管理管理器 ...

  8. IDEA使用总结1-Github下载代码和上传代码到Git

    1. 首先你需要在IDEA中创建一个项目,创建完项目后使能版本管理插件 选择git后创建本地git仓库成功,提示如下 2.第二步 commit代码到 commit时会提示是否需要进行检查什么的 3.第 ...

  9. Git 学习之git 分支(三)

    Git 分支 几乎每一种版本控制系统都以某种形式支持分支.使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作.在很多版本控制系统中,这是个昂贵的过程,常常需要创建一个源代码目录的 ...

  10. Git 学习之git 起步(一)

    起步 本章介绍开始使用 Git 前的相关知识.我们会先了解一些版本控制工具的历史背景,然后试着让 Git 在你的系统上跑起来,直到最后配置好,可以正常开始开发工作.读完本章,你就会明白为什么 Git ...