经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
Swift系列六 - 结构体与类的本质区别
来源:cnblogs  作者:1024星球  时间:2021/5/10 8:58:04  对本文有异议

在Swift标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分。

一、结构体

常见的Bool、Int、Double、String、Array、Dictionary等常见类型都是结构体。

自定义结构体:

  1. struct Date {
  2. var year: Int;
  3. var month: Int;
  4. var day: Int;
  5. }
  6. var date = Date(year: 2019, month: 06, day: 02)

所有结构体都有一个编译器自动生成的初始化器(initializer、初始化方法、构造器、构造方法)。

Date(year: 2019, month: 06, day: 02)传入的是所有成员值,用来初始化所有成员(叫做存储属性)。

1.1. 结构体的初始化器

编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始值。

从上面案例可以看出,编译器帮助生成初始化器的条件就是:让所有存储属性都有值。

> 思考:下面的代码能否编译通过?

可选项都有个默认值nil,所以可以编译通过。

1.2. 自定义初始化器

一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器。

1.3. 探究结构体初始化器的本质

下面的两段代码是等效的:

代码一:

代码二:

经过对比发现,代码一和代码二的init方法完全一样。也就是说,存储属性的初始化是在初始化构造方法中完成的。

1.4. 结构体的内存结构

  1. struct Point {
  2. var x = 10
  3. var y = 20
  4. var b = true
  5. }
  6. var p = Point()
  7. print(Mems.memStr(ofVal: &p))
  8. print(MemoryLayout<point>.size)
  9. print(MemoryLayout<point>.stride)
  10. print(MemoryLayout<point>.alignment)
  11. /*
  12. 输出:
  13. 0x000000000000000a 0x0000000000000014 0x0000000000000001
  14. 17
  15. 24
  16. 8
  17. */

因为存储属性xy各占8个字节(连续内存地址),Bool在内存中占用1个字节,所以Point一共占用17个字节,由于内存对齐是8,所以一共分配了24个字节。

二、类

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

定义类:

如果存储属性没有初始值,无参的初始化器也不会自动生成:

如果把上面的类换成结构体(struct)类型就不会报错:

2.1. 类的初始化器

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

成员的初始化是在这个初始化器中完成的。

下面的两段代码是等效的:
代码一:

  1. class Point {
  2. var x: Int = 0
  3. var y: Int = 0
  4. }
  5. var p1 = Point()

代码二:

  1. class Point {
  2. var x: Int
  3. var y: Int
  4. init() {
  5. self.x = 0
  6. self.y = 0
  7. }
  8. }
  9. var p1 = Point()

三、结构体与类的本质区别

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

3.1. 内存分析结构体与类

示例代码:

  1. class Size {
  2. var width: Int = 1
  3. var height: Int = 2
  4. }
  5. struct Point {
  6. var x: Int = 3
  7. var y: Int = 4
  8. }
  9. func test() {
  10. var size = Size()
  11. print("class-size对象的内存",Mems.memStr(ofRef: size))
  12. print("class-size指针的内存地址",Mems.ptr(ofVal: &amp;size))
  13. print("class-size对象的内存地址",Mems.ptr(ofRef: size))
  14. print("class-size.width的内存地址",Mems.ptr(ofVal: &amp;size.width))
  15. print("class-size.height的内存地址",Mems.ptr(ofVal: &amp;size.height))
  16. var point = Point()
  17. print("struct-point对象的内存",Mems.memStr(ofVal: &amp;point))
  18. print("struct-point的内存地址",Mems.ptr(ofVal: &amp;point))
  19. print("struct-point.x的内存地址",Mems.ptr(ofVal: &amp;point.x))
  20. print("struct-point.y的内存地址",Mems.ptr(ofVal: &amp;point.y))
  21. }
  22. test()
  23. /*
  24. 输出:
  25. class-size对象的内存 0x00000001000092a8 0x0000000200000002 0x0000000000000001 0x0000000000000002
  26. class-size指针的内存地址 0x00007ffeefbff4d0
  27. class-size对象的内存地址 0x000000010061fe80
  28. class-size.width的内存地址 0x000000010061fe90
  29. class-size.height的内存地址 0x000000010061fe98
  30. struct-point对象的内存 0x0000000000000003 0x0000000000000004
  31. struct-point的内存地址 0x00007ffeefbff470
  32. struct-point.x的内存地址 0x00007ffeefbff470
  33. struct-point.y的内存地址 0x00007ffeefbff478
  34. */

