1. 程式人生 > >Swift — struct與class的差異

Swift — struct與class的差異

Swift中類和結構體非常類似,都具有定義和使用屬性、方法、下標和構造器等面向物件特性,但結構體不具有繼承性,也不具備執行時強制型別轉換、使用析構器和使用引用計數等能力。

Swift中struct是值型別,而class是引用型別。值型別的變數直接包含他們的資料,引用型別的變數儲存對他們的資料引用,因此後者稱為物件,因此對一個變數操作可能影響另一個變數所有引用的物件。對於值型別都有自己的資料副本,因此對一個變數操作不可能影響另一個變數。

1,類和結構體的定義和例項化對比

定義

// 定義類
class 類名 {
  定義類的成員
}
// 建立一個 class 名稱為 ClassCoder
class ClassCoder {
  var name = "IAMCJ"
  var age = 0
}

// 定義結構體
struct 結構體名 {
  定義結構體的成員
}
// 建立一個 struct 名稱為 StructCoder
struct StructCoder {
  var name = "IAMCJ"
  var age = 0
}
例項化
// 類例項化
let classCoder = ClassCoder()
// class 不能直接用 ClassCoder(name:"CJ",age:18) 必需要自己建立建構函式才可以
classCoder.name = "CJ"
classCoder.age = 18

// 結構體例項化
var structCoder = StructCoder(name:"CJ",age:18)
// 另外一種例項化方法
var structCoder = StructCoder()
structCoder.name = "CJ"
structCoder.age = 18

class在例項化的時候不能自動把property放在constructor的引數裡,必須建立自己的建構函式才可以。

2,mutating關鍵字:

//在不修改原 class 和 struct 的情況下新增一個 method:modifyCoderName(newName:)
// 類
class ClassCoder {
    var name = "IAMCJ"
    var age = 0
}
extension ClassCoder {
    func modifyCoderName(newName:String) {
        self.name = newName
    }
}

// 結構體
struct StructCoder {
    var name = "IAMCJ"
    var age = 0
}
extension StructCoder {
    mutating func modifyCoderName(newName:String) {
        self.name = newName
    }
}

struct在func裡面需要修改property的時候需要加上mutating關鍵字,而class就不用。

3,class可以繼承,而struct不可以。

4,記憶體分配:struct記憶體是分配在棧上,class記憶體分配在堆上。

棧記憶體的儲存結構比較簡單,可以簡單理解為push到棧底pop出來,而要做的就是通過移動棧針來分配和銷燬記憶體。

堆記憶體相比棧有著更為負責的儲存結構,它的分配方式可以理解為在堆中尋找合適大小的空閒記憶體塊來分配記憶體,把記憶體塊重新插入堆類銷燬記憶體,當然這些僅僅是堆記憶體想必棧記憶體效能消耗大的一個方面,更重要的是堆記憶體支援多執行緒操作,響應的就要通過同步等方式保證執行緒的安全。

struct Point {
   var x, y: Double
   func draw() { ... }
}
let point1 = Point(x: 0, y: 0)
var point2 = point1
point2.x = 5

首先在程式碼執行之前編譯器會在棧上分配一塊4個word大小的記憶體,分配的過程就是我在上面提到的移動棧針

用(0,0)來初始化point1 並把point1賦值給point2,你可以理解為將value拷貝到分配好的棧記憶體上,因為struct是值型別所以point1和point2是兩個獨立的instance

修改point2.x不會影響point1

最後use point1 use point2離開作用域 移動棧針,銷燬棧記憶體

接下來我們看一下class的情況

class Point {
   var x, y: Double
   func draw() { ... }
}
let point1 = Point(x: 0, y: 0)
let point2 = point1
point2.x = 5

首先在程式碼執行之前編譯器會在棧上分配兩個word大小的記憶體,不過這一次不是用來儲存Point的property

程式碼執行初始化point1在堆上尋找合適大小的記憶體塊分配

然後將value拷貝到堆記憶體儲存,並在棧上儲存一個指向這塊堆記憶體的指標

可以看到堆上分配的是4個word大小的記憶體塊,剩餘的兩個藍色格子存放的是關於class生命週期相關函式表的指標
let point2 = point1執行的是引用語言,只是在point2的棧記憶體儲存了一個指向point1堆記憶體的指標

修改point2.x也會影響到point1

最後use point1 use point2 離開作用域,堆記憶體銷燬(查詢並把記憶體塊重新插入到棧記憶體中),棧記憶體銷燬(移動棧針)
當然棧記憶體在分配的過程中還要保證執行緒安全(這也是一筆很大的開銷)

5,引用計數:struct均為棧記憶體分配,不涉及任何引用計數;

6,方法派發:

  • 靜態派發:編譯器講函式地址直接編碼在彙編中,呼叫的時候根據地址直接跳轉到實現,編譯器可以進行內聯等優化,Struct都是靜態派發。
  • 動態派發:執行時查詢函式表,找到後再跳轉到實現,動態派發僅僅多一個查表環節並不是他慢的原因,真正的原因是它阻止了編譯器可以進行的內聯等優化手段

參考文章: