使用 MFC (Microsoft Foundation Classes) 開發簡單計算器,需求:四則運算、小數點、Switch 語句邏輯、暫存運算元與結果。本文提供完整架構設計、核心代碼與常見坑指南。
SimpleCalc。IDD_SIMPLECALC_DIALOG:
IDC_EDIT_DISPLAY (顯示輸入/結果,Right Align、Read Only)。IDC_BTN_0...IDC_BTN_9, IDC_BTN_ADD, IDC_BTN_SUB, IDC_BTN_MUL, IDC_BTN_DIV, IDC_BTN_DOT, IDC_BTN_EQ, IDC_BTN_CLR, IDC_BTN_CE。BN_CLICKED 處理器。// SimpleCalcDlg.h
double m_firstOperand; // 前一個運算元
double m_secondOperand; // 當前輸入運算元
int m_operator; // 運算子代碼:0=無, 1=+, 2=-, 3=*, 4=/
bool m_waitingForSecond; // 是否等待輸入第二運算元
CString m_displayStr; // 顯示字串 (DDX 綁定 IDC_EDIT_DISPLAY)
void CSimpleCalcDlg::OnBtnDigit(UINT nID) {
int digit = nID - IDC_BTN_0; // 假設 ID 連續
if (m_waitingForSecond) {
m_displayStr = _T("");
m_waitingForSecond = false;
}
if (m_displayStr == _T("0") || m_displayStr.IsEmpty())
m_displayStr = std::to_wstring(digit).c_str();
else
m_displayStr += std::to_wstring(digit).c_str();
UpdateData(FALSE); // 更新 UI
}
在類向導為 10 個數字鍵分別綁定,或手動在 ON_COMMAND_RANGE(IDC_BTN_0, IDC_BTN_9, OnBtnDigit)。
void CSimpleCalcDlg::OnBtnDot() {
if (m_waitingForSecond) { m_displayStr = _T("0"); m_waitingForSecond = false; }
if (m_displayStr.Find(_T('.')) == -1) m_displayStr += _T(".");
UpdateData(FALSE);
}
void CSimpleCalcDlg::OnBtnOperator(UINT nID) {
// 1. 取得當前顯示值作為運算元
double currentValue = _ttof(m_displayStr);
// 2. 若有前一運算子且非等待第二運算元,先執行前一運算 (連續運算支援)
if (m_operator != 0 && !m_waitingForSecond) {
Calculate(); // 執行 m_firstOperand (op) m_secondOperand
m_firstOperand = m_secondOperand; // 結果作為下一運算的左運算元
} else {
m_firstOperand = currentValue;
}
// 3. 記錄新運算子
switch (nID) {
case IDC_BTN_ADD: m_operator = 1; break;
case IDC_BTN_SUB: m_operator = 2; break;
case IDC_BTN_MUL: m_operator = 3; break;
case IDC_BTN_DIV: m_operator = 4; break;
}
m_waitingForSecond = true; // 下一個輸入為右運算元
UpdateData(FALSE);
}
void CSimpleCalcDlg::OnBtnEq() {
if (m_operator == 0) return; // 無運算子
m_secondOperand = _ttof(m_displayStr);
Calculate();
m_operator = 0; // 重置運算子
m_waitingForSecond = true; // 下一輸入重新開始
UpdateData(FALSE);
}
void CSimpleCalcDlg::Calculate() {
double result = 0;
switch (m_operator) {
case 1: result = m_firstOperand + m_secondOperand; break;
case 2: result = m_firstOperand - m_secondOperand; break;
case 3: result = m_firstOperand * m_secondOperand; break;
case 4:
if (m_secondOperand == 0) {
AfxMessageBox(_T("除數不能為零"));
result = m_firstOperand; // 保持原值
} else {
result = m_firstOperand / m_secondOperand;
}
break;
}
m_secondOperand = result;
m_displayStr.Format(_T("%.10g"), result); // 科學計數法自動切換,去除尾隨 0
}
void CSimpleCalcDlg::OnBtnClr() { // C: 全清
m_firstOperand = m_secondOperand = 0;
m_operator = 0;
m_waitingForSecond = false;
m_displayStr = _T("0");
UpdateData(FALSE);
}
void CSimpleCalcDlg::OnBtnCe() { // CE: 清除當前輸入
m_displayStr = _T("0");
m_waitingForSecond = false;
UpdateData(FALSE);
}
PreTranslateMessage 攔截 VK_NUMPAD0-9, VK_ADD 等觸發對應按鈕。1+2*3=7,需實作運算式解析 (Shunting-yard 算法或遞歸下降),而非簡單左結合。CListBox 記錄每次運算式與結果。Calculate() 邏輯抽離為獨立類/函數,配合 Google Test 驗證邊界值。| 現象 | 原因 | 修正 |
|---|---|---|
| 連續按運算子 (+ 後按 -) 邏輯錯亂 | 未處理「運算子替換」情況 | 在 OnBtnOperator 中判斷 m_waitingForSecond 為 true 時直接替換 m_operator |
| 小數精度顯示問題 (0.1+0.2=0.30000000000000004) | double 二進位浮點誤差 | 顯示用 Format(_T("%.10g")) 或引入定點數/十進位庫 |
| 除零崩潰 | 未檢查除數 | Calculate() 中 case 4 判斷 m_secondOperand == 0 |
MFC 計算器是理解「事件驅動、狀態機、UI/邏輯分離」的絕佳練習。掌握 Switch 狀態機模式,後續擴充科學計算機、程式設計師計算機皆同理可循。