C 语言宏定义函数编写时 do-while 的妙用和一些注意事项
在 C/C++ 语言中,我们都知道可以用宏定义来编写函数,一般称为宏函数。如果一个宏函数比较复杂,那么在编写这样的宏函数是有一定技巧和注意事项的。文章给出一些我认为值得关注的地方,以及一些注意事项(个人建议),最后介绍了一种使用 do-while 语句来编写宏定义函数的技巧。下面我将从简单到复杂,以便于大家理解。下面的例子均采用 C 语言,C++与 C 语言相同,就不再赘述。
一个简单的例子
宏定义函数最简单的情况便是只有一条语句。下面我给出了四个例子,请仔细观察各个例子之间的区别,并尝试思考哪些是可以正确运行的。
/* 例子 1 */
#include <stdio.h>
#define MACRO_FUNC() printf("Hello!\n")
int main()
{
if(2 > 1)
MACRO_FUNC()
else
printf("Fail.\n");
return 0;
}
/* 例子 2 */
#include <stdio.h>
#define MACRO_FUNC() printf("Hello!\n");
int main()
{
if(2 > 1)
MACRO_FUNC();
else
printf("Fail.\n");
return 0;
}
/* 例子 3 */
#include <stdio.h>
#define MACRO_FUNC() printf("Hello!\n");
int main()
{
if(2 > 1)
MACRO_FUNC()
else
printf("Fail.\n");
return 0;
}
/* 例子 4 */
#include <stdio.h>
#define MACRO_FUNC() printf("Hello!\n")
int main()
{
if(2 > 1)
MACRO_FUNC();
else
printf("Fail.\n");
return 0;
}
相信细心的你已经发现了,它们的区别在于宏定义函数末尾有无分号,以及调用宏定义函数时末尾有无分号。宏定义可以简单的理解为直接替换,这样的替换是 包括宏定义函数中的分号 ,那么想知道哪些例子是可以运行的,那么只需要将调用宏定义函数的语句,替换为宏定义函数的内容即可,替换后的结果如下:
/* 例子 1 */
#include <stdio.h>
int main()
{
if(2 > 1)
printf("Hello!\n") /* 语句没有以分号结束,错误 */
else
printf("Fail.\n");
return 0;
}
/* 例子 2 */
#include <stdio.h>
int main()
{
if(2 > 1)
printf("Hello!\n");;
/* 语句以两个分号结束,第二个分号使得 if 语句结束,else 语句无法找到对应的 if 语句,会产生错误 */
else
printf("Fail.\n");
return 0;
}
/* 例子 3 */
#include <stdio.h>
int main()
{
if(2 > 1)
printf("Hello!\n"); /* 正确 */
else
printf("Fail.\n");
return 0;
}
/* 例子 4 */
#include <stdio.h>
int main()
{
if(2 > 1)
printf("Hello!\n"); /* 正确 */
else
printf("Fail.\n");
return 0;
}
根据上面代码中的注释和分析可以得知,上述示例中例 3、例 4 是正确的。二者的共同点是宏定义函数和调用宏定义函数后面分号的总数为 1,二者的区别是例 3 是在调用宏函数时后面没有添加分号,而例 4 是宏定义函数的定义后面没有添加分号。那么这两种方法更好一些呢? 我个人的建议是例 4 的方法更好一些,下面我来解释一下原因:
一个宏函数可能会被多次调用,如果选择例 3 的方法,那么每次调用宏定义函数时都不需要写分号,看起来省事一点儿,但潜在的隐患很多,试想当你看到别人代码中一条语句是这样的 MACRO_FUNC() 而后面没有分号时,你会觉得很奇怪,可能会为其后面增加分号,在某些时刻它可以正常工作,但在上面的例子中会直接产生一个编译错误。如果你想采取例 3 那样的做法,那么你需要在编写代码时刻考虑是否应该添加分号。
而选用例 4 方法,虽然每次调用函数都需要添加分号,但是至少当你编写好宏定义函数后,你在编写其他代码时就不再需要考虑是否可以添加分号。同样地,用宏定义来定义一个常量时,例如:#define SIZE 100。我们也应该不在数字后面添加分号。
稍微复杂的例子
宏定义函数可以像正常的函数一样,执行一些复杂的功能,里面可以包含有多条语句。我们都知道,函数的语句都需要用花括号 { ... } 包围起来,宏定义函数也不例外。值得注意的是:宏定义中如果需要换行,那么需要在行尾添加续行符 —— 反斜杠 '\'。最后一行因为已经到了结束,因此并不需要添加此符号 '\'。续行符没有实际作用,只是为了宏定义函数方便阅读和编写。
示例代码如下:
#include <stdio.h>
#define MACRO_FUNC() \
{ \
printf("Hello\n"); \
printf("World\n"); \
} /* 根据上面例子的描述,我们不应该在宏定义末尾添加分号 */
int main()
{
if(2 > 1)
MACRO_FUNC();
else
printf("Fail.\n");
return 0;
}
根据上面提到的替换规则,宏定义的替换是所有内容的替换,包括分号,花括号等,注意,续行符在替换时需要被忽略。替换后的结果如下:
#include <stdio.h>
int main()
{
if(2 > 1)
{
printf("Hello\n");
printf("World\n");
}; /* 这个分号是调用 MACRO_FUNC(); 时末尾的分号 */
else
printf("Fail.\n");
return 0;
}
if 后面的语句块右花括号又有了分号,导致 else 语句又无法找到对应的 if 语句而产生错误。看来还是需要用例 3 那样的写法,代码如下:
#include <stdio.h>
#define MACRO_FUNC() \
{ \
printf("Hello\n"); \
printf("World\n"); \
} /* 根据上面例子的描述,我们不应该在宏定义末尾添加分号 */
int main()
{
if(2 > 1)
MACRO_FUNC() /* 末尾不添加分号 */
else
printf("Fail.\n");
return 0;
}
上面的代码是可以正常运行的。可以自己思考一下。可是我还想用例 4 那样的写法,下面会讲到一个技巧来达到这个目的。
采用 do-while 语句
do-while 语句的一个特点便是,会优先执行一次语句块,而后再判断 while 中的条件是否成立。当我们把 while 设置为 while(0) 时,那么 while 条件永不成立,do 后面语句块中的代码只会执行一次。因此,宏定义多行代码用 do { /* ... */ } while(0) 包围起来便可以达到目的,来试一试吧。示例代码如下:
#include <stdio.h>
#define MACRO_FUNC() \
do \
{ \
printf("Hello\n"); \
printf("World\n"); \
} while(0)
/* 根据上面例子的描述,我们不应该在宏定义末尾添加分号 */
int main()
{
if(2 > 1)
MACRO_FUNC();
else
printf("Fail.\n");
return 0;
}
我们将调用宏定义函数的部分替换为宏定义中的内容来看一看:
#include <stdio.h>
int main()
{
if(2 > 1)
do
{
printf("Hello\n");
printf("World\n");
} while(0); /* 分号来自调用宏定义函数末尾的分号 */
else
printf("Fail.\n");
return 0;
}
可以发现上面替换后的结果是可以正常执行的。
总结
总结一下,文章中涉及到的内容:
- 宏定义函数编写时需要换行时,需要使用续行符——反斜杠 '\'
- 宏定义函数可以理解为简单的替换,这样的替换包括分号,花括号等,但不包含分号。
- 为了避免考虑在调用宏定义函数时末尾是否需要加分号,我们在宏定义结束处不添加分号
- 可以使用
do { /* ... */ } while(0)将宏定义中的多行代码包围起来。
C 语言宏定义函数编写时 do-while 的妙用和一些注意事项的更多相关文章
- C语言宏定义函数中的“_##”的意思
最近在看google vp9的代码的时候碰到: #define intra_pred_sized(type, size) \ void vp9_##type##_predictor_##size##x ...
- 将C语言宏定义数值转换成字符串!
将C语言宏定义转换成字符串! 摘自:https://blog.csdn.net/happen23/article/details/50602667 2016年01月28日 19:15:47 六个九十度 ...
- C语言 宏定义之可变参数
可变参数宏定义 C99编译器标准允许你可以定义可变参数宏(variadic macros),这样你就可以使用拥有可以变化的参数表的宏.可变参数宏就像下面这个样子: #define dbgprint(. ...
- C 语言宏定义
C 语言宏定义1.例子如下: #define PRINT_STR(s) printf("%s",s.c_str()) string str = "abcd"; ...
- iOS 使用宏定义函数和代码块
iOS使用宏定义函数和代码块 今天在开发过程中碰到一个问题:就是父类中要向外发送通知,然后子类中或者其他类中来接收它.当然一般是把它写到类方法中去,但是有个问题,就是如果调用的类不是它的子类,就不能直 ...
- C语言宏定义时#(井号)和##(双井号)的用法
C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念).下面对常遇到的宏的使用问题做了简单总结. 关于#和## 在C语言的宏中,#的功能是将其后面 ...
- C语言宏定义和宏定义函数
要写好C语言,漂亮的宏定义是非常重要的.宏定义可以帮助我们防止出错,提高代码的可移植性和可读性等. 在软件开发过程中,经常有一些常用或者通用的功能或者代码段,这些功能既可以写成函数,也可以封装成为宏定 ...
- C语言宏定义时#(井号)和##(双井号)的用法1
#在英语里面叫做 pound 在C语言的宏定义中,一个#表示字符串化:两个#代表concatenate 举例如下: #include <iostream> void quit_comman ...
- 转:C语言宏定义时#(井号)和##(双井号)的用法
转自:http://www.cnblogs.com/welkinwalker/archive/2012/03/30/2424844.html#2678295 #在英语里面叫做 pound 在C语言的宏 ...
随机推荐
- React useEffect的源码解读
前言 对源码的解读有利于搞清楚Hooks到底做了什么,如果您觉得useEffect很"魔法",这篇文章也许对您有些帮助. 本篇博客篇幅有限,只看useEffect,力求简单明了,带 ...
- 用python做推荐系统(二)
一.简介 继上一篇基于用户的推荐算法,这一篇是要基于商品的,基于用户的好处是可以根据用户的评价记录找出跟他兴趣相似的用户,再推荐这些用户也喜欢的电影,但是万一这个用户是新用户呢?或是他还没有对任何电影 ...
- oracle mysql sql 根据一张表更新另一张表
update CDINFO.Dept_Dict tab1 set PART_FLAG = (select PART_FLAG from DICT.DEPARTMENT_DICT@zyhis4 tab2 ...
- 【JavaWeb学习】过滤器Filter
一.简介 Filter也称之为过滤器,它是Servlet技术中最激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静 ...
- 在GeneXus开发过程中如何进行自动化测试?
1. 简介 GXtest是基于专门为GeneXus平台开发的应用程序提供的自动化测试解决方案. 我们强调“解决方案”和“自动化”两个词: 解决方案:GXtest为整个GeneXus软件开发生命周期提供 ...
- Java 多线程安全问题简单切入详细解析
线程安全 假如Java程序中有多个线程在同时运行,而这些线程可能会同时运行一部分的代码.如果说该Java程序每次运行的结果和单线程的运行结果是一样的,并且其他的变量值也都是和预期的结果是一样的,那么就 ...
- Java 中的等待唤醒机制透彻讲解
线程的状态 首先了解一下什么是线程的状态,线程状态就是当线程被创建(new),并且启动(start)后,它不是一启动就进入了执行状态(run),也不是一直都处于执行状态. 这里说一下Java 的Thr ...
- Redis常用命令详细介绍
一.字符串 字符串键是Redis最基本的键值对类型,将一个单独的键和一个单独的值关联起来.通过字符串键,不仅可以存储和读取字符串,如果输入能被解释为整数和浮点数,还能执行自增或自减操作. 1.SET: ...
- [bzoj3925] [洛谷P3343] [ZJOI2015] 地震后的幻想乡
Description 傲娇少女幽香是一个很萌很萌的妹子,而且她非常非常地有爱心,很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们. 这不,幻想乡突然发生了地震,所有的道路都崩塌了.现在的首要任 ...
- 【Java面试】Mybatis篇
1.MyBatis编程步骤是什么样的? ① 创建SqlSessionFactory ② 通过SqlSessionFactory创建SqlSession ③ 通过sqlsession执行数据库操作 ④ ...