正如我之前的许多问题中提到的,我正在通过K& R工作,目前正在进入预处理器。一个更有趣的事情—我之前从未尝试过任何学习C—是 ## 预处理程序运算符。根据K& R:

  

预处理程序运算符 ##   提供了一种连接实际的方法   宏观扩张期间的争论。如果一个   替换文本中的参数是   与 ## 相邻,参数为   取而代之的是实际的论点    ## 和周围的空白区域   删除,并重新扫描结果。   例如,宏 paste   连接它的两个论点:

     

#define paste(front,back)front ## back

     

所以 paste(name,1)创建令牌   <代码> NAME1

如何以及为什么有人会在现实世界中使用它?它的使用的实际例子是什么,有什么需要考虑的?

有帮助吗?

解决方案

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__);

他们将它与另一个宏一起使用,该宏返回带有日期和时间的字符串。

__ DATE __ 旁边放 L 会给你一个编译错误。


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;

其他提示

当您使用令牌粘贴(' ## ')或字符串化('')预处理运算符时,要注意的一件事是必须使用额外的间接级别才能在所有情况下正常工作。

如果你不这样做并且传递给令牌粘贴操作符的项目本身就是宏,那么你将获得可能不是你想要的结果:

#include <stdio.h>

#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

这是我在升级到新版本的编译器时遇到的问题:

不必要地使用令牌粘贴操作符( ## )是不可移植的,可能会产生不需要的空格,警告或错误。

当令牌粘贴操作符的结果不是有效的预处理器令牌时,令牌粘贴操作符是不必要的并且可能有害。

例如,可以尝试使用令牌粘贴操作符在编译时构建字符串文字:

#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 &quot; XpmFreeAttributes&quot; (并且可能会拼错其中之一)。

我在C程序中使用它来帮助正确地执行一组必须符合某种调用约定的方法的原型。在某种程度上,这可以用于直接C中穷人的物体取向:

SCREEN_HANDLER( activeCall )

扩展为类似的东西:

STATUS activeCall_constructor( HANDLE *pInst )
STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent );
STATUS activeCall_destructor( HANDLE *pInst );

这对所有“派生的”强制执行正确的参数化。对象:

SCREEN_HANDLER( activeCall )
SCREEN_HANDLER( ringingCall )
SCREEN_HANDLER( heldCall )

上面的头文件等。如果您甚至碰巧想要更改定义和/或将方法添加到“对象”,它对维护也很有用。

SGlib 使用##来基本上捏造C中的模板。因为没有函数重载,所以使用##将类型名称粘贴到生成的函数的名称中。如果我有一个名为list_t的列表类型,那么我将获得名为sglib_list_t_concat的函数,依此类推。

我将它用于嵌入式非标准C编译器上的home roll assert:



#define ASSERT(exp) if(!(exp)){ \
                      print_to_rs232("Assert failed: " ## #exp );\
                      while(1){} //Let the watchdog kill us 


当您需要将宏参数与其他内容连接时,可以使用标记粘贴。

它可用于模板:

#define LINKED_LIST(A) struct list##_##A {\
A value; \
struct list##_##A *next; \
};

在这种情况下,LINKED_LIST(int)会给你

struct list_int {
int value;
struct list_int *next;
};

类似地,您可以为列表遍历编写函数模板。

我用它来为宏定义的变量添加自定义前缀。如下所示:

UNITTEST(test_name)

扩展为:

void __testframework_test_name ()

主要用途是当您有命名约定并且希望宏利用该命名约定时。也许你有几个方法系列:image_create(),image_activate()和image_release()也是file_create(),file_activate(),file_release()和mobile_create(),mobile_activate()和mobile_release()。

您可以编写一个用于处理对象生命周期的宏:

#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())

当然,有一种“最小版本的对象”。这不是唯一适用的命名约定 - 几乎绝大多数命名约定都使用公共子字符串来形成名称。它可以是函数名称(如上所述),或字段名称,变量名称或大多数其他内容。

WinCE中的一个重要用途:

#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))

在定义寄存器位描述时,我们执行以下操作:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

使用BITFMASK时,只需使用:

BITFMASK(ADDR)

对于日志记录非常有用。你可以这样做:

#define LOG(msg) log_msg(__function__, ## msg)

或者,如果您的编译器不支持功能功能

#define LOG(msg) log_msg(__file__, __line__, ## msg)

以上“功能”记录消息并准确显示记录消息的功能。

我的C ++语法可能不太正确。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top