這篇筆記會接續上一篇的內容,繼續講解類別相關的觀念與功能。
在同一個類別中,允許建立多個同名但參數不同的函式,這種機制稱為「多載 (Overloading)」。
以先前的Animal案為例,當我們在子類別Dog中新增函式時,只要讓它們根據傳入的參數數量或類型來執行不同的邏輯,就能成功實現函式多載。
// Dog Only Method
public string Play() => $"{Name} is playing with a ball";
public string Play(string toy) => $"{Name} is playing with a {toy}";
public void Play(string toy, int times)
{
for (int i = 0; i < times; i++)
{
Console.WriteLine($"{Name} is playing with a {toy} {i + 1} time(s)");
}
}
public void Play(string toy, int times, string location)
{
for (int i = 0; i < times; i++)
{
Console.WriteLine($"{Name} is playing with a {toy} in the {location} {i + 1} time(s)");
}
}
using Class_02;
var dog = new Dog("Rover", 5);
Console.WriteLine(dog.Play());
Console.WriteLine(dog.Play("frisbee"));
dog.Play("frisbee", 3);
dog.Play("frisbee", 3, "park");
在C#中,我們可以為函式的參數設定「預設值」。當呼叫函式不傳入該參數時,系統就會自動採用預設值。
延續先前的Play()函式範例,如果我們將原本用來處理多載的無參數Play()註解掉,並直接在帶參數的 Play(string item = "ball") 中加上預設值,就能用更精簡的程式碼達到相同的效果。
//public string Play() => $"{Name} is playing with a ball";
Console.WriteLine(dog.Play());//出現錯誤
預設參數翻立
public string Play(string toy = "ball") => $"{Name} is playing with a {toy}";//指定參數
public void Play(string toy = "ball", int times = 1)
{
for (int i = 0; i < times; i++)
{
Console.WriteLine($"{Name} is playing with a {toy} {i + 1} time(s)");
}
}
若同時使用多載與預設參數,需要特別注意編譯器的多載解析(Overload Resolution)限制。一旦定義了多個都帶有預設參數的同名函式,外部呼叫時若省略了引數,系統便會陷入無法判斷要調用哪一個方法成員的窘境,進而跳出編譯出錯的警告。
運算子多載(Operator Overloading)是OOP(物件導向)中很常見的功能。它的核心概念是「讓自訂類別,也能像內建型別一樣使用 + - == > 等運算子」
例如,這裡 + 是內建的。
int a = 3 + 5;
但如果你自己做一個Vector2類別:
Vector2 a = new Vector2(1, 2);
Vector2 b = new Vector2(3, 4);
Vector2 c = a + b;
正常情況下a + b其實不能用。
因為C#不知道「兩個 Vector2 相加到底代表什麼?」
範例:
namespace Class_02;
public class Vector
{
public double X;
public double Y;
public Vector(double x, double y)
{
X = x;
Y = y;
}
//operator overloading : +, -, ==, !=
public static Vector operator +(Vector p1, Vector p2)
=> new Vector(p1.X + p2.X, p1.Y + p2.Y);
public static Vector operator -(Vector p1, Vector p2)
=> new Vector(p1.X - p2.X, p1.Y - p2.Y);
public static bool operator ==(Vector p1, Vector p2)
=> p1.X == p2.X && p1.Y == p2.Y;
public static bool operator !=(Vector p1, Vector p2)
=> !(p1 == p2);
public override string? ToString()
{
return $"({X}, {Y})";
}
}
ToString()是用來定義當你的物件被轉成字串或被印出來時要顯示的內容,因為預設情況只會顯示類別名稱不好讀,所以你override它之後,就可以像 return $"({X}, {Y})"; 這樣讓輸出變成有意義的格式,例如座標顯示成 (3, 4),方便除錯和輸出資訊。
using Class_02;
var p1 = new Point(1, 2);
var p2 = new Point(2, 3);
var p3 = p1 + p2;
Console.WriteLine($"{p1} + {p2} = {p3}");
Console.WriteLine();
var p4 = p1 - p2;
Console.WriteLine($"{p1} - {p2} = {p4}");
Console.WriteLine();
Console.WriteLine($"{p1} == {p2} : {p1 == p2}");
常見的有:
| 運算子 | 用途 |
|---|---|
+ |
相加 |
- |
相減 |
* |
乘法 |
/ |
除法 |
== |
是否相等 |
!= |
不等 |
> < |
比較 |
++ -- |
遞增遞減 |
標註static的這個成員(變數或方法)屬於「類別本身」而不是某個物件,所以不需要建立實例(new)就可以直接用類別名稱存取,而且所有物件共用同一份static資料,代表的是整個類別層級的共用狀態或功能,而不是每個物件各自擁有一份。
namespace Class_02;
public class Cat : Animal
{
public static int Counter { get; set; }
public static int DoCount() => Counter;
public int CatCount() => Counter;
public Cat()
{
Name = "Dog";
Counter++;
}
public Cat(string name, int age)
{
Name = name;
Age = age;
Counter++;
}
public override void MakeSound()
{
Console.WriteLine("Meow Meow Meow");
}
}
using Class_02;
var cat1 = new Cat("Whiskers", 3);
var cat2 = new Cat("Mittens", 2);
var cat3 = new Cat("Shadow", 4);
Console.WriteLine(Cat.Counter);//靜態成員,因此用類別名稱存取
Console.WriteLine(Cat.DoCount());//靜態成員,因此用類別名稱存取
Console.WriteLine(cat1.CatCount());//非靜態成員,因此用物件名稱存取
靜態類別(static class)是指 整個類別只能用來存放「共用的功能或資料」,不能被建立成物件(不能new) 的類別,也就是說它本身就代表一組工具或功能集合,裡面的所有成員都必須是static,只能透過「類別名稱」直接使用,例如Mathf或Console,常用來做工具函式、計算方法或全域輔助功能,因為它不需要保存每個物件的狀態,所以更適合放「不依賴個體」的邏輯。
namespace Class_02;
public static class AutoFeeder
{
private static Random Rnd = new Random();
private static string[] _foods = new string[] { "kibble", "canned food", "treats", "raw food" };
public static string MakeFood()
{
int index = Rnd.Next(_foods.Length);//0, 1, 2, 3
return _foods[index];
}
}
using Class_02;
var dog = new Dog("kevin", 5);
var cat = new Cat("Whiskers", 3);
var count = 0;
while (true)
{
var food = AutoFeeder.MakeFood();
switch(food)
{
case"kibble":
Console.WriteLine($"{dog.Name} is eating {food}");
count++;
break;
case "canned food":
Console.WriteLine($"{cat.Name} is eating {food}");
count++;
break;
case "treats":
Console.WriteLine($"{dog.Name} and {cat.Name} are sharing {food}");
count++;
break;
case "raw food":
Console.WriteLine($"{dog.Name} and {cat.Name} are fighting over {food}");
count++;
break;
default: Console.WriteLine("Unknown food");
count++;
break;
}
Console.WriteLine("Press enter to continue...");
Console.ReadLine();
}
這篇筆記把多載、預設參數、運算子多載,還有靜態成員跟靜態類別都紀錄一遍。簡單來說,多載和預設參數能讓函式呼叫更靈活;運算子多載讓自訂物件也能直接用加減乘除,寫起來超直覺;而靜態(static)系列就是拿來搞定那些不用new、大家共用的工具和資料。學會這些技巧並靈活運用,程式碼不只變得更乾淨,架構也會變得更好維護。
下一篇將介紹get/set/init的使用方式。