递归下降法实现计算表达式

Expression.h

#ifndef EXPRESSION_H
#define EXPRESSION_H

///
/         文件名:Expression.h
/	        描述:实现计算表达式功能
/         开发者:Hope
/           时间:2013年7月11日 17:01:08

#include <assert.h>
#include <vector>
#include <string>

//定义IN、OUT辅助宏,瑾瑾是为了让人看见函数申明就知道这个参数是属于输入参数还是输出参数。
#ifndef IN
#define IN
#endif

#ifndef OUT
#define OUT
#endif

//最大允许的表达式长度,超过则不允许。
#define MAX_EXPRESSION_LEN				350		

class CExpression
{
public:
	CExpression();
	virtual ~CExpression();

	
	///计算数学表达式
	/// szExpressionString  - 输入的表达式字符串进行计算。
	/// dbResult			- 输出计算得到的结果。
	///
	///返回值:计算成功返回true,并且会将结果存放到dbResult中。失败返回false,同时也不会修改dbResult的值,
	///        这个时候执行GetErrorMessage可以得到发生错误的具体消息。
	///
	bool Calculate(const char* IN szExpressionString, double& OUT dbResult);


	///
	///获取错误消息
	///返回值:返回最后由Calculate函数计算出错后的错误消息文本,否则返回NULL,
	///
	const char* GetLastErrorMessage() const;

//类中结构的定义
private:
	//定义一个表达式类型,其设计为多个元素组成的集合。
	struct element;
	typedef std::vector<element> expression;

	//定义一个异常接收类型,catch时使用的。
	struct ExpressionException{};

	//表达式中元素的类型。
	enum ELEMENT_TYPE
	{
		ELEMENT_TYPE_NUMBER		= 0,	//指示这个元素是数值型,这里使用双精度存储,包含小数。
		ELEMENT_TYPE_OPERATOR	= 1,	//指示这个元素是操作符型,如:+-*/
		ELEMENT_TYPE_EXPRESSION = 2		//指示这个元素是表达式型,也就是CExpression类型的。
	};

	//定义元素结构
	struct element
	{
		ELEMENT_TYPE type;					//指示下面联合中的数据类型
		char expSymbol;						//如果是表达式,则存储这个表达式的符号,是正的还是负的。

		union
		{
			char			operatorVal;	//操作符数据。
			double			doubleVal;		//双精度值数据。
			expression*		pExpVal;		//表达式数据。
		};
	};

//包括了禁止赋值和copy构造和其他函数。
private:
	//禁止本类的赋值操作。
	CExpression& operator=(CExpression&){ assert(false); };
	CExpression(const CExpression&){ assert(false); };

	//
	///构造一个新的表达式
	///返回构造好的表达式指针,瑾瑾是new expression的结果。
	//
	expression* ConstructNewExpression();

	//
	///释放所有分配的表达式内存
	//
	void DestoryAllConstructExpression();



	//以下是递归下降中的推算函数
	//
	///计算表达式,求得结果并且返回,如果中途发生异常则会throw丢出。
	///返回值:是输入的参数exp求得的结果。
	//
	double CalculateExpression(expression& IN exp);

	//
	///移动到下一个字符代码去,m_currentScanIndex和m_currentScanCode会
	///修改掉。
	//
	void MoveToNextCode();

	//
	///获取下一个字符,不会修改m_currentScanIndex和m_currentScanCode的值
	///这里会去掉到下一个字符之间的空格。
	//
	char GetNextChar();

	//
	///判定一个字符是否为数字
	//
	bool CharIsNumber(char IN c);

	//
	///判定一个字符是否为操作符
	//
	bool CharIsOperator(char IN c);

	//
	///判定当前扫描位置是否结束了
	//
	bool IsEof();

	//
	///  执行错误处理函数,这个函数会丢出异常,同时将所有分配过的表达式
	///内存给释放掉。
	//
	void OnError(const char* IN szMoreMessage);

	//
	///表达式处理,失败时会执行OnError()
	//
	void ProcessExpression(expression& IN exp);

	//
	///括号处理,失败时会执行OnError()
	//
	bool ProcessBrackets(expression& IN exp);

	//
	///数值处理,失败时会执行OnError()
	//
	bool ProcessNumber(expression& IN exp);

	//
	///操作符处理,失败时会执行OnError()
	//
	bool ProcessOperator(expression& IN exp);

//私有的属性定义。
private:
	bool	m_bIsSuccessful;				//记录最后计算结果是否成功了。
	std::string	m_szErrorMessage;			//错误消息,如果计算过程产生错误,将存放在这里。
	const char*	m_szExpression;				//记录输入的表达式指针。

