# C# 簡單教學 [TOC] ## 一些小建議 1. 變數名稱不要亂取,盡量取跟用途相關的名稱 2. 程式不要用抄的,盡量自己想過寫看看,真的想不出來再去問人 3. 建議要寫註解,把自己思考的步驟寫下來,以後再看程式碼才看得懂 :::info Q: C# 註解怎麼寫 ? A: `//` 兩個斜線,同行後面開心怎麼寫就怎麼寫 ::: 3. 可以準備幾張紙,把程式執行狀態寫下來,可以避免在腦袋裡想的時候搞錯 4. 寫程式沒有捷徑,多寫多錯幾次才能找到自己的盲點,真正學會寫程式 5. 要會看錯誤訊息,從錯誤訊息中找出自己寫錯的地方 6. 除錯方法建議 : 可以在程式不確定的地方設停止點,比如把後面註解掉,然後把現在的情況印出來看看,檢查是不是正確的 7. 不一定要把所有東西背得滾瓜爛熟,而是要對各種東西有印象,知道它們是幹嘛的,忘記的時候有辦法查到就可以 ## 基本概念 1. 程式都是先從主程式 (Main) 開始跑 2. 程式執行順序是由上到下 3. 每行結尾要加`;` 4. 註解是 `//我是註解` 5. 層級之間是空 2 或 4 格 (建議空 4 格,另外不要用 Tab,除非你把 Tab 改成空格) ## 資料型態 ### int (整數) * 0,1,2,3,4,5,6,7,8,9 這些整數就是 int ### float (浮點數,也就是有小數) * 0.1 , 1.2 , 1.3 這些有小數的數字 ### string (字串) * `"hello123"` , `"資管系"` 這些用`" "`包起來的東西就是字串,裡面可以有中文英文數字符號 ### bool (True 或 False) * boolean (布林值) 的縮寫。名用來做邏輯判斷,比如說運算式比較的結果就會是 bool 型態的 True 或 False * True : 是 * False : 否 ### null (空值) * 空值 ::: info 取得資料的型態的方法 * 可以用`.GetType()`取得資料的型態 * 比如`"123".GetType()`的輸出會是`System.String`,就可以知道 "123" 是一個 string ::: ## 宣告變數 C# 中宣告變數要在前面加上變數型態 * 宣告一個整數,名稱是 a : `int a` * 宣告時也可以給變數一個初始值 : `int a = 0` * 宣告之後如果要改變變數的值,不用在前面加上型態,只要寫變數名稱 = 值 : `a = 1` * 那 a 這個變數的值就會變成 1 而不是原本的 0 了 ::: info `=`的意思 * 意思是把`=`右邊的值,放進`=`左邊的變數 * 所以通常用法是`num = 1`或`num1 = num2`(這時 num1 的值會變得跟 num2 一樣) * 如果要放入不同型態的值,不能直接用`=`,而是要先做型態轉換 ::: ## 型態轉換 ### 轉成 string (字串) 比如`string a = 123.ToString()`,意思是把整數`123`轉成字串後放入變數 a,這時 a 的值就會是 `"123"` ### 轉成 int (整數) 比如 `int a = Int32.Parse("123")`,意思是把字串`"123"`轉成整數後放入變數 a,這時 a 的值就會是 `123`。要注意的是,轉換型態時也要注意轉換的資料內容,比如說不能把字串 `"abc"` 轉成整數 ### 轉成 float (浮點數) 比如 `float a = float.Parse("123")`,意思是把字串`"123"`轉成浮點數後放入變數 a,這時 a 的值就會是 `123` ### 轉成 double 比如 `double a = double.Parse("123")`,意思是把字串`"123"`轉成 double 後放入變數 a,這時 a 的值就會是 `123` ## 輸入輸出 ### 輸出 輸出方式分為兩種 : 1. `Console.WriteLine()` : 印出內容後換行,下一次的輸出會在下一行 2. `Console.Write()` : 印出後不換行,下一次的輸出會接在後面 另外輸出還可以這樣寫( WriteLine 跟 Write 都可以用) : `Console.WriteLine("最大值={0} 最小值={1}", myMax, 1)` 假如 myMax = 2,那輸出會長這樣 : `最大值=2 最小值=1` ### 輸入 輸入方式分為兩種 : 1. `Console.ReadLine()` : 讀取輸入的一行文字 (輸入完按 Enter),通常會宣告一個 string 變數來存放輸入。如果想要輸入 int 就把輸入字串轉成 int 型態 2. `Console.ReadKey()` : 讀取任意輸入,通常是放在程式最後面,避免程式執行完直接關掉畫面 如果想要儲存輸入的資料,那就是用`Console.ReadLine()` ## 運算符號 基本運算符號有`+`,`-`,`*`,`/`,`%`,`()` 程式中數學的運算也是有先乘除後加減 ### 1. 加 `+` * 可以是數字之間的相加,就跟一般數學運算一樣 * 也可以做字串之間的相加,比如`"1"+"2"`的結果會是`"12"` ### 2. 減 `-` * 可以是數字之間的相減,就跟一般數學運算一樣 ### 3. 乘 `*` * 可以是數字之間的相乘,就跟一般數學運算一樣 ### 4. 除 `/` * 可以是數字之間的相除,就跟一般數學運算一樣 * 如果被除數和除數都是整數,那結果會是商,比如`3/2`的結果會是`1` * 如果被除數和除數中有浮點數,那結果會包含小數,比如`3.0/2`的結果會是`1.5` ### 5. 除 (取餘數) `%` * 可以是數字之間的相除,就跟一般數學運算一樣 * 輸出會是餘數,比如`11%4`的結果會是`3` ### 6. 小括號 `()` * 用小括號包起來的部分會先計算,可以有很多層小括號 * 比如 `18/((1+2)*3)`的結果會是 2 ### 7. `+=` 和 `++` 運算式縮寫 * 可以把一些運算式縮寫 * 比如 `a = a + 1` 可以縮寫成 `a += 1` * 同理,其他運算符號也可以這樣縮寫 * 另外`+`和`-`也可以縮寫成 `a++`和`a--`,意思就是`a = a+1`和`a = a - 1` ### 8. 次方和根號`Math.Pow()` * 使用方法`Math.Pow(變數,指數)` * 如果要求 3 的 2 次方,那就是`Math.Pow(3,2)` * 如果要求 9 的 根號,那就是`Math.Pow(9,0.5)` ### 練習題目 * https://code.im.ncnu.edu.tw/problem/1101HW1 * https://code.im.ncnu.edu.tw/problem/109HW1 ## 條件判斷 (1) - if else 可以用英文的意思來解釋 : * 如果 (if) 發生什麼事,就做什麼事 * 其他情況 (else),要做什麼事 * 有 if 才會有 else,也可以只有 if * 判斷式的符號有 `==`,`!=`,`>`,`<`,`>=`,`<=` ### if (如果) 在 if 判斷式中,如果要判斷等於`=`,那要寫這樣`==` * 如果 a 的值等於 1,就把 a 印出 : ```csharp if (a == 1) { Console.WriteLine(a); } ``` ### else (其他情況) * 如果 a 的值等於 1,就把 a 印出,否則印出 "a不等於1" : ```csharp if (a == 1) { Console.WriteLine(a); } else { Console.WriteLine("a不等於1"); } ``` * 判斷式也可以用不等於`!=` : ```csharp if (a != 1) { Console.WriteLine("a不等於1"); } else { Console.WriteLine(a); } ``` * 可以把多個條件拼起來 * 和 (and) * 所有條件都要符合 * 用 `&&` 連接 * 如果 a 不等於 1 而且 a 小於 10 就印出以下內容 ```csharp if (a != 1 && a < 10) { Console.WriteLine("a不等於1且a小於10"); } ``` * 或 (or) * 只要符合其中一個條件 * 用 `||` 連接 * 如果 a 不等於 1 或 a 小於 10 就印出以下內容 ```csharp if (a != 1 || a < 10) { Console.WriteLine("a不等於1或a小於10"); } ``` ### else if (其他狀況中如果) * 可以在 else 裡判斷其他情況 ```csharp int a = 3; if (a == 1) { Console.WriteLine("a等於1"); } else if (a < 10) { Console.WriteLine("a小於10"); } else { Console.WriteLine("a大於10"); } ``` * 輸出的結果會是"a小於10" * 要注意的是如果符合 if 條件,後面的 else if 和 else 它都不會去執行 * 比如這個範例如果 a 的值是 1,那符合 `if (a==1)` 條件後,下面的 else if 和 else 就不會去做判斷 ### 練習題目 * https://code.im.ncnu.edu.tw/problem/1101HW3 ## 條件判斷 (2) - switch case * 也可用`switch`和`case`來作條件判斷 * `switch`放要做判斷的資料 * `case`放判斷的條件 * 比如以下範例會針對不同輸入產生不同輸出 : ```csharp string data = Console.ReadLine(); switch (data) // 要做判斷的資料 { // case 是判斷的條件 case "1": Console.WriteLine("輸入是1"); break; case "2": Console.WriteLine("輸入是2"); break; case "3": case "4": Console.WriteLine("輸入是3或4"); break; default: Console.WriteLine("輸入是其他"); break; } ``` * 若符合`case`的條件,就會執行`case`內的程式 * 程式裡必須要加`break`或其他方法 (如`return`) 讓`case`可以結束 * 程式中的`case "3"`和`case "4"`這兩行表示符合這兩種條件其中一種就會做裡面的事情 * 如果沒有任何符合的條件,就會執行`default`的內容,相當於`if-else`中的`else` * 可以沒有`default`區塊 ## 迴圈 (1) - while 可以用英文的意思來解釋 : * 當 (while) 符合條件時,就一直重複做裡面的事,直到條件不符合 * 所以要注意不要寫成**無限迴圈** * 比如當 a 的值小於 10,就印出 a 的值 ```csharp int a = 1; while (a < 10) { Console.WriteLine(a); a++; } ``` 輸出就會是 : ``` 1 2 3 4 5 6 7 8 9 ``` ### 練習題目 * https://code.im.ncnu.edu.tw/problem/1091HW2 * https://code.im.ncnu.edu.tw/problem/1092%20HW1 ## 迴圈 (2) - for * 另一種迴圈 * 有一個固定格式`for (變數起始值; 迴圈終止值; 每次迴圈變數進行的動作)` * 比如 ```csharp for (int i = 0; i < 10; i++) { Console.WriteLine(i); } ``` * 意思就是迴圈從定義的`i`變數等於 0 開始,直到`i`不小於 10 迴圈結束,每次迴圈`i`的值加 1 * 輸出結果會是 : ``` 0 1 2 3 4 5 6 7 8 9 ``` * 這個`i`可以是在 for 迴圈定義的變數,也可以是之前就定義過的變數 ```csharp int a = 1; for (a = 2; a < 10; a++) { Console.WriteLine(a); } ``` * 輸出結果會是 : ``` 2 3 4 5 6 7 8 9 ``` ## 陣列 -- 一維陣列 * 可以把陣列想像成一個能塞很多東西進去的空間 * C# 裡陣列只能放同類型的資料,比如`int[]`裡面就只能放`int`,不能放`string` * 宣告陣列時也要宣告陣列的大小,也就是陣列最多能放幾個資料,比如`int[] a = new int[10]` 是一個長度為 10 的 int 陣列 * 宣告陣列時可以同時宣告陣列的內容,比如`int[] a = {1,2,3,4,5,6,7,8,9,10}` * 取得陣列內資料的方式是透過`[]`和索引值 (index) * 宣告陣列`int[] a = {1,2,3,4,5,6,7,8,9,10}` * 索引值是從 0 開始算 * 所以`a[0]`就會是 1 * 如果索引值超出陣列長度 -1,比如`a[10]`就會報錯`Index Out Of Range` * 如果陣列長度是 10,那索引值範圍就是 0~9 (10-1=9) * 取得一維陣列長度的方式是`陣列.Length` * 更改陣列中某資料的值 : * 宣告陣列`int[] a = {1,2,3,4,5,6,7,8,9,10}` * 如果寫`a[0] = 11` * 那陣列內容就會變成`{11,2,3,4,5,6,7,8,9,10}` ::: info **Split** 可以將資料以括號內的條件切分成一個陣列 * `Split(' ')` * 會將資料以`' '`切分成一個陣列 * 以下字串以`' '`切分後輸出的結果 : ```csharp string num = "1 2 3 4 5 6 7 8"; string[] nums = num.Split(' '); for (int i = 0; i < nums.Length; i++) { Console.WriteLine(nums[i]); } ``` * 會將 num 的字串內容以空格切分成一個陣列,陣列內容如下 : ```csharp nums = {"1","2","3","4","5","6","7","8"}; ``` **Array.ConvertAll** 把陣列內資料的資料型態都轉換成指定的資料型態 `Array.ConvertAll(陣列, 型態轉換)` 比如把字串陣列`text`的內容都轉成整數型態,並存到整數陣列 : ```csharp string text = {"1","2","3","4","5"}; int[] num = Array.ConvertAll(text.Split(' '), Int32.Parse); ``` ::: ### 練習題目 * https://code.im.ncnu.edu.tw/problem/1101HW6 * https://code.im.ncnu.edu.tw/problem/1101HW4 ::: info **字串比較** * 比較字串的大小 * 用 `String.Compare(字串1,字串2)` * 會回傳一個整數 * 如果整數是 0,代表兩個字串相同,大小是用編碼排序的 `int result = String.Compare(string1, string2)` ::: ## 讀檔和寫入檔案 ### 讀檔 * 可以讀取文字檔的內容 * 讀取內容後可以一次一行一行讀,也可以一次讀全部 ### `ReadLine()` : 一行一行讀 * 程式會從第一行開始讀,每做一次`ReadLine()`,目前讀的位置就會往下一行 * 可以用`StreamReader`讀取文字檔 (.txt) * 使用時程式碼最上面要加`using System.IO` * 方法是宣告一個變數,型態為`StreamReader` * `StreamReader 變數名稱 = new StreamReader("檔案路徑+名稱")` * 比如`StreamReader myfile = new StreamReader("input.txt")` ::: info **檔案路徑** * 檔案的存放位置 * 如果要讀取的檔案跟程式碼在同一層資料夾,可以不用加檔案路徑 * 否則要寫檔案的相對或絕對路徑 * 相對路徑 * 程式與檔案之間的相對位置 * 如果在同一層資料夾下可以直接寫檔名 * 比如程式碼`test.cs`同一層中有一個文字檔`test.txt` * 那`test.cs`程式碼用相對路徑來讀取檔案`test.txt`時,程式碼會長這樣 : ```csharp StreamReader myfile = new StreamReader("test.txt") ``` * 如果在不同曾資料夾下,就要把相對的路徑寫完整 * 比如程式碼`test.cs`同一層中有一個資料夾`test`,`test`資料夾中有文字檔`test.txt` * 那`test.cs`程式碼用相對路徑來讀取檔案`test.txt`時,程式碼會長這樣 : ```csharp StreamReader myfile = new StreamReader("test/test.txt") ``` * 絕對路徑 ::: * 通常會用`Split`將檔案內容轉換成陣列後來處理檔案內的資料 ### `ReadAllLines()` : 一次讀全部 * 一次讀全部,每一行文字都會放在一起 * 讀進來也可以用`Split`處理,通常會先以行來`Split`,再以每行的內容`Split` * 可以用`File.ReadAllLines()`讀取文字檔 (.txt) * 使用時程式碼最上面要加`using System.IO` * 方法是 `字串陣列 = File.ReadAllLines("檔案路徑+名稱")` * 假如一個文字檔`test.txt`內容如下 : ``` 張琇岑 24 78 陳聖心 67 76 林胤竹 89 24 ``` * 用`ReadAllLines`讀取該檔案 ```csharp string[] all_lines = File.ReadAllLines("test.txt"); // 這時 all_lines 的內容會長這樣 : {"張琇岑 24 78","陳聖心 67 76","林胤竹 89 24"} // 要再取得每行內的資料,就是每行都再做一次 Split for (int i = 0; i < all_lines.Length; i++) { string[] line = all_lines[i].Split(' '); // for 迴圈第一次時 line 內容會長這樣 {"張琇岑","24","78"} for (int j = 0; j < line.Length; j++) { Console.Write("{0} ",line[j]); // 印出每行內的每一項,中間都加一個空格 } Console.WriteLine(); // 每次印完一行內容就做換行 } ``` * 會得到以下輸出 : ``` 張琇岑 24 78 陳聖心 67 76 林胤竹 89 24 ``` ### 寫檔 * 可以用`StreamWriter`把文字寫入文字檔 * StreamWriter 變數名稱 = new StreamWriter("檔案路徑+名稱"); * 比如`StreamReader myfile = new StreamWriter("input.txt")` * 可以寫入後立即換行或不換行 * 寫入後換行就是`WriteLine()` * 比如`檔案.WriteLine("hello");` * 不換行就是`Write()` * 比如`檔案.Write("hello");` * 如果檔案原本有內容,原本的內容會被取代,有其他寫入方法可以接在檔案的最後面繼續寫入 ### 關閉檔案 讀寫完檔案之後要用`Close()`把檔案關閉 比如讀入檔案`file01`,關閉時程式碼如下 : `file01.Close()` ## try 和 catch * 可以用`try`跟`catch`來避免程式執行錯誤而停止 * 會先執行`try`的內容 * 如果`try`中發生執行錯誤,就會跳去執行`catch`的內容,而不會因為錯誤停止 * 可以在`catch`中印出錯誤訊息 * 比如以下程式的`try`中有陣列超出索引值的錯誤,那程式會跳到`catch`執行而不會停止 ```csharp try { Console.WriteLine("hello"); int[] num = {1}; Console.WriteLine(num[1]); Console.WriteLine("world"); } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine("hi"); } ``` * 輸出如下 : ``` hello 索引在陣列的界限之外。 hi ``` ## 主程式 / 函式 / 參數 ### 主程式 Main `public static void Main(String[] argv)` * 程式的起點,程式會先從主程式開始執行 * 該行結尾要加`{`,主程式的結尾要加`}`,主程式的內容在主程式的下層,所以都要空 4 格 * 一般會把資料輸入寫在主程式 ### 自定義函式 可以自己定義含式的名稱,可以想成我們把一件麻煩的事情交給別人來做 比如定義一個函式叫 SayHello,讓它幫我們做問好這件事情 : ```csharp public static void SayHello() { Console.WriteLine("Hello World!"); } public static void Main(String[] argv) { SayHello(); Console.WriteLine("no good..."); } ``` * 程式一樣是從主程式 (Main) 開始 * 我們定義好函式之後,可以在主程式或其他函式裡呼叫該函式 * 程式執行順序會式這樣 : Main -> SayHello() -> Console.WriteLine("Hello World!") -> Console.WriteLine("no good...") * 所以輸出結果會是 : ``` Hello World! no good... ``` ::: warning 要注意函式名稱不能重複,也不要使用到一些保留字,比如說 Console , ReadLine 之類的 ::: ### 函式裡放參數 可以定義傳送給函式的參數,數量不限,用`,`隔開,可以是各種資料型態 ```csharp public static void SayHello(string day, int num) { Console.WriteLine("今天{0}{1}",day,num); } public static void Main(String[] args) { SayHello("星期",1); } ``` * 要在函式的括號裡定義參數的型態和名稱 * 呼叫該函式時要傳送一樣數量且型態對應的參數 * 參數在該函式裡等同於一個被宣告的變數,變數名稱就是參數名稱,變數值則是我們呼叫函式時所放入的數值 * 輸出結果會是 : ``` 今天星期1 ``` ### 函式回傳 * 用`return`可以回傳 * 會停止現在的動作 * 然後回傳到呼叫的位置 * 比如以下範例 : ```csharp public static void SayHello() { int i = 0 if (i==0) { Console.WriteLine("good"); return; } Console.WriteLine("bad"); } public static void Main(String[] args) { Console.WriteLine("hello"); SayHello(); Console.WriteLine("goodbye"); } * 輸出會是 : ``` hello good goodbye ``` * `Console.WriteLine("bad")`因為前面`return`了,所以不會被執行到 * `return`在回傳時也可以回傳資料,下面會介紹怎麼回傳資料 ### 函式回傳值 可以定義函式回傳的資料型態,寫在 `void` 這個位置 * void : 沒有回傳資料 * int : 回傳值的型態是整數 * string : 回傳值的型態是字串 * 其他就依此類推 * 在函式內容要寫 `return 資料`,(可以不用加資料,不加代表沒有回傳值) * 如果函式有回傳值,在呼叫函式時可以透過`=`把回傳值存到變數裡 ```csharp public static string SayHello(string day, int num) { string today = "今天"+ day + num.toString(); return today; } public static void Main(String[] args) { string theDay = SayHello("星期",1); Console.WriteLine(theDay); } ``` ::: info **區域變數** 每個函式之間,在某一函式內宣告的某些資料型態 (如 int , string , float) 變數是屬於該函式,其他函式並不知道它的存在,也無法使用,這就是區域變數。相對的,各函式也可以在自己的函式裡宣告跟其他函式中同名的變數,其實兩者是互不相干的 ```csharp public static void SayHello() { int num1 = 0; num1 = 2; } public static void Main(String[] args) { int num1 = 1; SayHello(); Console.WriteLine(num1); } ``` 輸出的結果會是`1`,因為 SayHello 函式裡定義的 num1 跟主程式是互不相干的 ::: ### 練習題目 * https://code.im.ncnu.edu.tw/problem/1101quiz2 或把前面的題目改成用自訂函式來寫 * https://code.im.ncnu.edu.tw/problem/1101HW1 * https://code.im.ncnu.edu.tw/problem/109HW1 * https://code.im.ncnu.edu.tw/problem/1101quiz3 ## 陣列 - 多維陣列 * 陣列可以是多維的,比如一個二維陣列可能長這樣 `int[,] a = {{1,2},{3,4},{5,6}}` * `,`表示再多一維空間,所以`int[,] a`表示 a 是一個二維陣列 * 宣告多維陣列時也要宣告陣列的大小,比如`int[,] a = new int[2,3]`會產生一個 2*3 的陣列 : * `{{,,} , {,,}}` * 第一維的空間有 2 個,第二維的空間有 3 個 * 也可以宣告的時候同時宣告陣列內容 : * `int[,] a = {{1,2,3},{4,5,6}}` * 取得陣列內資料也是透過`[]`和索引值 * 宣告陣列`int[,] a = {{1,2,3},{4,5,6}}` * 那`a[0,0]`就會是 1,`a[1,2]`就會是 6 * 取得每一維陣列長度的方式是`陣列.GetLength(維度-1)` * 宣告陣列`int[,] a = {{1,2,3},{4,5,6}}` * `a.GetLength(0)`會得到一維的長度,也就是 2 * 那`a.GetLength(1)`就會得到二維的長度,也就是 3 * 更改陣列中某資料的值 : * 宣告陣列`int[,] a = {{1,2,3},{4,5,6}}` * 如果寫`a[0,1] = 11` * 那陣列內容就會變成`{{11,2,3},{4,5,6}}` ### 練習題目 * https://code.im.ncnu.edu.tw/problem/1101quiz6 * https://hackmd.io/muzpzdbmTIKrwK8tS8xuSw * https://code.im.ncnu.edu.tw/problem/199 ## 結構 * 假如今天你想要處理一份資料,這份資料裡有多種不同型態的內容(比如學生清單,裡面可能有姓名 (string)、學號 (string)、年齡 (int)),那處理起來可能會很麻煩,那這時就可以自己定義一個結構 (struct)來處理 * 這個結構有什麼屬性都可以自己定義,比如可以定義學生清單`StudentInfo`有姓名 (string)、學號 (string)、年齡 (int) * 如果我們要把數值給這些屬性的話,可以用建構函式 (要與結構名稱相同) 來做到,它的用途就是把宣告這個結構時給的變數放入結構的屬性 ```csharp=1 struct StudentInfo // 結構 { // 屬性 public string name; public string sid; public int age; // 建構函式 public StudentInfo(string myName, string mySid, int myAge) { // this 就是指你目前宣告的這個學生 this.name = myName; this.sid = mySid; this.age = myAge; } } ``` * 宣告一個學生清單的做法就像這樣 ```csharp=1 struct StudentInfo { public string name; public string sid; public int age; public StudentInfo(string myName, string mySid, int myAge) { this.name = myName; this.sid = mySid; this.age = myAge; } } public static void Main(String[] args) { // 宣告一個學生資料 yellowFish // 他的姓名是 "黃魚",學號是 "109213000" ,年齡是 21 StudentInfo yellowFish = new StudentInfo("黃魚","109213000",21); } ``` * 取得或修改 yellowFish 的學生資料的做法 ```csharp=1 struct StudentInfo { public string name; public string sid; public int age; public StudentInfo(string myName, string mySid, int myAge) { this.name = myName; this.sid = mySid; this.age = myAge; } } public static void Main(String[] args) { StudentInfo yellowFish = new StudentInfo("黃魚","109213000",21); // 更改 yellowFish 的姓名 yellowFish.name = "紅魚"; // 取得 yellowFish 的姓名 Console.WriteLine(yellowFish.name); } ``` * 假如我們想要這個學生清單幫我們做一些事情,可以定義專屬這個結構的函式 * 比如定義`StudentInfo`裡的`printInfo`函式可以印出這個學生的資料 * 呼叫這個函式的做法是`StudentInfo.printInfo()` ```csharp=1 struct StudentInfo { public string name; public string sid; public int age; public StudentInfo(string myName, string mySid, int myAge) { this.name = myName; this.sid = mySid; this.age = myAge; } public void printInfo() { Console.WriteLine("姓名: {0} 學號: {1} 年齡: {2}",this.name,this.sid,this.age); } } public static void Main(String[] args) { StudentInfo yellowFish = new StudentInfo("黃魚","109213000",21); yellowFish.printInfo(); } ``` * 輸出結果會是`姓名: 黃魚 學號: 109213000 年齡: 21` ### 練習題目 * https://hackmd.io/F1hzHmWDQjO9H5oPh-JOWg?view ## 建構子(類別) * 可以先看作跟結構是一樣的東西 * 可以自己宣告一個類別 (class),這個類別有什麼屬性和函式都可以自己定義 * 宣告的做法如下 ```csharp=1 // 宣告類別 public class StudentInfo { // 屬性 public string name; public string sid; public int age; // 建構函式 public StudentInfo(string myName, string mySid, int myAge) { this.name = myName; this.sid = mySid; this.age = myAge; } // 自訂函式 public void printInfo() { Console.WriteLine("姓名: {0} 學號: {1} 年齡: {2}",this.name,this.sid,this.age); } } public static void Main(String[] args) { // 宣告該類別的變數 StudentInfo yellowFish = new StudentInfo("黃魚","109213000",21); // 呼叫該類別的函式 yellowFish.printInfo(); } ``` ### 繼承 * 可以宣告一個新的類別,這個類別擁有其他類別的特徵,這就是繼承 * 宣告黃魚(yellowFish)類別繼承魚(fish)類別 ```csharp=1 // 繼承 public class fish { public string name; // 公有,所有class都可以存取 private int age; // 私有,其他class都不能存取 protected int length; // 保護,只有該class和繼承的class可以使用 // 建構函式 public fish() { this.name = ""; this.age = 0; this.length = 0; } public fish(string name, int age, int length) { // 給值 this.name = name; this.age = age; this.length = length; } // 自訂函式 public void setName(string name) { this.name = "笨魚"+name; } public void Show() { Console.WriteLine("名稱={0}, 年齡={1}, 長度={2}", this.name, this.age, this.length); } } public class yellowFish:fish { // yellowFish繼承fish public string gender; // 建構函式 public yellowFish(string name, int age, int length, string gender):base(name,age,length) { // 繼承A的建構函式 this.gender = gender; } public yellowFish():base() { // // 繼承A的建構函式 this.gender = ""; } private void setLength(int length) { this.length = length; } } public static void Main(string[] args) { yellowFish fish1 = new yellowFish("黃魚", 19, 30, "男"); //fish1.length = 10; // 非法 (因為 length 只有 fish 和 yellowFish 可以存取) //fish1.age = 20; // 非法 (因為 age 只有 fish 可以存取) fish1.Show(); Console.WriteLine(fish1.gender); Console.WriteLine("\nPress any key to continue..."); Console.ReadKey(); } ``` ### 解構子 * 寫一個函式,命名方法是`~類別名稱` * 當類別的生命週期結束時會被呼叫,把創建的類別資料刪除,並執行函式的內容 * 建立fish這個類別的解構函式如下: ```csharp=1 // ~類別名稱() ~fish() { Console.WriteLine("魚被刪除了"); } ``` ### GC.Collect() * 會呼叫系統立即清理記憶體,而不是讓系統自己判斷適合的刪除時機 * 會去呼叫解構子刪除不需要的類別資料 * 呼叫方法如下: ```csharp=1 // 解構子 ~fish() { Console.WriteLine("魚被刪除了"); } // 會去呼叫解構子 ~fish GC.Collect(); ``` ### set 和 get * 類似定義一個函式(不用括號),這個函式可以存入和讀取類別屬性的值 * set 存入值 (類別.函式 = 數值) * get 取出值 (類別.函式) * 宣告方法 ```cshrap=1 public class fish { public string name; private int age; // 只有fish這個class可以存取 public int length; // 建構函式 public fish(string name, int age, int length) { this.name = name; this.age = age; this.length = length; } // 用get和set讓其他class可以存取 public int fishAge { set { // 存入值 age = value; // value 就是傳過去的值 } get { // 取出值 return age; } } } public static void Main(String[] args) { fish yellowFish = new fish("黃魚",200,30); yellowFish.fishAge = 10; // 存入值,所以會執行set Console.WriteLine(yellowFish.fishAge); // 取出值,所以會執行get } ``` ### 練習題目 * https://hackmd.io/F1hzHmWDQjO9H5oPh-JOWg?view ## List (串列) * 可以當作是更方便的 array (陣列),但不用宣告大小 * List 可以增加或縮短長度,而 array 不行 * 取得和設定 List 中資料的方式都和陣列一樣 * 取得 List 長度的方式 `List名稱.Count` * 需要在最上面加`using System.Collections.Generic` ### 宣告方法 ```csharp=1 // List<型態> List名稱 = new List<型態>(); List<int> myList = new List<int>(); List<int> myList = new List<int>(){1,2,3,4,5}; // 給定起始值 ``` ### 新增 * 新增一筆資料 * 加在最後面 ```csharp=1 // myList的內容: {2} // List名稱.Add(新增的資料); myList.Add(1); // 結果為 {2,1} ``` * 加在指定索引值前面 ```csharp=1 // myList的內容: {10,11,13,14,15} // List名稱.Insert(index值,新增資料) myList.Insert(2,12); // 結果為 {10,11,12,13,14,15} ``` * 新增多筆資料 * 把一個 List 加在另一個 List 的最後面 ```csharp=1 // myList的內容: {10,11,12} // 被加入List名稱.AddRange(加入List名稱) List<int> newList = new List<int>() {44,55,66}; myList.AddRange(newList); // 結果為 {10,11,12,44,55,66} ``` ### 刪除 * 刪除一筆資料 ```csharp=1 // myList的內容: {10,11,13,14,15,11} // List名稱.Remove(刪除資料的值) myList.Remove(11); // 結果為 {10,13,14,15,11} // 如果有多個一樣的值,前面的會先被刪掉 ``` * 刪除多筆資料 ```csharp=1 // myList的內容: {10,11,13,14,15,11} // List名稱.RemoveRange(從哪個Index值開始,刪除幾筆) myList.RemoveRange(1,2); // 結果為 {10,14,15,11} ``` ## Stack 堆疊 * 先進後出 * 越後面放入的資料加入(Push)在越上面的位置 * 擺在越上面的資料會越先被取出來(Pop) ![](https://hackmd.io/_uploads/B1ABs9o8n.png) * 需要在最上面加`using System.Collections.Generic;` * 宣告方法 ```csharp=1 // Stack<型態> Stack名稱 = new Stack<型態>(); Stack<int> mystack = new Stack<int>(); ``` ### 新增 (Push) * 把一筆資料加在最後面 ```csharp=1 // Stack名稱.Push(加入的資料); mystack.Push(val); ``` ### 刪除 (Pop) * 取出最後一筆資料 ```csharp=1 // Stack名稱.Pop() mystack.Pop() a = mystack.Pop() // 紀錄並取出最後一筆資料 ``` ## 補充常用功能 (數學函式 / 亂數 / break) * https://code.im.ncnu.edu.tw/problem/1091%20HW6 ## 遞迴