06-結(jié)構(gòu)體和類

結(jié)構(gòu)體

  • 在 Swift 標(biāo)準(zhǔn)庫(kù)中,絕大多數(shù)的公開(kāi)類型都是結(jié)構(gòu)體,而枚舉和類只占很小一部分

  • 比如\color{green}{Bool}、\color{green}{Int}\color{green}{Double}、\color{green}{String}、\color{green}{Array}\color{green}{Dictionary}等常見(jiàn)類型都是結(jié)構(gòu)體

struct Date {
  var year: Int
  var month: Int
  var day: Int
}
var date = Date(year: 2019, month: 6, day: 23)  
  • 所有的結(jié)構(gòu)體都有一個(gè)編譯器自動(dòng)生成的初始化器(\color{green}{initializer},初始化方法、構(gòu)造器、構(gòu)造方法)

  • 在第6行調(diào)用的,可以傳入所有成員值,用以初始化所有成員(存儲(chǔ)屬性,Stored Property)

結(jié)構(gòu)體的初始化器

  • 編譯器會(huì)根據(jù)情況,可能會(huì)為結(jié)構(gòu)體生成多個(gè)初始化器,宗旨是:保證所有成員都有初始值
struct Point {
    var x: Int
    var y: Int
}

var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)       
var p3 = Point(x: 10)
var p4 = Point()

報(bào)錯(cuò)如下圖:需要保證都有值

image.png
struct Point {
    var x: Int = 0
    var y: Int
}

var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()
struct Point {
    var x: Int
    var y: Int = 0
}

var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()
struct Point {
    var x: Int = 0
    var y: Int = 0
}

var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()

// Tips:生成4種初始化器

思考:下面代碼能編譯通過(guò)嗎?

struct Point {
    var x: Int?
    var y: Int?
}

var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()

可選項(xiàng)都有個(gè)默認(rèn)值nil

因此可以編譯通過(guò)


自定義初始化器

  • 一旦在定義結(jié)構(gòu)體時(shí)自定義了初始化器,編譯器就不會(huì)再幫它自動(dòng)生成其他初始化器
struct Point {
    var x: Int = 0
    var y: Int
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()

窺探初始化器的本質(zhì)

  • 以下2段代碼完全等效
struct Point {
    var x: Int = 0
    var y: Int = 0
}

var p = Point()
struct Point {
    var x: Int 
    var y: Int
    init() {
        x = 0
        y = 0
    }
}
var p = Point()
推導(dǎo)過(guò)程:
  • 自己寫(xiě)一個(gè)初始化器,斷點(diǎn)看匯編。匯編斷點(diǎn) Xcode -> Debug Workflow -> Always show Disassembly


    image.png

發(fā)現(xiàn)一模一樣,所以推論正確。

image.png
TestSwift`testStruct():
    0x100002a10 <+0>:  pushq  %rbp
    0x100002a11 <+1>:  movq   %rsp, %rbp
    0x100002a14 <+4>:  subq   $0x10, %rsp
    0x100002a18 <+8>:  xorps  %xmm0, %xmm0
    0x100002a1b <+11>: movaps %xmm0, -0x10(%rbp)
->  0x100002a1f <+15>: callq  0x100002a40               ; Point.init() -> Point in TestSwift.testStruct() -> () at main.swift:6
    0x100002a24 <+20>: movq   %rax, -0x10(%rbp)
    0x100002a28 <+24>: movq   %rdx, -0x8(%rbp)
    0x100002a2c <+28>: addq   $0x10, %rsp
    0x100002a30 <+32>: popq   %rbp
    0x100002a31 <+33>: retq   

結(jié)構(gòu)體內(nèi)存結(jié)構(gòu)

struct Point {
    var x: Int = 0
    var y: Int = 0
    var origin: Bool = false  // 占1個(gè)字節(jié)
}

print(MemoryLayout<Point>.size)      // 17
print(MemoryLayout<Point>.stride)    // 24 
print(MemoryLayout<Point>.alignment) // 8
image.png

  • 類的定義和結(jié)構(gòu)體類似,但編譯器并沒(méi)有為類自動(dòng)生成可以傳入成員值的初始化器
class Point {                                 struct Point {
    var x: Int = 0                                var x: Int = 0
    var y: Int = 0                                var y: Int = 0
}                                             }
let p1 = Point()  生成一個(gè)無(wú)參初始化器           let p1 = Point()
let p2 = Point(x: 10, y: 20)                  let p2 = Point(x: 10, y: 20)
let p3 = Point(x: 10)                         let p3 = Point(x: 10)
let p2 = Point(y: 20)                         let p2 = Point(y: 20)                             
class Point {            Class 'Point' has no initializers                
    var x: Int                            
    var y: Int                              
} 

let p1 = Point()         'Point' cannot be constructed because it has no accessible initializers

類的初始化器

  • 如果類的所有成員都在定義的時(shí)候指定了初始值,編譯器會(huì)為類生成無(wú)參的初始化器

  • 成員的初始化是在這個(gè)初始化器中完成的

下面2段代碼是完全等效的:

class Point {
  var x: Int = 10 
  var y: Int = 20
} 
let p1 = Point()  
class Point {
  var x: Int  
  var y: Int  
  init() { 
      x = 10 
      y = 20
  } 
} 

let p1 = Point() 

結(jié)構(gòu)體與類的本質(zhì)區(qū)別

  • 結(jié)構(gòu)體是值類型(枚舉也是值類型),類是引用類型(指針類型)
class Size {                 struct Point {         func test() { 
  var width = 1                 var x = 3             var size = Size() 
  var height = 2                var y = 4             var point = Point()  
}                            }                       }

下圖都是針對(duì)64bit環(huán)境 (size對(duì)象的內(nèi)存地址 -> 指針變量地址 占8字節(jié), size對(duì)象:如????圖 占32個(gè)字節(jié))

image.png
推導(dǎo)過(guò)程:
0、了解基本調(diào)試命令

匯編模式下

  • fn + control + F7 : 指令單步執(zhí)行,當(dāng)遇到函數(shù)調(diào)用時(shí)會(huì)跳入函數(shù)內(nèi)部。

  • fn + control + F6: 指令單獨(dú)執(zhí)行,當(dāng)遇到函數(shù)調(diào)用時(shí)不會(huì)跳入函數(shù)內(nèi)部。

多****線程****之間的切換:

  • control + shift + F7: 切換到當(dāng)前線程,并執(zhí)行單步指令。

  • control + shift + F6: 切換到當(dāng)前線程,并跳轉(zhuǎn)到函數(shù)調(diào)用的者的下一條指令。

1、怎么證明在堆空間,占空間?

調(diào)用了alloc malloc的在對(duì)空間。

2、先看一下結(jié)構(gòu)體
image.png
image.png
image.png

發(fā)現(xiàn)根本沒(méi)有調(diào)用任何alloc,malloc相關(guān),在??臻g

3、看下類是否在堆空間
image.png
image.png
image.png
image.png
image.png
image.png

此處和馬哥說(shuō)的感覺(jué)函數(shù)差點(diǎn)名字,我猜測(cè)應(yīng)該是系統(tǒng)升級(jí)了?確實(shí)是有malloc分配空間。

func testClassAndStruct () {
    class Size {
        var width = 1
        var height = 2
    }

    struct Point {
        var x = 3
        var y = 4
    }

    print("MemoryLayout<Size>.stride", MemoryLayout<Size>.stride)
    print("MemoryLayout<Point>.stride", MemoryLayout<Point>.stride)

    print("---------")

    var size = Size()
    print("size變量的地址", Mems.ptr(ofVal: &size))
    print("size變量的內(nèi)存", Mems.memStr(ofVal: &size))

    print("size所指向的內(nèi)存地址", Mems.ptr(ofRef: size))
    print("size所指向的內(nèi)存內(nèi)容", Mems.memStr(ofRef: size))

    print("---------")

    var point = Point()
    print("point變量的地址", Mems.ptr(ofVal: &point))
    print("point變量的內(nèi)存", Mems.ptr(ofVal: &point))

}

testClassAndStruct()
image.png

無(wú)論Size對(duì)象存多少東西,指針變量占用都是8字節(jié)。 (64位的情況下)

對(duì)象的對(duì)空間申請(qǐng)過(guò)程

  • 在Swift中,創(chuàng)建類的實(shí)例對(duì)象,要向堆空間申請(qǐng)內(nèi)存,大概流程如下

    • Class.__allocating_init()

    • libswiftCore.dylib:****swift_allocObject

    • libswiftCore.dylib:****swift_slowAlloc

    • libsystem_malloc.dylib:****malloc

  • 通過(guò)class_getInstanceSize可以得知:類的對(duì)象至少需要占用多少內(nèi)存

class Point  {
  var x = 11 
  var test = true  
  var y = 22
} 

var p = Point() 
class_getInstanceSize(type(of: p)) // 40 
class_getInstanceSize(Point.self)  // 40 
  • 在Mac、iOS中的malloc函數(shù)分配的內(nèi)存大小總是16的倍數(shù) (這個(gè)是系統(tǒng)內(nèi)部的優(yōu)化,不用太糾結(jié))
func testClassAndStruct () {
    let ptr = malloc(1)
    print(malloc_size(ptr))

    let ptr1 = malloc(17)
    print(malloc_size(ptr1))
}

testClassAndStruct()
image.png

值類型

  • 值類型賦值給var、let或者給函數(shù)傳參,是直接將所有內(nèi)容拷貝一份

  • 類似于對(duì)文件進(jìn)行copy、paste操作,產(chǎn)生了全新的文件副本。屬于深拷貝(deep copy)

 struct Point {
   var x: Int
   var y: Int
}

func test() {
    var p1 = Point(x: 10, y: 20)
    var p2 = p1
}
image.png
用下面代碼利用匯編來(lái)驗(yàn)證結(jié)論是否正確:
func testValueType() {
    struct Point {
        var x: Int
        var y: Int
    }
    var p1 = Point(x: 10, y: 20)
    var p2 = p1

    p2.x = 11
    p2.y = 22

    print("123")
}

testValueType()
推導(dǎo)過(guò)程:
image.png

1、分析上述代碼


0xa -> 10  0x14-> 20  然后執(zhí)行init方法
movl   $0xa, %edi        
movl   $0x14, %esi
callq  0x100002a00               ; Point.init(x: Swift.Int, y: Swift.Int) -> Point in TestSwift.testValueType() -> () at main.swift:45

2、執(zhí)行 si 進(jìn)入callq 大概意思是把10 20 賦值 rax rdx

TestSwift`init(x:y:) in Point #1 in testValueType():
->  0x100002a00 <+0>:  pushq  %rbp
    0x100002a01 <+1>:  movq   %rsp, %rbp
    0x100002a04 <+4>:  movq   %rsi, %rdx
    0x100002a07 <+7>:  movq   %rdi, %rax
    0x100002a0a <+10>: popq   %rbp
    0x100002a0b <+11>: retq   

3、結(jié)合代碼看匯報(bào)分析

movq   %rax, -0x10(%rbp)        // rbp - 0x10 == 0x1000(假設(shè)地址是這個(gè)低地址) p1的內(nèi)存地址
movq   %rdx, -0x8(%rbp)         // rbp - 0x8 == 0x1008 (連續(xù)的地址值)

movq   %rax, -0x20(%rbp)        // rbp - 0x20 == p2的內(nèi)存地址
movq   %rdx, -0x18(%rbp)        // rbp - 0x18 == 

movq   $0xb, -0x20(%rbp)        // 11 22 給了p2的內(nèi)存
movq   $0x16, -0x18(%rbp)

rax == 10 
rdx == 20

值類型的賦值操作

var s1 = "Jack" 
var s2 = s1 
s2.append("_Rose") 
print(s1) // Jack 
print(s2) // Jack_Rose 
var a1 = [1, 2, 3] 
var a2 = a1 
a2.append(4) 
a1[0] = 2
print(a1) // [2, 2, 3] 
print(a2) // [1, 2, 3, 4] 
var d1 = ["max" : 10, "min" : 2] 
var d2 = d1 
d1["other"] = 7
d2["max"] = 12
print(d1) // ["other": 7, "max": 10, "min": 2] 
print(d2) // ["max": 12, "min": 2]  
  • 在Swift標(biāo)準(zhǔn)庫(kù)中,為了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技術(shù)

