预处理器 - 记号传递操作符##

原文译至:http://complete-concrete-concise.com/programming/c/preprocessor-the-token-pasting-operator

##可能是最不为人所知,也最缺少文档的预处理器操作符了。

记号传递操作符(##) 仅仅删除它周围的所有空格和将非空的字符连接到一起。它只能用于一个宏定义中,它被用于创建新的记号。

它不能是替换文本里的第一个或最后一个字符。

如果有多个记号传递操作符(##)和/或字符串化操作符(#)那么处理的顺序是不定的。

有效的记号是:

  • 标识符 (变量名,函数名,等等)
  • 关键字 (int, while, volatile, etc)
  • 文字 (字符串, 数字, 字符, true 或 false)
  • 操作符和标点 (+, -, *, (, etc)

如果使用##的不是有效的记号,那么行为是未定义的。

考虑一下如下的宏定义:使用##操作符来在替换字符串中移除额外的空格。

#define macro_start i ## n ##t m         ##ain(void)


在预处理后macro_start 名将会被替换成:

int main(void)

因为预处理器对##作如下处理:
  1. 移除所有的##
  2. 删除周围所有的空格
  3. 将非空格字符连接到一起

虽然记号传递操作符可用于无参数的宏,但是这没有什么意义。因为你可以无须##直接打印你需要的东西。

##只是在你用它来连接你传递到宏中的参数时才发挥它的威力(只要是关于预处理器的,它处理的所有东西就是一堆文本).

通常情况下,它用于自动创建新的标志符。例如:

#define my_macro(x) x##_macrofied

将连接传递的参数( x) 并追加后缀  _macrofied到x后面.
my_macro(identifier)

将会被扩展为:
identifier_macrofied


编译器在处理##的不同点

GCC 和Visual C++按不同的方式处理  ## .

如果连接的结果不是一个有效的预处理记号,GCC 的处理是严格的 – 它在编译阶段会产生一个错误。

Visual C++, 在另一方面,重新处理了连接的结果并支持它,而它在GCC中被视为无效的。

这两个编译器的处理都可以正常工作,因为标准并没有定义一个无效的记号就如何被处理的(它只是说行为是未定义的)。拒绝它好点因为这重新处理了结果并被解析成有效的记号。


例子:

下面的例子在GCC中失败但是Visual C++里成功:

#define macro_start int main ## (void)

在预处理器连接main和(后,我们得到记号

main(

这不是一个有效的记号。

GCC 不支持它。

另一方面,Visual C++ 重新处理来生成两个记号: 1) 一个标志符 main 和 2) 标点 / 操作符(。

两个编译器都能正常处理以下的宏

#define macro_increment(x) x+ ## +

因为它解析成两个记号。第一个是记号x,第二个是操作符++。


为什么用##?

它主要是用于降低重复的(易于出错)输入。

下面的代码定义了一个宏来创建一个C/C++中的新标量类型,并创建了6个针对那个类型的名字定制的函数。(初始化,加,减,乘,除和原始值访问)。不使用##操作符的话,这就要手工输入(如果你定义许多类型的话,这将重复操作,令人厌烦,而且容易出错)或是通过拷贝/粘贴/查找和替换操作(仍旧是重复操作,令人厌烦,而且容易出错)。

#define new_scalar_type(name, type) \
typedef struct \
{ \
    type value; \
} name; \
inline name name##_(type v) \
{ \
    name t; \
    t.value = v; \
    return t; \
} \
inline name add_##name(name a, name b) \
{ \
    name t; \
    t.value = a.value + b.value; \
    return t; \
} \
inline name sub_##name(name a, name b) \
{ \
    name t; \
    t.value = a.value - b.value; \
    return t; \
} \
inline name mul_##name(name a, name b) \
{ \
    name t; \
    t.value = a.value * b.value; \
    return t; \
} \
inline name div_##name(name a, name b) \
{ \
    name t; \
    t.value = a.value / b.value; \
    return t; \
} \
inline type value_##name(name a) \
{ \
    return a.value; \
} \

当你在你的代码里使用宏:

new_scalar_type(age, int);

你会得到
  • 一个名叫age的新类型,含一个int型
  • 一个初始化函数age age_(int t)
  • 一个加函数 age add_age(age a, age b)
  • 一个减函数age sub_age(age a, age b)
  • 一个乘函数age mul_age(age a, age b)
  • 一个除函数age div_age(age a, age b)
  • 一个原始值访问函数int value_age(age a)

如果你定义了一个新类型,叫做 weight_lbs, 那么你将会得到新的类型和相关的定制的命名函数,你就可以操作这些函数。

你也能定义一个新类型,按下面的方式使用:

new_scalar_type(age, int);

int main (void)
{
    age a = age_(42);
    age b = age_(24);
    age c = add_age(a, b);

    return value_age(c);
}


另外一个例子

下面的例子(被称作convert,能被用于自动生成一个将一个值转换成另一个值的函数 (例如, °F 到 °C 或英尺到英寸,等等):

#define convert(from, to, conversion, from_type, to_type) \
to_type convert_##from##_to_##to(from_type f) \
{ \
   return conversion; \
} \


它用到5个参数:

  1. from - a descriptive name of the unit we are converting from
  2. to - a descriptive name of the unit we are converting to
  3. conversion - the conversion equation (yes, macro parameters can be complex)
  4. from_type - the type we are converting from
  5. to_type - the type we are converting to

该宏可以这么使用:

convert(f, c, (f-32)*5.0/9.0, float, float);
convert(ft, in, ft * 12, int, int);


当宏被扩展时,我们得到两个叫做 convert_f_to_c 和 convert_ft_to_in的函数。这些可以按下面的方法使用:

int main (void)
{
    float a = 70.0;
    float b;
    int c = 3;
    int d;

    b = convert_f_to_c (a);
  
    d = convert_ft_to_in(c);

    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值