首页 > 编程知识 正文

swift官网,3通道存储器总线

时间:2023-05-05 03:37:06 阅读:162601 作者:1148

值类型假设:需要了解内存5大空间。 内存5大区域请参考本文的iOS-基础原理24 :内存5大区域。 以下所示

值类型-1

堆栈区域的地址大于堆区域的地址

堆栈从高地址到低地址向下延伸,由系统自动管理,是连续的内存空间

堆从低地址-高地址向上延伸,由程序员管理,堆空间结构像链表一样不连续

日常开发中的溢出是指堆栈溢出,可以理解为堆栈区域和堆栈区域的边界冲突的情况

全局区域和常量区域都存储在Mach-O的__TEXT cString段中

作为一个例子,我们引入什么是值类型

functest((/堆栈区域声明存储从age变量var age=18 //传递的值var age2=age //age的地址。 age2是指更改独立内存中的值age=30age2=45print ) )。

从age的存储器状况来看,从图中可以看出,堆栈区域直接容纳了值

获取age的堆栈地址: powithunsafepointer(to:age ) print ($0) ) ) ) ) ) ) ) ) ) ) ) )。

查看age内存状态: x/8g0x00007ffeefbff3e0

值类型-2

从age2的情况来看,从下图可以看出,age2的代入相当于取出age的值代入到age2中。 这里,由于age和age2地址相差8字节,所以可知堆栈空间是连续的,从高到低

值类型-3

因此,正如上面所解释的,age是值类型

值类型特征

1、地址中存储有值

2、在传递值类型的过程中,相当于传递了一个副本,即所谓的深度副本

3、交接值过程中,不共享状态

结构的一般写法

//*****写法1 * * * * structcjlteacher (varage : int=18 func teach () (print ) ) }}var t=CJLTeacher ) structcjlteacher { varage : intfuncteach () print('teach'}}vart=cjlteacher ) age:18 )是结构中属性的默认值

值类型-4

可以重写init方法,也可以使用系统缺省的

结构的SIL分析

如果没有init,则缺省初始化方法不同

值类型-5

如果提供了定制的init,则只有定制的

值类型-6

为什么结构是值类型? 定义和分析结构

structcjlteacher (varage : int=18 varage 2: int=20 ) vart=cjlteacher )打印(' end ' )打印t:po t,从下图中可以看到t

值类型-7

获取t的存储器地址,确认其存储器状况

地址获取: powithunsafepointer(to:t ) print ($0) ) ) ) ) ) )。

查看内存状态: x/8g0x0000000100008158

值类型-8

问:此时,将t代入t1。 修改t1后,t会发生变化吗?

直接打印t和t1,可以看出t没有随t1的变化而变化。 主要是因为t1和t之间是值的传递,即t1和t是不同的内存空间,直接将t的值复制到t1。 t1修改的存储器空间不影响t的存储器空间

值类型-9

SIL认证

同样,也可以通过分析SIL来验证结构是否为值类型

在SIL文件中,查看如何初始化结构,可以看到只有init,没有malloc,没有看到任何堆分配

>值类型-10

总结

结构体是值类型,且结构体的地址就是第一个成员的内存地址

值类型

在内存中直接存储值

值类型的赋值,是一个值传递的过程,即相当于拷贝了一个副本,存入不同的内存空间,两个空间彼此间并不共享状态

值传递其实就是深拷贝

引用类型 类

**类的常用写法 **

//****** 写法一 *******class CJLTeacher { var age: Int = 18 func teach(){ print("teach") } init(_ age: Int) { self.age = age }}var t = CJLTeacher.init(20)//****** 写法二 *******class CJLTeacher { var age: Int? func teach(){ print("teach") } init(_ age: Int) { self.age = age }}var t = CJLTeacher.init(20)

在类中,如果属性没有赋值,也不是可选项,编译会报错

引用类型-1

需要自己实现init方法

为什么类是引用类型?

定义一个类,通过一个例子来说明

class CJLTeacher1 { var age: Int = 18 var age2: Int = 20}var t1 = CJLTeacher1()

类初始化的对象t1,存储在全局区

打印t1、t:po t1,从图中可以看出,t1内存空间中存放的是地址,t中存储的是值

引用类型-2

获取t1变量的地址,并查看其内存情况

获取t1指针地址:po withUnsafePointer(to: &t1){print($0)}

