去年年底裸辞后,今年参加面试准备的面试题,有参考别人的,有真实面试到的。整理一下,留给需要的人。PS:没事不要裸辞,真的影响心态。
1、一个实例对象占用多大内存
占用了 16 字节,对象的成员变量只有一个 isa 指针,在 64 位系统上字节大小 8 字节,只使用了 8 字节。但是 alloc 底层实现做了处理,小于 16 的话,赋值 16 字节。
class_getInstanceSize 获取的实例大小是经过内存对齐的。是实例变量共占用的内存大小。
malloc_size 获取给实例对象分配的内存空间大小。OC 中给实例对象分配空间时, 是按照16, 32, 48, 64, 80, 96...按照16的倍数递增的。
2、OC 对象的分类
instance 对象在内存中存储的信息包括:
isa 指针
其他成员变量
class 对象在内存中存储的信息主要包括:
isa 指针
superclass 指针
类的属性信息(@property)
类的对象方法信息(instance method)
类的协议信息(protocol)
类的成员变量信息(ivar, 描述成员变量的类型和名字, 而不是如同实例一般具体的值)
...
类对象和元类对象在内存中的结构是一样的,class 对象中, 类方法信息为空, meta-class 方法中属性信息、对象方法信息、协议信息、成员变量信息为空。
3、验证NSObject的Meta-class对象中的superclass指向自身的Class对象(和问题2是连着的)
可以通过创建类 Person,定一个一个类方法 +test 不实现,在 NSObject 分类里定义一个 +test,实现一个 -test 方法,然后 [Person test] 会发现运行正常没有问题。这就正好验证了问题。
Person 通过 isa 找到元类,没发现方法,就通过元类 superClass 找到 NSObject 的元类,也没有发现方法,这个通过 superClass 找到类对象,发现方法。
4、KVO(以 Person 为例,属性 int age)
给对象添加观察者后,runtime 会生成一个新类NSKVONotifying_Person,isa 指针会指向新生成的类 NSKVONotifying_Person 类,他并重写属性方法。这个类有 setAge、class、_isKVOA、dealloc 方法,在setAge 里调用了 foundation NSSetIntValueAndNotify 在里头中执行了 willChangeValueforkey 调用了父类的 setAge didchangeValueForkey.
手动触发 kvo ,调用 willChangeValueforkey 和 didChagneValueforkey
5、KVC
使用 KVC 给一个对象赋值时, 会有以下方法和属性的调用顺序
查看 setKey: 方法是否存在, 如果存在直接调用, 如果不存在进入下一步
查看 _setKey: 方法是否存在, 如果存在直接调用, 如果不存在进入下一步
查看 +(BOOL)accessInstanceVariablesDirectly 方法的返回值, 默认返回 YES
YES: 可以访问成员变量, 进入下一步
NO: 不可以访问成员变量, 同时调用 -(void)setValue:(id)value forUndefinedKey:(NSString *)key 方法, 如果方法不存在会抛出异常
调用成员变量:_key, _isKey, key, isKey
调用顺序, 从左到右, 只有发现存在成员变量, 就不会在调用后续变量
如果没有成员变量, 会调用 -(void)setValue:(id)value forUndefinedKey:(NSString *)key 方法, 如果方法不存在会抛出异常
KVC 取值时, 方法和成员变量的调用顺序如下:
判断是否有这几个方法: getKey, key, isKey, _key
从左到右, 如果有方法, 直接调用, 取值结束,如果没有进入下一步
调用 +(BOOL)accessInstanceVariablesDirectly 查看是否可以访问成员变量. 默认YES
YES: 可以访问成员变量, 进入下一步
NO: 不可以访问成员变量, 判断是否实现 -(id)valueForUndefinedKey:(NSString *)key 方法, 实现时调用, 未实现报错
判断是否有这几个成员变量: _key, _isKey, key, isKey
从左到右, 如果有成员变量, 直接访问, 取值结束
如果没有这几个成员变量, 直接进入下一步
判断是否实现 -(id)valueForUndefinedKey:(NSString *)key 方法, 实现时调用, 未实现报错
使用 KVC 给属性赋值时会触发 KVO
6、category
Category 的实现原理
Category 编译之后的底层结构是 struct category_t, 里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候, runtime 会将 Category 的数据, 合并到类信息中(类对象、元类对象中)
Category 和 Class Extension 的区别是什么?
Class Extension 在编译的时候, 他的数据就已经包含在类信息中
Category 是在运行时, 才会将数据合并到类信息中
7、load 和 initialize
load
在程序启动的时候会通过函数指针的形式先加载所有类的 load 然后 category 的load 方法,category 会受编译顺序影响。父类先于子类调用 load,调用子类的load前先调用父类的load->因为底层源码中的逻辑就是通过递归将父类传入函数中。没有关系的也是根据编译顺序调用load,load 只会调用一次。
load 方法可以被子类继承吗?可以
initialize
通过底层源码可以知道,当对一个类发送消息时查找方法时,如果类没有初始化会先进行初始化,在进行初始化先会判断父类是否也是没有进行初始化(这块用的递归)会先调用父类的 initialize 然后子类,调用方式是消息发送。
如果子类(Student)没有实现 initialize 方法,但是直接在子类进行第一次消息发送时,其实会打印出两次父类(Person)的 initialize ,第一次的 Person (Test1) - initialize打印, 实际就是Person类初始化时调用的,在OC中, 使用消息机制调用类方法时, 调用顺序如下:
类对象通过 isa 找到元类对象, 在元类对象的方法列表中查找方法, 如果有就会调用
如果元类对象中没有调用的方法, 就会通过元类对象的superclass找到父类的元类对象, 接着父类的元类对象的方法列表中查找方法, 如果有就会调用
所以, Student 实际上在初始化时, 调用的是 objc_msgSend([Student class], @selector(initialize)), 但是因为 Student 并没有实现 initialize 方法, 所以 Student 调用了父类 Person 的 initialize 方法
8、关联对象
category 可以添加属性、协议、方法,但是不能添加成员变量,通过底层结构可以发现,并没有存储成员变量的地方。可以通过 runtime 来给 category 添加成员变量。通过对象关联的成员变量是在底层别统一管理的,不会合并到类对象的成员列表中。
关联对象底层是怎么实现的呢?核心有 4 个对象,分别是 AssociationsManager AssociationsHashMap、ObjectAssociationsMap、OjbectAssociation
9、block
block 底层结构是结构体,block 的代码块也封装成了函数。
block 捕获变量的不同,auto 变量(值捕获)(超过作用域就会销毁),如果用到了这个变量其实在结构体重会多生成一个捕获的成员变量。static 变量(指针捕获)捕获的是变量地址,block 结构中会多出一个指针类型的成员变量,如果在定义完block后修改了变量的值block 中的值也就改掉了。全局变量(不捕获)在block 中是不捕获的,直接使用,如果值修改了,block中使用时也就随着改了。总结就是会捕获局部变量(auto、static),auto是值引用,static是指针引用,不会捕获全局变量,直接使用。
block 有三种类型,分别为全局、栈、堆,处在不同的内存区域,数据区域,栈区,堆区。
MRC下 block 类型:
没有使用auto变量的就是 __NSGlobalBlock__ 类型,包括使用 block 参数、static变量和全局变量。
内部使用了 auto 类型变量的 block, 就是 __NSStackBlock__ 类型。
__NSStackBlock__ 类型的block调用copy后就是__NSMallocBlock__类型, 通过copy, 将block从栈区复制到了堆区(这也就是为什么以前的逻辑是需要将方法参数中的block先copy下)。
__NSGlobalBlock__ 类型的 block 调用 copy 后类型不变, 还是__NSGlobalBlock__ 类型(还在数据区)。
__NSMallocBlock__ 类型的 block 调用 copy 后类型不变, 还是__NSMallocBlock __类型(不会生成新的 block, 原有引用计数+1)。
栈中的 block 不会将对象类型的 auto 变量进行 retain 处理, 只有在将 block复制到堆上时, 才会将对象类型的 auto 变量进行 retain 处理(引用计数+1)
当堆中的 block 释放时, 会对其中的对象类型的 auto 变量进行 release 处理(引用计数-1), 如果此时对象类型的 auto 变量的引用计数为零, 就会被释放
ARC 下的 block:
在 arc 模式下,编译器会根据情况自动将栈区的 block 复制到堆区。
1、__NSStackBlock__ 类型的 block 做为函数返回值时, 会将返回的 block 复制到堆区。
2、将__NSStackBlock__ 类型的 block 赋值给 __strong 指针时, 会将 block复制到堆区。如果没有 __strong 指针引用的 stackblock,依然是 stackblock。
3、block 作为 Cocoa API 中方法名含有 usingBlock 的方法参数时, block 在堆区。
4、block 作为 GCD API 的方法参数时, block 在堆区。
不论在 ARC 还是 MRC下,栈中的 block 不会对捕获到的对象类型 auto 变量进行强引用(引用计数+1), 只会在 copy 到堆中时, 会对对象类型 auto 变量进行强引用
ARC 下, 被 __weak 修饰的对象类型 auto 变量, 在 block 复制到堆中时不会进行强引用。
__block 作用:
__block 可以解决 block 内部无法修改auto变量值的问题。
__block 不能修饰全局变量和静态变量(static), 只能修饰 auto 变量。
因为什么呢?block 捕获了使用 __block 修饰的 auto 变量后,在 block 结构体中多了一个结构体类型指针的成员变量。
mrc/arc 和 __block 修饰的变量情况很多,mrc 下不用进行强引用,arc 下会进行强引用(堆上的 block 情况下)。
要注意循环引用问题。
10、isa 指针
补充个只是就是位运算,|逻辑与,一个为 1,结果就为 1。逻辑&,都为 1 结果才为1.
在 arm64 架构之前,isa 就是一个普通指针,指向类对象和元类对象的地址。
arm64 位架构之后,对它进行了优化,变成了共用体(union)结构,存储了很多信息,使用位域存储更多信息。这也是进行内存优化需要知道的一点。
共用体结构中的结构体没有实际意义,只是用来描述内存中每一部分的作用。
1~61 位分别为:
nonpointer:0代表就是普通指针,存储着类对象和元类对象的内存地址信息,1代表是经过优化的;
has_assoc:是否设置过关联对象,没有的话释放的更快
has_cxx_dtor:是否有c++的析构函数,没有的话释放的更快
shiftcls:存储的类对象和元类对象的内存地址信息
deallocing:对象是否正在释放
magic:用于在调试时分辨对象是否未完成初始化
weakly_referenced:是否有弱引用指针指向对象,没有释放更快
extra_rc:存储这引用计数-1的值
has_sidetable:是否不足以存储引用技术,就使用sidetable存储
11、Class结构
Class 结构底层就是结构体,它的成员变量好多也是结构体,它有成员变量 isa,superClass,bits,cache。bits 在编译的时候指向 class_ro_t 然后在加载的时候指向了 class_rw_t,class_rt_t 的成员变量指向了 class_ro_t,class_rw_t 存储着属性信息、方法信息、协议信息,class_ro_t 存储着name、成员变量信息,instanceSize 等。
方法列表存储的就是method_t 也是结构体,method 结构体的成员变量有 SEL name(方法名字),types(编码,返回值类型,参数类型),IMP imp(函数指针),
cache 是一个结构体,成员变量有 buckets是个散列表,bucket 是一个散列表,成员变量有 key(存储的是 SEL),value(IMP),放进 cache 的调用过的方法其实都是放到了buckets 里了,根据 SEL&mask获取到数字,作为索引。在扩完容后,清空 cache(和散列表稍微有点不同)。
12、消息发送,动态方法解析,消息转发
这块稍微快点,消息发送其实就是方法查找的过程,先 cache 里找,没有就通过 bit->rw,如果methods 是拍好序的就通过二分法查找,没排序就遍历查找,依次找到基类,没有的就判断消息接受者是否存在,存在就走动态解析(如果没有经过动态解析的话),会区分是元类对象还是类对象来分别调用 instance 和 class,没有的话通过 resolveInstanceMethod/resolveClassMethod 来动态添加方法,然后标记为动态解析了,再重新走消息发送。如果动态添加方法就走消息转发,看 forwardingTargetforseleto 是否有返回值,不是nil 的话调用 objc_msgsend,为nil就走 mehtodsignnatureForslet ,返回值为nil,直接调用 dose not.. 崩溃,不为nil 调用 forwardInvocaiton
13、super
编译之后看源码,在子类中重写父类方法,调用 super test,其实就是 objc_msgSendSuper,还是给 self(消息接受者) 发的消息,只不过是从父类还是方法查找的。
14、atomic
底层实现中 reallySetProperty 方法中判断了 atomic,atomic 下加锁->操作->解锁。atomic 底层用的是 os_unfair_lock(这个锁效率几乎最高)
15、文件的读写安全
其实还是线程安全问题,这个场景就是读写锁解决。在iOS上实现读写锁的方式有两种:
1、使用 dispatch_barrier_async(异步栅栏函数)
2、使用 pthread_rwlock(读写锁)
16、内存管理-定时器
CADisplayLink:使用频率和屏幕刷新频率是一样的,都是 60FPS。
常见问题就是循环引用的问题,viewController 中持有 displayLink,displayLink 的 target 持有 viewController,相互强持有。解决办法可以使用中介者来解除掉这个持有环。使用 proxy(不用继承 NSProxy) 实现 forwardingTargetForSelector即可以解决。
NSProxy没有任何的父类, 与NSObject一样遵守<NSObject>协议
NSProxy是用来做消息转发的类, 如果自己没有实现目标方法, 那么就会立刻进入消息转发。实现 methodSignatureForSelector forwardInvocation 即可。
17、 Tagged Pointer
64bit后,主要用来优化NSNumber、NSDate、NSString小对象数据的。不需要动态分配内存,直接通过指针就可以知道数据类型、值。为什么多线程执行不会崩溃,因为在底层先判断是否是 isTaggedPointer,如果是就不执行 copy 和 release 操作了。
18、weak 原理
dealloc 底层实现逻辑,会看到判断是否是 isTaggedPointer,然后再判断是否 isa 优化过,是就判断 has_assoc/has_cxx_dtor/weakly_reference/has_sidetable_rc 都没有直接释放。有一个不满足就调用 object_disponse->destructInstance,判断有没有 cxxdtor 和关联对象,有就释放掉,然后进入 clearDeallocating,没有优化过就清掉 sidetable 对应的 weak 指针为 nil,有优化过就看是否有弱引用或使用了sidetable,就进入 clearDeallocating_slow,调用weak_clear_no_lock清空 weak 为nil。
19、autoreleasePool
自动释放池本质是一个结构体。
20、通知的实现原理
iOS9 之前添加观察者是对它进行的 unsafe_unretained 的引用,在观察者销毁后,容易出现 exc_bad_access 问题,所以在观察者销毁之前要移除观察者。在iOS9之后呢,改成了对观察者的弱引用了,所以理论上不移除也是没有关系的,但是最好按照通用的方式进行移除观察者。
通过通知中心添加观察者之后,会创建一个 observation(结构体) 对象,存储了观察者、SEL等。NSNotification 维护了 GSIMapTable(映射表)表的结构,用来存储 observation,分别是 nameless、named、cache,nameless 用来存储没有名字的observation,named 存储传入了名字的通知,cache 用于快速查找。nameless、named 同为 hash 表。结构如下:
nameless 表中:
GSIMapTable 结构如下
object:observation
object:observation
named 表中:
GSIMapTable 结构如下
name:mapTable
name:mapTable
mapTable 结构如下:
object:observation
object:observation
21、OC 运行时的动态体现
编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
动态类型:在运行时才确定对象的真实类型
动态绑定:运行时才动态的添加函数调用,在运行时才决定要调用什么方法,需要传什么参数进去
动态加载:在运行的时候加载可执行代码和资源,比如Retina设备上加载@2x的图片还有整合一些分类
22、runtime 的作用
1、方法交换,比如埋点的时候将 viewDidLoad/viewWillAppear 等方法交换;还有就是检查 NSArray/NSDictionary/NSString 在插入对象时进行对象的检查。
2、做动态消息转发的时候给一个类动态的添加方法
3、字典转模型等,动态的获取到属性
method swillzling 的注意点:
1、名字尽量添加前缀,不要和其他方法重名
2、交换方法应该在 +load 方法中。
3、交换方法应该放在 dispatch_once 中保证只交换一次,防止手动调用 +load 再将方法交换回来
4、交换的方法应该调用原先的类,保证不影响其它逻辑。
21、forwardingTargetForSelector 同为消息转发,但在实践层面上有什么区别
forwardingTargetForSelector 仅支持一个对象的返回,也就是说消息只能被转发给一个对象
forwardInvocation 可以将消息同时转发给任意多个对象
23、runloop 的作用
1、线程保活,使用 run 就会一直存在内存中,尽量使用 run after 方法
2、处理屏幕刷新,识别手势,事件传递
3、autoreleasePool
4、网络请求
5、定时器nstimer、perform selector
6、可以检测主线程的卡顿检测
runloop 中添加观察者,观察runloop 的几个状态的切换,beforewaiting 和
24、屏幕成像原理
cpu 和 gpu 协作将视图绘制好放到缓冲区,视频控制器从缓冲区获取并显示到屏幕上。
25、什么情况下会调用layoutSubviews
调用setNeedsLayout layoutIfNeed
执行 addsubview 时
视图 frame 变化时
横竖屏
滚动会触发 layoutSubviews
26、什么情况下会调用 drawRect
调用 setNeedDisplay 时
sizetofit 调用后
contentMode 设置为 redraw 时,每次 frame 改变时会调用 drawRect
controller 的 viewDidLoad 调用后,view 在显示前会调用 drawRect
27、wkwebview cookie
1、在 loadRequest 放到 request header 中(解决首个请求 cookie 问题)
2、通过 document.cookie 中设置 cookie(解决同域后续的请求 iframe、ajax)
3、302 重定向的cookie问题可以在 decidePolicy 的回调方法中拦截 302 请求,copy request,设置 cookie。(依然解决不了跨域请求的 cookie)
28、优化:列表优化、冷启动优化、内存优化、安装包瘦身
列表:1、
内存:使用检查工具,看是否有内存泄漏的,instrument-?leaks,或者现成的集成工具。
耗电:是否有大量的 I/O 操作、频繁的网络请求,实时的定位,图像渲染,CPU、GPU 的使用情况。
网络优化:使用合适的传输格式,压缩数据;使用缓存
启动优化:
29、实例方法和类方法的区别
1、方法的存储结构不一样,实例方法存储在类对象结构中,类方法存储在元类结构中;
2、实例方法可以访问成员变量;
3、类方法中不能调用实例方法,想要调用的话需要传入实例;
4、实例方法可以和类方法重名(原因是1)
30、如何检测野指针?
31、+class -class
+class
return self;
-class
return object_getClass(self)
object_getClass(类对象),则返回的是元类
32、使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
不需要,被关联的对象的生命周期内要比对象本身释放晚很多, 它们会在被 NSObject -dealloc 调用的 object_dispose() 方法中释放。
引申 能否向编译后得到的类中增加实例变量, 能否向运行时创建的类中添加实力变量?
不能再编译后得到的类中增加实例变量。因为编译后的类已经注册在runtime中, 类结构体中 objc_ivar_list 实例变量的链表和objc_ivar_list 实例变量的内存大小已经确定,所以不能向存在的类中添加实例变量
能在运行时创建的类中添加实力变量。调用class_addIvar 函数
33、单例怎么销毁
GCD dispathc_once 的方式
将 static dispatch_once_t token 拿到方法体外,将 once_t 置为0。
dispatch_once_t 的工作原理是 static 修饰会默认将其初始化为0,当且仅当为0 的时候 once 的 block 才会执行,如果执行了这个函数,这个dispatch_once_t 静态变成 -1了,就永远不会执行了。
不适用 GCD 实现单例,可以使用 @synchronized
+(instancetype)shareInstance {
static id instance;
@synchronized(self) {
instance = [self alloc] init];
}
return instance;
}
34、CALayer 有三个 layer tree
分别是 presentTree(动画数)modelTree(模型树)renderTree(渲染数)
在动画的其实是 presentLayer 属性值,最终显示在屏幕上的是 modelTree 的属性值。
35、static有什么作用?
static关键字可以修饰函数和变量,作用如下:
隐藏
通过static修饰的函数或者变量,在该文件中,所有位于这条语句之后的函数都可以访问,而其他文件中的方法和函数则不行
静态变量
类方法不可以访问实例变量(函数),通过static修饰的实例变量(函数),可以被类 方法访问;
持久
static修饰的变量,能且只能被初始化一次;
默认初始化
static修饰的变量,默认初始化为0;
36、HTTP 的 get/post 区别
get在特定浏览器和服务器上对 url 长度有限制,理论上是没有限制的。
get 将参数拼接到url 后面,不安全。在tcp建立连接后直接将请求头和数据一块发送给服务器,比post快。
post 将参数放到请求体中,在三次握手的第三次先将请求头发给服务器,响应 100 continue 后继续把数据发送给服务器,然后200ok。
37、为什么 gcd 生成的 timer 精确度高?
38、为什么刷新UI要在主线程操作
因为 UIKit 不是线程安全的,多个线程同时操作 UI 容易造成影响。
为什么不设计成线程安全的呢,线程安全涉及到了锁,而使用锁就会消耗性能,影响处理速度和渲染速度,在移动设备上CPU、内存等资源是比较紧缺的,所以操作 UI 都在主线程。平时通过 @property 生成属性的修饰符还是 nonatomic 的。更不用说 UI 出现频率这么高的角色了。
39、简单解释一下渲染机制
iOS 的渲染核心是 Core Animation,渲染层次依次是:图层数->呈现树->渲染树;
三个阶段:
CPU 阶段(进行 frame 布局,准备 view 和 layer 的层级关系)
OpenGL ES 阶段,iOS8 后变成了 Metal(渲染服务把前一阶段提供的图层上色,生成各种帧)
GPU 阶段(把上面的操作进行一系列的操作,最终显示在屏幕上)
详细一点就是:
CPU 进行 frame 布局,准备视图和图层之间的层级关系,CPU 会将处理视图和图层的层级关系打包,通过 IPC(进程间的通信)提交给渲染服务,渲染服务首先将图层交给 OpenGL ES,进行纹理生成和着色,生成前后帧缓存,再根据硬件的信号(VSync 或者 CADisplayLink 的)的通知进行前后帧缓存的切换,最后将需要的后帧缓存交给 GPU,进行采集图片和形状,进行交换,应用纹理混合,最终显示在屏幕上。
40、class_copyIvarList 与 class_copyPropertyList 的区别?
1.class_copyIvarList:能够获取.h和.m中的所有属性以及大括号中声明的变量,获取的属性名称有下划线(大括号中的除外)。
2.class_copyPropertyList:只能获取由property声明的属性,包括.m中的,获取的属性名称不带下划线。
40、多线程这些在哪里用过(得准备几个)
41、关于数据结构的几个问题
基本的数据结构得掌握了,如:数组、链表、散列表、堆、栈、树
1、后序遍历二叉树
2、二叉树的高度
3、反转链表
4、快排
5、有 n 阶台阶,一次可以迈一阶也可以迈两阶,有多少种迈法
6、有 4 只老鼠,15 个瓶子,其中有一个瓶子中装的是毒药,中毒后一天才毒发,怎么能在一天内知道那个瓶子是毒药呢
42、常见排序算法,排序算法稳定的意思,快排的复杂度什么时候退化,基本有序用什么
Z字遍历二叉树,归并排序
上台阶,加了个条件,这次上两级,下次就只能上一级
43、实现string类,实现构造,析构,里面加一个kmp(这个后面查询)
44、介绍智能指针,智能指针保存引用计数的变量存在哪里,引用计数是否线程安全(这个后面查询)
45、const关键字的作用
修饰变量,使变量只读,不可修改。
46、指针和引用的区别?引用能否为空
1、从内存上来说的话,系统会为指针分配内存空间,而引用于绑定的对象共享内存空间,系统不为引用变量分配空间。
2、指针初始化后可以改变指向的对象,引用在定义时就要初始化,且初始化后无法再绑定对象
3、引用访问对象是直接访问,指针访问是间接访问。
47、DNS解析,递归与迭代的区别
48、平时怎么学习技术的,看过哪些书,有过哪些输出
49、有哪些缺点?以及为什么离职
50、为什么要内存对齐,内存对齐的规则
内存对齐有利于加快内存读取速度,是一种用空间换时间来提升读取内存性能的提高。
规则:待补充
51、进程内存模型
52、进程间通信方式
53、虚拟内存,为什么要有虚拟内存,虚拟内存如何映射到物理内存
54、线程共享哪些内存空间
55、https 客户端验证公钥的方法
56、代码里面的内存泄漏怎么解决,智能指针的引用计数怎么实现,那些成员函数会影响到引用计数
57、代码里面有无线程安全问题,线程安全问题的是否会导致程序崩溃,为什么
58、lambda 表达式,它应用表达式外变量的方式和区别
59、decltype 的作用,他和 auto 有什么不同