vlambda博客
学习文章列表

C语言宏定义常见用法




·  正  ·  文  ·  来  ·  啦  ·


前言

------在上篇文章里面,我们分析了预处理的一个完整过程,这能够让我们理解一个写好的程序,在生成一个可执行文件,到底发生了什么,对我们在大型工程项目里面有助于对程序的理解;今天我们继续接着上篇文章的基础上,来分享有关c语言里面关于宏定义的用法!


宏定义基本语法

每个#define行(即逻辑行)由三部分组成:第一部分是指令 #define 自身,“#”表示这是一条预处理命令,“define”为宏命令。第二部分为宏(macro),一般为缩略语,其名称(宏名)一般大写,而且不能有空格,遵循C变量命令规则。第三部分“替换文本”可以是任意常数、表达式、字符串等。在预处理工作过程中,代码中所有出现的“宏名”,都会被“替换文本”替换。这个替换的过程被称为“宏代换”或“宏展开”(macro expansion)。“宏代换”是由预处理程序自动完成的。在C语言中,“宏”分为两种:无参数 和 有参数(这里有参数先不举例子,下面具体分析的话,读者可以详细看到示例来理解这个)。下面是宏定义的基本形式:


     #define   宏名     宏体注意:宏体后面不要加分号“;”,这个在写代码的时候要小心点哦



宏定义的优点和缺点

------优点:


1、方便程序的修改:


      使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时, 我们可以用较短的有意义的标识符来写程序,这样更方便一些(特别当跨平台的时候,要修改程序一些参数的时候,用宏定义的话,只需要修改宏定义的宏名就可以代表修改了整个程序里面用到这个宏名,就不用一个个去改了,极大的提升了工作效率!)。


2、提高程序的运行效率

     

       这里我们就拿带参宏和函数来对比了:


      (1)宏定义是在预处理期间处理的,而函数是在编译期间处理的。这个区别带来的实质差异是:宏定义最终是在调用宏的地方把宏体原地展开,而函数是在调用函数处跳转到函数中去执行,执行完后再跳转回来。


注:宏定义和函数的最大差别就是:宏定义是原地展开,因此没有调用开销;而函数是跳转执行再返回,因此函数有比较大的调用开销。所以宏定义和函数相比,优势就是没有调用开销,没有传参开销,所以当函数体很短(尤其是只有一句话时)可以用宏定义来替代,这样效率高。


     (2)带参宏和带参函数的一个重要差别就是:宏定义不会检查参数的类型,返回值也不会附带类型;而函数有明确的参数类型和返回值类型。当我们调用函数时编译器会帮我们做参数的静态类型检查,如果编译器发现我们实际传参和参数声明不同时会报警告或错误。


注:用函数的时候程序员不太用操心类型不匹配因为编译器会检查,如果不匹配编译器会警告(但是实际测试并没有警告,理论上是有的);用宏的时候程序员必须很注意实际传参和宏所希望的参数类型一致,否则可能编译不报错但是运行有误(一般所希望的是整型数据类型,不然结果一般会出错,下面的例子就是);而且最好在宏体里面每个参数都带小括号,因为有时候会涉及到运算符号的优先级问题,这样一来写程序的话就不会引起bug了。


#include <stdio.h>

#define MAX(a, b) (((a)>(b)) ? (a) : (b))

int max(int a, int b)
{
    if (a > b)
            return a;
    else
            return b;
}

int main(void)
{


    float a, b, c;
    a = 1.5;
    b = 4.7;



    c = MAX(a, b);                          // 展开后:c = (((a)>(b)) ? (a) : (b));
    printf("c = %d.\n", c);
    c = max(a, b);                          // 无法展开,只能调用
    printf("c = %d.\n", c);


    return 0;
}


我们来看一下它预处理过后成了什么样了:

2 "b.c" 2
6 "b.c"
int max(int a, int b)
{
   if (a > b)
   return a;
   else
    return b;
}

int main(void)
{


      float a, b, c;
      a = 1.5;
      b = 4.7;



      c = (((a)>(b)) ? (a) : (b));
      printf("c = %d.\n", c);
      c = max(a, b);
      printf("c = %d.\n", c);


      return 0;
}


演示结果(你会看到):

b.c: In function ‘main’:
b.c:25:15: warning: format ‘%d’ expects argument of type 
int’, but argument 2 has type ‘double’ [-Wformat=]
 printf("c = %d.\n", c);
          ~^
          %f
 b.c:27:15: warning: format ‘%d’ expects argument of type 
int’, but argument 2 has type ‘double’ [-Wformat=]
  printf("c = %d.\n", c);
          ~^
          %f
 root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
 c = -1272947832.
 c = 4.


总结:宏和函数各有千秋,各有优劣。总的来说,如果代码比较多用函数适合而且不影响效率;但是对于那些只有一两句话的函数开销就太大了,适合用带参宏。但是用带参宏又有缺点:不检查参数类型。


------缺点:


  • 由于是直接嵌入的,所以代码可能相对多一点。


  • 嵌套定义过多可能会影响程序的可读性,而且很容易出错,不容易调试。


  • 对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患。





