C++ 入門指南 V2.00 - 單元 17 - 修正後的數學公式




程式中可能會發生的錯誤有三種,分別是語法錯誤 (syntax error) 、執行期間錯誤 (run-time error) 及語意錯誤 (semantic error)



編譯器 (compiler) 會直接幫我們挑出語法錯誤,例如打錯識別字 (identifier) 名稱或是漏打分號等等。執行期間錯誤的話, C++ 另有例外處理 (exception handling) 的機制,讓我們可以處理丟出例外的狀況。三種錯誤中最麻煩的,就是語意錯誤了,因為有語意錯誤的程式,程式可以順利執行完畢,卻跑出錯誤的結果。


我們的 Encrypt 類別 (class) 目前正是碰到了發生語意錯誤的情況,這是說


y = a * x + b

m = y % n

r = m + diff


其中 ab 若是 09 隨機整數,有些組合成立,可以得到正確結果,有些組合卻會得到錯誤的結果,這是為什麼呢?嗯,好麻煩唷!這樣就得討論好多數學,打斷我們發展程式的腳步,所以我們不打算仔細討論這背後的數學理論,我們繼續測試,直接來找出哪些組合會得到錯誤的結果吧!


要知道哪些組合可能會發生錯誤,我們就得知道 ab 的值,這不難,印出來就看得到了


001 // 引入標準程式庫中的 cstdlib 及 ctime
002 #include <cstdlib> // srand(), rand()
003 #include <ctime>   // time()
004
005 // 引入標準程式庫中的 iostream
006 #include <iostream> // cout, endl
007
008 // 引入 Encrypt 類別的標頭檔 
009 #include "encrypt01.h"
010
011 // 使用 std 中的兩個名稱
012 using std::cout// 標準輸出串流的物件
013 using std::endl// 新行符號,等於 '\n'
014
015 // Encrypt 的建構函數 
016 Encrypt::Encrypt() {
017    // 呼叫 setter 設定 code_array
018    set_code_array();    
019 }
020
021 // 設定 code_array 的 setter 成員函數
022 void Encrypt::set_code_array() {
023    // 初始化擬隨機數產生器 
024    srand(time(0));
025
026    // 取得 a 、 b 值 
027    int a = rand() % 10;
028    cout << a << ", "// 印出 a 的值
029    int b = rand() % 10;
030    cout << b << ", "// 印出 b 的值
031
032    // 利用公式建立密碼表字串     
033    int x, y, m;
034    char c = 'a';
035    string s;
036    int i;
037    for (i = 0; i < 26; i++) {
038       x = c; 
039       y = x * a + b;
040       m = y % 26;
041       s += m + 97; 
042       c++; 
043    }
044
045    // 將建立好的密碼表直接設定給成員變數
046    code_array = s;
047 }
048
049 // 回傳密碼表字串的 getter 成員函數     
050 string Encrypt::get_code_array() {
051    return code_array;
052 }
053
054 /* encrypt03.cpp 
055    Kaiching Chang 
056    2014-5 */

這裡在 set_code_array() 加入印出 ab 值的程式碼,因為 ab 是屬於 set_code_array() 的區域變數 (local variable) ,因此只能在 set_code_array() 裡使用這兩個變數 (variable) ,記得,因為用到 coutendl ,所以 encrypt03.cpp 要先引進 iostream 然後 using


來編譯測試看看囉



第一次, a2b4 就出問題,再多測試幾次看看



跑了幾次下來,似乎 a 為偶數或 0 就會跑出不符預期的結果,那我們就把 a 改成不是偶數或 0 好了!公式修改如下


if (a % 2) != 0 {

   y = a * x + b

   m = y % n

   r = m + diff

}


這樣我們把發展中的版本 encrypt03.cpp 修改為 encrypt04.cpp ,其中需要修改的部份只有 set_code_array() ,如下


021 // 設定 code_array 的 setter 成員函數
022 void Encrypt::set_code_array() {
023    // 初始化擬隨機數產生器 
024    srand(time(0));
025     
026    // 取得 a 、 b 值,其中 a 不等於 0 或偶數 
027    int a = 0;
028    int b = 0;
029    while (a % 2 == 0) {
030       // rand() 回傳 0 到 RAND_MAX 之間的擬隨機整數
031       a = rand() % 10;
032       b = rand() % 10;
033    }
034     
035    // 印出 a 、 b 值
036    cout << a << ", "
037    cout << b << ", "
038
039    // 利用公式建立密碼表字串     
040    int x, y, m;
041    char c = 'a';
042    string s;
043    int i;
044    for (i = 0; i < 26; i++) {
045       x = c; 
046       y = x * a + b;
047       m = y % 26;
048       s += m + 97; 
049       c++; 
050    }
051
052    // 將建立好的密碼表直接設定給成員變數
053    code_array = s;
054 }

重新編譯執行,結果如下



我們先把 ab 的初值設為 0 ,然後以迴圈 (loop) 取得隨機的 ab 值,用 a 除以 2 的餘數為 0 當作迴圈的執行條件,因此當 a 等於 0 或偶數時,迴圈就會持續進行,直到 a 不為 0 或偶數為止。肉眼檢查下,似乎只要 a 為奇數,計算出的結果就不會有問題囉!


下面我們要開始實作處理編碼的部分,也就是實作 ToEncode() 成員函數 (member function) 。


中英文術語對照


語法錯誤 syntax error
執行期間錯誤 run-time error
語意錯誤 semantic error
編譯器 compiler
識別字 identifier
例外處理 exception handling
類別 class
區域變數 local variable
變數 variable
迴圈 loop
成員函數 member function

重點整理


  1. 程式中可能會發生語法錯誤、執行期間錯誤或語意錯誤,編譯器會直接挑出語法錯誤,而執行期間錯誤可用例外處理機制來防範。
  2. 發生語意錯誤的程式可順利執行跑出結果,可是結果不符合預期。
  3. Encrypt 的數學公式經過再三的測試檢驗,發現問題出在 a0 或偶數的情況,因此要避免 a0 或偶數。

問題與討論


  1. 什麼是語法錯誤?試舉出語法錯誤的五個例子。
  2. 什麼是執行期間錯誤?想一想有哪些情況會發生執行期間錯誤?又要怎麼樣防範呢?
  3. 為什麼語意錯誤最麻煩呢?
  4. 我們是用什麼方式找出數學公式的錯誤呢?

練習


  1. 承接上一個單元的猜數字遊戲,將新程式寫在 exercise1701.cpp 中,在遊戲迴圈中用變數 times 計算使用者猜測的次數,並在遊戲結束時印出 times
  2. 承上題,將新程式寫在 exercise1702.cpp 中,試著加入一個整數參數 lengthArrayNumber() 裡, length 取代固定的數字 4
  3. 承上題,將新程式寫在 exercise1703.cpp 中,將原本的數字 1000 改成用標準程式庫 cmathpow() 計算。

the end

沒有留言: