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<Point>.size) //17
print(MemoryLayout<Point>.stride) //24
print(MemoryLayout<Point>.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
  • 方法是存放在代码段,是不会占用实例对象的内存空间;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容