当前位置:  编程技术>c/c++/嵌入式

深入探讨:宏、内联函数与普通函数的区别

    来源: 互联网  发布时间:2014-10-16

    本文导语:  内联函数的执行过程与带参数宏定义很相似,但参数的处理不同。带参数的宏定义并不对参数进行运算,而是直接替换;内联函数首先是函数,这就意味着函数的很多性质都适用于内联函数,即内联函数先把参数表达式进行运...

内联函数的执行过程与带参数宏定义很相似,但参数的处理不同。带参数的宏定义并不对参数进行运算,而是直接替换
;内联函数首先是函数,这就意味着函数的很多性质都适用于内联函数,即内联函数先把参数表达式进行运算求值,然
后把表达式的值传递给形式参数。
    内联函数与带参数宏定义的另一个区别是,内联函数的参数类型和返回值类型在声明中都有明确的指定;而带参数
宏定义的参数没有类型的概念,只有在宏展开以后,才由编译器检查语法,这就存在很多的安全隐患。
    使用内联函数时,应注意以下问题:
    1)内联函数的定义性声明应该出现在对该函数的第一次调用之前。
    2)内联函数首先是函数,函数的很多性质都适用于内联函数,如内联函数可以重载。
    3)在内联函数中不允许使用循环语句和switch结果,带有异常接口声明的函数也不能声明为内联函数。
先说宏和函数的区别:
1. 宏做的是简单的字符串替换(注意是字符串的替换,不是其他类型参数的替换),而函数的参数的传递,参数是有数据类
型的,可以是各种各样的类型.
2. 宏的参数替换是不经计算而直接处理的,而函数调用是将实参的值传递给形参,既然说是值,自然是计算得来的.
3. 宏在编译之前进行,即先用宏体替换宏名,然后再编译的,而函数显然是编译之后,在执行时,才调用的.因此,宏占用的
是编译的时间,而函数占用的是执行时的时间.
4. 宏的参数是不占内存空间的,因为只是做字符串的替换,而函数调用时的参数传递则是具体变量之间的信息传递,形参
作为函数的局部变量,显然是占用内存的.
5. 函数的调用是需要付出一定的时空开销的,因为系统在调用函数时,要保留现场,然后转入被调用函数去执行,调用完,
再返回主调函数,此时再恢复现场,这些操作,显然在宏中是没有的.
现在来看内联函数:
所谓"内联函数"就是将很简单的函数"内嵌"到调用他的程序代码中,只样做的目的是为了避免上面说到的第5点,目的旨
在节约下原本函数调用时的时空开销.但必须注意的是:作为内联函数,函数体必须十分简单,不能含有循环、条件、选择
等复杂的结构,否则就不能做为内联函数了。事实上,即便你没有指定函数为内联函数,有的编译系统也会自动将很简
单的函数作为内联函数处理;而对于复杂的函数,即便你指定他为内联函数,系统也不会理会的。

介绍内联函数之前,有必要介绍一下预处理宏。内联函数的功能和预处理宏的
功能相似。相信大家都用过预处理宏,我们会经常定义一些宏,如
#define TABLE_COMP(x) ((x)>0?(x):0)
就定义了一个宏。

  为什么要使用宏呢?因为函数的调用必须要将程序执行的顺序转移到函数
所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行
该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地
址,转回后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一
定的时间和空间方面的开销,于是将影响其效率。而宏只是在预处理的地方把
代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个
函数更有效率。

  但是宏也有很多的不尽人意的地方。
  1、.宏不能访问对象的私有成员。
  2、.宏的定义很容易产生二意性。
我们举个例子:
#define TABLE_MULTI(x) (x*x)
  我们用一个数字去调用它,TABLE_MULTI(10),这样看上去没有什么错误,
结果返回100,是正确的,但是如果我们用TABLE_MULTI(10+10)去调用的话,
我们期望的结果是400,而宏的调用结果是(10+10*10+10),结果是120,这显
然不是我们要得到的结果。避免这些错误的方法,一是给宏的参数都加上括号。

#define TABLE_MULTI(x) ((x)*(x))
 
  这样可以确保不会出错,但是,即使使用了这种定义,这个宏依然有可能
出错,例如使用TABLE_MULTI(a++)调用它,他们本意是希望得到(a+1)*(a+1)的
结果,而实际上呢?我们可以看看宏的展开结果: (a++)*(a++),如果a的值是
4,我们得到的结果是5*6=30。而我们期望的结果是5*5=25,这又出现了问题。
事实上,在一些C的库函数中也有这些问题。例如: Toupper(*pChar++)就会对
pChar执行两次++操作,因为Toupper实际上也是一个宏。

  我们可以看到宏有一些难以避免的问题,怎么解决呢?
 
  下面就是用我要介绍的内联函数来解决这些问题,我们可以使用内联函数
来取代宏的定义。而且事实上我们可以用内联函数完全取代预处理宏。

  内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是
通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时
候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开
销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一
些问题。
  我们可以用Inline来定义内联函数,不过,任何在类的说明部分定义的函
