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

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

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

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() 中加入印出 ab 值的程式碼

// 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 類別
// 作者:張凱慶

這裡先將 ab 設定為整數 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>

我們先把 ab 的初值設為 0 ,然後以迴圈 (loop) 取得隨機的 ab 值,用 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 的數學公式經過再三的測試檢驗,發現問題出在 a0 或偶數的情況,因此要避免 a0 或偶數。
問題與討論
1. 什麼是語法錯誤?試舉出語法錯誤的五個例子。
2. 什麼是執行期間錯誤?想一想有哪些情況會發生執行期間錯誤?又要怎麼樣防範呢?
3. 為什麼語意錯誤最麻煩呢?
4. 我們是用什麼方式找出數學公式的錯誤呢?
練習
1. 承接上一個單元的猜數字遊戲,將新專案寫在 Exercise1701 中,在遊戲迴圈中用變數 times 計算使用者猜測的次數,並在遊戲結束時印出 times
2. 承上題,將新專案寫在 Exercise1702 中,在 Guess 類別設計一個檢查使用者輸入是否有重複數字的 FindNumber() 函數,結果回傳布林值。