在过来的一年很多人不满于公司没有福利、人际关系不好相处、没有发展前途的境遇等等,想着在开年来换一份工作来从新开始本人,那么 你 筹备好了吗?
上面是自己整顿的一份面试资料,本想本人用的,然而公司忽然给了我个惊喜,涨工资了!!!
- UIView和CALayer是什么关系
* UIView继承自UIResponder类,能够响应事件* CALayer间接继承自NSObject类,不能够响应事件* UIView是CALayer的delegate(CALayerDelegate)* UIView次要处理事件,CALayer负责绘制* 每个UIView外部都有一个CALayer在背地提供内容的绘制和显示,并且UIView的尺寸款式都由外部的Layer所提供。两者都有树状层级构造,Layer外部有SubLayers,View外部有SubViews,然而Layer比View多了个AnchorPoint
- NSCache和NSMutableDictionary的相同点与区别
相同点:
NSCache和NSMutableDictionary性能用法根本是雷同的
区别:
NSCache是线程平安的,NSMutableDictionary线程不平安,Mutable开发的类个别都是线程不平安的
当内存不足时NSCache会主动开释内存(所以从缓存中取数据的时候总要判断是否为空)
NSCache能够指定缓存的限额,当缓存超出限额主动开释内存
NSCache的Key只是对对象进行了Strong援用,而非拷贝,所以不须要实现NSCopying协定
- atomic的实现机制;为什么不能保障相对的线程平安(最好能够联合场景来说)
- atomic会对属性的setter/getter办法进行加锁,这仅仅只能保障在操作setter/getter办法是平安的。不能保障其余线程的平安
- 例如:线程1调用了某一属性的setter办法并进行到了一半,线程2调用其getter办法,那么会执行完setter操作后,再执行getter操作,线程2会获取到线程1setter后的残缺的值;当几个线程同时调用同一属性的setter、getter办法时,会获取到一个残缺的值,但获取到的值不可控
- iOS 中内省的几个办法
对象在运行时获取其类型的能力称为内省。内省能够有多种办法实现
OC运行时内省的4个办法:
- 判断对象类型:
-(BOOL) isKindOfClass: // 判断是否是这个类或者这个类的子类的实例-(BOOL) isMemberOfClass: // 判断是否是这个类的实例
- 判断对象/类是否有这个办法
-(BOOL) respondsToSelector: // 判断实例是否有这样办法+(BOOL) instancesRespondToSelector: // 判断类是否有这个办法
- objc在向一个对象发送音讯时,产生了什么
依据对象的isa指针找到该对象所属的类,去objc的对应的类中找办法
1.首先,在相应操作的对象中的缓存办法列表中找调用的办法,如果找到,转向相应实现并执行
2.如果没找到,在相应操作的对象中的办法列表中找调用的办法,如果找到,转向相应实现执行
3.如果没找到,去父类指针所指向的对象中执行1,2.
4.以此类推,如果始终到根类还没找到,转向拦挡调用,走音讯转发机制
5.如果没有重写拦挡调用的办法,程序报错作为一个开发者,有一个学习的气氛跟一个交换圈子特地重要,这是一个我的iOS交换群:642363427不论你是小白还是大牛欢送入驻 ,分享BAT,阿里面试题、面试教训,探讨技术, 大家一起交流学习成长!如有任何问题或倡议,请进群交换,谢谢。
- 你是否接触过OC中的反射机制?简略聊一下概念和应用
- class反射
- 通过类名的字符串模式实例化对象
Class class = NSClassFromString(@"student"); Student *stu = [[class alloc] init];
- 将类名变为字符串
Class class = [Student class];NSString *className = NSStringFromClass(class);
- SEL的反射
- 通过办法的字符串模式实例化办法
SEL selector = NSSelectorFromString(@"setName");[stu performSelector:selector withObject:@"Mike"];
- 将办法变成字符串
NSStringFromSelector(@selector(setName:));
- 这个写法会出什么问题@property (nonatomic, copy) NSMutableArray *arr;
增加,删除,批改数组内元素的时候,程序会因为找不到对应的办法而解体。起因:是因为copy就是复制一个不可变NSArray的对象,不能对NSArray对象进行增加/批改
- 如何让本人的类用copy修饰符
若想令本人所写的对象具备拷贝性能,则需实现NSCopying协定。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying与NSMutableCopying协定。
具体步骤:
1.需申明该类听从NSCopying协定
2.实现NSCopying协定的办法,具体区别戳这里
- NSCopying协定办法为:
- (id)copyWithZone:(NSZone *)zone { MyObject *copy = [[[self class] allocWithZone: zone] init]; copy.username = self.username; return copy;}
- 为什么assign不能用于润饰对象
首先咱们须要明确,对象的内存个别被调配到堆上,根本数据类型和oc数据类型的内存个别被调配在栈上
如果用assign润饰对象,当对象被开释后,指针的地址还是存在的,也就是说指针并没有被置为nil,从而造成了野指针。因为对象是调配在堆上的,堆上的内存由程序员调配开释。而因为指针没有被置为nil,如果后续的内存调配中,刚好调配到了这块内存,就会造成解体
而assign润饰根本数据类型或oc数据类型,因为根本数据类型是调配在栈上的,由零碎调配和开释,所以不会造成野指针
- 请写出以下代码输入
int a[5] = {1, 2, 3, 4, 5};int *ptr = (int *)(&a + 1);printf("%d, %d", *(a + 1), *(ptr + 1));
参考答案:2,随机值
剖析:
a代表有5个元素的数组的首地址,a[5]的元素别离是1,2,3,4,5。接下来,a + 1示意数据首地址加1,那么就是a[1],也就是对应于值为2,然而,这里是&a + 1,因为a代表的是整个数组,它的空间大小为5 * sizeof(int),因而&a + 1就是a + 5。a是个常量指针,指向以后数组的首地址,指针+1就是挪动sizeof(int)个字节
因而,ptr是指向int 类型的指针,而ptr指向的就是a + 5,那么ptr + 1也相当于a + 6,所以最初的(ptr + 1)就是一个随机值了。而*(ptr – 1)就相当于a + 4,对应的值就是5
- 一个view曾经初始化结束,view下面增加了n个button(可能应用循环创立),除用view的tag之外,还能够采纳什么方法来找到本人想要的button来批改Button的值
第一种:如果是点击某个按钮后,才会刷新它的值,其它不必批改,那么不必援用任何按钮,间接在回调时,就曾经将接管响应的按钮给传过来了,间接通过它批改即可
第二种:点击某个按钮后,所有与之同类型的按钮都要批改值,那么能够通过在创立按钮时将按钮存入到数组中,在须要的时候遍历查找
- UIViewController的viewDidUnload、viewDidLoad和loadView别离什么时候调用?UIView的drawRect和layoutSubviews别离起什么作用
第一个问题:
在控制器被销毁前会调用viewDidUnload
(MRC
下才会调用)
在控制器没有任何view
时,会调用loadView
在view
加载实现时,会调用viewDidLoad
第二个问题:
在调用setNeedsDisplay
后,会调用drawRect
办法,咱们通过在此办法中能够获取到context
(设置上下文),就能够实现绘图
在调用setNeedsLayout
后,会调用layoutSubviews
办法,咱们能够通过在此办法去调整UI。当然能引起layoutSubviews
调用的形式有很多种的,比方增加子视图、滚动scrollview
、批改视图的frame
等
- 主动开释池工作原理
主动开释池是NSAutorelease
类的一个实例,当向一个对象发送autorelease
音讯时,该对象会主动入池,待池销毁时,将会向池中所有对象发送一条release
音讯,开释对象[pool release]、[pool drain]
示意的是池自身不会销毁,而是池子中的长期对象都被发送release
,从而将对象销毁
- 苹果是如何实现autoreleasepool的
autoreleasepool
是由AutoreleasePoolPage
以双向链表的形式实现的,次要通过下列三个函数实现:
- 由
objc_autoreleasePoolPush
作为主动开释池作用域的第一个函数- 应用
objc_autorelease
将对象退出主动开释池- 由
objc_autoreleasePoolPop
作为主动开释池作用域的最初一个函数
- autorelease的对象何时被开释
RunLoop在每个事件循环完结后会去主动开释池将所有主动开释对象的援用计数减一,若援用计数变成了0,则会将对象真正销毁掉,回收内存。
在没有手动增加Autorelease Pool的状况下,autorelease的对象是在每个事件循环完结后,主动开释池才会对所有主动开释的对象的援用计数减一,若援用计数变成了0,则开释对象,回收内存。因而,若想要早一点开释掉autorelease对象,那么咱们能够在对象外加一个主动开释池。比方,在循环解决数据时,长期变量要疾速开释,就应该采纳这种形式:
// 通过alloc创立的对象,间接退出@autoreleasepool没有作用,需在创建对象前面显式增加autorelease// 通过类办法创立的对象不须要显式增加autorelease,起因是类办法创立的对象零碎会主动增加autoreleasefor (int i = 0; i < 1000000; i++) { @autoreleasepool { NSString *str = @"Abc"; str = [str lowercaseString]; str = [str stringByAppendingString:@"xyz"]; NSLog(@"%@", str); } // 出了这里,就会去遍历该主动开释池了}
- 简述内存治理根本准则
OC内存治理遵循谁创立,谁开释,谁援用,谁治理
的机制,当应用alloc、copy(mutableCopy)或者retian
一个对象时,你就有任务向它发送一条release或者autorelease
音讯开释该对象,其余办法创立的对象,不须要由你来治理内存,当对象援用计数为0时,零碎将开释该对象,这是OC的手动管理机制(MRC
)
向一个对象发送一条autorelease
音讯,这个对象并不会立刻销毁,而是将这个对象放入了主动开释池,待池子开释时,它会向池中每一个对象发送一条release
音讯,以此来开释对象
向一个对象发送release
音讯,并不意味着这个对象被销毁了,而是当这个对象的援用计数为0时,零碎才会调用dealloc
办法开释该对象和对象自身所领有的实例
- sizeof关键字
sizeof
是在编译阶段解决,且不能被编译为机器码。sizeof
的后果等于对象或类型所占的内存字节数。sizeof
的返回值类型为size_t
变量:int a; sizeof(a)
为4;
指针:int *p; sizeof(p)
为4;
数组:int b[10]; sizeof(b)
为数组的大小4*10;int c[0]; sizeof(c)
等于0sizeof(void)
等于1sizeof(void *)
等于4
- 什么是离屏渲染?什么状况下会触发?离屏渲染耗费性能的起因
离屏渲染就是在以后屏幕缓冲区以外,新开拓一个缓冲区进行操作
离屏渲染触发的场景有以下:
- 圆角(同时设置
layer.masksToBounds = YES、layer.cornerRadius
大于0)- 图层蒙版
- 暗影,
layer.shadowXXX
,如果设置了layer.shadowPath
就不会产生离屏渲染- 遮罩,
layer.mask
- 光栅化,
layer.shouldRasterize = YES
离屏渲染耗费性能的起因
须要创立新的缓冲区,离屏渲染的整个过程,须要屡次切换上下文环境,先是从以后屏幕(On-Screen)切换到离屏(Off-Screen)等到离屏渲染完结当前,将离屏缓冲区的渲染结果显示到屏幕上,又须要将上下文环境从离屏切换到以后屏幕
- ARC 下,不显式指定任何属性关键字时,默认的关键字都有哪些
根本数据类型默认关键字是:atomic, readwrite, assign
一般Objective-C
对象默认关键字是:atomic, readwrite, strong
- OC中的类办法和实例办法有什么本质区别和分割
类办法:
- 类办法是属于类对象的
- 类办法只能通过类对象调用
- 类办法中的 self 是类对象
- 类办法能够调用其余的类办法
- 类办法中不能拜访成员变量
- 类办法中不能间接调用对象办法
实例办法:
- 实例办法是属于实例对象的
- 实例办法只能通过实例对象调用
- 实例办法中的 self 是实例对象
- 实例办法中能够拜访成员变量
- 实例办法中间接调用实例办法
- 实例办法中也能够调用类办法(通过类名)
- 是否向编译后失去的类中减少实例变量?是否向运行时创立的类中增加实例变量?为什么?
- 不能向编译后失去的类中减少实例变量
- 能向运行时创立的类中增加实例变量
- 因为编译后的类曾经注册在
runtime
中,类构造体中的objc_ivar_list
实例变量的链表和instance_size
实例变量的内存大小曾经确定,同时runtime
会调用class_setIvarLayout
或class_setWeakIvarLayout
来解决strong weak
援用,所以不能向存在的类中增加实例变量
运行时创立的类是能够增加实例变量,调用class_addIvar
函数。然而得在调用objc_allocateClassPair
之后,objc_registerClassPair
之前,起因同上
- runtime如何通过selector找到对应的IMP地址(别离思考实例办法和类办法)Selector、Method 和 IMP的有什么区别与分割
对于实例办法,每个实例的isa
指针指向着对应类对象,而每一个类对象中都有一个对象办法列表。对于类办法,每个类对象的isa
指针都指向着对应的元类对象,而每一个元类对象中都有一个类办法列表。办法列表中记录着办法的名称,办法实现,以及参数类型,其实selector
实质就是办法名称,通过这个办法名称就能够在办法列表中找到对应的办法实现Selector、Method 和 IMP
的关系能够这样形容:在运行期散发音讯,办法列表中的每一个实体都是一个办法(Method
)它的名字叫做选择器(SEL
)对应着一种办法实现(IMP
)
- objc_msgSend、_objc_msgForward都是做什么的?OC 中的音讯调用流程是怎么的
objc_msgSend
是用来做音讯发送的。在OC
中,对办法的调用都会被转换成外部的音讯发送执行_objc_msgForward
是IMP
类型(函数指针)用于音讯转发的:当向一个对象发送一条音讯,但它并没有实现的时候,_objc_msgForward
会尝试做音讯转发- 在音讯调用的过程中,
objc_msgSend
的动作比拟清晰:首先在Class
中的缓存查找IMP
(没缓存则初始化缓存)如果没找到,则向父类的Class
查找。如果始终查找到根类仍旧没有实现,则用_objc_msgForward
函数指针代替IMP
。最初,执行这个IMP
。当调用一个NSObject
对象不存在的办法时,并不会马上抛出异样,而是会通过多层转发,层层调用对象的-resolveInstanceMethod:、-forwardingTargetForSelector:、-methodSignatureForSelector:、-forwardInvocation:
等办法。其中最初-forwardInvocation:
是会有一个NSInvocation
对象,这个NSInvocation
对象保留了这个办法调用的所有信息,包含Selector名,参数和返回值类型
,能够从这个NSInvocation
对象里拿到调用的所有参数值
- class办法和objc_getClass办法有什么区别
object_getClass(obj)
返回的是obj
中的isa
指针,即指向类对象的指针;而[obj class]
则分两种状况:一是当obj
为实例对象时,[obj class]
中class
是实例办法,返回的是obj
对象中的isa
指针;二是当obj
为类对象(包含元类和根类以及根元类)时,调用的是类办法,返回的后果为其自身
- OC中向一个nil对象发送音讯将会产生什么
在OC
中向nil
发送音讯是齐全无效的,只是在运行时不会有任何作用;向一个nil
对象发送音讯,首先在寻找对象的isa
指针时就是0地址
返回了,所以不会呈现任何谬误,也不会解体
- _objc_msgForward函数是做什么的?间接调用它将会产生什么
_objc_msgForward
是一个函数指针(和IMP
的类型一样)用于音讯转发;当向一个对象发送一条音讯,但它并没有实现的时候,_objc_msgForward
会尝试做音讯转发objc_msgSend
在消息传递
中的作用。在消息传递
过程中,objc_msgSend
的动作比拟清晰:首先在Class
中的缓存查找IMP
(没有缓存则初始化缓存
)如果没找到,则向父类的Class
查找。如果始终查找到根类
仍旧没有实现,则用_objc_msgForward
函数指针代替IMP
,最初执行这个IMP
一旦调用了_objc_msgForward
,将跳过查找IMP
的过程,间接触发音讯转发
,如果调用了_objc_msgForward
,即便这个对象的确曾经实现了这个办法,你也会通知objc_msgSend
,我没有在这个对象里找到这个办法的实现,如果用不好会间接导致程序Crash
- 什么时候会报unrecognized selector的异样
- 当调用该对象上某个办法,而该对象上没有实现这个办法的时候。能够通过音讯转发进行解决,流程见下图
- OC在向一个对象发送音讯时,runtime库会依据对象的isa指针找到该对象理论所属的类,而后在该类中的办法列表以及其父类办法列表中寻找办法运行,如果在最顶层的父类中仍然找不到相应的办法时,程序在运行时会挂掉并抛出异样unrecognized selector sent to XXX然而在这之前,OC的运行时会给出三次援救程序解体的机会
- Method resolution(音讯动静解析)
OC运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。如果你增加了函数,那运行时零碎就会重新启动一次音讯发送的过程,否则,运行时就会移到下一步,音讯转发(Message Forwarding)
// 重写 resolveInstanceMethod: 增加对象办法实现+ (BOOL)resolveInstanceMethod:(SEL)sel { // 如果是执行 run 函数,就动静解析,指定新的 IMP if (sel == NSSelectorFromString(@"run:")) { // class: 给哪个类增加办法 // SEL: 增加哪个办法 // IMP: 办法实现 => 函数 => 函数入口 => 函数名 // type: 办法类型:void用v来示意,id参数用@来示意,SEL用:来示意 class_addMethod(self, sel, (IMP)runMethod, "v@:@"); return YES; } return [super resolveInstanceMethod:sel];}//新的 run 函数void runMethod(id self, SEL _cmd, NSNumber *meter) { NSLog(@"跑了%@", meter);}
- Fast forwarding(音讯接受者重定向)
如果指标对象实现了-forwardingTargetForSelector:,Runtime这时就会调用这个办法,给你把这个音讯转发给其余对象的机会。只有这个办法返回的不是nil和self,整个音讯发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会持续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创立任何新的对象,但下一步转发会创立一个NSInvocation对象,所以绝对更快点
// 音讯接受者重定向- (id)forwardingTargetForSelector:(SEL)aSelector{ if (aSelector == @selector(run:)) { return [[Person alloc] init]; // 返回 Person 对象,让 Person 对象接管这个音讯 } return [super forwardingTargetForSelector:aSelector];}
- Normal forwarding(音讯重定向)
这一步是Runtime最初一次给你解救的机会。首先它会发送-methodSignatureForSelector:音讯取得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会收回-doesNotRecognizeSelector:音讯,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创立一个NSInvocation对象并发送-forwardInvocation:音讯给指标对象
// 获取函数的参数和返回值类型,返回签名- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if ([NSStringFromSelector(aSelector) isEqualToString:@"run:"]) { return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; } return [super methodSignatureForSelector:aSelector];}// 音讯重定向- (void)forwardInvocation:(NSInvocation *)anInvocation { // 从 anInvocation 中获取音讯 SEL sel = anInvocation.selector; if (sel == NSSelectorFromString(@"run:")) { // 1\. 指定以后类的一个办法作为IMP // anInvocation.selector = @selector(readBook:); // [anInvocation invoke]; // 2\. 指定其余类来执行这个IMP Person *p = [[Person alloc] init]; // 判断 Person 对象办法是否能够响应 sel if([p respondsToSelector:sel]) { // 若能够响应,则将音讯转发给其余对象解决 [anInvocation invokeWithTarget:p]; } else { // 若依然无奈响应,则报错:找不到响应办法 [self doesNotRecognizeSelector:sel]; } }else{ [super forwardInvocation:anInvocation]; }}- (void)doesNotRecognizeSelector:(SEL)aSelector { [super doesNotRecognizeSelector:aSelector];}
既然-forwardingTargetForSelector:和-forwardInvocation:都能够将音讯转发给其余对象解决,那么两者的区别在哪?
区别就在于-forwardingTargetForSelector:只能将音讯转发给一个对象。而-forwardInvocation:能够把音讯存储,在你感觉适合的机会转发进来,或者不解决这个音讯。批改音讯的target,selector,参数等。将音讯转发给多个对象
- iOS layoutSubviews什么时候会被调用
> * `init`办法不会调用`layoutSubviews`,然而是用`initWithFrame`进行初始化时,当`rect`的值不为`CGRectZero`时,会触发> * `addSubview`会触发`layoutSubviews`办法> * `setFrame`只有当设置的`frame`的参数的`size`与原来的`size`不同,才会触发其`view`的`layoutSubviews`办法> * 滑动`UIScrollView`会调用`scrollview`及`scrollview`上的`view`的`layoutSubviews`办法> * 旋转设施只会调用`VC`的`view`的`layoutSubviews`办法> * 间接调用`[self setNeedsLayout];`(这个在下面苹果官网文档里有阐明)> `-layoutSubviews`办法:这个办法默认没有做任何事件,须要子类进行重写> `-setNeedsLayout`办法:标记为须要从新布局,异步调用`layoutIfNeeded`刷新布局,不立刻刷新,但`layoutSubviews`肯定会被调用> `-layoutIfNeeded`办法:如果有须要刷新的标记,立刻调用`layoutSubviews`进行布局(如果没有标记,不会调用`layoutSubviews`)> 如果要立刻刷新,要先调用`[view setNeedsLayout]`,把标记设为须要布局,而后马上调用`[view layoutIfNeeded]`,实现布局> 在视图第一次显示之前,标记总是`须要刷新`的,能够间接调用`[view layoutIfNeeded]`
- 上面代码会产生什么问题
@property (nonatomic, strong) NSString *str;dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);for (int i = 0; i < 1000000 ; i++) { dispatch_async(queue, ^{ self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i]; });}
会crash。因为在并行队列DISPATCH_QUEUE_CONCURRENT中异步dispatch_async对str属性进行赋值,就会导致str曾经被release了,还会执行release。这就是向已开释内存的对象发送音讯而产生crash
具体解析:对str属性strong润饰进行赋值,相当与MRC中的
- (void)setStr:(NSString *)str{ if (str == _str) return; id pre = _str; [str retain];//1.先保留新值 _str = str;//2.再进行赋值 [pre release];//3.开释旧值}
那么如果并发队列里调度的线程A执行到步骤1,还没到步骤2时,线程B执行到步骤3,那么当线程A再执行步骤3时,旧值就会被适度开释,导致向已开释内存的对象发送音讯而解体
- 诘问:怎么批改这段代码变为不解体呢
1、应用串行队列
将set办法改成在串行队列中执行就行,这样即便异步,但所有block操作追加在队列最初顺次执行
2、应用atomic
atomic关键字相当于在setter办法加锁,这样每次执行setter都是线程平安的,但这只是独自针对setter办法而言的广义的线程平安
3、应用weak关键字
weak的setter没有保留新值的操作,所以不会引发反复开释。当然这个时候要看具体情况是否应用weak,可能值并不是所须要的值
4、应用互斥锁,保证数据拜访的唯一性@synchronized (self) {self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];}
5、应用Tagged Pointer
Tagged Pointer是苹果在64位零碎引入的内存技术。简略来说就是对于NSString(内存小于60位的字符串)或NSNumber(小于2^31),64位的指针有8个字节,齐全能够间接用这个空间来间接示意值,这样的话其实会将NSString和NSNumber对象由一个指针转换成一个值类型,而值类型的setter和getter又是原子的,从而线程平安
- 发散:上面代码会crash吗
@property (nonatomic, strong) NSString *str;dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);for (int i = 0; i < 1000000 ; i++) { dispatch_async(queue, ^{ // 相比下面,仅字符串变短了 self.str = [NSString stringWithFormat:@"%d",i]; NSLog(@"%d, %s, %p", i, object_getClassName(self.str), self.str); });}
不会crash。而且发现str这个字符串类型是NSTaggedPointerString
Tagged Pointer是一个可能晋升性能、节俭内存的乏味的技术
Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate(起初能够存储小字符串)
Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的一般变量而已
它的内存并不存储在堆中,也不须要malloc和free,所以领有极快的读取和创立速度