1. 程式人生 > >幾個預編譯指令的用法

幾個預編譯指令的用法

    預處理過程掃描原始碼,對其進行初步的轉換,產生新的原始碼提供給編譯器。可見預處理過程先於編譯器對原始碼進行處理。
在C語言中,並沒有任何內在的機制來完成如下一些功能:在編譯時包含其他原始檔、定義巨集、根據條件決定編譯時是否包含某些程式碼。要完成這些工作,就需要使用預處理程式。儘管在目前絕大多數編譯器都包含了預處理程式,但通常認為它們是獨立於編譯器的。預處理過程讀入原始碼,檢查包含預處理指令的語句和巨集定義,並對原始碼進行響應的轉換。預處理過程還會刪除程式中的註釋和多餘的空白字元。
預處理指令是以#號開頭的程式碼行。#號必須是該行除了任何空白字元外的第一個字元。#後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字元。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對原始碼做某些轉換。下面是部分預處理指令:

        指令             用途
         #           空指令,無任何效果
         #include    包含一個原始碼檔案
         #define     定義巨集
         #undef      取消已定義的巨集
         #if         如果給定條件為真,則編譯下面程式碼
         #ifdef      如果巨集已經定義,則編譯下面程式碼
         #ifndef     如果巨集沒有定義,則編譯下面程式碼
         #elif       如果前面的#if給定條件不為真,當前條件為真,則編譯下面程式碼
         #endif      結束一個#if……#else條件編譯塊
         #error      停止編譯並顯示錯誤資訊

一、檔案包含
    #include預處理指令的作用是在指令處展開被包含的檔案。包含可以是多重的,也就是說一個被包含的檔案中還可以包含其他檔案。標準C編譯器至少支援八重巢狀包含。
    預處理過程不檢查在轉換單元中是否已經包含了某個檔案並阻止對它的多次包含。這樣就可以在多次包含同一個標頭檔案時,通過給定編譯時的條件來達到不同的效果。例如:

        #define AAA
        #include "t.c"
        #undef AAA
        #include "t.c"

    為了避免那些只能包含一次的標頭檔案被多次包含,可以在標頭檔案中用編譯時條件來進行控制。例如:
        /*my.h*/
        #ifndef MY_H
        #define MY_H
          ……
        #endif

    在程式中包含標頭檔案有兩種格式:
        #include <my.h>
        #include "my.h"
    第一種方法是用尖括號把標頭檔案括起來。這種格式告訴預處理程式在編譯器自帶的或外部庫的標頭檔案中搜索被包含的標頭檔案。第二種方法是用雙引號把標頭檔案括起來。這種格式告訴預處理程式在當前被編譯的應用程式的原始碼檔案中搜索被包含的標頭檔案,如果找不到,再搜尋編譯器自帶的標頭檔案。
    採用兩種不同包含格式的理由在於,編譯器是安裝在公共子目錄下的,而被編譯的應用程式是在它們自己的私有子目錄下的。一個應用程式既包含編譯器提供的公共標頭檔案,也包含自定義的私有標頭檔案。採用兩種不同的包含格式使得編譯器能夠在很多標頭檔案中區別出一組公共的標頭檔案。

