[Swift] Class v.s. Struct

由於這兩個東西基本上作用類似,但又有一些地方不同,這裡來比較一下這兩個的差別

首先,Class跟 Struct都藍圖的概念,定義之後可以生成特定結構的物件

// define
class MyClass { ... }
struct MyStruct { ... }
// initialize
MyClass()
MyStruct()

由於基本的功能是類似的,所以這裡要反過來看看,這兩個東西有什麼不一樣的

是否有預設的 init()

上述例子中我定義了一個 class及一個 struct,可以看到沒有初始值的 class會爆出錯誤。

可以如此的修正

class MyClass {
var student:String

init(student:String){
self.student=student
}
}

當然,struct其實也是有 init()的,只是偷偷幫我們預設了起來,我們也可以自己修改

struct MyStruct {
var student:String

init(student:String){
self.student=student
}
}

不過一但自行生成一個 init(student:String),原本的 init()就不能使用了,如果想使用就得自己生成

struct MyStruct {
var student:String = "default"

init(){}
init(student: String){
self.student = student
}
}

繼承的有無

class一個很方便的特性是,如果想要一個既定的 class增加功能,可以直接以繼承的方式進行後進行改寫

class MyClass: SuperClass { ... }

而struct並沒有繼承的功能

Mutable / Immutable

這裡先定義一個function,改變 student的初始值

func changeName(newName: String) {
student = newName
}

從上面的提示可以得知,struct是 immutable的,也就是無法被改變的,這裡需要加上 mutating

mutating func changeName(newName: String) {
student = newName
}

當然並不會因為我們加個關鍵字,就改變了 struct的性質,當我們調用changeName時,實際上是消滅舊的 struct,並建立了一個新的 struct出來,這個新的 struct的student值便是使用剛剛傳入的 newName

而 immutable的特性也會延伸到宣告的地方

當以 let進行宣告,而想要改變 student值時,class因為是 mutable的所以沒有問題,而 struct部分,我們現在要做的動作便是要把現有的 struct拆掉重建成新的,違反我們的常數宣告(let)而爆出錯誤。

這裡比較容易搞混的就是,struct中改變內部值的行為,並不如一般想像的是只是簡單地改變屬性,而是整個打掉重建,而這個行為如果不清楚便有可能會錯誤地使用 let來宣告,跳紅字時還不清楚為何要改成 var

傳值 / 傳參考

首先來看個例子,這裡我先創造出一個 A,然後以「 = 」方式進行複製,然後改變 A的值來比較兩者的差異

var classA = MyClass(student: "peter")
var classB = classA
classA.changeName(newName: "Bob")
print(classA.student)
print(classB.student)
var structA = MyStruct(student: "john")
var structB = structA
structA.changeName(newName: "Bob")
print(structA.student)
print(structB.student)

這時候的結果是?

也就是 classA與 classB的 student都變成Bob,struct部分則只有 structB改變

差別在於,class創造出來的物件並不是儲存物件本身,而是儲存物件在記憶體的位置,struct則是儲存物件本身

現實一點的例子,class的宣告就像是創造出一張相片後,將照片的網址提供出來給大家取用,所以儲存的資料其實是照片的網址,而 struct則就像是創造出一張相片後,多洗幾張將副本發送出去

所以當對照片進行修改時,class就像是從遠端操控照片的檔案,而因為檔案只有那一個,所以一修改大家手上看到的照片都會改變,而 struct因為複本有很多個,改了其中一個複本並不會影響其他的照片

這裡來試著 print本身試試看

可以看到 class創造的物件就是一段神秘文字,不像 struct的可以看到內容物,並且可以看到 classA跟 classB的內容是相等的

所以整段 code可以這樣解釋

// 創造一個初始值為 student: "peter"的物件後,將物件位置儲存至 classA
var classA = MyClass(student: "peter")
// 將物件的位置複製至classB
var classB = classA
// 將物件的 student值改為 "Bob"
classA.changeName(newName: "Bob")
// 創造一個初始值為 student: "john"的物件後,將物件儲存至 structA
var structA = MyStruct(student: "john")
// 複製一個跟 structA一饃一樣的物件後,儲存至 structB
var structB = structA
// 將儲存在structA的物件的 student值改為 "Bob"
structA.changeName(newName: "Bob")

由於 classA跟 classB指向的物件是同一個,才會有修改 A的值連 B的值也發生了改變的情形發生

這個傳參考的性質常常會帶來一些誤解,例如寫了一段監聽的程式碼

func inform(物件){
if(物件發生改變){
發出通知
}
}
// 這裡使用上例的 classA以及 structA
inform(classA)
inform(structA)
classA.changeName(newName: "Bob")
// 沒反應
structA.changeName(newName: "Bob")
// 通知來了

這裡的 classA指向的物件確實發生了改變,然而因為 classA儲存的位置並未發生變化,所以不會觸發通知,在這種情形下,監聽的對象就不該是物件,而是應該監聽物件中的屬性值是否發生改變,然而當要監聽的屬性值一多起來就不是很方便了

在這種情形下 struct是一個很好的選擇,因為 immutable的特性,每次要改變就要生成新的物件,所以可以直接監聽物件本身是否發生改變,而不需要一一監聽物件的屬性

基於傳參考的特性,除非有需要用到繼承之類的功能,或是真的需要傳參考的特性,不然連蘋果都推薦預設使用 struct,想知道細節的可以點進下方的連結觀看

參考:

--

--