C++ 入門指南 4.01
單元 17 - 修正後的數學公式
程式中可能會發生的錯誤有三種,分別是語法錯誤 (syntax error) 、執行期間錯誤 (run-time error) 及語意錯誤 (semantic error)
→ compiler or interpreter
Runtime Errors
→ exception handling
Semantic Errors
→ programmer
編譯器 (compiler) 會直接幫我們挑出語法錯誤,例如打錯識別字 (identifier) 名稱或是漏打分號等等。執行期間錯誤的話, C++ 另有例外處理 (exception handling) 的機制,讓我們可以處理丟出例外的狀況。三種錯誤中最麻煩的,就是語意錯誤了,因為有語意錯誤的程式,程式可以順利執行完畢,卻跑出錯誤的結果。
我們的 Encrypt 類別 (class) 目前正是碰到了發生語意錯誤的情況,這是說
m = y % n
r = m + diff
其中 a 與 b 若是 0 到 9 隨機整數,有些組合成立,可以得到正確結果,有些組合卻會得到錯誤的結果,這是為什麼呢?嗯,好麻煩唷!這樣就得討論好多數學,打斷我們發展程式的腳步,所以我們不打算仔細討論這背後的數學理論,我們繼續測試,直接來找出哪些組合會得到錯誤的結果吧!
要知道哪些組合可能會發生錯誤,我們就得知道 a 與 b 的值,這不難,印出來就看得到了
// 引入標準程式庫中的 cstdlib 及 ctime #include <cstdlib> // srand(), rand() #include <ctime> // time() // 引入標準程式庫中的 iostream #include <iostream> // cout, endl // 引入 Encrypt 類別的標頭檔 #include "encrypt01.h" // 使用 std 中的兩個名稱 using std::cout; // 標準輸出串流的物件 using std::endl; // 新行符號,等於 '\n' // Encrypt 的建構函數 Encrypt::Encrypt() { // 呼叫 setter 設定 code_array set_code_array(); } // 設定 code_array 的 setter 成員函數 void Encrypt::set_code_array() { // 初始化擬隨機數產生器 srand(time(0)); // 取得 a 、 b 值 int a = rand() % 10; cout << a << ", "; // 印出 a 的值 int b = rand() % 10; cout << b << ", "; // 印出 b 的值 // 利用公式建立密碼表字串 int x, y, m; char c = 'a'; string s; int i; for (i = 0; i < 26; i++) { x = c; y = x * a + b; m = y % 26; s += m + 97; c++; } // 將建立好的密碼表直接設定給成員變數 code_array = s; } // 回傳密碼表字串的 getter 成員函數 string Encrypt::get_code_array() { return code_array; } /* 《程式語言教學誌》的範例程式 http://kaiching.org/ 檔名:encrypt03.cxx 功能:實作發展中版本的 Encrypt 類別 作者:張凱慶 */
這裡在 set_code_array() 加入印出 a 、 b 值的程式碼,因為 a 、 b 是屬於 set_code_array() 的區域變數 (local variable) ,因此只能在 set_code_array() 裡使用這兩個變數 (variable) ,記得,因為用到 cout 與 endl ,所以 encrypt03.cxx 要先引進 iostream 然後 using cout 與 endl 。
來編譯測試看看囉
第一次, a 為 1 , b 為 2 沒出問題,再多測試幾次看看
跑了幾次下來,似乎 a 為偶數或 0 就會跑出不符預期的結果,那我們就把 a 改成不是偶數或 0 好了!公式修改如下
y = a * x + b
m = y % n
r = m + diff
}
這樣我們把發展中的版本 encrypt03.cxx 修改為 encrypt04.cxx ,其中需要修改的部份只有 set_code_array() ,如下
// 引入標準程式庫中的 cstdlib 及 ctime #include <cstdlib> // srand(), rand() #include <ctime> // time() // 引入標準程式庫中的 iostream #include <iostream> // cout, endl // 引入 Encrypt 類別的標頭檔 #include "encrypt01.h" // 使用 std 中的兩個名稱 using std::cout; // 標準輸出串流的物件 using std::endl; // 新行符號,等於 '\n' // Encrypt 的建構函數 Encrypt::Encrypt() { // 呼叫 setter 設定 code_array set_code_array(); } // 設定 code_array 的 setter 成員函數 void Encrypt::set_code_array() { // 初始化擬隨機數產生器 srand(time(0)); // 取得 a 、 b 值,其中 a 不等於 0 或偶數 int a = 0; int b = 0; while (a % 2 == 0) { // rand() 回傳 0 到 RAND_MAX 之間的擬隨機整數 a = rand() % 10; b = rand() % 10; } // 印出 a 、 b 值 cout << a << ", "; cout << b << ", "; // 利用公式建立密碼表字串 int x, y, m; char c = 'a'; string s; int i; for (i = 0; i < 26; i++) { x = c; y = x * a + b; m = y % 26; s += m + 97; c++; } // 將建立好的密碼表直接設定給成員變數 code_array = s; } // 回傳密碼表字串的 getter 成員函數 string Encrypt::get_code_array() { return code_array; } /* 《程式語言教學誌》的範例程式 http://kaiching.org/ 檔名:encrypt04.cxx 功能:實作發展中版本的 Encrypt 類別 作者:張凱慶 */
重新編譯執行,結果如下
我們先把 a 與 b 的初值設為 0 ,然後以迴圈 (loop) 取得隨機的 a 、 b 值,用 a 除以 2 的餘數為 0 當作迴圈的執行條件,因此當 a 等於 0 或偶數時,迴圈就會持續進行,直到 a 不為 0 或偶數為止。肉眼檢查下,似乎只要 a 為奇數,計算出的結果就不會有問題囉!
下面我們要開始實作處理編碼的部分,也就是實作 ToEncode() 成員函數 (member function) 。
中英文術語對照 | |
---|---|
類別 | class |
編譯器 | compiler |
例外處理 | exception handling |
識別字 | identifier |
迴圈 | loop |
區域變數 | local variable |
成員函數 | member function |
執行期間錯誤 | run-time error |
語意錯誤 | semantic error |
語法錯誤 | syntax error |
變數 | variable |
重點整理 |
---|
1. 程式中可能會發生語法錯誤、執行期間錯誤或語意錯誤,編譯器會直接挑出語法錯誤,而執行期間錯誤可用例外處理機制來防範。 |
2. 發生語意錯誤的程式可順利執行跑出結果,可是結果不符合預期。 |
3. Encrypt 的數學公式經過再三的測試檢驗,發現問題出在 a 為 0 或偶數的情況,因此要避免 a 為 0 或偶數。 |
問題與討論 |
---|
1. 什麼是語法錯誤?試舉出語法錯誤的五個例子。 |
2. 什麼是執行期間錯誤?想一想有哪些情況會發生執行期間錯誤?又要怎麼樣防範呢? |
3. 為什麼語意錯誤最麻煩呢? |
4. 我們是用什麼方式找出數學公式的錯誤呢? |
練習 |
---|
1. vector 可以用 erase() 移除元素, erase() 的參數為迭代器, begin() 為 vector() 的第一個元素, end() 為最後一個元素,寫一個程式 exercise1701.cxx ,宣告建立一個整數型態的 vector ,然後先移除掉第一個元素,繼續移除掉索引值 2 之後的元素,並把移除結束後的所有元素內容印出來。 參考程式碼 |
2. 承上題, vector 的 push_back() 可以在 vector 的最後加入指定元素,寫一個程式 exercise1702.cxx ,宣告建立一個整數型態的 vector ,然後用 push_back() 分別加入兩個整數,並把加入結束後的所有元素內容印出來。 參考程式碼 |
3. 承上題, vector 的 pop_back() 可以直接移除最後一個元素,寫一個程式 exercise1703.cxx ,宣告建立一個整數型態的 vector ,然後用 pop_back() 分別移除兩個元素,並把移除結束後的所有元素內容印出來。 參考程式碼 |
4. 承上題, vector 的 resize() 可以重新調整元素個數,若參數大於元素個數就自動補 0 ,反之小於元素個數會刪除索引值在後的元素,寫一個程式 exercise1704.cxx ,宣告建立一個整數型態的 vector ,然後用 resize() 分別調整兩次元素個數,並把調整結束後的所有元素內容印出來。 參考程式碼 |
5. 承上題, vector 的 swap() 可以對換不同 vector 的元素,寫一個程式 exercise1705.cxx ,宣告建立兩個整數型態的 vector ,然後用 swap() 對換元素內容,並把調整結束後的所有元素內容印出來。 參考程式碼 |
6. 標準程式庫中 algorithm 的 shuffle() 可以攪亂集合體型態中的元素順序,需要三個參數,第一個為集合體型態元素的起始指標,第二個為結束指標,第三個為擬隨機數產生器,寫一個程式 exercise1706.cxx ,宣告建立一個整數型態的陣列,然後用 shuffle() 攪亂元素順序,以 default_random_engine(0) 當作第三個參數,最後把所有元素內容印出來。 參考程式碼 |
7. 承上題,寫一個程式 exercise1707.cxx ,將 default_random_engine(0) 改成 mt19937() 並以系統時間當做種子,最後把所有元素內容印出來。 參考程式碼 |
8. 承上題,寫一個程式 exercise1708.cxx ,將 shuffle() 換成 random_shuffle() ,呼叫 random_shuffle() 不需要提供第三個參數,最後把所有元素內容印出來。 參考程式碼 |
9. 承上題,寫一個程式 exercise1709.cxx ,加入一個產生隨機數的函數 myrandom() ,並以 myrandom 當作 random_shuffle() 的第三個參數,最後把所有元素內容印出來。 參考程式碼 |
10. 承上題,寫一個程式 exercise1710.cxx ,將陣列改成 vector ,最後把所有元素內容印出來。 參考程式碼 |
相關教學影片