快轉到主要內容

[從C#到Swift] 09. Structures and Classes

結構與類別的比較與定義 (Comparing Structures and Classes)
#

1. 核心觀念
#

  • 概念解說: 在 Swift 中,結構 (struct) 與類別 (class) 是建構程式碼的通用基石。它們都能定義屬性 (Properties) 來儲存值,也能定義方法 (Methods) 來提供功能。 雖然兩者語法相似,但 Class 多了繼承、執行時期型別檢查、解構子 (Deinitializers) 以及參考計數 (Reference Counting) 等功能。然而,Swift 官方建議在不需要繼承或參考語意時,優先使用 Struct,除非你需要上述 Class 獨有的特性。
  • 關鍵語法struct, class
  • Note

傳統上 Class 的實體被稱為物件 (Object)。但在 Swift 中,Struct 和 Class 的功能非常接近,因此官方文件多使用更通用的術語「實體 (Instance)」。

2. 範例
#

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

邏輯解說: 這段程式碼定義了一個名為 Resolution 的結構,用來描述像素解析度;以及一個名為 VideoMode 的類別,用來描述影片模式。Swift 會根據初始值(例如 0)自動推斷屬性的型別為 Intname 屬性被定義為 String? (Optional String),因此會自動獲得預設值 nil

3. C#
#

概念對應: 這在 C# 中對應到 structclass 的定義。

C# 範例

struct Resolution {
    public int Width;
    public int Height;    
}

class VideoMode {
    public Resolution Resolution = new Resolution();
    public bool Interlaced = false;
    public double FrameRate = 0.0;
    public string? Name; 
}

關鍵差異分析

  • 語法面
    • Swift 變數宣告使用 var (可變) 或 let (常數),且不需要 public 關鍵字即可在模組內存取(預設為 internal)。
    • Swift 的屬性宣告直接給定初始值即可完成型別推斷,語法比 C# 簡潔。
  • 行為面
    • Swift 的 Struct 可以實作 Protocol (類似 C# Interface)、擁有方法、Extensions 等,幾乎擁有 Class 的大部分功能,除了繼承。
    • 在 C# 中,Struct 通常用於輕量級資料封裝;在 Swift 中,Struct 是一等公民,甚至標準庫中的 String, Array, Dictionary 都是 Struct。

實體化與屬性存取 (Structure and Class Instances & Accessing Properties)
#

1. 核心觀念
#

  • 概念解說: 定義好型別後,需要建立「實體」來使用。Swift 使用建構子語法來建立實體,最簡單的形式就是在型別名稱後加上空括號 ()。存取屬性則使用點語法 (.)。

2. 範例
#

let someResolution = Resolution()
let someVideoMode = VideoMode()

print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0".

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280".

邏輯解說: 這裡建立了 ResolutionVideoMode 的實體,並且存取其中的屬性。值得注意的是,即使是深層的屬性(如 someVideoMode.resolution.width),也可以直接透過點語法進行讀取與賦值。

3. C#
#

概念對應: 這與 C# 的物件建立與屬性存取一致。

C# 範例

var someResolution = new Resolution();
var someVideoMode = new VideoMode();

Console.WriteLine($"The width of someResolution is {someResolution.Width}");

someVideoMode.Resolution.Width = 1280;

結構的成員逐一建構子 (Memberwise Initializers for Structure Types)
#

1. 核心觀念
#

  • 概念解說: Swift 的 Struct 有一個非常方便的特性:如果沒有自定義建構子,編譯器會自動生成一個「成員逐一建構子 (Memberwise Initializer)」,讓你可以在初始化時直接指定各個屬性的值。Class 則沒有這項功能。

2. 範例
#

let vga = Resolution(width: 640, height: 480)

邏輯解說Resolution 是一個 Struct,所以自動獲得了一個包含 widthheight 參數的建構子。

3. C#
#

概念對應: C# classstruct 通常需要手動撰寫建構子或使用 Object Initializer 語法。

C# 範例

// C# Object Initializer syntax
var vga = new Resolution { Width = 640, Height = 480 };

// 或者若有定義建構子
// var vga = new Resolution(640, 480);

關鍵差異分析

  • 語法面:Swift 的成員初始化器是函數參數形式 (width: 640),而 C# 的物件初始化器是大括號屬性賦值 { Width = 640 }
  • 行為面:Swift 的自動生成建構子是強制的參數傳遞(除非屬性有預設值),而 C# 的 Object Initializer ({ }) 只是在物件建立後設定屬性,語意上略有不同。Swift 的 Class 不會自動獲得這種建構子,必須手動定義 init

結構與列舉是數值型別 (Structures and Enumerations Are Value Types)
#

1. 核心觀念
#

  • 概念解說Value Type 的特性是當它被指派給變數、常數,或傳遞給函式時,內容會被複製 (Copied)。在 Swift 中,所有的 structenum 都是 Value Type。這包含了 Swift 標準庫中的 Int, Double, Bool, String, Array, Dictionary
  • Note

Swift 標準庫中的集合(如 Array, Dictionary, String)使用了 Copy-on-Write 優化技術。只有在真正修改資料時,才會執行實際的記憶體複製,否則僅是共享記憶體,確保效能。

2. 範例
#

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide".

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide".

邏輯解說: 當 hd 賦值給 cinema 時,發生了複製。因此,修改 cinemawidth 完全不會影響到原始的 hd。它們是記憶體中兩個獨立的實體。

3. C#
#

概念對應: C# 的 struct 也是 Value Type,行為與此相同。

關鍵差異分析

  • 行為面
    • String 的差異:C# 的 stringReference Type ,但在 Swift 中 StringStruct (Value Type)
    • Collection 的差異:C# 的 List<T>Dictionary<TKey, TValue>Class (Reference Type)。如果你將一個 List 賦值給另一個變數,修改其中一個會影響另一個。但在 Swift 中,ArrayDictionaryStruct,賦值會產生複製,修改互不影響。

類別是參考型別 (Classes Are Reference Types)
#

1. 核心觀念
#

  • 概念解說Reference Type 在賦值或傳遞時不會複製,而是傳遞記憶體位址的參考 (Reference)。多個變數可能同時指向同一個實體。Swift 的 class 是 Reference Type。

2. 範例
#

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0".

邏輯解說tenEightyalsoTenEighty 指向同一個 VideoMode 實體。因此,透過 alsoTenEighty 修改 frameRatetenEighty 看到的數值也會跟著改變。

3. C#
#

概念對應: 這完全對應 C# 的 class 行為。


識別運算子 (Identity Operators)
#

1. 核心觀念
#

  • 概念解說: 因為 Class 是參考型別,有時候我們需要知道兩個變數是否「指向同一個記憶體實體」。Swift 提供了 === (Identical to) 和 !== (Not identical to) 來進行判斷。
  • 關鍵語法===, !==

2. 範例
#

if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}

邏輯解說: 這裡使用 === 來檢查兩個變數是否參考到同一個 VideoMode 實體。注意這與 == (Equal to) 不同,== 通常是用來比較「值」是否相等(需要實作 Equatable protocol)。

3. C#
#

概念對應: 這相當於 C# 中的 Object.ReferenceEquals(a, b) 或是在未覆寫 == 運算子時的預設 Reference 比較。

C# 範例

if (Object.ReferenceEquals(tenEighty, alsoTenEighty)) {
    // ...
}
// 或者對於一般 class
if (tenEighty == alsoTenEighty) { }

關鍵差異分析

  • 語法面:Swift 強制區分「參考相等 (===)」與「數值相等 (==)」。C# 的 == 對於 Reference Type 預設是比較參考,但對於 string 或重載過運算子的型別則是比較值,容易混淆。Swift 的設計更加明確。