	int		m_currentScanIndex;				//记录当前扫描到的字符索引。
	char	m_currentScanCode;				//存储当前扫描到的字符。
	std::vector<expression*> m_constructExpressionManager;	//记录在计算过程中所有分配过的表达式指针。以备不用的时候可以释放掉。
															//可以视作为内存管理器。
};


//获取错误消息的inline函数。
inline const char* CExpression::GetLastErrorMessage() const
{
	return (m_bIsSuccessful ? NULL : m_szErrorMessage.c_str());
}

#endif	//EXPRESSION_H



Expression.cpp

#include "stdafx.h"
#include "Expression.h"

#pragma warning(disable: 4996)
using namespace std;

#define MAX_NUMBER_LEN					64		//最大的数值尺度,也就是在输入表达式的时候数值长度不允许超过这个值,否则截断。
#define ZeroMem(Destination,Length)		memset((Destination),0,(Length))

CExpression::CExpression()
	:m_bIsSuccessful(false),
	m_szExpression(NULL),
	m_currentScanCode(0),
	m_currentScanIndex(0)
{
	
}

CExpression::~CExpression()
{

}


///计算数学表达式
/// szExpressionString  - 输入的表达式字符串进行计算。
/// dbResult			- 输出计算得到的结果。
///
///返回值:计算成功返回true,并且会将结果存放到dbResult中。失败返回false,同时也不会修改dbResult的值,
///        这个时候执行GetErrorMessage可以得到发生错误的具体消息。
///
bool Calculate(const char* IN szExpressionString, double& OUT dbResult);
bool CExpression::Calculate(const char* IN szExpressionString, double& OUT dbResult)
{
	//不支持超长的表达式。
	assert(strlen(szExpressionString) <= MAX_EXPRESSION_LEN);

	//首先清除状态记录,并记录输入值。
	m_bIsSuccessful = true;
	m_currentScanIndex = 0;
	m_currentScanCode = 0;
	m_szExpression = szExpressionString;

	try
	{
		expression* pExp = ConstructNewExpression();
		MoveToNextCode();
		ProcessExpression(*pExp);	//把输入看作是表达式识别

		if (IsEof())
		{
			//计算表达式,存储结果。
			dbResult =  CalculateExpression(*pExp);

			//由于在计算的时候已经清理掉了构造的表达式内存。所以这里瑾瑾是简单的清除即可。
			DestoryAllConstructExpression();
			return true;
		}
		else
			OnError("不是预计的结尾符号。");
	}
	catch (ExpressionException)
	{
		int nErrPos = m_currentScanIndex - 1;
		char errorMessage[MAX_EXPRESSION_LEN + 100];
		char* pErrorMessage = errorMessage;
		_snprintf_c(pErrorMessage, sizeof(errorMessage), "Error Expresstion~!!\nError Position:%d\nExpression Len:%d\nMore Message:%s\nMore:%s\n", 
			nErrPos + 1, strlen(m_szExpression), m_szErrorMessage.c_str(), m_szExpression);
		
		int nIndicationLen = strlen("more:") + nErrPos;
		pErrorMessage += strlen(pErrorMessage);
		memset(pErrorMessage, ' ', nIndicationLen);
		pErrorMessage += nIndicationLen;
		strcpy(pErrorMessage, "^\n");
		m_szErrorMessage = errorMessage;
	}
	catch(...)
	{
		m_szErrorMessage = "发生未知的异常。";
	}
	return false;
}

