8.1.2 带参数的宏定义
前面讲的是不带参数的宏定义,只是进行简单的内容替换,那这里要讲的带参数的宏定义,就不仅是进行简单的内容替换,还要进行参数替换。其一般形式为:
作用:用右边的“被替换的内容”代替“宏名(参数表)”,但具体怎样替换,后面会详细讲,和不带参数的宏定义相比,这里多了个参数表。在被替换的内容中,一般都会包含参数表中所指定的参数。看看如下范例:
在上面的范例中,用了宏S(3,2),系统是怎样替换的呢?把3、2分别代替宏定义中的形参a、b,最终用3*2替换了S(3,2)。所以程序代码“intArea=S(3,2);”就等价于“int Area=3*2;”。
刚刚说过,一般“被替换的内容”中都会包含参数表中所指定的参数,但不包含也是可以的,但若不包含,那么通过参数表传进去这个参数就没什么意义了。看看如下范例:
带参数的宏定义展开置换的总结:
对一般形式中提到的“被替换的内容”,要从左到右处理。如果“被替换的内容”中有“宏名”后列出的形参,如a、b,则将程序代码中相应的实参(可以是常量、变量或者表达式)代替形参,如果“被替换的内容”中的项并不是“宏名”后列出的形参,则保留,如上面a*b中的“*”就会被保留。看看如下范例:
有几点说明:
(1)如果代码中出现“area=S(1+5);”,替换后会变成3.1415926*1+5*1+5,这肯定不对,程序代码的原意是替换后变成3.1415926*(1+5)*(1+5)。为了解决这个问题,要在形参外面加一个括号。如下所示:
这样S(1+5)在替换展开后才能变成3.1415926*(1+5)*(1+5)。
(2)宏定义时,宏名和带参数的括号之间不能加空格,否则,空格之后的内容都作为被替代内容的一部分。看如下代码:
这样,S成为不带参数的宏定义,代表被替换内容“(r)PI*(r)*(r)”,显然是不对的。
是不是感觉带参数的宏和函数挺像的,宏也有实参和形参,所以两者不好区分?实际上两者之间还是有很不相同的地方。总结一下:
(1)函数调用是先求出实参表达式的值,然后传递给形参,带参数的宏只进行简单的内容替换,宏展开时并不求值,如上面的S(1+5),宏展开时并不求1+5的值,只是原样用实参替换掉形参。
(2)函数调用是在程序运行阶段执行到该函数时才执行其中的代码,这涉及比如为所调用的函数分配临时内存等一系列工作。但宏展开是在编译阶段进行的,而且展开时也并不分配内存,当然也不存在“值传递”“返回值”等只有在函数调用中才存在的说法。
(3)宏的参数没有类型这个说法,只是一个符号,展开时用指定内容替换。例如#defineS(r)PI*(r)*(r),其中的r是没有类型这种概念和说法的。
(4)宏展开每进行一次,源程序代码都会有所增多,如“area=S(1+5);”,在宏展开时会被替换成“area=3.1415926*(1+5)*(1+5);”,显然代码变多了,所以使用宏的次数如果增多,源程序代码就会增多,但函数调用不会使源程序代码增多。
(5)宏展开只占用编译时间,不占用运行时间,而函数调用占用运行时间(分配内存、传递参数、执行函数体、返回值等)。
有时会用宏来代替一些较复杂的语句,看看如下相对复杂一点的范例,求x和y的最大值:
在程序代码中像下面这样使用即可:
还有能代替多行语句的宏定义写法,看看如下范例,注意末尾的“\”,用来表示下一行代码和本行代码本是同一行,这种用法在一定程度上能简化程序书写。
#define中还有一些特殊的用法,如#、##等,不过这些都并不常用,所以这里不准备花过多篇幅讨论,以免给读者增加学习和理解负担,在笔者看来,知识永远也学不完,花过多篇幅讲述一个不常用的知识点并不划算,将来万一需要,在已有知识的基础之上,通过搜索引擎进一步学习来快速掌握新知识,才是正确的学习方法。
宏定义的应用因人而异,有些人会大量频繁地使用,有些人用的相对比较少,日后阅读一些比较大型的跨平台C语言/C++项目源码时,可能会看到更多的宏定义应用场合。