【學習資料】
《C#圖解教程》(第4~7章):https://www.cnblogs.com/moonache/p/7687551.html
電子書下載:https://pan.baidu.com/s/1mhOmBG0
【內容】
-
- 所有類的基類
- 類的組成
- 類的實例化、內存分配
- 類的特性:封裝、繼承、多態
- 接口
- 擴展知識
- 隱藏基類的成員
- struct、class
- 抽象函數(abstract)、虛函數(virtual)
- 抽象類(abstract)、接口(interface)
【筆記】
所有類的基類: object
類由2部分組成
> 數據成員:存儲與類或類的實例相關的數據
> 函數成員:執行代碼

類的實例化
> 聲明一個類的引用,指向空數據null

> new數據部分,並將引用指向數據

【類的特性】
類的三大特性:封裝、繼承、多態
> 封裝
> 訪問修飾符
> 繼承
> 派生子類
> 構造/析構函數
> 多態
> 靜態多態性:函數重載(不同參數)、運算符重載
> 動態多態性:虛函數、抽象函數、隱藏方法
- 封裝

- 繼承
- this : 當前類對象
- base : 父類對象
- sealed:密封類,不能被繼承
-
sealed class Entity { } class Car : Entity // sealed不能被繼承,會報錯 { }
-
- C#不支持類的多繼承,但可以繼承多個接口(interface)
-
- 父類初始化:在初始化列表里進行
-
public Tabletop(double l, double w) : base(l, w) { }
- 構造函數
- 在創建類對象時,會執行構造函數:Car car = new Car()
- 構造順序:先調用父類的構造,再調用子類的
- 析構函數
- 在釋放對象時(GC垃圾回收),會執行析構函數
- 析構順序:先調用子類的析構,再調用父類的
-
class Entity { // 默認構造函數,無參數 public Entity() { Debug.Log("new Entity"); } // 帶參構造函數 public Entity(int id) { Debug.Log("new Entity id=" + id); } // 重寫析構函數,一個類只能有一個,且無參數 ~Entity() { Debug.Log("delete Entity"); } } class Car : Entity // sealed不能被繼承,會報錯 { // 不寫父類的構造,默認調用默認構造函數Entity() public Car() { Debug.Log("new Car"); } // 在初始化列表: 調用父類的帶參構造函數 public Car(int id) : base(id) { Debug.Log("new Car"); } // 重寫析構函數,一個類只能有一個,且無參數 ~Car() { Debug.Log("delete Car"); } }
- 多態
-
靜態多態性
-
函數重載(不同參數)
-
public Tabletop(double l, double w) : base(l, w) { }
-
-
-
- 運算符重載
-
public static Box operator +(Box b, Box c) { }
-
-
- 動態多態性:子類重寫父類方法
- 虛函數
- 關鍵字:virtual / override
- 子類重寫虛函數后:不管在父類中調用,還是在子類中調用,執行的都是子類重寫后的函數 (Car.Move)
- 調用父類虛函數的方法:通過 base 關鍵字調用(base.Move())
- 注:子類也可不重寫父類的虛函數
-
class Entity { public void AI() { Move(); } public virtual void Move() // 虛函數聲明 virtual { Debug.Log("Entity.Move"); } } class Car : Entity { // 重寫方法 public override void Move() // 重寫虛函數 override { Debug.Log("Car.Move"); } }
// 測試Test void Start() { Entity entity = new Car(); Car car = (Car)entity; entity.Move(); // 輸出:Car.Move car.AI(); // 輸出:Car.Move }
- 虛函數
- 動態多態性:子類重寫父類方法
-
-
- 抽象函數
- 關鍵字:abstract / override
- 注:父類只聲明抽象函數,沒有具體實現
- 注:存在抽象函數的類,必須聲明為抽象類,且不能實例化成對象
-
// 含有抽象函數,必須聲明為抽象類,且不能實例化對象 abstract class Entity { public abstract void Move(); // 抽象函數 } class Car : Entity { // 子類必須實現父類的抽象函數 public override void Move() { Debug.Log("Car.Move"); } } void Start() { // 報錯,抽象類無法實例化 //Entity entity2 = new Entity(); Car entity = new Car(); entity.Move(); // 輸出:Car.Move }
- 抽象函數
-
-
-
- 隱藏方法 (不推薦,容易出錯)
- 子類重寫父類虛函數時,不寫 override 關鍵字,編譯器也會顯示警告(warning)
- 注:具體執行父類/子類函數,根據調用環境決定(指向對象的引用類型、父類or子類其他函數中進行調用)
- 測試1:不同引用類型調用Move 、通過AI()函數中調用
-
class Entity { public void AI() { Move(); } public virtual void Move() { Debug.Log("Entity.Move"); } } class Car : Entity { // 不寫override public void Move() { Debug.Log("Car.Move"); } } void Start() { Entity entity = new Car(); Car car = (Car)entity; entity.Move(); // 輸出:Entity.Move entity.AI(); // 輸出:Entity.Move car.Move(); // 輸出:Car.Move car.AI(); // 輸出:Entity.Move }
-
- 測試2:子類重寫AI()函數
-
class Entity { public virtual void AI() { Move(); } public virtual void Move() { Debug.Log("Entity.Move"); } } class Car : Entity { public override void AI() { Move(); } // 不寫override public void Move() { Debug.Log("Car.Move"); } } void Start() { Entity entity = new Car(); Car car = (Car)entity; entity.Move(); // 輸出:Entity.Move entity.AI(); // 輸出:Car.Move car.Move(); // 輸出:Car.Move car.AI(); // 輸出:Car.Move }
-
- 隱藏方法 (不推薦,容易出錯)
-
【接口】
> 關鍵字:interface
> 相當於是個規則,里面只能有:方法、屬性、索引、事件
> 一個類只能繼承一個父類,但可以實現多個接口
> 注:接口 也可以 繼承另一個 接口
> 注:一個類繼承了接口后,接口中所有的方法(包括屬性、索引、事件)都必須實現
> 注:實現接口中的方法必須定義為 public
interface EntityInterface { int Value { get; set; } // 可以聲明屬性,但子類中必須實現 void Move(); // 不用寫修飾符(public) //int value; // 不能定義變量 //void AI() { Move(); } // 不能實現接口函數 } class Car : EntityInterface { // 必須實現接口中的屬性,且必須為 public public int Value { get; set; } // 必須實現接口中的函數,且必須為 public public void Move() { } }
【擴展知識】
- 隱藏基類的成員
- 子類通過new關鍵字,隱藏父類的成員數據、方法
- 具體調用的是哪個?根據調用環境決定(指向對象的引用類型、父類or子類其他函數中進行調用)

-
- 案例1:不同引用類型獲取.value、調用父類的PrintValue
-
class A { public int value = 1; public void PrintValue() { Debug.Log("A.value=" + value); } } class B : A { public new int value = 2; } void Start() { A a = new B(); B b = (B)a; Debug.Log(a.value); // 1 Debug.Log(b.value); // 2 a.PrintValue(); // A.value=1 b.PrintValue(); // A.value=1 }
-
- 案例2:子類隱藏父類的PrintValue
-
class A { public int value = 1; public void PrintValue() { Debug.Log("A.value=" + value); } } class B : A { public new int value = 2; // 隱藏父類函數 public new void PrintValue() { Debug.Log("B.value=" + value); } } void Start() { A a = new B(); B b = (B)a; a.PrintValue(); // A.value=1 b.PrintValue(); // B.value=2 }
-
- 案例3:子類重寫父類的PrintValue
-
class A { public int value = 1; public virtual void PrintValue() { Debug.Log("A.value=" + value); } } class B : A { public new int value = 2; // 重寫父類函數 public override void PrintValue() { Debug.Log("B.value=" + value); } } void Start() { A a = new B(); B b = (B)a; a.PrintValue(); // B.value=2 b.PrintValue(); // B.value=2 }
-
- 案例1:不同引用類型獲取.value、調用父類的PrintValue
-
struct 、class
-
類 是 引用類型,結構體 是 值類型
-
因此結構體不能=null,也不需要使用new
-
結構體 不支持繼承
-
結構體 不能聲明 默認(無參)構造函數,不能聲明 析構函數
-
結構體成員 不能指定為 abstract、virtual 或 protected
-
抽象函數(abstract) 、虛函數(virtual)
-
abstract:父類只聲明沒有具體實現,子類必須重寫實現父類中的抽象函數
-
virtual:子類可以不重寫父類的虛函數
-
抽象類(abstract) 、接口(interface)
-
抽象類:依然是一個類,不能被實例化,它仍然包含類的函數
-
接口:相當於是個規則,里面只能有方法、屬性、索引、事件
-
抽象類:有抽象的方法,也有不抽象的方法。子類必須實現父類的抽象方法
-
接口:繼承了接口后,所有的接口方法都必須實現
-
一個類只能繼承一個父類,但是可以實現多個接口