//以下是递归下降中的推算函数
//
///计算表达式,求得结果并且返回,如果中途发生异常则会throw丢出。
///返回值:是输入的参数exp求得的结果。
//
double CExpression::CalculateExpression(expression& IN exp)
{
	assert(exp.size() > 0);

	//首先计算表达式中的子表达式。然后将其类型变为数值型。递归求解。
	for (unsigned int i = 0; i < exp.size(); ++i)
	{
		element& tCurrentScanExp = exp[i];
		if (tCurrentScanExp.type == ELEMENT_TYPE_EXPRESSION)
		{
			tCurrentScanExp.doubleVal = CalculateExpression(*(tCurrentScanExp.pExpVal));
			tCurrentScanExp.type = ELEMENT_TYPE_NUMBER;

			//如果这个表达式的符号是负,则修改结果为负数。
			if (tCurrentScanExp.expSymbol == '-')
				tCurrentScanExp.doubleVal = -tCurrentScanExp.doubleVal;
		}
	}

	//然后先计算表达式中的优先级高的表达式,乘号和除号
	for (expression::iterator itor = exp.begin(); itor != exp.end(); ++itor)
	{
		element& tCurrentScanExp = *itor;

		//必须满足当前扫描到的是乘号或者除号,才进行运算。
		if(tCurrentScanExp.type == ELEMENT_TYPE_OPERATOR && 
			(tCurrentScanExp.operatorVal == '*' || tCurrentScanExp.operatorVal == '/'))
		{
			double dbLeftVal	= (itor - 1)->doubleVal;	//存储扫描的操作符前的数值,所谓的左值
			double dbRightVal	= (itor + 1)->doubleVal;	//存储扫描的操作符后的数值,所谓的右值

			switch(tCurrentScanExp.operatorVal)
			{
			case '*':	tCurrentScanExp.doubleVal = dbLeftVal * dbRightVal; break;
			case '/':
				//如果右值为0,则左值除以右值属于异常操作。
				if (dbRightVal == 0)
					OnError("计算结果发现除数为0,结束运行。");
				
				tCurrentScanExp.doubleVal = dbLeftVal / dbRightVal;
				break;
			default:
				assert(false);
				break;
			}

			//执行操作后修改为值类型,并且删除右值和左值元素,注意这里删除的顺序必须是这样,不要修改。
			tCurrentScanExp.type = ELEMENT_TYPE_NUMBER;
			exp.erase(itor + 1);
			itor = exp.erase(itor - 1);
		}
	}

	//再计算表达式中的加减号
	for (expression::iterator itor = exp.begin(); itor != exp.end(); ++itor)
	{
		element& tCurrentScanExp = *itor;

		//必须满足当前扫描到的是乘号或者除号,才进行运算。
		if(tCurrentScanExp.type == ELEMENT_TYPE_OPERATOR && 
			(tCurrentScanExp.operatorVal == '+' || tCurrentScanExp.operatorVal == '-'))
		{
			double dbLeftVal	= (itor - 1)->doubleVal;	//存储扫描的操作符前的数值,所谓的左值
			double dbRightVal	= (itor + 1)->doubleVal;	//存储扫描的操作符后的数值,所谓的右值

			switch(tCurrentScanExp.operatorVal)
			{
			case '+':	tCurrentScanExp.doubleVal = dbLeftVal + dbRightVal; break;
			case '-':	tCurrentScanExp.doubleVal = dbLeftVal - dbRightVal; break;
			default:
				assert(false);
				break;
			}

			//执行操作后修改为值类型,并且删除右值和左值元素,注意这里删除的顺序必须是这样,不要修改。
			tCurrentScanExp.type = ELEMENT_TYPE_NUMBER;
			exp.erase(itor + 1);
			itor = exp.erase(itor - 1);
		}
	}

	//第0个则是结果了。
	return exp[0].doubleVal;
}

//
///构造一个新的表达式
///返回构造好的表达式指针,瑾瑾是new expression的结果。
//
CExpression::expression* CExpression::ConstructNewExpression()
{
	expression* pNewExpression = new expression;
	if (NULL == pNewExpression)
	{
		//内存错误。
		m_currentScanIndex++;
		OnError("构造表达式的时候发生内存错误。");
		return NULL;
	}
	//printf("new 0x%x\n", pNewExpression);

	m_constructExpressionManager.push_back(pNewExpression);
	return pNewExpression;
}

//
///释放所有分配的表达式内存
//
void CExpression::DestoryAllConstructExpression()
{
	for (vector<expression*>::iterator itor = m_constructExpressionManager.begin(); 
		itor != m_constructExpressionManager.end(); ++itor)
	{
		//printf("delete 0x%x\n", *itor);
		delete *itor;
	}
	
	m_constructExpressionManager.clear();
}

//
///移动到下一个字符代码去
///理论上是支持空格的,因为这里会把空格去掉,只是在控制台上scanf的时候会有问题。
//
void CExpression::MoveToNextCode()
{
	m_currentScanCode = m_szExpression[m_currentScanIndex++];
	while(m_currentScanCode == ' ') 
		m_currentScanCode = m_szExpression[m_currentScanIndex++];
}

//
///判定一个字符是否为数字
//
bool CExpression::CharIsNumber(char IN c)
{
	return c >= '0' && c <= '9' || c == '.';
}

//
///判定一个字符是否为操作符
//
bool CExpression::CharIsOperator(char IN c)
{
	return c == '+' || c == '-' || c == '*' || c == '/';
}

