(轉載)#define、#undef、#ifdef、#ifndef、#if、#elif、#else、#endif、defined 的用法

轉載來自:https://www.cnblogs.com/fazero/p/5016514.html)

(轉載來自:https://blog.csdn.net/qq_33658067/article/details/79443014)

#ifndef 作用:防止頭文件的重複包含和編譯

定義

  #ifndef x

  #define x

  ...

  #endif

  這是宏定義的一種,它可以根據是否已經定義了一個變量來進行分支選擇,一般用於調試等等.實際上確切的說這應該是預處理功能中三種(宏定義,文件包含和條件編譯)中的一種----條件編譯。 C語言在對程序進行編譯時,會先根據預處理命令進行“預處理”。C語言編譯系統包括預處理,編譯和鏈接等部分。

        條件指示符#ifndef檢查預編譯常量在前面是否已經被宏定義。如果在前面沒有被宏定義,則條件指示符的值爲真,於是從#ifndef到#endif之間的所有語句都被包含進來進行編譯處理。相反,如果#ifndef指示符的值爲假,則它與#endif指示符之間的行將被忽略。條件指示符#ifndef 的最主要目的是防止頭文件的重複包含和編譯。

  #ifndef x

  //先測試x是否被宏定義過

  #define x

  //如果沒有宏定義下面就宏定義x並編譯下面的語句

  ...

  #endif

  //如果已經定義了則編譯#endif後面的語句

補充一些內容

  千萬不要忽略了頭件的中的#ifndef,這是一個很關鍵的東西。比如你有兩個C文件,這兩個C文件都include了同一個頭文件。而編譯時,這兩個C文件要一同編譯成一個可運行文件,於是問題來了,大量的聲明衝突。

  還是把頭文件的內容都放在#ifndef和#endif中吧。不管你的頭文件會不會被多個文件引用,你都要加上這個。一般格式是這樣的:

  #ifndef <標識>

  #define <標識>

  ......

  #endif

  <標識>在理論上來說可以是自由命名的,但每個頭文件的這個“標識”都應該是唯一的。標識的命名規則一般是頭文件名全大寫,前後加下劃線,並把文件名中的“.”也變成下劃線,如:stdio.h

  #ifndef _STDIO_H_

  #define _STDIO_H_

  ......

        #endif

const 與 #define的比較
    C++ 語言可以用const來定義常量,也可以用 #define來定義常量。但是前者比後者有更多的優點:
(1)       const常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,並且在字符替換可能會產生意料不到的錯誤(邊際效應)。
(2)       有些集成化的調試工具可以對const常量進行調試,但是不能對宏常量進行調試。
        【規則5-2-1】在C++ 程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。

 

 

本文主要介紹c語言中條件編譯相關的預編譯指令,包括  #define、#undef、#ifdef、#ifndef、#if、#elif、#else、#endif、defined。

#define            定義一個預處理宏
#undef            取消宏的定義

#if                   編譯預處理中的條件命令,相當於C語法中的if語句
#ifdef              判斷某個宏是否被定義,若已定義,執行隨後的語句
#ifndef            與#ifdef相反,判斷某個宏是否未被定義
#elif                若#if, #ifdef, #ifndef或前面的#elif條件不滿足,則執行#elif之後的語句,相當於C語法中的else-if
#else              與#if, #ifdef, #ifndef對應, 若這些條件不滿足,則執行#else之後的語句,相當於C語法中的else
#endif             #if, #ifdef, #ifndef這些條件命令的結束標誌.
defined          與#if, #elif配合使用,判斷某個宏是否被定義

二、條件編譯

條件編譯是根據實際定義宏(某類條件)進行代碼靜態編譯的手段。可根據表達式的值或某個特定宏是否被定義來確定編譯條件。

最常見的條件編譯是防止重複包含頭文件的宏,形式跟下面代碼類似:

1 #ifndef ABCD_H
2 #define ABCD_H
3 
4 // ... some declaration codes
5 
6 #endif // #ifndef ABCD_H

在實現文件中通常有如下類似的定義:

 1 #ifdef _DEBUG
 2 
 3 // ... do some operations
 4 
 5 #endif
 6 
 7 #ifdef _WIN32
 8 
 9 // ... use  Win32 API
10 
11 #endif

這些都是條件編譯的常用情境。

四、預編譯指令應用舉例

1. #define、#undef

#define命令定義一個宏:
#define MACRO_NAME[(args)] [tokens[(opt)]]
之後出現的MACRO_NAME將被替代爲所定義的標記(tokens)。宏可帶參數,而後面的標記也是可選的。

宏定義,按照是否帶參數通常分爲對象宏、函數宏兩種。
對象宏: 不帶參數的宏被稱爲"對象宏(objectlike macro)"。對象宏多用於定義常量、通用標識。例如:

// 常量定義
#define MAX_LENGTH 100
// 通用標識,日誌輸出宏
#define SLog printf
// 預編譯宏
#define _DEBUG

函數宏:帶參數的宏。利用宏可以提高代碼的運行效率: 子程序的調用需要壓棧出棧, 這一過程如果過於頻繁會耗費掉大量的CPU運算資源。 所以一些代碼量小但運行頻繁的代碼如果採用帶參數宏來實現會提高代碼的運行效率。但多數c++程序不推薦使用函數宏,調試上有一定難度,可考慮使用c++的inline代替之。例如:

// 最小值函數
#define MIN(a,b) ((a)>(b)? (a):(b))
// 安全釋放內存函數
#define SAFE_DELETE(p) {if(NULL!=p){delete p; p = NULL;}}

#undef可以取消宏定義,與#define對應。

2. defined

defined用來測試某個宏是否被定義。defined(name): 若宏被定義,則返回1,否則返回0。
它與#if、#elif、#else結合使用來判斷宏是否被定義,乍一看好像它顯得多餘, 因爲已經有了#ifdef和#ifndef。defined可用於在一條判斷語句中聲明多個判別條件;#ifdef和#ifndef則僅支持判斷一個宏是否定義。

#if defined(VAX) && defined(UNIX) && !defined(DEBUG) 

和#if、#elif、#else不同,#ifdef、#ifndef、defined測試的宏可以是對象宏,也可以是函數宏。

3. #ifdef、#ifndef、#else、#endif

條件編譯中相對常用的預編譯指令。模式如下:

#ifdef ABC
// ... codes while definded ABC
#elif (CODE_VERSION > 2)
// ... codes while CODE_VERSION > 2
#else
// ... remained cases
#endif // #ifdef ABC 

#ifdef用於判斷某個宏是否定義,和#ifndef功能正好相反,二者僅支持判斷單個宏是否已經定義,上面例子中二者可以互換。如果不需要多條件預編譯的話,上面例子中的#elif和#else均可以不寫。

4. #if、#elif、#else、#endif

#if可支持同時判斷多個宏的存在,與常量表達式配合使用。常用格式如下:

#if 常量表達式1
// ... some codes
#elif 常量表達式2
// ... other codes
#elif 常量表達式3
// ...
...
#else
// ... statement
#endif

常量表達式可以是包含宏、算術運算、邏輯運算等等的合法C常量表達式,如果常量表達式爲一個未定義的宏, 那麼它的值被視爲0。

#if MACRO_NON_DEFINED // 等價於

#if 0

在判斷某個宏是否被定義時,應當避免使用#if,因爲該宏的值可能就是被定義爲0。而應當使用#ifdef或#ifndef。
注意: #if、#elif之後的宏只能是對象宏。如果宏未定義,或者該宏是函數宏,則編譯器可能會有對應宏未定義的警告。

五、總結

 

本文主要介紹c語言中有關預編譯的指令。撰寫本文的目的在於理清相關概念調用,在後續預編譯使用時可以找到最合適的指令及格式。比如同時滿足多個宏定義的預編譯、多分支預編譯、#elif和#else指令的配合等。

 

一、if條件編譯,選擇編譯

(1)        

    #if ()

        //*******

    #endif

(2)

    #if ()

        //******

    #else

        //******

    #endif

(3)

    #if ()

        //******

    #elif ()

        //******

    #elif ()

        //******

    #endif

二、注意此處不能加“()”不然會把括號也視爲宏定義的字符串

#define DBG

三、

#define  A  C(0、1...)

四、注意此處不能加“()”不然會把括號也視爲宏定義的字符串

#define  DBG

#undefine DBG

五、注意此處不能加“()”不然會把括號也視爲宏定義的字符串

(1)    

    #ifdef  DBG

        #define UNDBG

        #define UNDBG1

    #endif

(2)

    #ifundef  DBG

        #define UNDBG

        #define UNDBG

    #endif

六、符合條件“&& 和 ||”複合條件下必須加上“()”標準形式如: # if (define (DBG)) || (define (DBG1))

      (1)

      # if define DBG || define DBG1 || define DBG2

            //******

        #endif

(2)

        #if !define DBG || !define DBG2

            //******

        #endif

 

 

       1、條件編譯

              請看下面一個例子:


 
  1. #include<stdio.h>

  2. #define BB

  3. #ifdef AA

  4. #define HELLO "hello world"

  5. #elif BB

  6. #define HELLO "hello CC"

  7. #endifint main()

  8. {

  9. printf("%s\n",HELLO);

  10. return 1;

  11. }

 

        如果你覺得這個打印會是hello CC.那你就和我犯了一樣的錯誤了。如果你用gcc -E hello.c -o hello.i 編譯,(這條是預編譯命令,下面會講到。)會出現:error: #if with no expression的錯誤。原因是BB雖然定義了,但是定義的是空值,放在#elif後面就不行。因爲#elif不僅僅是檢查後面的宏有沒有定義,還會檢查其值。但是#ifdef就只是檢查後面的宏是否定義,而不管其值爲多少。讀者可以把#define

        BB改成#define AA試一下,結果就會打印hello world了。

        讀者如果有興趣,也可以把#define BB改成#define BB   0  試一試,這時用gcc -E hello.c -o hello.i預編譯可以編譯通過,但是編譯過程就不行了,因爲#elif   0爲假,HELLO沒有定義。

         這幾個宏是爲了進行條件編譯。一般情況下,源程序中所有的行都參加編譯。但是有時希望對其中一部分內容只在滿足一定條件才進行編譯,也就是對一部 分內容指定編譯的條件,這就是“條件編譯”。有時,希望當滿足某條件時對一組語句進行編譯,而當條件不滿足時則編譯另一組語句。
    條件編譯命令最常見的形式爲: 
    #ifdef 標識符 
    程序段1 
    #else 
    程序段2 
    #endif 
     
    它的作用是:當標識符已經被定義過(一般是用#define命令定義),則對程序段1進行編譯,否則編譯程序段2。 
    其中#else部分也可以沒有,即: 
    #ifdef 
    程序段1 
    #denif 
     
    這裏的“程序段”可以是語句組,也可以是命令行。這種條件編譯可以提高C源程序的通用性。如果一個C源程序在不同計算機系統上運行,而不同的計算機又有一定的差異。例如,我們有一個數據類型,在Windows平臺中,應該使用long類型表示,而在其他平臺應該使用float表示,這樣往往需要對源程序作必要的修改,這就降低了程序的通用性。可以用以下的條件編譯:
    #ifdef WINDOWS 
    #define MYTYPE long 
    #else 
    #define MYTYPE float 
    #endif 
     
    如果在Windows上編譯程序,則可以在程序的開始加上 
    #define WINDOWS 
     
    這樣則編譯下面的命令行: 
    #define MYTYPE long 
     
    如果在這組條件編譯命令之前曾出現以下命令行: 
    #define WINDOWS 0 
     
    則預編譯後程序中的MYTYPE都用float代替。這樣,源程序可以不必作任何修改就可以用於不同類型的計算機系統。當然以上介紹的只是一種簡單的情況,可以根據此思路設計出其它的條件編譯。
    例如,在調試程序時,常常希望輸出一些所需的信息,而在調試完成後不再輸出這些信息。可以在源程序中插入以下的條件編譯段: 
    #ifdef DEBUG 
    print ("device_open(%p)\n", file); 
    #endif 
     
    如果在它的前面有以下命令行: 
    #define DEBUG 
     
    則在程序運行時輸出file指針的值,以便調試分析。調試完成後只需將這個define命令行刪除即可。有人可能覺得不用條件編譯也可達此目的,即在調試時加一批printf語句,調試後一一將printf語句刪除去。的確,這是可以的。但是,當調試時加的printf語句比較多時,修改的工作量是很大的。用條件編譯,則不必一一刪改printf語句,只需刪除前面的一條“#define DEBUG”命令即可,這時所有的用DEBUG作標識符的條件編譯段都使其中的printf語句不起作用,即起統一控制的作用,如同一個“開關”一樣。
    有時也採用下面的形式: 
    #ifndef 標識符 
    程序段1 
    #else 
    程序段2 
    #endif 
     
    只是第一行與第一種形式不同:將“ifdef”改爲“ifndef”。它的作用是:若標識符未被定義則編譯程序段1,否則編譯程序段2。這種形式與第一種形式的作用相反。
    以上兩種形式用法差不多,根據需要任選一種,視方便而定。 
    還有一種形式,就是#if後面的是一個表達式,而不是一個簡單的標識符: 
    #if 表達式 
    程序段1 
    #else 
    程序段2 
    #endif 
     
    它的作用是:當指定的表達式值爲真(非零)時就編譯程序段1,否則編譯程序段2。可以事先給定一定條件,使程序在不同的條件下執行不同的功能。 
    例如:輸入一行字母字符,根據需要設置條件編譯,使之能將字母全改爲大寫輸出,或全改爲小寫字母輸出。 
    #define LETTER 1 
    main() 
    { 
    char str[20]="C Language",c; 
    int i="0"; 
    while((c=str[i])!='\0'){ 
    i++; 
    #if LETTER 
    if(c>='a'&&c<='z') c="c-32"; 
    #else 
    if(c>='A'&&c<='Z') c="c"+32; 
    #endif 
    printf("%c",c); 
    } 
    } 
     
    運行結果爲:C LANGUAGE 
    現在先定義LETTER爲1,這樣在預處理條件編譯命令時,由於LETTER爲真(非零),則對第一個if語句進行編譯,運行時使小寫字母變大寫。如果將程序第一行改爲:
    #define LETTER 0 
     則在預處理時,對第二個if語句進行編譯處理,使大寫字母變成小寫字母(大寫字母與相應的小寫字母的ASCII代碼差32)。此時運行情況爲: 
    c language 
    有人會問:不用條件編譯命令而直接用if語句也能達到要求,用條件編譯命令有什麼好處呢?的確,此問題完全可以不用條件編譯處理,但那樣做目標程序長(因爲所有語句都編譯),而採用條件編譯,可以減少被編譯的語句,從而減少目標的長度。當條件編譯段比較多時,目標程序長度可以大大減少。


淺談#ifdef在軟件開發中的妙用

  筆者從事UNIX環境下某應用軟件的開發與維護工作,用戶分佈於全國各地,各用戶需要的基本功能都是一樣的,但在某些功能上要隨着需求變化,不斷加以升級,要想實現全國各地用戶的升級工作是很困難的,而我們則只是利用E-mail發送補丁程序給用戶,這些補丁程序都是在一套軟件的基礎上不斷地修改與擴充而編寫的,並由不同的標誌文件轉入到不同的模塊,雖然程序體積在不斷擴大,但絲毫不影響老用戶的功能,這主要是得益於C程序的#ifdef/#else/#endif的作用。


  我們主要使用以下幾種方法,假設我們已在程序首部定義#ifdef DEBUG與#ifdef TEST:

  1.利用#ifdef/#endif將某程序功能模塊包括進去,以向某用戶提供該功能。

  在程序首部定義#ifdef HNLD:

  #ifdef HNLD

  include"n166_hn.c"

  #endif

  如果不許向別的用戶提供該功能,則在編譯之前將首部的HNLD加一下劃線即可。

  2.在每一個子程序前加上標記,以便追蹤程序的運行。

  #ifdef DEBUG

  printf(" Now is in hunan !");

  #endif

  3.避開硬件的限制。有時一些具體應用環境的硬件不一樣,但限於條件,本地缺乏這種設備,於是繞過硬件,直接寫出預期結果。具體做法是:

  #ifndef TEST

  i=dial();

  //程序調試運行時繞過此語句

  #else

  i=0;

  #endif

  調試通過後,再屏蔽TEST的定義並重新編譯,即可發給用戶使用了。

# ifdef  #ifndef 等用法(轉)

  頭件的中的#ifndef,這是一個很關鍵的東西。比如你有兩個C文件,這兩個C文件都include了同一個頭文件。而編譯時,這兩個C文件要一同編譯成一個可運行文件,於是問題來了,大量的聲明衝突。

還是把頭文件的內容都放在#ifndef和#endif中吧。不管你的頭文件會不會被多個文件引用,你都要加上這個。一般格式是這樣的:

#ifndef <標識> 
#define <標識>

...... 

#endif

<標識>在理論上來說可以是自由命名的,但每個頭文件的這個“標識”都應該是唯一的。標識的命名規則一般是頭文件名全大寫,前後加下劃線,並把文件名中的“.”也變成下劃線,如:stdio.h

#ifndef _STDIO_H_ 
#define _STDIO_H_

......

#endif

2.在#ifndef中定義變量出現的問題(一般不定義在#ifndef中)。

#ifndef AAA
#define AAA
...
int i;
...
#endif
裏面有一個變量定義
在vc中鏈接時就出現了i重複定義的錯誤,而在c中成功編譯。

結論:

(1).當你第一個使用這個頭的.cpp文件生成.obj的時候,int i 在裏面定義了當另外一個使用這個的.cpp再次[單獨]生成.obj的時候,int i 又被定義然後兩個obj被另外一個.cpp也include 這個頭的,連接在一起,就會出現重複定義.

(2).把源程序文件擴展名改成.c後,VC按照C語言的語法對源程序進行編譯,而不是C++。在C語言中,若是遇到多個int i,則自動認爲其中一個是定義,其他的是聲明。

(3).C語言和C++語言連接結果不同,可能(猜測)是在進行編譯的時候,C++語言將全局
變量默認爲強符號,所以連接出錯。C語言則依照是否初始化進行強弱的判斷的。(參考)

解決方法:

(1).把源程序文件擴展名改成.c。

(2).推薦解決方案:
.h中只聲明 extern int i;在.cpp中定義

<x.h>
#ifndef __X_H__
#define __X_H__
extern int i;
#endif //__X_H__
<x.c>
int i;

注意問題:

(1).變量一般不要定義在.h文件中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章