C++ 9日目:演算子の入力

この章では、四則演算の演算子ボタンがおされたときの処理の説明をしていきます

処理概要

演算子の処理概要について、もう一度おさらいをします

演算子の前まで入力していた値(current_buffer)を、stackにコピー後、current_bufferをクリアします
stackは2つの数値を格納する配列で、stack[0]は演算子に対して左辺値を、stack[1]は右辺値を格納します
入力された演算子はlast_operatorに代入しディスプレイにも表示します
stack[0]、stack[1]がそろった場合、last_operatorの値の演算子で計算を行い、計算結果をディスプレイに表示し、stack[0]にコピーします
ー(マイナス記号)は演算子として使用し、負の値の入力は フェーズ2+/-切り替えボタンを使用します

なお、演算子の押された位置で以下のことを考慮します

  1. 入力の先頭で演算子を押す(current_buffer、stack[0]ともに空文字)
    ×1、-1、などを想定
    演算子の前に0が入力されたと判断し、stack[0]に0を代入する
  2. 数値、演算子、数字のように押される場合
    ①1+、1-、1×、1÷など想定
    演算子に対して右辺値が未定義なので、計算処理は実施しない
    ②1+1、1×2、1+2+3×4などを想定
    stack[0]、stack[1]がそろったので、演算子で計算を行い、計算結果をディスプレイに表示し、stack[0]にコピーする
  3. 演算子のあとに演算子を押す
    1++、1×-2、1‐÷2 、1 ×÷ー+2などを想定
    入力された演算子はlast_operatorに代入しディスプレイにも表示する

前提条件

演算子ボタンが押される直前の電卓は、処理概要で記載した「考慮すること」に対して、それぞれどのような状態になっているかを考えてみます

演算子の表示部分数値の表示部分current_bufferstack[0]状態の説明
入力の先頭で演算子を押した場合
数値数値1+、1-、1×、1÷など数値の後に演算子が押された場合
-、+、×、÷数値数値数値1+1、1×2、1+2+3×4などを左辺値、演算子 右辺値と押された状態
-、+、×、÷数値数値演算子のあとに演算子を押す
=数値数値計算完了後の状態

処理内容

演算子ボタンのイベント処理フロー

1. 直前の入力が数値かチェック

  • もしcurrent_bufferが空でない(=数値が入力されている)場合:
    – もしstack[0]が空なら、current_bufferの値をstack[0]にコピーすると同時にresultにstack[0]の値をコピーする
    – もしstack[0]に値があるなら、current_bufferの値をstack[1]にコピーし、calculate()関数で計算を実行する
  • もしcurrent_bufferが空(=直前の入力が演算子または計算後)の場合:
    – もしstack[0]が空なら、0stack[0]に代入する(0 + 5 のような計算に対応)
    – それ以外の場合は、何もしない(演算子の上書きに対応)

2.共通の処理

  1. calculate()で計算した結果(result)をstack[0]にコピーする
  2. stack[0]の値をディスプレイに表示する
  3. stack[1]をクリアする
  4. 現在の演算子をlast_operatorに保存し、ディスプレイに表示する
  5. current_bufferをクリアする

実装

イベントハンドラの設定

VisualC++の機能を使用して四則演算のボタンのイベントハンドラを作成します
その後、イベントハンドラに処理を記述していきます
作成されたイベントハンドラは次の4つです
afx_msg void OnBnClickedButtonPlus(); +演算子の処理
afx_msg void OnBnClickedButton1Minus(); -演算子の処理
afx_msg void OnBnClickedButtonMulti(); ×演算子の処理
afx_msg void OnBnClickedButtonDiv(); ÷演算子の処理
また、次の関数を作成しました
void clickOperatorButton(LPCTSTR lpText); 演算子が押された時の共通処理
double calculate(); 演算子と右辺値、左辺値から計算を実行する
void disableButton(); ACボタン以外ボタンを押せないようにする
double IsNumberAndConvert(const TCHAR* lpText); 文字列から数値に変換する。変換できない場合エラーをThrowする

1. MyCalculatorDlg.h

class CMyCalculatorDlg : public CDialogEx
{
// コンストラクション
public:
    CMyCalculatorDlg(CWnd* pParent = nullptr);  // 標準コンストラクター

// ダイアログ データ
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_MYCALCULATOR_DIALOG };
#endif

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV サポート


// 実装
private:
    void AddEditBoxText(LPCTSTR lpText);
    void setEditBoxText(LPCTSTR lpText);
    void initVariable();
    void clickOperatorButton(LPCTSTR lpText); // 演算子が押された時の共通処理
    void setOperatorText(LPCTSTR lpText); // 引数の文字列を演算子表示エリアに表示する
    void clearCurrentBuffer();
    double calculate(); // 演算子と右辺値、左辺値から計算を実行する  
    void HandleNumberClick(LPCTSTR number_char);
    void disableButton();  // ACボタン以外ボタンを押せないようにする  
    void enableButton();
    double IsNumberAndConvert(const TCHAR* lpText); // 文字列から数値に変換する。変換できない場合エラーをThrowする  
    CString ConvertDoubleToCString(double result); // 数値から文字列に変換する

    CString current_buffer = _T("");
    CString last_operator = _T("");
    CString stack[2] = {_T(""),_T("")};

protected:
    HICON m_hIcon;

    // 生成された、メッセージ割り当て関数
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedButtonAc();
    afx_msg void OnBnClickedButtonZero();
    afx_msg void OnBnClickedButtonOne();
    afx_msg void OnBnClickedButtonTwo();
    afx_msg void OnBnClickedButtonThree();
    afx_msg void OnBnClickedButtonFour();
    afx_msg void OnBnClickedButtonFive();
    afx_msg void OnBnClickedButtonSix();
    afx_msg void OnBnClickedButtonSeven();
    afx_msg void OnBnClickedButtonEight();
    afx_msg void OnBnClickedButtonNine();
    afx_msg void OnBnClickedButtonPlus();  // +演算子
    afx_msg void OnBnClickedButton1Minus(); // -演算子
    afx_msg void OnBnClickedButtonMulti();  // ×演算子
    afx_msg void OnBnClickedButtonDiv();  // ÷演算子
    afx_msg void OnBnClickedButtonDecimal();
};

2. MyCalculatorDlg.cpp

disableButton()

// ACボタン以外のボタンを押せなくする
void CMyCalculatorDlg::disableButton()
{
    CWnd* pAC = GetDlgItem(IDC_BUTTON_AC); // ACボタンのハンドル
    CWnd* pCtrl = GetTopWindow();
    do {
        if (pCtrl != pAC) pCtrl->EnableWindow(FALSE); // ACボタンでなければdisable
    } while (pCtrl = pCtrl->GetNextWindow());
}

IsNumberAndConvert(const TCHAR* lpText)

// 引数の文字列をdouble型に変換する
// 変換できない場合はエラーをThrowする  
double CMyCalculatorDlg::IsNumberAndConvert(const TCHAR* lpText) {
    TCHAR* endptr;
    double result = _tcstod(lpText, &endptr);

    // 文字列全体が消費され、かつ変換された文字が1つ以上あるか確認
    if (*endptr == '\0' && endptr != lpText) {
        return result;
    }
    else { // 数値として変換できない場合はエラーをThrowする
        throw std::invalid_argument("数式が不完全です");
    }
}

calculate()

/*
stack[0] last_operatorの値 stack[1]から数式を生成し計算し、結果を返す
stack[0]、stack[1]が数値として判断できない場合はエラーをThrowする  
*/
double CMyCalculatorDlg::calculate() 
{
    // 初期値はstack[1]の値とする
    double result = IsNumberAndConvert(stack[1]);

    // stack[0],stack[1]が数値として正しいかチェックしdouble型に変換する
    double val1;
    double val2;

    val1 = IsNumberAndConvert(stack[0]); // 数値に変換する
    val2 = IsNumberAndConvert(stack[1]); // 数値に変換する

    //  (1)stack[0] operatorの値 stack[1]から数式を生成し計算する
    if (last_operator == "*") {
        result = val1 * val2;
    }
    else if (last_operator == "/") {
        if (fabs(val2) < DBL_EPSILON) {
            throw std::invalid_argument("Cannot divide by zero");
        }
        else {
            result = val1 / val2;
        }
    }
    else if (last_operator == "+") {
        result = val1 + val2;
    }
    else if (last_operator == "-") {
        result = val1 - val2;
    }

    return result;
}

clickOperatorButton(LPCTSTR lpText)

/*
ディスプレイ:
	(1) 演算子を表示する
	(2) 途中までの計算結果を表示する
内部計算:
	演算子の前まで入力していた値(current_buffer)を、確定した値としstackにコピー後、current_bufferをクリアする
この関数が呼び出されるパターン
    (1)先頭に演算子  → current_bufferが空
	   stack[0]が空の場合
	(2)数値のあとに演算子 → current_bufferに値が入っている
	   stack[0]が空またはstack[1]に値が入っている
	(3)演算子が二重 → current_bufferが空
	   stack[0]に値が入っている
*/	
void CMyCalculatorDlg::clickOperatorButton(LPCTSTR lpText)
{
	bool flg = current_buffer.IsEmpty();
	double result;

	try {
		if (flg == false) { //	1. current_bufferに値が入っている場合
			//  current_bufferの値をstack[0]もしくはstack[1]へコピーする
			//(stack[0]が空文字のときはstack[0]へ、そうでなければstack[1]へ)
			if (stack[0].IsEmpty()) {
				stack[0] = current_buffer;
				// stack[0]が数値として正しいかチェックし、double型に変換する
				result = IsNumberAndConvert(stack[0]);
			}
			else {
				stack[1] = current_buffer;
				result = calculate();
			}
		}
		else // current_bufferが空文字のとき
		{
			//  current_buffer、stack[0]が空文字のとき
			// stack[0]は0とする
			if (stack[0].IsEmpty()) {
				stack[0] = "0";
			}

			result = IsNumberAndConvert(stack[0]);
		}

		// 計算結果をstack[0]にコピーする
		stack[0] = ConvertDoubleToCString(result);

		setEditBoxText(stack[0]);

		//	(2)stack[1]の値をクリアする
		stack[1] = "";

		//	2. operatorの値を選択された演算子に変更する
		last_operator = lpText;
		setOperatorText(lpText);

		//	3. current_bufferの値を空文字にする
		clearCurrentBuffer();
	}
	catch (const std::invalid_argument& e) {
		CString str(e.what());
		setEditBoxText(str);
		disableButton();
	}
}

この関数は、各演算子ボタンのハンドラから呼び出されます

void CMyCalculatorDlg::OnBnClickedButtonPlus()
{
	clickOperatorButton(_T("+")); // 共通関数を呼び出す
}

void CMyCalculatorDlg::OnBnClickedButton1Minus()
{
	clickOperatorButton(_T("-"));// 共通関数を呼び出す
}

void CMyCalculatorDlg::OnBnClickedButtonMulti()
{
	// TODO: ここにコントロール通知ハンドラー コードを追加します。
	clickOperatorButton(_T("*"));// 共通関数を呼び出す

}

void CMyCalculatorDlg::OnBnClickedButtonDiv()
{
	// TODO: ここにコントロール通知ハンドラー コードを追加します。
	clickOperatorButton(_T("/"));// 共通関数を呼び出す

}

以上が、演算子ボタンを実装したプログラムです
次は=ボタンの実装を行います

コメント

この記事へのコメントはありません。

関連記事

C++ 基礎編 1日目

Java 応用編 6日目

Python 応用編 5日目

PAGE TOP