異常處理(捕捉異常) try-catch-finally
[範例] 讓用戶輸入兩個數字,輸入非整數類型,處理該異常,並讓用戶重新輸入,輸出這個兩個數的和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int num1 = 0, num2 = 0;
Console.WriteLine("請輸入第一個數:");
while (true)
{ //使用死循環,讓用戶可以重新輸入
try {
num1 = Convert.ToInt32(Console.ReadLine());
break; //輸入正確就跳出循環
} catch {
Console.WriteLine("您輸入的不是整數,請重新輸入");
}
}
Console.WriteLine("請輸入第二個數:");
while (true)
{ //使用死循環,讓用戶可以重新輸入
try {
num2 = Convert.ToInt32(Console.ReadLine());
break; //輸入正確就跳出循環
} catch {
Console.WriteLine("您輸入的不是整數,請重新輸入");
}
}
int sum = num1 + num2;
Console.WriteLine(sum);
Console.ReadKey();
類的定義和聲明
範列1:定義一個敵人的類(Enemy),可以設置血量、速度,有AI,Move方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Enemy
{
private float hp; //血量
private float speed; //速度
public float HP
{
get { return hp; }
set { hp = value; }
}
public float Speed {
get { return speed; }
set { speed = value; }
}
public void AI() {
Move();
Console.WriteLine("這是Enemy的AI方法");
}
public void Move()
{
Console.WriteLine("這是Enemy的Move方法");
}
}
聲明Enemy對象,用new實體化Enemy類別
1
2
Enemy enemy = new Enemy();
enemy.AI(); //調用AI方法
屬性的定義
1
2
3
4
5
private float speed; //速度
public float Speed {
get { return speed; }
set { speed = value; }
}
簡寫
1
public float Speed { get; set; }
賦值時,可以先進行一些邏輯判斷(設置前,可以先對該值進行校驗)
1
2
3
4
5
6
7
private int age;
public int Age
{
set {
if (value >= 0) age = value; //設置前,可以先對該值進行校驗
}
}
匿名型別var
var 由編譯器自動推斷型別
this 和 base關鍵字
- this 可以存取”當前類別”所有的成員
- base 可以存取”父類別”公開的成員
this可以訪問當前類別中定義的字段(成員變數)、屬性(get/set)和方法,有沒有this都可以訪問。base可以調用父類別中公有的字段和方法。
繼承
父類別
1
public class Person {...}
子類別:加上「:Person」繼承父類別
1
2
public class Teacher : Person {...}
public class Student : Person {...}
虛擬方法virtual/override
父類別:方法加上virtual
1
2
3
4
5
6
public class Enemy
{
public virtual void Move() { //virtual虛方法,繼承後可以重寫override此方法
Console.WriteLine("這是Enemy的Move方法");
}
}
子類別:方法加上override
1
2
3
4
5
6
public class Type2Enemy: Enemy
{
public override void Move() { //override重寫,原本的方法就不在了
Console.WriteLine("這裡是Type2Enemy的移動方法");
}
}
使用virtul可以選擇要不要重寫,不重寫就會以父類中的函數為主,重寫就會以子類的函數為主。
隱藏方法-new關鍵字
父類別
1
2
3
4
5
6
public class Enemy
{
public void Move() {
Console.WriteLine("這是Enemy的Move方法");
}
}
子類別:方法加上new關鍵字,隱藏父類別的move方法
new隱藏,只是把父類別方法隱藏,看不到了,實際這個方法還存在
1 2 3 4 5 6 public class Type2Enemy: Enemy { public new void Move() { //new隱藏,只是把父類別方法隱藏,看不到了,實際這個方法還存在 Console.WriteLine("這裡是Type2Enemy的移動方法"); } }一般很少用new關鍵字隱藏方法,容易造成混亂
1 2 3 4 Type2Enemy enemy1 = new Type2Enemy(); enemy1.Move(); //隱藏方法: 聲明子類對象,會調用子類的move方法 Enemy enemy2 = new Type2Enemy(); enemy2.Move(); //隱藏方法: 聲明父類對象,會調用父類的move方法抽象類別abstract
- 抽象類不能實體化
- 抽象類只有函數定義,沒有函數體(相當於不完整的函數)
- 它也是虛擬virtual的,需要被重寫的override
- 類是一個模版,抽象類就是一個不完整的模版
為什麼要使用抽象類? 例如:我們定義了鳥類,但不同的鳥有不同的飛行方式,那麼,我們可以透過抽象方法,讓繼承鳥類的所有子類都必須重寫overrid飛行方法。
通過抽象類別裡面的抽象方法,定義了一些子類別必須要實現的一些功能
定義鳥類(父類)
1
2
3
4
5
6
7
8
9
10
//當一個類當中存在抽象函數abstract的時候,這個類必須也聲明為抽象的
//一個抽象類 就是不完整的模版,不可以使用抽象類去實體化構造對象
public abstract class Bird
{
public float speed;
public void Eat() { }
//每個鳥類飛行的方式不同,就可以定義為抽象類abstract,讓不同子類去重寫方法
//所有繼承bird的類,都要去實現abstract抽象方法
public abstract void Fly(); //方法中有抽象函數abstract,class就要改成抽象類abstract
}
定義鳥鴉-繼承鳥類(子類)
1
2
3
4
5
6
//我們繼承了一個抽象類的時候,必須去實現抽象方法(給出函數體)
public class Crow : Bird {
public override void Fly() {
Console.WriteLine("鳥鴉在飛行");
}
}
聲明使用
1
2
Crow crow = new Crow();
crow.Fly();
我們可以透過抽象類(Bird)去聲明對象(Crow),但是不可以去構造(因為它是不完整)
1
2
3
4
Bird bird = new Crow(); //我們可以透過抽象類(Bird)去聲明對象(Crow),但是不可以去構造(因為它是不完整的方法)
bird.Fly();
//Bird bird = new Bird(); //Error: 抽象類不能實體化
一定要重寫方法,就可以聲明抽象類 使用virtul可以選擇要不要重寫,不重寫就會以父類中的函數為主,重寫就會以子類的函數為主
密封類和密封方法sealed (很少去用)
對於類,表示不能被繼承,對於方法,表示不能重寫該方法。
密封類
1
sealed class BaseClass { } //這裡聲明一個密封類
1
public class DerivedClass: BaseClass { } //sealed密封類無法被繼承
密封方法
1
2
3
public class BaseClass {
public virtual void Move() { }
}
1
2
3
4
5
public class DerivedClass: BaseClass { //sealed密封類無法被繼承
public sealed override void Move() { //我們可以把重寫的方法聲明為密封方法,表示該方法不能被重寫
base.Move();
}
}
什麼時候使用 密封類和密封方法?
- 防止重寫某些類導致代碼混亂
- 商業原因
派生類的構造方法
- 在子類別中調用父類別默認的構造方法(無參數) 會先調用父類的,再調用子類的
1 2 3 4 5
public class BaseClass { public BaseClass() { Console.WriteLine("Base Class無參構造函式"); } }
在子類的構造方法加上 base(), 可以調用父類中無參的構造函式 在這裡base()可以不寫,因為默認會調用父類中的默認構造函式
1 2 3 4 5
public class DerivedClass : BaseClass { public DerivedClass(): base() { //調用父類中無參的構造函式(base()可不寫) Console.WriteLine("這是Derived Class無參的構造函式"); } }
調用的順序,會先調用父類的無參構造函式,再調用子類的
1 2
DerivedClass o1 = new DerivedClass();//會先調用父類的,再調用子類的 Console.ReadKey();
- 在子類別中調用父類別有參數的構造方法(有參數)
1
2
3
4
5
6
7
public class BaseClass {
private int x;
public BaseClass(int x) {
this.x = x;
Console.WriteLine("x賦值完成");
}
}
在子類別有參數的構造方法加上base(x),相當於把x參數傳給父類別的構造方法
1
2
3
4
5
6
7
public class DerivedClass : BaseClass {
private int y;
public DerivedClass(int x, int y): base(x) { //base(x),相當於把x參數傳給父類別的構造方法
this.y = y;
Console.WriteLine("y賦值完成");
}
}
調用的順序: 不管是什麼情況,都是會先調用父類當中的構造方法,再調用子類的
1
DerivedClass o1 = new DerivedClass(1, 2); //不管是什麼情況,都是會先調用父類當中的構造方法,再調用子類的
修飾符protected(保護的)
protected保護的,當沒有繼承時,它的作用跟private是一樣的,當有繼承的時候,表示可以被子類訪問的字段或方法。
設為protected保護的,只有繼承時,才可以訪問到:
1
2
3
4
5
6
7
8
public class BaseClass {
private int x;
protected int z; //protected保護的,只有繼承時,才可以訪問到
public BaseClass(int x) {
this.x = x;
Console.WriteLine("x賦值完成");
}
}
父類z變數設為protected保護的, 子類繼承後,就可以訪問到(可以使用base.來訪問)
1
2
3
4
5
6
7
8
public class DerivedClass : BaseClass {
private int y;
public DerivedClass(int x, int y): base(x) { //base(x),相當於把x參數傳給父類別的構造方法
this.y = y;
base.z = 100;//父類z變數設為protected保護的, 子類繼承後,就可以訪問到
Console.WriteLine("y賦值完成");
}
}
其他修飾符
new 用來隱藏繼承的方法 virtual 虛擬方法,父類的方法加上virtual,子類可以重寫該方法。
1
public virtual void Test() {...}
父類(基類),繼承的類(子類/派生類)
abstract 抽象類別或抽象方法,抽象方法只有定義方法,沒有提供實作代碼(也就是沒有方法體) 該類有抽象方法,類別一定要加上abstract成為抽象類別
1
2
3
public abstract class MyClass {
public abstract void Test();
}
override 重寫方法,重寫繼承的虛擬方法(virtual)或抽象方法(abstract)
1
public override void Test() {...}
sealed密封,可以用在類/方法/屬性,對於類,不能繼承該類,sealed用於override方法(對於己重寫的方法),繼承該類就不能再重寫該方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class X {
protected virtual void F() { Console.WriteLine("X.F"); }
protected virtual void F2() { Console.WriteLine("X.F2"); }
}
//class Y重寫F方法,並加上sealed密封
class Y : X {
sealed protected override void F() { Console.WriteLine("Y.F"); }
protected override void F2() { Console.WriteLine("Y.F2"); }
}
//class Z繼承Y, 因為F()加上sealed,就不能再重寫該方法
class Z : Y {
// Attempting to override F causes compiler error CS0239.
// protected override void F() { Console.WriteLine("Z.F"); }
// Overriding F2 is allowed.
protected override void F2() { Console.WriteLine("Z.F2"); }
}
static 靜態方法。應用於所有成員(字段/屬性/方法)
- 靜態的(公有的),可以用來修飾字段/屬性/方法,通過類去訪問。
- 靜態只有一份,是共用的
定義和實現接口(介面)
1
2
3
4
5
6
7
8
//定義接口
interface IFly {
void Fly();
}
//實現接口
class Bird : IFly {
public void Fly() { //do something }
}
接口可以彼此繼承
1
2
3
4
5
6
7
8
9
10
11
12
//定義接口兩個接口並彼此繼承
interface IA {
void Method1();
}
interface IB: IA {
void Method2();
}
//繼承IB並實現接口
class Bird : IB {
public void Method1() { //do something }
public void Method2() { //do something }
}
集合類-列表List的創建和使用
創建空的列表
1
2
List<int> scoreList = new List<int>(); //指定類型創建
var scoreList = new List<int>()//匿名方式創建
創建列表並給上初始值123
1
var scoreList = new List<int>() { 1,2,3 }; //創建列表並給上初始值123
列表List加入數據
1
2
3
4
var scoreList = new List<int>(); //創建空的列表
scoreList.Add(123); //列表加入數據
Console.WriteLine(scoreList[0]); //索引方式取值
Console.ReadKey();
列表List遍歷
1
2
3
4
5
6
7
8
9
var list = new List<int>() { 1, 3, 4, 7, 84 };
//for
for (int i = 0; i < list.Count; i++) {
Console.Write($"{list[i]} ");
}
//foreach
foreach (var item in list) {
Console.Write($"{item} ");
}
操作列表List的屬性和方法
1
2
3
4
5
6
7
8
9
10
11
List<int> scoreList = new List<int>();
scoreList.Add(100); //加入元素
scoreList.Add(200);
scoreList.Add(300);
scoreList.Insert(1, -99); //插入元素,指定索引位置插入元素
scoreList.RemoveAt(0);//移除指定索引置的元素
int index = scoreList.IndexOf(200); //尋找元素的索引值
int index = scoreList.IndexOf(400); //元素不存在會返回-1
Console.WriteLine(scoreList.IndexOf(100));//有相同的元素值,會先返回前面的索引值
Console.WriteLine(scoreList.LastIndexOf(100));//從後面開始搜索,有匹配就返回值,元素不存在一樣返回-1
scoreList.Sort(); //從小到大排序
泛型類別的定義 ClassA
定義一個泛型類別,就是定義一個類別,這個類中某些字段的類型是不確定的,這些類型可以在類別構造(實體化)的時候確定下來。
1
2
3
4
5
6
7
8
9
10
11
12
13
//加上尖括號<>,裡面加上T,T代表類型,這就是泛型
public class ClassA<T> //T代表一個數據類型,當使用ClassA進行構造的時候,需要制定T的類型
{
private T x;
private T y;
public ClassA(T x, T y) {
this.x = x;
this.y = y;
}
public string GetSum() { //因為還沒有確定類型,先用string
return x +""+ y;
}
}
當我們實體化泛型類別的時候,需要制定泛型的類型
1
2
3
4
5
var o1 = new ClassA<int>(23,45); //當我們利用泛型構造的時候,需要制定泛型的類型
string s = o1.GetSum();
Console.WriteLine(s);
var o2 = new ClassA<string>("www", ".google");
Console.WriteLine(o2.GetSum());
泛型可以多個類型<T,A>,用class聲明的時候,這兩個類型都要指定
1
2
3
4
5
6
public class ClassA<T,A> //這個泛型有兩個類型:T跟A
{
private T x; //用T去聲明變量x
private T y; //用T去聲明變量y
private A z; //用A去聲明變量z
}
用ClassA聲明變量,指定T為string類型,A為int類型
1
2
var o2 = new ClassA<string, int>("www", ".google"); //構造時,T代表string類型,A代表int類型
Console.WriteLine(o2.GetSum());
泛型方法 Method(T a, Tb) {…}
定義泛型方法就是定義一個方法,這個方法的參數類型是不確定,這調用這個方法的時候,再去確定方法的參數類型。
舉例: 實現任意類型組成字串的方法:
1
2
3
4
//泛型方法
public static string GetSum<T>(T a, T b) {
return a +""+ b;
}
1
2
3
4
//調用泛型方法(用不同類型)
Console.WriteLine(GetSum(1,8));
Console.WriteLine(GetSum(9.31, 6.668));
Console.WriteLine(GetSum("www",".google"));
只有一個類型,不指定也可以,它會自動推斷類型
泛型方法也可以有多個類型:
1
2
3
static string GetSum<T, T2, T3, T4>(T a, T b) { //這裡聲明4個類型,在調用方法的時候,也要指定4個類型
return a + "" + b;
}
聲明調用方法時,要指定所有類型
1
2
3
Console.WriteLine(GetSum<int, int, int, int>(1, 8));
Console.WriteLine(GetSum<double, double, double, double>(9.31, 6.668));
Console.WriteLine(GetSum<string, string, string, string>("www", ".google"));