美文网首页
swift基础——类

swift基础——类

作者: 夜凉听风雨 | 来源:发表于2022-04-12 23:07 被阅读0次

    类的定义和结构体类似,但编译器井没有为类自动生成可以传入成员值的初始化器

    类.png

    编译器只为类自动生成了一个无参的初始化器。

    结构体.png

    编译器为结构体自动生成了无参和有参的初始化器。

    图片.png

    如果没有初始值,类直接报错,编译器连无参的初始化器都不会为它自动生成。

    初始化器

    如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器
    成员的初始化是在这个初始化器中完成的

    图片.png

    上面2段代码是相等的。

    结构体与类的本质区别

    结论:

    结构体是值类型(枚举也是值类型) , 类是引用类型(指针类型)

    图片.png

    上图所示是一个结构体和类的对象内存分布。

    变量point存储在栈空间,因为它是值类型,所以它的成员x,y都在栈空间中。
    变量size也存储在栈空间,因为类是引用类型,所以它的内存中存储的是size对象的内存地址。而对象存储在堆空间,该对象占用32个字节。前8个字节存储的是指向类型信息的内存地址。第二个8字节存储的是指向引用计数的内存地址。最后16个字节存储的是成员width和height的值。

    证明

    使用工具Mems.swift来打印变量size和point的地址,以及它们的内容。 Mems.swift下载地址
    代码如下:

        print("size变量的地址===", Mems.ptr(ofVal: &size))
        print("size变量的内容===", Mems.memStr(ofVal: &size))
        print("size所指向内存的地址===", Mems.ptr(ofRef: size))
        print("size所指向内存的内容===", Mems.memStr(ofRef: size))
        print("point变量的地址===", Mems.ptr(ofVal: &point))
        print("point变量的内容===", Mems.memStr(ofVal: &point))
        print("size变量占用空间===", MemoryLayout<Size>.stride)
        print("point变量占用空间===", MemoryLayout<Point>.stride)
    

    打印结果:

    size变量的地址=== 0x00007ffeefbff460
    size变量的内容=== 0x00000001006667a0
    size所指向内存的地址=== 0x00000001006667a0
    size所指向内存的内容=== 0x000000010000c298 0x0000000200000003 0x0000000000000001 0x0000000000000002
    point变量的地址=== 0x00007ffeefbff450
    point变量的内容=== 0x0000000000000003 0x0000000000000004
    size变量占用空间=== 8
    point变量占用空间=== 16
    

    通过打印结果可以发现,变量size和point的地址是紧挨在一起的,只相差了16个字节,这16个字节其实就是point所占用的空间。
    point的内容分别是3和4,这也和我们的初始化的值吻合。
    size的内容是一个地址,这个地址其实就是对象所在的堆区地址空间。
    size指向的对象内容前2个8字节是两个内存地址,后第2个8字节分别存的是值3和4,和我们的初始化值吻合。
    size变量占用空间是8个字节,因为它是一个指针类型,只存一个地址。
    point变量占用空间是16个字节,因为它是一个值类型,会根据成员来开辟不同的空间。

    值类型

    • 值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份,类似于对文件进行copy、paste操作 ,产生了全新的文件副本。属于深拷贝( deep copy )

    既然结构体是值类型,那么下面的代码p1和p2应该是两个完全不同的空间,并且更改p2的值,p1并不会被修改。

      struct Point {
            var x: Int
            var y: Int
        }
        
        var p1 = Point(x: 10, y: 20)
        var p2 = p1
        p2.x = 11
        p2.y = 22
    

    查看汇编代码证明:

    下面的汇编代码大致流程是调用Point.init方法,将10赋值给p1.x,20赋值给p1.y。然后将10赋值给p2.x,20赋值给p2.y。最后将12赋值给p2.x,22赋值给p2.y。

        0x1000035a1 <+1>:  movq   %rsp, %rbp
        0x1000035a4 <+4>:  subq   $0x20, %rsp
        0x1000035a8 <+8>:  xorps  %xmm0, %xmm0
        0x1000035ab <+11>: movaps %xmm0, -0x10(%rbp)
        0x1000035af <+15>: movaps %xmm0, -0x20(%rbp)
        0x1000035b3 <+19>: movl   $0xa, %edi // 把10给edi
        0x1000035b8 <+24>: movl   $0x14, %esi // 把20给esi
        0x1000035bd <+29>: callq  0x1000035f0 ; Point.init(x: Swift.Int, y: Swift.Int) -> Point in swiftTest.test() -> () at main.swift:34 
    // 跳到下面的init方法里去
    ->  0x1000035c2 <+34>: movq   %rax, -0x10(%rbp)  // 把rax里的内容(10)给-0x10(%rbp) 也就是p1.x
        0x1000035c6 <+38>: movq   %rdx, -0x8(%rbp) // 把rdx里的内容(20)给-0x8(%rbp) 也就是p1.y
        0x1000035ca <+42>: movq   %rax, -0x20(%rbp) // 把rax里的内容(10)给-0x20(%rbp) 也就是p2.x
        0x1000035ce <+46>: movq   %rdx, -0x18(%rbp) // 把rdx里的内容(20)给-0x18(%rbp)也就是p2.y
        0x1000035d2 <+50>: movq   $0xb, -0x20(%rbp)  // 把12给-0x20(%rbp) 也就是p2.x
        0x1000035da <+58>: movq   $0x16, -0x18(%rbp) // 把22给-0x18(%rbp) 也就是p2.y
        0x1000035e2 <+66>: addq   $0x20, %rsp
        0x1000035e6 <+70>: popq   %rbp
    
    
    // ------ init方法汇编代码-------
    swiftTest`init(x:y:) in Point #1 in test():
        0x1000035f0 <+0>:  pushq  %rbp
    ->  0x1000035f1 <+1>:  movq   %rsp, %rbp
        0x1000035f4 <+4>:  movq   %rdi, %rax // 把rdi(上面的edi)里的内容(10)给rax
        0x1000035f7 <+7>:  movq   %rsi, %rdx // 把rsi(上面的esi)里的内容(20)给rdx
        0x1000035fa <+10>: popq   %rbp
        0x1000035fb <+11>: retq   
    

    引用类型

    引用赋值给var、let或者给函数传参,是将内存地址拷贝一份。类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件。属于浅拷贝( shallow copy ),修改替身的值也就相当于在修改原型的值。

    图片.png

    如上图所示,类是引用类型,所以s2=s1时就是浅拷贝,s1和s2内存空间里存储的都是Size(width:10, height:20)这个实例的地址。

    汇编证明类是引用类型

    运行如下代码:

    class Size {
        var width: Int
        var height: Int
        init(width: Int, height: Int) {
            self.width = width
            self.height = height
        }
    }
    
    func test() {
        var s1 = Size(width: 10, height: 20)
        var s2 = s1
        s2.width = 11
        s2.height = 22
    }
    
    test()
    

    断点后查看汇编代码

    调用allocating_init方法后在堆空间开辟地址存储实例,将返回的堆空间地址复制给了s1和s2两个变量的空间,所以s1和s2存储的都是同一块地址。

    汇编1.png

    查看allocating_init后rax寄存器存储地址对应的内存内容,可以看到就是实例的内存分布,有10和20两个数。

    内存图.png

    最后看汇编中修改s2.width和s2.height,其实修改的就是实例在堆空间中那个地址第16个字节和第32个字节。

    汇编2.png

    相关文章

      网友评论

          本文标题:swift基础——类

          本文链接:https://www.haomeiwen.com/subject/onpwsrtx.html