C# 入門指南

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

~~學習進度表~~

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

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

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

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

y = a * x + b
m = y % n
r = m + diff

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

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

using System;

namespace EncryptDemo04
{
    class EncryptDemo04
    {
        // 宣告密碼表欄位
        public string code;

        // 建構子,呼叫建立密碼表的 SetCode() 方法
        public EncryptDemo04()
        {
            SetCode();
        }

        // 建立密碼表
        public void SetCode()
        {
            // 宣告變數
            int a, b, x, y, m;
            char c = 'a';
            // a 、 b 初始化
            Random r = new Random();
            a = r.Next(0, 10); 
            // 印出 a
            Console.Write("a:{0}, ", a);
            b = r.Next(0, 10); 
            // 印出 b
            Console.Write("b:{0}, ", b);
            code = "";
            // 建立密碼表的迴圈
            for (int i = 0; i < 26; i++)
            {
                x = c;
                y = x * a + b;
                m = y % 26;
                code += Convert.ToChar(m + 97);
                c++;
            }
        }

        // 編碼方法
        public string ToEncode(string s)
        {
            return s;
        }

        // 解碼方法
        public string ToDecode(string s)
        {
            return s;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 印出 20 個密碼表
            for (int i = 0; i < 20; i++)
            {
                // 建立密碼物件
                EncryptDemo04 e = new EncryptDemo04();
                // 印出密碼表
                Console.WriteLine(e.code);
            }
        }
    }
}

//《程式語言教學誌》的範例程式
// http://kaiching.org/
// 專案:EncryptDemo04
// 檔名:Program.cs
// 功能:示範利用 C# 設計 Encrypt 類別
// 作者:張凱慶

這裡在 SetCode() 中加入印出 a 、 b 值的程式碼

// a 、 b 初始化
Random r = new Random();
a = r.Next(0, 10); 
// 印出 a
Console.Write("a:{0}, ", a);
b = r.Next(0, 10); 
// 印出 b
Console.Write("b:{0}, ", b);

來執行測試看看囉

C:\EncryptDemo04> dotnet run
a:3, b:6, loruxadgjmpsvybehknqtwzcfi
a:4, b:6, eimquycgkoswaeimquycgkoswa
a:9, b:1, qzirajsbktcludmvenwfoxgpyh
a:6, b:8, syekqwciouagmsyekqwciouagm
a:1, b:1, uvwxyzabcdefghijklmnopqrst
a:2, b:6, suwyacegikmoqsuwyacegikmoq
a:1, b:9, cdefghijklmnopqrstuvwxyzab
a:8, b:9, fnvdltbjrzhpxfnvdltbjrzhpx
a:4, b:2, aeimquycgkoswaeimquycgkosw
a:0, b:4, eeeeeeeeeeeeeeeeeeeeeeeeee
a:4, b:8, gkoswaeimquycgkoswaeimquyc
a:8, b:9, fnvdltbjrzhpxfnvdltbjrzhpx
a:3, b:0, filoruxadgjmpsvybehknqtwzc
a:4, b:4, cgkoswaeimquycgkoswaeimquy
a:3, b:4, jmpsvybehknqtwzcfiloruxadg
a:1, b:2, vwxyzabcdefghijklmnopqrstu
a:6, b:4, ouagmsyekqwciouagmsyekqwci
a:7, b:3, gnubipwdkryfmtahovcjqxelsz
a:0, b:3, dddddddddddddddddddddddddd
a:5, b:0, rwbglqvafkpuzejotydinsxchm
C:\EncryptDemo04>

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

if (a % 2) != 0 {
   y = a * x + b
   m = y % n
   r = m + diff
}

這樣我們把發展中的專案版本 EncryptDemo04 修改為 EncryptDemo05 ,其中需要修改的部份只有 SetCode() ,如下

using System;

namespace EncryptDemo05
{
    class EncryptDemo05
    {
        // 宣告密碼表欄位
        public string code;

        // 建構子,呼叫建立密碼表的 SetCode() 方法
        public EncryptDemo05()
        {
            SetCode();
        }

        // 建立密碼表
        public void SetCode()
        {
            // 宣告變數
            int a, b, x, y, m;
            char c = 'a';
            // a 、 b 初始化
            Random r = new Random();
            a = 0;
            b = 0;
            // a 不能是 0 或偶數
            while (a % 2 == 0)
            {
                a = r.Next(1, 10);
            }
            b = r.Next(0, 10);
            Console.Write("a:{0}, ", a);
            Console.Write("b:{0}, ", b);
            code = "";
            // 建立密碼表的迴圈
            for (int i = 0; i < 26; i++)
            {
                x = c;
                y = x * a + b;
                m = y % 26;
                code += Convert.ToChar(m + 97);
                c++;
            }
        }

