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




·  正  ·  文  ·  来  ·  啦  ·


前言

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

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

上海2024年4月17日 /美通社/ -- 在2024 F1中国站即将拉开帷幕之际,高端全合成润滑油品牌美孚1号今日举办了品牌50周年庆祝活动。三届F1年度车手总冠军马克斯•维斯塔潘也亲临现场,共同庆祝这一里程...

关键字: BSP 汽车制造 行业标准 产品系列

北京2024年4月17日 /美通社/ -- 2024年4月13日,由北京康盟慈善基金会主办的"县域诊疗,规范同行"——肿瘤诊疗学术巡讲项目首站在广州隆重召开。本次会议邀请全国多位肺癌领域专家和县域同道...

关键字: AI技术 医疗服务 BSP 互联网

海口2024年4月16日 /美通社/ -- 4月14日,在中法建交60周年之际,科学护肤先锋品牌Galenic法国科兰黎受邀入驻第四届中国国际消费品博览会(以下简称"消博会")法国馆。Galenic法...

关键字: NI IC BSP ACTIVE

上海2024年4月17日 /美通社/ -- 每年4月17日是世界血友病日。今年,世界血友病日以"认识出血性疾病,积极预防和治疗"为主题,呼吁关注所有出血性疾病,提升科学认知,提高规范化诊疗水平,让每一位出血性疾病患者享有...

关键字: VII 动力学 软件 BSP

伦敦2024年4月16日 /美通社/ -- ATFX宣布任命Siju Daniel为首席商务官。Siju在金融服务行业拥有丰富的经验和专业知识,曾在全球各地的高管职位上工作了19年以上。Siju之前担任FXCM首席商务官...

关键字: NI AN SI BSP

嵌入式开发作为信息技术领域的重要分支,其涉及的语言种类繁多,各具特色。这些语言的选择取决于目标平台的特性、性能需求、开发者的熟练程度以及项目的具体要求。本文将详细介绍几种常见的嵌入式开发语言,包括C语言、C++、汇编语言...

关键字: 嵌入式开发 C语言

Java语言和C语言是两种不同的编程语言,它们在语法、特性和应用领域上有许多差别。下面将详细介绍Java语言和C语言之间的差异以及它们各自的技术特点。

关键字: Java语言 C语言 编程

嵌入式系统是一种专门设计用于特定应用领域的计算机系统,它通常由硬件和软件组成,并且被嵌入到其他设备或系统中,以实现特定的功能。在嵌入式系统的开发过程中,选择适合的编程语言是至关重要的。C语言是一种被广泛应用于嵌入式系统开...

关键字: 嵌入式 计算机 C语言

C语言是一种广泛应用于软件开发领域的编程语言。它是由贝尔实验室的Dennis Ritchie在20世纪70年代初创建的,旨在为UNIX操作系统的开发提供一种高级编程语言。C语言具有简洁、高效、可移植性强等特点,因此成为了...

关键字: C语言 操作系统 应用程序
关闭
关闭