1. 程式人生 > >C語言巨集定義的使用

C語言巨集定義的使用

巨集定義採用define關鍵字進行定義,是簡單的字串替換,主要分有引數和無引數兩種。這裡就平常碰到的使用方法做一個總結,方便以後檢視。 1、#define MAX_NUM 1000     普通巨集定義,最大值為1000 2、#define  NULL_PTR  ((void*)0)     定義空指標 3、用巨集定義斷言(assert)
#define  ASSERT(condition, message) {\

  if(!(condition)){\

  logError("Assertion failed:",#condition,message);\

  exit(EXIT_FAILURE);\

}\
}

4、防止一個頭檔案被重複包含,在比較大的工程中經常使用 #ifndef  _DCS_CONTAINER_H_ #define _DCS_CONTAINER_H_ #endif 5、帶引數的,例如:求最大最小值 #define   MAX( x, y )  ( ((x) > (y)) ? (x) : (y) ) 
#define   MIN( x, y )  ( ((x) < (y)) ? (x) : (y) ) 其實上面這兩種寫法不是特別好,在引數x,y帶有運算性質的時候容易出錯: 舉例:
float a = 1.0f;
float b = MIN(a++, 1.5f);
// => float b = ((a++) < (1.5f) ? (a++) : (1.5f))
// => a=3.000000, b=2.000000  //實際輸出結果(應該跟你期望的不一樣吧) 推薦:GUN C中MIN的寫法:
#define MIN(A,B)    ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
這裡定義了三個語句,分別以輸入的型別申明瞭__a和__b,並使用輸入為其賦值,接下來做一個簡單的條件比較,得到__a和__b中的較小值,並使用賦值擴充套件將結果作為返回。這樣的實現保證了不改變原來的邏輯,先進行一次賦值,也避免了括號優先順序的問題,可以說是一個比較好的解決方案。 6、預定義巨集
ANSI C標準中有幾個標準預定義巨集(也是常用的):
__LINE__:在原始碼中插入當前原始碼行號;
__FILE__:在原始檔中插入當前原始檔名; __FUNCTION__: 在原始檔中插入當前源函式名;
__DATE__:在原始檔中插入當前的編譯日期
__TIME__:在原始檔中插入當前編譯時間;
__STDC__:當要求程式嚴格遵循ANSI C標準時該標識被賦值為1;
__cplusplus:當編寫C++程式時該識別符號被定義。 程式碼:
int main(int argc, char** argv) 
{
	printf("hello world!\nfile = %s \nfunction = %s \nline = %d \ndate = %s \ntime = %s",
			__FILE__, __FUNCTION__, __LINE__, __DATE__, __TIME__);
	return 0;
}

執行結果: 7、可變引數巨集:__VA_ARGS__:
可變引數巨集不被ANSI/ISO C++ 所正式支援。C99編譯器標準允許你可以定義可變引數巨集(variadicmacros),這樣你就可以使用擁有可以變化的引數表的巨集。可變引數巨集就像下面這個樣子:
 #define DEBUG(...) printf(__VA_ARGS__)
預設號代表一個可以變化的引數表。使用保留名__VA_ARGS__把引數傳遞給巨集。當巨集的呼叫展開時,實際的引數就傳遞給 printf()了。例如:
 DEBUG("Y = %d\n", y);
而處理器會把巨集的呼叫替換成:
 printf("Y = %d\n", y); GCC中同時支援如下的形式
#define LOG(format, ...) fprintf(stdout, format, __VA_ARGS__) LOG("hello\n", 0);    輸出:hello 不過,這樣寫會有一個問題,就是當你不輸入後面的引數的時候編譯會不過。 例如:LOG(“hello\n"); 後面沒有跟引數,編譯失敗 原因:編譯後變成    fprintf("hello\n", );   多了一個逗號!! 有解決方法麼?有。”##“就要派上用場了。 ##符號在 逗號 和 引數名 之間時不做連字元作用,而作為變參巨集的特別用處,簡單理解:可以吃掉前面的空格和逗號 小技巧:
#ifdef DEBUG
#define LOG(format, ...) fprintf(stdout, ">> "format"\n", ##__VA_ARGS__)
#else
#define LOG(format, ...)
#endif
在除錯環境下,LOG巨集是一個變參輸出巨集,以自定義的格式輸出;
在釋出環境下,LOG巨集是一個空巨集,不做任何事情。 #define LOG(format, ...) fprintf(stdout, format, ##__VA_ARGS__)
#define LOG(format, args...) fprintf(stdout, format, ##args)
"##"的作用是對token進行連線,在上例中,format、__VA_ARGS__、args即是token,如果token為空,那麼不進行連線,所以允許省略可變引數(__VA_ARGS__和args)
當__VA_ARGS__是空引數時,##運算子會把前面的逗號吃掉。
##運算子把前後兩個預處理Token連線成一個預處理Token,並且變數式巨集定義中也可以用##運算子 8、特殊符號: # #將其後面的巨集引數進行字串化操作。 #define LOG(module)  fprintf(stderr, "error: "#module"\n") LOG(I = 0) 輸出:error: I = 0 9、特殊符號: ## ## 是一種分隔連線方式,先分隔,再進行強制連線。"##"的作用是對token進行連線,在前面的例子中中,format、__VA_ARGS__、args即是token,如果token為空,那麼不進行連線,所以允許省略可變引數(__VA_ARGS__和args)
當__VA_ARGS__是空引數時,##運算子會把前面的逗號吃掉。(前面已經給例子了)
##運算子把前後兩個預處理Token連線成一個預處理Token,並且變數式巨集定義中也可以用##運算子
#define NAME(n) x ## n
#define PRINT_N(n) printf("x" #n " = %d/n", x ## n);

int NAME(1) = 66; // becomes int x1 = 66;
    int NAME(2) = 88; // becomes int x2 = 88;
    PRINT_N(1); // becomes printf("x1 = %d/n", x1);
    PRINT_N(2); // becomes printf("x2 = %d/n", x2);
#define LOG(format, ...) fprintf(stdout, format, ##__VA_ARGS__)
#define LOG(format, args...) fprintf(stdout, format, ##args) 10、巨集定義中的 do{} while(0)語句 使用do{….}while(0) 把整個執行部分包裹起來,使該巨集成為一個獨立的語法單元,從而不會與上下文發生混淆。 例如:
#define action(x,y) {do something}
	
      if(x>y)
        action(x,y);
      else 
      	otheraction();
在action(x,y);  處編譯的時候出錯了,因為多了一個分號。上述巨集如果換成如下就沒問題:
#define action(x,y) \
		do{ \
		do something; \
		} while(0)