预定义符号与宏

预处理器在源码编译之前进行的一些文本性质的操作,它的主要任务包括删除注释,插入被#include指令包含的文件内容,定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。

预定义符号

__DATE__,字符串常量类型,表示当前所在源文件的编译日期,输出格式为Mmm dd yyyy(如May 27 2006)。

__TIME__,字符串常量类型,表示当前所在源文件的编译日期,输出格式为hh:mm:ss(如09:11:10)。

__FILE__,字符串常量类型,表示当前所在源文件名,且包含文件路径。

__LINE__,整数常量类型,表示当前所在源文件中的行号。

__FUNCTION__,字符串常量类型,表示当前所在函数名。

__FILE__,进行编译的源文件。

预处理器在源码编译之前进行的一些文本性质的操作,它的主要任务包括删除注释,插入被#include指令包含的文件内容,定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。

宏定义   

宏定义的作用一般是用一个短的名字代表一个长的代码序列。

宏定义包括无参数宏定义和带参数宏定义两类。

宏名和宏参数所代表的代码序列可以是任何意义的内容,如类型、常量、变量、操作符、表达式、语句、函数、代码块等。

但要尤其注意的是宏名和宏参数必须是合法的标识符,其所代表的内容及意义在宏展开前后必须一直是独立且保持不变的,不能分开解释和执行。

无参数宏定义

用一个用户指定的称为宏名的标识符来代表一个代码序列,这种定义的一般形式为#define 标识符 代码序列。

其中#define之后的标识符称为宏定义名(简称宏名),在宏定义#define之前可以有若干个空格、制表符,但不允许有其它字符,宏名与代码序列之间用空格符分隔。

形式为: 

#define name stuff

 带参数宏定义

带参数宏定义进一步扩充了无参数宏定义的能力,这时的宏展开既进行宏名的替换又进行宏参数的替换。带参数的宏定义的一般形式为

#define name(parameter-list)   stuff

其中参数表中的参数之间用逗号分隔,在代码序列中必须要包含参数表中的的参数。

在定义带参数的宏时,宏名与左圆括号之间不允许有空白符,应紧接在一起,否则变成了无参数的宏定义。

带参数宏调用提供的实参个数必须与宏定义中的形式参数个数相同。

宏名的作用域

宏定义的有效范围称为宏名的作用域,宏名的作用域从宏定义的结束处开始到其所在的源代码文件末尾。宏名的作用域不受分程序结构的影响。如果需要终止宏名的作用域,可以用预处理指令#undef加上宏名。
宏名一般用大写字母,以便与变量名区别。如有必要,宏名可被重复定义,被重复定义后,宏名原先的意义被新意义所代替。

注意:

宏定义代码序列中必须把""配对,不能把字符串""拆开。例如#define NAME "vrmozart不合法,应为#define NAME "vrmozart"。
宏定义代码序列中可以引用已经定义的宏名,即宏定义可以嵌套。

多行宏   

宏定义在源文件中必须单独另起一行,换行符是宏定义的结束标志,因此宏定义以换行结束,不需要分号等符号作分隔符。

如果一个宏定义中代码序列太长,一行不够时,可采用续行的方法。续行是在键入回车符之前先键入符号\,注意回车要紧接在符号\之后,中间不能插入其它符号,当然代码序列最后一行结束时不能有\。

注意多行宏在调用时只能单独一行调用,不能用在表达式中或作为函数参数。例如: 

#define DEBUG_PRINT printf("FILE %s line %d:"\
                             "x=%d,y=%d,z=%d,\
                             __FILE__,__LINE__,\
                             x,y,z)                    

 宏展开   

预处理器在处理宏定义时,会对宏进行展开(即宏替换)。

宏替换首先将源文件中在宏定义随后所有出现的宏名均用其所代表的代码序列替换之,如果是带参数宏则接着将代码序列中的宏形参名替换为宏实参名。

宏替换只作代码字符序列的替换工作,不作任何语法的检查,也不作任何的中间计算,一切其它操作都要在替换完后才能进行。

如果宏定义不当,错误要到预处理之后的编译阶段才能发现。

源代码中的宏名和宏定义代码序列中的宏形参名必须是标识符才会被替换,即只替换标识符,不替换别的东西,像注释、字符串常量以及标识符内出现的宏名或宏形参名则不会被替换。 如果希望宏定义代码序列中标识符内出现的宏形参名能够被替换,可以在宏形参名与标识符之间添加连接符##,在宏替换过程中宏形参名和连接符##一起将被替换为宏实参名。##用于把宏参数名与宏定义代码序列中的标识符连接在一起,形成一个新的标识符。例如:

 #define BLOG(name) my_name_blog="name" //宏定义代码序列中的宏形参名name也都不会被替换。
 #define BLOG(name) my_##name,BLOG(vrmozart)//表示my_vrmozart

如果希望宏定义代码序列中的宏形参名被替换为宏实参名的字符串形式(即在宏实参名两端加双引号"),而不是替换为宏实参名,可以在宏定义代码序列中的宏形参名前面添加符号#。#用于把宏参数名变为一个字符串形式。例如:

#define STR(name) #vrmozart,STR(vrmozart)表示"vrmozart"

当宏参数是另一个宏的时候,需要注意的是宏定义代码序列中有用#或##的宏参数是不会再展开。

宏的独立性   

在宏定义中说过,宏名和宏形参名所代表的内容及意义在宏展开前后必须一直是独立且保持不变的,不能分开解释和执行。其原因如下,在宏调用时,用宏定义的代码序列替换宏名,用宏实参名替换宏形参名。替换后,宏定义的代码序列就与源文件中相邻的代码自然连接,宏实参名也与代码序列中相邻的代码自然连接,宏定义的代码序列和宏实参名的独立性就不一定依旧存在。例如:

#define SQR(x) x*x,希望实现表达式的平方计算。
对于宏调用p=SQR(y),能得到希望的宏展开p=y*y。但对于宏调用q=SQR(u+v),得到的宏展开是q=u+v*u+v。显然,后者的展开结果不是程序设计者所希望的。为能保持宏实参名替换后的独立性,应在宏定义中给形式参数加上括号。进一步,为了保证宏名调用的独立性,作为算式的宏定义代码序列也应加括号。SQR宏定义改写成#define SQR(x) ((x)*(x))才是正确的宏定义。

 宏调用与函数调用的区别

函数调用在程序运行时实行,而宏展开是在编译的预处理阶段进行.

函数调用占用程序运行时间,宏调用只占编译时间.

函数调用对实参有类型要求,而宏调用实在参数与宏定义形式参数之间没有类型的概念,只有字符序列的对应关系.

函数调用可返回一个值,宏调用获得希望的代码序列。

函数调用时,实参表达式分别独立求值在前,执行函数体在后。宏调用是实参字符序列替换形式参数。

转载于:https://www.cnblogs.com/xiaojianliu/articles/8723096.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值