当前位置:首页 > 嵌入式 > 嵌入式大杂烩
[导读]·  正  ·  文  ·  来  ·  啦  · 前言 ------在上篇文章里面,我们分析了预处理的一个完整过程,这能够让我们理解一个写好的程序,在生成一个可执行文件,到底发生了什么,对我们在大型工程项目里面有助于对程序的理解;今天我们继续接着上篇文章的基础上




·  正  ·  文  ·  来  ·  啦  ·


前言

------在上篇文章里面,我们分析了预处理的一个完整过程,这能够让我们理解一个写好的程序,在生成一个可执行文件,到底发生了什么,对我们在大型工程项目里面有助于对程序的理解;今天我们继续接着上篇文章的基础上,来分享有关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__)
                       ^




总结

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



关注公众号,每周分享至少3篇开源技术干货,文章中如有没看懂的地方可以私聊我,我看到了会立马回复你,个人微信号:a18879499804

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