# 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)

* 需要在最上面加`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
## 遞迴