C++で電卓を作ろう その2

I,はじめに

昨年C++で電卓を作ろう、という記事を書きました。

ですが、その際のコードは複数の計算をうまく処理することが出来ませんでした。

その原因は、数値の処理を右から行っていたからでした。

今回それを修正し、左から処理するようにしたため、多少は使いやすくなりました。

そのため、記事にしました。

II,ソースコード

今回作成したコードはこちらになります。

cpp-calc2.cpp

/*
 *	C++ で電卓を作ろう
 *	2017/12/29
 *	2018/1/5 追記
 *	jskny
 */

#include	<iostream>
#include	<string>
#include	<vector>
#include	<cstdlib> // atoi のため
#include	<stdexcept>


using namespace std;


class Token {
public:
	enum Type {
		NIL = 0,
		ADD = 1000,
		SUB,
		MULT,
		DIV,
		VALUE = 2222
	};

	void		SetType(const Token::Type& op) { this->m_type = op; };
	Token::Type	GetType(void) const { return (this->m_type); };

	void		SetValue(const int v) { this->m_value = v; };
	int		GetValue(void) const { return (this->m_value); };


	void		Clear() {
		this->SetType(NIL);
		this->SetValue(0.0f);
	};


	Token() :
		m_type(NIL), m_value(0.0f)
	{
	}


private:
	Token::Type	m_type;
	int		m_value;

};


// 文字列処理
vector<Token> Lex(const string& tmp)
{
	Token tk;
	vector<Token> ret;

	// 文字列解析等
	for(int i = 0; i < tmp.length(); ++i) {
// cout << tmp[i] << endl;
		// 演算子の処理
		if (tmp[i] == '+') {
			tk.Clear();
			tk.SetType(Token::ADD);
			ret.push_back(tk);
			continue;
		}
		else if (tmp[i] == '-') {
			tk.Clear();
			tk.SetType(Token::SUB);
			ret.push_back(tk);
			continue;
		}
		else if (tmp[i] == '*') {
			tk.Clear();
			tk.SetType(Token::MULT);
			ret.push_back(tk);
			continue;
		}
		else if (tmp[i] == '/') {
			tk.Clear();
			tk.SetType(Token::DIV);
			ret.push_back(tk);
			continue;
		}

		// 数値データの処理
		if (tmp[i] >= '0' && tmp[i] <= '9') {
			int j = i;
// cout << "Check num. : " << tmp << endl;
			for (j = i; j < tmp.length(); ++j) {
				if (tmp[j] >= '0' && tmp[j] <= '9') {
// cout << "TMP is " << tmp[j] << endl;
					continue;
				}
				else {
					break;
				}
			}

			char* nStr = new char[j-i + 2];
			for (int k = 0; k < j-i; ++k) {
				nStr[k] = tmp[i+k];
// cout << nStr[k] << endl;
			}
			nStr[j-i+1] = '\0';
// cout << "nStr is " << nStr << endl;

			tk.Clear();
			tk.SetType(Token::VALUE);
			tk.SetValue(atoi(nStr));
			ret.push_back(tk);

			delete(nStr);
			nStr = 0;

			i = j - 1;
			continue;
		}
	}

	return (ret);
}


