許多開發者認為 MFC 寫計算器容易,SDK 難。其實只要掌握「建立框架 → 訊息處理」的固定模式,SDK 同樣得心應手。核心需兩個字串/數值轉換函式:
double strtod(const char* nptr, char** endptr):字串 → 雙精度浮點數char* _gcvt(double value, int digits, char* buffer):雙精度浮點數 → 字串(digits 為有效位數)WndProc:處理 WM_CREATE 建立按鈕/編輯框、WM_COMMAND 分派按鈕事件、WM_DESTROY 結束程式。NumResult()(數字鍵)、EquResult()(運算鍵)兩大核心函式。
// 全域狀態變數
HWND hEditResult; // 顯示用編輯框
char lpszAddItem[256]; // 目前輸入緩衝
char lpszResult1[256], lpszResult2[256]; // 運算元 1/2
char lpszOpt[2] = "N"; // 目前運算子:'N'=無, '+','-','*','/','%','S'(sqrt)
int nOptF = 0; // 0=等待新輸入, 1=正在輸入數字
bool bDec = false; // 是否已輸入小數點
double nResult1, nResult2; // 運算中間結果
NumResult()
void NumResult(char* NumData) {
if (nOptF == 0) SetWindowText(hEditResult, ""); // 新運算起始,清空顯示
int nMax = GetWindowTextLength(hEditResult) + 1;
GetWindowText(hEditResult, lpszAddItem, nMax); // 取得現有字串
strcat(lpszAddItem, NumData); // 追加新數字/小數點
if (strcmp(lpszOpt, "N") == 0) { // 尚未按運算子
strcpy(lpszResult1, lpszAddItem);
SetWindowText(hEditResult, lpszResult1);
} else { // 已按運算子,輸入第二運算元
strcpy(lpszResult2, lpszAddItem);
SetWindowText(hEditResult, lpszResult2);
}
nOptF = 1; // 標記已輸入數字
}
EquResult()
void EquResult() {
// 1. 取得第一運算元
if (strcmp(lpszOpt, "N") == 0) {
nResult1 = strtod(lpszResult1, &stop);
} else {
// 2. 依前一運算子執行運算
nResult1 = strtod(lpszResult1, &stop);
nResult2 = strtod(lpszResult2, &stop);
switch (*lpszOpt) {
case '+': nResult1 += nResult2; break;
case '-': nResult1 -= nResult2; break;
case '*': nResult1 *= nResult2; break;
case '/':
if (nResult2 == 0) { MessageBox(hWnd, "除數不能為零!", "錯誤", MB_OK); break; }
nResult1 /= nResult2; break;
case '%': nResult1 /= 100; break;
case 'S': // sqrt
if (nResult1 < 0) { MessageBox(hWnd, "負數沒有平方根!", "錯誤", MB_OK); break; }
nResult1 = sqrt(nResult1); break;
}
}
// 3. 顯示結果並重置狀態
_gcvt(nResult1, 15, lpszResult1);
SetWindowText(hEditResult, lpszResult1);
nOptF = 0; bDec = false; strcpy(lpszOpt, "N");
}
WM_COMMAND 分派範例
switch (LOWORD(wParam)) {
case IDB_NUM0: if (nOptF) NumResult("0"); break;
case IDB_NUM1: NumResult("1"); break;
// ... IDB_NUM2 ~ IDB_NUM9 類似
case IDB_NUMDEC: if (!bDec) { NumResult("."); bDec = true; nOptF = 1; } break;
case IDB_OPTADD: EquResult(); strcpy(lpszOpt, "+"); break;
case IDB_OPTSUB: EquResult(); strcpy(lpszOpt, "-"); break;
case IDB_OPTMUL: EquResult(); strcpy(lpszOpt, "*"); break;
case IDB_OPTDIV: EquResult(); strcpy(lpszOpt, "/"); break;
case IDB_OPTSQRT: strcpy(lpszOpt, "S"); EquResult(); break;
case IDB_OPTPCT: strcpy(lpszOpt, "%"); EquResult(); break;
case IDB_CANCLE: // 全部重置
SetWindowText(hEditResult, "0");
nResult1 = nResult2 = 0;
strcpy(lpszResult1, "0"); strcpy(lpszResult2, "0");
nOptF = 0; bDec = false; strcpy(lpszOpt, "N");
break;
}
#include
#include // sqrt
#include // _gcvt
#include // strtod
#include // strcmp/strcpy/strcat
#include "resource.h" // 控制項 ID 定義
SDK 版計算器雖代碼量較大,但完全掌控視窗建立與訊息流程,適合理解 Windows 核心機制。後續可擴充:
WM_CHAR/WM_KEYDOWN)原始碼已整理附於專案,歡迎參考修改。