正如我之前的许多问题所述,我正在通过K&R工作,目前正在进入预处理器.其中一个更有意思的事情 - 我之前从未尝试过的任何学习C的尝试 - 是##
预处理器操作员.根据K&R的说法:
预处理器运算符
##
提供了一种在宏扩展期间连接实际参数的方法.如果替换文本中##
的参数与a相邻,则参数将替换为实际参数,##
并删除周围的空白区域,并重新扫描结果.例如,宏paste
连接其两个参数:
#define paste(front, back) front ## back
所以
paste(name, 1)
创建令牌name1
.
如何以及为什么有人会在现实世界中使用它?它的使用的实际例子是什么,有什么需要考虑的?
当您使用令牌粘贴(' ##
')或字符串化(' #
')预处理运算符时,需要注意的一点是,必须使用额外的间接级别才能在所有情况下正常工作.
如果你不这样做,并且传递给令牌粘贴操作符的项目本身就是宏,你将获得可能不是你想要的结果:
#include#define STRINGIFY2( x) #x #define STRINGIFY(x) STRINGIFY2(x) #define PASTE2( a, b) a##b #define PASTE( a, b) PASTE2( a, b) #define BAD_PASTE(x,y) x##y #define BAD_STRINGIFY(x) #x #define SOME_MACRO function_name int main() { printf( "buggy results:\n"); printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__))); printf( "\n" "desired result:\n"); printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__))); }
输出:
buggy results: SOME_MACRO__LINE__ BAD_PASTE( SOME_MACRO, __LINE__) PASTE( SOME_MACRO, __LINE__) desired result: function_name21
CrashRpt:使用##将宏多字节字符串转换为Unicode
CrashRpt(崩溃报告库)中的一个有趣用法如下:
#define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) //Note you need a WIDEN2 so that __DATE__ will evaluate first.
在这里,他们希望使用双字节字符串而不是每字节一个字节的字符串.这可能看起来毫无意义,但他们这样做是有充分理由的.
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);
他们将它与另一个宏一起使用,该宏返回带有日期和时间的字符串.
放在L
旁边__ DATE __
会给你一个编译错误.
Windows:将##用于通用Unicode或多字节字符串
Windows使用类似以下内容:
#ifdef _UNICODE #define _T(x) L ## x #else #define _T(x) x #endif
并且_T
在代码中无处不在
各种库,用于清洁访问器和修饰符名称:
我也看到它在代码中用于定义访问器和修饰符:
#define MYLIB_ACCESSOR(name) (Get##name) #define MYLIB_MODIFIER(name) (Set##name)
同样,您可以将此相同方法用于任何其他类型的聪明名称创建.
各种库,使用它一次制作多个变量声明:
#define CREATE_3_VARS(name) name##1, name##2, name##3 int CREATE_3_VARS(myInts); myInts1 = 13; myInts2 = 19; myInts3 = 77;
这是我在升级到新版本的编译器时遇到的问题:
不必要地使用令牌粘贴operator(##
)是不可移植的,可能会产生不需要的空格,警告或错误.
当令牌粘贴操作符的结果不是有效的预处理器令牌时,令牌粘贴操作符是不必要的并且可能是有害的.
例如,有人可能会尝试使用token-pasting运算符在编译时构建字符串文字:
#define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a##+##b) #define NS(a, b) STRINGIFY(a##::##b) printf("%s %s\n", PLUS(1,2), NS(std,vector));
在某些编译器上,这将输出预期的结果:
1+2 std::vector
在其他编译器上,这将包括不需要的空格:
1 + 2 std :: vector
相当现代的GCC版本(> = 3.3左右)将无法编译此代码:
foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token
解决方案是在将预处理程序令牌连接到C/C++运算符时省略令牌粘贴运算符:
#define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a+b) #define NS(a, b) STRINGIFY(a::b) printf("%s %s\n", PLUS(1,2), NS(std,vector));
关于连接的GCC CPP文档章节有关于令牌粘贴操作符的更多有用信息.
这在各种情况下都很有用,以免不必要地重复自己.以下是Emacs源代码中的示例.我们想从库中加载许多函数.应将函数"foo"赋值给fn_foo
,等等.我们定义以下宏:
#define LOAD_IMGLIB_FN(lib,func) { \ fn_##func = (void *) GetProcAddress (lib, #func); \ if (!fn_##func) return 0; \ }
然后我们可以使用它:
LOAD_IMGLIB_FN (library, XpmFreeAttributes); LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer); LOAD_IMGLIB_FN (library, XpmReadFileToImage); LOAD_IMGLIB_FN (library, XImageFree);
好处是不必写都fn_XpmFreeAttributes
和"XpmFreeAttributes"
(和风险拼错其中之一).