示例代码的在内存中:

经过分析可以看到,结构体的数据是直接存到栈空间的,类的实例是用指针指向堆空间的内存,指针在栈空间。上面示例代码中类的实例占用32个字节,其中前面16个字节分别存储指向类型信息和引用计数,后面16个字节才是真正用来存储数据的。而结构体占用的内存大小等于存储属性所占内存大小之和。

> 注意:在C语言中,结构体是不能定义方法的,但是在C++Swift中,可以在结构体和类中定义方法。在64bit环境中,指针占用8个字节。
>> 扩展:值类型(结构体、枚举)的内存根据所处的位置不同,内存的位置也不一样。例如,定义一个全局的结构体,内存在数据段(全局区)中;如果在函数中定义,内存存放在栈空间;如果在类中定义一个结构体,内存跟随对象在堆空间。

3.2. 汇编分析结构体与类

Swift中,创建类的实例对象,要向堆空间申请内存,大概流程如下:

  • Class.__allocating_init()
  • libswiftCore.dylib:_swift_allocObject_
  • libswiftCore.dylib:swift_slowAlloc
  • libsystem_malloc.dylib:malloc

在Mac,iOS中的malloc函数分配的内存大小总是16的倍数(为了做内存优化)。

通过class_getInstanceSize可以得知类的对象真正使用的内存大小。

  1. import Foundation
  2. class Point {
  3. var x: Int = 3
  4. var y: Int = 4
  5. var b: Bool = true
  6. }
  7. var p = Point()
  8. print(class_getInstanceSize(type(of: p)))
  9. print(class_getInstanceSize(Point.self))
  10. /*
  11. 输出:
  12. 40
  13. 40
  14. */

内存占用大小 = 8(指向类型信息) + 8(引用计数) + 8(存储属性x) + 8(存储属性y) + 1(存储属性b) = 33;

内存分配大小 = 8(指向类型信息) + 8(引用计数) + 8(存储属性x) + 8(存储属性y) + Max(1(存储属性b), 8(内存对齐数)) = 40;

> 扩展:如果底层调用了alloc或malloc函数,说明该对象存在堆空间,否则就是在栈空间。

3.2.1. 汇编分析结构体

第一步:创建结构体,打断点进入汇编:

第二步:在callq...init()函数处进入函数实现体(lldb进入函数体指令:si):

结论:rbp就是局部变量,所以结构体创建的对象是在栈中存储的。

> 扩展:一般情况下,rbp就是局部变量,rip是全局变量,ret是函数返回。

3.2.2. 汇编分析类

第一步:创建结构体,打断点进入汇编:

第二步:在callq...__allocating_init()...函数处打断点,进入函数体:

第三步:在callq...swift_allocObject函数处打断点,进入函数体:

第四步:一直进入到libswiftCore.dylib swift_allocObject:中,在callq...swift_slowAlloc处打断点进入:

第五步:malloc出现了,这时候继续进入函数体:

第六步:最终,对象是在libsystem_malloc.dylib库中执行的malloc

经过上面分析,可以清晰的看到,对象是在堆空间存储的。

扩展:在Mac、iOS中,创建对象都是调用的libsystem_malloc.dylib动态库。

原文链接:http://www.cnblogs.com/idbeny/p/swift-syntax-struct-class.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号