淺析Swift中struct與class的區別(彙編角度底層分析)
概述
相對Objective-C,Swift使用結構體Struct的比例大大增加了,其中Int,Bool,以及String,Array等底層全部使用Struct來定義!在Swift中結構體不僅可以定義成員變數(屬性),還可以定義成員方法,和類比較相似,都是具有定義和使用屬性,方法以及初始化器等面向物件特性,但是結構體是不具有繼承性,不具備執行時強制型別轉換的以及引用計數等能力的!
下面來從彙編角度分析struct與class的區別!
基本知識
1、結構體
自動初始化器
在63行的呼叫中可以傳入所有的成員值,用以初始化所有成員(儲存屬性,Stored Property)
在Struct Date定義中,並沒有出現init初始化方法,但是發現Date會自動出現填入成員值的初始化方法
結論所有結構體都會有一個編譯器自動生成的初始化器(initializer,構造器,構造方法),編譯器會根據情況,可能會為結構體生成多個初始化器,但是宗旨是:保證所有成員都有初始值
舉例1
下面四個初始化器,第一個初始化器之後保證了x,y都有值,滿足了上面說的保證所有成員都有初始值
p1,p2,p3都不能操作成功,因為不能保證全部成員值都有值
通過上面的舉例,編譯器主動生成了一個初始化器,用於接受成員值x,y的初始化器,其他不會主動生成
舉例2
下面四個初始化器,第一個第二個p0,p1保證了x,因為x定義的時候賦值為0了,保證了成員值都有初始化值
p2,因為不能保證全部成員值都有值
通過舉例2,編譯器主動生成了兩個初始化器,用於接受x,y以及單獨接受y即可,其他的初始化器不會生成
舉例3
下面成員值在定義的時候就已經給定了初始化值,已經保證了所有成員值肯定會有初始化值
所以四個初始化器都可以,編譯器會自動生成四個初始化器
舉例4
下面程式碼能編譯通過嘛?
struct Point { var x: Int? var y: Int? } var p0 = Point(x: 0,y: 10) var p1 = Point(y: 0) var p2 = Point(x: 0) var p3 = Point()
定義var x: Int? 相當於將nil 賦值給x,所以上面四個都是可以編譯通過的 可選項都有個預設值nil
自定義初始化器
一旦在定義結構體的時候自定義好了初始化器,編譯器就不會再幫它自動生成其他初始化器
舉例1
struct Point { var x: Int = 0 var y: Int = 0 init(x: Int,y: Int) { self.x = x self.y = y } } var p0 = Point(x: 0,y: 10) var p1 = Point(y: 0) var p2 = Point(x: 0) var p3 = Point()
在定義成員值時並賦值了初始值,也自定義初始化器,所以編譯器就不會自動生成其他初始化器
看下這兩種初始化有何區別?
func testStruct() { struct Point { var x: Int = 0 var y: Int = 0 } var _ = Point() } testStruct()
func testStruct() { struct Point { var x: Int var y: Int init() { x = 0 y = 0 } } var _ = Point() } testStruct()
通過彙編來檢視是否有區別,兩個一模一樣,都是下面
TestSwift`init() in Point #1 in testStruct(): -> 0x100001940 <+0>: pushq %rbp 0x100001941 <+1>: movq %rsp,%rbp 0x100001944 <+4>: xorps %xmm0,%xmm0 0x100001947 <+7>: movaps %xmm0,-0x10(%rbp) 0x10000194b <+11>: movq $0x0,-0x10(%rbp) 0x100001953 <+19>: movq $0x0,-0x8(%rbp) 0x10000195b <+27>: xorl %eax,%eax 0x10000195d <+29>: movl %eax,%ecx 0x10000195f <+31>: movq %rcx,%rax 0x100001962 <+34>: movq %rcx,%rdx 0x100001965 <+37>: popq %rbp 0x100001966 <+38>: retq
記憶體結構
看一下下面一個結構體的記憶體結構
根據記憶體地址檢視
從上面的儲存可看到,三個屬性的儲存地址是相鄰的!!!
也可以通過封裝的Mems記憶體類來直接查詢
2、類
類的定義和結構體類似,但是編譯器並沒有為類自動生成可以傳入成員值的初始化器
上面class定義,知編譯器不會自動生成可以傳入成員值的初始化器,因為定義的x,y都具有初始化值,xcode還會自動的生成無參的初始化值,如果x,y沒有初始化值,連無參的初始化器都不會呼叫成功!
上面如果改成struct修飾,就不會有任何的錯誤
結論:
如果類的所有成員都在定義的時候制定了初始值,編譯器會為類生成無參的初始化器
區別
1. 結構體是值型別(列舉也是值型別),類是引用型別(指標型別)
class Size { var width = 1 var height = 2 } struct Point { var x = 3 var y = 4 } func test() { var size = Size() var point = Point() }
對於上面的程式碼,point為值型別,如果值型別在函式裡面定義,就放在棧空間,point裡面有x,y共有16個位元組,假設起始地址為0x10000,而Size物件是引用型別,size指標變數存放在棧空間中,存放的是地址(指標型別佔用8個位元組),地址指向的為堆空間,堆空間的大小為32個位元組,記憶體結構大致如
而size物件記憶體則放在堆空間,結構結構如下
進行驗證(如果彙編裡面沒有出現alloc,malloc等詞,基本就不是堆空間)
發現size指標變數和point變數地址挨著很近!!!
進一步,我們想觀看size指標變數指向的堆空間的內容和指標地址,通過Mems工具類檢視
對於上面的補充
對於類建立的物件都是是堆空間,只是類物件的指標變數可能會在不同的地方,如上面size是在函式裡面,size指標變數放在棧裡面,但是Size物件就是堆空間,不存在其他的,如果建立size物件在函式外建立,則size指標變數就放在了全域性區裡面
拓展
值型別: 值型別賦值給var,let或者給函式傳參,是直接將所有內容拷貝一份,類似於對檔案進行copy,paste操作,產生了新的檔案副本,屬於深拷貝(deep copy)
彙編指令小技巧
引用型別: 引用賦值給var,是將記憶體地址拷貝一份,類似於製作一個檔案的替身(快捷方式、連結)指向的是同一個檔案,屬於淺拷貝(shallow copy)
上面可看出,s1,s2 都指向同一記憶體,當更改s2的值時,s1也會更改掉,此為淺拷貝的應用!!!
總結
到此這篇關於Swift--struct與class的區別(彙編角度底層分析)的文章就介紹到這了,更多相關swift struct與class內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!