InterView一个靠谱的iOS开发

1. UIView的bounds和frame和center的关系。

  • frame:描述当前视图在其父视图中的位置和大小。
  • bounds:描述当前视图在其自身坐标系统中的位置和大小。
  • center:描述当前视图的中心点在其父视图中的位置。
  • View B是View A的子视图,那么ViewB的frame属性为origin(200,100),size(200,250),而View B的bounds属性为origin(0,0),size(200,250)。center属性则用CGPoint表示矩形中心点在其父视图中的位置,View B的center属性为(300,200)。

2. Objective-c中:isKindOfClass,isMemberOfClass,isSubclassOfClass的区别。;isEqual和isEqualToString和==三者的区别;

  • - (BOOL)isKindOfClass:(Class)aClass:返回一个BOOL类型的值,表示调用该方法的类是不是参数类或者继承于参数类;
  • - (BOOL)isMemberOfClass:(Class)aClass:返回一个BOOL类型的值,表示调用该方法的类是不是参数类;
  • + (BOOL)isSubclassOfClass:(Class)aClass:返回一个BOOL类型的值,表示调用该方法的类是否是参数类的一个子类或者是这个类的自己。
  • ==:对于基本类型,==运算符比较的是值;对于对象类型,==运算符比较的是对象的地址(便是否为同一对象);
  • isEqual:NSObject方法,返回一个bool值判断两个对象是否相等。若是两个对象是相等的,那么他们必须有相同的哈希值
  • isEqualToString:NSString方法,而NSString是继承自NSObject的,因此isEqualToString应该是isEqual的衍生方法,是对isEqual的细分。速度效率上优于isEqual。类似的还有isEqualToArray等。

3. loadinitialize方法的区别是什么?

  • 调用方式
    • load是:根据函数地址直接调用
    • initialize是:经过objc_msgSend调用
  • 调用时刻
    • load是:runtime加载类、分类的时候调用(只会调用一次)
    • initialize是:类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用屡次)
  • 调用顺序
    • load是:先调用类的load(先编译的类,有限调用load。调用子类的load以前,会先调用父类的load);再调用分类的load(先编译的分类,优先调用``load)。 * initialize是:先初始化父类,再初始化子类(可能最终调用的是父类的initialize`方法)。

4. 宏定义来获取数组元素的个数

#define SIZE_ARRAY(a) (sizeof(a) / sizeof((a)[0]))
sizeof函数是求对象空间大小的函数。 arry是整个数组,arry[0]是数组中第一个元素。ios

5. KVO的底层原理

我以前的OC底层知识点里有写。 juejin.im/post/5aa25a…算法

6. 消息调用的过程

我以前的OC底层知识点里有写。 juejin.im/post/5aa25a…sql

7. http有哪些部分

HTTP 是基于 TCP/IP协议来传输信息的应用层协议。它不涉及数据包(packet)传输,主要规定了客户端和服务器之间的通讯格式,默认使用80端口。数据库

  • HTTP 的请求报文分为三个部分:请求行、请求头、请求体
    • 请求行(Request line)分为三个部分:请求方法、请求地址和协议版本。
    • 请求头可用于传递一些附加信息,格式为:键: 值,注意冒号后面有一个空格:
    • 请求体(又叫请求正文)是 post 请求方式中的请求参数,以 key = value 形式进行存储,多个请求参数之间用&链接,若是请求当中请求体,那么在请求头当中的 Content-Length 属性记录的就是该请求体的长度。
  • HTTP 响应的格式上除响应状态行(第一行)与请求报文的请求行不同以外,其余的就格式而言是同样的。
    • 响应状态行:
      • 1XX 消息 表示请求已经被接收了,服务器正在进行处理
      • 2XX 成功 表示请求已经成功接收且被处理
      • 3XX 重定向 表示须要客户端采起进一步的操做才能完成请求
      • 4XX 请求错误 表示客户端错误,多是请求的语法有错或该请求没法实现
      • 5XX 服务器错误 表示服务器错误,服务器端没法实现发出的合理请求
    • 响应头一样可用于传递一些附加信息
    • 响应体也就是网页的正文内容,通常在响应头中会用 Content-Length 来明确响应体的长度,便于浏览器接收,对于大数据量的正文信息,也会使用 chunked 的编码方式。
  • 三次握手四次挥手
    • 第一次握手:浏览器准备向服务器创建链接,发送同步序列编号(SYN)到服务器,等待服务器确认
    • 第二次握手:服务器确认该同步序列编号(SYN),本身也发送一个同步序列编号(SYN+ACK)给浏览器
    • 第三次握手:浏览器收到服务器的同步序列编号(SYN+ACK),向服务器发送确认包(ACK),客户端和服务器进入 ESTABLISHED(TCP 链接成功)状态,完成三次握手。
    • 挥手的过程和握手过程区别在第二次,当Server端收到Client端的SYN链接请求报文后,能够直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。可是关闭链接时,当Server端收到FIN报文时,极可能并不会当即关闭SOCKET,因此只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端全部的报文都发送完了,我才能发送FIN报文,所以不能一块儿发送。故须要四步挥手。
  • HTTP/2
    • 二进制协议:
      HTTP/1.1版的头信息确定是文本(ASCII编码),数据体能够是文本,也能够是二进制。HTTP/2则是一个完全的二进制协议,头信息和数据体都是二进制,而且统称为"帧"(frame):头信息帧和数据帧。
    • 多工:
      HTTP/2复用TCP链接,在一个链接里,客户端和浏览器均可以同时发送多个请求或回应,并且不用按照顺序一一对应,这样就避免了"队头堵塞"。
    • 数据流:
      HTTP/2的数据包是不按顺序发送的,同一个链接里面连续的数据包,可能属于不一样的回应。所以,必需要对数据包作标记,指出它属于哪一个回应。HTTP/2 将每一个请求或回应的全部数据包,称为一个数据流(stream)。每一个数据流都有一个独一无二的编号。数据包发送的时候,都必须标记数据流ID,用来区分它属于哪一个数据流。另外还规定,客户端发出的数据流,ID一概为奇数,服务器发出的,ID为偶数。
    • 头信息压缩:
      HTTP协议不带有状态,每次请求都必须附上全部信息。头信息使用gzip或compress压缩后再发送;另外一方面,客户端和服务器同时维护一张头信息表,全部字段都会存入这个表,生成一个索引号,之后就不发送一样字段了,只发送索引号,这样就提升速度了。
    • 服务器推送:
      HTTP/2容许服务器未经请求,主动向客户端发送资源,这叫作服务器推送(server push)。

8. get和post的区别,tcp和udp的区别,七层网络模型

  • HTTP的底层是TCP/IP。因此GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP连接。GET和POST能作的事情是同样同样的。你要给GET加上request body,给POST带上url参数,技术上是彻底行的通的。
  • GET产生一个TCP数据包;POST产生两个TCP数据包。(对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据))
  • TCP和UDP的区别
    • TCP面向链接(如打电话要先拨号创建链接);UDP是无链接的,即发送数据以前无需创建链接。
    • TCP提供可靠的服务。也就是说经过TCP链接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
    • TCP 面向字节流,其实是TCP把数据当作是一连串无结构的字节流。UDP是面向报文的,UDP没有拥塞控制,所以网络出现拥塞不会使源主机的发送速率下降(对实时应用颇有用,如IP电话,实时视频会议等)。
    • UDP具备较好的实时性,工做效率比TCP高,适用于对高速传输和实时性有较高的通讯或广播通讯。
    • 每一条TCP链接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通讯。
    • TCP对系统资源要求较多,UDP对系统资源要求较少。
  • 客户端实现TCP编程通常步骤
    • 建立一个socket,用函数socket();
    • 设置socket属性,用函数setsockopt();可选
    • 绑定IP地址、端口等信息到socket上,用函数bind();可选
    • 设置要链接的对方的IP地址和端口等属性;
    • 链接服务器,用函数connect();
    • 收发数据,用函数send()和recv(),或者read()和write();
    • 关闭网络链接;
  • 客户端实现UDP编程通常步骤
    • 建立一个socket,用函数socket();
    • 设置socket属性,用函数setsockopt();可选
    • 绑定IP地址、端口等信息到socket上,用函数bind();可选
    • 设置对方的IP地址和端口等属性;
    • 发送数据,用函数sendto();
    • 关闭网络链接;
  • OSI七层网络模型:是一个标准,而非实现。
    • 物理层:底层数据传输,如网线;网卡标准。
    • 数据链路层:定义数据的基本格式,如何传输,如何标识;如网卡MAC地址。
    • 网络层:定义IP编址,定义路由功能;如不一样设备的数据转发。
    • 传输层:端到端传输数据的基本功能;如 TCP、UDP。
    • 会话层:控制应用程序之间会话能力;如不一样软件数据分发给不一样软件。
    • 标识层:数据格式标识,基本压缩加密功能。
    • 应用层:各类应用软件,包括 Web 应用。
  • TCP/IP简化四层网络模型
    • 网络访问层:物理层,数据链路层。
    • 网络层
    • 传输层
    • 应用层:会话层,标识层,应用层

9. 传递响应链

  • 事件的产生
    • 发生触摸事件后,系统会将事件加入到一个UIApplication管理的队列中,先产生的事件先处理。
    • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,一般先发送事件给应用程序的主窗口(keyWindow)。
    • 主窗口(keyWindow)会在视图层次结构中找到一个最合适的视图来处理触摸事件,寻找最合适的视图的关键就是hitTest:withEvent:方法。
  • 事件的传递(找到合适的view,hitTest:withEvent:)过程。事件的传递是自上到下的顺序,即UIApplication->window->处理事件最合适的view。
    • 首先判断主窗口(keyWindow)本身是否能接受触摸事件。判断触摸点是否在本身身上。
    • 子控件数组中从后往前遍历子控件,重复上一步的步骤。
    • 经过前面的步骤找到了fitView,那么会把这个事件交给这个fitView,再遍历这个fitView的全部子控件,直到没有更合适的View。
    • 若是没有符合条件的子控件,那么就认为本身最合适处理这个事件,也就是本身是最合适的view。userInteractionEnabled = NO,hiden = YES,alpha<=0.01都不能接收事件。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    // 1.判断下窗口可否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil;
    // 2.判断下点在不在窗口上
    // 不在窗口上
    if ([self pointInside:point withEvent:event] == NO) return nil;
    // 3.从后往前遍历子控件数组
    int count = (int)self.subviews.count;
    for (int i = count - 1; i >= 0; i--)     {
        // 获取子控件
        UIView *childView = self.subviews[i];
        // 坐标系的转换,把窗口上的点转换为子控件上的点
        // 把本身控件上的点转换成子控件上的点
        CGPoint childP = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childP withEvent:event];
        if (fitView) {
            // 若是能找到最合适的view
            return fitView;
        }
    }
    // 4.没有找到更合适的view,也就是没有比本身更合适的view
    return self;
}
// 做用:判断下传入过来的点在不在方法调用者的坐标系上
// point:是方法调用者坐标系上的点
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
//{
// return NO;
//}
复制代码
  • 事件的响应(responderchain过程) 个人理解hitTest的过程是将响应者对象(继承自UIResponder的对象)找出来并入栈的过程。将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理 (即调用super的touches方法),这是一个出栈执行的过程,从最后入栈的最合适的view开始执行。
    • 判断当前是不是控制器的View,若是是控制器的View,上一个响应者就是控制器
    • 若是不是控制器的View,上一个响应者就是父控件
    • 找到最合适的view会调用touches方法处理事件
    • 判断最合适的view是否处理事件(是否实现touches方法)–>没有实现默认会将事件传递给上一个响应者–>找到上一个响应者–>最终到keyWindow找不到方法做废。
//只要点击控件,就会调用touchBegin,若是没有重写这个方法,本身处理不了触摸事件
// 上一个响应者多是父控件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
// 默认会把事件传递给上一个响应者,上一个响应者是父控件,交给父控件处理
[super touchesBegan:touches withEvent:event]; 
// 注意不是调用父控件的touches方法,而是调用父类的touches方法
// super是父类 superview是父控件 
}
复制代码

10. 经过一个view查找它所在的viewController

经过响应者链条机制找到当前view所属的控制器编程

#import "UIView+Tool.h"

@implementation UIView (Tool)
//经过响应者链条获取view所在的控制器
- (UIViewController *)parentController
{
    UIResponder *responder = [self nextResponder];
    while (responder) {
        if ([responder isKindOfClass:[UIViewController class]]) {
            return (UIViewController *)responder;
        }
        responder = [responder nextResponder];
    }
    return nil;
}
@end
复制代码

11. 经过NSObject对象,获取当前控制器对象

#import "NSObject+Tool.h"
#import "UIView+Tool.h"
@implementation NSObject (Tool)
//经过展现window的布局视图能够获取到控制器实例对象    modal的展示方式须要取到控制器的根视图
- (UIViewController *)currentViewController
{
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    // modal展示方式的底层视图不一样
    // 取到第一层时,取到的是UITransitionView,经过这个view拿不到控制器
    UIView *firstView = [keyWindow.subviews firstObject];
    UIView *secondView = [firstView.subviews firstObject];
    UIViewController *vc = [secondView parentController];
    
    if ([vc isKindOfClass:[UITabBarController class]]) {
        UITabBarController *tab = (UITabBarController *)vc;
        if ([tab.selectedViewController isKindOfClass:[UINavigationController class]]) {
            UINavigationController *nav = (UINavigationController *)tab.selectedViewController;
            return [nav.viewControllers lastObject];
        } else {
            return tab.selectedViewController;
        }
    } else if ([vc isKindOfClass:[UINavigationController class]]) {
        UINavigationController *nav = (UINavigationController *)vc;
        return [nav.viewControllers lastObject];
    } else {
        return vc;
    }
    return nil;
}
@end
复制代码

12. 如何扩大view的响应范围

// 在view中重写如下方法,其中self.button就是那个但愿被触发点击事件的按钮
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];
    if (view == nil) {
        // 转换坐标系
        CGPoint newPoint = [self.button convertPoint:point fromView:self];
        // 判断触摸点是否在button上
        if (CGRectContainsPoint(self.button.bounds, newPoint)) {
            view = self.deleteButton;
        }
    }
    return view;
}

复制代码

13. 线程之间的通讯方式

  • GCD实现线程通讯
//开启一个全局队列的子线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            //1. 开始请求数据
            //...
            // 2. 数据请求完毕
            //咱们知道UI的更新必须在主线程操做,因此咱们要从子线程回调到主线程
        dispatch_async(dispatch_get_main_queue(), ^{

                //我已经回到主线程更新
        });

    });
    
        //线程延迟调用 通讯
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"## 在主线程延迟5秒调用 ##");
    });
复制代码
  • perfermselecter选择器实现线程通讯
//数据请求完毕回调到主线程,更新UI资源信息  waitUntilDone    设置YES ,表明等待当前线程执行完毕
 [self performSelectorOnMainThread:@selector(dothing:) withObject:@[@"1"] waitUntilDone:YES];
  //将当前的逻辑转到后台线程去执行
[self performSelectorInBackground:@selector(dothing:) withObject:@[@"2"]];
        //当咱们须要在特定的线程内去执行某一些数据的时候,咱们须要指定某一个线程操做
    [self performSelector:@selector(dothing:) onThread:thread withObject:nil waitUntilDone:YES];
复制代码
  • NSOperation实现线程通讯
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = image;
        }];
复制代码

14. app进程之间的通讯方式

  • URL Scheme:ios最经常使用的app通讯方式,经过openURL方式进行跳转,能够携带参数
  • Keychain:系统地Keychain是一个安全的存储容器,它本质上就是一个sqllite数据库,它的位置存储在/private/var/Keychains/keychain-2.db,不过它所保存的全部数据都是通过加密的,能够用来为不一样的app保存敏感信息。
  • UIPasteboard:剪切板功能
  • UIDocumentInteractionController:主要是用来实现同设备上app之间的共享文档,以及文档预览、打印、发邮件和复制等功能
  • local socket:一个App1在本地的端口port1234进行TCP的bind和listen,另一个App2在同一个端口port1234发起TCP的connect链接,这样就能够创建正常的TCP链接,进行TCP通讯了,那么就想传什么数据就能够传什么数据了。
  • AirDrop:经过AirDrop实现不一样设备的App之间文档和数据的分享
  • UIActivityViewController:iOSSDK中封装好的类在App之间发送数据、分享数据和操做数据
  • App Groups:同一个开发团队开发的App之间,包括App和Extension之间共享同一份读写空间,进行数据共享。

15. 两个进程分别指向同一个地址空间并初始化一个值,分别输出是什么

物理地址:内存单元所看到的地址。逻辑地址(虚拟地址):CPU所生成的地址。每一个进程都有本身的虚拟地址空间,不一样进程的相同的虚拟地址显然能够对应不一样的物理地址。所以地址相同(虚拟地址)而值不一样没什么奇怪。数组

16. 判断一个字符串是否全部的大写字母都在小写字母前面

循环对照ASCALL码表,A~Z为65~90,a~z为97~122。从前到后遍历字符是否在‘A’~‘Z’区间,若是是继续执行循环。若是不是,判断是否在'a'~'z'中:若是不是,继续循环;若是是,标记临界点,继续循环,判断以后的字符是否在‘A’~‘Z’区间,是返回false,不然返回true。xcode

17. 提升编译速度,减少编译生成的app的大小的方法。

项目瘦身

编译速度优化

  • 正确使用.pch文件
    • pch文件中尽可能不要导入宏定义,咱们都直到宏定义是去匹配的机制,若是全局匹配,无疑很耗费时间。最好写到每一部分头文件中。
    • pch文件主要进行加载的是一些比较大的文件。可是若是是咱们本身写的一些比较大的文件,尽可能不要导入太多,对编译速度自己是很差的。并且pch中的文件代码在多个项目中的可复用并很差。比较建议一些系统级经常使用框架。
  • 正确的import操做
    • 如B类用到A类,有时可能须要将A类在.h文件中声明一个属性,这个时候会在BClass.h中直接#import “AClass.h”,这样作法是不合理的,由于编译时是不须要把AClass中的所有信息编译尽可能,只须要直到该类被引入便可。因此此时使用·@class AClass
    • 另外若是引用链A -> B -> C -> D... ,如编译链中最后一个类发生更改,那么整个从A开始的相关类都要从新编译。
  • 打包静态连接库
    • 平常开发能够将咱们不会修改或已经成熟的业务模块打包成.a静态库连接。打包成静态连接库后,生成的就是对应CPU架构下的二进制文件,编译时不会占用编译时间,运行时直接写入内存。固然打包静态库涉及release和debug版本,真机和模拟器兼容版本。
    • 采用cocoapod管理维护.a静态连接库。
  • 借助CCache工具:ccache是一个能够把编译中间产物缓存起来的工具,目前能够支持C、C++、Objective-C、Objective-C++。brew安装,xcode配置。在第一次启用ccache编译时由于全部文件都没有作过编译缓存,从第二次开始编译速度就会有所提高。
  • 控制换行,空白行的数量,控制方法的数量,目录深度不要太深等等。(倒以为这些不必,影响有限反而会波及到项目管理和代码规范)。

18. 程序执行的过程

  • 预处理(Prepressing) 预处理的过程,其实,主要是处理那些源代码中以#开始的预编译指令,hello.c->hello.i。好比#inclode,#define等,处理过程以下:
    • 将全部的#define删除,而且展开全部的宏定义。
    • 处理全部的条件预编译指令,好比#if,#ifdef,#elif,#else,#endif等。
    • 处理#include``#import预编译指令,将被包含的文件插入到该预编译指令的位置。在这个插入的过程,是递归进行的,也就是说被包含的文件,可能还包含其余文件。
    • 删除全部注释///***/
    • 添加行号和文件标识,以便编译时产生调试的行号及编译错误报警行号
    • 保留全部的#pragma 编译器指令,由于编译器须要使用他们。
  • 编译(Compilation)
    编译过程可分为6步:词法分析、语法分析、语义分析、源代码优化、代码生成、目标代码优化。hello.i -> hello.a
    • 词法分析:扫描器(Scanner)将源代码的字符序列分割成一系列的记号(Token)。
      注:lex工具,可实现按照用户描述的词法规则将输入的字符串分割为一个一个记号。
    • 语法分析:语法分析器将记号(Token)产生语法树(Syntax Tree)。
      注:yacc工具(yacc: Yet Another Compiler Compiler)可实现语法分析,根据用户给定的语法规则对输入的记号序列进行解析,从而构建一个语法树,因此它也被称为“编译器编译器(Compiler Compiler)”。
    • 语义分析:编译器所分析的语义是静态语义,所谓静态语义就是指在编译期能够肯定的语义,一般包括声明,和类型的匹配,类型的转换。
      注:与之对于的为动态语义分析,只有在运行期才能肯定的语义。
    • 源代码优化:源代码优化器(Source Code Optimizer),在源码级别进行优化,例如(2+6)这个表达式,其值在编译期就能够肯定。
    • 目标代码生成:代码生成器(Code Generator)。
    • 目标代码优化:目标代码优化器(Target Code Optimizer)。

最后的俩个步骤十分依赖与目标机器,由于不一样的机器有不一样的字长,寄存器,整数数据类型和浮点数据类型等。浏览器

  • 汇编(Assembly)

汇编器是将汇编代码转变成机器能够执行的命令,每个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,因此根据汇编指令和机器指令的对照表一一翻译便可。汇编过程能够经过如下方式完成。-> hello.o。缓存

  • 连接(Linking)安全

    • 静态连接
      把一个程序分红多个模块,把一个程序分割为多个模块,而后经过某种方式组合造成一个单一的程序,这就是连接。

      hello.o文件,既目标文件,是以分段的形式组织在一块儿的。其简单来讲,把程序运行的地址划分为了一段一段的片断,有的片断是用来存放代码,叫代码段,这样,能够给这个段加个只读的权限,防止程序被修改;有的片断用来存放数据,叫数据段,数据常常修改,因此可读写;有的片断用来存放标识符的名字,好比某个变量 ,某个函数,叫符号表;等等。因为有这么多段,因此为了方便管理,因此又引入了一个段,叫段表,方便查找每一个段的位置。

      当文件之间相互须要连接的时候,就把相同的段合并,而后把函数,变量地址修改到正确的地址上。这就是静态连接。

      • 假若有多个程序都连接一个相同的静态库,这样程序运行起来后,就有了多个副本。对于计算机的内存和磁盘的空间浪费比较严重。
      • 程序的更新,部署,和发布会带来不少麻烦。一旦程序任何位置有一个小小的改动,都会致使从新下载整个程序。
    • 动态连接
      咱们的想法很简单,就是当第一个例子在运行时,在内存中只有一个副本;第二个例子在发生时,只须要下载更新后的lib,而后连接,就行了。那么其实,这就是动态连接的基本思想了:把连接这个过程推迟到运行的时候在进行。在运行的时候动态的选择加载各类程序模块,这个优势,就是后来被人们用来制做程序的插件(Plug-in)。

      动态连接器。它会在程序运行的时候,把程序中全部未定义的符号(好比调了动态库的一个函数,或者访问了一个变量)绑定到动态连接库中。

      可能有的人,就要问了,多个程序应用一个库不会有问题么?变量冲突?是这样的。动态连接文件,把那些须要修改的部分分离了出来,与数据放在了一块儿,这样指令部分就能够保持不变,而数据部分能够在每一个进程中拥有一个副本,这种方案就是目前被称为地址无关代码(PIC,Position-independent Code)的技术。

    • 静态库

    一组相应目标文件的集合,咱们称它为库。在Linux平台上,常以.a或者.o为拓展名的文件,咱们最经常使用的C语言静态库,就位于/usr/lib/libc.a;而在Windows平台上,常以.lib为拓展名的文件,好比Visual C++附带的多个版本C/C++运行库,在VC安装的目录下的lib\目录。

    • 动态库

    一组相应目标文件的集合,咱们称它为库。在Linux平台上,动态连接文件为称为动态共享对象(DSO,Dynamic Shared Objects),简称共享对象。他们通常常以.so为拓展名的文件;而在Windows平台上,动态连接文件被称为动态连接库(DLL,Dynamical Linking Library),一般就是咱们常见的.dll为拓展名的文件。

  • 装载(Loading)
    其实目标文件,内部结构上来讲和可执行文件的结构几乎是同样的,因此通常跟可执行文件格式一块儿用一种格式进行存储。总的来讲,装载作了如下三件事情

    • 建立虚拟地址空间。
    • 读取可执行文件头,而且创建虚拟空间与可执行文件的映射关系。
    • 将CPU的指令寄存器设置为运行库的初始函数(初始函数不止一个,第一个启动函数为:_start),初始了main()函数的环境,而后指向可执行文件的入口。

针对C/C++的GCC编译过程

19. 算法奇数排在前面,偶数排在后面

我以前的排序算法知识点里有写。juejin.im/post/5aa78b…

20. 下图是一颗四则运算树:x=(1+2)+((5*6)-7)+(3/4),请翻译成代码,并对树求值。

提示:可用对象描述每个节点。

  • 运算树,首先想到经过递归实现,递归数据建模以下。
- (void)testTreeArithmetic{
    
    NSDictionary * root =@{
                           @"type":@"addition",
                           @"values":@[
                                      @{
                                          @"type":@"addition",
                                          @"values":@[
                                                  @{
                                                      @"type":@"number",
                                                      @"value":@"1",
                                                  },
                                                  @{
                                                      @"type":@"number",
                                                      @"value":@"2"
                                                      }
                                                  ]
                                      },
                                      @{
                                          @"type":@"subtraction",
                                          @"values":@[
                                                  @{
                                                      @"type":@"multiplication",
                                                      @"values":@[
                                                              @{
                                                                  @"type":@"number",
                                                                  @"value":@"5"
                                                              },
                                                              @{
                                                                  @"type":@"number",
                                                                  @"value":@"6"
                                                                  }
                                                              ]
                                                  },
                                                  @{
                                                      @"type":@"number",
                                                      @"value":@"7"
                                                      }
                                                  ]
                                       },
                                      @{
                                          @"type":@"division",
                                          @"values":@[
                                                  @{
                                                      @"type":@"number",
                                                      @"value":@"3"
                                                      },
                                                  @{
                                                      @"type":@"number",
                                                      @"value":@"4"
                                                      }
                                                  ]
                                       }
                               ],
                           };
    self.treeModel = [TreeModel mj_objectWithKeyValues:root];
    NSLog(@"-------------- %f", [self calc:self.treeModel]);
   
}
复制代码
@interface TreeModel : NSObject

@property (nonatomic,copy) NSString * type;
@property (nonatomic,strong) NSArray * values;
@property (nonatomic,strong) NSString * value;
@end
复制代码
  • 运算执行以下:
- (float)calc:(TreeModel *)tree{
    if ([tree.type isEqualToString:@"number"]) {
        return [tree.value floatValue];
        
    }else if ([tree.type isEqualToString:@"addition"]){
        float v = 0;
        for (TreeModel * model in tree.values) {
            v +=  [self calc:model];
        }
        return v;
        
    }else if ([tree.type isEqualToString:@"subtraction"]){
        float v =0;
        for (int i = 0; i<tree.values.count; i++) {
            TreeModel * model =[tree.values objectAtIndex:i];
            if (i == 0) {
                v = [self calc:model];
            }else{
                v -= [self calc:model];
            }
        }
        return v;
        
    }else if ([tree.type isEqualToString:@"multiplication"]){
        float v = 1;
        for (TreeModel * model in tree.values) {
            v *= [self calc:model];
        }
        return v;
        
    }else if ([tree.type isEqualToString:@"division"]){
        float v = 0;
        for (int i = 0; i<tree.values.count; i++) {
            TreeModel * model = [tree.values objectAtIndex:i];
            if (i == 0) {
                v = [self calc:model];
            }else
            {
                v /= [self calc:model];
            }
        }
        return v;
    }
    return 0;
}
复制代码
相关文章
相关标签/搜索