C++ inline 函数简介

1.inline 函数简介

inline 函数由 inline 关键字定义,引入 inline 函数的主要原因是用它替代 C 中复杂易错不易维护的宏函数。

2.编译器对 inline 函数的处理办法

编译器在编译阶段完成对 inline 函数的处理,即对 inline 函数的调用替换为函数的本体。但 inline 关键字对编译器只是一种建议,编译器可以这样去做,也可以不去做。从逻辑上来说,编译器对 inline 函数的处理步骤一般如下:
(1)将 inline 函数体复制到inline函数调用处;
(2)为所用 inline 函数中的局部变量分配内存空间;
(3)将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
(4)如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用GOTO)。

比如如下代码:

//求0-9的平方
inline int inlineFunc(int num)
{  if(num>9||num<0)return -1;  return num*num;  
}  int main(int argc,char* argv[])
{int a=8;int res=inlineFunc(a);cout<<"res:"<<res<<endl;
}

inline 之后的 main 函数代码类似于如下形式:

int main(int argc,char* argv[])
{int a=8;{  int _temp_b=8;  int _temp;  if (_temp_q >9||_temp_q<0) _temp = -1;  else _temp =_temp*_temp;  b = _temp;  }
}  

经过以上处理,可消除所有与调用相关的痕迹以及性能的损失。inline 通过消除调用开销来提升性能。

3.inline 函数使用的一般方法

函数定义时,在返回类型前加上关键字 inline 即把函数指定为内联,函数申明时可加也可不加。但是建议函数申明的时候,也加上 inline,这样能够达到"代码即注释"的作用。

使用格式如下:

inline int functionName(int first, int secend,...) {/****/};

inline如果只修饰函数的申明的部分,如下风格的函数foo不能成为内联函数:

inline void foo(int x, int y); //inline仅与函数声明放在一起void foo(int x, int y){}

而如下风格的函数foo 则成为内联函数:

void foo(int x, int y);inline void foo(int x, int y){} //inline与函数定义体放在一起

4.inline 函数的优点与缺点

从上面可以知道,inline函数相对宏函数有如下优点:
(1)内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。

(2)内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
例如宏函数和内联函数:

//宏函数
#define MAX(a,b) ((a)>(b)?(a):(b))//内联函数
inline int MAX(int a,int b)
{return a>b?a:b;
}

使用宏函数时,其书写语法也较为苛刻,如果对宏函数出现如下错误的调用,MAX(a,"Hello"); 宏函数会错误地比较int和字符串,没有参数类型检查,但是使用内联函数的时候,会出现类型不匹配的编译错误。

(3)在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。

(4)内联函数在运行时可调试,而宏定义不可以。

万事万物都有阴阳两面,内联函数也不外乎如此,使用inline函数,也要三思慎重。inline函数的缺点总结如下:
(1)代码膨胀。
inline函数带来的运行效率是典型的以空间换时间的做法。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

(2)inline 函数无法随着函数库升级而升级。
如果f是函数库中的一个inline函数,使用它的用户会将f函数实体编译到他们的程序中。一旦函数库实现者改变f,所有用到f的程序都必须重新编译。如果f是non-inline的,用户程序只需重新连接即可。如果函数库采用的是动态连接,那这一升级的f函数可以不知不觉的被程序使用。

(3)是否内联,程序员不可控。
inline函数只是对编译器的建议,是否对函数内联,决定权在于编译器。编译器认为调用某函数的开销相对该函数本身的开销而言微不足道或者不足以为之承担代码膨胀的后果则没必要内联该函数,若函数出现递归,有些编译器则不支持将其内联。

5.inline函数的注意事项

了解了内联函数的优缺点,在使用内联函数时,我们也要注意以下几个事项和建议。

(1)使用函数指针调用内联函数将会导致内联失败。
也就是说,如果使用函数指针来调用内联函数,那么就需要获取inline函数的地址。如果要取得一个inline函数的地址,编译器就必须为此函数产生一个函数实体,那么就内联失败。

(2)如果函数体代码过长或者有多重循环语句,if或witch分支语句或递归时,不宜用内联。

