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;
}
複製代碼
相關文章
相關標籤/搜索