简单了解宏以及宏和函数的区别

  1.=》宏的认识


简单来说:宏定义又称为宏代换、宏替换,简称“宏”。是C提供的三种预处理功能的其中一种。

 宏定义是C提供的三种预处理功能的其中一种,这三种预处理包括:宏定义、文件包含、条件编译.

=》无参的宏定义

 宏定义又称为宏代换、宏替换,简称“宏”。

宏定义的格式很关键。 

基本的开头格式是  比如我们要写一个game.h的头文件


#ifndef _GAME_H__   这一句的意思就是如果game.h没有被定义 然后往下跑,若已存在那就直接cut. 
#define _GAME_H__    然后这句就是开始定义了
                    //举个例子我们现在要定义一个宏
#define n =10  ×   //表面的意思你是让N等于10  其实呢  n代表的是   “=10” 
#define n 10;  ×   //这里又错了   不能再宏后面加“ ;
#define n 10   √  //正确的方法

#endif               //_GMAE_H__这只是一个好习惯..

在一个宏定义中,编译器可以检测到绝大多数由多余符号所导致的错误。但不幸的是,编译器
会将每一处使用这个宏 的地方标为错误,而不会直接找到错误的根源——宏定义本身,因为宏
定义已经被预处理器删除了。

=》小细节


 (1)宏名一般用大写

 (2)使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。例如:数组
      大小常用宏定义

 (3)预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
 (4)宏定义末尾不加分号;

 (5)宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。

 (6)可以用#undef命令终止宏定义的作用域

 (7)宏定义可以嵌套

 (8)字符串" "中永远不包含宏

 (9)宏定义不分配内存,变量定义分配内存。


=》有参的宏定义

 #define指令—带参数的宏]  #define 标识符(x1, x2,…,xn)替换列表 

#ifndef  _GMAE_H__

#define _GAME_H__

#define MAX(x,y)    ((x)>(y) ? (x) :(y)) //这样的就是一个标准有参宏定义

#define IS_EVEN(n)   ((n)%2==0)        //记住有参的宏定义中每一个参数都要带括号记住!!!
                                        //为了避免不必要的错误0.0
#endif   //_GAME_H__

 (1)实参如果是表达式容易出问题

          #define S(r) r*r

          area=S(a+b);第一步换为area=r*r;,第二步被换为area=a+b*a+b;

          这就是为什么每个参数都要带括号

           正确的宏定义是#define S(r) ((r)*(r))

 (2)宏名和参数的括号间不能有空格

 (3)宏替换只作替换,不做计算,不做表达式求解

 (4)函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存

 (5)宏的哑实结合不存在类型,也没有类型转换。

 (6)函数只有一个返回值,利用宏则可以设法得到多个值

 (7)宏展开使源程序变长,函数调用不会

 (8)宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)


2.=》1使用带参数的宏替代实际的函数的优点



1. 程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销——存储上下文信

息、复制参数的值等。而一个宏的调用则没有这些运行开销。


2. 宏会更“通用”。与函数的参数不同,宏的参数没有类型。因此,只要预处理后的程

序依然是合法的,宏可以接受任何类型的参数。例如,我们可以使用MAX宏从两个数中选

出较大的一个,数的类型可以是int,long int,float,double等等。


2.=》2带参数的宏也有一些缺点:



=》1编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导致程


序的源代码增加(因此编译后的代码变大)。宏使用得越频繁,这种效果就越明显。当


宏调用嵌套时,这个问题会相互叠加从而使程序更加复杂。思考一下,如果我们用MAX宏


来找出3个数中最大的数会怎样?

            n = MAX(i, MAX(j,k));  

           下面是预处理后的这条语句:


            n=((i)>(((j)>(k)?(j):(k)))?(i):(((j)>(k)?(j):(k)))); 

 

=》2宏参数没有类型检查。当一个函数被调用时,编译器会检查每一个参数来确认它们


是否是正确的类型。如果不是,或者将参数转换成正确的类型,或者由编译器产生一个


出错信息。预处理器不会检查宏参数的类型,也不会进行类型转换。



=》3无法用一个指针来指向一个宏。C语言允许指针指向函数。这一概念在特定的编程条


件下非常有用。宏会在预处理过程中被删除,所以不存在类似的“指向宏的指针”。因


此,宏不能用于处理这些情况。

 常用的预定义的宏

 1、__DATE__

   “替代文字”是一个含有编译日期的字符串字面值,日期格式为“mm dd yyyy”
   (例如:“Mar 19 2006”)。如 果日期小于10日,就在日的前面放一个空格符。

 2、__FILE__

   此字符串字面值含有目前源代码文件名称。

 3、__LINE__

   一个整数常量,其值是目前源代码的行号(包含__LINE__宏所指的那一行代码),从文件头
   开始算起。

__LINE__和__FILE__用于打印调试信息会非常方便。

printf("line = %d\n", __LINE__);