(3)类的 constructors、destructors 和虚函数往往不是 inline 函数的最佳选择。
类的构造函数(constructors)可能需要调用父类的构造函数,析构函数同样可能需要调用父类的析构函数,二者背后隐藏着大量的代码,不适合作为inline函数。虚函数(destructors)往往是运行时确定的,而inline是在编译时进行的,所以内联虚函数往往无效。如果直接用类的对象来使用虚函数,那么对有的编译器而言,也可起到优化作用。

(4)至于内联函数是定义在头文件还是源文件的建议。
内联展开是在编译时进行的,只有链接的时候源文件之间才有关系。所以内联要想跨源文件必须把实现写在头文件里。如果一个 inline 函数会在多个源文件中被用到,那么必须把它定义在头文件中。

// base.h
class base{protected:void fun();};// base.cpp
#include base.h
inline void base::fun(){}//derived.h
#include base.h
class Derived: public base{public:void g();};// derived.cpp
void Derived::g(){fun();} //VC2010: error LNK2019: unresolved external symbol

上面这种错误,就是因为内联函数 fun() 定义在编译单元 base.cpp 中,那么其他编译单元中调用fun()的地方将无法解析该符号,因为在编译单元 base.cpp 生成目标文件 base.obj 后,内联函数fun()已经被替换掉,编译器不会为 fun() 生成函数实体,链接器自然无法解析。所以如果一个 inline 函数会在多个源文件中被用到,那么必须把它定义在头文件中。

这里有个问题,当在头文件中定义内联函数,那么被多个源文件包含时,如果编译器因为 inline 函数不适合被内联时,拒绝将inline函数进行内联处理,那么多个源文件在编译生成目标文件后都将各自保留一份inline函数的实体,这个时候程序在链接阶段会出现重定义错误吗?答案是不会,原因是,链接器在链接的过程中,会删除多余的inline函数实体,只保留一份,所以不会报重定义错误,因此我们不需要使用 static 关键字去多余地修饰inline函数,即不必像下面这样。

//test.h
static inline int max(int a,int b)
{return a>b?a:b;
}

(5)能否强制编译器进行内联操作?
也有人可能会觉得能否强制编译器进行函数内联,而不是建议编译器进行内联呢?很不幸的是目前还不能强制编译器进行函数内联,如果使用的是 MS VC++, 注意 __forceinline 如同 inine 一样,也是一个用词不当的表现,它只是对编译器的建议比inline更加强烈,并不能强制编译器进行inline操作。

(6)如何查看函数是否被内联处理了?
在 VS2017 中查看预处理后的.i文件,发现inline函数的内联处理不是在预处理阶段,而是在编译阶段。将源文件编译为汇编代码,或者将可执行文件反汇编生成汇编代码,在汇编代码中查看inline函数被调用处是否出现汇编的call指令,如果没有则说明inline函数在被调用处进行了函数体的替换操作,即内联处理。具体可以参考内联函数到底有没有被嵌入到调用处呢?。

(7)C++类成员函数定义在类体内为什么不会报重定义错误?
类成员函数定义在类体内,并随着类的定义放在头文件中,当被不同的源文件包含,那么每个源文件都应该包含了类成员函数的实体,为何在链接的过程中不会报函数的重定义错误呢?

原因是在类里定义时,这种函数会被编译器编译成内联函数,在类外定义的函数则不会。内联函数的好处是加快程序的运行速度,缺点是会增加程序的尺寸。比较推荐的写法是把一个经常要用的而且实现起来比较简单的小型函数放到类里去定义,大型函数最好还是放到类外定义。

可能存在疑问,类体内的成员函数被编译器内联处理,但并不是所有的成员函数都会被内联处理,比如包含递归的成员函数。但是实际测试,将包含递归的成员函数定义在类体内,被不同的源文件包含并不会报重定义错误,为什么会这样呢?请保持着疑问与好奇心,请继续往下看。

如果编译器发现被定义在类体内的成员函数无法被内联处理,那么在程序的链接过程中也不会出现函数重定义的错误。其原因是什么呢?其实很简单,类体内定义的成员函数即使不被内联处理,在链接时,链接器会对重复的成员函数实体进行冗余优化,只保留一份函数实体,也就不会出现函数重定义的错误了。

除了 inline 函数,C++编译器在很多时候都会产生重复的代码,比如模板(Templates)、虚函数表(Virtual Function Table)、类的默认成员函数(构造函数、析构函数和赋值运算符)等。以函数模板为例,在多个源文件中生成相同的实例,链接时不会出现函数重定义的错误,实际上是一个道理,因为链接器会对重复代码进行删除,只保留一份函数实体。

