美文网首页
学习日记_2022年03月

学习日记_2022年03月

作者: 今晚煮面 | 来源:发表于2022-04-01 15:24 被阅读0次

03_07:今天学习的是闭包:什么是闭包?闭包就是有函数+捕获的变量,那什么是闭包表达式?其实定义函数有2种方式,第一就是func关键字,另一个就是闭包表达式,它也是一个函数。什么叫自动闭包?那就是autoclosurce这个就是在定义函数的时候,你加上autoclosurce这个关键字,然后你传参数的时候,它会自动的把参数变成闭包表达式。闭包捕获一个变量的时候,就会调用malloc向堆申请内存,然后堆返回的内存都是16的倍数,每个变量前16个自己是固定的,前八是存储类型数据,接下来的8个是引用计数,捕获一个变量申请一次,2个变量就申请2次,不是说2个变量的话就是前面的16个字节,再加上2个变量哦,不是这样的,这2个变量的内存是分别申请的,不在一起的;在堆申请的内存是随机的,而且在堆申请的到的内存都是脏的,要自己初始化,如果是在闭包里使用了全局变量,那就不会向堆申请内存?为什么呢,因为全局变量就是在整个运行周期都存在的哦,捕获临时变量的时候向堆申请内存,就是为了保住临时变量的生命周期啊,所以全局变量的话就不会向堆申请内存。全局变量在的地址一般都比较小的哦,因为在最上面,然后堆的一般是0x10xx这样的,然后栈的话就比较大了,函数调用的时候会在栈开辟内存给他,存它的数据。rax寄存器一般拿来存放函数的返回值,cdi,rdi,r8,r9这种就是拿来放函数的参数的。把一个函数赋值给一个变量时,这个变量存有什么数据呢?首先是有它返回的那个函数的指针,还有一个就是堆的内存地址,调用函数里面的函数时,其实是传递了2个参数,那个捕获的变量也当参数传给函数的。rip是一个变化的地址,就是它所在的那行的下面的地址,如果一个函数里有2个临时变量,然后有2个函数都是用到了这2个临时变量,这2变量都会在堆那里分别申请一块内存,然后2个函数共享这个2个数据。如果函数里面的临时变量没被函数捕获,那它们就是在栈区,跟随函数的生命周期,函数调用结束,函数栈里的数据就会销毁了哦,那2个临时变量就没有了,如果被函数捕获了,放堆区了,就不会这样销毁,那就是根据它的引用计数来决定是否销毁了。函数,方法是不占用对象的内存的,因为函数放在代码区,函数调用的时候会传参数和对象本身进来去调用,如果2个对象分别调用同一个方法,那么它们使用的函数指针肯定是一样的,因为都是调用代码区的方法,都是一份,所以指针地址是一样的。闭包表达式会有点滞后性哦。lldb指令:si,register read exa,bt;寄存器有些是用来装函数参数的,有些是用来做函数函数返回值的,如果你想找到一块刚申请好的内存,那你就去找malloc这个方法后面一行的寄存器,打印它的地址,它肯定就是刚申请的堆的内存地址。

03_08:今天学了什么?属性,下标,继承,属性分为存储属性,计算属性,存储属性是占用对象的内存的,也就是说如果一个对象在堆申请了内存,那存储属性是占用内存空间的,计算属性就不用占对象的内存空间,下标其实就是一个方法,函数,不过它的声明是叫subscript,反正本质也是函数,也有参数,也有返回值,其实我也没想明白这个东西到底有什么用,反正就拿来当函数用吧,反正具体也没想到什么使用场景。继承的话,首先存储属性是可以继承的,然后子类通过父类集成的属性,也在子类占用空间的哦,如果是存储属性,那可以通过继承,然后重写为计算属性,计算属性也还是只能重写为计算属性,只读属性可以重写为读写属性,读写属性不可以重写为只读属性,通过继承然后重写父类的方法,需要在方法前面加override这个关键字,可以在重写的时候给属性添加属性检测器哦,类型方法如果用class修饰的话,子类可以继承,如果用static修饰的话,子类不可以继承,因为static限定了作用于就在当前类。

