第四次作业(计算器第二步)
计算器第二步
题目链接:
下面按照解决的顺序一 一记录说明
1.命令行和main函数的参数
题目的第一点的要求说要在命令行(如windows下的cmd)里调用编译好的程序,而不是在编译器里面运行。那个命令行的窗口长得和运行窗口好像,一开始没看懂是什么意思。命令行什么的,百度一下也都有,大概了解了一下,这里不再多说,主要是会把命令行窗口打开(windows图标键+R)。然后我照着题目里面的截图测试了一下:
好像都和题目里的截图不一样,但是感觉不是什么大问题,就这样子好了(别人的博客有提到说,将main函数写成提示的样子才不用换行输参数,但是我也有加啊,迷之错误)。
然后第一点下面有个提示:int main(int argc, char* argv[]),百度了一下(链接):第一个表示参数的个数;第二个参数中argv[0]为自身运行目录路径和程序名,argv[1]指向第一个参数、argv[2]指向第二个参数、等等,然后main函数的参数值是从操作系统命令行上获得的。当我们要运行一个可执行文件时,在DOS提示符下键入文件名,再输入实际参数即可把这些实参传送到main的形参中去。下面来举例说明,用来说明的代码和结果如下:
#include<stdlib.h>
#include<iostream>
using namespace std;
int main(int argc, char* argv[])
{
int i=argc;
cout<<argc<<endl;
while(i>=0)
{
cout<<argv[argc-i]<<endl;
i--;
}
system("pause");
return 0;
}
2.优先级 栈以及sstream
优先级问题也就是把运算符和数字分存到两个栈之后要怎么保证原来的运算顺序,关于这个问题是百度了好久,最后感觉勉强能够理解的是这个(链接)(别人用到的什么前缀中缀后缀当初好像也有看到过,但是没有去弄清楚。。。而是选择了这个)。大概是用一个数组来判断优先级,还在接收的队列两边加上#用于判断结束什么的。大概的框架有了之后,就又去百度了栈和sstream。然后在先不考虑负数,参数-a等细节问题下,先开始了较为粗糙的代码的尝试。
Calculation
这是比较能看的代码,前面全部都写在类里面,写了好长好长。有个if和else隔了好远,编译的时候有个错误就说else前面没有if,但是缩进明明没有错。。。还有各种各样的错误,比如说原来switch条件里要是int类型,string类型即使长度为一也不能和单个字符比较,赋值(像把 # push进去要双引号才能编译通过)。。。为了编译能够通过也是折腾了好久。
class Calculation
{
public:
void getStringQueue(queue<string>q)
{
//把'#'压入接收的队列的队尾
q.push("#");
//把'#'压入字符栈顶
characterstack.push("#");
stringstream stream;
string tempstr;
int n=q.size(),tempnum;
//遍历队列,用两个栈计算算式
for(int i=0; i<n; i++)
{
tempstr=q.front();
q.pop();
//对队列中的字符的操作
if(tempstr=="+"||tempstr=="-"||tempstr=="*"||tempstr=="/"||tempstr=="("||tempstr==")"||tempstr=="#")
{
calculate(position(characterstack.top() ,tempstr),tempstr);
}
//对队列中的数字的操作
else
{
//利用sstream将字符串转化为数字
//压入数字栈中
stream<<tempstr;
stream>>tempnum;
numberstack.push(tempnum);
}
}
}
};
position
这是用来判断当前的字符在优先级数组中的位置的,一个参数是运算符栈栈顶的字符,另一个是当前扫描到的算式中运算符。
char position(string characterstack,string tempstr )
{
int x,y;
switch(characterstack[0])
{
case '+':
x=0;
break;
case'-':
x=1;
break;
case'*':
x=2;
break;
case'/':
x=3;
break;
case'(':
x=4;
break;
case')':
x=5;
break;
case'#':
x=6;
break;
}
switch(tempstr[0])
{
case'+':
y=0;
break;
case'-':
y=1;
break;
case'*':
y=2;
break;
case'/':
y=3;
break;
case'(':
y=4;
break;
case')':
y=5;
break;
case'#':
y=6;
break;
}
return judge[x][y];
}
calculate
这个就是主要的计算过程了,优先级什么都有数组来判断了,其实就是情况多了一点。
void calculate(char judge,string tempstr)
{
int num,num1,num2,tempnum;
//栈顶字符优先于算式字符时
//弹出数字栈首的两个元素
//用字符栈顶的字符计算出结果
//将结果压入数字栈顶
switch(judge)
{
case'>':
num1=numberstack.top();
numberstack.pop();
num2=numberstack.top();
numberstack.pop();
switch(characterstack.top()[0])
{
case'+':
num=num1+num2;
numberstack.push(num);
characterstack.pop();
break;
case'-':
num=num2-num1;
numberstack.push(num);
characterstack.pop();
break;
case'*':
num=num1*num2;
numberstack.push(num);
characterstack.pop();
break;
case'/':
num=num2/num1;
numberstack.push(num);
characterstack.pop();
break;
}
break;
//当算式字符优先于栈顶字符时
//直接将算式字符压入字符栈顶
case'<' :
characterstack.push(tempstr);
break;
//当栈顶字符和算式字符优先级一样时
case'=':
//当算式字符为'#'时
//表示遍历结束
//输出数字栈顶的结果
//并将两个栈清空
if(tempstr[0]=='#')
{
cout<<numberstack.top()<<endl;
numberstack.pop();
characterstack.pop();
}
//当栈顶字符为'('算式字符为')'时
//弹出栈顶字符
else
{
characterstack.pop();
}
break;
//当优先级数组的元素为0时
//说明输入的算式出现了不合法的情况,括号不匹配之类的
default:
cout<<"wrong input"<<endl;
system("pause");
}
}
judge
说明什么的在calculation都有提到,演算一下会好理解。
//优先级判断数组
char judge[7][7]= {{'>','>','<','<','<','>','>'},
{'>','>','<','<','<','>','>'},
{'>','>','>','>','<','>','>'},
{'>','>','>','>','<','>','>'},
{'<','<','<','<','<','=','0'},
{'>','>','>','>','0','>','>'},
{'<','<','<','<','<','0','='}
};
好不容易编译通过的代码,随便来了一个测试样例,结果是这样的:
完全没有任何输出。。。模拟了一下发现最后的#只和+比了一下之后就没比下去了,也就不会出现#对#的输出情况,所以我在calculation的>的情况里又加了下面的代码,让它一直比较下去。
while(!characterstack.empty())
{
calculate(position(characterstack.top() ,tempstr),tempstr);
}
但是测试结果是这样的:
加了一些输出来找bug:
后来发现是stream每次都要清空,就加上了'stream.clear()',结果是正确的:
然后又试了试两个数的减法什么的,发现代码里面的减法和除法都写反了。。。
接下来又试了试三个数的加法,结果是停止运行。。。
看来还是while的那个语句有问题,第二个+被push进去之后还会一直执行,然后我又折腾调试了一阵,加了个条件,进栈之后就不会再比较下去了。
然后在不加括号的情况下,基本上都没有问题了。
接下来我又随便输入了(1+2)*3,结果是停不下来的wrong input。。。发现还是跟刚才的while语句有关,一直在调用,所以在judge出现=的第二种情况(就是栈顶是左括号,扫到的是右括号),一样加个条件判断一下,就不会一直调用了。
这样正整数四则运算是差不多了的感觉:
接下来主要是负数的判断这个问题,然后代码也要再改的规范一点(话说上次附加的规范作业我没去改来着。。。),最后再把完善的代码传到github好了。
还有一件事是写博客真的花了好长的时间,是不是太啰嗦了点。。。(2016.04.09)
3.负数的判断以及参数-a
算式里面合法的负号就只有开头的负数不用加上括号吧,那需要考虑的就只有两种情况。比起在存到栈里的时候再去考虑,觉得在Scan类里的拆分数字和符号的时候考虑负数比较方便(毕竟calculation类里的代码已经这么长了),代码也没有很复杂,调试几次就差不多了(这是scan类里面的函数):
queue<string> ToStringQueue(string input)
{
//第二次题目要求数位不超过十位(包括小数位)
//这是用来记录数位的变量
int cnt=0;
queue<string>q;
string str;
//遍历传进来的字符串
for(int i=0; i<input.size(); i++)
{
//遍历到运算符或是符号
if(input[i] == '+'||input[i] == '-'||input[i] == '*'||input[i] == '/'||input[i] == '('||input[i] == ')')
{
//把碰到运算符之前接起来的数字符压入队列中
if(!str.empty())
{
q.push(str);
}
//特判第一个数字是负数的情况
//不入栈,准备与数字符接在一起
if(input[i]=='-'&&i==0)
{
str=str+input[i];
}
//算式中间出现合法的负数的情况
//不入栈,准备与数字符·接在一起
else if(input[i]=='-'&&q.back()=="(")
{
str=str+input[i];
}
//其他运算符直接入栈,并把中间变量置空
else
{
cnt=0;
str=input[i];
q.push(str);
str.clear();
}
}
//遍历到数字符
else
{
//把相邻数字符接起来
str=str+input[i];
//记录位数
if(input[i]!='.')
{
cnt++;
}
//位数报错
if(cnt==11)
{
cout<<"wrong input"<<endl;
system("pause");
}
//最后是数字符的话,直接入栈
//原来都是碰到运算符才入栈
if(i==input.size()-1)
{
q.push(str);
}
}
}
return q;
}
用第二次作业的代码测试了一下:
应该没有忽略什么情况吧,这样子在calculaton类中遍历接收的队列元素时,负号和减号就已经在不同的元素里了。于是就去测试了一下考虑了负数的calculation:
关于参数-a的问题,感觉也不是什么大问题,只要判断输入的字符串是不是"-a",是的话就再输入一个字符串,并且打印出来。但是却意外地调试了很久,后来发现是输入字符串用了getline,碰到空格并不会分成两个字符串,所以输出的一直是0。换成用cin输入就愉快的解决了参数-a这个问题:
(2016 04 12)
4.后记
打到这里估计是差不多了,最后还剩下一些收尾的工作,然后这是:GITHUB的链接
还有一件事是,终于可以不用换行输参数了。定义完字符串之后,直接初始化为argv[argc-1],不用再用cin输入。
5.修正:
1.swith优化问题:实际去打才发现自己并不会用swith,然后编译通过之后就是谜一般的停止运行,后来发现是没用命令行来调用。
2.关于string和char:百度了一下,发现string是类,而不是数据类型,不能像int,char那样直接加减。所以说就算string的长度为一,也不能直接和char进行赋值比较。
3.关于负号和减号的问题:加了个条件来特判开头是-(-...)的情况,但是右括号后面再输入其他的东西就会停止运行,找了半天,最后发现是改代码的时候复制黏贴漏了点东西。。。最后结果如下图
4.关于输出只有整数部分的问题:把计算过程中用到的变量改成double就好,试了一下发现cout有几位小数就输几位,不像%lf那样不足六位会补零,以下是运行结果:
5.关于优先级判断数组:行从上到下,列从左到右都一样,都是依次代表+ - * / ( ) #
6.把类拆成.cpp实现.h声明:链接.