(一)基本知識
前置處理器指示符以“#”號開頭標識,處理這些指示符的程式叫做前置處理器。
#include指示符讀入指定檔案的內容,它有兩種格式:
#include <some_file.h>
#include "my_file.h"
如果檔案名稱用<>括起來,表明這個檔案是一個工程或標準標頭檔,尋找過程會先檢查預定義的目錄,我
們可以通過設定環境變數或命令列來修改這些目錄。如果檔案名稱用“”引起來,則表明該檔案是使用者提供的
標頭檔,尋找該檔案時將從當前檔案所在目錄開始。
由於標頭檔的嵌套關係,一個標頭檔可能會被再次包含,條件指示符可防止這種標頭檔的重複處理,例
如:
#ifndef BOOKSTORE_H
#define BOOKSTORE_H
//bookstore.h的內容
#endif
#ifndef指明,如果某個宏未被定義,要怎麼處理
#ifdef指明,如果某個宏已被定義,要怎麼處理。
比如:
int main(){
#ifdef DEBUG
cout<<"begining execution of main/n";
#endif
string word="hello world!";
cout<<world<<endl;
}
本例中,如果未定義DEBUG,實際被編譯的代碼如下:
int main(){
string word="hello world!";
cout<<world<<endl;
}
如果已定義DEBUG,則傳給編譯器的程式碼為:
int main(){
cout<<"begining execution of main/n";
string word="hello world!";
cout<<world<<endl;
}
我們在編譯器時可以使用-D選項,並且在後面寫上前置處理器常量的名字,這樣就能在命令列中定義預
處理器常:
$CC -DDEBUG main.c
也可以在程式中用#define指示符定義前置處理器常量。
(二)自動定義的前置處理器名字
編譯C++程式時,編譯器自動定義了一個前置處理器名字__cplusplus(前面是兩個底線)。因此,我們可
以根據它來判斷該程式是否是 C++ 程式,以便有條件的包含一些代碼。如
#ifdef __cplusplus
extern "C"
#endif
int min( int, int );
在編譯標準 C 時,編譯器將自動定義__STDC__.
其他一些比較常用的預定義名字是:
__LINE__ :檔案被編譯的行數
__FILE__ :被編譯的檔案的名字
__TIME__ :當前被編譯檔案的編譯時間
__DATE__ :當前被編譯檔案的編譯日期
另外, C99新增了 __func__ 指示被編譯的函數的名字,對於gcc,與__func__等價的名字為
__FUNCTION__ .
上述預定義的名字對寫trace有很大的協助,比如:
if (i==0)
cerr<<"error:"<<__FILE__<<":line:"<<__LINE<<":func"<<__func__<<"i must be non-zero/n";
(三)C與C++的標頭檔
C庫檔案的C++名字總是以字母C開頭,後面是去掉尾碼.h的C名字,如assert():如果使用C++的標頭檔則
為cassert,C則為assert.h,所以使用標頭檔的C名字或C++名字,方法也不相同。仍以assert為例:
C++: #include <cassert>
using namespace std;
C: #include <assert.h>
(四)宏替換
在 ISO C 之前,從未詳細描述宏替換進程。這種不明確性產生很多有分歧的實現。依賴於比明顯常量
替換和簡單類函數宏更奇特的事情的任何代碼可能並不真正可移植。此外,ISO C 宏替換演算法可以完成在舊
C 版本中無法完成的工作。例如:
#define name (*name)
使 name 的任何使用均替換為通過 name 進行的間接引用。舊 C 預先處理程式會產生大量
圓括弧和星號,並最終產生關於宏遞迴的錯誤。
ISO C 對宏替換方法的主要更改是要求宏參數,而不是要求那些本身是宏替換操作符 # 和 ## 的操作
數並且在替換標記列表中替換之前要遞迴擴充的參數。然而,這種更改很少在結果標記中產生實際差異。
比如,使用 # 宏替換操作符和字串文字共置
#define str(a) #a "!"
str(x y)
以上代碼產生兩個字串文字 "x y" 和 "!",它們在共置後產生相同的 "x y!"
對於可變參數的宏,gnuc與sun cc相同的處理方式如下:
#define identifier (...) replacement_list
#define identifier (identifier_list, ...) replacement_list
如果列出的宏參數以省略符號結尾,那麼該宏的調用允許使用除了宏參數以外的其他更多參數。附加參數
被收集在一個單獨的字串中,該字串可以包括逗號。可以使用宏其他清單中的名稱 __VA_ARGS__ 來引
用這些附加參數(replacement_list的可變參數用__VA_ARGS__替換).
而對於 gnu c 來說,還有一種方式,使用##args來引用(replacement_list的可變參數為##args),具
體的定義方式也有所不同:
#define identifier (args...) replacement_list
#define identifier (identifier_list, args...) replacement_list
(五)宏中"#"和"##"的用法
I、一般用法
我們使用#把宏參數變為一個字串,用##把兩個宏參數貼合在一起.
用法:
#include<cstdio>
#include<climits>
using namespace std;
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
int main()
{
printf(STR(vck)); // 輸出字串"vck"
printf("%d/n", CONS(2,3)); // 2e3 輸出:2000
return 0;
}
II、當宏參數是另一個宏的時候
需要注意的是凡宏定義裡有用'#'或'##'的地方宏參數是不會再展開.
1, 非'#'和'##'的情況
#define TOW (2)
#define MUL(a,b) (a*b)
printf("%d*%d=%d/n", TOW, TOW, MUL(TOW,TOW));
這行的宏會被展開為:
printf("%d*%d=%d/n", (2), (2), ((2)*(2)));
MUL裡的參數TOW會被展開為(2).
2, 當有'#'或'##'的時候
#define A (2)
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
printf("int max: %s/n", STR(INT_MAX)); // INT_MAX #i nclude<climits>
這行會被展開為:
printf("int max: %s/n", "INT_MAX");
printf("%s/n", CONS(A, A)); // compile error
這一行則是:
printf("%s/n", int(AeA));
INT_MAX和A都不會再被展開, 然而解決這個問題的方法很簡單. 加多一層中間轉換宏.
加這層宏的用意是把所有宏的參數在這層裡全部展開, 那麼在轉換宏裡的那一個宏(_STR)就能得到正確的宏
參數.
#define A (2)
#define _STR(s) #s
#define STR(s) _STR(s) // 轉換宏
#define _CONS(a,b) int(a##e##b)
#define CONS(a,b) _CONS(a,b) // 轉換宏
printf("int max: %s/n", STR(INT_MAX)); // INT_MAX,int型的最大值,為一個變數
#include<climits>
輸出為: int max: 0x7fffffff
STR(INT_MAX) --> _STR(0x7fffffff) 然後再轉換成字串;
printf("%d/n", CONS(A, A));
輸出為:200
CONS(A, A) --> _CONS((2), (2)) --> int((2)e(2))
III、'#'和'##'的一些應用特例
1、合并匿名變數名
#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line)
#define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int); 即: static int _anonymous70; 70表示該行行號;
第一層:ANONYMOUS(static int); --> __ANONYMOUS0(static int, __LINE__);
第二層: --> ___ANONYMOUS1(static int, _anonymous, 70);
第三層: --> static int _anonymous70;
即每次只能解開當前層的宏,所以__LINE__在第二層才能被解開;
2、填充結構
#define FILL(a) {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG{
IDD id;
const char * msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相當於:
MSG _msg[] = {{OPEN, "OPEN"},
{CLOSE, "CLOSE"}};
3、記錄檔案名稱
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
4、得到一個數實值型別所對應的字串緩衝大小
#define _TYPE_BUF_SIZE(type) sizeof #type
#define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_MAX)];
--> char buf[_TYPE_BUF_SIZE(0x7fffffff)];
--> char buf[sizeof "0x7fffffff"];
這裡相當於:
char buf[11];