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




·  正  ·  文  ·  来  ·  啦  ·


前言

------在上篇文章里面,我们分析了预处理的一个完整过程,这能够让我们理解一个写好的程序,在生成一个可执行文件,到底发生了什么,对我们在大型工程项目里面有助于对程序的理解;今天我们继续接着上篇文章的基础上,来分享有关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获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

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

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