C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念)。下面对常遇到的宏的使用问题做了简单总结。

关于###

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

#define WARN_IF(EXP)    do{ if (EXP)    fprintf(stderr, "Warning: " #EXP "/n"); }   while(0)

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

WARN_IF (divider == 0);

被替换为

do {

if (divider == 0)

fprintf(stderr, "Warning" "divider == 0" "/n");

} while(0);

这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。

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

struct command

{

char * name;

void (*function) (void);

};

#define COMMAND(NAME) { NAME, NAME ## _command }

// 然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了:

struct command commands[] = {

COMMAND(quit),

COMMAND(help),

...

}

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

#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d

typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);

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

// typedef struct _record_type name_company_position_salary;

关于...的使用

...在C宏中称为Variadic Macro,也就是变参宏。比如:

#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)

// 或者

#define myprintf(templt,args...) fprintf(stderr,templt,args)

第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏 中,我们显式地命名变参为args,那么我们在宏定义中就可以用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出 现。当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:

myprintf(templt,);

的形式。这时的替换过程为:

myprintf("Error!/n",);

替换为:

fprintf(stderr,"Error!/n",);

这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:

myprintf(templt);

而它将会被通过替换变成:

fprintf(stderr,"Error!/n",);

很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:

#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)

这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:

myprintf(templt);

被转化为:

fprintf(stderr,templt);

这样如果templt合法,将不会产生编译错误。 这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。

错误的嵌套-Misnesting

宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。

由操作符优先级引起的问题-Operator Precedence Problem

由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。比如:

#define ceil_div(x, y) (x + y - 1) / y

那么

a = ceil_div( b & c, sizeof(int) );

将被转化为:

a = ( b & c  + sizeof(int) - 1) / sizeof(int);

// 由于+/-的优先级高于&的优先级,那么上面式子等同于:

a = ( b & (c + sizeof(int) - 1)) / sizeof(int);

这显然不是调用者的初衷。为了避免这种情况发生,应当多写几个括号:

#define ceil_div(x, y) (((x) + (y) - 1) / (y))

消除多余的分号-Semicolon Swallowing

通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:

MY_MACRO(x);

但是如果是下面的情况:

#define MY_MACRO(x) { /* line 1 */ /* line 2 */ /* line 3 */ }

//...

if (condition())

MY_MACRO(a);

else

{...}

这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:

#define MY_MACRO(x) do {

/* line 1 */ /* line 2 */ /* line 3 */ } while(0)

这样只要保证总是使用分号,就不会有任何问题。

Duplication of Side Effects

这里的Side Effect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。比如:

#define min(X,Y) ((X) > (Y) ? (Y) : (X))

//...

c = min(a,foo(b));

这时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:

#define min(X,Y) ({ typeof (X) x_ = (X); typeof (Y) y_ = (Y); (x_ < y_) ? x_ : y_; })

({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。

##连接符和#符的使用相关推荐

  1. 【C++】46.宏定义##连接符和符#的使用

    推荐下面两篇博客: 1.C/C++语言宏定义##连接符和符#的使用 2.c++/c中的##连接符的使用 在c/c++语言中,我们可以使用##来对一些字符进行连接,方便使用,尤其是在宏定义中使用该符号, ...

  2. C/C++语言宏定义##连接符和符#的使用

    C/C++语言宏定义##连接符和符#的使用     [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/details/53113187 (一) ...

  3. 计算机硬盘分区和盘符,盘符

    盘符是DOS.WINDOWS系统对于磁盘存储设备的标识符.一般使用26个英文字符加上一个冒号:来标识.由于历史的原因,早期的PC机一般装有两个软盘驱动器,所以,"A:"和" ...

  4. 【听哥一句劝,C++水很深,你把握不住啊!】C++提高班之 符与*符

    C++提高班之 &符与*符 像&和*这样的符号,既可以作为表达式中的运算符,也能作为声明的一部分出现,符号的上下文决定了符号的意义: int i = 27;int &r = i ...

  5. C# 字符串去除制表符回车符换行符

    #region 字符串去除制表符回车符换行符             public string strChar(string Str)             {                 s ...

  6. java部分基础知识 (二):计算机组成原理 原码 补码 反码 按位符 移位符 按位与 按位或 按位抑或 非 分析hashMap的put方法原理

    这里写目录标题 引言 符号位 正数的二进制计算 负数的二进制计算 按位符和移位符 按位符 移位符 分析hashMap运算符 byte和char 总结 引言 最近做完一个项目后,我忽然发现自己的基础并不 ...

  7. C语言关键字 数据类型 格式符 修饰符 运算符一览

    文章目录 关键字 数据类型关键字 输出格式符.修饰符 printf 输出格式符 conversion specifier 输出格式符修饰符 modifier 输入格式符.修饰符 scanf 输入格式符 ...

  8. oracle 去空格 换行符,ORACLE:除去回车符,换行符

    T-SQL的回车和换行符(SQL) T-SQL的回车和换行符(SQL) sql server中的回车换行字符是  char(13)+char(10) 回车:char(13) 换行:char(10) 实 ...

  9. C语言宏定义##连接符和#符的使用及其它宏定义注意事项

    C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念).下面对常遇到的宏的使用问题做了简单总结. 关于#和## 在C语言的宏中,#的功能是将其后面 ...

最新文章

  1. 前端、云与人工智能的碰撞 | GDG广州
  2. 【Interfacenavigation】用RecyclerView创建一个列表(4)
  3. 信息学奥赛C++语言: 回文数个数
  4. flex 常用正则验证举例
  5. http://www.cnblogs.com/huxj/archive/2009/11/21/1607791.html
  6. cordova打开外部链接_企业网盘的分享链接功能 这种的你一定没见过
  7. Java面向对象 Main函数 静态的应用 单例设计模式
  8. python相关系数显著性检验矩阵_python散点图及相关系数矩阵计算和相关性验证
  9. 俺也去了WinHec..............
  10. 关于log4j:WARN No appenders could be found for logger (org.apache.hadoop.metrics2.li)的问题
  11. 神经性疼痛的表现是什么,神经性疼痛常见部位是
  12. H3C 物联网路由器4G配置
  13. 您可能不知道WooCommerce可以做的10件事
  14. 全网最详细的微信小程序开发教程
  15. PPPoE拨号过程解析
  16. Python问题:FileNotFoundError: [WinError 2] 系统找不到指定的文件。(已解决)
  17. 手写板行业调研报告 - 市场现状分析与发展前景预测
  18. 最全的数据分析平台整理
  19. 华为鸿蒙周易,世纪工程背后的大局--港珠澳大桥的风水秘密!
  20. c语言考试系统设计报告,C语言课程设计(单项选择题标准化考试系统)报告

热门文章

  1. 常见的虚拟机需要配置的服务
  2. 为别人软件加入广告或者密码(特别思路)
  3. 吃饭/训觉-工作室应用隐私政策
  4. linux里的进程简介
  5. 织梦CMS AJAX分页,可自定义typeid,调取任意内容
  6. 怎么用代码制作WordPress的归档页面
  7. Android 菜单(OptionMenu)大全 建立你自己的菜单
  8. python socket编程实现的简单tcp迭代server
  9. 合成/聚合原则: 桥接模式
  10. 基于android平台的24点游戏设计与实现需求分析,基于Android平台的24点游戏设计与实现需求分析_毕业设计论文.doc...