你真的会用宏定义吗?【C语言】

一句话解释宏定义

所谓宏定义,就是用标识符来表示一些东西,所表示的对象可以是一个数字,也可以是一个字符串,甚至是一段程序。对于#define MAX 100,MAX 就是那个标识符,也称宏名,100 就是替换标识符的内容,也称作宏的定义
在对C源程序预处理时,代码中的所有被宏定义过的标识符都会被替换成它的定义,这个过程叫做"宏替换"或"宏展开"

宏定义详解

  • 简单的宏(对象式宏)#define 标识符 替换列表
    #define MAX 5               
    #define reg register        //为关键字register创造一个别名reg 
    #define do_forever for(;;)  //一个无限循环函数

替换列表可以包括数值常量、字符常量、字符串常量、关键字、标识符和操作符等,当预处理器遇到一个宏定义时,则会将对应替换列表中的内容替换。

  • 带参数的宏(函数式宏)#define 标识符(参数列表) 替换列表
    函数式宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参
    函数式宏的参数没有限制,可以是类型名、变量名等,因此可以做到函数所不可能的操作,如下面第三个宏定义和#运算符的例子
    #define MAX(x,y) x>y ? x:y               
    #define IS_EVEN(x) ((x)%2 == 0)    
    #define OFFSETOF(StructName, MemberName) (size_t)&(((StructName *)0)->MemberName)   
    //宏定义实现offsetof( )函数,求结构体中某成员的偏移量
    //宏定义的参数为类型名
  • 用于宏定义的一些特殊运算符
    • #运算符
      #运算符将宏的一个参数转换成字符串,仅出现在带参数的宏定义的替换列表中,可以看作给这个参数加上了双引号
      #include<stdio.h>
      #define tostring(c) #c
      #define PRINT(n) printf("The value of "#n" is %d\n", n);
      int main()
      {
          int a = 0, b = 1;
          printf(tostring(a)"\n");
          PRINT(a);       //实际操作的是参数名本身,而不是参数的值
          PRINT(b);
          return 0;
      }
      //out:
      //a
      //The value of a is 0
      //The value of b is 1
      
    • #@运算符
      运算符将宏的一个参数转换成字符,仅出现在带参数的宏定义的替换列表中,可以看作给这个参数加上了单引号
      #include<stdio.h>
      #define tochar(n) #@n
      int main()
      {
          int x = 10;
          printf("%c = %d", tochar(x), x);
      }
      //out:
      //x = 10
      
    • ##运算符
      ##运算符可将两个片段合成一个片段,并进行替换
      #include<stdio.h>
      #define CAT(m, n) m##n 
      int main()
      {
          int num1 = 10;
          printf("%d", CAT(num, 1));
          return 0;
      }
      //out:
      //10
      

宏定义的常见错误使用

  • #include<stdio.h>
    #define ADD1(m, n) m+n
    int main()
    {
        int m = 2, n = 10;
        printf("%d\n", 3 * ADD1(m, n));
        return 0;
    }
    //预计输出:60
    //实际输出:16
    //改正  #define ADD1(m, n) (m+n)
    
  • #define ADD2(m, n) m++; n++;
    int main()
    {
        int m = 1, n = 1;
        if (1) {m--; n--;}
        else ADD2(m, n);
        printf("%d %d\n", m, n);
        return 0;
    }
    //预计输出:0 0
    //实际输出:0 1
    //改正  #define ADD2(m, n) {m++; n++;}
    
  • #define MAX(a, b) ( (a) > (b) ? (a) : (b) )
    int main()
    {
        int x = 5, y = 8;
        int z = MAX(x++,y++);
        printf("%d %d %d\n", x, y, z);
        return 0;
    }
    //预计输出:6 9 8
    //实际输出:6 10 9
    //改正  避免宏定义中使用带副作用的参数
    

这些错误都是因为宏定义只进行了简单的文本替换,程序并没有按照假想的逻辑而执行,不过大部分的错误都能通过规范的编程习惯来避免。
实际开发中,我们很少会使用宏定义去处理复杂的事情。

预定义宏

一些C预先定义完成特定功能的宏

名字定义
__FILE__被编译的文件名
__LINE__文件当前的行号
__DATE__文件被编译的日期
__TIME__文件被编译的时间
__STDC__如果编译器符合C标准,其值为 1

对宏定义的一些说明

  • 宏的本质是进行简单的文本替换,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现,稍不注意可能出现意想不到的错误
  • 宏的替换列表可以包含对其他宏的调用,即宏定义允许嵌套
  • 预处理器只会替换完整的符号,并不会替换宏的片段,也不会替换双引号之内的宏,如:#define MAX 10不会对BUFFER_MAX中的MAX进行替换
  • 宏定义的作用范围通常到出现该宏的文件末尾
  • 宏定义可使用#undef指令取消 #define 标识符
  • 函数式宏与函数
属性函数式宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度相对较快由于存在函数调用与返回,所有可能相对较慢
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果更容易控制
参数类型宏的参数与类型无关,只要对参数的操作合法的,它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的。
调试不可调试可调试
递归不可递归可递归

参考书目:《C语言程序设计现代方法 第二版》

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值