本文主要是介绍预处理器 - 记号传递操作符##,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
原文译至: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)
因为预处理器对##作如下处理:
- 移除所有的##
- 删除周围所有的空格
- 将非空格字符连接到一起
虽然记号传递操作符可用于无参数的宏,但是这没有什么意义。因为你可以无须##直接打印你需要的东西。
##只是在你用它来连接你传递到宏中的参数时才发挥它的威力(只要是关于预处理器的,它处理的所有东西就是一堆文本).
通常情况下,它用于自动创建新的标志符。例如:
#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个参数:
from
- a descriptive name of the unit we are converting fromto
- a descriptive name of the unit we are converting toconversion
- the conversion equation (yes, macro parameters can be complex)from_type
- the type we are converting fromto_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; }
这篇关于预处理器 - 记号传递操作符##的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!