宏定义的用法

1、嵌套宏的使用:

 #include <stdio.h>

 #define M    10  
 #define N     M
 int main(void)
 
{

   printf("the M is %d\n",M);

   printf("the N is %d\n",N);

   return 0;

 }


预处理之后:


 # 5 "b.c"
int main(void)
{

    printf("the M is %d\n",10);

    printf("the N is %d\n",10);

    return 0;

}


演示结果(简单来讲嵌套宏说白了还是直接替换的作用):

root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
the M is 10
the N is 10


2、#运算符

      出现在宏定义中的#运算符把跟在其后的参数转换成一个字符串。有时把这种用法的#称为字符串化运算符。例如:

#include <stdio.h>

#define  M(n)   "hhh"#n        

int main(void)
{

printf("the M(6) is %s\n",M(6));


return 0;

}


演示结果:

   the M(6is hhh6


3、##运算符

      ##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。看下面的例子:

#include <stdio.h>

#define  M(a,b,c)          a##b##c

int main(void)
{

  printf("the M(2,3,4) is %d\n",M(2,3,4));
 return 0;

}


演示结果:

the M(2,3,4is 234


4、宏定义中使用了do{}  while(0) (这种形式在代码还是经常能看的到的,下面我还是用例子来慢慢引导大家来看懂这用这个的含义):

 #include <stdio.h>

 #define  M(n)  \
 printf("the n is %d\n",n);\
 printf("the M(n) is %d\n",n);


 int main(void)
 
{

   int n=8;
   int a=1;
   if(a)
           M(n);


   return 0;

}


预处理后(你可以看到这样一来,第二条语句就没有在我们if语句的范围内了,而且读者应该注意到,带参宏有点像函数调用,调用这个也是一条语句,所以语句后面加了“;”,这里在实际编译过程中是多加了,会导致编译报错;但是不在这条语句后面加的话,就不像一条语句了,不过它是可以编译通过的,下面改进后的程序就是这种情况):

 # 8 "b.c"
 int main(void)
{

     int n=8;
     int a=1;
    if(a)
      printf("the n is %d\n",n);printf("the M(n) is %d\n",n);;


   return 0;

}

改进后(加了{}):
 #include <stdio.h>

 #define  M(n)  \
 {printf("the n is %d\n",n);\
 printf("the M(n) is %d\n",n);}


 int main(void)
 
{

    int n=8;
    int a=1;
   if(a)
          M(n);
   else
    printf("error\n");
   return 0;    
}
预处理后(这个程序就会报错了,就是我上面分析的原因):
 # 8 "b.c"
 int main(void)
 {

     int n=8;
     int a=1;
     if(a)
    {printf("the n is %d\n",n);printf("the M(n) is 
 %d\n"
,n);};
     else
      printf("error\n");

   return 0;

 }

演示结果:
  root@ubuntu-virtual-machine:/home/ubuntu# gcc b.c
  b.c: In function ‘main’:
  b.c:15:2: error: ‘else’ without a previous ‘if
  else
  ^~~~

最后我们来使用这个结构再次来改进上面的代码看看效果如何:
 #include <stdio.h>

 #define  M(n)  \
 do{\
     printf("the n is %d\n",n);\
     printf("the M(n) is %d\n",n);\
 }while(0)


 int main(void)
{

      int n=8;
      int a=1;
      if(a)
            M(n);
      else
     printf("error\n");

      return 0;

  }

预处理后(加了do{}while(0)后,上面的问题就全部解决了):
 # 10 "b.c"
 int main(void)
 {

      int n=8;
      int a=1;
      if(a)
     do{printf("the n is %d\n",n);printf("the M(n) is 
   %d\n"
,n);}while(0);
      else
       printf("error\n");

    return 0;

 }

5、可变宏的使用:

      C99中规定宏可以像函数一样带有可变参数,实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个英文输入法下的句号)。这样预定义宏__VA_ARGS__就可以被用在替换部分中,以表明省略号代表什么:

#include<stdio.h>
#define Variable_Macro(...)   printf(__VA_ARGS__)
int main(void)
{
     Variable_Macro("This is a variable macro test...\n");
     Variable_Macro("My age is %d",22);
     return 0;
}


预处理后:

   # 3 "b.c"
   int main(void)
  
{
      printf("This is a variable macro test...\n");
      printf("My age is %d",22);
      return 0;
  }


演示结果:

 

 This is a variable macro test...
 My age is 22


注意:带参宏后面不能再有参数,而我们的带参函数前面必须要有参数(这里我就不举例子关于带参函数了)

#include<stdio.h>
#define Variable_Macro(...,a)   printf(__VA_ARGS__)
int main(void)
{
     Variable_Macro("This is a variable macro test...\n");
     Variable_Macro("My age is %d",22);
     return 0;
}


演示结果:

   

 root@ubuntu-virtual-machine:/home/ubuntu# gcc b.c
 b.c:2:27: error: missing ')' in macro parameter list
 #define Variable_Macro(...,a)   printf(__VA_ARGS__)
                       ^




总结

今天的分享就到这里了,晚安!