//
///判定当前扫描位置是否结束了,0和换行都视为结束
//
bool CExpression::IsEof()
{
	return m_currentScanCode == 0 || m_currentScanCode == 0x0A || m_currentScanCode == 0x0D;
}

//
///  执行错误处理函数,这个函数会丢出异常,同时将所有分配过的表达式
///内存给释放掉。
//
void CExpression::OnError(const char* IN szMoreMessage)
{
	m_bIsSuccessful = false;
	m_szErrorMessage = szMoreMessage;
	DestoryAllConstructExpression();
	throw ExpressionException();
}

//
///获取下一个字符
//
char CExpression::GetNextChar()
{
	//保留现有的索引和代码值,瑾瑾是返回下一个字符。
	int i = m_currentScanIndex;
	char theNextChar = m_szExpression[i++];

	while(theNextChar == ' ') 
		theNextChar = m_szExpression[i++];

	return theNextChar;
}

//
///表达式处理,失败时会执行OnError()
//
void CExpression::ProcessExpression(expression& IN exp)
{
	//表达式既不是数字,也不是括号,则表示这个是错误的
	if (!ProcessNumber(exp) && !ProcessBrackets(exp))
		OnError("必须是数字/正括号。");
}

//
///括号处理,失败时会执行OnError()
//
bool CExpression::ProcessBrackets(expression& IN exp)
{
	//记录这个括号中的表达式符号
	char theExpSymbol = '+';

	//括号的开始可以是正负号,或者直接就是正括号。
	if (m_currentScanCode == '+' || m_currentScanCode == '-')
	{
		//如果是正负号开始,那么下一个字符必定是正括号开始的,否则为错误。
		if (GetNextChar() != '(')
			return false;

		theExpSymbol = m_currentScanCode;
		MoveToNextCode();
	}

	//括号则是以正括号开始
	if (m_currentScanCode == '(')
	{
		MoveToNextCode();

		element tempNewElement;
		ZeroMem(&tempNewElement, sizeof(tempNewElement));
		tempNewElement.expSymbol = theExpSymbol;
		tempNewElement.type = ELEMENT_TYPE_EXPRESSION;
		tempNewElement.pExpVal = ConstructNewExpression();
		exp.push_back(tempNewElement);
		ProcessExpression(*tempNewElement.pExpVal);		//正括号后是表达式,如果不则错误。

		//然后就是反括号:(1+2) 之类的,如果不是则错误。
		if (m_currentScanCode != ')')
			OnError("必须是反括号。");

		//跳过烦括号
		MoveToNextCode();

		//如果下一个还是正括号,也就是允许反括号和正括号之间没有操作符,即为乘号。
		//比如:(1+2)(2+3)将在这里被等同于:(1+2)*(2+3),就是在之间添加乘号了。
		if (m_currentScanCode == '(')
		{
			ZeroMem(&tempNewElement, sizeof(tempNewElement));
			tempNewElement.type = ELEMENT_TYPE_OPERATOR;
			tempNewElement.operatorVal = '*';
			exp.push_back(tempNewElement);

			//然后按照括号的方式处理就好了。
			if (!ProcessBrackets(exp))
				return false;
		}
		else if (m_currentScanCode != ')' && !ProcessOperator(exp) && !IsEof()) //如果反括号后面不是正括号、操作符、结束符号则为错误。
			OnError("必须是反括号/操作符/结束符号。");										  //也就是 (1+2) 后面必须跟正括号/操作符/结束符

		return true;
	}
	return false;
}

