C/C++預處理指令#define,#ifdef,#ifndef,#endif…

本文主要記錄了C/C++預處理指令,常見的預處理指令以下:html

  1. #空指令,無任何效果
  2. #include包含一個源代碼文件
  3. #define定義宏
  4. #undef取消已定義的宏
  5. #if若是給定條件爲真,則編譯下面代碼
  6. #ifdef若是宏已經定義,則編譯下面代碼
  7. #ifndef若是宏沒有定義,則編譯下面代碼
  8. #elif若是前面的#if給定條件不爲真,當前條件爲真,則編譯下面代碼
  9. #endif結束一個#if……#else條件編譯塊
  10. #error中止編譯並顯示錯誤信息

原本只是想了解一下#ifdef,#ifndef,#endif的,沒想到查出來這麼多的預處理指令,上面的多數都是常見的,可是平時沒有怎麼注意預處理這方面的內容,因此這裏梳理一下知識吧。同時有什麼不妥的地方,或者遺漏了什麼內容,還請留言指出。linux


什麼是預處理指令?編程

預處理指令是以#號開頭的代碼行。#號必須是該行除了任何空白字符外的第一個字符。#後是指令關鍵字,在關鍵字和#號之間容許存在任意個數的空白字符。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯以前對源代碼作某些轉換。windows

之前沒有在乎的學者注意了,預處理指令是在編譯器進行編譯以前進行的操做.預處理過程掃描源代碼,對其進行初步的轉換,產生新的源代碼提供給編譯器。可見預處理過程先於編譯器對源代碼進行處理。在不少編程語言中,並無任何內在的機制來完成以下一些功能:在編譯時包含其餘源文件、定義宏、根據條件決定編譯時是否包含某些代碼(防止重複包含某些文件)。要完成這些工做,就須要使用預處理程序。儘管在目前絕大多數編譯器都包含了預處理程序,但一般認爲它們是獨立於編譯器的。預處理過程讀入源代碼,檢查包含預處理指令的語句和宏定義,並對源代碼進行響應的轉換。預處理過程還會刪除程序中的註釋和多餘的空白字符。數組


#include包含一個源代碼文件編程語言

這個預處理指令,我想是見得最多的一個,簡單說一下,第一種方法是用尖括號把頭文件括起來。這種格式告訴預處理程序在編譯器自帶的或外部庫的頭文件中搜索被包含的頭文件。第二種方法是用雙引號把頭文件括起來。這種格式告訴預處理程序在當前被編譯的應用程序的源代碼文件中搜索被包含的頭文件,若是找不到,再搜索編譯器自帶的頭文件。採用兩種不一樣包含格式的理由在於,編譯器是安裝在公共子目錄下的,而被編譯的應用程序是在它們本身的私有子目錄下的。一個應用程序既包含編譯器提供的公共頭文件,也包含自定義的私有頭文件。採用兩種不一樣的包含格式使得編譯器可以在不少頭文件中區別出一組公共的頭文件。函數


#define定義宏工具

