我是靠谱客的博主 饱满花卷,最近开发中收集的这篇文章主要介绍结构体与类,值类型与引用类型,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

结构体与类的重要区别

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两个值(小端法)。

最后

以上就是饱满花卷为你收集整理的结构体与类,值类型与引用类型的全部内容,希望文章能够帮你解决结构体与类,值类型与引用类型所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(73)

评论列表共有 0 条评论

立即
投稿
返回
顶部