天天看点

C++版 逆波兰式 解析四则运算字符串

#include <stack>
#include <string>
#include <memory>
using namespace std;

/*本工具提供四则运算解析功能

	使用时,引入本文件即可。
	通过调用 Chion::Math::ParsStr("100+200*3/(54+89)");即可得到结果
	注意:如果输入的字符串非法,则返回结果是一个空指针。

	如果你对算法有兴趣,那么下面的说明可以了解一下:
	我对输入的原始的字符串称为源字符串SourceString
	对于从SourceString解析下来的一个元素称为SourceElement
		SourceElement有可能是一个数字,一个运算符,一个左括号,一个右括号或者为空。
		两个SourceElement如果都是运算符,可以进行优先级比较:
			一种方法是调用SourceElement1.Compare(SourceElement2)
			另一种方法是用减法运算 SourceElement1 -SourceElement2
			根据结果来判断其大小:
				1:SourceElement1大于SourceElement2 
				0:SourceElement1等于SourceElement2 
				-1:SourceElement1小于SourceElement2
				-2:错误,说明两个中存在不是运算符的情况
	通过SourceString::GetNextObject()可以不断解析到新的元素。直到SourceElement为空,表示结束。

	算法如下(算法思路摘自百度百科):
	首先需要分配2个栈,一个作为临时存储运算符的栈S1,一个作为输入逆波兰式的栈S2(空栈),从中缀式的左端开始取字符,逐序进行如下步骤:
	(1)若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈
	(2)若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,如果该运算符优先级(不包括括号运算符)大于S1栈栈顶运算符优先级,则将该运算符进S1栈,否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符低于(不包括等于)该运算符优先级,最后将该运算符送入S1栈。
	(3)若取出的字符是“(”,则直接送入S1栈顶。
	(4)若取出的字符是“)”,则将距离S1栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入S2栈,此时抛弃“(”。
	(5)重复上面的1~4步,直至处理完所有的输入字符
	(6)当所有字符处理完之后,如果S1中还有数据,则逐个出栈,依次送入S2栈。
	完成以上步骤,S2栈便为逆波兰式输出结果。不过S2应做一下逆序处理。便可以按照逆波兰式的计算方法计算了!
*/

namespace Chion{
	/*枚举了元素对象可能存在的情况*/
	typedef enum{
		TYPE_EMPTY,			//空
		TYPE_NUMBER,		//数字
		TYPE_OPERATOR,		//运算符
		TYPE_BRACKETS_L,	//左括号
		TYPE_BRACKETS_R,	//右括号
	}ENUM_OBJECT_TYPE;

	/*元素对象,它表示从源字符串上解析出来的一个单元*/
	struct SourceElement
	{
		/*构造一个空的对象*/
		SourceElement(){
			m_type = TYPE_EMPTY;
			m_value = 0.0;
			m_op = '\0';
		}

		/*构造一个括号对象,如果参数有误,则构造一个空对象*/
		SourceElement(ENUM_OBJECT_TYPE type){
			m_value = 0.0;
			m_op = '\0';
			switch (type)
			{
			case TYPE_BRACKETS_L:
			case TYPE_BRACKETS_R:
				m_type = type;
				break;
			case TYPE_EMPTY:
			case TYPE_NUMBER:
			case TYPE_OPERATOR:
			default:
				m_type = TYPE_EMPTY;
				break;
			}
		}

		/*构造一个操作符对象,如果参数有误,则构造一个空对象。参数取值只能是'+'、'-'、'*'、'/'*/
		SourceElement(char op){
			if (op == '+'
				||
				op == '-'
				||
				op == '*'
				||
				op == '/'
				)
			{
				m_type = TYPE_OPERATOR;
				m_op = op;
			}
			else
			{
				m_type = TYPE_EMPTY;
				m_op = '\0';
			}
			m_value = 0.0;
		}

		/*构造一个数字对象*/
		SourceElement(double value)
		{
			m_type = TYPE_NUMBER;
			m_value = value;
			m_op = '\0';
		}

		ENUM_OBJECT_TYPE type() const{
			return m_type;
		}

		/*当类型为数字时,返回数值。其它类型时,返回0,此时0无意义。*/
		double value(){
			if (m_type == TYPE_NUMBER)
			{
				return m_value;
			}
			else
			{
				return 0.0;
			}
		}

		/*当类型为操作符时,返回操作符'+' '-' '*' '/'。其它类型时,返回'\0'*/
		char op(){
			if (m_type == TYPE_OPERATOR)
			{
				return m_op;
			}
			else
			{
				return '\0';
			}

		}