数都会被自动的认为是内联函数。

  下面我们来介绍一下内联函数的用法。
 
  内联函数必须是和函数体申明在一起,才有效。像这样的申明
Inline Tablefunction(int I)是没有效果的,编译器只是把函数作为普通的函
数申明,我们必须定义函数体。

Inline tablefunction(int I) {return I*I};
 
  这样我们才算定义了一个内联函数。我们可以把它作为一般的函数一样调
用。但是执行速度确比一般函数的执行速度要快。

  我们也可以将定义在类的外部的函数定义为内联函数,比如:
代码如下:
 
Class TableClass{
 Private:
  Int I,j;
 Public:
  Int add() { return I+j;};
  Inline int dec() { return I-j;}
  Int GetNum();
}
inline int tableclass::GetNum(){
return I;
}
 
  上面申明的三个函数都是内联函数。在C++中,在类的内部定义了函数体的
函数,被默认为是内联函数。而不管你是否有inline关键字。

  内联函数在C++类中,应用最广的,应该是用来定义存取函数。我们定义的
类中一般会把数据成员定义成私有的或者保护的,这样,外界就不能直接读写我
们类成员的数据了。
    对于私有或者保护成员的读写就必须使用成员接口函数来进行。如果我们把
这些读写成员函数定义成内联函数的话,将会获得比较好的效率。
代码如下:
 
Class sample{
 Private:
  Int nTest;
 Public:
  Int readtest(){ return nTest;}
 Void settest(int I) {nTest=I;}
}
 
  当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如
