2001 Spring C++ 程式設計課程程式作業

視窗版計算機程式

繳交日期:90/6/5 (二)
程式 Demo 日期:90/6/5 (二) 9:20-10:10, 90/6/6 (三) 13:00-14:00 PC 教室

這個作業延續前面幾個關於運算式的作業 (運算式計算程式 - C 版本運算式計算程式 - C++ 版本 I運算式計算程式 - C++ 版本 II 繼承類別設計運算式計算程式 - C++ 版本 II), 設計一個視窗版本的計算機程式, 程式運作起來就像是一個簡單的計算機 (Windows 裡也提供一個功能較強的小算盤應用程式)。

作業目的

說明:

操作介面:

這是一個視窗版本的應用程式, 使用者可以透過滑鼠進行點選, 模擬一個簡單的計算機的功能。 當然使用者也可以利用鍵盤來操作相同的界面, 其中 0-9, A-F, ., +, -, *, /, = (Keypad 上的也可以) 等按鍵就是這些按鈕的鍵盤界面, = 號的鍵盤界面還可以用 , +/- 號的鍵盤界面則為 Ctrl-'-', CE 的鍵盤界面是 Delete 按鍵, CC 的鍵盤界面是 BackSpace 按鍵, 十六進位的鍵盤界面則為 或是 Ctrl-H, 十進位的鍵盤界面是 或是 Ctrl-D, 八進位的鍵盤界面是 或是 Ctrl-O, 二進位的鍵盤界面是 或是 Ctrl-B。

視窗程式骨架:

為了配合使用 C++ 來製作物件化的程式, 這個骨架程式是利用 VC++/MFC 來製作的, 你可以在 Windows 95/98/ME/NT/2000 上面來開發, 程式碼壓縮起來在 CalWinV1Raw900311a.zip, 請下載, 並且用 VC 6.0 (如果是 6.0 以前的版本你需要重新建立 project workspace) 編譯, 編譯結果可以得到 CalWINV1.exe, 這個程式包含了所有的使用者介面 (顯示視窗、按鈕、及鍵盤介面), 如果你嘗試去執行它的話, 你可以發現所有的操作都有相對的螢幕反應, 但是就是缺了一個計算機的運算核心, 這一部分也就是此作業的主要部分了。

這個骨架程式的物件圖如下:

程式執行起來的時候會有一個 CCalWINV1App 的全域性物件產生 (在 CalWINV1.cpp 裡), 其建構元會引發一系列的動作, 其中包括呼叫下列函式: CCalWINV1Dlg 型態的 dlg 物件是螢幕上整個小算盤界面視窗在 C++ 程式內的相對物件,dlg.DoModal() 負責開啟視窗的動作, 並且含有維持視窗運作的訊息驅動迴圈, 此函式開始執行後, 視窗才會開啟在螢幕上, 等到使用者關閉這個視窗後, DoModal() 函式才會結束。

dlg 物件既然代表螢幕上的視窗界面, 所有使用者的操作與輸入動作也都由這個物件來負責接收與處理, 為了維持這個模組的獨立性, 我們讓 CCalWINV1Dlg 最主要負責第一線的視窗輸出與輸入, 這個物件和我們執行的 GUI 平台有密切關係, 資料輸入後程式的反應則不要直接加在這個物件裡, 所以我們設計了一個 CActionHandler 類別, 這個類別本來應該只是一個純粹的抽象類別 (Abstract Base Class), 目的是訂定 GUI 與真正系統模型之間的介面, 在這個作業裡我們為了要讓此骨架程式可以獨立編譯執行, 所以我們替所有 CActionHandler 類別的介面函式製作了簡單的實體。

你自己的程式不應該用 CActionHandler handler;, 而應該由 CActionHandler 類別衍生出你自己的類別來處理使用者的輸入, 例如 class CCalculator : public CActionHandler {....};, 上面的宣告也就可以改為 CCalculator calculator; 了, 當然,為了建立你自己這個 calculator 物件和 GUI 平台界面之間的關係, 你需要將 calculator 物件的位址傳給 dlg 物件, 同時你也需要在這個時候將 dlg 物件的參考記錄在 calculator 物件中, 以便 calculator 物件可以透過這個參考來改變視窗內的輸出。

注意:

輸出介面說明:

在 CCalWINV1Dlg 類別中提供下面幾個改變輸出的介面函式: 如果你的程式需要更改視窗中顯示的數字時, 請呼叫 updateDisplayPanel() 並且提供一個顯示字串給它, 例如: 如果你的程式不希望使用者隨時按下更改進位制度的按鈕的話, 可以呼叫 disableNumberSystemChange() 函式; 如果希望還原為可以更改的狀態的話請呼叫 enableNumberSystemChange() 函式。

輸入程式說明:

介面 (Interface) CActionHandler:

請繼承 CActionHandler 覆寫 (override) 其中的虛擬函式 (見下表)。

函式 呼叫時機
OnButton0(), OnButton1(), OnButton2(),
OnButton3(), OnButton4(), OnButton5(),
OnButton6(), OnButton7(), OnButton8(),
OnButton9(), OnButtonA(), OnButtonB(),
OnButtonC(), OnButtonD(), OnButtonE(),
OnButtonF(), OnButtonDot()
使用者按下 0-9, A-F, 和小數點按鈕時
OnButtonCC(), OnButtonCE() 使用者按下 CC 及 CE 按鈕時
OnButtonPlus(), OnButtonMinus(), OnButtonMult(),
OnButtonDiv(), OnButtonNeg(), OnButtonEqual()
分別在使用者按下 +, -, *, /, +/-, = 按鈕時
OnRadioBin(), OnRadioOct(),
OnRadioDec(), OnRadioHex()
分別在使用者按下 二進位, 八進位, 十進位, 十六進位 按鈕時

當使用者以滑鼠按下視窗中任一按鈕或是操作對等的鍵盤界面時, 相對應之函式將被視窗系統呼叫到, 如此你寫的程式可以一一處理使用者的輸入, 例如: 當使用者連續按下數字鈕時 (例如: '1'  '2'  '3'  '.'  '4'  '5'  '6'), 你的程式應該要一位數一位數地合成這些按鈕成為一數字 123.456, 其後當使用者按下 + - * / 按鈕時, 程式需要進行相對應的判斷與運算。

由於視窗圖形界面將所有的按鈕都顯示在視窗內, 使用者基本上可以用任何順序來操作這個介面, 例如使用者可以按下列按鈕 '1'  '2'  '.'  '3'  '.'  '5'  '*'  '+'  '0'  '2'  '7'  '=' 在前面這個範例中你知道出現第二個小數點時應該是錯的, 應該加以忽略,出現 '+' 號時也是有問題的, 可以忽略, 或是可以取代前面的 '*' 號, 出現 '0' 的時候則是完全多餘的。

這些條件測試如果要一一考量, 用 if/else 的語法來實作的話, 很容易一不小心就錯了, 通常我們可以運用狀態圖來輔助我們思考, 避免忽略考慮一些可能發生的狀況。

結合 C++ 版本的運算式計算類別

在這個作業中, 你須要運用上一次作業的 COperatorStack, COperandStack, 和 CCalculator 類別, 其中 CCalculator 類別物件因為負責輸入, 因此在此次作業中必須繼承自 CActionHandler 類別, 覆寫其中所有的虛擬函式來處理使用者的輸入, 同時該物件應該至少有下圖中的三種輸入狀態:
每一個輸入狀態下可能發生的輸入包括:

0-9 A-F . 的數字輸入按鈕
+ - * / +/- = 的符號按鈕
十六進位, 十進位, 八進位, 二進位等進位制轉換按鈕
CC, CE 的控制按鈕

請注意:

請根據下面的部分範例完成完整狀態圖

上面狀態圖中狀態轉換所伴隨的動作如下:

action0 no operation
action1 a. 在顯示欄位中顯示小數點
b. 小數點位數設為 0
action2 a. 清除顯示欄位
b. 顯示欄位中顯示 0
c. 執行 action1
action3 a. push operand to operand stack
b. process operator
c. 更新顯示
d. 允許使用者切換進位制