有關#define這個宏定義,在C語言中使用的不少,由於#define存在一些不足,C++強調使用const來定義常量。宏定義了一個表明特定內容的標識符。預處理過程會把源代碼中出現的宏標識符替換成宏定義時的值。記住僅僅是進行標識符的替換。下面列舉一些#define的使用:spa

  1. 用#define實現求最大值和最小值的宏
    #include <stdio.h>
    #define MAX(x,y) (((x)>(y))?(x):(y))
    #define MIN(x,y) (((x)<(y))?(x):(y))
    int main(void)
    {
    #ifdef MAX    //判斷這個宏是否被定義
        printf("3 and 5 the max is:%d\n",MAX(3,5));
    #endif
    #ifdef MIN
        printf("3 and 5 the min is:%d\n",MIN(3,5));
    #endif
        return 0;
    }
    
    /*
     * (1)三元運算符要比if,else效率高
     * (2)宏的使用必定要細心,須要把參數當心的用括號括起來,
     * 由於宏只是簡單的文本替換,不注意,容易引發歧義錯誤。
    */
  2. 宏定義的錯誤使用
    #include <stdio.h>
    #define SQR(x) (x*x)
    int main(void)
    {
        int b=3;
    #ifdef SQR//只須要宏名就能夠了,不須要參數,有參數的話會警告
        printf("a = %d\n",SQR(b+2));
    #endif
        return 0;
    }
    
    /*
     *首先說明,這個宏的定義是錯誤的。並無實現程序中的B+2的平方
     * 預處理的時候,替換成以下的結果:b+2*b+2
     * 正確的宏定義應該是:#define SQR(x) ((x)*(x))
     * 因此,儘可能使用小括號,將參數括起來。
    */
  3. 宏參數的鏈接
    #include <stdio.h>
    #define STR(s) #s
    #define CONS(a,b) (int)(a##e##b)
    int main(void)
    {
    #ifdef STR
        printf(STR(VCK));
    #endif
    #ifdef CONS
        printf("\n%d\n",CONS(2,3));
    #endif
        return 0;
    }
    
    /* (絕大多數是使用不到這些的,使用到的話,查看手冊就能夠了)
     * 第一個宏,用#把參數轉化爲一個字符串
     * 第二個宏,用##把2個宏參數粘合在一塊兒,及aeb,2e3也就是2000
    */
  4. 用宏獲得一個字的高位或低位的字節
    #include <stdio.h>
    #define WORD_LO(xxx) ((byte)((word)(xxx) & 255))
    #define WORD_HI(xxx) ((byte)((word)(xxx) >> 8))
    int main(void)
    {
        return 0;
    }
    
    /*
     * 一個字2個字節,得到低字節(低8位),與255(0000,0000,1111,1111)按位相與
     * 得到高字節(高8位),右移8位便可。
    */
  5. 用宏定義獲得一個數組所含元素的個數
    #include <stdio.h>
    #define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))
    int main(void)
    {
        int array[100];
    #ifdef ARR_SIZE
        printf("array has %d items.\n",ARR_SIZE(array));
    #endif
        return 0;
    }
    /*
     *總的大小除以每一個類型的大小
     */

關於#define宏的使用,應該特別當心,尤爲是含有參數計算的時候如小2示例,最保險的作法將參數用括號括起來。調試


#ifdef,#ifndef,#endif...的使用

以上這些預編譯指令,都是條件編譯指令,也就是說,將決定那些代碼被編譯,而哪些不被編譯。

  1. 示例1:
    #include <stdio.h>
    #include <stdlib.h>
    #define DEBUG
    int main(void)
    {
        int i = 0;
        char c;
        while(1)
        {
            i++;
            c = getchar();
            if('\n' != c)
            {
                getchar();
            }
            if('q' == c || 'Q' == c)
            {
    #ifdef DEBUG//判斷DEBUG是否被定義了
                printf("We get:%c,about to exit.\n",c);
    #endif
                break;
            }
            else
            {
                printf("i = %d",i);
    #ifdef DEBUG
                printf(",we get:%c",c);
    #endif
                printf("\n");
            }
        }
        printf("Hello World!\n");
        return 0;
    }
    
    /*#endif用於終止#if預處理指令。*/
  2. ifdef 和 #ifndef
    #include <stdio.h>
    #define DEBUG
    main()
    {
    #ifdef DEBUG
        printf("yes ");
    #endif
    #ifndef DEBUG
        printf("no ");
    #endif
    }
    //#ifdefined等價於#ifdef;
    //#if!defined等價於#ifndef
  3. #else指令

    001

  4. #elif指令

    002

  5. 其餘一些指令
    #error指令將使編譯器顯示一條錯誤信息,而後中止編譯。
    #line指令能夠改變編譯器用來指出警告和錯誤信息的文件號和行號。
    #pragma指令沒有正式的定義。編譯器能夠自定義其用途。典型的用法是禁止或容許某些煩人的警告信息。