6.小结

可以将内联理解为 C++ 中对于函数专有的宏,对于 C 的函数宏的一种改进。对于常量宏,C++ 提供 const 替代;而对于函数宏,C++ 提供的方案则是 inline。C++ 的内联机制,既具备宏代码的效率,又增加了安全性,还可以自由操作类的数据成员,算是一个比较完美的解决方案。

来源:https://dablelv.blog.csdn.net/article/details/52065524

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/338590.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

intellij ide_UltraESB的首选IDE – IntelliJ IDEA

intellij ide在AdroitLogic&#xff0c;我们长期以来一直在使用IntelliJ IDEA进行开发。 它是Java和相关语言/技术的最佳IDE&#xff08;它可能也是许多其他语言的选择&#xff0c;但我的经验主要是Java和相关技术&#xff09;。 Groovy和IDEA的Grails的集成很棒。 通过自动发…

C++抽象类

概念在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。抽象类往往用来表征对问题领域进行分…

tibco_TIBCO产品的微服务和DevOps

tibco大家都在谈论微服务 &#xff0c;这些天。 您可以在数百篇文章和博客文章中读到很多有关微服务的信息。 马丁福勒 &#xff08; Martin Fowler &#xff09;的文章是一个很好的起点&#xff0c;该文章引发了有关这种新架构概念的大量讨论。 另一个不错的资源是独立于供应…

C atoi函数

作用atoi()函数将数字格式的字符串转换为整数类型。例如&#xff0c;将字符串1253124127转换成数字1253124127。注意首要注意atoi函数可以转化如下这种字符串1fdafhdjfhkas关于参数的注意事项&#xff0c;atoi()函数的参数是要转换的字符串。该字符串的格式为[空格][符号][数字…

开式蓄冷罐与闭式蓄冷罐_一罐将其全部统治:Arquillian + Java 8

开式蓄冷罐与闭式蓄冷罐借助Java 8 &#xff0c;已实现了许多新的语言改进&#xff0c;以简化开发人员的生活。 在我看来&#xff0c; Java 8的最大优点之一是&#xff0c;在某些情况下&#xff0c;已开发的代码看起来比使用以前的方法更漂亮&#xff0c;我指的是Lambdas和Meth…

C 预处理指令

C 预处理指令C语言、C 语言的预处理器。用于在编译器处理程序之前预扫描源代码&#xff0c;完成头文件的包含, 宏扩展, 条件编译, 行控制&#xff08;line control&#xff09;等操作编译的四个阶段C语言标准规定&#xff0c;预处理是指前4个编译阶段&#xff08;phases of tra…

将html代码转换为dom,将HTML字符转换为DOM节点并动态添加到文档中

将HTML字符转换为DOM节点并动态添加到文档中将字符串动态转换为DOM节点&#xff0c;在开发中经常遇到&#xff0c;尤其在模板引擎中更是不可或缺的技术。字符串转换为DOM节点本身并不难&#xff0c;本篇文章主要涉及两个主题&#xff1a;1 字符串转换为HTML DOM节点的基本方法及…

通过Spring集成进行消息处理

Spring Integration提供了Spring框架的扩展&#xff0c;以支持著名的企业集成模式。 它在基于Spring的应用程序中启用轻量级消息传递&#xff0c;并支持与外部系统的集成。 Spring Integration的最重要目标之一是为构建可维护且可测试的企业集成解决方案提供一个简单的模型。 …

鸿蒙系统多会发布,华为官宣鸿蒙系统将发布,还将发布多款新品

华为今日官宣&#xff0c;6月2日20&#xff1a;00&#xff0c;将举行鸿蒙操作系统及华为全场景新品发布会。【1、鸿蒙OS2.0】本次发布会的重点将是推出华为今年的重点战略产品——鸿蒙系统。届时鸿蒙OS2.0正式版将陆续推送给手机用户。5月中旬时华为就先行推送了一波鸿蒙OS开发…

C语言结构体字节对齐

默认字节对齐C语言结构体字节对齐是老生常谈的问题了&#xff0c;也是高频面试题&#xff0c;现在我们来深入研究这个问题&#xff0c;彻底弄懂到底是怎么回事&#xff0c;给你一个结构体定义和平台机器位数就能手动计算出结构体占用字节数&#xff0c;现在我们不使用宏#pragma…