printf("file = %s\n", __FILE__);

 4、__TIME__

   此字符串字面值包含编译时间,格式为“hh:mm:ss”(范例:“08:00:59”)。

 5、__STDC__

   整数常量1,表示此编译器遵循ISOC标准。



3.=》#运算符


它仅允许出现在带参数的宏的替换列表中。

宏定义可以包含两个运算符:#和##。编译器不会识别这两种运算符相反,它们会在预处理时被执行

#运算符将一个宏的参数转换为字符串字面量(字符串字面量(string literal)是指双

引号引住的一系列字符,双引号中可以没有字符,可以只有一个字符,也可以有很多个

字符 简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号.

其实还挺有用的比如下面这个例子

#ifndef _GAME_H__
#define _GAME_H__


#define printf_s(x) printf(#x " = %d\n", x)


#endif              // _GAME_H__

然后在函数中 在宏替换中 会变成这样 举个例子x 输入的是   liangliang

printf("liangliang" " = %d\n",x);

然后你就会发现前面自动帮你打印了  这个就很方便了。


4. =》##运算符


刚刚了解了#的用法,现在看看##  他的意思就是一个“连接符”,也是一个预处理运算符,这里
的语言符号不一定是宏的变量。并且双井号不能作为第一个和最后一个存在。
##运算符可以将两个记号(例如标识符),“粘”在一起,成为一个新的记号。如果其中的一个
操作数是宏函数,“粘合”会在当形式参数被相应的实际参数替换后发生。
比如下面这个例子
#ifndef _GAME_H__
#define _GAME_H__

#define int fun_s(x) hehe##x
//那么预处理结束后, 如果x=liang 那么结果 声明  int heheliang;

#endif //_GAME_H__ 
虽然感觉## 没什么用... 可能是我的技术还没有达到。但是有很经典的例子 比如

当MAX的参数有副作用时会无法正常工作。一种解决方法是用MAX宏来写一个max函数。遗憾
的是,往往一个 max函数是不够的。我们可能需要一个实际参数是int值的max函数,还需要
参数为float值的max函数,等等。除了 实际参数的类型和返回值的类型之外,这些函数都一
样。因此,这样定义每一个函数似乎是个很蠢的做法。