果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式
调用函数。这样,内联函数就和普通函数执行效率一样了。

 
宏的使用
/*这一系列文章《C++ Tips》是公司Code Committee专家会推荐工程师看的,感觉很好,拿出来与大家共同提高。并不
是知道多少会使人与人产生差别,真正的差别在于你能做到多少。
很多程序员不知道C中的“宏”到底是什么意思?特别是当宏有参数的时候,经常把宏和函数混淆。我想在这里我还是
先讲讲“宏”,宏只是一种定义,他定义了一个语句块,当程序编译时,编译器首先要执行一个“替换”源程序的动作
,把宏引用的地方替换成宏定义的语句块,就像文本文件替换一样。这个动作术语叫“宏的展开”。使用宏是比较“危
险”的,因为你不知道宏展开后会是什么一个样子。例如下面这个宏:
   #define MAX(a, b) a>b?a:b
当我们这样使用宏时,没有什么问题: MAX( num1, num2 ); 因为宏展开后变成 num1>num2?num1:num2;。 但是,如
果是这样调用的,MAX( 17+32, 25+21); 呢,编译时出现错误,原因是,宏展开后变成:17+32>25+21?17+32:25+21,
Woh,这是什么啊?
所以,宏在使用时,参数一定要加上括号,上述的那个例子改成如下所示就能解决问题了。
   #define MAX( (a), (b) ) (a)>(b)?(a)b)
即使是这样,也不这个宏也还是有Bug,因为如果我这样调用 MAX(i++,j++); , 经过这个宏以后,i和j都被累加了两
次,这绝不是我们想要的。所以,在宏的使用上还是要谨慎考虑,因为宏展开是的结果是很难让人预料的。而且虽然,
宏的执行很快(因为没有函数调用的开销),但宏会让源代码澎涨,使目标文件尺寸变大,(如:一个50行的宏,程序
中有1000个地方用到,宏展开后会很不得了),相反不能让程序执行得更快(因为执行文件变大,运行时系统换页频繁
)。
因此,在决定是用函数,还是用宏时得要小心。
 
C++中的内联函数定义很简单,只要在普通的函数前加一个关键字inline就可以了,除此之外和普通函数表面上没有什
么区别(包括函数的调用方式),因为这样,所以在很多的C++初学者(甚至一些有C++编程经验的人) 看来,内联只
是一个概念而已,其实这是对内联函数没有彻底的认识,下面我们就来谈谈内联函数和普通 函数以及和宏的区别,相
信读完下面的部分,你对这三者一定有了很好的理解。
       内联函数和普通函数最大的区别在于内部的实现方面,而不是表面形式,我们知道普通函数在被调用时,系统
首先要 跳跃到该函数的入口地址,执行函数体,执行完成后,再返回到函数调用的地方,函数始终只有一个拷贝; 而
内联函数则不需要进行一个寻址的过程,当执行到内联函数时,此函数展开(很类似宏的使用),如果在 N处调用了此
内联函数,则此函数就会有N个代码段的拷贝。
       从内联函数的调用来看,它因为少了一个寻址过程而提高了代码的执行效率,但是这是以空间的代价来换取的

       声明为内联的函数,其代码段不能太长,过长,一些编译器则视为普通 函数(究竟函数体多长就超过了限制,
这个好象没有规定,这个也确实不好规定,个人觉得应该视函数体的逻辑而定)。
      下面是内联函数的声明举例:
     inline void SetVal(int a){ m_b = a};
     inline int GetVal(){ return m_b};
     从上面的例子可以看出,内联函数的声明和实现通常都会在一个文件当中(一般放在.h中就可以了)。
     下面我们再来说说内联函数与宏的区别。很多的资料上,在谈到内联函数时就说,内联函数和宏很类似,但是类
似归类似,毕竟我们不能把这两者互换使用。
     这两者的相似之处在于执行时编译器对其的处理,会将其代码展开,执行完后继续下面的处理。不同之处在于宏
是简单的文本替换,它不能返回值,也没有一般函数参数的概念;而内联函数则具备了普通函数的特征,如参数列表,
返回值等。下面我们举个例子说明:
     1.#define COUNT(X)(X * X) // 一个计算乘积的宏
     2.inline int count(int x){return x*x} //一个计算乘积的内联函数
   
     printf(COUNT(3)); // 结果为 COUNT(3) ( 3 * 3) = 9;
     printf(count(3)); // 结果为 count(3){return 3*3 }=9;

     上面的例子好象不足以说明两者的区别,我们把上面的例子的调用改改,再看看结果

     printf(COUNT(2+3)); //结果为COUNT(2+3)(2+3 * 2+3) = 11
     printf(count(2+3)); //结果为count(2+3){return 5*5 ;} = 25;

     如果宏要达到乘积为25的结果,应该这样写:
     #define COUNT(X)((X)*(X))
     对应到上面的例子就是 #define COUNT(2+3)((2+3)*(2+3))

    
 
 

您可能感兴趣的文章:

  • 深入mysql YEAR() MONTH() DAYOFMONTH()日期函数的详解
  • 内联函数inline与宏定义深入解析
  • 虚函数与纯虚函数(C++与Java虚函数的区别)的深入分析
  • SQL SERVER函数之深入表值函数的处理分析
  • 深入探讨:main函数执行完毕后,是否可能会再执行一段代码?
  • 深入解析函数指针与返回函数的指针
  • 基于memset()函数的深入理解
  • C语言typedef与复杂函数声明问题的深入解析
  • 深入VC回调函数的使用详解
  • strcat函数与strncat函数的深入分析
  • 深入Main函数中的参数argc,argv的使用详解
  • 深入C++中构造函数、拷贝构造函数、赋值操作符、析构函数的调用过程总结
  • 深入解析C++中的构造函数和析构函数
  • MySQL中group_concat函数深入理解
  • 基于Mysql的IP处理函数inet_aton()与inet_ntoa()的深入分析
  • C++函数参数取默认值的深入详解
  • 解析VC中创建DLL,导出全局变量,函数和类的深入分析
  • 深入理解atoi()与itoa()函数的用法
  • 深入C++拷贝构造函数的总结详解
  • 一般函数指针和类的成员函数指针深入解析
  •  
    本站(WWW.)旨在分享和传播互联网科技相关的资讯和技术,将尽最大努力为读者提供更好的信息聚合和浏览方式。
    本站(WWW.)站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。












  • 相关文章推荐
  • 大家能否深入探讨一下J2EE到底包含那些东东,在实际企业应用是否如同J2EE所承诺的一样!
  • 深入探讨:Oracle中如何查询正锁表的用户以及释放被锁的表的方法
  • 基于c中使用ftruncate()前需要fflush(),使用后需要rewind()的深入探讨
  • 深入探讨java的接口和抽象的内涵!
  • 深入探讨java的接口和抽象的内涵!(续上贴,上贴分已给)
  • C++实现strcmp字符串比较的深入探讨
  • 深入探讨:MySQL数据库MyISAM与InnoDB存储引擎的比较
  • 深入探讨C#中的结构struct
  • 整体刷新和局部刷新frameset窗口问题深入探讨
  • 用32位int型变量表示单引号括起来的四个字符的深入探讨
  • 深入探讨CSS中字体元素
  • 深入探讨C#中的const、readonly关键字
  • 函数中使用require_once问题深入探讨 优雅的配置文件定义方法推荐
  • 深入探讨:oracle中row_number() over()分析函数用法
  • 深入探讨C++父类子类中虚函数的应用
  • 深入探讨linux下进程的最大线程数、进程最大数、进程打开的文件数
  • 深入探讨:oracle中方案的概念以及方案与数据库的关系
  • Java源码分析:深入探讨Iterator模式
  • 深入探讨Linux静态库与动态库的详解(一看就懂)
  • 深入探讨JAVA中的异常与错误处理
  • Docker支持更深入的容器日志分析
  • 关于《深入浅出MFC》
  • Linux有没有什么好的高级的书,我要深入,
  • 深入理解linux内核
  • [100分]有没有关于binutils的深入的资料?或者深入底层的资料?
  • 深入理解PHP内核 TIPI
  • 想深入学习Java应该学习哪些东西
  • 哪位有《JSP深入编程》电子版?
  • 想要深入学习LINUX该学什么?
  • 100分求:哪儿有《深入理解linux内核》可供下哉!
  • 如何深入Linux的内核学习?


  • 站内导航:


    特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

    ©2012-2021,,E-mail:www_#163.com(请将#改为@)

    浙ICP备11055608号-3