C++ 入門指南

單元 17 - 修正後的數學公式

本書已有新版,請參考 C++ 入門指南 4.01 - 單元 17 - 修正後的數學公式

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

Syntax Errors
→ compiler or interpreter
Runtime Errors
→ exception handling
Semantic Errors
→ programmer

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

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

y = a * x + b
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.cpp
   功能:實作發展中版本的 Encrypt 類別
   作者:張凱慶 */

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

來編譯測試看看囉

-encrypt0301-

第一次, a 為 8 , b 為 0 就出問題,再多測試幾次看看

-encrypt0302-

跑了幾次下來,似乎 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() ,如下

// 引入標準程式庫中的 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.cpp
   功能:實作發展中版本的 Encrypt 類別
   作者:張凱慶 */

重新編譯執行,結果如下

-encrypt04-

我們先把 a 與 b 的初值設為 0 ,然後以迴圈 (loop) 取得隨機的 a 、 b 值,用 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 的數學公式經過再三的測試檢驗,發現問題出在 a 為 0 或偶數的情況,因此要避免 a 為 0 或偶數。
問題與討論
1. 什麼是語法錯誤?試舉出語法錯誤的五個例子。
2. 什麼是執行期間錯誤?想一想有哪些情況會發生執行期間錯誤?又要怎麼樣防範呢?
3. 為什麼語意錯誤最麻煩呢?
4. 我們是用什麼方式找出數學公式的錯誤呢?
練習
1. 承接上一個單元的猜數字遊戲,將新程式寫在 exercise1701.cpp 中,在遊戲迴圈中用變數 times 計算使用者猜測的次數,並在遊戲結束時印出 times 。
2. 承上題,將新程式寫在 exercise1702.cpp 中,試著加入一個整數參數 length 到 ArrayNumber() 裡, length 取代固定的數字 4 。
3. 承上題,將新程式寫在 exercise1703.cpp 中,將原本的數字 1000 改成用標準程式庫 cmath 的 pow() 計算。

上一頁 單元 16 - 繼續測試
回 C++ 入門指南目錄
下一頁 單元 18 - 編碼
回 C++ 教材
回程式語言教材首頁