--- tags: 視窗程式設計 --- # 單位轉換程式:建立函式(方法) 接下來我們幫同學建立「函式」的概念,還記得前述的說明嗎? :::info 1. 你需要重複貼上好多程式碼 2. 因為每一個單位轉換都需要仔細的改程式,如果要測試,很麻煩 3. 如果有錯,還要把所有程式都找過一次,很痛苦 ::: 你可以檢視目前的程式,你一定會發現:**==很多地方看起來都很像==**。 我們有沒有辦法把一些看起來很類似的程式,通通打包起來,寫在某個地方,然後如果我要使用的時候再來「呼叫」這段打包好的程式?就不用一直重複複製貼上這些類似的程式。 當然可以,這就是「**函式(方法)**」最重要的用途!簡單來說,**函式就是把常用的程式碼打包裝在一個區塊中,方便我們在寫程式時隨時呼叫、使用**。 同學可以先參考下圖: ![](https://i.imgur.com/QmBnp8o.png) 先看左側,程式最基本的執行流程就是一步一步往下執行,依照步驟完成工作,如果某段程式是會重複在不同階段執行,你可能需要不斷的重複複製貼相同的程式碼。程式會變的又臭又長,難以閱讀,又難以維護。 所以我們**可以將一大段程式打包起來,需要的時候才呼叫,只需要一行就能執行這一大段程式碼**(請看右側),而且打包起來的程式有一定的獨立性,當你需要除錯、修改,只需要修改函式就好。 :::info 適度的將重複性高的程式「打包」成函式(方法),能讓程式看起來更簡潔! ::: ## 函式的觀念可以想成販賣機 ![](https://i.imgur.com/glcO2dp.jpg =500x) **你一定用過販賣機,投幣之後,按照你的選擇(輸入),他會給你不同的商品(結果)。** 函式也是類似的概念,你設計好一個函式,可以給予不同的輸入值,經過函式內的程式運算,得到值。 以下是一個販賣機的「想像函式」: ```csharp= 販賣機($_money, $_number){ $drink = "沒有飲料"; if ($_money == 30){ switch ($_number){ case 0: $drink = "可樂"; break; case 1: $drink = "綠茶"; break; } } return $drink; } 販賣機(30, 1); // 販賣機掉出綠茶 販賣機(20, 1); // 販賣機不會有飲料出來 販賣機(30, 3); // 販賣機不會有飲料出來 ``` ## 函式 Function 的介紹 當程式越來越複雜的時候,就必須將一些重複、或者需要獨立處理,有特別用途的程式,拆開整理成容易管理的程式片段,這些小程式就稱為「**函式**」。 :::info 函式是一種有名稱(類似變數)且獨立的程式片段,可以接收任何型態的參數,處理完成後也可以輸出任何型態的結果。 ::: 最簡單的函式可以是這個樣子的: :::success 假設我們要計算「兩個數值相加」,所以我們設計一個函式,我們名稱就稱為「Add」,並且這個函式可以輸入任何兩個數值,然後可以回傳結果。 ::: ```csharp= // 存取修飾詞 回傳數值的資料型態 函数名稱(參數列表) public int Add(int a, int b) { int sum = a + b; // 函式裡面的程式,通常要縮排 return sum; // 函式的程式要回傳出來的結果 } // 使用函式 int result = Add(5, 3); // 執行 Add 函式,並且輸入 5 和 3 Console.WriteLine(result); // 函式傳出來的結果:8 ``` 程式碼簡單說明如下: :::danger 第2至7行: 1. 定義了一個名為 Add 的函式,該函式接收兩個整數型態的參數 a 和 b,並且回傳一個整數值。 2. 在函式內部,將參數 a 與 b 相加,並將結果儲存在一個稱為 sum 的整數型態變數中。 3. 通過 return 關鍵字,將計算結果 sum 回傳給呼叫該函式的程式碼。 第10與11行則是使用這個函式: 1. 如果要執行這個「Add函式」,你只要把 Add 打出來,用括號把兩個整數值 (5和3) 包起來,然後用逗點隔開 5 和 3。 2. 5 + 3 的結果會儲存在 result 變數中,然後使用 Console.WriteLine 將結果輸出到控制台上。 3. 由於我們傳入的是 5 和 3,所以 Add 函式會計算它們的和,結果是 8。 ::: ## 函式的撰寫要注意的地方 * **在函式內部要執行的程式碼要用一組大括號 { } 包裹** 你可以仔細觀察之前寫的範例函式,有沒有注意到兩個數值 a 與 b 相加的程式碼,被包裹在一組大括號裡面? :::success 通常,我們會把一組大括號的左右符號,都用一行來寫,程式碼則是縮排,目的是這樣比較好閱讀。 ::: * **回傳值的資料形態** 函式有其回傳值。一個函式在定義時,就要先定義其回傳值的資料形態,此項寫法如:int、string、double、或是類別名。若不將此函式設回傳值,則此項須寫為void。若回傳值的資料形態不為void,表示函式一定要寫回傳值。反之,若回傳值的資料形態為void,表示函式就不能寫回傳值。 * **參數列表** 通常大概寫成用括號包裹所有的參數列表,例如:*(參數型別 參數1, 參數型別 參數2 ...)* 用來設定該函式的所有參數,參數的數目可以任意多個,也可以完全都沒有,參數是由函式外部輸入,並在函式內部的程式碼產生作用。 ### 實際上也很重要,但以下可以先跳過 * **存取修飾詞:可寫可不寫**。共有三種寫法: 1. public:代表此函式能夠讓程式中所有類別存取。 2. protected:代表此函式只能夠讓其所屬的類別、及這個類別的子類別存取。 3. private:代表此函式只能夠讓其所屬的類別存取。 4. 若不寫,則預設為private。 * **static:可寫可不寫** 若有寫,則此函式就是「靜態函式」,靜態函式只能對其所屬類別中的靜態變數做存取,而不能存取非靜態變數。若不寫,則此函式就是「非靜態函式」,非靜態函式則可以存取靜態變數與非靜態變數兩者。 ## 函式的類型 無論是哪一種程式語言,函式都可以寫成四種型式,以下是 C# 中的寫法: 1. **沒有參數也沒有回傳值** ```csharp= void 函式名稱() { 你的程式碼; } 函式名稱(); //執行這個函式 ``` 如果你看到你的方法有**void,代表沒有回傳值** 2. **有參數、沒有回傳值** ```csharp= void 函式名稱(參數型別 參數1, 參數型別 參數2 ...) { 你的程式碼; } 函式名稱(123, "佛光大學"); //執行這個函式 ``` 3. **有參數、有回傳值** ```csharp= 回傳值型別 函式名稱(參數型別 參數1, 參數型別 參數2 ...) { 你的程式碼; return 回傳值; } int Answer = 函式名稱(123, "佛光大學"); //執行這個函式 ``` 4. **沒有參數、有回傳值** ```csharp= 回傳值型別 方法(函式)名稱() { 你的程式碼; return 回傳值; } int Answer = 函式名稱(); //執行這個函式 ``` 以上四種形式可以依照你的需要,將需要「打包」的程式給包裝起來。 ## 開始修改吧! 以下程式碼則是要用在我們的長度計算機裡面,我們先介紹怎麼使用函式?再介紹怎麼建立函式? ## 怎麼使用函式? 我們先挑選「公分輸入文字框」,修改原有的事件綁定程式片段,建議同學要跟之前的版本做一下比較,你會更理解為什麼要這樣修改?你也可以發現,其實只是把單位轉換計算的部分刪除(這邊老師暫時先把它註解,讓你可以做比較),然後加上一段「執行」計算長度函式的語法。 ```csharp= private void txtCM_KeyUp(object sender, EventArgs e) { strInput = txtCM.Text; if (double.TryParse(strInput, out douOutput) == true) { //我們把以下的單位轉換,改成可以共用的函式 //txtM.Text = string.Format("{0:0.##########}", douOutput / 100); //txtKM.Text = string.Format("{0:0.##########}", douOutput / 100000); //txtIn.Text = string.Format("{0:0.##########}", douOutput / 2.54); //txtFt.Text = string.Format("{0:0.##########}", douOutput / 30.48); //txtYard.Text = string.Format("{0:0.##########}", douOutput / 91.44); //執行計算長度函式 caculateAnswer(0, douOutput); } else { txtInfo.Text = "請輸入數字"; txtCM.Text = ""; } } ``` 你可以看的到,原本需要五行的單位轉換計算,就變成只要一行就可以完成。 ```csharp caculateAnswer(0, douOutput); ``` :::info 請特別注意:呼叫函式的方法需要指定「函式名稱」,有參數的話要放在括號裡面,參數用逗點隔開。 ::: ## 怎麼建立函式? 現在你應該已經初步理解為什麼函式可以大幅度減少程式碼了?接下來要教同學怎麼建立函式? 以下是原來的單位轉換計算程式碼,我們要將它「打包」起來。 ```csharp= // 我們把這一段,改成可以共用的函式 txtM.Text = string.Format("{0:0.##########}", douOutput / 100); txtKM.Text = string.Format("{0:0.##########}", douOutput / 100000); txtIn.Text = string.Format("{0:0.##########}", douOutput / 2.54); txtFt.Text = string.Format("{0:0.##########}", douOutput / 30.48); txtYard.Text = string.Format("{0:0.##########}", douOutput / 91.44); ``` 打包的結果如同以下程式碼,我們設計一個單位轉換計算的函式,沒有回傳值(void),設計兩個參數,1.類別參數(_kind)、2.數值參數(_value)。 ```csharp= // 設計一個單位轉換計算的函式,沒有回傳值,設計兩個參數,1.類別參數、2.數值參數 private void caculateAnswer(int _kind, double _value) { if (_kind != 0) txtCM.Text = string.Format("{0:0.##########}", _value); if (_kind != 1) txtM.Text = string.Format("{0:0.##########}", _value / 100); if (_kind != 2) txtKM.Text = string.Format("{0:0.##########}", _value / 100000); if (_kind != 3) txtIn.Text = string.Format("{0:0.##########}", _value / 2.54); if (_kind != 4) txtFt.Text = string.Format("{0:0.##########}", _value / 30.48); if (_kind != 5) txtYard.Text = string.Format("{0:0.##########}", _value / 91.44); } ``` ## 單位轉換計算函式 以上的單位轉換計算函式有幾個重點: **類別參數**:包括0到1的數字,用來代表照使用者選擇的長度單位類型,我們的範例程式將每個長度類別設定為: 1. 公分輸入文字框:0 2. 公尺輸入文字框:1 3. 公里輸入文字框:2 4. 英吋輸入文字框:3 5. 英呎輸入文字框:4 6. 英碼輸入文字框:5 **數值參數**:使用者在選擇的單位中,所輸入的數字。例如:如果使用者選擇在公分輸入文字框輸入100,類別參數就是0,數值參數為100。 **計算方式**:你可以觀察我們設計的單位轉換計算函式,都是以「公分」為基礎來做計算。以下我們說明這個函式程式運作的流程,以使用者選擇在公分輸入文字框為案例說明: 使用者選擇在公分輸入文字框,所以「_kind=0」,輸入「_value=100」的數值,接下來程式會一口氣跑好幾個判斷式,除了第4、5行程式沒有跑,其他都會一一計算並且在每一個輸入文字框顯示結果。 那如果使用者選擇其他長度單位的輸入文字框,那又會怎麼計算呢?這邊我們來看看「公尺的綁定事件程式碼片段」。 ```csharp= // 公尺的綁定事件程式碼片段 private void txtM_KeyUp(object sender, KeyEventArgs e) { strInput = txtM.Text; if (double.TryParse(strInput, out douOutput) == true) { caculateAnswer(1, douOutput * 100); // 事先將公尺轉換成公分 } else { txtInfo.Text = "請輸入數字"; txtM.Text = ""; } } ``` 你可以看的到,如果使用者選擇公尺的輸入文字框,會事先將公尺轉換成公分,然後丟到單位轉換計算的函式做計算,因為函式內部的單位轉換是將「公分數值轉為其他長度單位」。 所以不管有多少長度單位,只要能事先轉換成公分,就可以處理各式各樣的長度單位,這樣程式就會更簡單也更容易修改。你可以再研究一下其他單位,你應該發現原理都是一樣的。 ## 結語 這個章節我們初步介紹了 C# 的函式寫法。 **函式是模組化程式設計的基石**,除了函式可以在程式中重複使用,我們還可以將各種不同的功能分別寫成不同功能的函式,可以將不同功能函式組合,組合出完整的系統或軟體功能。 這樣就可以讓你不會寫出「落落長」程式碼,可以適當的整合重複程式碼,還可以簡化程式設計的複雜程度,讓你寫出更清楚更好修改的程式碼。 之後我們會在之後的章節介紹類別與物件的概念,你可以更感受到函式的優點,並且函式也是類別的基本元素,因此了解函式,等於你更深入了解程式設計的世界。