//
///数值处理,支持小数处理,失败时会执行OnError()
//
bool CExpression::ProcessNumber(expression& IN exp)
{
	//数字函数则开头必定是正负号或者是数字
	char numCode = 0;
	if (m_currentScanCode == '+' || m_currentScanCode == '-')
	{
		if (!CharIsNumber(GetNextChar()))
			return false;

		numCode = m_currentScanCode;
		MoveToNextCode();
	}

	//然后就必须是数值
	if (CharIsNumber(m_currentScanCode))
	{
		//定义数值缓冲区,存放需要转换的数值。
		char numberBuffer[MAX_NUMBER_LEN + 1];
		bool bExistPoint = false;	//如果已经发现了小数点则为true,当再次发现小数点的时候则为错误
		int nBufIndex = 0;			//数值缓冲区的索引值

		//如果有符号的,则先把符号写在前面
		if (numCode == '+' || numCode == '-')
			numberBuffer[nBufIndex++] = numCode;

		//然后保存当前位置的数值,如果是小数点,则记录之
		numberBuffer[nBufIndex++] = m_currentScanCode;
		if (m_currentScanCode == '.')
			bExistPoint = true;

		//循环判定数字,发现重复小数点的时候为异常,超过限定最大数值区域,则截断。
		while(CharIsNumber(m_szExpression[m_currentScanIndex]))
		{
			if (m_szExpression[m_currentScanIndex] == '.')
			{
				if (bExistPoint)		//这里表明是小数点重复了。
				{
					m_currentScanIndex++;
					OnError("出现了重复的小数点。");
				}
				else
					bExistPoint = true;
			}

			//如果超过预定的数字最大长度,则截断之。
			if (nBufIndex >= MAX_NUMBER_LEN)
				m_currentScanIndex++;
			else
				numberBuffer[nBufIndex++] = m_szExpression[m_currentScanIndex++];
		}
		numberBuffer[nBufIndex] = 0;

		//加入一个数值元素。
		element tempNewElement;
		ZeroMem(&tempNewElement, sizeof(tempNewElement));
		tempNewElement.type = ELEMENT_TYPE_NUMBER;
		tempNewElement.doubleVal = atof(numberBuffer);

		//当前一个元素对象是操作符,并且是除号,而且这个数字还是0的时候,则需要提示除0错误。
		if (tempNewElement.doubleVal == 0 && exp.size() > 0)
		{
			element& beforeOperatorElement = exp.back();
			if (beforeOperatorElement.type == ELEMENT_TYPE_OPERATOR && beforeOperatorElement.operatorVal == '/')
				OnError("除数为0,结束执行。");
		}
		exp.push_back(tempNewElement);

		MoveToNextCode();
		
		//如果数字后面直接就是括号,则可以插入一个乘号。
		if (m_currentScanCode == '(')
		{
			ZeroMem(&tempNewElement, sizeof(tempNewElement));
			tempNewElement.type = ELEMENT_TYPE_OPERATOR;
			tempNewElement.operatorVal = '*';
			exp.push_back(tempNewElement);
		}

		//然后数字后面必须是操作符、括号、结束符,反括号。如果不是则异常了。
		if (!ProcessOperator(exp) && !ProcessBrackets(exp) && !IsEof() && m_currentScanCode != ')')
			OnError("必须是操作符/结束符号/正括号/反括号。");

		return true;
	}
	return false;
}

//
///操作符处理,失败时会执行OnError()
//
bool CExpression::ProcessOperator(expression& IN exp)
{
	//操作符则必定是以操作符,加减乘除开始的。
	if (CharIsOperator(m_currentScanCode))
	{
		element tempNewElement;
		ZeroMem(&tempNewElement, sizeof(tempNewElement));
		tempNewElement.operatorVal = m_currentScanCode;
		tempNewElement.type = ELEMENT_TYPE_OPERATOR;
		exp.push_back(tempNewElement);

		MoveToNextCode();

		//操作符后面必定是数字或者是括号
		if (!ProcessNumber(exp) && !ProcessBrackets(exp))
			OnError("必须是数字/正括号。");

		return true;
	}
	return false;
}

调用,main.cpp

// Calc.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "Expression.h"

int _tmain(int argc, _TCHAR* argv[])
{
	CExpression exp;
	double dbResult = 0;
	char szExpression[MAX_EXPRESSION_LEN + 1];	//设定允许的缓冲区为最大允许的表达式长度+1,这样限制输入的表达式长度。

	printf("欢迎使用控制台计算器~~!\n");
	printf("本计算器支持表达式输入加、减、乘、除和小括号。\n\n");

	while(1)
	{
		printf("请输入需要计算的表达式(输入q退出,c清屏):");
		gets_s(szExpression, sizeof(szExpression) - 1);

		//当输入c的时候清屏。
		if ((szExpression[0] == 'c' || szExpression[0] == 'C') && szExpression[1] == 0)
		{
			system("cls");
			continue;
		}

		//当输入q的时候退出。
		if ((szExpression[0] == 'q' || szExpression[0] == 'Q') && szExpression[1] == 0)
			return 0;

		//计算表达式。如果错误输出异常信息,否则输出结果。
		if (!exp.Calculate(szExpression, dbResult))
			printf("error message:%s\n", exp.GetLastErrorMessage());
		else
			printf("Expression:%s\nResult = %.3f\n\n", szExpression, dbResult);
	}

	return 0;
}


效果:



  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值