查看t1全局区地址内存情况:x/8g 0x0000000100008218

查看t1地址中存储的堆区地址内存情况:x/8g 0x00000001040088f0

引用类型-4

引用类型 特点

1、地址中存储的是堆区地址

2、堆区地址中存储的是值

问题1:此时将t1赋值给t2,如果修改了t2,会导致t1修改吗?

通过lldb调试得知,修改了t2,会导致t1改变,主要是因为t2、t1地址中都存储的是 同一个堆区地址,如果修改,修改是同一个堆区地址,所以修改t2会导致t1一起修改,即浅拷贝

引用类型-5

问题2:如果结构体中包含类对象,此时如果修改t1中的实例对象属性,t会改变吗?

代码如下所示

class CJLTeacher1 { var age: Int = 18 var age2: Int = 20}struct CJLTeacher { var age: Int = 18 var age2: Int = 20 var teacher: CJLTeacher1 = CJLTeacher1()}var t = CJLTeacher()var t1 = tt1.teacher.age = 30//分别打印t1和t中teacher.age,结果如下t1.teacher.age = 30 t.teacher.age = 30

从打印结果中可以看出,如果修改t1中的实例对象属性,会导致t中实例对象属性的改变。虽然在结构体中是值传递,但是对于teacher,由于是引用类型,所以传递的依然是地址

同样可以通过lldb调试验证

打印t的地址:po withUnsafePointer(to: &t){print($0)}

打印t的内存情况: x/8g 0x0000000100008238

打印t中teacher地址的内存情况:x/8g 0x000000010070e4a0

引用类型-6

注意:在编写代码过程中,应该尽量避免值类型包含引用类型

查看当前的SIL文件,尽管CJLTeacher1是放在值类型中的,在传递的过程中,不管是传递还是赋值,teacher都是按照引用计数进行管理的

引用类型-7


可以通过打印teacher的引用计数来验证我们的说法,其中teacher的引用计数为3

引用类型-8
主要是是因为:

main中retain一次

teacher.getter方法中retain一次

teacher.setter方法中retain一次

引用类型-9

mutating

通过结构体定义一个栈,主要有push、pop方法,此时我们需要动态修改栈中的数组

如果是以下这种写法,会直接报错,原因是值类型本身是不允许修改属性的

引用类型-10

将push方法改成下面的方式,查看SIL文件中的push函数

struct CJLStack { var items: [Int] = [] func push(_ item: Int){ print(item) }}

引用类型-11
从图中可以看出,push函数除了item,还有一个默认参数self,self是let类型,表示不允许修改

尝试1:如果将push函数修改成下面这样,可以添加进去吗?

struct CJLStack { var items: [Int] = [] func push(_ item: Int){ var s = self s.items.append(item) }}

打印结果如下


可以得出上面的代码并不能将item添加进去,因为s是另一个结构体对象,相当于值拷贝,此时调用push是将item添加到s的数组中了

根据前文中的错误提示,给push添加mutating,发现可以添加到数组了

struct CJLStack { var items: [Int] = [] mutating func push(_ item: Int){ items.append(item) }}

查看其SIL文件,找到push函数,发现与之前有所不同,push添加mutating(只用于值类型)后,本质上是给值类型函数添加了inout关键字,相当于在值传递的过程中,传递的是引用(即地址)

inout关键字

一般情况下,在函数的声明中,默认的参数都是不可变的,如果想要直接修改,需要给参数加上inout关键字

未加inout关键字,给参数赋值,编译报错

引用类型-14

添加inout关键字,可以给参数赋值

总结

1、结构体中的函数如果想修改其中的属性,需要在函数前加上mutating,而类则不用

2、mutating本质也是加一个 inout修饰的self

3、Inout相当于取地址,可以理解为地址传递,即引用

4、mutating修饰方法,而inout 修饰参数

总结

通过上述LLDB查看结构体 & 类的内存模型,有以下总结:

值类型,相当于一个本地excel,当我们通过QQ传给你一个excel时,就相当于一个值类型,你修改了什么我们这边是不知道的

引用类型,相当于一个在线表格,当我们和你共同编辑一个在先表格时,就相当于一个引用类型,两边都会看到修改的内容

结构体中函数修改属性, 需要在函数前添加mutating关键字,本质是给函数的默认参数self添加了inout关键字,将self从let常量改成了var变量

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。