meetup_使用RxNetty访问Meetup的流API

meetup本文将涉及多个主题&#xff1a;响应式编程&#xff0c;HTTP&#xff0c;解析JSON以及与社交API集成。 完全在一个用例中&#xff1a;我们将通过非夸张的RxNetty库实时加载和处理新的metup.com事件&#xff0c;结合Netty框架的强大功能和RxJava库的灵活性。 Meetup提供了…

html约束验证的例子,HTML5利用约束验证API来检查表单的输入数据的代码实例

HTML5对于表单有着极大程度的优化&#xff0c;无论是语义&#xff0c;小部件&#xff0c;还是数据格式的验证。我猜你肯定会以浏览器兼容作为借口不愿意使用这些“新功能”&#xff0c;但这绝不应该成为使你停滞不前的原因&#xff0c;况且还有像Modernizr和ployfill这样的工具…

C语言经典题(1)

输入某年某月某日&#xff0c;判断这一天是这一年的第几天&#xff1f;程序分析&#xff1a;以3月5日为例&#xff0c;应该先把前两个月的加起来&#xff0c;然后再加上5天即本年的第几天&#xff0c;特殊情况&#xff0c;闰年且输入月份大于3时需考虑多加一天#include int mai…

C语言面试-指针和引用的使用场景?

先解决两个疑问◆ 指针和引用的不同之处是什么&#xff1f;◆ 何时用用指针&#xff1f;何时用引用&#xff1f;指针和引用的不同之处看如下代码&#xff1a;指针是用来表示内存地址的&#xff0c;而指针这个整数正是被指向的变量地址。而引用就是给变量重新起了一个名字&#…

C语言指针变量与一维数组

数组元素之间的地址是相连的&#xff1b;变量地址绝对不是相连的&#xff0c;如果找到规律那也只是一个偶然的&#xff0c;不是必然的&#xff1b;1. 指针变量和一位数组下面对指针数组进行分析&#xff0c;index(小标是从0开始)&#xff0c;array数组是int类型&#xff0c;每一…

input发送a.jax_JAX-RS 2.0:服务器端处理管道

input发送a.jax这篇文章的灵感来自JAX-RS 2.0规范文档 &#xff08;附录C&#xff09;中的Processing Pipeline部分。 我喜欢它的原因在于它提供了JAX-RS中所有模块的漂亮快照-以准备好吞咽的胶囊形式&#xff01; 礼貌– JAX-RS 2.0规范文档 因此&#xff0c;我想到了使用此…

html 登录失败,qq音乐登录失败 QQ音乐总是显示登录失败是怎么回事

urlproc.exe是什么进程?没见过&#xff0c;请前辈们指点&#xff1f;造成QQ音乐登录不上现象的原因有如下三种可能&#xff1a; 一、木马病毒对QQ音乐的必要组件或文件进行破坏&#xff0c;导致QQ音乐登录失败&#xff0c;登陆不上的情况发生。 二、Windows系统防火墙(或其他安…

C 常对象成员

C 常对象成员在C 中&#xff0c;可以将对象的成员声明为const&#xff0c;包括常数据成员和常成员函数C 常数据成员 常数据成员的作用与一般常变量相似&#xff0c;用关键字const来声明常数据成员。常数据成员的值是不能改变的&#xff0c;只能通过构造函数的参数初始化表对常数…

娄底二中高考2021成绩查询,2021年娄底高考状元名单公布,娄底高考状元学校资料及最高分...

2019年高考已经落下帷幕&#xff0c;高考放榜时刻就要到来&#xff0c;每年的高考状元都会被各界高度关注&#xff0c;那么今年娄底高考状元花落谁家呢&#xff1f;娄底高考状元会给人带来惊喜吗&#xff0c;让我们一起期待2019年娄底高考状元的诞生。下面小编为给为梳理下历年…

C 常指针

C 指向对象的常指针C 定义指向对象的常指针的一般形式为 类名 * const指针变量名&#xff1b;也可以在定义指针变量时使之初始化在C 中&#xff0c;虽然指向对象的常指针变量的值不能改变&#xff0c;但可以改变其所指向对象的值&#xff0c;如果想将一个指针变量固定地与一个对…