Swift--struct与class的区别(汇编角度底层分析)
本人已迁移博客至掘进,以后会在掘进平台更新最新的文章也会有更多的干货,欢迎大家关注!!!https://juejin.im/user/588993965333309
概述
相对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,y都有值,因为x定义的时候赋值为0了,保证了成员值都有初始化值
p2,p3都不能操作成功,因为不能保证全部成员值都有值
通过举例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. 结构体是值类型(枚举也是值类型), 类是引用类型(指针类型)
2. 值类型(结构体,枚举)不支持继承,类支持继承
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,let或者给函数传参, 是将内存地址拷贝一份,类似于制作一个文件的替身(快捷方式、链接)指向的是同一个文件,属于浅拷贝(shallow copy)
上面可看出,s1, s2 都指向同一内存,当更改s2的值时,s1也会更改掉,此为浅拷贝的应用!!!