概述
结构体与类的重要区别
Swift中,结构体与类的一个重要区别就是结构体是一个值类型而类是一个引用类型。如果定义一个全局变量的结构体,其数据将存储在数据段;而如果定义一个全局变量的类对象,数据段将存放其在堆空间的指针,其实际的数据将存储在堆空间(和类型信息与引用计数一起)。
值类型与引用类型
值类型与引用类型之间的一个重要差异在于,如果将一个值类型赋值给一个变量/常量,是直接将所有内容拷贝一份;而引用类型赋值给变量/常量,实际上是将内存地址拷贝一份,属于浅拷贝 (shallow copy)。
实际上在Swift的标准库中,String, Array, Dictionary, Set(这些都是值类型)都采用了Copy on write的技术,直到修改时才会真的进行深度拷贝,以保证最佳的性能。
结构体与类中的方法
无论是结构体还是类,其中的方法本质都是函数,存放在代码段中。
从代码的层面观察结构体与类的区别
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: 11)
// p.x = 11
// p.y = 44
let s = Size(width: 10, height: 20)
// s = Size(width: 11, height:22)
s.width = 33
s.height = 44
这里所有被注释掉的代码编译都会报错,可以看到结构体的let p更像是一个const常量,而类的let s则更像是一个const指针,不可以让它指向另一个对象,但是可以修改它所指向的对象的成员值。
从汇编的层面观察结构体与类的区别
仍然使用上一段的代码,在声明p的一行打上断点,进入汇编指令的界面观察这里会发生什么。
-> 0x10000299a <+10>: movl $0xa, %eax
0x10000299f <+15>: movl %edi, -0xc(%rbp)
0x1000029a2 <+18>: movq %rax, %rdi
0x1000029a5 <+21>: movl $0x14, %eax
0x1000029aa <+26>: movq %rsi, -0x18(%rbp)
0x1000029ae <+30>: movq %rax, %rsi
0x1000029b1 <+33>: callq 0x100002ac0 ; TestSwift.Point.init(x: Swift.Int, y: Swift.Int) -> TestSwift.Point at main.swift:1
可以看到10和20分别赋值给了rdi和rsi作为参数传递给了Point的init方法。进入Point的init方法中,可以看到:
TestSwift`Point.init(x:y:):
-> 0x100002ac0 <+0>: pushq %rbp
0x100002ac1 <+1>: movq %rsp, %rbp
0x100002ac4 <+4>: movq %rdi, %rax
0x100002ac7 <+7>: movq %rsi, %rdx
0x100002aca <+10>: popq %rbp
0x100002acb <+11>: retq
发现Point的init方法非常简单,仅仅是将rdi和rsi中的参数不经任何处理放在rax和rdx中原样返回。
0x1000029ba <+42>: movq %rax, 0x99ef(%rip) ; TestSwift.p : TestSwift.Point
0x1000029c1 <+49>: movq %rdx, 0x99f0(%rip) ; TestSwift.p : TestSwift.Point + 8
而后便将这两个数据放在数据段中连续的两个位置(以rip作为基准,rip中存放着下一条指令所在的地址)。
然后再在声明s的位置打上断点
0x1000029cd <+61>: movl $0xa, %edi
0x1000029d2 <+66>: movl $0x14, %esi
同样也将10与20分别存放在rdi与rsi中,调用Size.__allocating_init。进入这个函数中:
-> 0x100002e90 <+0>: pushq %rbp
0x100002e91 <+1>: movq %rsp, %rbp
0x100002e94 <+4>: pushq %r13
0x100002e96 <+6>: subq $0x18, %rsp
0x100002e9a <+10>: movl $0x20, %eax
0x100002e9f <+15>: movl $0x7, %edx
0x100002ea4 <+20>: movq %rdi, -0x10(%rbp)
0x100002ea8 <+24>: movq %r13, %rdi
0x100002eab <+27>: movq %rsi, -0x18(%rbp)
0x100002eaf <+31>: movq %rax, %rsi
0x100002eb2 <+34>: callq 0x10000744a ; symbol stub for: swift_allocObject
0x100002eb7 <+39>: movq -0x10(%rbp), %rdi
0x100002ebb <+43>: movq -0x18(%rbp), %rsi
0x100002ebf <+47>: movq %rax, %r13
0x100002ec2 <+50>: callq 0x100002ed0 ; TestSwift.Size.init(width: Swift.Int, height: Swift.Int) -> TestSwift.Size at main.swift:9
0x100002ec7 <+55>: addq $0x18, %rsp
0x100002ecb <+59>: popq %r13
0x100002ecd <+61>: popq %rbp
0x100002ece <+62>: retq
一眼看到两个函数:alloc_Object函数和Size的init方法。在alloc_Object中申请堆空间的内存,并且rsi中存放着其要申请32字节的内存。调用完alloc_Object后rax中将存放分配到的内存地址,跳到这里直接用register read rax就可以查看其内存地址为0x00000001007c27e0,可以看出来是在堆空间的位置。完成赋值后直接看这一块内存信息:
A0 C2 00 00 01 00 00 00 03 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00
前十六个字节存放着类型信息与引用计数,后十六个字节存放着10,20两个值(小端法)。
最后
以上就是饱满花卷为你收集整理的结构体与类,值类型与引用类型的全部内容,希望文章能够帮你解决结构体与类,值类型与引用类型所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复