void Calc(const string& tmp)
{
	vector<Token> listLex;
	vector<Token> stackCalc;

	// 文字列分解
	listLex = Lex(tmp);

	// 計算処理(掛け算、割り算の優先順位は考慮しない)
	// 12+32-44+11 の時
	// Op stack [+, -, +]
	// Value stack [12, 32, 44, 11]
	vector<Token> stackOp;
	vector<Token> stackValue;
	for (auto itr = listLex.begin(); itr != listLex.end(); ++itr) {
		if (itr->GetType() != Token::VALUE) {
cout << "PUSHD(T) " << itr->GetType() << endl;
			stackOp.push_back(*itr);
			continue;
		}
		else {
cout << "PUSHD(V) " << itr->GetValue() << endl;
			stackValue.push_back(*itr);
			continue;
		}
	}

	while (!stackOp.empty()) {
		Token t = stackOp.front();
		stackOp.erase(stackOp.begin());

		if (t.GetType() == Token::ADD) {
			Token n1, n2;
			n1 = stackValue.front();
			stackValue.erase(stackValue.begin());
			n2 = stackValue.front();
			stackValue.erase(stackValue.begin());
cout << "ADD : " << n1.GetValue() << "+" << n2.GetValue() << endl;
			t.SetType(Token::VALUE);
			t.SetValue(n1.GetValue() + n2.GetValue());
			stackValue.insert(stackValue.begin(), t);
			continue;
		}
		else if (t.GetType() == Token::SUB) {
			Token n1, n2;
			n1 = stackValue.front();
			stackValue.erase(stackValue.begin());
			n2 = stackValue.front();
			stackValue.erase(stackValue.begin());
cout << "SUB : " << n1.GetValue() << "-" << n2.GetValue() << endl;
			t.SetType(Token::VALUE);
			t.SetValue(n1.GetValue() - n2.GetValue());
			stackValue.insert(stackValue.begin(), t);
			continue;
		}
		else if (t.GetType() == Token::MULT) {
			Token n1, n2;
			n1 = stackValue.front();
			stackValue.erase(stackValue.begin());
			n2 = stackValue.front();
			stackValue.erase(stackValue.begin());
cout << "MULT : " << n1.GetValue() << "*" << n2.GetValue() << endl;
			t.SetType(Token::VALUE);
			t.SetValue(n1.GetValue() * n2.GetValue());
			stackValue.insert(stackValue.begin(), t);
			continue;
		}
		else if (t.GetType() == Token::DIV) {
			Token n1, n2;
			n1 = stackValue.front();
			stackValue.erase(stackValue.begin());
			n2 = stackValue.front();
			stackValue.erase(stackValue.begin());
cout << "DIV : " << n1.GetValue() << "/" << n2.GetValue() << endl;
			if (n1.GetValue() == 0 || n2.GetValue() == 0) {
				throw std::runtime_error("an 0 division occurred.");
			}

			t.SetType(Token::VALUE);
			t.SetValue(n1.GetValue() / n2.GetValue());
			stackValue.insert(stackValue.begin(), t);
			continue;
		}
	}

	if (!stackValue.empty()) {
		cout << "RESULT : " << stackValue.front().GetValue() << endl;
	}
	else {
		throw std::runtime_error("Inputed expr is unjust.");
	}

	return;
}


int main(int argc, char* argv[])
{
	string tmp;
	cout << "Welcome to CPP-CALC." << endl;
	cout << "Inputing a 'quit' or 'exit' will terminate." << endl;
	while (true) {
		tmp = "";
		cout << " > ";
		cin >> tmp;
		if (tmp == "quit" || tmp == "exit") {
			break;
		}

		try {
			Calc(tmp);
		}
		catch (const std::runtime_error& e) {
			cerr << e.what() << endl;
			break;
		}
		catch (const std::logic_error& e) {
			cerr << e.what() << endl;
			break;
		}
		catch (...) {
			cerr << "Unexpected error." << endl;
			break;
		}
	}

	cout << "Quit." << endl;
	return (0);
}

III,動作等

基本的な動作等は、前回の記事をご参照ください。

1、複数の数値の計算

では、前回の記事で達成できなかった「100*4-2」の式を試してみましょう。

Welcome to CPP-CALC.
Inputing a 'quit' or 'exit' will terminate.
 > 100*4-2
PUSHD(V) 100
PUSHD(T) 1002
PUSHD(V) 4
PUSHD(T) 1001
PUSHD(V) 2
MULT : 100*4
SUB : 400-2
RESULT : 398

このように、正しい結果の398が出力できるようになりました。

他の文字列は計算過程等を可視化するためのデバッグ用の文字列です。

2、できない事

(1)意図的な負の数の処理

これはどういうことかというと、100*-1のように意図的に負の数であることを宣言した式を処理する事が現段階では出来ません。これは、文字列の処理の際、減算演算子と負の数のメルクマールを区別していないからです。

仮に、本コードでこれを試すと、ソフトウェアが落ちます。

(2)優先順位を付けた計算

これも、まだ対応できていないことです。

「10*2+1」のように左から処理する分には問題はないのですが、「33+10*2」のような式の場合は、前回の記事と同様の問題が生じます。

(ア)成功例
Welcome to CPP-CALC.
Inputing a 'quit' or 'exit' will terminate.
 > 10*2+1
PUSHD(V) 10
PUSHD(T) 1002
PUSHD(V) 2
PUSHD(T) 1000
PUSHD(V) 1
MULT : 10*2
ADD : 20+1
RESULT : 21
(イ)失敗例
Welcome to CPP-CALC.
Inputing a 'quit' or 'exit' will terminate.
 > 33+10*2
PUSHD(V) 33
PUSHD(T) 1000
PUSHD(V) 10
PUSHD(T) 1002
PUSHD(V) 2
ADD : 33+10
MULT : 43*2
RESULT : 86

本来は、10*2が先に計算され、その結果である20と33が加算され53が正しい結果として出力されるべきですが、出来ていません。

IV,終わりに

今回改めて記事にしましたが、変更点は式の処理を終わりから行うか初めからやるかという小さなものに過ぎません。

正しい優先順位を付けた計算をやろうとすると、結構大変なのでまた今度!

以上

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA