1. 程式人生 > >資料結構(c++)(3)--簡單的計算器

資料結構(c++)(3)--簡單的計算器

        接著上一篇部落格(點選開啟連結)中關於棧在中綴表示式和字尾表示式中的應用,這次分享下自己的一個簡單的計算器實現的程式碼。

        那就暴力簡單些,直接上程式碼:

類定義的程式碼如下:

#ifndef CALCULATOR_H
#define CALCULATOR_H
#include<string>
#include<vector>

using namespace std;
class Calculator
{
public:
	Calculator(int num = 0)
	{
		init(num);
	}
	bool isValid(const string &expression) const;
	double calculate(const string &expression) const;    //主要是對字尾表示式求解結果
	double arithmeticSign(char sign, double num1, double num2) const;   //用於計算兩個數之間的結果  
	friend double string_to_digit(const string &str);  //將字串轉化為數字
private:
	int m_num;
	vector<char> m_standard;
	void init(int num);
	bool isStandard(const string &expression) const;
	bool isSign(char ch) const;      //判斷一個字元是否是運算子
	bool ComparePriority(char sign1, char sign2)const;    //比較兩個運算子的優先順序
	void infixToSuffix(const string &expression, vector<double> &num_vec, vector<char> &cmp_vec, vector<bool> &loc_vec)const;   //將一箇中綴表示式轉化為字尾表示式
};
#endif
類的具體實現如下:
#include<stack>
#include<iostream>
#include<map>
#include"Calculator.h"

using namespace std;
void Calculator::init(int num)
{
	char str[18] = { '0','1','2','3','4','5','6','7','8','9','.','(',')','+','-','*','/' };
	m_num = num;
	for (int i = 0; str[i] != '\0'; i++)
		m_standard.push_back(str[i]);
}
bool Calculator::isStandard(const string &expression) const
{
	for (int i = 0; i<expression.size(); i++)
	{
		if (find(m_standard.begin(), m_standard.end(), expression[i]) == m_standard.end())
			return false;
	}
	return true;
}
bool Calculator::isSign(char ch) const
{
	if (find(m_standard.begin() + 11, m_standard.end(), ch) == m_standard.end())
		return false;
	return true;
}
void Calculator::infixToSuffix(const string &expression, vector<double> &num_vec, vector<char> &cmp_vec, vector<bool> &loc_vec)const
{
	if (!isStandard(expression))
	{
		cerr << "**********輸入有誤**********" << endl;
		exit(EXIT_FAILURE);
	}
	if (!isValid(expression))
	{
		cerr << "**********輸入有誤**********" << endl;
		exit(EXIT_FAILURE);
	}
	int first = 0;
	stack<char> stackChar;
	for (int i = 0; i<expression.size(); i++)
	{
		if (isSign(expression[i]) || (i == expression.size() - 1))
		{
			string substr(expression.begin() + first, expression.begin() + i);
			if (i == expression.size() - 1 && !isSign(expression[i]))   //當i代表最後一個字元且不是運算子的時候,那麼這個數字得合併向前一個數字中
				substr += expression[i];
			if (!substr.empty())   //此時處理的是數字
			{
				double num = string_to_digit(substr);
				//將數字放入數字向量,並記錄相應的位置屬性
				num_vec.push_back(num);
				loc_vec.push_back(false);
			}
			if(isSign(expression[i]))    //處理字元的時候
			{
				if (expression[i] == ')')
				{
					while (!stackChar.empty())
					{
						char cmp = stackChar.top();
						stackChar.pop();
						if (cmp == '(')
							break;
						//將運算子放入字元向量中,並用位置向量記錄位置屬性
						cmp_vec.push_back(cmp);
						loc_vec.push_back(true);

					}
				}
				else
				{
					if (!stackChar.empty())
					{
						char ch = stackChar.top();
						if (ch=='('||!ComparePriority(ch, expression[i]))
						{
							stackChar.push(expression[i]);
						}
						else
						{
							cmp_vec.push_back(ch);
							stackChar.pop();
							loc_vec.push_back(true);
						}
					}
					else {
						stackChar.push(expression[i]);
					}
				}
			}
			first = i + 1;
		}
	}
	while (!stackChar.empty())
	{
		cmp_vec.push_back(stackChar.top());
		stackChar.pop();
		loc_vec.push_back(true);
	}
}


bool Calculator::isValid(const string &expression) const
{
	if (isStandard(expression) == false)
		return false;
	stack<char> stk;
	for (int i = 0; i<expression.size(); i++)
	{
		if (expression[i] == '(' || expression[i] == '[')
			stk.push(expression[i]);
		else if (expression[i] == ')')
		{
			if (stk.top() != '(')
				return false;
			else
				stk.pop();
		}
		else if (expression[i] == ']')
		{
			if (stk.top() != '[')
				return false;
			else
				stk.pop();
		}
	}
	if (stk.empty())
		return true;
	else
		return false;
}
double Calculator::calculate(const string &expression) const
{
	vector<double> num_vec;
	vector<char> cmp_vec;
	vector<bool> loc_vec;
	stack<double> stackDouble;
	stack<char> stackChar;
	int index_num = 0, index_cmp = 0;

	//將中綴表示式轉化為字尾表示式
	infixToSuffix(expression, num_vec, cmp_vec, loc_vec);

	for (int i = 0; i < loc_vec.size(); i++)
	{
		if (!loc_vec[i])      //當i位置處是數字的時候
		{
			stackDouble.push(num_vec[index_num++]);
		}
		else
		{
			if (stackDouble.size() < 2)
			{
				cerr << "對不起,輸入的表示式有誤,無法計算" << endl;
				exit(EXIT_FAILURE);
			}
			double num1 = stackDouble.top();
			stackDouble.pop();
			double num2 = stackDouble.top();
			stackDouble.pop();
			stackDouble.push(arithmeticSign(cmp_vec[index_cmp++], num2, num1));
		}
	}
	if (stackDouble.size() != 1 || !stackChar.empty())
	{
		cerr << "對不起,輸入的表示式有誤,無法計算" << endl;
		exit(EXIT_FAILURE);
	}
	return stackDouble.top();


}