解决的办法是定义一个宏,并使它展开后成为max函数的定义。宏会有唯一的参数type,它表示形式
参数和返回值的 类型。这里还有个问题,如果我们是用宏来创建多个max函数,程序将无法编译。
(C语言不允许在同一文件中出现 两个重名的函数。)为了解决这个问题,我们是用##运算符为每
个版本的max函数构造不同的名字。
 
  #define GENERIC_MAX (type)

  type type##_max(type x,  type y)
    { 
          return x > y ? x :y;
    }

   GENERIC_MAX(float

   //预处理器会将这行展开为下面的代码:
   float float_max(float x, float y) { return x > y ? x :y; }


5=》宏的通用属性


1=》宏的替换列表可以包含对另一个宏的调用。例如,我们可以用宏PI来定义宏TWO_PI:
#definePI      3.14159
#defineTWO_PI  (2*PI)
当预处理器在后面的程序中遇到TWO_PI时,会将它替换成(2*PI)。接着,预处理器会重新检查
替换列表,看它是否包含其他宏的调用(在这个例子中,调用了宏PI)。预处理器会不断重新检查
替换列表,直到将所有的宏名字都替换掉为止

2=》预处理器只会替换完整的记号,而不会替换记号的片断。因此,预处理器会忽略嵌在标识符名
、字符常量、字符串字面量之中的宏名

3=》一个宏定义的作用范围通常到出现这个宏的文件末尾。由于宏是由预处理器处理的,他们不遵
从通常的范围规则。一个定义在函数中的宏并不是仅在函数内起作用,而是作用到文件末尾。

4=》宏不可以被定义两遍,除非新的定义与旧的定义是一样的。小的间隔上的差异是允许的,但是
宏的替换列表(和参数,如果有的话)中的记号都必须一致。

5=》宏可以使用#undef指令“取消定义”。#undef指令有如下形式:
[#undef指令]  #undef  标识符 
其中标识符是一个宏名。例如,指令
#undef N
会删除宏N当前的定义。(如果N没有被定义成一个宏,#undef指令没有任何作用。)#undef指令的
一个用途是取消一个宏的现有定义,以便于重新给出新的定义。

6=》宏定义中的do-while循环do 


do循环必须始终随跟着一个分号,因此我们不会遇到在if语句中使用宏那样的问题了。

一个例子

#define fun(s)  
 do{  

   get(x);

      }while(0);
当使用fun宏时,一定要加分号:
     如果fun(str);

     那么 /* becomes do {  gets(str);  } while (0);  */

为什么在宏定义时需要使用do-while语句呢? 我们知道do-while循环语句是先执行循环体再判断条件是
否成立, 所 以说至少会执行一次。当使用do{ }while(0)时由于条件肯定为false,代码也肯定只 执行一
次, 肯定只执行一次的 代码为什么要放在do-while语句里呢? 这种方式适用于宏定义中存在多语句的情况。
  1.   #define TEST(a, b)  a++;b++;  
  2.    
  3. if (expr)  
  4.     TEST(a, b);  
  5. else  
  6.     do_else();  
  7. 代码进行预处理后,会变成:  
  8. if (expr)  
  9.     a++;b++;  
  10. else  
  11.     do_else();  

    这样if-else的结构就被破坏了if后面有两个语句,这样是无法编译通过的,那为什么非要

do-while而不是简单的用{}括起来呢。 这样也能保证if后面只有一个语句。例如上面的例子,

在调用宏TEST的时候后面加了一个分号, 虽然这个分号可有可无, 但是出于习惯我们一般都会

写上。 那如果是把宏里的代码用{}括起来,加上最后的那个分号。 还是不能通过编译。 所以一

般的多表达式宏定义中都采用do-while(0)的方式。



7.=》总结


好多人不知道平时使用宏定义还是函数 其实我个人挺喜欢用函数的,但是呢各有各的好处。。。。

他们的区别,大家可以看着哪个合适用哪个


1.宏会在编译器在对源代码进行编译的时候进行简单替换,不会进行任何逻辑检测,即简单代码复制而已


2.宏进行定义时不会考虑参数的类型。

3.参数宏的使用会使具有同一作用的代码块在目标文件中存在多个副本,即会增长目标文件的大小。
4.参数宏的运行速度会比函数快,因为不需要参数压栈/出栈操作。
5.参数宏在定义时要多加小心,多加括号。
6.函数只在目标文件中存在一处,比较节省程序空间。
7.函数的调用会牵扯到参数的传递,压栈/出栈操作,速度相对较慢。
8.函数的参数存在传值和传地址(指针)的问题,参数宏不存在。
各有利弊吧看着用,但是宏在有的地方用会有意想不到的结果和便利。。 这些就是我个人的一些见解。。
哈哈终于写完!  有诚意的文章.......(有些是我粘贴复制的,但是都搞清楚了).


  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Pulover Macro Creator 中文版是基于 AutoHotkey 的轻量级应用程序,可为您提供实用的自动化工具和脚本生成器。它显示了一个全面的界面,可让您记录鼠标的移动,键盘手势,管理窗口,文件,控件以及创建 If/Else 语句。为了使该应用程序直观易懂并使其有效,该应用程序的所有主要特征和功能都在顶部工具栏中保持了大量可用。 开源免费自动化工具 Pulover's Macro Creator 中文版开源免费自动化工具 Pulover’s Macro Creator 中文版 特色亮点 Pulover Macro Creator 可以自动完成操作,从简单的鼠标单击到具有循环和条件的复杂。内置的记录器是一种方便的工具,可轻松创建精确的。 滑鼠与键盘 发送击键,键盘命令,移动并单击。 控制指令 在后台控制窗口。设置并从窗口的控件中获取信息。 窗口命令 等待窗口存在或处于活动状态。移动,调整大小,最大化,最小化和设置各种窗口设置。 图像和像素搜索 在屏幕上搜索图像或像素。制作屏幕截图并定义要采取的措施。 循环 简单和高级循环。遍历文件和文件夹,文本文件和对象。 如果陈述 控制设置条件的流程。 变量与函数 定义和修改变量和对象。使用字符串,数字和变量。创建数组。 Internet Explorer 和常规 COM 对象 IE 的简化界面,并支持自动热键表达式,从而可以控制 Excel 和 Word 等应用程序。 用户定义的功能 使用要在中使用的输入和输出值定义自己的函数。 使用 Pulover Macro Creator,您可以制作脚本,然后将其设置为自动执行各种繁琐的任务,这些任务只会消耗您一天的时间并降低生产力。无论您是需要插入自定义代码的程序员,还是开发人员测试应用程序功能并做出响应的开发人员,都可以轻松地为您提供帮助。 无需使用任何程序设计知识即可使用 Pulover Macro Creator,因为该应用程序设计为易于使用。如果您对是什么以及如何使用有基本的了解,那么只需单击记录按钮并导出项目就可以满足您的需要。 这并不意味着该应用程序仅限于基本脚本。使用Pulover的Macro Creator,您可以在一个脚本下加入不同的,然后从预览窗口中将代码复制到所需的任何位置。它提供文本,控件,消息框,窗口,图像/像素搜索,循环和标签命令,以及变量和功能。 导出时,您可以设置其名称,触发脚本的热键,设置循环数,甚至选择阻止鼠标光标,以便运行而不会中断。 有了以上要考虑的内容以及关于 Pulover Macro Creator 的更多发现,可以肯定地说这确实是一种可靠且实用的自动化工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值