小結:

預處理就是在進行編譯的第一遍詞法掃描和語法分析以前所做的工做。說白了,就是對源文件進行編譯前,先對預處理部分進行處理,而後對處理後的代碼進行編譯。這樣作的好處是,通過處理後的代碼,將會變的很精短。


參考資料:晚上的影子

 

 

2016年11月12日更新:

寫這篇博文的時候, 尚未參加工做. 如今回過頭來, 感受這篇內容寫的仍是很晦澀難懂. 由於當時的我處於學生時代, 對於技術的理解只有輸入, 沒有過多的工程化的輸出, 致使一些東西理解的仍是不夠透徹. 過於這些宏的理解, 目前, 能夠簡單的作一下的總結(宏的基礎知識,往下看便可);

工做中常常這樣使用宏:

1. 經常使用宏來調試代碼:

#if 0
///< 舊的代碼(或函數) (舊的代碼, 將會被預處理的時候,屏蔽掉, 不進行編譯)
#else
///< 新的代碼(或函數)
#endif

#ifndef JOE_DEBUG
///< 新的代碼(或函數)
#else
///< 舊的代碼(或函數) (舊的代碼, 將會被預處理的時候,屏蔽掉, 不進行編譯)
#endif

#ifdef Q_DEBUG
///< 新的代碼(或函數)
#else
///< 舊的代碼(或函數) (舊的代碼, 將會被預處理的時候,屏蔽掉, 不進行編譯)
#endif

  經過以上相似的方法, 能夠防止因爲過多的修改代碼, 而把代碼修改的一塌糊塗. 建議修改代碼的時候, 作到保護好之前的代碼, 儘可能不進行代碼的刪除操做. 切記, 能不刪除, 就不刪除...不要養成隨手就刪除的習慣. 要養成使用宏和註釋代碼的習慣. 

  2. 使用宏來根據不一樣的平臺包含不一樣的文件. 不少時候, 咱們的代碼是須要跨系統平臺編譯和運行的. 好比: 一個小功能代碼, 須要既能夠在Win下面運行,  還要能夠在Max, linux上面運行. 但是, 由於系統的不同, 有些時候, 頭文件的包含的名字是不同的. 因此,這時候, 就是用到了宏. 由於咱們使用編程工具分不一樣的系統平臺, 編程工具自身的環境就會包含不一樣平臺的系統宏, 假設OS_Win, OS_Mac, OS_Linux 分別代碼三種系統不一樣的宏. 並且,Win版本的編程工具中已經定義了OS_Win, 相似的Mac下, 編程工具定義的是OS_Mac, Linux...

#ifdef OS_Win
#include <windows.h>
#endif

#ifdef OS_Mac
#include <mac.h>
#endif

#ifdef OS_Linux
#include <linux.h>
#endif

/** 不只使用在頭文件的包含. 並且,對於不一樣的系統平臺. 你也可使用不一樣的代碼結構. */

   3. 接着就是宏定義了. 使用宏來定義一些常量, 表達式...詳細內容, 見下面

 


 

2016年12月29日更新:

今天查看之前文件的時候, 忽然發現了#error 這個預處理指令.而後回想一下工做, 發現這個指令使用場景仍是不少的.好比: 一個項目的模塊兒之多,源文件之大,代碼之多,那麼其中的宏, 也會不少. 免不了衝突定義.這時候, 咱們就須要編譯器能及早的告訴咱們.那就是在編譯的時候.#error就能夠這麼實現:

/** 若是JOE宏沒有定義,那麼編譯就此結束, 編譯器就會顯示紅色的錯誤 */ #ifndef JOE #error "JOE is not exits" #endif

 


 

2017年01月28日更新:

  #pragma pack

相關文章
相關標籤/搜索