2026-04-23 一步步地深入了解 C# 中類別(Classes)的語法構造及其背後的設計理念
2026-04-23 一步步地深入了解 C# 中類別(Classes)的語法構造及其背後的設計理念
我:
妳一步一步來, 然後出題問我, 確定我能否理解. 比如說 為什麼類別型前面的修飾詞為什麼要用 static void?
GPT:
當然,讓我們一步步地深入了解 C# 中類別(Classes)的語法構造及其背後的設計理念。我們將從最基本的編程概念開始,逐漸引入類別和物件,並通過實際範例和問題來確保你能夠理解每個部分。
目錄
- 程序式編程的基礎
- 程序式編程的限制
- 引入類別與物件
- 類別的基本構造
- 存取修飾符(Access Modifiers)
- 方法的修飾符:
static和void - 繼承(Inheritance)
- 多態性(Polymorphism)
- 封裝(Encapsulation)
- 小結與練習題
1. 程序式編程的基礎
程序式編程(Procedural Programming)
在程序式編程中,程式被組織為一系列的函數或方法,這些函數按照順序執行。這種編程方式強調的是「做什麼」(What to do),而不是「是什麼」(What something is)。
範例:簡單的程序式代碼
1 | using System; |
輸出範例:
1 | 請輸入你的名字: Alice |
解析
- 程式由
Main方法開始執行,依次呼叫GetName、GetAge和DisplayInfo方法。 - 每個方法負責一個特定的功能,如獲取使用者輸入或顯示信息。
2. 程序式編程的限制
隨著程式規模的增大,程序式編程可能會面臨以下挑戰:
代碼重用困難:
- 當需要多次使用相同的功能時,可能會重複編寫相似的代碼,導致代碼冗餘和難以維護。
維護困難:
- 大型程序中,找到特定功能的代碼位置變得困難,修改一處代碼可能影響到其他部分。
數據與功能的分離:
- 數據(變數)和操作數據的函數是分開的,缺乏組織,難以理解數據和功能之間的關係。
缺乏模組化:
- 難以將程式劃分為獨立的模組或部分,每個部分都可以獨立開發和測試。
範例:代碼重用困難
假設我們有多個不同的地方需要獲取使用者的名字和年齡,可能會重複呼叫 GetName 和 GetAge 方法,但這些方法並沒有組織在一起,難以管理。
3. 引入類別與物件
面向物件程式設計(OOP)
為了解決上述問題,面向物件程式設計(Object-Oriented Programming, OOP) 被引入。OOP 將數據和操作數據的功能組織在一起,形成物件(Objects),並通過類別(Classes)來定義物件的結構和行為。
類別與物件的概念
- 類別(Class):類別是物件的藍圖,定義了物件的屬性(數據)和方法(功能)。
- 物件(Object):物件是類別的實例,擁有類別定義的屬性和方法。
為什麼使用類別?
代碼重用:
- 通過定義類別,可以創建多個物件,重複使用相同的代碼。
封裝:
- 將數據和操作數據的方法組織在一起,保護數據不被外部直接訪問或修改。
模組化:
- 將程式劃分為獨立的類別,每個類別負責特定的功能,提升代碼的可維護性和可讀性。
繼承與多態性:
- 通過繼承,可以創建更具特化性的類別,並實現多態性,增強靈活性。
範例:轉換為類別
讓我們將之前的程序式代碼轉換為面向物件的方式。
1 | using System; |
輸出範例:
1 | 請輸入你的名字: Bob |
解析
- 定義了一個
Person類別,包含Name和Age屬性,以及GetName、GetAge和DisplayInfo方法。 - 在
Main方法中,創建了一個Person物件,並呼叫其方法來獲取和顯示信息。 - 這樣的結構更具組織性,且易於擴展和維護。
4. 類別的基本構造
類別的結構
一個完整的 C# 類別通常包含以下部分:
修飾符(Modifiers):
- 定義類別的可見性和其他屬性,如
public、private、abstract等。
- 定義類別的可見性和其他屬性,如
類別名稱(Class Name):
- 遵循命名規則,通常使用 Pascal 命名法。
繼承(Inheritance):
- 指定類別從哪個基類繼承,使用冒號
:。
- 指定類別從哪個基類繼承,使用冒號
介面實作(Interface Implementation):
- 指定類別實作哪些介面。
成員(Members):
- 包含欄位(Fields)、屬性(Properties)、方法(Methods)、建構函數(Constructors)等。
範例:類別的基本構造
1 | public class Car |
解析
public class Car:定義了一個公開的Car類別。欄位:
private string model;:私有欄位,只能在Car類別內部訪問。
屬性:
public string Make { get; set; }:公開屬性,提供對Make的讀寫訪問。public string Model:公開屬性,通過get和set訪問私有欄位model。
建構函數:
public Car(string make, string model):初始化Make和Model屬性。
方法:
public void DisplayInfo():顯示車輛信息。
5. 存取修飾符(Access Modifiers)
存取修飾符是什麼?
存取修飾符(Access Modifiers) 是用來控制類別及其成員(如方法、屬性、欄位等)的可見性和訪問範圍的關鍵字。透過存取修飾符,你可以決定哪些部分的程式碼可以訪問特定的類別或其成員,從而實現資訊隱藏(Information Hiding)和封裝(Encapsulation)。
C# 中的主要存取修飾符
C# 提供了多種存取修飾符,每種修飾符定義了不同的訪問範圍。以下是主要的存取修飾符及其說明:
a. public
- 描述:公開的,沒有任何限制。任何其他類別或組件都可以訪問標記為
public的類別或成員。 - 使用場景:當你希望某個類別或成員能被所有其他程式碼訪問時使用。
範例:
1 | public class Person |
b. private
- 描述:私有的,只能在定義它的類別內部訪問。其他類別無法訪問標記為
private的成員。 - 使用場景:當你希望某個成員只在類別內部使用,並且不允許外部直接訪問或修改時使用。
範例:
1 | public class Person |
c. protected
- 描述:受保護的,只能在定義它的類別及其派生類別中訪問。
- 使用場景:當你希望某個成員在類別內部及其子類別中可訪問,但不允許其他外部類別訪問時使用。
範例:
1 | public class Animal |
d. internal
- 描述:內部的,只能在同一個組件(Assembly)內部訪問。不同組件之間無法訪問標記為
internal的成員。 - 使用場景:當你希望某個類別或成員僅在其所在的組件內部使用,不對外部公開時使用。
範例:
1 | internal class Helper |
e. protected internal
- 描述:受保護且內部的,表示該成員可以在同一個組件內部訪問,或在任何派生類別中訪問(無論組件是否相同)。
- 使用場景:當你希望成員在內部組件內部及其派生類別中可訪問時使用。
範例:
1 | public class Vehicle |
f. private protected(C# 7.2 及以上版本)
- 描述:私有且受保護的,表示該成員只能在定義它的類別內部,或在同一組件中的派生類別中訪問。
- 使用場景:當你希望成員僅在類別內部及其派生類別(且必須在同一組件內部)中可訪問時使用。
範例:
1 | public class BaseClass |
了解存取修飾符的重要性
正確使用存取修飾符有助於:
- 實現封裝:保護內部數據不被外部直接訪問或修改。
- 控制依賴:限制哪些類別或模組可以訪問特定的成員,減少程式碼的耦合度。
- 提高安全性:防止敏感數據被未經授權的部分訪問或更改。
練習題 1
問題:
考慮以下類別定義,哪一行會導致編譯錯誤?為什麼?
1 | public class BankAccount |
選項:
A. BankAccount account = new BankAccount();
B. account.Balance = 1000m;
C. public decimal Balance { get; set; }
D. public void Deposit(decimal amount)
答案: B
解釋:
Balance屬性的set訪問器被標記為private,因此只能在BankAccount類別內部訪問。Main方法位於外部,無法訪問private的set方法,因此account.Balance = 1000m;會導致編譯錯誤。
6. 方法的修飾符:static 和 void
了解 static 和 void
在 C# 中,static 和 void 是用於方法定義的關鍵字,它們有不同的用途和意義。
static 關鍵字
描述:
static用於定義靜態方法、屬性、欄位等。靜態成員屬於類別本身,而不是類別的實例。用途:
- 靜態方法:可以在不創建類別實例的情況下呼叫。
- 靜態欄位:所有類別實例共享同一個靜態欄位。
- 靜態類別:包含僅有靜態成員的類別,無法被實例化。
範例:靜態方法和靜態類別
1 | using System; |
輸出:
1 | Pi 的值是 3.141592653589793 |
void 關鍵字
- 描述:
void表示方法不返回任何值。 - 用途:用於不需要返回結果的方法,如輸出信息、修改物件狀態等。
範例:
1 | public class Logger |
為什麼要使用 static 和 void?
**
static**:- 當某個方法的功能不依賴於類別的實例時,可以將其定義為靜態方法,這樣可以直接通過類別名稱呼叫,無需創建物件實例。
- 範例中的
MathUtilities類別是一個靜態類別,包含靜態方法Add和Multiply,這些方法不需要任何物件狀態即可運行。
**
void**:- 當一個方法的主要目的是執行某個操作而不需要返回任何結果時,使用
void作為返回類型。 - 範例中的
Logger.Log方法只是記錄一條信息,無需返回任何值,因此使用void。
- 當一個方法的主要目的是執行某個操作而不需要返回任何結果時,使用
練習題 2
問題:
以下哪一行的語法是正確的?
1 | public class Calculator |
選項:
A. Calculator.Add(5, 3);
B. Calculator.Multiply(5, 3);
C. new Calculator().Add(5, 3);
D. new Calculator().Multiply(5, 3);
答案: A 和 D
解釋:
- 選項 A (
Calculator.Add(5, 3);) 是正確的,因為Add方法是靜態方法,可以直接通過類別名稱呼叫。 - 選項 B (
Calculator.Multiply(5, 3);) 是錯誤的,因為Multiply方法是非靜態的,需要通過物件實例呼叫。 - 選項 C (
new Calculator().Add(5, 3);) 是錯誤的,因為Add方法是靜態的,不應該通過物件實例呼叫。 - 選項 D (
new Calculator().Multiply(5, 3);) 是正確的,因為Multiply方法是非靜態的,需要通過物件實例呼叫。
7. 繼承(Inheritance)
繼承的概念
繼承(Inheritance) 是 OOP 的一個重要特性,允許你基於現有的類別創建新的類別。新類別(派生類別)繼承自一個基類(父類別),並可以擴展或修改其行為。
為什麼需要繼承?
代碼重用:
- 重用基類中的屬性和方法,避免重複編寫相同的代碼。
建立類別層次結構:
- 將相關的類別組織在一起,形成層次化的結構,提高代碼的可理解性。
多態性:
- 通過基類引用來操作派生類物件,實現靈活的代碼設計。
繼承的語法
使用冒號 : 來指定繼承的基類。
範例:基類與派生類
1 | using System; |
輸出:
1 | Buddy 正在吃食物。 |
解析
**基類
Animal**:- 定義了
Name屬性和Eat方法。 - 定義了一個虛擬方法
Speak,允許派生類覆蓋。
- 定義了
**派生類
Dog**:- 繼承自
Animal。 - 覆蓋了基類的
Speak方法,提供了特定的實現。 - 定義了一個派生類特有的方法
Fetch。
- 繼承自
在
Main方法中:- 創建了一個
Dog物件myDog,並呼叫其方法。 - 使用基類引用
myAnimal指向myDog物件,展示了多態性。 - 注意:基類引用無法訪問派生類特有的方法,如
Fetch。
- 創建了一個
練習題 3
問題:
考慮以下類別定義,哪一行會導致編譯錯誤?為什麼?
1 | public class Vehicle |
選項:
A. Vehicle myVehicle = new Vehicle();
B. Vehicle myCar = new Car();
C. myVehicle.Start();
D. myCar.OpenTrunk();
答案: D
解釋:
myCar是一個基類Vehicle的引用,即使它實際上指向一個Car物件,但基類引用無法訪問派生類特有的方法OpenTrunk。因此,myCar.OpenTrunk();會導致編譯錯誤。
8. 多態性(Polymorphism)
多態性的概念
多態性(Polymorphism) 是 OOP 的另一個核心概念,允許相同的接口在不同的類別中有不同的實現。這使得程式碼更加靈活和可擴展。
為什麼需要多態性?
靈活性:
- 能夠使用基類引用來操作不同的派生類物件,減少程式碼的耦合度。
可擴展性:
- 新增派生類時,不需要修改使用基類引用的現有代碼。
代碼簡潔:
- 通過統一的接口來操作不同的類別,減少了代碼的冗餘。
多態性的實現
在 C# 中,多態性通常通過以下兩個步驟實現:
基類中的虛擬方法:
- 使用
virtual關鍵字標記基類中的方法,允許派生類覆蓋。
- 使用
派生類中的覆蓋方法:
- 使用
override關鍵字在派生類中覆蓋基類的方法,提供具體的實現。
- 使用
範例:多態性的應用
1 | using System; |
輸出:
1 | 圓形A 的半徑是 5,面積是 78.54。 |
解析
**基類
Shape**:- 定義了
Name屬性和虛擬方法Area和Display。
- 定義了
**派生類
Circle和Rectangle**:- 覆蓋了基類的
Area方法,提供了具體的計算邏輯。 - 覆蓋了基類的
Display方法,提供了更具體的信息顯示。
- 覆蓋了基類的
在
Main方法中:- 創建了一個包含不同派生類物件的
List<Shape>。 - 遍歷這個列表,呼叫每個物件的
Display方法,根據實際物件的類型執行相應的覆蓋方法。
- 創建了一個包含不同派生類物件的
練習題 4
問題:
以下程式碼的輸出是什麼?
1 | using System; |
選項:
A. Animal speaks
B. Cat meows
C. 會導致編譯錯誤
D. 不會有任何輸出
答案: B
解釋:
myAnimal是一個基類Animal的引用,但它指向一個Cat物件。- 由於
Speak方法是虛擬的(virtual),並且Cat類別覆蓋了它(override),因此呼叫myAnimal.Speak()時會執行Cat類別中的Speak方法,輸出 “Cat meows”。
9. 封裝(Encapsulation)
封裝的概念
封裝(Encapsulation) 是 OOP 的一個核心概念,旨在將數據(屬性)和操作數據的方法封裝在一起,並控制對這些數據的訪問。這有助於保護數據不被外部不當修改,並確保物件的一致性。
如何實現封裝
使用私有欄位(Private Fields):
- 將類別的欄位設為
private,防止外部直接訪問或修改。
- 將類別的欄位設為
使用公開屬性(Public Properties):
- 通過
get和set訪問器來提供對私有欄位的控制性訪問。
- 通過
使用方法(Methods):
- 提供公開的方法來操作和修改私有欄位,確保數據的一致性和有效性。
範例:封裝與存取修飾符
1 | public class BankAccount |
輸出:
1 | 存入: 500, 新餘額: 1500 |
解析
**私有欄位
balance**:- 只能在
BankAccount類別內部訪問,外部無法直接修改。
- 只能在
**公開屬性
Balance**:- 提供對
balance的讀取權限(get),但寫入權限(set)是私有的,只能在類別內部修改。
- 提供對
**公開方法
Deposit和Withdraw**:- 提供了控制
balance修改的方法,確保金額的有效性。
- 提供了控制
**私有方法
LogTransaction**:- 僅供
BankAccount類別內部使用,用於記錄交易(此範例中未實作)。
- 僅供
優點:
資訊隱藏:
- 外部程式碼無法直接訪問或修改
balance,只能通過公開的方法進行操作,確保數據的一致性和安全性。
- 外部程式碼無法直接訪問或修改
控制權限:
- 使用公開屬性和方法來控制對數據的訪問和修改,防止不當操作。
練習題 5
問題:
考慮以下類別定義,哪一行的代碼會導致編譯錯誤?為什麼?
1 | public class Person |
選項:
A. Person person = new Person();
B. person.SetName("John");
C. Console.WriteLine(person.Name);
D. person.Name = "Doe";
答案: D
解釋:
Name屬性的set訪問器被標記為private,因此只能在Person類別內部訪問。Main方法位於外部,無法直接修改Name屬性,因此person.Name = "Doe";會導致編譯錯誤。
10. 小結與練習
小結
我們已經逐步探討了 C# 中類別的語法構造及其背後的設計理念,從程序式編程的基礎到面向物件程式設計(OOP)的核心概念,包括:
程序式編程的基礎:
- 基於函數和指令的編程方式,適用於簡單的任務,但隨著程式規模增大,維護和重用變得困難。
程序式編程的限制:
- 代碼重用困難、維護困難、數據與功能的分離、缺乏模組化等問題。
引入類別與物件:
- 通過類別組織數據和功能,實現代碼重用、封裝和模組化,提升程式的可維護性和可讀性。
類別的基本構造:
- 學習類別的修飾符、名稱、繼承、介面實作、成員(欄位、屬性、方法、建構函數等)的結構。
存取修飾符(Access Modifiers):
- 使用存取修飾符控制類別及其成員的可見性,實現資訊隱藏和封裝。
**方法的修飾符:
static和void**:- 理解
static和void的用途,並學會如何正確使用它們來定義方法。
- 理解
繼承(Inheritance):
- 基於基類創建派生類,實現代碼重用和建立類別層次結構。
多態性(Polymorphism):
- 通過基類引用操作派生類物件,實現靈活的代碼設計和減少耦合。
封裝(Encapsulation):
- 將數據和操作數據的方法組織在一起,並控制對數據的訪問,確保物件的一致性和安全性。
練習題 6
問題:
請完成以下類別定義,使其符合封裝原則,並能正確顯示餘額。
1 | public class Wallet |
答案:
1 | public class Wallet |
解析
- **私有欄位
balance**:保護數據不被外部直接訪問。 - **公開屬性
Balance**:僅提供讀取權限,防止外部修改。 - **公開方法
AddMoney和SpendMoney**:通過方法來控制balance的修改,確保數據的有效性。
下一步
現在,你已經掌握了 C# 類別的基本構造、存取修飾符、靜態與非靜態方法、繼承和多態性的基礎知識。下一步,我們可以進一步探討以下主題:
- 屬性(Properties) 的進階使用,如自動實作屬性、只讀屬性等。
- 介面(Interfaces) 的深入理解與應用。
- 抽象類別(Abstract Classes) 與 封閉類別(Sealed Classes) 的使用場景。
- 委派(Delegates) 和 事件(Events) 的基礎知識。
- 例外處理(Exception Handling) 的基礎和最佳實踐。
提問
在我們繼續深入之前,請回答以下問題來檢查你的理解:
問題:
為什麼在 BankAccount 類別中,我們將 balance 欄位設為 private,並通過公開的 Deposit 和 Withdraw 方法來修改它,而不是直接將 balance 設為 public?
選項:
A. 為了讓外部程式碼更容易訪問和修改 balance。
B. 為了保護數據不被外部不當修改,確保數據的一致性和安全性。
C. 因為 C# 語法要求所有欄位必須是私有的。
D. 為了讓 balance 可以在派生類別中被訪問。
請選出正確答案並解釋原因。
答案: B
解釋:
將 balance 欄位設為 private,並通過公開的 Deposit 和 Withdraw 方法來修改它,是為了保護數據不被外部不當修改,確保數據的一致性和安全性。這種做法實現了封裝(Encapsulation),避免了外部直接操作 balance,從而可以在方法中添加必要的邏輯來驗證和控制數據的修改。
總結
通過這一系列的步驟,我們從最基本的程序式編程開始,逐漸引入了類別與物件、存取修飾符、靜態與非靜態方法、繼承和多態性等 OOP 的核心概念。這些知識將幫助你設計出結構良好、易於維護和擴展的 C# 程式。
接下來的步驟
屬性的進階使用:
- 了解自動實作屬性、只讀屬性、計算屬性等。
介面(Interfaces):
- 深入理解介面的概念,如何定義和實作介面,介面的應用場景。
抽象類別與封閉類別:
- 探討何時使用抽象類別,何時使用封閉類別,及其在設計中的作用。
委派與事件:
- 學習委派的基本概念,如何使用委派來實現事件驅動的程式設計。
例外處理:
- 理解如何使用
try-catch塊來處理例外,保證程式的穩定性和可靠性。
- 理解如何使用
如果你對以上內容有任何疑問,或希望深入了解某個特定主題,請隨時告訴我!