二、巨集
    巨集定義了一個代表特定內容的識別符號。預處理過程會把原始碼中出現的巨集識別符號替換成巨集定義時的值。巨集最常見的用法是定義代表某個值的全域性符號。巨集的第二種用法是定義帶引數的巨集,這樣的巨集可以象函式一樣被呼叫,但它是在呼叫語句處展開巨集,並用呼叫時的實際引數來代替定義中的形式引數。
    1.#define指令
        #define預處理指令是用來定義巨集的。該指令最簡單的格式是:首先神明一個識別符號,然後給出這個識別符號代表的程式碼。在後面的原始碼中,就用這些程式碼來替代該識別符號。這種巨集把程式中要用到的一些全域性值提取出來,賦給一些記憶識別符號。
            #define MAX_NUM 10
            int array[MAX_NUM];
            for(i=0;i<MAX_NUM;i++)  /*……*/
       
        在這個例子中,對於閱讀該程式的人來說,符號MAX_NUM就有特定的含義,它代表的值給出了陣列所能容納的最大元素數目。程式中可以多次使用這個值。作為一種約定,習慣上總是全部用大寫字母來定義巨集,這樣易於把程式紅的巨集識別符號和一般變數識別符號區別開來。如果想要改變陣列的大小,只需要更改巨集定義並重新編譯程式即可。
        巨集表示的值可以是一個常量表達式,其中允許包括前面已經定義的巨集識別符號。例如:
            #define ONE 1
            #define TWO 2
            #define THREE (ONE+TWO)
        注意上面的巨集定義使用了括號。儘管它們並不是必須的。但出於謹慎考慮,還是應該加上括號的。例如:
            six=THREE*TWO;
        預處理過程把上面的一行程式碼轉換成:
            six=(ONE+TWO)*TWO;
        如果沒有那個括號,就轉換成six=ONE+TWO*TWO;了。
        巨集還可以代表一個字串常量,例如:
            #define VERSION "Version 1.0 Copyright(c) 2003"
    2.帶引數的#define指令
        帶引數的巨集和函式呼叫看起來有些相似。看一個例子:
            #define Cube(x) (x)*(x)*(x)
        可以時任何數字表達式甚至函式呼叫來代替引數x。這裡再次提醒大家注意括號的使用。巨集展開後完全包含在一對括號中,而且引數也包含在括號中,這樣就保證了巨集和引數的完整性。看一個用法:
            int num=8+2;
            volume=Cube(num);
        展開後為(8+2)*(8+2)*(8+2);
        如果沒有那些括號就變為8+2*8+2*8+2了。
        下面的用法是不安全的:
            volume=Cube(num++);
        如果Cube是一個函式,上面的寫法是可以理解的。但是,因為Cube是一個巨集,所以會產生副作用。這裡的擦書不是簡單的表示式,它們將產生意想不到的結果。它們展開後是這樣的:
            volume=(num++)*(num++)*(num++);
        很顯然,結果是10*11*12,而不是10*10*10;
        那麼怎樣安全的使用Cube巨集呢?必須把可能產生副作用的操作移到巨集呼叫的外面進行:
            int num=8+2;
            volume=Cube(num);
            num++;
    3.#運算子
        出現在巨集定義中的#運算子把跟在其後的引數轉換成一個字串。有時把這種用法的#稱為字串化運算子。例如:

            #define PASTE(n) "adhfkj"#n

            main()
            {
               printf("%s\n",PASTE(15));
            }
        巨集定義中的#運算子告訴預處理程式,把原始碼中任何傳遞給該巨集的引數轉換成一個字串。所以輸出應該是adhfkj15。
    4.##運算子
        ##運算子用於把引數連線到一起。預處理程式把出現在##兩側的引數合併成一個符號。看下面的例子:

            #define NUM(a,b,c) a##b##c
            #define STR(a,b,c) a##b##c

            main()
            {
                printf("%d\n",NUM(1,2,3));
                printf("%s\n",STR("aa","bb","cc"));
            }

        最後程式的輸出為:
                 123
                 aabbcc
        千萬別擔心,除非需要或者巨集的用法恰好和手頭的工作相關,否則很少有程式設計師會知道##運算子。絕大多數程式設計師從來沒用過它。

三、條件編譯指令
    條件編譯指令將決定那些程式碼被編譯,而哪些是不被編譯的。可以根據表示式的值或者某個特定的巨集是否被定義來確定編譯條件。
    1.#if指令
        #if指令檢測跟在製造另關鍵字後的常量表達式。如果表示式為真,則編譯後面的程式碼,知道出現#else、#elif或#endif為止;否則就不編譯。
    2.#endif指令
        #endif用於終止#if預處理指令。

            #define DEBUG 0
            main()
            {
                #if DEBUG
                    printf("Debugging\n");
                #endif
                    printf("Running\n");
            }

        由於程式定義DEBUG巨集代表0,所以#if條件為假,不編譯後面的程式碼直到#endif,所以程式直接輸出Running。
        如果去掉#define語句,效果是一樣的。
    3.#ifdef和#ifndef
        #define DEBUG

        main()
        {
            #ifdef DEBUG
                printf("yes\n");
            #endif
            #ifndef DEBUG
                printf("no\n");
            #endif
        }
        #if defined等價於#ifdef; #if !defined等價於#ifndef
    4.#else指令
        #else指令用於某個#if指令之後,當前面的#if指令的條件不為真時,就編譯#else後面的程式碼。#endif指令將中指上面的條件塊。

        #define DEBUG

        main()
        {
            #ifdef DEBUG
                printf("Debugging\n");
            #else
                printf("Not debugging\n");
            #endif
                printf("Running\n");
       }

    5.#elif指令
        #elif預處理指令綜合了#else和#if指令的作用。

        #define TWO

        main()
        {
            #ifdef ONE
                printf("1\n");
            #elif defined TWO
                printf("2\n");
            #else
                printf("3\n");
            #endif
        }
        程式很好理解,最後輸出結果是2。

    6.其他一些標準指令
        #error指令將使編譯器顯示一條錯誤資訊,然後停止編譯。
        #line指令可以改變編譯器用來指出警告和錯誤資訊的檔案號和行號。
        #pragma指令沒有正式的定義。編譯器可以自定義其用途。典型的用法是禁止或允許某些煩人的警告資訊。
    
    7.編譯器預定義的巨集
    __cplusplus__
    __STDC__
    __LINE__
    __FILE__
    __FUNCTION__
    __TIME__
    __DATE__