概述
1.inline函数简介:
定义:由inline关键字来定义 | 引用的原因:用他来代替C中复杂易错不用维护的宏函数。 |
2.编译器对inline函数的处理方法
编译器是在编译阶段对inline函数作出处理,将调用动作替换为函数的本体,但是他只是一种建议,编译器可做可不做。
其逻辑处理方式一般采取如下措施:
1.将inline函数体复制到inline函数的调用点处, |
2.为所用inline函数中的局部变量分配空间 |
3.将inline函数的输入参数和返回值映射到调用方法的局部变量空间中 |
4.if inline 函数有多个返回点,将其转变为inline函数代码块末尾的分支(用GOTO) |
3.inline函数使用的一般方法
函数定义时,在返回类型前面加上inline关键字,即把函数指定为内联。函数申明处可加可不加,但是建议加上因为这样起到了“代码即注释“的作用。
inline int functionName(int first,int second,......){/* */};
if inline 只修饰了申明部分,那么该函数不能成为内联函数。
inline void foo(int x,int y);//inline与函数申明放在一起
void foo(int x,int y){}
4.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.内联函数在运行时可以调试(debug版本(和static函数大致相同),relese版本不可以(和宏函数大致相似)),宏定义则不可以。
二:世界万物都有其两面性,内联函数也不例外。
1.代码膨胀: | 典型的以空间换取时间。 |
2.inline函数无法随函数库的升级而升级 | if f是函数库中的一个inline函数,使用它的用户会将f函数实体编译到他们的程序中。一旦函数库实现者改变f,all用到该函数的地方都必须重新编译,if 函数是no_inline函数,则只需要重新链接该函数, |
3.是否内敛,程序员不可控 | inline只是对编译器的建议,是否对函数内联,取决于编译器。 |
5.inline函数的使用注意事项
1.使用函数指针调用内联函数将会导致内联失败。 | 也就是说,如果使用函数指针来调用内联函数,既要获取inline函数的地址。如果要取得一个内敛函数的地址,编译器就必须为此函数产生一个函数实体,那么内敛失败。 |
2.如果函数体代码太长或者有多重循环语句 | if或者switch分支语句或递归时(终止条件是某个变量(某条件下)return。内联函数是在编译期展开,但是我们不能确定这个条件的成立。),不宜用内联。 |
3.类的构造(constructors),析构(destructors)和虚函数往往不是内联函数的最佳选择 | 因为类的构造和析构函数可能需要调用父类的构造或者析构函数,二者背后往往隐藏大量的代码,不适合做inline函数。虚函数是在运行时确定的,而inline函数是在编译阶段进行的,所以内联虚函数往往无效。 |
4.至于内联函数是定义在头文件还是源文件的建议 | 内联展开是在编译阶段进行的,只有链接的时候源文件才有联系,所以内联想要跨文件必须写在头文件中。例: |
//base.h
class Base{protected:void fun();};
//base.cpp
#include void Base::fun(){}
//derived.h
#include base.h
class Derived:public Base{public:void g();};
//derived.cpp
void Derived::g(){fun();}
//error LINK2019:unresolved external symbol
上面这种错误,就是因为内联函数fun()定义在编译单元base.cpp中,那么其他编译单元调用fun()的地方将无法解析该符号,因为在编译单元base.cpp生成的目标文件base.obj后,内联函数fun()已经被替换掉,编译器不会为fun()生成函数实体,链接器自然无法解析。所以inline函数会在多个源文件中被用到,那么必须把它定义在头文件中。
这里存在一个问题:
当在头文件中定义内联函数,那么被多个源文件包含时,如果编译器因为inline函数不适合被内联时,拒绝将inline函数进行内联处理,那么多个源文件在编译生成目标文件后都将各自保留一份inline函数的实体,这个时候程序在链接阶段就会出现重定义。解决办法是:在inline函数前使用static关键字。
//test.h
static inline max(int a,int b)
{
return a>b?a:b;
}
事实上,inline函数具有内部链接的特性,所以如果实际上没有被内联处理,也不会报重定义的错误,因此static有点多余。
5.如何查看函数是否被内敛处理了:
查看预处理后的 .i文件,inline函数的内联处理不是在预处理阶段,而是在编译阶段。编译源文件为汇编代码或者反汇编查看有没有相关的函数调用call,如果没有就是被内联了。具体可以参考here。
6.C++类成员函数定义在类体内为什么不会报重定义错误?
类成员函数定义在类体内,并随着类的定义放在头文件中,当被不同的源文件包含,那么每个源文件都应该包含了类成员函数的实体,为何在链接的过程中不会报函数的重定义错误呢?
原因是:在类里定义时,这种函数会被编译器编译成内联函数,在类外定义的函数则不会。内联函数的好处是加快程序的运行速度,缺点是会增加程序的尺寸。比较推荐的写法是把一个经常要用的而且实现起来比较简单的小型函数放到类里去定义,大型函数最好还是放到类外定义。
可能存在的疑问:类体内的成员函数被编译器内联处理,但并不是所有的成员函数都会被内联处理,比如包含递归的成员函数。但是实际测试,将包含递归的成员函数定义在类体内,被不同的源文件包含并不会报重定义错误,为什么会这样呢?请保持着疑问与好奇心,请继续往下看。
如果编译器发现被定义在类体内的成员函数无法被内联处理,也不会出现重定义的错误,因为C++中存在5种作用域的级别,分别是文件域(全局作用域)、命名空间域、类域、函数作用域和代码块作用域(局部域)。当类成员函数被定义在类体内,那么其作用域也就被限制在类域,当然定义在类体外的函数作用域也是属于类域的。显然并不是因为作用域的原因而不会产生重定义的错误。
那么原因究竟是什么呢?其实很简单,类体内定义的成员函数就是inline函数,即使不被内联处理,inline函数的特性就是不具有外部连接性。所以并不会与其他源文件中的同名类域中的成员函数发生冲突,也就不会造成重定义的错误。
6.inline函数 static函数 与宏函数的比较
1.inline函数:(编译阶段(单文件))
本文件可见;不产生符号;无栈帧的开辟;在调用点展开
2.static函数:(编译阶段)
本文件可见;产生一个local符号;有栈帧的开辟
3.宏函数:
本文件可见;预编译阶段;无类型检查
不产生符号;调用点展开;无法调试。
7.总结
可以将内联理解为C++中对于函数专有的宏,对于C的函数宏的一种改进。对于常量宏,C++提供const替代;而对于函数宏,C++提供的方案则是inline。C++ 通过内联机制,既具备宏代码的效率,又增加了安全性,还可以自由操作类的数据成员,算是一个比较完美的解决方案
最后
以上就是优美康乃馨为你收集整理的C++ --------inline关键字的全部内容,希望文章能够帮你解决C++ --------inline关键字所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复