# 第6章:物件與類別
### 6.1 前言
早期軟體開發主要使用程序導向設計,其特點是結構化、可讀性高、易偵錯和維護。程序導向設計通過「自上而下分析」、「模組化設計」和提供足夠的流程控制來實現,並使用「分而治之」的方法將大問題分解為小問題解決。然而,程序導向方法在面對複雜系統時會遇到資料和方法相依性的挑戰,導致後期開發速度減慢。
物件導向程式設計 (OOP) 則將資料和方法封裝在一起成為物件,模擬真實世界的關係,使得設計和理解程式更加直觀。OOP 的出現解決了傳統程序導向在面對複雜系統時的不足,例如 Windows 系統,透過 GUI 介面讓操作更簡單,但需要更複雜的系統處理,因而 OOP 成為開發複雜應用程式的必要方法。
---
### 6.2.1
**i.) 物件的屬性**
在物件導向程式設計 (OOP) 中,物件具有屬性(通常使用 Property 一詞)。屬性描述了物件的外觀和特質。以 David 為例,David 的身高是 180 公分,其中 "David" 是物件名稱,"Tall" 是屬性名稱,"180" 是屬性值。用 C# 表示如下:
```
David.Tall = 180;
```
物件名稱和屬性名稱之間用點號區隔。一個物件可以有多個屬性,如 David 的體重、出生日期和血型等。在視窗應用程式中,表單和其上的控制項也是物件,每個物件都有許多屬性,有些是特有的,有些是共享的。
在整合開發環境 (IDE) 的屬性視窗中,會列出當前表單和所有控制項的屬性名稱,這些屬性可以在編輯階段修改,也可以在程式中設定或修改。示例:
```
label1.Text = "Hello World!"; // 設定標籤控制項的文字為 "Hello World!"
label1.BackColor = Color.Yellow; // 設定標籤控制項的背景色為黃色
```
相同的物件類型 (如標籤控制項) 具有相同的屬性,但屬性值可以不同。例如,David 和 Tom 同樣是人 (物件),但 David 的身高 (屬性) 比 Tom 高,David 的體重 (屬性) 比 Tom 輕。屬性值可以由物件自行改變(如人會長高、變老),也可以由外界改變(如染髮)。
**ii.) 物件具有方法**
每個物件除了屬性外,還具有「行為」,這些行為由物件的方法 (Method) 來定義。例如,人會走路,車子會跑。物件之間可以透過方法進行互動,如開車時用手轉動方向盤讓車子轉彎。方法是物件可執行的動作。
#### 方法的使用
方法通常伴隨參數。以下例子將車子 (Carl) 移動到座標 (100, 200):
```
Carl.Move(100, 200);
```
這裡,Move 是方法,100 和 200 是 X 和 Y 座標的參數。再舉例,‘Console’ 類別的 ‘WriteLine’ 方法:
```
Console.WriteLine("Hello World!");
```
這個方法在螢幕顯示 "Hello World!",並將游標移到下一行。
#### 簡化方法數目
使用參數可以簡化方法數目。例如,設計汽車的排檔方法。如果分別為每個檔位設計不同的方法:
```
Carl.Gear1(); // 一檔
Carl.Gear2(); // 二檔
Carl.Gear3(); // 倒檔
```
這樣會導致方法數目過多。使用參數來解決:
```
Carl.Gear("Forward", 1); // 使用前進一檔
Carl.Gear("Forward", 2); // 使用前進二檔
Carl.Gear("Backward", 1); // 使用倒檔
```
這樣只需一個 Gear 方法,並通過參數來控制不同檔位和方向,使程式設計更簡潔。
#### iii.) 物件的訊息與事件
#### 訊息
在物件導向程式設計中,方法代表物件的行為或功能,用於處理由外部傳入的訊息並作出回應。物件透過訊息進行互相交流,這是物件活動的核心。沒有訊息,物件就無法運作,就像人無法與外界溝通一樣。因此,訊息是物件不可或缺的特性。
#### 事件
事件是物件的一種方法,但這種方法是由物件本身或其他物件來啟動執行的。例如,當在 button1 按鈕上按一下時,會觸發該按鈕的 Click 事件。在程式中,需要先設定當 button1 的 Click 事件被觸發時,會由 button1_Click 事件處理函式來處理。以下是設定方法:
```
button1.Click += new EventHandler(button1_Click);
void button1_Click(object sender, EventArgs e)
{
// 處理按鈕點擊事件的程式碼
}
```
#### iv.) 物件的訊息與事件
在物件導向程式設計中,同一類別的物件在執行時必須能夠被清楚辨別,以便程式能正確存取屬性或執行方法。例如,命令一號車前進、二號車後退,就必須能區分這些命令是針對哪個物件發出的。以下是範例:
```
Carl.Forward(); // 一號車前進
Car2.Backward(); // 二號車後退
```
在這裡,Carl 和 Car2 是一號車和二號車的物件名稱,Forward 和 Backward 是這些物件的方法。C# 會為不同的物件分配不同的記憶體空間,使其能夠獨立運作。
---
### 6.2.2
### 類別
##### 定義
* 類別:一種設計模板,用來描述一群具有相同性質的物件的屬性和方法。
* 物件:根據類別模板創建的具體實體,可以執行類別定義的方法。
##### 比喻
* 類別:如同建築的藍圖,描述建築物的結構和功能,但本身不是建築物。
* 物件:根據藍圖建造的建築物,是類別的具體實體。
##### 範例
* 類別:車子,包含輪子、方向盤、煞車等屬性,和會跑、會停、會轉彎等方法。
* 物件:腳踏車、越野車,都是車子類別的具體實體。
##### 使用
* 類別:定義結構和行為,不可直接使用。
* 物件:依據類別生成,實際操作和執行。
##### 例子
```
int A; // 宣告變數A是一個整數
int B; // 宣告變數B是一個整數
A = 10; // 正確,將10賦值給變數A
B = A + 5; // 正確,將A加5的結果賦值給變數B
int = 20; // 錯誤,int 是類別,不能直接賦值
```
這段程式碼中,int 是一個類別,A 和 B 是由 int 類別衍生的物件。類別本身不能直接賦值或運算,但通過物件 A 和 B 可以進行操作。
---
### 6.3 物件導向程式設計的特性
在使用物件導向程式設計(Object-Oriented Programming, OOP)之前,除了理解物件和類別的關係,還需要了解物件導向程式設計的特性。這些特性包括:
#### 1. 抽象化 (Abstraction)
抽象化是將現實世界中的實體抽取出其本質特徵,而忽略其非本質特徵的過程。在程式設計中,抽象化是通過定義類別來描述物件的屬性和方法,使複雜系統簡化為可管理的模組。
#### 2. 封裝 (Encapsulation)
封裝是將物件的狀態(屬性)和行為(方法)包裝在一起,並隱藏物件的內部細節,只對外界提供有限的接口。這樣可以保護物件的資料,防止外部程式直接修改,並使程式更易於維護和修改。
#### 3. 繼承 (Inheritance)
繼承是新類別(子類別)從現有類別(父類別)獲得屬性和方法的機制。繼承使得子類別可以重用父類別的程式碼,並可以擴展或修改父類別的行為,從而實現程式碼的重用和擴展。
#### 4. 多形 (Polymorphism)
多形是指相同的方法在不同物件上可以有不同的實現。這意味著一個方法可以在不同的類別中有不同的行為,從而使程式具有更大的靈活性和可擴展性。
#### 5. 動態繫結 (Dynamic Binding)
動態繫結是指在執行期間(而不是在編譯期間)決定方法呼叫的具體實現。這使得程式可以在運行時根據物件的實際類型動態地選擇適當的方法實現,從而增加程式的靈活性和可維護性。
---
### 6.4 物件與類別的建立
#### 6.4.1 如何建立類別
在 C# 中,使用 class 關鍵字來定義一個類別。需要注意的是,類別的定義可以放在任何地方,但不能放在方法(函式)或事件中,也不能放在 namespace{…} 區域外面。換句話說,類別定義必須是全域性的宣告。
#### 範例
以下是一個使用 class 定義一個空白類別 MyFirstClass 的範例,並使用這個類別來建立物件名稱為 A 的物件:
```
using System;
namespace MyNamespace
{
// 定義類別 MyFirstClass
class MyFirstClass
{
// 這裡可以添加屬性和方法
}
class Program
{
static void Main(string[] args)
{
// 建立一個屬於 MyFirstClass 類別的物件 A
MyFirstClass A = new MyFirstClass();
Console.WriteLine("建立一個屬於 MyFirstClass 類別的物件");
Console.WriteLine("物件已建立完成!!");
Console.WriteLine("請按<Enter>鍵結束");
Console.ReadLine();
}
}
}
```
##### 執行結果
建立一個屬於 MyFirstClass 類別的物件
物件已建立完成!!
請按<Enter>鍵結束
---
#### 6.4.2 命名空間
命名空間 (Namespace) 可以有效地將眾多物件根據其用途進行分類,並避免不同廠商採用相同名稱時造成的衝突。假設我們要在同一個 C# 程式檔中宣告兩個名稱相同的類別,C# 編譯器會因為名稱重複而出現錯誤。此時,可以利用命名空間來解決這個問題。
#### 範例
假設 IBM 和 Apple 兩家公司都生產筆記型電腦 Notebook,可以使用命名空間來區分這兩家公司的 Notebook 類別。
程式碼
```
using System;
namespace IBM
{
// 定義 IBM 命名空間中的 Notebook 類別
class Notebook
{
public void Display()
{
Console.WriteLine("IBM Notebook");
}
}
}
namespace Apple
{
// 定義 Apple 命名空間中的 Notebook 類別
class Notebook
{
public void Display()
{
Console.WriteLine("Apple Notebook");
}
}
}
class Program
{
static void Main(string[] args)
{
// 使用 IBM 命名空間中的 Notebook 類別
IBM.Notebook ibmNotebook = new IBM.Notebook();
ibmNotebook.Display(); // 輸出: IBM Notebook
// 使用 Apple 命名空間中的 Notebook 類別
Apple.Notebook appleNotebook = new Apple.Notebook();
appleNotebook.Display(); // 輸出: Apple Notebook
Console.WriteLine("請按<Enter>鍵結束");
Console.ReadLine();
}
}
```
這樣一來,即使兩個命名空間中都定義了 Notebook 類別,也不會發生名稱衝突,因為它們位於不同的命名空間中。命名空間可以有效地組織和管理程式碼,特別是在大型應用程式中。
#### 巢狀命名空間
在此範例中,IBM 公司細分成台灣 IBM 和日本 IBM,並在各自的子命名空間中定義 Notebook 類別。
程式碼
```
using System;
namespace IBM
{
namespace Taiwan
{
// 定義台灣 IBM 命名空間中的 Notebook 類別
class Notebook
{
public void Display()
{
Console.WriteLine("Taiwan IBM Notebook");
}
}
}
namespace Japan
{
// 定義日本 IBM 命名空間中的 Notebook 類別
class Notebook
{
public void Display()
{
Console.WriteLine("Japan IBM Notebook");
}
}
}
}
namespace Apple
{
// 定義 Apple 命名空間中的 Notebook 類別
class Notebook
{
public void Display()
{
Console.WriteLine("Apple Notebook");
}
}
}
class Program
{
static void Main(string[] args)
{
// 使用台灣 IBM 命名空間中的 Notebook 類別
IBM.Taiwan.Notebook taiwanNotebook = new IBM.Taiwan.Notebook();
taiwanNotebook.Display(); // 輸出: Taiwan IBM Notebook
// 使用日本 IBM 命名空間中的 Notebook 類別
IBM.Japan.Notebook japanNotebook = new IBM.Japan.Notebook();
japanNotebook.Display(); // 輸出: Japan IBM Notebook
// 使用 Apple 命名空間中的 Notebook 類別
Apple.Notebook appleNotebook = new Apple.Notebook();
appleNotebook.Display(); // 輸出: Apple Notebook
Console.WriteLine("請按<Enter>鍵結束");
Console.ReadLine();
}
}
```
這樣一來,即使在更細分的情況下,也能夠清晰地區分不同分支機構中的類別,避免名稱衝突,並且使程式碼結構更加清晰和易於管理。
---
#### 6.4.3 建立屬性
建立屬性的方式大致上可以分成以下兩種方法:
1. 直接在類別中宣告 public 變數。
2. 使用 get 和 set 存取子。
#### 一、如何使用 public 變數建立物件屬性
```
using System;
namespace VehicleNamespace
{
// 定義 Car 類別
public class Car
{
// 宣告 public 變數 Speed 作為屬性
public int Speed;
}
class Program
{
static void Main(string[] args)
{
// 建立 Car 類別的物件 myCar
Car myCar = new Car();
// 設定 myCar 的 Speed 屬性
myCar.Speed = 100;
// 輸出 myCar 的 Speed 屬性值
Console.WriteLine("Car speed: " + myCar.Speed + " km/h");
Console.WriteLine("請按<Enter>鍵結束");
Console.ReadLine();
}
}
}
```
---
#### 6.4.4 如何建立方法
```
namespace ConsoleMemMethod1
{
class Car // 定義Car類別
{
// 宣告私有變數_x, _y用來表示目前車子的X, Y座標位置
private int _x, _y;
// 定義Movie方法,用來設定目前車子的X, Y座標位置
public void Move(int vX, int vY)
{
_x = vX;
_y = vY;
}
}
class Program
{
static void Main(string[] args)
{
Car Benz = new Car();
Benz.Move(100, 200);
}
}
}
```
新增了一個Accelerate方法,用來將車子的速度加1
```
namespace ConsoleCallPropertyFunction1
{
class Car // 定義Car類別
{
// 宣告_speed私有變數用來存放車子的速度值
private int _speed = 0;
// 定義Speed速度屬性
public int Speed
{
get
{
return _speed; // 傳回目前的速度
}
set
{
if (value < 0) value = 0; // 速度不可小於0
if (value > 200) value = 200; // 速度不可大於200
_speed = value; // 設定速度
}
}
// 定義Accelerate()方法,用來指定目前車子速度+1
public void Accelerate()
{
Speed++; // 速度 + 1
}
}
class Program
{
static void Main(string[] args)
{
Car Benz = new Car();
Benz.Speed = 199;
Console.WriteLine($"\n 1. 現在速度:{Benz.Speed}");
Console.WriteLine("\n 加速 ...");
Benz.Accelerate();
Console.WriteLine($"\n 2. 現在速度:{Benz.Speed}");
Console.WriteLine("\n 加速 ...");
Benz.Accelerate();
Console.WriteLine($"\n 3. 現在速度:{Benz.Speed}");
Console.Read();
}
}
}
```
---
6.4.5 如何建立事件
#### 步驟一:建立委派(delegate)型別
委派型別類似於C++中的函式指標,可以指向方法的參考指標。在事件處理上,委派型別最常應用。本例使用下面的敘述定義事件的委派型別,名稱為DangerEvent,事件傳入的參數為一個int整數型別。
```
delegate void DangerEvent(int vSpeed);
```
#### 步驟二:建立事件
在類別中宣告一個事件,這個事件的名稱叫做Danger,屬於DangerEvent委派型別,且有一個整數參數vSpeed。
```
public event DangerEvent Danger;
```
#### 步驟三:觸動事件
當物件發覺Speed屬性值超過200時,在Speed屬性的set區段中,呼叫事件。
```
if (Danger != null)Danger(value);
```
#### 步驟四:定義事件處理函式
在Program類別中設計一個靜態方法TooFast來當做事件處理函式,參數要和Car類別中的Danger事件定義一樣。
#### 步驟五:指定事件處理方法
在宣告Car物件後,使用new關鍵字來建立屬於DangerEvent委派型別的實體,然後將Danger事件對應到TooFast方法。
```
Benz.Danger += new DangerEvent(TooFast);
```
---
### 6.5 實例-堆叠
堆疊(Stack)是一種後進先出(Last In, First Out, LIFO)的資料結構,可以通過壓入(Push)和彈出(Pop)操作來管理資料。
在程式設計中,堆疊通常用於管理函式的呼叫、運算式的評估、暫存資料等場景。
---
## 以上所有程式碼均可在課本書附案例中找到
這裏幫大家附上鏈接
\\/\\/\\/\\/\\/\\/\\/\\/
[](https://)https://www.gotop.com.tw/books/download.aspx?bookid=AEL022700