		int Compare(const SourceElement& obj)
		{
			if (this->m_type != ENUM_OBJECT_TYPE::TYPE_OPERATOR
				||
				obj.type() != ENUM_OBJECT_TYPE::TYPE_OPERATOR)
			{
				return -2;
			}
			else
			{
				if (this->m_op == '*' || this->m_op == '/')
				{
					if (obj.m_op == '*' || obj.m_op == '/')
					{
						return 0;
					}
					else
					{
						return 1;
					}
				}
				else
				{
					if (obj.m_op == '*' || obj.m_op == '/')
					{
						return -1;
					}
					else
					{
						return 0;
					}
				}

			}

		}

		int operator-(const SourceElement &obj){
			if (this->m_type != ENUM_OBJECT_TYPE::TYPE_OPERATOR
				||
				obj.type() != ENUM_OBJECT_TYPE::TYPE_OPERATOR)
			{
				return -2;
			}
			else
			{
				if (this->m_op == '*' || this->m_op == '/')
				{
					if (obj.m_op == '*' || obj.m_op == '/')
					{
						return 0;
					}
					else
					{
						return 1;
					}
				}
				else
				{
					if (obj.m_op == '*' || obj.m_op == '/')
					{
						return -1;
					}
					else
					{
						return 1;
					}
				}

			}
		}
	private:
		ENUM_OBJECT_TYPE m_type;
		double m_value;
		char m_op;
	};

	/*源字符串的封装对象,封装对源字符串的一些操作*/
	struct SourceString{
		SourceString(const string &str)
			:m_pos(0){
			//只保留四则运算中的有用字符
			for (auto &e : str)
			{
				if (
					(e >= '0' && e <= '9')
					||
					e == '.'
					||
					e == '+' || e == '-' || e == '*' || e == '/'
					||
					e == '(' || e == ')'
					)
				{
					m_str += e;
				}
			}
		}

		/*
			从m_pos位置开始,在源字符串上解析出一个元素,解析完毕,m_pos指向元素下一个位置
			如果解析失败,m_pos已经超出字符串范围,返回一个type为空的对象。
		*/
		SourceElement GetNextObject()
		{
			//解析到了尽头,无法再继续解析,返回空对象
			if (m_pos >= m_str.size())
			{
				return SourceElement();
			}

			//获取一个字符
			char c = m_str[m_pos];
			switch (c)
			{
				//运算符号,合法(游标可进位)
			case '+':
			case '*':
			case '/':
				m_pos++;
				return SourceElement(c);
			/*
				如果遇到减号,需要独立判断,看这是一个运算符,还是属于数字前面的负号
				当它满足两个条件时,该减号为负号:
					1.它的后面是数字
					2.它的前面是加减乘除或者左括号或者前面没有其它字符
			*/
			case '-':
				if (m_pos + 1 < m_str.size() && m_str[m_pos + 1] >= '0' && m_str[m_pos + 1] <= '9'
					&&
					(m_pos == 0||
					m_str[m_pos - 1] == '('||
					m_str[m_pos - 1] == '+'||
					m_str[m_pos - 1] == '-'||
					m_str[m_pos - 1] == '*'||
					m_str[m_pos - 1] == '/'))
				{
					string number = ParseNumber();
					return number.size() ? SourceElement(atof(number.c_str())) : SourceElement();
				}
				else
				{
					m_pos++;
					return SourceElement(c);
				}

			//左括号,合法(游标可进位)
			case '(':
				m_pos++;
				return SourceElement(TYPE_BRACKETS_L);
				//右括号,合法(游标可进位)
			case ')':
				m_pos++;
				return SourceElement(TYPE_BRACKETS_R);
			case '.'://小数点出现在第一个字符是不正常的,非法(游标不进位)
				return SourceElement();
			default://数字,需要连续解析
			{
				string number = ParseNumber();		//要解析的数字
				return number.size()? SourceElement(atof(number.c_str())):SourceElement();
			}
			}
		}
	private:
		/*
			它从m_pos位置开始,解析出一个数字来。解析完毕后,m_pos指向数字的下一个位置。
			如果解析失败,它返回空字符串
		*/
		string ParseNumber(){
			string number;
			bool bDot(false);	//小数点是否出现的标志
			//往后继续解析
			for (; m_pos < m_str.size();)
			{
				//获取一个字符
				char n = m_str[m_pos];
				if (n == '-' && number.size()==0)//如果是减号,且出现在开头位置,表示这是一个负号
				{
					number += n;
					m_pos++;
				}
				else if (n >= '0' && n <= '9')	//如果是数字,则追加到number,游标进位
				{
					number += n;
					m_pos++;
				}
				else if (n == '.')		//如果是小数点,首次出现是合法的,追加并游标进位。否则判为非法,游标不进位
				{
					if (bDot == false)
					{
						number += n;
						m_pos++;
						bDot = true;
					}
					else
					{
						return "";
					}
				}
				else//如果是其它字符,表示在此终止,游标不进位,停留在该字符上
				{
					break;
				}
			}

			return number;
		}
	private:
		string m_str;
		int m_pos;
	};

