在C語言的標准庫中,printf、scanf、sscanf、sprintf、sscanf這些標准庫的輸入輸出函數,參數都是可變的。在調試程序時,我們可能希望定義一個參數可變的輸出函數來記錄日志,那么用可變參數的宏是一個不錯的選擇。
在C99中規定宏也可以像函數一樣帶可變的參數,如:
#define LOG(format, ...) fprintf(stdout, format, __VA_ARGS__)
其中,...表示可變參數列表,__VA_ARGS__在預處理中,會被實際的參數集(實參列表)所替換。
同時gcc還支持帶可以變參數名的方式(注意:VC不支持):
#define LOG(format, args...) fprintf(stdout, format, args)
同樣,args在預處理過程中,會被實際的參數集所替換。其用法和上面的方式一樣,只是參數的符號有變。
需要注意的是,上述兩種方式的可變參數不能省略,盡管可以傳一個空參數進去。說到這里,有必要提一下“##”連接符號的用法,“##”的作用是對token進行連接,上例中format,args,__VA_ARGS都可以看作是token,如果token為空,“##”則不進行連接,所以允許省略可變參數。對上述2個示例的改造:
#define LOG(format, ...) fprintf(stdout, format, ##__VA_ARGS__)
#define LOG(format, args...) fprintf(stdout, format, ##args)
即然參數可以省略,那么用宏定義一個開關,實現一個輸出日志的函數就簡單了:
#ifdef DEBUG
#define LOG(format, ...) fprintf(stdout, ">>>>>" format "<<<<", ##__VA_ARGS__)
#else
#define LOG(format, ...)
#endif
example
#include "stdio.h"
#define LOG_TYPE1(format, ...) do{ \
printf(format, __VA_ARGS__); \
\
} while(0)
#define LOG_TYPE2(format, args...) do{ \
printf(format, args); \
\
} while(0)
#define LOG_TYPE3(format, ...) do{ \
printf(format, ##__VA_ARGS__); \
\
} while(0)
#define LOG_TYPE4(format, args...) do{ \
printf(format, ##args); \
\
} while(0)
#define LOG(x) printf("LOG "#x" %d \n", x);
int value = 10;
int main()
{
printf("hello world. \n");
//LOG_TYPE1("hello %d \n"); error
LOG_TYPE1("hello %d \n", 1);
//LOG_TYPE2("hello \n"); error
LOG_TYPE2("hello %d \n", 2);
LOG_TYPE3("hello 3\n");
LOG_TYPE3("hello %d\n", 3);
LOG_TYPE4("hello 4\n");
LOG_TYPE4("hello %d\n", 4);
LOG(10);
LOG(value);
return 0;
}