美文网首页
06-结构体和类

06-结构体和类

作者: 二斤寂寞 | 来源:发表于2023-04-06 11:26 被阅读0次

结构体

  • 在 Swift 标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分

  • 比如\color{green}{Bool}\color{green}{Int}\color{green}{Double}\color{green}{String}\color{green}{Array}\color{green}{Dictionary}等常见类型都是结构体

struct Date {
  var year: Int
  var month: Int
  var day: Int
}
var date = Date(year: 2019, month: 6, day: 23)  
  • 所有的结构体都有一个编译器自动生成的初始化器(\color{green}{initializer},初始化方法、构造器、构造方法)

  • 在第6行调用的,可以传入所有成员值,用以初始化所有成员(存储属性,Stored Property)

结构体的初始化器

  • 编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始值
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()

报错如下图:需要保证都有值

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种初始化器

思考:下面代码能编译通过吗?

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()

可选项都有个默认值nil

因此可以编译通过


自定义初始化器

  • 一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器
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()

窥探初始化器的本质

  • 以下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()
推导过程:
  • 自己写一个初始化器,断点看汇编。汇编断点 Xcode -> Debug Workflow -> Always show Disassembly


    image.png

发现一模一样,所以推论正确。

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   

结构体内存结构

struct Point {
    var x: Int = 0
    var y: Int = 0
    var origin: Bool = false  // 占1个字节
}

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

  • 类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
class Point {                                 struct Point {
    var x: Int = 0                                var x: Int = 0
    var y: Int = 0                                var y: Int = 0
}                                             }
let p1 = Point()  生成一个无参初始化器           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

类的初始化器

  • 如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器

  • 成员的初始化是在这个初始化器中完成的

下面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() 

结构体与类的本质区别

  • 结构体是值类型(枚举也是值类型),类是引用类型(指针类型)
class Size {                 struct Point {         func test() { 
  var width = 1                 var x = 3             var size = Size() 
  var height = 2                var y = 4             var point = Point()  
}                            }                       }

下图都是针对64bit环境 (size对象的内存地址 -> 指针变量地址 占8字节, size对象:如👉🏻图 占32个字节)

image.png
推导过程:
0、了解基本调试命令

汇编模式下

  • fn + control + F7 : 指令单步执行,当遇到函数调用时会跳入函数内部。

  • fn + control + F6: 指令单独执行,当遇到函数调用时不会跳入函数内部。

多****线程****之间的切换:

  • control + shift + F7: 切换到当前线程,并执行单步指令。

  • control + shift + F6: 切换到当前线程,并跳转到函数调用的者的下一条指令。

1、怎么证明在堆空间,占空间?

调用了alloc malloc的在对空间。

2、先看一下结构体
image.png image.png image.png

发现根本没有调用任何alloc,malloc相关,在栈空间

3、看下类是否在堆空间
image.png image.png image.png image.png image.png image.png

此处和马哥说的感觉函数差点名字,我猜测应该是系统升级了?确实是有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变量的内存", Mems.memStr(ofVal: &size))

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

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

    var point = Point()
    print("point变量的地址", Mems.ptr(ofVal: &point))
    print("point变量的内存", Mems.ptr(ofVal: &point))

}

testClassAndStruct()
image.png

无论Size对象存多少东西,指针变量占用都是8字节。 (64位的情况下)

对象的对空间申请过程

  • 在Swift中,创建类的实例对象,要向堆空间申请内存,大概流程如下

    • Class.__allocating_init()

    • libswiftCore.dylib:****swift_allocObject

    • libswiftCore.dylib:****swift_slowAlloc

    • libsystem_malloc.dylib:****malloc

  • 通过class_getInstanceSize可以得知:类的对象至少需要占用多少内存

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函数分配的内存大小总是16的倍数 (这个是系统内部的优化,不用太纠结)
func testClassAndStruct () {
    let ptr = malloc(1)
    print(malloc_size(ptr))

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

testClassAndStruct()
image.png

值类型

  • 值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份

  • 类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy)

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

func test() {
    var p1 = Point(x: 10, y: 20)
    var p2 = p1
}
image.png
用下面代码利用汇编来验证结论是否正确:
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()
推导过程:
image.png

1、分析上述代码


0xa -> 10  0x14-> 20  然后执行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、执行 si 进入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、结合代码看汇报分析

movq   %rax, -0x10(%rbp)        // rbp - 0x10 == 0x1000(假设地址是这个低地址) p1的内存地址
movq   %rdx, -0x8(%rbp)         // rbp - 0x8 == 0x1008 (连续的地址值)

movq   %rax, -0x20(%rbp)        // rbp - 0x20 == p2的内存地址
movq   %rdx, -0x18(%rbp)        // rbp - 0x18 == 

movq   $0xb, -0x20(%rbp)        // 11 22 给了p2的内存
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标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术

  • 比如仅当有“写”操作时,才会真正执行拷贝操作

  • 对于标准库值类型的赋值操作,Swift 能确保最佳性能,所有没必要为了保证最佳性能来避免赋值

  • 建议:不需要修改的,尽量定义成 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}或者给函数传参,是将内存地址拷贝一份

  • 类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件。属于浅拷贝(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
 // 请问s1.width和s1.height是多少? 
image.png
推导过程:此过程比较困难,先看下汇编的寄存器模块 00-汇编语言
1、了解下常用的指令
image.png
2、推导分析
  • rax一般是函数返回的对象地址值

  • 0x10(%rbp) 这里根据前面的判断是一个局部变量,结合代码推导出是 s1

  • 0x60(%rbp) 这里根据前面的判断是一个局部变量,结合代码推导出是 s2

image.png
3、查看一下内存存放是否和猜想一致
  • 断点处拿到rax 地址

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

  • 前8个字节放类信息地址

  • 引用计数

image.png
4、查看一下11 22 哪里修改的 寻找立即数
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) 指向了新的对象
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)

测试:

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 

枚举、结构体、类都可以定义方法

  • 一般把定义在枚举、结构体、类内部的函数,叫做方法
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 ♥ 
  • 方法占用对象的内存么?

    • 不占用
  • 方法的本质就是函数

  • 方法、函数都存放在代码段

思考:

以下结构体、类对象的内存结构是怎样的?

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) 

相关文章

  • 06-结构体和类

    结构体 结构体的初始化器 思考:下面代码能编译通过么? 自定义初始化器 窥探初始化器的本质 结构体内存结构 类 类...

  • 类和结构体

    类与结构体 本节内容包括: 类和结构体对比 结构体和枚举是值类型 类是引用类型 类和结构体的选择 集合类型的赋值与...

  • Swift Tour Learn (六) -- Swift 语法

    Swift语法基础(五)-- (类和结构体、属性、方法) 本章将会介绍 类和结构体对比结构体和枚举是值类型类是引用...

  • swift基础_结构体和类

    一.结构体和类 结构体张这个样子 类张这个样子 二.结构体和类的区别 针对结构体, 即使你没有定义函数,编译器也会...

  • Day9 类和结构体

    本页包含内容:• 类和结构体对比• 结构体和枚举是值类型• 类是引用类型• 类和结构体的选择• 字符串、数组、和字...

  • Swift学习_基本语法之枚举&类&结构体

    1.枚举 类和结构体 在swift中类和结构体类似,可以把结构体理解成是一种轻量级的类,在swift中结构体不仅可...

  • swift面向对象特性——类和结构体

    类和结构体的定义 类的语法格式为: 结构体的语法格式为: Swift中类和结构体的名称都以大写字母开头。类的方法和...

  • SwiftUI-结构体与类

    一.结构体与类 结构体和类都可以使用属性和方法构建复杂数据类型,但是什么时候使用结构体,什么时候使用类呢?结构体和...

  • Swift5.1类和结构体

    9.类和结构体 结构体和类相比Swift中结构体和类的共同点:- 定义属性⽤于存储值- 定义方法⽤于提供功能- 定...

  • Swift笔记<十五>结构体和类的区别

    结构体和类的区别 结构体有逐一构造器,类没有 结构体是值类型,类是引用类型 结构体不是继承(意味着没有多态)

网友评论

      本文标题:06-结构体和类

      本文链接:https://www.haomeiwen.com/subject/pbtqrdtx.html