Swift5.x入门09--结构体与类

结构体

  • 在Swift标准库中,绝大多数公开的类型都是结构体,而枚举和类只占很少的一部分;
  • Bool,Int,Double,String,Array,Dictionary等常见类型都是结构体;
struct Date {
    var year: Int
    var month: Int
    var day: Int
}

import Foundation

var date = Date(year: 2021, month: 7, day: 29)
  • 定义了一个结构体Date,其内部有三个成员;
  • 所有的结构体都有一个编译器自动生成的初始化器(初始化方法,构造方法),例如Date(year: 2021, month: 7, day: 29)
  • 结构内部的所有成员,专业术语叫做存储属性

结构体的初始化器

  • 编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始值;
struct Point {
    var x: Int = 0
    var y: Int = 0
}

import Foundation

var point = Point(x: 10, y: 10)
point = Point(x: 20)
point = Point(y: 30)
point = Point()
  • 由于成员x,y有默认值,所有编译器会自动生成4个初始化器;
自定义初始化器
  • 在定义结构体时,一旦自定义了初始化器,编译器就不会自动生成其他的初始化器;
struct Point {
    var x: Int = 0
    var y: Int = 0
    init(x: Int,y: Int) {
        self.x = x
        self.y = y
    }
}
  • init为自定义初始化器;
结构体的内存结构
struct Point {
    var x: Int = 0
    var y: Int = 0
    var origin: Bool = false
    init(x: Int,y: Int,origin: Bool) {
        self.x = x
        self.y = y
        self.origin = origin
    }
}

import Foundation

var point = Point(x: 10, y: 20,origin: true)

print(MemoryLayout.size) //17
print(MemoryLayout.stride) //24
print(MemoryLayout.alignment) //8

  • 结构体变量point的内存布局如下所示:
Snip20210730_58.png

  • 类的定义与结构体的类似,但编译器并没有为类自动生成可以传入成员值的初始化器;
class PointXY{
    var x: Int = 0
    var y: Int = 0
}

import Foundation
//调用
var pointXY = PointXY()
  • 因为成员x与y有默认值,所以编译器自动生成了无参的初始化器;
  • 若没有默认值,那么编译器不会生成任何初始化器;

结构体与类的本质区别

  • 结构体是值类型,枚举也是值类型;
  • 类是引用类型,指针类型;
struct Point {
    var x: Int = 3
    var y: Int = 4
}

class Size {
    var width: Int = 5
    var height: Int = 6
}

import Foundation

var point = Point()
var size = Size()
Snip20210731_59.png
  • 结构体变量point是数值类型,在栈区分配内存空间,其栈地址为0x1000083B0,然后后面的16个字节存储的是其成员x,y的值;
  • Size类的实例对象size是指针类型,size是指针变量,,在栈区分配内存空间,占8个字节,其栈地址为0x1000083C0,其内存地址中存储的是实例对象的内存地址即0x100657540,x/4gx 0x100657540可以获取实例对象的内容,打印出4个8字节的内容,可以看到后面的16个字节存储的是其成员width,height的值;
值类型
  • 值类型赋值给let,var或者函数传参,是直接将所有的内容拷贝一份,属于深拷贝;
struct Point {
    var x: Int
    var y: Int
}

func testZLX() -> Void {
    
    var point = Point(x: 10, y: 20)
    var point1 = point

    point1.x = 11
    point1.y = 22

    print(point.x)
    print(point.y)
}

import Foundation

testZLX()
  • 当断点停在var point = Point(x: 10, y: 20)所在行,查看汇编代码如下:
    0x100003040 <+0>:   pushq  %rbp
    0x100003041 <+1>:   movq   %rsp, %rbp
    0x100003044 <+4>:   subq   $0x90, %rsp
    0x10000304b <+11>:  xorps  %xmm0, %xmm0
    0x10000304e <+14>:  movaps %xmm0, -0x10(%rbp)
    0x100003052 <+18>:  movaps %xmm0, -0x20(%rbp)
    0x100003056 <+22>:  movl   $0xa, %edi
    0x10000305b <+27>:  movl   $0x14, %esi
->  0x100003060 <+32>:  callq  0x100003030               ; Swift09_枚举的内存布局.Point.init(x: Swift.Int, y: Swift.Int) -> Swift09_枚举的内存布局.Point at main.swift:32
    0x100003065 <+37>:  movq   %rax, -0x10(%rbp)
    0x100003069 <+41>:  movq   %rdx, -0x8(%rbp)
    0x10000306d <+45>:  movq   %rax, -0x20(%rbp)
    0x100003071 <+49>:  movq   %rdx, -0x18(%rbp)
    0x100003075 <+53>:  movq   $0xb, -0x20(%rbp)
    0x10000307d <+61>:  movq   $0x16, -0x18(%rbp)
    0x100003085 <+69>:  movq   0xf7c(%rip), %rcx         ; (void *)0x00007fff80cc5020: type metadata for Any
  • movl $0xa, %edi是将10存入edi寄存器;
  • movl $0x14, %esi是将20存入esi寄存器;
  • 然后进入初始化器,汇编如下:
Swift09_枚举的内存布局`Point.init(x:y:):
->  0x100003030 <+0>:  pushq  %rbp
    0x100003031 <+1>:  movq   %rsp, %rbp
    0x100003034 <+4>:  movq   %rdi, %rax
    0x100003037 <+7>:  movq   %rsi, %rdx
    0x10000303a <+10>: popq   %rbp
    0x10000303b <+11>: retq   
  • movq %rdi, %rax将寄存器rdi中的值存入rax寄存器,即将10存入rax寄存器;
  • movq %rsi, %rdx将寄存器rsi中的值存入rdx寄存器,即将20存入rdx寄存器;
  • 执行完初始化器方法,紧接着执行以下指令:
  • movq %rax, -0x10(%rbp) 将rax寄存器中的值10,写入内存地址(rbp - 0x10)中;
  • movq %rdx, -0x8(%rbp)将rdx寄存器中的值20,写入内存地址(rbp - 0x8)中;
  • (rbp - 0x10)本质就是变量point的内存地址;
  • movq %rax, -0x20(%rbp) 将rax寄存器中的值10,写入内存地址(rbp - 0x20)中;
  • movq %rdx, -0x18(%rbp)将rdx寄存器中的值20,写入内存地址(rbp - 0x18)中;
  • (rbp - 0x18)本质就是变量point1的内存地址;
  • 从这里可以看出point与point1是两份不同的内存地址,从而证明了值类型的传递属于深拷贝;
  • 那么更改point1的成员值,不会影响到point;

值类型的赋值操作

import Foundation

var str1 = "123"
var str2 = str1
str2.append("456")
print("\(str1) -- \(str2)") //123 --123456

var arr1 = [1,2,3]
var arr2 = arr1
arr2.append(4)
print(arr1) //[1,2,3]
print(arr2) //[1,2,3,4]

var dic1 = ["name":"li","age":"10"]
var dic2 = dic1
dic2["height"] = "175"
print(dic1) //["name": "li", "age": "10"]
print(dic2) //["name": "li", "age": "10", "height": "175"]
  • String,Array,Dictionary都是值类型,则值传递时都是深拷贝,所以更改其中一个变量的内容,不会影响到副本中的内容;
  • 在Swift标准库中,为了提升性能,String,Array,Dictionary,Set采用了Copy On Write技术,即只有当在真正改写内容时才会进行深拷贝,否则本体与副本使用同一块内存;
引用类型
  • 引用赋值给let,var或者给函数传参,是将内存地址(引用)拷贝一份,属于浅拷贝;
class Size {
    var width: Int = 3
    var height: Int = 4
}

import Foundation

var size1 = Size()
var size2 = size1
  • 原理如下图所示:
Snip20210731_60.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
    }
}
Snip20210731_61.png
  • let point是常量值类型,即point的值不可以修改;
  • let size是常量指针,即size的值不可以修改,也就是size的指向不可以修改,但其指向的对象可以修改;
对象申请堆内存空间的过程
  • 在Swift中,创建实例对象,需要向堆申请内存空间,其详细过程如下所示:
  • Class.__allocating_init()
  • libSwiftCore.dylib:swift_allocObject
  • libSwiftCore.dylib:swift_slowAlloc
  • libsystem_malloc.dylib:malloc
  • 在Mac,iOS中maclloc分配堆内存空间函数,采用16字节内存对齐的算法;
  • class_getInstanceSize()函数是获取实例对象占用的内存大小,采用的是8字节内存对齐算法;
import Foundation

func TestInstanceSize() -> Void {
    class Point {
        var x: Int = 100 //8
        var y: Int = 200 //8
        var origin: Bool = false //1
        //再加上前面的16个字节 总共实际会占用33个字节
    }
    let point = Point()
    //内存对齐之后的内存大小 8字节对齐
    print(class_getInstanceSize(type(of: point))) //40
    print(class_getInstanceSize(Point.self)) //40
    //maclloc申请堆空间内存 会采用16字节对齐,
    //所以最终会分配48个字节
}

TestInstanceSize()
  • point实例对象实际占用的内存大小为33个字节;
嵌套类型
struct Test {
    enum Rank : Int {
        case two = 2,three,four
        case J,Q,K
    }
}

import Foundation

print(Test.Rank.two.rawValue) //2
  • 结构体中嵌套枚举类型;
枚举,结构体,类都可以定义方法
  • 一般把定义在枚举,结构体,类内部的函数,叫做方法;
class Point {
    var x: Int = 100 //8
    var y: Int = 200 //8
    var origin: Bool = false //1
    func show() -> Void {
        print("Point class -- show")
    }
}

struct Size {
    var width: Int
    var height: Int
    func show() -> Void {
        print("Size struct -- show")
    }
}

enum Grade : Int {
    case perfect = 1,great,good,bad
    func show() -> Void {
        print("Grade enum -- show")
    }
}

import Foundation

var point = Point()
point.show()

var size = Size(width: 100, height: 200)
size.show()

var grade = Grade.great
grade.show()
  • 调试结果如下:
Snip20210731_62.png
  • 方法是存放在代码段,是不会占用实例对象的内存空间;

你可能感兴趣的:(Swift5.x入门09--结构体与类)