第四次作业,还没开始就要deadline了,我的内心嘿嘿嘿嘿嘿。
第一步尝试 计算 优先级的实现
计算器的计算部分最难实现的是优先级的计算。之前完成计算器时查阅过有关资料,了解到需要栈的使用以及前缀后缀中缀表达式的转换。
最开始时我计划将中缀表达式转换为前缀表达式,再对前缀表达式进行求值。着手时发现转换过程需要从右至左扫描中缀表达式(即逆序),而之前的作业中将表达式拆分时运用queue,对于这种先进先出的结构,无法通过两个队列间的转换完成逆序,所以我选择将中缀表达式转换为后缀表达式再求值。
step one: 将中缀表达式转换为后缀表达式
- 初始化两个栈,运算符栈signS,储存中间结果的栈numS。
- 从左至右扫描中缀表达式。(从队首开始扫描)
2-1. 遇到操作数,将其压入numS。
2-2. 遇到括号
2-2-1. 若为"(",直接压入signS。
2-2-2. 为")",依次弹出signS栈顶运算符并压入numS,直到遇到"(",将这对括号舍弃。
2-3. 遇到运算符,比较其与signS栈顶运算符的优先级。
2-3-1. signS为空或signS栈顶为"("或该运算符的优先级比signS栈顶元素高,该运算符压入signS。
2-3-2. 其余情况,将signS栈顶元素弹出并压入numS,回到2-2继续比较。- 将signS中元素依次弹出并压入numS。
- 此时numS中为后缀表达式的逆序情况.将numS中元素依次弹出并压入signS,signS中即为后缀表达式的正确打开方式。(依赖于栈的操作出栈入栈都对栈顶元素操作完成此逆序过程)。
step two: 后缀表达式求值
- 1 从左至右扫描signS,将数字压入numS。
- 2 遇到运算符时,弹出numS栈顶的两个数,用运算符对他们做相应的计算,将运算结果压入numS。
- 3 最后numS中的一个元素即位所求的值。
代码贴
#include"Calculation.h"
#include<sstream>
#define SIGNNUM 6
#include<iostream>
#include<ctype.h>
using namespace std;
string priority[SIGNNUM][2]={{"(","5"},{")","5"},{"+","1"},{"-","1"},{"*","2"},{"/","2"}}; // 储存运算符优先级数组
/*返回运算符在优先级数组中的位置,方便优先级比较*/
int Calculation::comPri(string input)
{
for(int j = 0; j < SIGNNUM; j++)
{
if(input == priority[j][0])
{
return j;
}
}
}
/*将中缀表达式转换为后缀表达式*/
void Calculation::toPrefix(queue<string> q)
{
string last;
int size = q.size();
numS.push("0");
for(int i = 0;i < size; i++)
{
if( isdigit(q.front()[0]) || (q.front()[0] == '-' && isdigit(q.front()[1])) )
{
numS.push(q.front());
}
else if(q.front() == "(" )
{
signS.push(q.front());
}
else if(q.front() == ")" )
{
while(signS.top()!="(")
{
numS.push(signS.top());
signS.pop();
}
signS.pop();
}
else if(q.front() == "+" || q.front() == "-" || q.front() == "*" || q.front() == "/")
{
while(1)
{
if(signS.empty() || signS.top() == "(" || priority[comPri(q.front())][1] > priority[comPri(signS.top())][1])
{
signS.push(q.front());
break;
}
else
{
numS.push(signS.top());
signS.pop();
}
}
}
last = q.front();
q.pop();
}
while(!signS.empty())
{
numS.push(signS.top());
signS.pop();
}
while(!numS.empty())
{
signS.push(numS.top());
numS.pop();
}
}
/*后缀表达式求值*/
void Calculation::Calculate()
{
stringstream stream;
double num1;
double num2;
double res;
string result;
int size = signS.size();
for(int i = 0; i < size; i++)
{
if(isdigit(signS.top()[0]) || (signS.top()[0] == '-' && isdigit(signS.top()[1])))
{
numS.push(signS.top());
}
else if(signS.top() == "+" || signS.top() == "-" || signS.top() == "*" || signS.top() == "/")
{
stream << numS.top();
stream >> num1;
stream.clear();
numS.pop();
stream << numS.top();
stream >> num2;
stream.clear();
numS.pop();
if(signS.top() == "+")
{
res = num1 + num2;
}
else if(signS.top() == "-")
{
res = num2 - num1;
}
else if(signS.top() == "*")
{
res = num1 * num2;
}
else if(signS.top() == "/")
{
res = num2 / num1;
}
stream << res;
stream >> result;
stream.clear();
numS.push(result);
}
signS.pop();
}
stream << numS.top();
stream >> res;
cout << res;
stream.clear();
}
第二部分的完成包括了了解
,使用stringstream对象简化类型转换。 样例与注意点:
基本数据类型转换例子 int转string
#include <string>
#include <sstream>
#include <iostream>
int main()
{
std::stringstream stream;
std::string result;
int i = 1000;
stream << i; //将int输入流
stream >> result; //从stream中抽取前面插入的int值
std::cout << result << std::endl; // print the string "1000"
}
重复利用stringstream对象
多次转换中使用同一个stringstream对象,记住每次转换前要使用clear()方法。
在多次转换中重复使用同一个stringstream(而不是每次都创建一个新的对象)对象最大的好处在于效率。stringstream对象的构造和析构函数通常是非常耗费CPU时间的。
以上两个部分粗略完成时并没有太大问题,大致思路完成后,一个bug出现了,对"-"的判断。依照之前将表达式拆解存入队列思路,数字与符号显性分开,而"-"可能是减号也可能是负号,目前解决方案为判断"-"的意义,若"-"前并没有符号或"-"前为"(",则判定为符号,先往栈中压入"0"做减法运算得到负数.目前还在调试中,调试是对思维的挑战。(该过程已修正)
修正bug过程,修正题目中超过十位报错的处理,在Calculation类中新添getResult方法,使得计算过程仅需调用getResult一个方法。
/*计算表达式*/
void Calculation::getResult(queue<string> q)
{
Calculation::toPrefix(q);
Calculation::Calculate();
}
4.14 修正bug与更新 在付少航姐的脑洞下,考虑如3 * -6 2(3+5)也为合理情况,修改Scan类中拆分表达式的方法,将判断负数过程合并到拆分表达式过程,同时加入对后种情况的判定,补入乘号变为2*(3+5)。
更新一些判断,输入表达式要求()括号匹配,除数要求不为0,否则拒绝计算。
还有很多情况是我没有考虑到或考虑到还没实现的(逃 ,希望一步步能做的更好,有所进步。
第二步尝试 在命令行中调用编译好的可执行文件
我的理解,c++是面向对象的语言,重要的是对象和对象能调用的方法,而不需要知道方法的具体实现,所以我的主函数只有对对象的创建及调用对象的相应方法。我对自己这部分的理解还存有疑惑,希望老师大神予以解答。
在Scan类中添加judge方法进行判断命令行输入的表达式,将需要进行计算的表达式传给拆分表达式的方法。(已修正,在main函数里处理argc和argv)
同时为了解决传入-a等参数时输出表达式的问题,在Scan类中添加bool类型isPrint,并传给输出表达式的函数。
int main(int argc, char* argv[])
{
string input; //接收键盘输入的表达式
Scan sc;
//cin>>input;
if(argc == 2)
{
input = argv[1];
}
else if(argc > 2)
{
sc.isPrint = true;
input = argv [argc-1];
}
queue<string> q = sc.toStringQueue(input); //存放获取的队列
Print pr;
pr.printQue(q);
Calculation cal;
cal.getResult(q);
return 0;
}