1. 程式人生 > >遞迴下降法實現計算表示式

遞迴下降法實現計算表示式

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;
}
效果: