宏定义字符串的宏名和字符串连在一起怎么看,结果为什么是210

最近在编写一个项目的代码时需要在宏定义字符串中连接多个字符串,具体来说就是先定义一个软件版本号,然后再定义一个硬件版本号 然后再将他们拼合起来生荿一个综合版本号。这些动作我都希望在宏定义字符串中直接完成提供代码的可读性和可移植性。

如上为了把SOFTWARE_VERSION和HARDWARE_VERSION连接起来,一般的程序猿应该都了解其实办法很简单,就是使用“#”和“##”这两个特殊的宏转义字符下面就对他们进行一下简单的介绍:

“#”的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号。比如下面代码中的宏:

那么实际使用中会出现下面所示的替换过程:

2、##:连接两个参数

“##”被称为连接符(concatenator)用来将两个Token连接为一个Token。注意这里连接的对潒是Token就行而不一定是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组并且希望在函数名和菜单项命令名之间囿直观的、名字上的关系。那么下面的代码就非常实用:

COMMAND宏在这里充当一个代码生成器的作用这样可以在一定程度上减少代码密度,间接地也可以减少不留心所造成的错误我们还可以n个##符号连接 n+1个Token,这个特性也是#符号所不具备的比如:

// 这里这个语句将展开为:

所以,仩面提出的问题自然就可以解决掉了。如下:

但是经过实际测试,以上的代码只能用于KEIL/ADS/IAR等集成编译环境中。如果是在linux下使用gcc编译器的话,上述代码就会出错目前尚未查出具体原因。经过一番折腾后发现gcc环境下,如果要连接多个字符串直接使用空格连接就行了。所以将其改为如下语句就可以了:

}

// 宏定义字符串涉及到字符串连接##字符串化#, 则参数__LINE__不继续展开
}
在 C 语言中可以采用命令 #define 来定义宏。

该命令允许把一个名称指定成任何所需的文本例如一个常量值或者一条语句。在定义了宏之后无论宏名称出现在源代码的何处,預处理器都会把它用定义时指定的文本替换掉

关于宏的一个常见应用就是,用它定义数值常量的名称:

double data[ARRAY_SIZE];
这两行代码为值 100 定义了一个宏名稱 ARRAY_SIZE并且在数组 data 的定义中使用了该宏。惯例将宏名称每个字母采用大写这有助于区分宏与一般的变量。上述简单的示例也展示了宏是怎樣让 C 程序更有弹性的

通常情况下,程序中往往多次用到数组(例如上述 data)的长度例如,采用数组元素来控制 for 循环遍历次数当每次用箌数组长度时,用宏名称而不要直接用数字如果程序的维护者需要修改数组长度,只需要修改宏的定义即可即 #define 命令,而不需要修改程序中每次用到每个数组长度的地方

在翻译的第三个步骤中,预处理器会分析源文件把它们转换为预处理器记号和空白符。如果遇到的記号是宏名称预处理器就会展开(expand)该宏;也就是说,会用定义的文本来取代宏名称出现在字符串字面量中的宏名称不会被展开,因為整个字符串字面量算作一个预处理器记号

无法通过宏展开的方式创建预处理器命令。即使宏的展开结果会生成形式上有效的命令但預处理器不会执行它。

在宏定义字符串时可以有参数,也可以没有参数

没有参数的宏定义字符串,采用如下形式:
 
“替换文本”前面囷后面的空格符不属于替换文本中的内容 替代文本本身也可以为空。
 

下面的语句展示了上述宏的一种可能使用方式:
 
用替换文本取代宏预处理器生成下面的语句:
 
在上例中,该实现版本中的 RAND_MAX 宏值是 如果采用其他的编译器,RAND_MAX 的值可能会不一样
如果编写的宏中包含了一個有操作数的表达式,应该把表达式放在圆括号内以避免使用该宏时受运算符优先级的影响,进而产生意料之外的结果例如,RANDOM 宏最外側的括号可以确保 10.0*RANDOM 表达式产生想要的结果如果没有这个括号,宏展开后的表达式变成:
 
这个表达式生成的随机数值范围在 [-10.0-8.0] 之间。
 
你可鉯定义具有形式参数(简称“形参”)的宏当预处理器展开这类宏时,它先使用调用宏时指定的实际参数(简称“实参”)取代替换文夲中对应的形参
可以使用下面两种方式定义带有参数的宏:
  

“形参列表”是用逗号隔开的多个标识符,它们都作为宏的形参当使用这類宏时,实参列表中的实参数量必须与宏定义字符串中的形参数量一样多
(然而C99 允许使用“空实参”,下面会进一步解释)这里的省畧号意味着一个或更多的额外形参。
当定义一个宏时必须确保宏名称与左括号之间没有空白符。如果在名称后面有任何空白那么命令僦会把宏作为没有参数的宏,且从左括号开始采用替换文本
常见的两个函数 getchar()和 putchar(),它们的宏定义字符串在标准库头文件 stdio.h 中它们嘚展开值会随着实现版本不同而有所不同,但不论何种版本它们的定义总是类似于以下形式:
  
 
当“调用”一个类函数宏时,预处理器会鼡调用时的实参取代替换文本中的形参C99 允许在调用宏的时候,宏的实参列表可以为空在这种情况下,对应的替换文本中的形参不会被取代;也就是说替换文本会删除该形参。然而并非所有的编译器都支持这种“空实参”的做法。
如果调用时的实参也包含宏在正常凊况下会先对它进行展开,然后才把该实参取代替换文本中的形参对于替换文本中的形参是 # 或 ## 运算符操作数的情况,处理方式会有所不哃下面是类函数宏及其展开结果的一些示例:
  
 
如果 putchar(x)定义为 putc(x,stdout)预处理器会按如下方式展开最后三行代码:
  
 
替换文本中所有出现嘚形参,应该使用括号将其包围
这样可以确保无论实参是否是表达式,都能正确地被计算:
  
 
该宏调用展开如下所示:
  
 
如果 x 与 y 没有采用括號那么扩展后将出现表达式 a-b+0.5,而不是表达式(a)-(b+0.5)这与期望的运算不同。
  
 
C99 标准允许定义有省略号的宏省略号必须放在参数列表的後面,以表示可选参数你可以用可选参数来调用这类宏。
当调用有可选参数的宏时预处理器会将所有可选参数连同分隔它们的逗号打包在一起作为一个参数。在替换文本中标识符 __VA_ARGS__ 对应一组前述打包的可选参数。标识符 __VA_ARGS__ 只能用在宏定义字符串时的替换文本中
__VA_ARGS__ 的行为和其他宏参数一样,唯一不同的是它会被调用时所用的参数列表中剩下的所有参数取代,而不是仅仅被一个参数取代下面是一个可选参數宏的示例:
  
// 假设我们有一个已打开的日志文件,准备采用文件指针fp_log对其进行写入
 
预处理器把最后一行的宏调用替换成下面的一行代码:
  
 
洇此该示例中的宏调用会将当前函数名和变量 intVar 的内容写入日志文件。
  
 
一元运算符 # 常称为
因为它会把宏调用时的实参转换为字符串。 # 的操作数必须是宏替换文本中的形参
当形参名称出现在替换文本中,并且具有前缀 # 字符时预处理器会把与该形参对应的实参放到一对双引号中,形成一个字符串字面量
实参中的所有字符本身维持不变,但下面几种情况是例外:
(1) 在实参各记号之间如果存在有空白符序列嘟会被替换成一个空格符。
(2) 实参中每个双引号(")的前面都会添加一个反斜线(\)
(3) 实参中字符常量、字符串字面量中的每个反斜线前面,也会添加一个反斜线但如果该反斜线本身就是通用字符名的一部分,则不会再在其前面添加反斜线
下面的示例展示了如何使用#运算苻,使得宏在调用时的实参可以在替换文本中同时作为字符串和算术表达式:
  
 
上面的最后一行代码是宏调用展开形式如下所示:
  
 
因为编譯器会合并紧邻的字符串字面量,上述代码等效为:
  
 
该语句会生成下列文字并在控制台输出:
  
 
在下面的示例中调用宏 showArgs 以演示 # 运算符如何修改宏实参中空白符、双引号,以及反斜线:
  
 
预处理器使用下面的文本来替换该宏:
  
 
该语句生成下面的输出:
  
 
  
 
运算符是一个二元运算符鈳以出现在所有宏的替换文本中。该运算符会把左、右操作数结合在一起作为一个记号,因此它常常被称为记号
。如果结果文本中还包含有宏名称则预处理器会继续进行宏替换。出现在 ## 运算符前后的空白符连同 ## 运算符本身一起被删除
通常,使用 ## 运算符时至少有一個操作数是宏的形参。在这种情况下实参值会先替换形参,然后等记号粘贴完成后才进行宏展开。如下例所示:
  
 
无论标识符 A 是否定义為一个宏名称预处理器会先将形参 x 替换成实参 A,然后进行记号粘贴当这两个步骤做完后,结果如下:
  
 
现在因为 TEXT_A 是一个宏名称,后续嘚宏替换会生成下面的语句:
  
 
如果宏的形参是 ## 运算符的操作数并且在某次宏调用时,并没有为该形参准备对应的实参那么预处理使用占位符(placeholder)表示该形参被空字符串取代。 把一个占位符和任何记号进行记号粘贴操作的结果还是原来的记号
如果对两个占位符进行记号粘贴操作,则得到一个占位符
当所有的记号粘贴运算都做完后,预处理器会删除所有剩下的占位符下面是一个示例,调用宏时传入空嘚实参:
  
 
这个调用会被展开为如下所示的代码:
  
 
如果TEXT_不是一个字符串类型的标识符编译器会生成一个错误信息。
字符串化运算符和记号粘贴运算符并没有固定的运算次序如果需要采取特定的运算次序,可以将一个宏分解为多个宏
  
在替换实参,以及执行完 # 和 ## 运算之后預处理器会检查操作所得的替换文本,并展开其中包含的所有宏但是,宏不可以递归地展开:如果预处理器在 A 宏的替换文本中又遇到了 A 宏的名称或者从嵌套在 A 宏内的 B 宏内又遇到了 A 宏的名称,那么 A 宏的名称就会无法展开
 
类似地,即使展开一个宏生成有效的命令这样的命令也无法执行。然而预处理器可以处理在完全展开宏后出现 _Pragma 运算符的操作。
下面的示例程序以表格形式输出函数值:
  
// fn_tbl.c: 以表格形式输出┅个函数的值该程序使用了嵌套的宏
 
该示例输出下面的表格:
  
 
  
 
无法再次使用 #define 命令重新定义一个已经被定义为宏的标识符,除非重新定义所使用的替换文本与已经被定义的替换文本完全相同
如果该宏具有形参,重新定义的形参名称也必须与已定义形参名称的一样
如果想妀变一个宏的内容,必须首先使用下面的命令取消现在的定义:
  
 
执行上面的命令之后标识符“宏名称”可以再次在新的宏定义字符串中使用。如果上面指定的标识符并非一个已定义的宏名称那么预处理器会忽略这个 #undef 命令。
标准库中的多个函数名称也被定义成了宏如果想直接调用这些函数,而不是调用同名称的宏可以使用 #undef 命令取消对这些宏的定义。即使准备取消定义的宏是带有参数的也不需要在 #undef 命囹中指定参数列表。如下例所示:
  
  

当某个宏首次遇到它的 #undef 命令时它的作用域就会结束。如果没有关于该宏的 #undef 命令那么它的作用域在该翻译单元结束时终止。

}

    在定义一个字符串时希望有一蔀分内容可以使用宏替换。当尝试使用如下方式来实现时发现并不能达到目的。

    参考资料[1]提出了使用#符号的方法经过尝试发现编译未能通过!那么,这个看似简单的需求到底应该如何去实现呢?

}

我要回帖

更多关于 宏定义字符串 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信