iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 15
0
Software Development

Swift 菜鳥的30天系列 第 15

Day-15 Swift 語法(11) - Class 與 Struct 的愛恨交織

廢話區

阿... 終於鐵人過半了,說快不快(說慢也不慢就是了)
Keep Going. 鐵人結束依然要繼續加油


類與結構體 (Classes and Structures)

Class 和 Struct 是一種多功能且靈活的構造體,作為程式碼中的構造基礎。你可以使用與常數,變數和函數完全相同的語法來定義屬性和方法,以便為 Class 和 Struct 增加功能。

與其他編程語言不同,Swift 不會要求你為自定義的 Class 和 Struct 單獨的端口實現文件,你可以在文件中定義一個 Class 或是 Struct ,並且這個 Class 或者 Struct 的外部端口被自動提供給其他程式碼使用。

一個 Class 的實例傳統上被稱為 "對象" 。然而,Swift 中的 Class 和 Struct 在功能上比其他語言更加接近。


Class 與 Struct 比較

Swift 中的 Class 與 Struct 有很多相同的地方,例如他們兩者都能:

  • 定義屬性來儲存值
  • 定義方法來提供功能
  • 定義下標用來允許下標語法訪問值
  • 定義初始化器來設置它們的初始狀態
  • 可以被擴展到超出默認實現的功能
  • 符合協議並提供某種的標準功能

Class 有,但 Struct 卻沒有的功能:

  • 繼承可以讓一個 Class 繼承另一個 Class 的特性
  • 類型轉換讓你能夠在運行時檢查和解釋 Class 實例的類型。
  • 反初始化可以使一個 Class 中的實例釋放它分配到的資源
  • 引用計數允許多個引用一個 Class 中的實例

Struct 在程式碼中是透過複製來傳遞,並不會使用引數計數


定義語法

Class 與 Struct 的定義語法的差別,只有差別在關鍵字的使用不同:

class ClassName {

}
struct StructName {

}

這邊我們創建一個叫 Cube 的 Class 和一個 Color 的 Struct

struct Color {
    var red = 0
    var green = 0
    var blue = 0
    var black = false
}
// 儲存三原色,紅、綠、藍的值以及黑色預設值是 false:

class Cube {
    var length = 0
    var witdh = 0
    var height = 0
    var color = Color()
}
// 儲存立方體的長、寬、高的變數,以及一個 color ,我們用 Color 結構體實例來初始化,它使屬性的類型被推斷為 Color

Class 與 Struct 實例

Color 的結構體定義和 Cube 的類定義只描述了「 什麼是 Color 和 Cube 」。因此,你需要創建一個 Struct 或 Class 的實例。兩者創建實例的方式也是大同小異:

let colorSet = Color() //創建 Struct 的實例
let cubeSet = Cube() //創建 Class 的實例

Struct 和 Class 兩者都能使用初始化器語法來生成新的實例。初始化器語法最簡單的是在 Class 或 Struct 的名字後面接一個空的圓括號 ( )。如此一來就成功創建兩者的實例了,其中的屬性都初始化它們的默認值。

訪問屬性

你可以使用點語法來拜訪 Class 或是 Struct 中的屬性,也就是只要在他們兩者的實例名稱後加上一個點(.),就能拜訪他們其中的屬性,例如我們要拜訪 colorSet 中的red:
https://ithelp.ithome.com.tw/upload/images/20180103/201077012M7iQUC5Sw.png

你也可以拜訪子屬性,例如我們 cubeSet 下的 color 屬性下的 black 屬性:
https://ithelp.ithome.com.tw/upload/images/20180103/20107701CCeRpAPn3f.png

你也可以使用點語法在變數中賦予一個新的值:
https://ithelp.ithome.com.tw/upload/images/20180103/20107701kelyQuTEN6.png

結構體類型的成員初始化器

所有的結構體都有一個自動生成的成員初始化器,你可以使用它來初始化新結構體實例的成員屬性。新實例屬性的初始化值可以通過屬性名稱傳遞到成員初始化器中:

let rgb = Color(red: 255, green: 0, blue: 0, black: false)

與 Struct 不同,Class 的實例不會接收默認的成員初始化器


結構體和枚舉是值類型

值類型是一種當它被賦值到常數或者變數,或者被傳遞給函數時會被複製的類型。實際上,Swift 中所有的基本類型Int,Float-Point,Bool,String,Array 和 Dictionary 都是值類型,並且都以結構體的形式在後台實現。

所有結構和枚舉都是 Swift 中的值類型。 這意味著您創建的任何 Struct 和 enum 實例(以及它們作為屬性的任何值類型)在你的程式碼中傳遞時總是被複製。

這此我們宣告一個 redColor 的常數,並將設置為(red: 255, green: 0, blue: 0, black: false)的 Color 實例,之後並賦值給名為 apple 的變數,以當前 redColor 的值初始化:

let redColor = Color(red: 255, green: 0, blue: 0, black: false)
var apple = redColor

因為 Color 是一個結構體,現有實例的拷貝會被製作出來,然後這份新的拷貝就賦值給了 apple 。儘管 redColor 和 apple 有相同的屬性值,但是在後台中他們是兩個完全不同的實例。

如果我們這時將變數 apple 中的 red 進行修改,修改為 240:

