實習目標 | 本實驗接續Lab 1-1, 在 Visual C++ 環境中熟悉使用 assert() 巨集 (macro) 來驗證/確保 C/C++ 程式在開發過程中不斷修改或是增加功能時的正確性 |
---|---|
步驟一 | 請下載 statisticsAndSort.exe 程式, 這個程式基本上讀取一個資料檔案raw1.dat, 計算檔案內所有資料的平均值, 各種資料出現的中間值, 以及出現頻率最高的數值。 在 Lab 1-1 中我們完成了一個分開在多個 .cpp 檔案裡的 程式, 但是這個程式基本上還有很多的假設與很多的漏洞。 請下載資料檔案raw2.dat 以及 raw3.dat 執行, 你會發現一些問題。 例如輸入 raw2.dat 時好像資料量太多了, 輸入 raw3.dat 時有一些資料好像超過範圍 (1-9), 或是輸入一個不存在的檔案名稱 (例如 raw4.dat) 時程式都應該會出問題。 請依照下列的步驟在程式適當的地方加入 assert() 的敘述來確保我們在撰寫程式時的一些假設。 |
步驟二 | 在 readFile() 函式中有下列敘述:
gets(filename); fp = fopen(filename, "rt"); fscanf(fp, "%d", dataSize);在撰寫上述程式片段時其實有一個假設存在, 就是 "gets(filename) 所讀到使用者輸入的檔案名稱所對應的檔案是存在的", 所以程式才沒有去偵測如果呼叫 fopen() 開啟檔案時失敗的話該如何處理。 假設我們希望在錯誤的時候直接結束程式的話, 我們可以運用 assert() 函式來確保這個假設是正確的。 我們將程式改為 gets(filename); fp = fopen(filename, "rt"); assert(fp != 0); fscanf(fp, "%d", dataSize); 就可以自動的驗證這個假設。 請修改你的程式加上 assert() 巨集的呼叫, 注意要使用 assert() 必須要加上 #include<assert.h> , assert() 巨集的用法如果不曉得的話, 請查詢 MSDN Library, 然後再試看看輸入一個錯誤的檔案名稱會發生什麼事情 通常發生 assert 錯誤時有經驗的程式設計者會直接按 "重試" 進入 debugger 確定到底是哪裡出錯了(以後再談) |
步驟三 | 請在程式適當的地方加上 assert() 敘述以保證檔案中讀到的資料個數不大於陣列的大小 DATASIZE (這是一個用前處理器 #define 命令定義的常數, 在哪一個檔案裡定義的, 就只有那個檔案用得到, 當然最簡單的方式就是在每一個檔案中都用 #define 重新定義過, 可是這樣就出現重複(redundancy), 同樣的定義出現在很多地方, 以後修改時很容易成為 bug 的來源。所以請多產生一個檔案 main.h, 把 #define DATASIZE 150 移到 main.h 中, 需要用到 DATASIZE 的 .cpp 檔案裡再加上 #include "main.h"; 另外一種方式就是用函式的參數來傳遞 ) |
步驟四 | 請在程式適當的地方加上 assert() 敘述以保證檔案中讀到的資料範圍都在 1 和 9 之間 |
步驟六 | 請在程式適當的地方加上 assert() 敘述以保證 sort() 函式所得到的結果是對的
(我們不希望每次都用眼睛檢查那些輸出的百筆資料來驗證排序是否正確)。 通常我們會用別的演算法來驗證,
例如一個排序過後的陣列裡面的元素應該是由大到小的排列, 所以我們可以另外寫一個小函式來驗證陣列中第 i 個元素是不是大於等於第 i+1 個元素。
例如: int isDecending(int a[], int size) { int i; for (i=1; i<size; i++) if (a[i]>a[i-1]) return 0; return 1; } |
步驟七 | 因為你在上一個步驟已經加上驗證 sort() 結果的 assert()
敘述了, 所以你現在可以輕鬆地應付兩種狀況:
#include <stdlib.h> int compare(const void *elem1, const void *elem2); void main() { int a[] = {5, 3, 4, 1, 2}; int numElements = 5; qsort(a, numElements, sizeof(int), compare); for (i=0; i<numElements; i++) printf("%d ", a[i]); printf("\n"); } int compare(const void *elem1, const void *elem2) { if (*(int *)elem1 < *(int *)elem2) return -1; else if (*(int *)elem1 == *(int *)elem2) return 0; else return 1; } 或是簡化一下 int compare(const void *elem1, const void *elem2) { return *(int *)elem1 - *(int *)elem2; }上面這段程式你也許會直接拷貝進到你的程式裡測試, 不過你要曉得用拷貝的和你自己思考一下, 重新寫出來的效果是非常不一樣的, 省掉這一點點時間也許就造成你只記得要使用 qsort 時就 copy-and-paste, 那就糟了, 你要記得的是 qsort 的使用模型:
這才是你在練習過以後應該要知道的東西 (你花一些時間練習好英文打字以後, 這種程式片段需要直接思考, 直接寫出來, 不需要邊看編寫, 我們也會慢慢地把關鍵程式碼從網頁中移除) 也許你從來沒有看過上面範例的用法, 或是覺得這樣子的 C 程式的語法上有點生疏, 分成三部份解釋一下:
|
步驟八 | 想想看這個程式裡還有什麼地方有假設可以確認的?
|
步驟九 | 請助教檢查後, 將所完成的 專案 (只需保留 .cpp, .h, .sln 以及 .vcxproj 檔案即可; 刪除掉 .sdf, .filters, .users, debug/ 資料匣, 以及 ipch/ 資料匣下的所有內容) 以 zip/rar/7zip 程式將整個資料匣壓縮起來, 登入上傳網頁, 選擇 Lab1-2, 下星期的實習課程需要使用你今天所完成的程式 |
步驟十 | 請注意, assert() 是程式開發人員用的工具, 在程式開發完成交給使用者的時候, 程式裡不應該有這樣子的敘述... 難道好不容易加進去的東西還要一列一列拿掉嗎? 不不不!!! 用下面的敘述可以讓某一個檔案裡所有的 assert() 敘述都不發生作用, #define NDEBUG 需要在每一個檔案的開頭(#include <assert.h> 之前)都定義一次。 另外一種方法是使用編譯器的旗標來控制, 在選單中選取 專案 / lab1-2 屬性 / 組態屬性 / C/C++ / 前置處理器 => 在前置處理器定義最後加入 assert() 是 ANSI C 和 ISO C++ 都有提供的巨集 後續大家也許會嘗試運用不同的單元測試工具, 例如 CppUnit, Google Test, NUnit, 或是 MS 的 CppUnitTestFramework, 這些環境下都有自己定義的 assert 巨集, 例如 CppUnit 的 CPP_ASSERT_XXXXX 巨集 |
回
C++ 物件導向程式設計課程 首頁 製作日期: 02/20/2013 by 丁培毅 (Pei-yih Ting) E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615 海洋大學 電機資訊學院 資訊工程學系 Lagoon |