iOS開發系列--C語言之預處理

KenshinCui發表於2014-07-18

概述

大家都知道一個C程式的執行包括編譯和連結兩個階段,其實在編譯之前前處理器首先要進行預處理操作,將處理完產生的一個新的原始檔進行編譯。由於預處理指令是在編譯之前就進行了,因此很多時候它要比在程式執行時進行操作效率高。在C語言中包括三類預處理指令,今天將一一介紹:

  1. 巨集定義
  2. 條件編譯
  3. 檔案包含

巨集定義

對於程式中經常用到的一些常量或者簡短的函式我們通常使用巨集定義來處理,這樣做的好處是對於程式中所有的配置我們可以統一在巨集定義中進行管理,而且由於巨集定義是在程式編譯之前進行替換相比定義成全域性變數或函式效率更高。

//
//  main.c
//  Pretreatment
//
//  Created by Kenshin Cui on 14-6-28.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>
#define PI 3.14 //巨集定義一般大寫
#define R 10
#define S 2*PI*R //在另一個巨集裡面引用了上面的巨集

int main(int argc, const char * argv[]) {
    float r=10.5;
    double area=PI*r*r;
    printf("area=%.2f\n",area);
    
    double a=S;
    printf("a=%.2f\n",a);
    printf("PI=3.14\n");//注意輸出結果不是3.14=3.14而是PI=3.14,字串中的PI並不會被替換
#undef PI //強制終止巨集定義,否則它的範圍一直到檔案結束
    int PI=3.1415926;
    double area2=PI*r*r;
    printf("area2=%.2f\n",area2);
    
    
    return 0;
}

巨集定義實際的操作就是在預處理時進行對應替換,這個階段不管語法是否正確,而且對於字串中出現的巨集名不會進行替換。巨集定義的功能事實上是非常強大的,除了簡單的常量替換還可以傳入引數:

//
//  1.2.c
//  Pretreatment
//
//  Created by Kenshin Cui on 14-7-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>
#define SUM(a,b) a+b
#define SUB(a,b) (a-b)
#define MUL (a,b) (a*b) //這麼定義是錯誤的,前處理器會認為巨集名為”MUL“,替換內容為”(a,b) (a*b)“


int main(int argc, const char * argv[]) {
    
    int a=2,b=3,c,d;
    c=SUM(a, b);
    printf("c=%d\n",c); //結果:c=5
    d=SUM(a, b)*2;
    printf("d=%d\n"); //結果:8,為什麼不是10呢?因為替換後:d=a+b*2也就是2+3*2=8
    
    int e=SUB(b, a)*2;
    printf("(b-a)*2=%d\n",e); //結果:2,如果SUB定義時不加括號這裡應該是-1
    
    return 0;
}
上面我們可以看出帶引數的巨集功能很強大,有點類似於函式,同函式不同的是它只是簡單的替換,不涉及儲存空間分配,引數、返回值等問題,但是由於它在預處理階段展開,所以一般效率較高。使用帶引數的巨集需要注意的就是結果最好用括號括起來否則很容易出現問題(在上面的SUM例子中我們應該已經看到了);還有一點就是帶引數的巨集定義時名稱和引數之間不要有空格。

條件編譯

條件編譯其實就是在編譯之前前處理器根據預處理指令判斷對應的條件,如果條件滿足就將對應的程式碼編譯進去,否則程式碼就根本不進入編譯環節(相當於根本就沒有這段程式碼)。

//
//  main.c
//  Pretreatment
//
//  Created by Kenshin Cui on 14-06-28.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>
#define COUNT 1

int main(int argc, const char * argv[]) {
    
//判斷是否定義了 COUNT 巨集
#if defined(COUNT) //等價於:#ifdef COUNT,相反如果判斷沒有定義過則可以通過#if !defined(COUNT)或者#ifndef COUNT
    printf("COUNT defined\n");
#endif
    
//判斷巨集定義COUNT是否等於1
#if COUNT==1
    showMessage("hello,world!\n");
#else
    say();
#endif
    
    return 0;
}

檔案包含

檔案包含指令#include在前面也多次使用過,這裡再次強調一下。首先使用#include“xxx”包含和使用#include <xxx>包含的不同之處就是使用<>包含時,前處理器會搜尋C函式庫標頭檔案路徑下的檔案,而使用“”包含時首先搜尋程式所在目錄,其次搜尋系統Path定義目錄,如果還是找不到才會搜尋C函式庫標頭檔案所在目錄。

另外在使用#include的時候我們需要注意包含檔案的時候是不能遞迴包含的,例如a.h檔案包含b.h,而b.h就不能再包含a.h了;還有就是重複包含雖然是允許的(這裡指的是重複包含標頭檔案)但是這會降低編譯效能,不妨看一下下面的例子:

includeRepeat

上面有三段程式碼,在main.c和person.h中都包含了message.h而main.c自身又包含了person.h,這樣程式在預處理階段會對包含內容進行替換,替換後mian.c中包含了兩個#include “message.h”雖然沒有報錯,但這會影響編譯的效能,正確的做法應該是這樣的:

correctInclude

其實就是用巨集定義判斷一個巨集是否定義了,如果沒有定義則會定義這個巨集,這樣以來如果已經包含過則這個巨集定義肯定已經定義過了,即使再包含也不會重新定義了,下面的程式碼也就不會包含進去。

相關文章