        // 編碼方法
        public string ToEncode(string s)
        {
            return s;
        }

        // 解碼方法
        public string ToDecode(string s)
        {
            return s;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 印出 20 個密碼表
            for (int i = 0; i < 20; i++)
            {
                // 建立密碼物件
                EncryptDemo05 e = new EncryptDemo05();
                // 印出密碼表
                Console.WriteLine(e.code);
            }
        }
    }
}

//《程式語言教學誌》的範例程式
// http://kaiching.org/
// 專案:EncryptDemo05
// 檔名:Program.cs
// 功能:示範利用 C# 設計 Encrypt 類別
// 作者:張凱慶

這裡先將 a 、 b 設定為整數 0 ,由於 a 不能是 0 偶數,所以就用 while 迴圈判斷 a 是否為奇數,直到 a 為奇數就結束迴圈

// a 、 b 初始化
Random r = new Random();
a = 0;
b = 0;
// a 不能是 0 或偶數
while (a % 2 == 0)
{
    a = r.Next(1, 10);
}
b = r.Next(0, 10);

重新執行,結果如下

C:\EncryptDemo05> dotnet run
a:9, b:1, qzirajsbktcludmvenwfoxgpyh
a:9, b:1, qzirajsbktcludmvenwfoxgpyh
a:1, b:6, zabcdefghijklmnopqrstuvwxy
a:1, b:1, uvwxyzabcdefghijklmnopqrst
a:5, b:0, rwbglqvafkpuzejotydinsxchm
a:7, b:1, elszgnubipwdkryfmtahovcjqx
a:7, b:0, dkryfmtahovcjqxelszgnubipw
a:5, b:8, zejotydinsxchmrwbglqvafkpu
a:1, b:1, uvwxyzabcdefghijklmnopqrst
a:9, b:5, udmvenwfoxgpyhqzirajsbktcl
a:5, b:5, wbglqvafkpuzejotydinsxchmr
a:5, b:9, afkpuzejotydinsxchmrwbglqv
a:5, b:4, vafkpuzejotydinsxchmrwbglq
a:3, b:1, gjmpsvybehknqtwzcfiloruxad
a:1, b:3, wxyzabcdefghijklmnopqrstuv
a:3, b:6, loruxadgjmpsvybehknqtwzcfi
a:9, b:7, wfoxgpyhqzirajsbktcludmven
a:3, b:0, filoruxadgjmpsvybehknqtwzc
a:3, b:7, mpsvybehknqtwzcfiloruxadgj
a:9, b:7, wfoxgpyhqzirajsbktcludmven
C:\EncryptDemo05>

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

下面我們要開始實作處理編碼的部分,也就是實作 ToEncode() 方法。

相關教學影片

上一頁 單元 16 - 繼續測試
回 C# 入門指南首頁
下一頁 單元 18 - 編碼
回 C# 教材首頁
回程式語言教材首頁
中英文術語對照
類別class
驅動器driver
例外exception
例外處理exception handling
識別字identifier
迴圈loop
執行期間錯誤run-time error
語意錯誤semantic error
語法錯誤syntax error
參考資料
1. C# 程式設計手冊 | Microsoft Docs - 類別
重點整理
1. 程式中可能會發生語法錯誤、執行期間錯誤或語意錯誤,編譯器會直接挑出語法錯誤,而執行期間錯誤可用例外處理機制來防範。
2. 發生語意錯誤的程式可順利執行跑出結果,可是結果不符合預期。
3. Encrypt 的數學公式經過再三的測試檢驗,發現問題出在 a 為 0 或偶數的情況,因此要避免 a 為 0 或偶數。
問題與討論
1. 什麼是語法錯誤?試舉出語法錯誤的五個例子。
2. 什麼是執行期間錯誤?想一想有哪些情況會發生執行期間錯誤?又要怎麼樣防範呢?
3. 為什麼語意錯誤最麻煩呢?
4. 我們是用什麼方式找出數學公式的錯誤呢?
練習
1. 承接上一個單元的猜數字遊戲,將新專案寫在 Exercise1701 中,在遊戲迴圈中用變數 times 計算使用者猜測的次數,並在遊戲結束時印出 times 。
2. 承上題,將新專案寫在 Exercise1702 中,在 Guess 類別設計一個檢查使用者輸入是否有重複數字的 FindNumber() 函數,結果回傳布林值。