#define宏定义
1 #define的概念
#define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。
(1) 简单的宏定义:
#define <宏名> <字符串>
例: #define PI
3.1415926
(2) 带参数的宏定义
#define <宏名> ( <参数表>) <宏体>
例: #define A(x)
x
一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换。
2 宏替换发生的时机
预处理器将源程序文件中出现的对宏的引用展开成相应的宏定义,即本文所说的#define的功能,由预处理器来完成。
经过预处理器处理的源程序与之前的源程序有所有不同,在这个阶段所进行的工作只是纯粹的替换与展开,没有任何计算功能。
3 #define使用中的常见问题解析
3.1 简单宏定义使用中出现的问题
在简单宏定义的使用中,当替换文本所表示的字符串为一个表达式时,容易引起误解和误用。如下例:
例1 #define N
2+2
void main()
{
int a=N*N;
printf(“%d”,a);
}
(1) 出现问题:在此程序中存在着宏定义命令,宏N代表的字符串是2+2,在程序中有对宏N的使用,一般同学在读该程序时,容易产生的问题是先求解N为 2+2=4,然后在程序中计算a时使用乘法,即N*N=4*4=16,其实该题的结果为8,为什么结果有这么大的偏差?
(2)问题解析:宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方 只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2*2+2,计算后=8,这就是宏替换的实质,如何写程序才 能完成结果为16的运算呢?
(3)解决办法:将宏定义写成如下形式
#define N (2+2)
这样就可替换成(2+2)*(2+2)=16
3.2 带参数的宏定义出现的问题
在带参数的宏定义的使用中,极易引起误解。例如我们需要做个宏替换能求任何数的平方,这就需要使用参数,以便在程序中用实际参数来替换宏定义中的参数。一般学生容易写成如下形式:
#define area(x) x*x
这在使用中是很容易出现问题的,看如下的程序
void main()
{
int y=area(2+2);
printf(“%d”,y);
}
按理说给的参数是2+2,所得的结果应该为4*4=16,但是错了,因为该程序的实际结果为8,仍然是没能遵循纯粹的简单替换的规则,又是先计算再替换 了,在这道程序里,2+2即为area宏中的参数,应该由它来替换宏定义中的x,即替换成2+2*2+2=8了。那如果遵循(1)中的解决办法,把2+2 括起来,即把宏体中的x括起来,是否可以呢?#define area(x) (x)*(x),对于area(2+2),替换为(2+2)*(2+2)=16,可以解决,但是对于area(2+2)/area(2+2)又会怎么样 呢,有的学生一看到这道题马上给出结果,因为分子分母一样,又错了,还是忘了遵循先替换再计算的规则了,这道题替换后会变为 (2+2)*(2+2)/(2+2)*(2+2)即4*4/4*4按照乘除运算规则,结果为16/4*4=4*4=16,那应该怎么呢?解决方法是在整个 宏体上再加一个括号,即#define area(x) ((x)*(x)),不要觉得这没必要,没有它,是不行的。
要想能够真正使用好宏定义,那么在读别人的程序时,一定要记住先将程序中对宏的使用全部替换成它所代表的字符串,不要自作主张地添加任何其他符号,完全展 开后再进行相应的计算,就不会写错运行结果。如果是自己编程使用宏替换,则在使用简单宏定义时,当字符串中不只一个符号时,加上括号表现出优先级,如果是 带参数的宏定义,则要给宏体中的每个参数加上括号,并在整个宏体上再加一个括号。
4 宏定义的优点
(1) 方便程序的修改
使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时, 我们可以用较短的有意义的标识符来写程序,这样更方便一些。我们所说的常量改变不是在程序运行期间改变,而是在编程期间的修改,举一个大家比较熟悉的例 子,圆周率π是在数学上常用的一个值,有时我们会用3.14来表示,有时也会用3.1415926等,这要看计算所需要的精度,如果我们编制的一个程序中 要多次使用它,那么需要确定一个数值,在本次运行中不改变,但也许后来发现程序所表现的精度有变化,需要改变它的值, 这就需要修改程序中所有的相关数值,这会给我们带来一定的不便,但如果使用宏定义,使用一个标识符来代替,则在修改时只修改宏定义即可,还可以减少输入 3.1415926这样长的数值多次的情况,我们可以如此定义 #define pi 3.1415926,既减少了输入又便于修改,何乐而不为呢?
(2) 提高程序的运行效率
使用带参数的宏定义可完成函数调用的功能,又能减少系统开 销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子 函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽 略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销(这个切换的工作也是由操作系统上的一个专门程序来做的,可想而知,其代码里是没有函数调用的,该程序要用汇编语言写,汇编语言是没有函数概念的)就相对较大了,但使用带参数的宏定义就不会出现这个问 题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义 所占用的目标代码空间相对较大(就是因为进行了宏展开,即复制了一份宏所代表的字符串代码过来)。所以在使用时要依据具体情况来决定是否使用宏定义。
#define宏定义的更多相关文章
- 面试问题5:const 与 define 宏定义之间的区别
问题描述:const 与 define 宏定义之间的区别 (1) 编译器处理方式不同 define宏是在预处理阶段展开: const常量是编译运行阶段使用: (2) 类型和安全检查不同 ...
- define宏定义中的#,##,@#及\符号
define宏定义中的#,##,@#及\符号 在#define中,标准只定义了#和##两种操作.#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串. 1.# (string ...
- iOS define 宏定义 和 const定义常量区别
const const 是c++中的修饰符. c++中常用来定义常量,修饰左值. #define 宏定义语句, 在预处理阶段直接做文本替换,不做类型检查. 它们之间的最大区别: 1. 对于co ...
- iOS 7:漫谈#define 宏定义(转)
iOS :漫谈#define 宏定义 #define宏定义在C系开发中可以说占有举足轻重的作用.底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步 ...
- #define宏定义形式的"函数"导致的bug
定义了一个宏定义形式的"函数": #define SUM8(YY)\ {\ int Y = YY>>2;\ ...\ } 然后使用的时候,传入了一个同名的变量Y: i ...
- define宏定义和const常变量区别
1.define是宏定义,程序在预处理阶段将用define定义的内容进行了替换.因此程序运行时,常量表中并没有用define定义的常量,系统不为它分配内存.const定义的常量,在程序运行时在常量表中 ...
- typedef 类型重命名 和 #define 宏定义(1)
http://www.blogjava.net/jasmine214--love/archive/2010/11/29/339307.html 在现实生活中,信息的概念可能是长度,数量和面积等.在C语 ...
- #define 宏定义
gcc Semaphores.c -lpthread --std=c99 1. 边际效应 2. \ 宏定义中一行写不下时,不能换行,必须\+换行 #define CPE_MAIN_PRINTF(fmt ...
- #define宏定义中## #@ # \ 符号使用
C/C++ 宏命令的神奇用法. 先看下面三条语句: #define Conn(x,y) x##y#define ToChar(x) #@x#define ToString(x) ...
随机推荐
- strcmp在CTF中的案例
当strcmp比较出错的时候就会为null.null即为0故输出flag. strcmp(arr,str); ?test[]=1 <?php define('FLAG', 'pwnhub{THI ...
- Android开发艺术探索读书笔记——进程间通信
1. 多进程使用场景 1) 应用某些模块由于特殊需求须要执行在单独进程中. 如消息推送,使消息推送进程与应用进程能单独存活,消息推送进程不会由于应用程序进程crash而受影响. 2) 为加大一个应用可 ...
- 记录一个glibc 导致的段错误以及gdb 移植
上一篇我有相关关于一个段错误的记录,现在记录当时的段错误具体是在哪里的. // 从 GNU 的官网下载当前在使用的 glibc 的源代码以及最新的 glibc 源代码 // 地址如下: http:// ...
- [内核]Linux UserSpace和Kernel之间如何联系
转自:http://blog.csdn.net/dreaming_my_dreams/article/details/8272586 应用层和驱动的衔接,一直是一个老大难问题,若弄不清楚,总觉得驱动写 ...
- Unity3D 5.0版本+注册工具分享
Unity3D引擎5.0正式版本发布也有一段时间了.笔者今天下载了新版本顺便分享一下资源. 主要有两个资源,一个是5.0f4的官方客户端,另外一个是vs的调试插件.有需要的盆友就拿去.都在下面的连接地 ...
- hdu4746 Mophues 莫比乌斯
/** 题目:hdu4746 Mophues 链接:http://acm.hdu.edu.cn/showproblem.php?pid=4746 题意:求x,y在给定范围内gcd(x,y)分解素因子的 ...
- 回文树(统计所有回文串的个数) - MCCME 1750 Подпалиндромы
Подпалиндромы Problem's Link: http://informatics.mccme.ru//mod/statements/view.php?chapterid=1750# M ...
- CSS实现文字半透明显示在图片上方法
CSS实现文字半透明显示在图片上方法 在css中文字半透明我们会需要使用滤镜效果也就是css中的filter:alpha来实现了,下面来看两个文字显示在图片上并且半透明的例子. CSS让一行文字显示在 ...
- AWT是Java最早出现的图形界面,但很快就被Swing所取代
AWT是Java最早出现的图形界面,但很快就被Swing所取代. Swing才是一种真正的图形开发. AWT在不同平台所出现的界面可能有所不同:因为每个OS都有自己的UI组件库,java调用不同系统的 ...
- openssl升级并发症
简单介绍一下系统环境: 操作系统:redhat6.3 ,安装的主要涉及到的包有: root@192.168.100.252:/root# rpm -qa | grep ssh openssh-.3p1 ...