03_09:今天学了什么呢?多态,初始化,可选链,协议,元类型等,今天学的东西好多啊,我都记不住了,今天都是讲语言的,多态,父类指针指向子类对象,其实多态的底层实现就跟c++的虚表一样的,首先调用方法的时候会传入类对象的指针,然后其实类对象的指针指向的是堆里面的类对象的内容,所以可以拿到它的地址值,然后根据内存排布我们知道它的前8位存放了类型信息的地址,然后我们就可以找到了存放类型信息的内存,然后通过看汇编我们找到了是通过类型信息内存的首地址+偏移量然后就找到了函数的地址,然后就可以调用函数了,函数都是依次排布的,然后函数有当前类的,也有从父类继承来的,有了地址,然后就可以调用了。初始化就要就是有两个初始化器,一个叫指定初始化器,一个叫便利初始化器,然后它们的规则就是,便利初始化器只能调用当前类的其他便利初始化器,只能横向调用,并且最终都会调用制定初始化器,然后制定初始化器再调用它的直接父类的初始化器,然后一直往上,直到没有父类了为止,整个初始化过程分为2歌阶段,第一个阶段就是从子类到父类,便利初始化器调用当前类的制定初始化器,然后再调用制定初始化器,不过要先把当前类的存储属性都初始化,然后再调用父类的指定初始化器,然后再把父类的存储属性初始化,然后再往上,直到最上的类,然后都是先完成自己的存储属性初始化然后再往上走,然后到了基类后,这个时候所有的存储属性都初始化完了,这个时候就完成了第一阶段,这个时候再从上往下走,这个时候就可以重新做自己定制的内容了,有可以使用self,也可以给其他属性赋值了。所谓可选链就是可选类型的时候,一定要用?访问,然后一定是可选类型有值了才可以继续往下走,只要其中一个可选类型是nil,那整个链就断了,不会再往下走了。协议(protocol)这个就是定义了一些方法,然后枚举,结构体,类都可以通过遵循这些协议,然后可以得到这些方法,实现这些方法,swift的规定就是你遵循了就要全部实现,不能像oc那样,有些可以实现,有些不实现。其中其实还有很多小细节的,我记不清了,这个就到时候用到的时候再查找吧。元类型,其实就是存储在堆里类型对象前8个字节那里存储的东西,就是类型信息,medadata就是这个东西了,跟oc的isa指针差不多意思吧反正

03_10:今天学了什么呢?好像学了错误处理,字符串,数组的底层实现;错误处理其实就是error的处理,其实有2种方式处理,第一就是throw这个错误,就是把错误往上级函数抛,让上级调用函数处理,然后一直抛到给系统,其实这样就会最后崩溃,其实我感觉这个没啥意思,我还不如自己不处理,也是照样崩溃的,第二就是用do catch 来处理,就是有错误了,然后用catch来接这个错误,然后做相应的处理,这样就可以了,这样至少不会崩溃。字符串的底层存储实现其实就是如果字符串长度小于15个字节的话,字符串的数据就会直接存储在字符串变量的内存中,直接用ascii的形式存储,如果大于15个字节的话,就会把字符实际的内容存储在常量区,字符常量,这个就是在编译期的时候就确定的东西,然后在字符串变量里面后八个字节存储这个地址,然后通过地址我们就可以找到字符串数据,如果对字符串进行拼接的话,就会开辟新的内存空间,也就是会开辟堆空间来存储字符数据,在变量里还是存储这个堆空间的地址,通过这个地址字符数据;然后数组的话,它是通过结构体实现的,然后是一个值类型的,不过它的底层实现其实是引用类型的,也就是说它的数据其实也是存储在堆空间的,当给数组添加数据的时候,它也是会调用malloc方法,开辟堆空间的内存来存储数组数据,而且它的大小还会动态改变,随着你添加数据的个数,会动态增大,都是用16的备注增加的,然后它会在前面的空间存储一些类型信息,引用计数之类的,后面才开始存储数据。字符绑定,什么叫字符绑定,其实就是动态库的,动态库在加载到内存的时候是放在最下面的,内存地址也是最大的,然后刚开始的时候其实它的函数地址是不知道的,然后编译器会先给他一个固定的函数地址,反正是假的函数地址,等到你真的用到它,需要加载它的时候就会出现了字符绑定,然后通过开始那个假地址,通过各种各种,然后最后找到那个真实的函数地址,这个时候它就会更新存储在常量区之前的那个假的地址,这个时候常量代码区存储的才是真的动态库函数地址,如果你这个时候再调用一次刚才那个函数的话,你就会发现它的地址变了,变成了真正的函数地址了,跟踪汇编的话,你会发现之前是很多汇编代码调用的,现在都没有了,通过那个地址直接找到了函数,因为字符绑定后它有了真的函数地址,可以直接调用了哦,还有一个就是如果你需要定义一些方法的话,其实直接用struct定义比用类定义好,为什么?因为用结构体定义的方法,调用的时候是直接调用的,callq,你看汇编就能看得出来,用类的话,还有很多事情要做,还有通过地址找到堆空间,然后再偏移找到函数的地址,它这个函数地址实现差不多跟c++的虚表一样,反正整个流程下来是比较久的,结构体的话什么都不用,直接callq 调用,很快的哦,记住!