狀態圖的程式實作:

  1. 狀態本身需要在 CCalculator 類別內以一變數來實作, 例如:State CCalculator::m_state, State 為一 enum 自定型態: enum State {readyS, integerS, floatS};

  2. 當 CActionHandler 的衍生類別中的 OnButtonDot() 函式被呼叫的時候, 代表使用者按下界面中的小數點按鈕, 此時依照系統目前不同的輸入狀態會有不同的處理方式, 範例程式碼如下:

  3. 每一個 action##() 函式則如狀態轉換的動作表中所示來實作其內容

程式偵錯方法:

程式撰寫的時候難免會發生一些錯誤, 以物件導向的方法設計出來的程式, 一般來說透過封裝而使得錯誤的擴散效應 (ripple effect) 不大, 不是非常需要使用到 Visual Studio 的 Source Level Debugger, 如果你發現你非常依賴它的話, 那你設計程式的習慣一定有一些問題; 那你一定會問說 Source Level Debugger 做什麼用的, 既然寫出來就一定是有它的目的, 不是嗎? 對,我們常常在寫 MFC 的 GUI 程式時需要透過它來快速地斷定問題的發生點, 但是常常也止於此, 自己寫的程式邏輯如果需要使用 Source Level Debugger 才看得出問題的話, 那如果不是程式的架構太亂, 就是你沒有經過整體的設計了。

在所提供的程式骨架中有附加一個 dbwin32.exe 的程式, 這個程式配合 Visual C/C++ MFC 內提供的 TRACE() macro, 你應該可以很快地替自己的程式除錯, TRACE() 的用法和 printf() 幾乎是一樣的, 你只要用類似下面範例的寫法就可以在 dbwin32 的視窗中看到偵錯的結果:

請注意 Build/Set Active Configuration 必須要設為 Win32 Debug 模式, TRACE() 才能順利工作

程式測試:

圖形界面程式的測試是有學問的, 並不是每次都隨便測測就好的, 這個作業是一個相當有代表性的程式, 程式並不會很困難, 但是輸入的組合很多種, 每次你發現一種錯誤, 並且嘗試更改你的程式之後, 你能夠把之前測過的資料都完全測試一遍嗎? 當然你會說 "這怎麼可能,這樣測的話豈不累死", 可是你也知道每次當你修改你的程式以後, 都有一定的機率使得你改了程式的這一部分, 另外一部分原本對的卻被你改錯了, (物件導向程式中良好的封裝特性會讓這種可能性降低, 但是相信我, 這種強況還是很容易發生的), 那這樣子漫無目標地修改下去怎麼有可能對自己的程式有信心呢? 以前不是圖形界面的時候, 我們可以用 shell 程式來將測試過的資料記錄下來, 在作業一 Demo 批次測試的時候大家應該都有印象, 希望你也已經學會這樣子做了。

在測試你的程式之前需要根據程式的規格好好地分析一下可能出現的輸入狀況, 然後做一個批次檔案一一測試。 另外還最好能夠用程式產生隨機的輸入組合來測試, 可以測試合理的輸入, 也應該測試任意的輸入。

在測試圖形界面時我們也應該要這樣子做, 你可以在 CCalculator 類別內另外撰寫一個函式, 依照適當的順序來呼叫 OnButtonXXX() 以模擬使用者的操作, 例如:

就可以測試 123.45 * 67.8 = 這樣的運算式了。

程式繳交說明:

  1. 請說明你的程式所完成的功能, 哪些是要求的功能, 哪些是你自己設計的功能
  2. 請繪出系統的類別圖,並標示各類別之介面
  3. 請繪出輸入類別的狀態圖,並加以說明
  4. 請列印除了程式骨架之外的程式碼 (.h 及 .cpp 檔案),並加以說明, 如果你有較為特殊的設計的話, 請加以說明
  5. 請將自行設計的測試資料、測試目標、與測試結果印出來
  6. 請將程式目錄中 *.ncb *.plg *.aps ..\debug\*.* ..\release\*.* 的檔案刪除後 zip 起來,然後以附件形式 email 到 pyting@cs.ntou.edu.tw, 信件標題請依照下列範例撰寫:

程式 Demo 說明:

  1. 程式必須能夠現場編譯沒有錯誤
  2. 請說明你的測試方法與測試資料,並操作測試
  3. 請回答你的程式設計上的相關問題

程式擴充功能:

C++ 程式設計課程 首頁

製作日期: 03/10/2001 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@cs.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 理工學院 資訊科學系 Lagoon