1. 程式人生 > >c/c++中define用法詳解及程式碼示例

c/c++中define用法詳解及程式碼示例

c++中define用法

define在c++語言中用法比較多,這裡對其進行整理。

1.無參巨集定義

無參巨集的巨集名後不帶引數。
其定義的一般形式為:

#define  識別符號  字串

其中的“#”表示這是一條預處理命令。凡是以“#”開頭的均為預處理命令。“define”為巨集定義命令。“識別符號”為所定義的巨集名。“字串”可以是常數、表示式、格式串等。
例如:

#define MAXNUM 99999

這樣MAXNUM就被簡單的定義為99999。

2.有參巨集定義

C++語言允許巨集帶有引數。在巨集定義中的引數稱為形式引數,在巨集呼叫中的引數稱為實際引數。
對帶引數的巨集,在呼叫中,不僅要巨集展開,而且要用實參去代換形參。
帶參巨集定義的一般形式為:

 #define  巨集名(形參表)  字串

在字串中含有各個形參。在使用時呼叫帶參巨集呼叫的一般形式為:巨集名(實參表);
例如:

#define add(x, y) (x + y)
int main()
{
    cout << "1 plus 1 is " << add(1, 1.5) << ".\n";
    //輸出“1 plus 1 is 2.5.”
    system("pause");
    return(0);
}

這個“函式”定義了加法,但是該“函式”沒有型別檢查,有點類似模板,但沒有模板安全,可以看做一個簡單的模板。

注意:該“函式”定義為(a + b),在這裡加括號的原因是,巨集定義只是在預處理階段做了簡單的替換,如果單純的替換為a + b時,當你使用5 * add(2, 3)時,被替換為5 * 2 + 3,值為13,而非5 * (2 + 3),值為25。

3.巨集定義中的特殊操作符
define 中的特殊操作符有#,##和… and __VA_ARGS__
(1)#
假如希望在字串中包含巨集引數,ANSI C允許這樣作,在類函式巨集的替換部分,#符號用作一個預處理運算子,它可以把語言符號轉化程字串。例如,如果x是一個巨集參量,那麼#x可以把引數名轉化成相應的字串。該過程稱為字串化。

例如:

#incldue <stdio.h>
#define PSQR(x) printf("the square of" #x "is %d.\n",(x)*(x))
int main(void)
{
    int y =4;
    PSQR(y);
    //輸出:the square of y is 16.
PSQR(2+4); //輸出:the square of 2+4 is 36. return 0; }

(2)##

##運算子可以用於類函式巨集的替換部分。另外,##還可以用於類物件巨集的替換部分。這個運算子把兩個語言符號組合成單個語言符號。
例如:

#include <stdio.h>
#define XNAME(n) x##n
#define PXN(n) printf("x"#n" = %d\n",x##n)
int main(void)
{
    int XNAME(1)=12;//int x1=12;
    PXN(1);//printf("x1 = %d\n", x1);
    //輸出:x1=12
    return 0;
}

(3)可變引數巨集 …和__VA_ARGS__

__VA_ARGS__ 是一個可變引數的巨集,很少人知道這個巨集,這個可變引數的巨集是新的C99規範中新增的,目前似乎只有gcc支援(VC6.0的編譯器不支援)。
實現思想就是巨集定義中引數列表的最後一個引數為省略號(也就是三個點)。這樣預定義巨集__VA_ARGS__就可以被用在替換部分中,替換省略號所代表的字串。

例如:

#define PR(...) printf(__VA_ARGS__)
int main()
{
    int wt=1,sp=2;
    PR("hello\n");
    //輸出:hello
    PR("weight = %d, shipping = %d",wt,sp);
    //輸出:weight = 1, shipping = 2
    return 0;
}

省略號只能代替最後面的巨集引數。
#define W(x,…,y)錯誤!
但是支援#define W(x, …),此時傳入的引數個數必須能夠匹配。

這裡再介紹幾個系統的巨集:

1) __VA_ARGS__ 是一個可變引數的巨集,很少人知道這個巨集,這個可變引數的巨集是新的C99規範中新增的,目前似乎只有gcc支援(VC6.0的編譯器不支援)。巨集前面加上##的作用在於,當可變引數的個數為0時,這裡的##起到把前面多餘的”,”去掉的作用,否則會編譯出錯, 你可以試試。
2) __FILE__ 巨集在預編譯時會替換成當前的原始檔名
3) __LINE__巨集在預編譯時會替換成當前的行號
4) __FUNCTION__巨集在預編譯時會替換成當前的函式名稱

4.巨集定義中的多行定義

非常經典。

#define MACRO(arg1, arg2) do { /
/* declarations */ /
stmt1; /
stmt2; /
/* ... */ /
} while(0) /* (no trailing ; ) */

記得要在每一個換行的時候加上一個”/”

5.巨集定義中的條件編譯

在大規模的開發過程中,特別是跨平臺和系統的軟體裡,define最重要的功能是條件編譯。

#ifdef WINDOWS
......
(#else)
......
#endif
#ifdef LINUX
......
(#else)
......
#endif

可以在編譯的時候通過#define設定編譯環境。

6.如何取消巨集

//定義巨集
#define [MacroName] [MacroValue]
//取消巨集
#undef [MacroName]

7.防止重複包含標頭檔案
由於標頭檔案包含可以巢狀,那麼C檔案就有可能包含多次同一個標頭檔案,就可能出現重複定義的問題的。
通過條件編譯開關來避免重複包含(重複定義)
例如:

#ifndef __headerfileXXX__
#define __headerfileXXX__
…
檔案內容
…
#endif

小結及說明

1) 巨集定義是用巨集名來表示一個字串,在巨集展開時又以該字串取代巨集名,這只是一種簡單的代換,字串中可以含任何字元,可以是常數,也可以是表示式,預處理程式對它不作任何檢查。如有錯誤,只能在編譯已被巨集展開後的源程式時發現。
2) 巨集定義不是說明或語句,在行末不必加分號,如加上分號則連分號也一起置換。
3) 巨集定義必須寫在函式之外,其作用域為巨集定義命令起到源程式結束。如要終止其作用域可使用#undef命令。只要函式定義在#undefine之後,則函式無法使用#define的內容。
4) 巨集名在源程式中若用引號括起來,則預處理程式不對其作巨集代換。
5) 巨集定義允許巢狀,在巨集定義的字串中可以使用已經定義的巨集名。在巨集展開時由預處理程式層層代換。
例如:

 #define PI 3.1415926
 #define S PI*y*y          /* PI是已定義的巨集名*/

6) 習慣上巨集名用大寫字母表示,以便於與變數區別。但也允許用小寫字母。
7) 可用巨集定義表示資料型別,使書寫方便。
應注意用巨集定義表示資料型別和用typedef定義資料說明符的區別。
巨集定義只是簡單的字串代換,是在預處理完成的,而typedef是在編譯時處理的,它不是作簡單的代換,而是對型別說明符重新命名。被命名的識別符號具有型別定義說明的功能。
請看下面的例子:

#define PIN1 int *
typedef (int *) PIN2;

從形式上看這兩者相似, 但在實際使用中卻不相同。
下面用PIN1,PIN2說明變數時就可以看出它們的區別:
PIN1 a,b;在巨集代換後變成:
int *a,b;
表示a是指向整型的指標變數,而b是整型變數。
然而:
PIN2 a,b;
表示a,b都是指向整型的指標變數。因為PIN2是一個型別說明符。由這個例子可見,巨集定義雖然也可表示資料型別, 但畢竟是作字元代換。在使用時要分外小心,以避出錯。
8) 對“輸出格式”作巨集定義,可以減少書寫麻煩。
例如:

#define P printf
#define D "%d\n"
#define F "%f\n"
main(){
  int a=5, c=8, e=11;
  float b=3.8, d=9.7, f=21.08;
  P(D F,a,b);
  P(D F,c,d);
  P(D F,e,f);
}

9)帶參巨集定義中,巨集名和形參表之間不能有空格出現。
10)在帶參巨集定義中,形式引數不分配記憶體單元,因此不必作型別定義。而巨集呼叫中的實參有具體的值。要用它們去代換形參,因此必須作型別說明。這是與函式中的情況不同的。在函式中,形參和實參是兩個不同的量,各有自己的作用域,呼叫時要把實參值賦予形參,進行“值傳遞”。而在帶參巨集中,只是符號代換,不存在值傳遞的問題。
11)在巨集定義中的形參是識別符號,而巨集呼叫中的實參可以是表示式。