double string_to_digit(const string &str)
{
	double digit = 0;
	auto it = find(str.begin(), str.end(), '.');
	if (it == str.end())
	{
		int n = 1;
		for (int i = str.size() - 1; i >= 0; i--)
		{
			digit += (str[i] - '0')*n;
			n *= 10;
		}
	}
	else
	{
		int n = 1;
		int location = it - str.begin();
		for (int i = location - 1; i >= 0; i--)
		{
			digit += (str[i] - '0')*n;
			n *= 10;
		}
		double m = 0.1;
		for (int i = location + 1; i<str.size(); i++)
		{
			digit += (str[i] - '0')*m;
			m *= 0.1;
		}
	}
	return digit;
}
double Calculator::arithmeticSign(char sign, double num1, double num2) const
{
	if (sign == '+')
		return num1 + num2;
	else if (sign == '-')
		return num1 - num2;
	else if (sign == '*')
		return num1*num2;
	else if (sign == '/')
	{
		if (num2 == 0)
		{
			cerr << "********除數不能為0*******" << endl;
			exit(EXIT_FAILURE);
		}
		return num1 / num2;
	}
}

bool Calculator::ComparePriority(char sign1, char sign2)const     //用於比較第一個符號的優先順序是否比第二個大
{
	map<char, int> SignGather;
	SignGather.insert(pair<char, int>('(', 1));
	SignGather.insert(pair<char, int>('*', 2));
	SignGather.insert(pair<char, int>('/', 2));
	SignGather.insert(pair<char, int>('+', 3));
	SignGather.insert(pair<char, int>('-', 3));
	if (SignGather[sign1]<SignGather[sign2])
		return true;
	else
		return false;

}

測試的main函式如下:
#include<iostream>
#include"Calculator.h"
using namespace std;

int main()
{
	Calculator calculator;
	string str;
	cout << "請輸入一個數學表示式(其中要求符號是英文,且無空白符):" << endl;
	cin >> str;
	double result = calculator.calculate(str);
	cout << result << endl;
	return 0;

}
下面主要說一下這個程式碼的思路:

(1)首先我們主要是構造一個計算器的類Calculator,用於處理我們的計算表示式。

(2)對於輸入的數學表示式,我們用string進行存放,但是並不會將它作為類的成員變數,因為我們都知道,對於一個計算器而言,沒有必要儲存這種變數,因為使用者會一直輸入不同的表示式,所以儲存它是沒有什麼太大的作用的,所以我們這裡就不儲存它了。

(3)對於輸入的表示式,我們有專門的函式isValid和isStandard對其進行初步的簡單檢測,以判定是否符合標準。當然對於這個標準而言,我們所定義的標準是存放在成員變數m_standard中的,在這個向量中,我們定義了數學表示式中允許出現的合法的字元。

(4)之後,就是需要對錶達式進行解析了,因為我們是將表示式當做一個string讀入的,所以如何判定數字和運算子需要我們自己處理。這裡我們定義了一個函式幾個函式進行這方面的處理,其中string_to_digit函式會將制定的字串轉化為數字,isSign函式會判斷一個字元是否為元素符。在這裡我們需要著重注意一下如何拆分表示式,在程式中,對於表示式的處理,我是這樣做的:我會從下標0開始逐個讀取string表示式中的字元,定義一個first用於標記讀的起始位置,當讀到運算子的時候我會停下來,因為在數學表示式中,運算子之間的就是操作數了(當然對於“(”、“)”與其它操作符相鄰的情況也可以處理的),這個時候,在first和運算子之間的這部分子串就試數字,這個時候我們可以使用函式將數字解析出來,同時也就解析出來運算子。

(5)對於解析出來的數字和運算子,我們首先需要將它們處理成字尾表示式,這才是我們的主要目的。那麼問題來了,這個字尾表示式該如何進行存放呢?繼續存放成一個string?這顯然不是我們願意看見的,因為我們好不容易將string解析出來了,這個時候又把它轉化回去,想想都不開心。有什麼辦法可以同時記錄下數字和字元的字尾表示式呢?這裡呢,我們用三個向量進行存放,分別為num_vec,cmp_vec和loc_vec,分別用於存放數字、運算子和位置引數。在處理string表示式過程中,當解析出一個數字的時候,我們就將它放入num_vec中,並將一個false放入loc_vec中;當解析出一個運算子的時候,我們就將它放入cmp_vec中,並將一個true放入loc_vec中,直至處理完畢。在這裡main,loc_vec是很重要的,我們將用它記錄數字和運算子的位置,用false表示此處的東西在num_vec中,用true表示此處的東西在cmp_vec中,大家仔細體會下這個處理方法,並結合程式碼看下。

(6)在步驟(5)中,我們可以得到一個字尾表示式了,之後便可以根據得到的num_vec,cmp_vec和loc_vec對字尾表示式進行讀取和處理,這部分的處理就比較簡單了,大家可以看下程式碼。

        至此,我們的這個簡單的計算器就可以使用了,當然啦這是一個命令列的計算器,比較粗糙,後面將會更新一個圖形化介面的計算器。當然,這個程式碼沒有經過很多的測試,可能存在不足,如果有錯誤請大家留言,我會做出相應的修改,這次就分享到這了。