03_11:今天学了什么呢?可选项本质,运算符重载,扩展,访问控制,内存管理;可选项的本质其实就是枚举,它就是有两个case,一个叫.none,一个叫.some,然后这个.some还是支持泛型的;运算符重载就是你可以对现有的运算符进行自定义实现,比如实现两个类对象的加法之类的;扩展,类,结构体,枚举都可以使用扩展,扩展就是扩展方法,函数,存储属性不能扩展,因为存储属性是占用对象内存的,这个在初始化的时候就已经决定了,所以不能通过扩展区更改;访问控制就是像访问权限之类的,有5个等级,open:当前模块和其他模块都可以访问,并且可以重载和继承,public是当前模块和其他模块可以访问,但是不能重载和继承;internal,就是当前模块可以访问,一般类型前面没有写的默认就是这个权限;fileprivate就是当前定义的文件可以访问;private这个就是定义的当前作用域,也就是这个大括号里可以访问;内存管理其实跟oc的差不多,也是对堆的对象用引用计数进行管理,还有自动释放池,关于循环引用就2种方案解决,一个是弱引用,这个是用在对象会有可能为nil的时候用的,这个就是对象引用计数为0后,会把它置为nil,然后它必须是var定义的,无主引用的话,是用在对象不为nil的时候,这个的话就是对象引用计数为0 了,它不会为nil,还是保存这个对象的内存地址,所以这个时候如果还是被访问的话就会崩溃,提示坏地址访问。

03_12:学了啥?指针,字面量协议,模式匹配;指针就是地址值,不过我们在swift中可以新建指针,也就是malloc分配内存,然后返回地址,也就是指针,然后通过指针进行操作,指针有2种类型,也有可更改的,或者只读的,通过指针去访问地址内容,这个还是比较方便的呢;字面量协议就是只要你遵守了字面量的协议,你就可以通过字面量去初始化一个变量;模式匹配,这个东西好多的,反正就是到时候用到就知道了,反正基本也都能看懂吧反正,就这样了。

03_13:今天学什么了?从oc到swift,从swift到oc;oc调用swift的话,其实xcode已经默认生成了一个targetname-swift桥接文件,不过如果是swift调用oc的话,需要自己新建一个targetname-bridge-header.h这样的一个头文件,然后oc把想给swift调用的文件的.h文件import进去;如果swift的想给oc调用的话,首先需要继承自nsobject?为什么一定要继承自nsobject呢?首先是因为如果想进行方法调用的话,肯定要走runtime那套流程,要走这个流程最重要的东西是要有isa指针,要这个指针的话肯定是要继承自nsobject的了。然后想暴露属性,成员或者方法给oc调用的话,需要加上@objc,如果一个类都想暴露出去的话,就在class前面加@objcmembers这个关键字。还有一点就是oc的方法调用就是走msg_send这个流程,swift方法调用就是走虚表这个流程,反正具体走那个流程就看他在那个环境里了。

