结构体
先看一下简单的语法书写
栗子1
struct Point {
var x: Int = 0
var y: Int
}
var p1 = Point(x: 10, y:20)// 正确
var p2 = Point(y:20)//错误
var p3 = Point(x: 10)//错误
var p4 = Point()//错误
栗子2
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y:20)// 正确
var p2 = Point(x: 10)//错误
var p3 = Point( y:20)//错误
var p4 = Point()//错误
struct Point {
var x: Int = 0
var y: Int = 0
}
var p1 = Point(x: 10, y:20)// 正确
var p2 = Point(x: 10)//正确
var p3 = Point(y:20)//正确
var p4 = Point()//正确
编译器干了啥?
编译器会根据情况,可能会为结构体生成生成多个初始化器,宗旨是:保证所有成员都有初始值.
🤔下面的代码能编译通过吗?
struct Point {
var x: Int?
var y: Int?
}
var p1 = Point(x: 10, y:20)
var p2 = Point(x: 10)
var p3 = Point( y:20)
var p4 = Point()
自定义初始化器
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(x: 10)//错误
var p3 = Point( y:20)//错误
var p4 = Point()//错误
窥看初始化器本质
栗子1
struct Point {
var x: Int = 0
var y: Int = 0
}
var p = Point()
栗子2
struct Point {
var x: Int
var y: Int
init() {
self.x = 0
self.y = 0
}
}
var p = Point()
栗子1和栗子2完全等价
结构体的本质
func testStruct() {
struct Point {
var x: Int = 10
var y: Int = 20
var b: Bool = false
}
var p = Point.init(x: 10, y: 10, b: true)
print(Mems.memStr(ofVal: &p))
print(MemoryLayout<Point>.size)
print(MemoryLayout<Point>.stride)
print(MemoryLayout<Point>.alignment)
}
testStruct()
打印结果:
0x000000000000000a 0x000000000000000a 0x0000000000000001
17
24
8
Program ended with exit code: 0
类
类的定义和结构体是相似的,但是编译器并没有为类自动生成可以传入成员值的初始化器.
class Point {
var x: Int = 0
var y: Int = 0
func test() { }
}
var p1 = Point()//正确
var p2 = Point(x: 10, y: 20)//错误
var p3 = Point(x: 10)//错误
var p4 = Point(y: 20)//错误
对比结构体
struct Point {
var x: Int = 0
var y: Int = 0
func test() { }
}
var p1 = Point()//正确
var p2 = Point(x: 10, y: 20)//正确
var p3 = Point(x: 10)//正确
var p4 = Point(y: 20)//正确
结构体和类的本质区别
结构体和枚举都是值类型
类是引用类型(指针类型)
class Size {
var width = 1
var height = 2
}
struct Point {
var x = 3
var y = 4
}
func test() {
var size = Size()
var point = Point()
}
问题:函数调用时,内存在哪里?
0x10000就是point结构体变量的内存地址
0x10008就是point结构体中y的内存地址
size是指针变量,占用8个字节,放在zhan里.
内存分布
注:上图指针针对的是64bit环境
对象的堆空间申请过程
🤔怎么看有木有在堆空间呢?
在swift里面就看有木有调用alloc、malloc这些函数.
func testClassAndStruct() {
class Size {
var w = 1
var h = 2
}
struct Point {
var x = 3
var y = 4
}
var s = Size()
var p = Point()
}
在swift中,创建类的实例对象,要向堆空间申请内存,大概流程如下:
- Class.__allocating_init()
- libswiftCore.dyld:swift_allocObject
- libswiftCore.dyld:swift_slowAlloc
- libsystem_malloc.dylib:malloc
class Point {
var x = 3
var y = 4
var b = true
}
var p = Point()
class_getInstanceSize(type(of:p))// 40
class_getInstanceSize(Point.self)// 40
func testClassAndStruct() {
class Size {
var w = 1
var h = 2
}
struct Point {
var x = 3
var y = 4
}
print("MemoryLayout<Size>.stride",MemoryLayout<Size>.stride)
print("MemoryLayout<Point>.stride",MemoryLayout<Point>.stride)
print("-------------------------")
var s = Size()
print("s变量的内存地址:",Mems.ptr(ofVal: &s))
print("s变量的内存的内容:",Mems.memStr(ofVal: &s))
print("-------------------------")
print("s所指向的内存地址:",Mems.ptr(ofRef: s))
print("s所指向的内存的内容:",Mems.memStr(ofRef: s))
print("-------------------------")
var p = Point()
print("p变量的内存地址:",Mems.ptr(ofVal: &p))
print("p变量的内存的内容:",Mems.memStr(ofVal: &p))
}
打印结果:
MemoryLayout<Size>.stride 8
MemoryLayout<Point>.stride 16
-------------------------
s变量的内存地址: 0x00007ffeefbff4a0
s变量的内存的内容: 0x00000001006060e0
-------------------------
s所指向的内存地址: 0x00000001006060e0
s所指向的内存的内容: 0x0000000100008298 0x0000000200000002 0x0000000000000001 0x0000000000000002
-------------------------
p变量的内存地址: 0x00007ffeefbff480
p变量的内存的内容: 0x0000000000000003 0x0000000000000004
Program ended with exit code: 0
由此可见:
1、s变量的内存地址0x00007ffeefbff4a0
、p变量的内存地址0x00007ffeefbff480
相邻,相差16个字节;
2、0x00000001006060e0
这个就是堆空间对象的地址;
3、MemoryLayout<Size>.stride
为8,s所指向的内存的内容为32?
4、如何知道一个对象创建出来占用多少堆空间地址?
var ptr = malloc(16)
print(malloc_size(ptr))// 16
小常识:在Mac、iOS中的malloc函数分配的内存大小总是16的倍数
通过class_getInstanceSize可以得知类的对象真正使用的内存大小
来一个问题🤔: 结构体内存一定在栈里吗?
不一定,要看你的结构体变量在哪里定义的.
- 如果结构体变量是在函数里定义,它的内存在栈空间.
如:
func test() {
var p = Ponit()//结构体
}
- 如果结构体变量是在外边定义的,那它的内存就在数据段(全局区)是一个全局变量.
如:
var p = Ponit()//结构体
class Size {
var w = 1
var h = 2
}
- 如果结构体变量在一个类里面创建,那它的内存就在堆空间.
如:
class Size {
var w = 1
var h = 2
var p = Ponit()//结构体
}
思考🤔:
class Size {
var w = 1
var h = 2
func test() {
var p = Ponit()//结构体
}
}
上面这个结构体变量p内存地址在哪里?
联想一下: 类的内存在哪里呢?
无论在哪里创建对象,这个对象的内存一定在堆空间.只不过指针变量的内存在的位置不一定.函数里面定义就在栈空间,函数外边定义就在(数据段)全局区.
值类型
值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份(深copy).
func testValueType() {
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = p1
p2.x = 11
p2.y = 21
print("1111")
}
汇编观察上面的值类型
mov qword ptr [rbp - 0x10], rax // rbp - 0x10 0x1000 p1的内存地址
mov qword ptr [rbp - 0x8], rdx // rbp - 0x8 0x1008
mov qword ptr [rbp - 0x20], rax // rbp - 0x20 p2的内存地址
mov qword ptr [rbp - 0x18], rdx // rbp - 0x18
mov qword ptr [rbp - 0x20], 0xb // 11 给了p2
mov qword ptr [rbp - 0x18], 0x15 // 22
全局定义
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = p1
p2.x = 11
p2.y = 21
print("1111")
汇编观察上面的值类型
mov qword ptr [rip + 0x57e9], rax 0x1000019e7 + 0x57e9 0x1000071D0 p1的内存地址
mov qword ptr [rip + 0x57ea], rdx 0x1000019ee + 0x57ea 0x1000071D8
0x1000019ff <+79>: mov rax, qword ptr [rip + 0x57ca] // 10
0x100001a06 <+86>: mov qword ptr [rip + 0x57d3], rax // 0x1000071E0 p2的内存地址
0x100001a0d <+93>: mov rax, qword ptr [rip + 0x57c4] // 20
0x100001a14 <+100>: mov qword ptr [rip + 0x57cd], rax // 0x1000071E8
0x100001a1b <+107>: lea rdi, [rbp - 0x18]
规律:局部变量的内存格式 [rbp - 0x10],全局变量的内存格式 [rip + 0x57e9]
全局变量:整个程序运行过程中,只存在一份内存;
值类型的赋值操作
栗子1
var s1 = "Jack"
var s2 = s1
s2.append("_rose")
print(s1)//Jack
print(s2)//Jack_rose
栗子2
var a1 = [1,2,3]
var a2= a1
a2.append(4)
a1[0] = 2
print(a1)// [2,2,3]
print(a2)// [1,2,3,4]
栗子3
var d1 = ["max":10, "min":21]
var d2= d1
d1["other"] = 7
d1["max"] = 12
print(d1)// ["other:12, ""max":10, "min":21]
print(d2)// ["max":12,"min": 21]
在swift标准库中,为了提升性能,String、Array、Dictionary、Set采用了Copy On Write.
比如仅当有“写”操作时,才会真正执行拷贝操作
对于标准库值类型的赋值操作,Swift能确保最佳性能,所以没必要为了保证性能来避免赋值.
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10,y: 20)
p1 = Point(x: 20,y: 30)
留意: 这个操作修改的还是p1原来的内存.
引用类型
引用赋值给var 、let 或者给函数传参,是将内存地址拷贝一份(有点向快捷方式的概念),属于浅拷贝.
class Point {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
func test() {
var p1 = Point(x: 10,y: 20)
p1 = p2
}
网友评论