C++ で電卓を作ろう

I,はじめに

情報系の専門学校に通っている友人からJavaで電卓を作る授業をしたという話を聞きました。

そこで、最近書いていなかったC++でコンソール上で動く電卓でも作ろうと思い、書き始めました。

2018/1/5 よりましなコードを書きましたのでこちらをご参照ください。

II,ソースコード

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

main.cpp

/*
 *	C++ で電卓を作ろう
 *	2017/12/29
 *	jskny
 */

#include	<iostream>
#include	<string>
#include	<stack>
#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> Rex(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> listRex;
	stack<Token> stackCalc;

	// 文字列分解
	listRex = Rex(tmp);

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

	while (!stackOp.empty()) {
		Token t = stackOp.top();
		stackOp.pop();

		if (t.GetType() == Token::ADD) {
			Token n1, n2;
			n1 = stackValue.top();
			stackValue.pop();
			n2 = stackValue.top();
			stackValue.pop();
// cout << "ADD : " << n2.GetValue() << "+" << n1.GetValue() << endl;
			t.SetType(Token::VALUE);
			t.SetValue(n2.GetValue() + n1.GetValue());
			stackValue.push(t);
			continue;
		}
		else if (t.GetType() == Token::SUB) {
			Token n1, n2;
			n1 = stackValue.top();
			stackValue.pop();
			n2 = stackValue.top();
			stackValue.pop();
// cout << "SUB : " << n2.GetValue() << "-" << n1.GetValue() << endl;
			t.SetType(Token::VALUE);
			// n1,n2の順番を逆にしてしまうと、引く数から元の数を減算することになる。
			t.SetValue(n2.GetValue() - n1.GetValue());
			stackValue.push(t);
			continue;
		}
		else if (t.GetType() == Token::MULT) {
			Token n1, n2;
			n1 = stackValue.top();
			stackValue.pop();
			n2 = stackValue.top();
			stackValue.pop();
// cout << "MULT : " << n2.GetValue() << "*" << n1.GetValue() << endl;
			t.SetType(Token::VALUE);
			t.SetValue(n2.GetValue() * n1.GetValue());
			stackValue.push(t);
			continue;
		}
		else if (t.GetType() == Token::DIV) {
			Token n1, n2;
			n1 = stackValue.top();
			stackValue.pop();
			n2 = stackValue.top();
			stackValue.pop();
// cout << "DIV : " << n2.GetValue() << "/" << n1.GetValue() << endl;
			if (n1.GetValue() == 0 || n2.GetValue() == 0) {
				throw std::runtime_error("an 0 division occurred.");
			}

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

	if (!stackValue.empty()) {
		cout << "RESULT : " << stackValue.top().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、整数の計算

(1)足し算

Welcome to CPP-CALC.
Inputing a 'quit' or 'exit' will terminate.
 > 123+5
RESULT : 128

(2)引き算

 > 12-5
RESULT : 7

(3)掛け算

 > 12*3
RESULT : 36

(4)割り算

 > 128/2
RESULT : 64

2、出来ない事

優先順位をつけた計算は本コードでは実現できません。

また複数の計算を同時に行うと、最後の演算子から計算が始まるため、結果を保障できません。

(1)優先順位を無視する例

Console

Welcome to CPP-CALC.
Inputing a 'quit' or 'exit' will terminate.
 > 10*2+1
RESULT : 30

正解は21であるところ、このコードは30を出力しています。

それは、計算過程が最後の演算子から順に行われるからです。

つまり、2+1を先にやり、その結果の3と10を掛け算しているのです(あ、石を投げないでくださいっっ)。

(2)複数の計算を同時に行った場合

(ア)成功例
Welcome to CPP-CALC.
Inputing a 'quit' or 'exit' will terminate.
 > 2+3+4+5
RESULT : 14
(イ)失敗例
Welcome to CPP-CALC.
Inputing a 'quit' or 'exit' will terminate.
 > 100*4-2
RESULT : 200

これも、本来なら、398になるところ、最後の演算子から順に計算しているため、4-2の結果の2と100を掛け算してるため、こうした結果になります。

(3)弁解

なぜ、掛け算と割り算を優先して処理できないのかというと、面倒くさかったからです。

申し訳ありません。

IV,終わりに

なんとなく作ってみたが、やはりもうちょっとしっかりした計算機能を持たせるべきだったと思います。

いつかやると思います。

以上

コメントを残す

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

CAPTCHA