c/c++ 宏中"#"和"##"的用法
2007年05月14日 星期一 上午 10:19
一、一般用法
我們使用#把宏參數(shù)變?yōu)橐粋€(gè)字符串,用##把兩個(gè)宏參數(shù)貼合在一起.
用法:
#i nclude<cstdio>
#i nclude<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;
}
二、當(dāng)宏參數(shù)是另一個(gè)宏的時(shí)候
需要注意的是凡宏定義里有用'#'或'##'的地方宏參數(shù)是不會(huì)再展開(kāi).
1, 非'#'和'##'的情況
#define TOW (2)
#define MUL(a,b) (a*b)
printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));
這行的宏會(huì)被展開(kāi)為:
printf("%d*%d=%d\n", (2), (2), ((2)*(2)));
MUL里的參數(shù)TOW會(huì)被展開(kāi)為(2).
2, 當(dāng)有'#'或'##'的時(shí)候
#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>
這行會(huì)被展開(kāi)為:
printf("int max: %s\n", "INT_MAX");
printf("%s\n", CONS(A, A)); // compile error
這一行則是:
printf("%s\n", int(AeA));
INT_MAX和A都不會(huì)再被展開(kāi), 然而解決這個(gè)問(wèn)題的方法很簡(jiǎn)單. 加多一層中間轉(zhuǎn)換宏.
加這層宏的用意是把所有宏的參數(shù)在這層里全部展開(kāi), 那么在轉(zhuǎn)換宏里的那一個(gè)宏(_STR)就能得到正確的宏參數(shù).
#define A (2)
#define _STR(s) #s
#define STR(s) _STR(s) // 轉(zhuǎn)換宏
#define _CONS(a,b) int(a##e##b)
#define CONS(a,b) _CONS(a,b) // 轉(zhuǎn)換宏
printf("int max: %s\n", STR(INT_MAX)); // INT_MAX,int型的最大值,為一個(gè)變量 #i nclude<climits>
輸出為: int max: 0x7fffffff
STR(INT_MAX) --> _STR(0x7fffffff) 然后再轉(zhuǎn)換成字符串;
printf("%d\n", CONS(A, A));
輸出為:200
CONS(A, A) --> _CONS((2), (2)) --> int((2)e(2))
三、'#'和'##'的一些應(yīng)用特例
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表示該行行號(hào);
第一層:ANONYMOUS(static int); --> __ANONYMOUS0(static int, __LINE__);
第二層: --> ___ANONYMOUS1(static int, _anonymous, 70);
第三層: --> static int _anonymous70;
即每次只能解開(kāi)當(dāng)前層的宏,所以__LINE__在第二層才能被解開(kāi);
2、填充結(jié)構(gòu)
#define FILL(a) {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG{
IDD id;
const char * msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相當(dāng)于:
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、得到一個(gè)數(shù)值類(lèi)型所對(duì)應(yīng)的字符串緩沖大小
#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"];
這里相當(dāng)于:
char buf[11];
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
c和c++調(diào)式 利用宏獲得函數(shù)名
僅僅為了獲取函數(shù)名,就在函數(shù)體中嵌入硬編碼的字符串,這種方法單調(diào)乏味還易導(dǎo)致錯(cuò)誤,不如看一下怎樣使用新的C99特性,在程序運(yùn)行時(shí)獲取函數(shù)名吧。
對(duì)象反射庫(kù)、調(diào)試工具及代碼分析器,經(jīng)常會(huì)需要在運(yùn)行時(shí)訪(fǎng)問(wèn)函數(shù)的名稱(chēng),直到不久前,唯一能完成此 項(xiàng)任務(wù)并且可移植的方法,是手工在函數(shù)體內(nèi)嵌入一個(gè)帶有該函數(shù)名的硬編碼字符串,不必說(shuō),這種方法非常單調(diào)無(wú)奇,并且輕易導(dǎo)致錯(cuò)誤。本文將要演示怎樣使用 新的C99特性,在運(yùn)行時(shí)獲取函數(shù)名。
那么怎樣以編程的方式從當(dāng)前運(yùn)行的函數(shù)中得到函數(shù)名呢?
答案是:使用__FUNCTION__ 及相關(guān)宏。
引出問(wèn)題
通常,在調(diào)試中最讓人心煩的階段,是不斷地檢查是否已調(diào)用了特定的函數(shù)。對(duì)此問(wèn)題的解決方法,一般是添加一個(gè)cout或printf()——假如你使用C語(yǔ)言,如下所示:
void myfunc()
{
cout<<"myfunc()"<<endl;
//其他代碼
}
通常在一個(gè)典型的工程中,會(huì)包含有數(shù)千個(gè)函數(shù),要在每個(gè)函數(shù)中都加入一條這樣的輸出語(yǔ)句,無(wú)疑難過(guò)上“蜀山”啊,因此,需要有一種機(jī)制,可以自動(dòng)地完成這項(xiàng)操作。
獲取函數(shù)名
作為一個(gè)C++程序員,可能經(jīng)常碰到 __TIME__、__FILE__、__DATE__ ,__LINE__ 這樣的宏,它們會(huì)在編譯時(shí),分別轉(zhuǎn)換為包含編譯時(shí)間、處理的轉(zhuǎn)換單元名稱(chēng)及當(dāng)前時(shí)間的字符串。
在最新的ISO C標(biāo)準(zhǔn)中,如大家所知的C99,加入了另一個(gè)有用的、類(lèi)似宏的表達(dá)式__func__,其會(huì)報(bào)告未修飾過(guò)的(也就是未裁剪過(guò)的)、正在被訪(fǎng)問(wèn)的函數(shù)名。請(qǐng) 注重,__func__不是一個(gè)宏,因?yàn)轭A(yù)處理器對(duì)此函數(shù)一無(wú)所知;相反,它是作為一個(gè)隱式聲明的常量字符數(shù)組實(shí)現(xiàn)的:
static const char __func__[] = "function-name";
在function-name處,為實(shí)際的函數(shù)名。為激活此特性,某些編譯器需要使用特定的編譯標(biāo)志,請(qǐng)查看相應(yīng)的編譯器文檔,以獲取具體的資料。
有了它,我們可免去大多數(shù)通過(guò)手工修改,來(lái)顯示函數(shù)名的苦差事,以上的例子可如下所示進(jìn)行重寫(xiě):
void myfunc()
{
cout<<"__FUNCTION__"<<endl;
}
官 方C99標(biāo)準(zhǔn)為此目的定義的__func__標(biāo)識(shí)符,確實(shí)值得大家關(guān)注,然而,ISO C++卻不完全支持所有的C99擴(kuò)展,因此,大多數(shù)的編譯器提供商都使用 __FUNCTION__ 取而代之,而 __FUNCTION__ 通常是一個(gè)定義為 __func__ 的宏,之所以使用這個(gè)名字,是因?yàn)樗咽艿搅舜蠖鄶?shù)的廣泛支持。
在Visual Studio 2005中,默認(rèn)情況下,此特性是激活的,但不能與/EP和/P編譯選項(xiàng)同時(shí)使用。請(qǐng)注重在IDE環(huán)境中,不能識(shí)別__func__ ,而要用__FUNCTION__ 代替。
Comeau的用戶(hù)也應(yīng)使用 __FUNCTION__ ,而不是 __func__ 。
C++ BuilderX的用戶(hù)則應(yīng)使用稍稍不同的名字:__FUNC__ 。
GCC 3.0及更高的版本同時(shí)支持 __func__ 和__FUNCTION__ 。
一旦可自動(dòng)獲取當(dāng)前函數(shù)名,你可以定義一個(gè)如下所示顯示任何函數(shù)名的函數(shù):
void show_name(const char * name)
{
cout<<name<<endl;
}
void myfunc()
{
show_name(__FUNCTION__); //輸出:myfunc
}
void foo()
{
show_name(__FUNCTION__); //輸出:foo
}
因?yàn)?__FUNCTION__ 會(huì)在函數(shù)大括號(hào)開(kāi)始之后就立即初始化,所以,foo()及myfunc()函數(shù)可在參數(shù)列表中安全地使用它,而不用擔(dān)心重載。
簽名與修飾名
__FUNCTION__ 特性最初是為C語(yǔ)言設(shè)計(jì)的,然而,C++程序員也會(huì)經(jīng)常需要有關(guān)他們函數(shù)的額外信息,在Visual Studio 2005中,還支持另外兩種非標(biāo)準(zhǔn)的擴(kuò)展特性:__FUNCDNAME__ 與 __FUNCSIG__ ,其分別轉(zhuǎn)譯為一個(gè)函數(shù)的修飾名與簽名。函數(shù)的修飾名非常有用,例如,在你想要檢查兩個(gè)編譯器是否共享同樣的ABI時(shí),就可派得上用場(chǎng),另外,它還能幫助 你破解那些含義模糊的鏈接錯(cuò)誤,甚至還可用它從一個(gè)DLL中調(diào)用另一個(gè)用C++鏈接的函數(shù)。在下例中,show_name()報(bào)告了函數(shù)的修飾名:
void myfunc()
{
show_name(__FUNCDNAME__); //輸出:
?myfunc@@YAXXZ}
一 個(gè)函數(shù)的簽名由函數(shù)名、參數(shù)列表、返回類(lèi)型、內(nèi)含的命名空間組成。假如它是一個(gè)成員函數(shù),它的類(lèi)名和const/volatile限定符也將是簽名的 一部分。以下的代碼演示了一個(gè)獨(dú)立的函數(shù)與一個(gè)const成員函數(shù)簽名間的不同之處,兩個(gè)函數(shù)的名稱(chēng)、返回類(lèi)型、參數(shù)完全相同:
void myfunc()
{
show_name(__FUNCSIG__); // void __cdecl myfunc(void)
}
struct S
{
void myfunc() const
{
show_name(__FUNCSIG__); //void __thiscall S::myfunc(void) const
}
};