分享

不可小看的技术——C语言中的“宏”

 庆亮trj21bcn0z 2017-11-23

宏定义是我们C语言学习中非常重要的内容。一些基础的用法大家都比较清楚了,我们简单总结一下。

不可小看的技术——C语言中的“宏”

  1. 宏定义的格式为:#define标识符 字符串

  2. 宏定义属于预处理命令,在编译过程中的预处理阶段处理

  3. 宏定义只是单纯的替换,所以当被替换内容涉及运算等的时候最好加上括号()

  4. 宏定义的标示符一般用大写。

  5. 宏定义的标示符为常量标示符,即不可再赋值。

  6. 宏定义末尾不加分号。

以上说的是宏定义的最基本用法,可以带来很多好处。比如让我们的标示符有意义,让我们的代码修改更方便,可以替代在代码中常用的字符串缩短代码等。其实在宏定义中,我们也可以像一个“函数”一样实现一个的功能,这种用法叫函数宏,函数宏在我们对宏定义的使用中更加的常见,下面我们从五个方面来了解下函数宏的使用。

  1. 函数宏的书写

#define MAX(a,b) ((a)>(b)?(a):(b)) ,这就是一个简单的函数宏,我们同样可以传递参数,实现功能。但是在书写上注意两点MAX和左“(”之间没有空格,因为宏定义把标示符后的第一个空格会认为是标示符与字符串的分割。当然我们在写宏的时候有时候会写多行,这样我们一般用“\”进行分割

  1. 加括号

我们说到宏只是简单的替换,即使是函数宏也是这样的,所以为了避免一些优先级的错误不要忘记加括号

  1. 宏的副作用

这也是函数宏和函数不同的地方。比如上边的例子#define MAX(a,b) ((a)>(b)?(a):(b))我们传入的参数是++a和++b。很显然如果使用函数实现这个功能的话a和b均自加一次,但是如果用宏实现替换后就变成((++a)>(++b)?(++a):(++b)),很明显,这与函数就完全不同了。

  1. do{}while(0)结构

例如:

  1. #define DELETE_POINTER(p) \

  2. do \

  3. { \

  4. if(NULL != p) \

  5. delete p; \

  6. p = NULL; \

  7. }while(0)

这种结构在函数宏里非常常见,它不仅可以在调用后加分号保持代码的格式一致性,还可以避免一些复杂的宏定义产生的错误。当然,每行后面不要忘了也是需要”\”的。

  1. 函数宏中的#和##运算符

在函数宏中#可以实现由函数宏实参生成字符串常量,##实现了由函数宏实参生成标识符的一部分。(前者用于拼接字符串后者用于拼接标示符)看一下下边的示例:

#

假如希望在字符串中包含宏参数,ANSI C允许这样作,在类函数宏的替换部分,#符号用作一个预处理运算符,它可以把语言符号转化程字符串。例如,如果x是一个宏参量,那么#x可以把参数名转化成相应的字符串。该过程称为字符串化(stringizing).

  1. #incldue

  2. #define PSQR(x) printf('the square of' #x 'is %d.\n',(x)*(x))

  3. int main(void)

  4. {

  5. int y =4;

  6. PSQR(y);

  7. PSQR(2+4);

  8. return 0;

  9. }

输出结果:

the square of y is 16.

the square of 2+4 is 36.

第一次调用宏时使用“y”代替#x;第二次调用时用“2+4'代#x。

##

##运算符可以使用类函数宏的替换部分。另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如:

#define XNAME(n) x##n

这样宏调用:

XNAME(4)

展开后:x4

程序:

  1. #include

  2. #define XNAME(n) x##n

  3. #define PXN(n) printf('x'#n' = %d\n',x##n)

  4. int main(void)

  5. {

  6. int XNAME(1)=12;//int x1=12;

  7. PXN(1);//printf('x1 = %d\n', x1);

  8. return 0;

  9. }

那么说了这么多,大家一定有疑问,函数宏和函数的区别又有什么呢?

我们把上面第一条的例子用函数来实现:

int max( int a, int b)

{

return (a > b a : b)

}

如果这段代码要频繁使用,让我们选择用函数宏或者用函数来实现。很显然,我们不会选择用函数来完成这个任务,原因有两个:首先,函数调用会带来额外的开销,它需要开辟一片栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈。这种开销不仅会降低代码效率,而且代码量也会大大增加,而使用宏定义则在代码规模和速度方面都比函数更胜一筹;其次,函数的参数必须被声明为一种特定的类型,所以它只能在类型合适的表达式上使用,我们如果要比较两个浮点型的大小,就不得不再写一个专门针对浮点型的比较函数。反之,上面的那个宏定义可以用于整形、长整形、单浮点型、双浮点型以及其他任何可以用“>”操作符比较值大小的类型,也就是说,宏是与类型无关的。和使用函数相比,使用宏的不利之处在于每次使用宏时,一份宏定义代码的拷贝都会插入到程序中。除非宏非常短,否则使用宏会大幅度增加程序的长度。还有一些任务根本无法用函数实现,但是用宏定义却很好实现。比如参数类型没法作为参数传递给函数,但是可以把参数类型传递给带参的宏。

看下面的例子:

#define MALLOC(n, type) \

( (type *) malloc((n)* sizeof(type)))

利用这个宏,我们就可以为任何类型分配一段我们指定的空间大小,并返回指向这段空间的指针。我们可以观察一下这个宏确切的工作过程:

int *ptr;

ptr = MALLOC ( 5, int );

将这宏展开以后的结果:

ptr = (int *) malloc ( (5) * sizeof(int) );

这个例子是宏定义的经典应用之一,完成了函数不能完成的功能。

把类型作为函数宏参数是C语言实现泛型的一种手段,这也是函数宏最常用的场合之一。在后续出现的编程语言入C++中把这种需求作为一种新的语法特性(模板)加以实现。

我们主要介绍了函数宏的用法,熟练的掌握还需要更多的练习,希望在以后代码编程过程中可以将函数宏融入我们的代码,使我们的代码水平不断提高。

不可小看的技术——C语言中的“宏”

持续更新

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多