	class Math{
	public:
		static std::shared_ptr<double> ParseStr(const string &sourceStr){
			SourceString source(sourceStr);
			stack<SourceElement> s1;		//运算符栈
			stack<SourceElement> s2;		//逆波兰式栈
			//从左至右逐个对象地解析
			for (SourceElement obj = source.GetNextObject();
				obj.type() != ENUM_OBJECT_TYPE::TYPE_EMPTY;
				obj = source.GetNextObject())
			{
				switch (obj.type())
				{
				case ENUM_OBJECT_TYPE::TYPE_NUMBER:	//数字直接入栈
					s2.push(obj);
					break;
				case ENUM_OBJECT_TYPE::TYPE_OPERATOR://运算符则要判断来处理
				{
					//如果栈顶元素是运算符,且优先级低于等于当前运算符,则弹出放入s2。
					while(
							s1.size()
							&&
							(
								obj.Compare(s1.top()) == 0	//等于
								||
								obj.Compare(s1.top()) == -1	//低于
							)
						)
					{
						s2.push(s1.top());
						s1.pop();
					}
					s1.push(obj);
				}
					break;
				case ENUM_OBJECT_TYPE::TYPE_BRACKETS_L:	//左括号直接入栈
					s1.push(obj);
					break;
				case ENUM_OBJECT_TYPE::TYPE_BRACKETS_R:	//右括号,清空S1栈顶,直到遇见左括号或清空全部
				{
					while (s1.size())
					{
						if (s1.top().type() == TYPE_OPERATOR){
							s2.push(s1.top());
							s1.pop();
						}
						else if (s1.top().type() == TYPE_BRACKETS_L)
						{
							s1.pop();
							break;
						}
						else
						{
							break;
						}
					}
				}
					break;
				default:
					break;
				}
			}

			//把S1剩下的都放入S2
			while (s1.size())
			{
				s2.push(s1.top());
				s1.pop();
			}

			//由于s2中的逆波兰式是倒过来的,我们先把它倒置到s1中
			while (s2.size())
			{
				s1.push(s2.top());
				s2.pop();
			}
			//开始计算结果
			while (s1.size())
			{
				auto obj = s1.top();
				s1.pop();
				switch (obj.type())
				{
				case TYPE_NUMBER://如果是数字,则放入S2
					s2.push(obj);
					break;
				case TYPE_OPERATOR://如果是运算符,则从s2中取两个数来计算
				{
					if (s2.size() < 2)
					{
						//出错了,s2中的数不足两个
						return nullptr;
					}
					auto numb2 = s2.top();	//先出来的是第二个运算值
					s2.pop();
					auto numb1 = s2.top();
					s2.pop();
					//根据运算符,进行计算
					switch (obj.op())
					{
					case '+':
						s2.push(SourceElement(numb1.value() + numb2.value()));
						break;
					case '-':
						s2.push(SourceElement(numb1.value() - numb2.value()));
						break;
					case '*':
						s2.push(SourceElement(numb1.value() * numb2.value()));
						break;
					case '/':
						if (abs(numb2.value()) < 0.00000001){
							//出错了,被除数不能为零
							return nullptr;
						}
						s2.push(SourceElement(numb1.value() / numb2.value()));
						break;
					default:
						//出错了,这里不应该出现四则运算外的运算符
						return nullptr;
						break;
					}
				}
					break;
				default:
					//出错了,这里不应该出现数字和运算符外其它的类型
					return nullptr;
				}
				
			}
			if (s2.size() != 1)
			{
				//出错了,合法的计算结果里,此时应该只有一个值
				return nullptr;
			}
			return std::shared_ptr<double>(new double(s2.top().value()));
		}
	};
}
           

继续阅读