03_23:今天学了什么呢?在flutter中,万物都是widget,首先入口函数main,runapp,然后在这里就要传入一个widget,在flutter中首先会有一个materialapp这个是一个设计样式,然后接下来就是一个scaffod,这个scaffod就相当于iOS开发中的viewcontroller,然后它可以有一个导航栏,一个body,还可以有一个底部导航栏,然后在widget的里面可以添加child的widget,这个就是一个,然后多个的话用children,反正就是会形成一个树状的结构,然后widget有stateless和stateful2种的,stateless就是无状态的,这个就是用在静态界面的时候,就是说里面的内容都是静态的,不会改变的,stateful用于有状态改变的,然后它的状态由一个stata子类来管理的,stateless的它的生命周期就比较简单了,就是constructor和build方法两个,不过在stateful的就比较复杂了,首先是widget的constructor,然后再到它的createstate,然后再到state的constructor,initconstruct,然后在到build,在到dispose。在布局方面,其实也是flex布局的思想,纵轴的话就是colum,横轴就是row,然后其实就是跟小程序那里的差不多啊,这个的元素是widget,小程序的是view,布局思想基本差不多的。必选参数,位置可选参数,命名可选参数,初始化列表,在这里可以判断或者给参数一个初始值或者参数的值的有无的判断。在state子类有变量改变需要更新页面的时候,就是用setstate方法,这个底层就是会让它最终调用build方法,其实就是有一个dirt的一个状态管理,它有一个bool值来管理,需要更新渲染的时候是yes,就是用来标记是否要调用build方法,如果你修改了变量,就能检测到的哦。widget例如,text,button,listview,container,center,richtext,textspan,icon,image等等

03_24:今天学了什么呢?dio网络请求的三方库,async,await异步请求,listview,gridview,wrap,flex布局,父widget像子widget传递数据,第一步新建一个共享的数据,然后把widget都包裹在它里面,然后发送通知,第三步就是放监听者,swiper,轮播图,

03_28:今天学了什么呢?

===如果在面试的时候遇到难题,我们有3种方法分析解决复杂的问题:画图能使抽象问题形象化,举例使抽象问题具体化,分解使复杂问题简单化

===面试官除了希望应聘者的代码能够完成基本的功能之外,还会关注应聘者是否考虑了边界问题,特殊输入*(比如null指针,空字符串等)以及错误处理

===在介绍项目经验的时候,应聘者不必详述项目的背景,而要突出介绍自己完成的工作以及取得的成绩

===值类型的实例在栈上分配内存,而引用类型的实例在堆上分配内存

===其实查找的本质就是找到一个切入点,然后不断的缩小查找的范围,从而找到答案

===合并两个数组或者字符串的时候,如果从前往后复制每个数字或者字符需要重复移动数字或者字符多次,那么我们可以考虑从后往前复制,这样就能减少移动的次数,从而提高效率

===二叉树有很多特例,二叉搜索树就是其中一个,在二叉搜索树中,左子节点总是小于或者等于根节点,而右子节点总是大于或者等于根节点。我们可以平均在O(logn)的时间内根据数值在二叉搜索树中找到一个节点;二叉树的另外2个特例就是堆和红黑树。堆可以分为最大堆和最小堆,在最大堆中根节点的值最大,在最小堆中根节点的值最小,有很多需要快速查找到最大值或者最小值的问题都可以用堆来解决。红黑树是把树中的节点定义为红黑两种颜色,并通过规则确保从根节点到叶节点的最长路径的长度不超过最短路径的2倍。

===用两个栈实现一个队列;用两个队列实现一个栈

===有很多算法都可以用递归和循环两种不同的方式实现。通常基于递归的实现方法代码会比较简洁,但性能不如基于循环的实现方法。因为递归的时候,如果数据长度比较长的话,会导致调用栈的层级很深有可能导致栈溢出的风险。

===位运算可以看成是一类特殊的算法,它是把数字表示成二进制之后对0和1的操作。由于位运算的对象为二进制数字,所以不是很直观,但是掌握它也不难,因为总共只有与,或,异或,左移,右移5种位运算

03_29:今天学了什么呢?

===如果面试题是要求在排序的数组(或者部分排序的数组,排序数组的旋转)中查找一个数字或者统计某个数字出现的次数,我们都可以尝试用二分查找算法

