---
# System prepended metadata

title: C# 簡單教學

---

# 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
## 遞迴