结构体和类
结构体
- 在swift标准库中,绝大多数的公开类型都是结构体,枚举和类只占很小一部分
- 比如Bool、Int、Double、String、Array、Dictionary等常见类型都是结构体
- 所有的结构体都有一个编译器自动生成的初始化器(initializer、初始化方法、构造器、构造方法)
struct Date {
var year: Int // 存储属性
var month: Int
var day: Int
}
// 这里调用的,就是初始化器,可以传入所有成员值,用以初始化所有成员(存储属性、Stored Property)
var date = Date(year: 2019, month: 8, day: 2)
结构体的初始化器
- 编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是: 保证所有成员有初始值
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = Point(y: 32) // 错误 x没有初始化
struct Point {
var x: Int = 0
var y: Int
}
var p2 = Point(y: 32)// 正确 编译器提供了2种初始化器
struct Point {
var x: Int?
var y: Int?
}
var p1 = Point(x: 10, y: 20)
var p2 = Point(y: 32)
var p3 = Point()
// 可选项有个默认值nil,所以都可以编译通过
- 自定义初始化器
- 一旦在定义结构体时自定义了初始化器,编译器就不会帮它自动生成其他初始化器
struct Point {
var x: Int = 0
var y: Int = 0
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var p1 = Point(x: 10, y: 20)
var p2 = Point(y: 32)// 不能调用init 初始化器
var p3 = Point() // 同上
初始化器的本质
- 下面2个代码完全等效
struct Point {
var x: Int
var y: Int
init() {
x = 0
y = 0
}
}
var p = Point()
struct Point {
var x: Int = 0
var y: Int = 0
}
//
var p = Point()
// 对应的汇编代码 相同
TestS`init() in Point #1 in testStruct():
-> 0x100001bf0 <+0>: pushq %rbp
0x100001bf1 <+1>: movq %rsp, %rbp
0x100001bf4 <+4>: xorps %xmm0, %xmm0
0x100001bf7 <+7>: movaps %xmm0, -0x10(%rbp)
0x100001bfb <+11>: movq $0x0, -0x10(%rbp)
0x100001c03 <+19>: movq $0x0, -0x8(%rbp)
0x100001c0b <+27>: xorl %eax, %eax
0x100001c0d <+29>: movl %eax, %ecx
0x100001c0f <+31>: movq %rcx, %rax
0x100001c12 <+34>: movq %rcx, %rdx
0x100001c15 <+37>: popq %rbp
0x100001c16 <+38>: retq
- 结构体的内存布局
struct Point {
var x: Int = 10
var y: Int = 14
var origin: Bool = true
}
var p = Point()
print(Mems.memStr(ofVal: &p)) // 0x000000000000000a 0x000000000000000e 0x0000000000000001
print(MemoryLayout<Point>.size)// 17
print(MemoryLayout<Point>.stride) // 24
print(MemoryLayout<Point>.alignment) // 8
类
- 类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
- 如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器
- 成员的初始化实在这个初始化器中完成的
class Point {
var x: Int = 10
var y: Int = 14
}
let p1 = Point()
结构体和类的本质区别
- 结构体时值类型,类是引用类型(指针类型)
![]()
class Size {
var width = 1
var height = 2
}
struct Point {
var x = 3
var y = 4
}
var size = Size()
// 类不论你在哪里创建,对象内存都是在堆空间,而指针变量则要看在哪里创建
print("size所指向内存的大小",Mems.size(ofRef: size));
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("------------------------------")
var point = Point()
// 结构体变量在函数里定义内存在栈空间,在函数外全局定义则是在数据段全局区,如果在类里则是在堆空间
// 结构体的内存在哪取决于定义的位置
print("point变量的地址",Mems.ptr(ofVal: &point))
print("point变量的内存",Mems.memStr(ofVal: &point))
/*
size所指向内存的大小 32
size变量的地址 0x00007ffeefbff580
size变量的内存 0x0000000100712bc0
size所指向内存的地址 0x0000000100712bc0
size所指向内存的内容 0x0000000100007778 对象的类型信息
0x0000000200000002 引用计数相关
0x0000000000000001 0x0000000000000002 // 成员变量
这个占用32个字节
------------------------------
point变量的地址 0x00007ffeefbff560
point变量的内存 0x0000000000000003 0x0000000000000004
*/
- 对象在堆空间内存申请过程
- Class._allocating_init()
- libswiftCore.dylib:_swift_allocObject
- libswiftCore.dylib:_swift_slowAlloc
- libsystem_malloc.dylib:malloc
- 在mac、ios中malloc函数分配的内存大小总是16的倍数
值类型
-
值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
- 类似于对文件进行copy、paste操作一样,产生了全新的文件副本,属于深拷贝
-
汇编分析
// 结构体在全局区 在数据段
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
movq %rax, 0x790(%rip) // 0x100001978 + 0x790 = 0x100002108
movq %rdx, 0x791(%rip) // 0x10000197f + 0x791 = 0x100002110
var p2 = p1
movq 0x763(%rip), %rax // 0x1000019a5 + 0x763 = 0x100002108 --> 10
movq %rax, 0x76c(%rip) // 10放到 0x1000019b3 + 0x76c = 0x10000211F -->p2的内存地址
movq 0x75d(%rip), %rax // 20
movq %rax, 0x766(%rip) // 20 放到 0x1000019be + 0x766 = 0x100002124
p2.x = 11
p2.y = 22
// 结构体在函数里的情况
func testValueType() {
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = p1
// rax 里是10 rdx 是20
movq %rax, -0x10(%rbp) // rbp -0x10 p1 的内存地址
movq %rdx, -0x8(%rbp) // rbp -0x8 连续的16个字节
movq %rax, -0x20(%rbp) // rbp -0x20 p2 的内存地址
movq %rdx, -0x18(%rbp) // // rbp -0x18 连续的16个字节
p2.x = 11
p2.y = 22
movq $0xb, -0x20(%rbp)
movq $0x16, -0x18(%rbp) // 11 和 22 给了上面p2的内存
print("123")
}
testValueType()
-
规律
- 内存地址格式为: 0x3bdc(%rip),一般是全局变量,全局区(数据段)
- 内存地址格式为: -0x78(%rbp),一般是局部变量,栈空间
- 内存地址格式为: -0x10(%rax),一般是堆空间
-
在Swift标准库中,为了提升性能,String、Array、Dictoinary、Set采用了 Copy On Write技术
- 比如仅当有"写"的操作时,才会真正执行深拷贝操作
- 建议: 不需要修改的尽量定义为let
- 如果是自定义值类型,是不会采用自动采用Copy On Write技术
引用类型
- 值类型赋值给var、let或者给函数传参,是将内存地址拷贝一份
- 类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件,属于浅拷贝(shallow copy)
![]()
- 类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件,属于浅拷贝(shallow copy)
func testReferenceType() {
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var s1 = Size(width: 10, height: 20)
// rax 是返回值的地址 -0x10(%rbp)局部变量地址 就是s1的地址 rax = 0x0000000100706ea0
// rax 里的r内容 在堆里
// A0 21 00 00 01 00 00 00 指向类型信息
// 02 00 00 00 00 00 00 00 引用计数
// 0A 00 00 00 00 00 00 00 --> 10
// 14 00 00 00 00 00 00 00 --> 20
movq %rax, -0x10(%rbp)
var s2 = s1
// -0x60(%rbp) s2的地址
movq %rax, -0x60(%rbp)
s2.width = 11
// 将s2的地址给了rax 然后rax+10 刚好是s2存储10的位置
movq -0x60(%rbp), %rax
// 将11 放到了 rax+0x10 刚好是s2存储10的位置
movq $0xb, 0x10(%rax)
s2.height = 22
// 将$0x16 22 放到了s2第二个成员的位置
$0x16, 0x18(%rax)
}
testReferenceType()
枚举、结构体、类都可以定义方法
- 一般把定义在枚举、结构体、类内部的函数,叫做方法
- 要使用实例调用方法
- 方法不占用对象的内存
- 方法的本质就是函数,函数、方法都存放在代码段



浙公网安备 33010602011771号