===实现快速排序算法的关键在于先在数组中选择一个数字,接下来把数组中的数字分为2部分,比选择的数字小的数字移动到数组的左边,比选择的数字大的移动到数组的右边

===如果我们需要重复地多次计算相同的问题,通常可以选择用递归或者循环两种不同的方法。递归是在一个函数内部调用这个函数自身。而循环则是通过设置计算的初始值以及终止条件,在一个范围内重复运算。

===递归是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数,返回地址以及临时变量,而往栈里压入数据和弹出数据都需要时间,另外递归中有可能有很多计算都是重复的,从而很大的影响性能,最重要的递归还会有更严重的问题:调用栈溢出,因为每个进程的栈的容量都是有限的,当递归的调用层级太多时,就会超出栈的容量,从而导致调用栈溢出。

===斐波拉契数列的本质就是后面的答案都是要依赖前面的答案的:f(n) = f(n-1)+f(n-2)

===所谓位运算的异或:就是相同为0,不同为1

===把一个整数减去1之后再和原来的整数做位与运算,得到的结果相当于是把整数的二进制表示中的最右边一个1变成0,很多二进制的问题都可以用这个思路解决

===由于计算机表示小数(包括float和double类型小数)都有误差,我们不能直接用==判断两个小数是否相等,如果两个小数的差的绝对值很小,我们就认为它们相等

===我们可以使用字符串或者数组来表示大数

===如果面试题是关于n位整数并且没有限定n的取值范围,或者是输入任意大小的整数,那么题目很有可能是需要考虑大数问题的,字符串是一个简单,有效的表示大数的方法

===当我们想删除一个链表的节点的时,并不一定要删除这个节点本身,可以先把下一个节点的内容复制出来覆盖被删除节点的内容,然后把下一个节点删除。其实关于链表的问题一定要考虑的几个问题:1,头指针为null 2,要操作的节点在开头 3,要操作的节点在结尾 4,整个链表就只有一个节点,这几个情况都是需要考虑的

===当我们用一个指针遍历不能解决问题的时候,可以尝试用两个指针来遍历链表,可以让其中一个指针遍历的速度快一些(比如一次在链表上走2步),或者让它先在链表上走若干步

===代码完整性:完成基本功能,考虑边界条件,做好错误处理

===代码鲁棒性:采取防御式编程,处理无效的输入

03_30:今天学了什么呢?

===不管是广度优先遍历一个有向图还是一棵树,都要用到队列。第一步我们把起始节点(对树而言是跟节点)放入队列中,接下来每次从队列的头部取出一个节点,遍历这个节点之后把从它能达到的节点(对树而言是子节点)都依次放入队列。我们重复这个遍历过程,直到队列中的节点全部被遍历为止

===如果面试题是要求处理一颗二叉树的遍历序列,我们可以先找到二叉树的根节点,再基于根节点把整颗树的遍历序列拆分为左子树对应的子序列和右子树对应的子序列,接下来再递归地处理这两个子序列

===如果面试题是按照一定要求摆放若干个数字,我们可以先求出这些数字的所有排列,然后再一一判断每个排列是不是满足题目给定的要求

===如果需要判断多个字符是不是在某个字符串里出现过或者统计多个字符在某个字符串中出现的次数,我们可以考虑基于数组创建一个简单的哈希表,这样可以用很小的空间消耗换来时间效率的提升

===单向链表和栈:从链表的头部开始遍历,然后把它的值压栈,然后遍历到最后的时候,其实在栈顶的数字就是链表的尾部的值;单向链表和栈的配合使用,我们可以找到链表的尾部

03_31:今天学了什么呢?

===异或运算的性质:任何一个数字异或它自己都等于0。也就是说我们从头到尾一次异或数组中的每一个数字,最终的结果刚好是那个只出现一次的数字,因为那些成对出现的数字全部在异或中抵消了

===抽象建模的第一步是选择合理的数据结构来表述问题,毕竟数据结构只有有限的几种而已;第二步就是分析模型中的内在规律,并用编程语言表述这种规律。

===不使用新的变量,交换两个变量的值;1,加减法  2,异或

相关文章

网友评论

      本文标题:学习日记_2022年03月

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