这几天看了栈的结构,编写一个计算器小程序作为练习。
这个程序只能处理加法和乘法,只能输入整数,可以处理多重括号
编写过程中,感觉最有点混乱,主要是编程前期设计不够,边想边完成,导致结构和逻辑总是不清晰
中途重新设计了一遍,感觉清晰了一些,但是看着很复杂,不是特别优美。
在中缀表达式转后缀表达式的函数中,还增加了很多表达式形式是否正确的判断,大家可以测试一下还有没有未考虑到的情况,欢迎大家给我留言
这里先记录一下,然后后面再改进。
欢迎大家给我提意见和建议
/*******************************************
* 简易计算器程序(练习栈的应用)
* 功能:能计算 加法 和 乘法
* 可以使用括号
* 待改进的地方: 只能处理整数
没有除法和加法的运算
* 实现过程:用户输入 -> 中缀表达式转换为后缀表达式
-> 使用后缀表达式计算
-> 返回结果
* 重点部分: 主要集中在中缀表达式转换为后缀表达式
的函数infix2postfix中,包括如下:
* 如何从string中分离数字和操作符
* 如何保证表达式语法正确
* 对不正确的表达式返回原因并停机
* 作者:FastestSnail
* 时间:2016年12月
* 转载请注明出处
*******************************************/
#include<vector>
#include<string>
#include<sstream>
#include<iostream>
#include<iterator>
#include<algorithm>
using namespace std;
int cal(const string & s); //用于计算输入的表达式
const string infix2postfix(const string &s); // 把中缀表达式变成后缀表达式
void errQuit(const string &msg); // 用于输出错误信息,这些错误无法再被处理,直接停机
int main()
{
string source;
cout << endl;
cout << " 请输入需要计算的表达式: ";
cin >> source;
cout << " ---------------------------------------" << endl;
cout << " 你输入的是: " << source << endl;
cout << " ---------------------------------------" << endl;
cout << " 结果为: " << cal(source) << endl;
cout << endl;
getchar();
return 0;
}
void errQuit(const string &msg)
{
cerr << "******\a" << msg << '\n'<< endl;
exit(-1);
}
int cal(const string & s)
{
string postfix(infix2postfix(s));
cout << " 后缀表达式为:";
cout << postfix << endl;
cout << " ---------------------------------------" << endl;
vector<int> numStack; // 用来存放数字的栈
int letfNum = 0; // 用来存放左操作数
int rightNum = 0; // 用来存放右操作数
int result = 0; // 用来存放结果
istringstream in(postfix);// 将后缀表达式变成流,然后通过每次读取一个字符来构成数字或者操作符
string tmpNum="";
char tmp;
while ((tmp = in.get()) != -1) // 调试的时候发现,如果直接用in>>tmp,会吃掉中间的空格,
// 因此改用get函数,当结束时,返回-1(调试的时候发现的)
{
if (tmp >= '0' && tmp <= '9') // 如果是数字,则不断的累积在tmpNum
{
tmpNum += tmp;
}
else if (tmp == ' ') // 如果为空格,则有两种情况。
{
// 第一种情况是空格在数字后面(说明此时tmpNum不为空串)
// 则读取出这个数字,放入栈中
// 清空tmpNum内容
if (!tmpNum.empty())
{
istringstream ss(tmpNum);
int castedNum;
ss >> castedNum;
// 以上为C++中一种string转int的方法
numStack.push_back(castedNum);
tmpNum.clear();
}
else // 第二种情况,此时没有数字需要读取
continue;
}
else if (tmp == '*' || tmp == '+' ) // 根据相应的符号进行计算
{
letfNum = numStack.back();
numStack.pop_back();
rightNum = numStack.back();
numStack.pop_back();
if (tmp == '*')
result = letfNum * rightNum;
else
result = letfNum + rightNum;
numStack.push_back(result);
}
else
{
errQuit("计算过程中出现错误!");
}
} // while结束,说明此时获得的后缀表达式读取完成,这个时候result的值就是最终的结果了
return result;
}
const string infix2postfix(const string &s)
{
string result(""); // 输出结果
vector<char> stackOp; // 存放操作符的栈
// 用于判断左括号出现的位置是否适宜
// 左括号出现必须前面是操作符,或者栈空的情况
// 因此在输入操作符的时候,afterOp 为真
// 输入数字后,afterOp为假
bool afterOp = false;
// 用于判断除了左括号以外的其他符号的位置
// 只有在数字后面才能添加其他操作符
// 在数字后面则为真,否则为假
bool afterNum = false;
// 用于判断是否在右括号后面,右括号后面必须为操作符
// 在有括号后面则为真,否则为假
bool afterRightBracket = false;
istringstream in(s); // 把输入的string按流的形式处理
char tmp; // 用于临时存放输入的字符
while (in >> tmp)
{
if ((tmp >= '0' && tmp <= '9')) // 判断是否为数字,如果为数字,就直接放在输出
{
if (afterRightBracket) errQuit("右括号后必须为操作符,不能为数字!");
// 设置相应的状态变量
afterOp = false;
afterNum = true;
afterRightBracket = false;
result += tmp; // 输出结果到变量
} // 处理数字的部分结束,循环进入下一次,开始其他输入(包括正确的符号和非法的符号)
else // 处理符号
{
if (tmp == ')')
{
if (stackOp.empty())
errQuit("表达式不完整!"); // 防止用户输入(类似于): 1)
}
if ( tmp == '+' || tmp == '*' )
{
if (result.empty()) // 如果是符号,但是结果的长度为空,说明还没有输入操作符就输入操作数了,非法!
errQuit("输入错误!数据在操作符之前输入了"); // 防止用户输入(类似于): +1
}
if (stackOp.empty() && (tmp == '(' || tmp == '+' || tmp == '*' ) ) //处理符号栈空的情况
{
if (tmp == '(')
{
if (!afterOp && !result.empty())
errQuit("左括号输入位置出错!");// 防止用户输入(类似于): 1(2+3)
}
stackOp.push_back(tmp);
result += ' '; // 在输出中补充空白,以便分辨不同的数字和操作符
// 设置相应的状态变量
afterOp = true;
afterNum = false;
afterRightBracket = false;
continue;
}
// 以上三个if是对特殊初始情况的判断
// 判断 每个不同的符号,进行相应的操作
if (tmp == '(')
{
if (!afterOp) errQuit("左括号输入位置出错!");
stackOp.push_back(tmp);
// 设置相应的状态变量
afterOp = true;
afterNum = false;
afterRightBracket = false;
}
else if (tmp == ')')
{
if (!afterNum && !afterRightBracket) errQuit("右括号输入位置出错!");
char pop = stackOp.back();
while ( pop != '(') // 获得右括号,在左括号之前的操作符都要全部退栈
{
stackOp.pop_back();
result += ' ';
result += pop;
if (stackOp.empty()) break;
pop = stackOp.back();
}
if (pop == '(') // 如果跳出循环是因为左括号,则正确,左括号退栈,否则括号不匹配
{
stackOp.pop_back();
// 设置相应的状态变量
afterOp = true;
afterNum = false;
afterRightBracket = true;
}
else
errQuit("括号不匹配,缺少左括号");
}
else if (tmp == '*') // 处理输入为乘号的情况
{
if (!afterNum && !afterRightBracket) errQuit("乘号* 位置错误!");//乘号必须出现在数字或右括号之后
char pop = stackOp.back();
if (pop == '*') //因为乘号在这里是优先级最高的,因此栈内只可能出现一次乘号,所以用if
{
stackOp.pop_back();
result += ' ';
result += pop;
result += ' ';
}
else // 如果不是乘号,但是给结果放入一个空格,从而分开各个数
{
result += ' ';
}
stackOp.push_back(tmp); // 把这次的操作符放入栈内
// 设置相应的状态变量
afterOp = true;
afterNum = false;
afterRightBracket = false;
}
else if (tmp == '+')
{
if (!afterNum && !afterRightBracket) errQuit("加号+ 位置错误!"); //加号必须出现在数字或右括号之后
char pop = stackOp.back();
while (pop == '*' || pop == '+')
{
stackOp.pop_back();
result += ' ';
result += pop;
result += ' ';
if (stackOp.empty()) break;
pop = stackOp.back();
}
if (pop == '(')
{
result += ' ';
}
stackOp.push_back(tmp);
// 设置相应的状态变量
afterOp = true;
afterNum = false;
afterRightBracket = false;
}
else // 剩下的其他输入都为非法
errQuit("输入错误!");
} // 处理符号的部分结束
} // while 结束
// 弹出符号栈中剩余的操作符到输出变量中
while (!stackOp.empty())
{
char pop = stackOp.back();
if (pop == '(') errQuit("括号不匹配,缺少右括号");
else
{
result += ' ';
result += stackOp.back();
stackOp.pop_back();
}
}
return result;
}