  • 比如僅當(dāng)有“寫(xiě)”操作時(shí),才會(huì)真正執(zhí)行拷貝操作

  • 對(duì)于標(biāo)準(zhǔn)庫(kù)值類型的賦值操作,Swift 能確保最佳性能,所有沒(méi)必要為了保證最佳性能來(lái)避免賦值

  • 建議:不需要修改的,盡量定義成 let

值類型的賦值操作

 struct Point {
   var x: Int  
   var y: Int 
 } 

 var p1 = Point(x: 10, y: 20) 
 p1 = Point(x: 11, y: 22) 
image.png

引用類型

  • 引用賦值給\color{green}{var}、\color{green}{let}或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份

  • 類似于制作一個(gè)文件的替身(快捷方式、鏈接),指向的是同一個(gè)文件。屬于淺拷貝(shallow copy)

class Size {
  var width: Int
  var height: Int
  init(width: Int, height: Int) {
    self.width = width  
    self.height = height 
  }  
}  
func test() {
  var s1 = Size(width: 10, height: 20) 
  var s2 = s1  
}  
s2.width = 11
s2.height = 22
 // 請(qǐng)問(wèn)s1.width和s1.height是多少? 
image.png
推導(dǎo)過(guò)程:此過(guò)程比較困難,先看下匯編的寄存器模塊 00-匯編語(yǔ)言
1、了解下常用的指令
image.png
2、推導(dǎo)分析
  • rax一般是函數(shù)返回的對(duì)象地址值

  • 0x10(%rbp) 這里根據(jù)前面的判斷是一個(gè)局部變量,結(jié)合代碼推導(dǎo)出是 s1

  • 0x60(%rbp) 這里根據(jù)前面的判斷是一個(gè)局部變量,結(jié)合代碼推導(dǎo)出是 s2

image.png
3、查看一下內(nèi)存存放是否和猜想一致
  • 斷點(diǎn)處拿到rax 地址

  • Xcode -> Debug -> Debug WorkFLow -> View Memory

  • 前8個(gè)字節(jié)放類信息地址

  • 引用計(jì)數(shù)

image.png
4、查看一下11 22 哪里修改的 尋找立即數(shù)
image.png

引用類型的賦值操作

class Size {
  var width: Int
  var height: Int
  init(width: Int, height: Int) {
    self.width = width  
    self.height = height 
  }  
}  

var s1 = Size(width: 10, height: 20)
s1 = Size(width: 11, height: 22) 指向了新的對(duì)象
image.png

值類型、引用類型的 let

struct Point {
    var x: Int
    var y: Int                                                
}

class Size { 
    var width: Int
    var height: Int
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }                                               
} 
let p = Point(x: 10, y: 20)
p = Point(x: 11, y: 22)\
p.x = 33
p.y = 44

let s = Size(width: 10, height: 20)
s = Size(widht: 11, height: 22)  
s.width = 33
s.height = 44
let str = "Jack"
str.append("_Rose")

let arr = [1, 2, 3]
arr[0] = 11
arr.append(4)

測(cè)試:

func testInstanceSize() {
    class Point {
        // 16
        var x = 11 // 8
        var test = true // 1
        var y = 22 // 8
    } // 33 40 48
    let p = Point() // malloc
    print(class_getInstanceSize(type(of: p)))
    print(class_getInstanceSize(Point.self)) // [Point class]  [p class]
    print(Mems.size(ofRef: p))
}

嵌套類型

struct Poker {
  enum Suit : Character {
    case spades = "?", hearts = "?",  diamonds = "?", clubs = "?"
  } 

  enum Rank : Int {
     case two = 2, three, four, five, six, seven, eight, nine, ten  
     case jack, queen, king, ace  
   }  
 } 
print(Poker.Suit.hearts.rawValue) 

var suit = Poker.Suit.spades 
suit = .diamonds 

var rank = Poker.Rank.five 
rank = .king 

枚舉、結(jié)構(gòu)體、類都可以定義方法

  • 一般把定義在枚舉、結(jié)構(gòu)體、類內(nèi)部的函數(shù),叫做方法
class Size {
  var width = 10 
  var height = 10 

  func show() {
    print("width=\(width), height=\(height)") 
  }  
}

let s = Size()
s.show() // width=10, height=10 
struct Point {
  var x = 10 
  var y = 10 

  func show() {
   print("x=\(x), y=\(y)")  
  } 
} 
let p = Point() 
p.show() // x=10, y=10 
enum PokerFace : Character {
    case spades = "?", hearts = "?", diamonds = "?", clubs = "?"
    func show() {
      print("face is \(rawValue)") 
    }  
}
let pf = PokerFace.hearts 
pf.show() // face is ? 
  • 方法占用對(duì)象的內(nèi)存么?

    • 不占用
  • 方法的本質(zhì)就是函數(shù)

  • 方法、函數(shù)都存放在代碼段

思考:

以下結(jié)構(gòu)體、類對(duì)象的內(nèi)存結(jié)構(gòu)是怎樣的?

struct Point {
  var x: Int  
  var b1: Bool  
  var b2: Bool  
  var y: Int  
}

var p = Point(x: 10, b1: true, b2: true, y: 20) 
class Size {
  var width: Int  
  var b1: Bool
  var b2: Bool
  var height: Int
  init(width: Int, b1: Bool, b2: Bool, height: Int) {
    self.width = width  
    self.b1 = b1  
    self.b2 = b2  
    self.height = height  
  } 
}  
var s =  Size(width: 10, b1: true, b2: true, height: 20) 
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容