apple.red = 240

我們在分別印出 apple.red 以及 redColor 中的值,你會發現 redColor 中的值依然還是 255:
https://ithelp.ithome.com.tw/upload/images/20180103/20107701tR5wyNoXNX.png

簡單來說,apple 只是被賦了一個 redColor 當前值,也就是從 redColor 複製一份到 apple 中,所以他們只是值相同,但卻是兩個不同的實例,所以你修改 apple.red 值,redColor.red 當然也不會被改動,因為他們是不同的實例。

在枚舉(enum)中也是相同的意思,我們先創建一個方向的枚舉:

enum direction {
    case up,down,left,right
}

並且我們也跟上面例子進行相同動作:

var goWhere = direction.down // 宣告一個名為 goWhere 的變數,值為 direction 枚舉中的 down
let goDown = goWhere // 宣告一個名為 goDown 的常數,複製一份 goWhere 的當前值給 goDown 作為值
goWhere = .up // 將 goWhere 中的值改為 direction.up

我們使用一個 if 判斷是來判斷 goDown 是不是不等於 .down :
https://ithelp.ithome.com.tw/upload/images/20180103/20107701VjCyPaxKpe.png

也證明了兩者為不同實例,goDown 只是接收到一份從 goWhere 複製的當前值作為自己的值,所以修改 goWhere中的值不會影響到 goDown,goDown 值依然還是 down。


類是引用類型

不同於值類型,引用類型在賦值給常數或變數或是傳遞給函數時不會被複製。相較於複製,這裡是對於同一個現有的實例進行引用。

首先我們先定義一個 square 常數,設置他引用一個 Cube 類的新實例。

let square = Cube()
square.color = redColor
square.height = 100
square.length = 100
square.witdh = 100

如果我們這時如同上面的 struct 和 enum 中,一樣將 square 宣告給另一個常數(ectangle),並將 ectangle 中的 heighnt 改為值 200:

let ectangle = square
ectangle.height = 200

這時我們來查看我們 square.height 中的值是否改變:
https://ithelp.ithome.com.tw/upload/images/20180103/20107701ZTdxlDhFkd.png

當時的我看到這邊也是滿頭的黑人問號,想說不是一樣存在於不同實例上,結果值也變了,何況他還是存在於一個常數中。雖然 square 和 ectangle 被宣告為一個常數,但你仍然可以更改 square.height和 ectangle.height,因為他們兩個常數本身的值並沒有改變。square 和 ectangle 本身並沒有儲存 Cube 實例,他們兩者都是在後台引用 Cube 實例。它是我們 Cube 中的屬性 height 在改變而不是引用 Cube 的常數的值在改變。

因為 Class 是引用類型,所以 square 和 ectangle 其實都是引用了相同的 Cube 實例。所以它們只是相同實例的兩個不同命名罷了。


特徵運算符

由於類是引用類型,因此在後台可能有很多個常量和變量都是引用同一個類的實例,相同這詞在 struct 和 enum 就並非是真的相同,因為他們賦值給常數或變數時,或者傳遞給一個函數時他們總是被複製過去的。

有時候找出兩個常數或者變數是否引用來自於同一個 Class 實例是有用的,所以,Swift提供了兩個特徵運算符:

  • 相同於( === )
  • 不相同於( !== )

利用這兩個運算符來檢查兩者是否引用相同的實例:
https://ithelp.ithome.com.tw/upload/images/20180103/20107701TeAloaZqKU.png

這邊的相同於( === )意味著兩個類類型常數或者變量引用來自於相同的實例,而不是兩者相等(==)的意思。


Class 和 Struct 之間的選擇

您可以使用 Class 和 Struct 來定義自定義的數據類型,以用作程序代碼的構建塊。總而言之,Class 的實例是透過引用來傳遞的;Struct 則是透過複製來傳遞的,這也表示他的適用於不同類型的任務,所以當你考慮項目所需的數據結構和功能時,請確定每個數據結構是定義為 Class 還是 Struct。

按照通用準則,當符合以下一項或多項情況時可以考慮創建一個 Struct :

  • Struct 的主要目的封裝一些相對簡單的數據值。
  • 當賦值或傳遞 Struct 實例時,合理的期望封裝的值是被複製的,而不是引用的。
  • Struct 儲存的任何屬性都是他們自己的值類型,它們也將被複製而不是引用。
  • 該 Struct 不需要繼承其他現有類型的屬性或行為。

適合使用 Struct 創建的例子如下:

  • 幾何形狀的大小,可能封裝了一個 width 和 height 屬性,兩者類型都為 Double
  • 一定範圍的路徑,可能封裝了一個 start 和 length 屬性,兩者類型都為 Int
  • 三維坐標系的一個點,可能封裝了 x , y 和 z 三個屬性,類型都為 Double

在其他情況下,你應該定義一個 Class 並創建該 Class 的實例,透過引用來管理和傳遞。實際上,這也代表著大多數自定義的數據結構應該為 Class 而不是 Struct。


上一篇
Day-14 Swift 語法(10) - 多種用途的 Enumerations
下一篇
Day-16 Swift 語法(12) - Methods 方法
系列文
Swift 菜鳥的30天30

尚未有邦友留言

立即登入留言