C++ 入門指南 4.01

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

-unit17-

程式中可能會發生的錯誤有三種,分別是語法錯誤 (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

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

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

// 引入標準程式庫中的 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() 加入印出 ab 值的程式碼,因為 ab 是屬於 set_code_array()區域變數 (local variable) ,因此只能在 set_code_array() 裡使用這兩個變數 (variable) ,記得,因為用到 coutendl ,所以 encrypt03.cxx 要先引進 iostream 然後 using coutendl

來編譯測試看看囉

-encrypt0301-

第一次, a1b2 沒出問題,再多測試幾次看看

-encrypt0302-

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

if (a % 2) != 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 類別
   作者:張凱慶 */

重新編譯執行,結果如下

-encrypt04-

我們先把 ab 的初值設為 0 ,然後以迴圈 (loop) 取得隨機的 ab 值,用 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 的數學公式經過再三的測試檢驗,發現問題出在 a0 或偶數的情況,因此要避免 a0 或偶數。
問題與討論
1. 什麼是語法錯誤?試舉出語法錯誤的五個例子。
2. 什麼是執行期間錯誤?想一想有哪些情況會發生執行期間錯誤?又要怎麼樣防範呢?
3. 為什麼語意錯誤最麻煩呢?
4. 我們是用什麼方式找出數學公式的錯誤呢?
練習
1. vector 可以用 erase() 移除元素, erase() 的參數為迭代器, begin()vector() 的第一個元素, end() 為最後一個元素,寫一個程式 exercise1701.cxx ,宣告建立一個整數型態的 vector ,然後先移除掉第一個元素,繼續移除掉索引值 2 之後的元素,並把移除結束後的所有元素內容印出來。 參考程式碼
2. 承上題, vectorpush_back() 可以在 vector 的最後加入指定元素,寫一個程式 exercise1702.cxx ,宣告建立一個整數型態的 vector ,然後用 push_back() 分別加入兩個整數,並把加入結束後的所有元素內容印出來。 參考程式碼
3. 承上題, vectorpop_back() 可以直接移除最後一個元素,寫一個程式 exercise1703.cxx ,宣告建立一個整數型態的 vector ,然後用 pop_back() 分別移除兩個元素,並把移除結束後的所有元素內容印出來。 參考程式碼
4. 承上題, vectorresize() 可以重新調整元素個數,若參數大於元素個數就自動補 0 ,反之小於元素個數會刪除索引值在後的元素,寫一個程式 exercise1704.cxx ,宣告建立一個整數型態的 vector ,然後用 resize() 分別調整兩次元素個數,並把調整結束後的所有元素內容印出來。 參考程式碼
5. 承上題, vectorswap() 可以對換不同 vector 的元素,寫一個程式 exercise1705.cxx ,宣告建立兩個整數型態的 vector ,然後用 swap() 對換元素內容,並把調整結束後的所有元素內容印出來。 參考程式碼
6. 標準程式庫中 algorithmshuffle() 可以攪亂集合體型態中的元素順序,需要三個參數,第一個為集合體型態元素的起始指標,第二個為結束指標,第三個為擬隨機數產生器,寫一個程式 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 ,最後把所有元素內容印出來。 參考程式碼

相關教學影片

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