内联函数 订阅
在计算机科学中,内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展)。 展开全文
在计算机科学中,内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展)。
信息
外文名
Inline function
意思是
inline关键字修饰的函数
特    点
在编译时将函数体嵌入每个调用处
中文名
内联函数
性    质
计算机术语
内联函数简介
在计算机科学中,内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展);也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。但在选择使用内联函数时,必须在程序占用空间和程序执行效率之间进行权衡,因为过多的比较复杂的函数进行内联扩展将带来很大的存储资源开支。另外还需要特别注意的是对递归函数的内联扩展可能引起部分编译器的无穷编译。 [1] 
收起全文
精华内容
下载资源
问答
  • 内联函数(内嵌函数,内置函数) 调用函数时需要一定的时间和空间的开销。C++提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联...
  • 内联函数在运行时可调试,而宏定义不可以;2.编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会; 3.内联函数可以访问类的成员变量,宏定义则不能; 4.在类中声明同时定义的成员...
  • 用法: 输入: fi=一个为每个变量使用不同名称的内联函数输出: f0=同一个函数fi,但转换成符号函数 例子: 创建一个不能进行符号微分的内联对象fi=inline('x^2+3*yz^4','x','y','z') f0=inline2sym(fi) 现在表达式...
  • arm 内联函数手册

    2018-06-13 10:07:18
    arm算法优化编程手册,提供armv7 armv8的所有内联指令
  • inline函数(内联函数)从概念上讲是编译器使用函数实现的真实代码来替换每一次的函数调用,带来的最直接的好处就是节省了函数调用的开销,而缺点就是增加了所生成字节码的尺寸。基于此,在代码量不是很
  • 内联函数的inline要加在函数前面,不可以加在声明前面。 class A { public:void Foo(int x, int y) { } // 自动地成为内联函数 } //正确写法: // 头文件 class A { public: void Foo(int x, int y); } // 定义...
  • 先简明扼要,说下关键:1、内联函数在可读性方面与函数是相同的,而在编译时是将函数直接嵌入调用程序的主体,省去了调用/返回指令,这样在运行时速度更快。2、内联函数可以调试,而宏定义是不可以调试的。内联函数...
  • ARM 底层指令集,用于嵌入式开发,用于提升性能的开发手册和文档,通常C语言开发使用内联函数指令
  • 使用反射绑定,您可以在渲染中自由使用内联函数,而不必担心React纯组件的浪费地重新渲染。 它适用于无状态功能组件和类组件。 最好的部分是,它几乎不需要任何代码更改 :raising_hands: ,以获取有关反射绑定的...
  • 内联函数与普通函数的区别详解。
  • 这是一组 Fortran 2003 模块,允许调用类似于 Matlab 内联函数(在其他语言中也称为“匿名”或“lambda”函数)的“公式”对象。 用户可以在字符串中给出一个公式并通过 eval 过程对其进行评估。 这些实现利用 ...
  • 内联函数

    2019-10-28 22:18:57
    内联函数 编译时将函数体代码和实参代替函数调用语句 如: 在C++中我们通常定义以下函数来求两个整数的最大值: 代码如下: int max(int a, int b) { return a > b ? a : b; } 为这么一个小的操作定义一...

    内联函数

    编译时将函数体代码和实参代替函数调用语句

    如:

    在C++中我们通常定义以下函数来求两个整数的最大值:

    代码如下:

    
    int max(int a, int b)
    {
     return a > b ? a : b;
    }

    为这么一个小的操作定义一个函数的好处有:

    ① 阅读和理解函数 max 的调用,要比读一条等价的条件表达式并解释它的含义要容易得多
    
    ② 如果需要做任何修改,修改函数要比找出并修改每一处等价表达式容易得多
    
    ③ 使用函数可以确保统一的行为,每个测试都保证以相同的方式实现
    
    ④ 函数可以重用,不必为其他应用程序重写代码

    虽然有这么多好处,但是写成函数有一个潜在的缺点:调用函数比求解等价表达式要慢得多。在大多数的机器上,调用函数都要做很多工作:调用前要先保存寄存器,并在返回时恢复,复制实参,程序还必须转向一个新位置执行

    C++中支持内联函数,其目的是为了提高函数的执行效率,用关键字 inline 放在函数定义(注意是定义而非声明,下文继续讲到)的前面即可将函数指定为内联函数,内联函数通常就是将它在程序中的每个调用点上“内联地”展开,假设我们将 max 定义为内联函数:

    代码如下:

    inline int max(int a, int b)
    {
     return a > b ? a : b;
    }

    则调用: cout<<max(a, b)<<endl;
    在编译时展开为: cout<<(a > b ? a : b)<<endl;从而消除了把 max写成函数的额外执行开销。

    将内联函数放入头文件

    关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 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 与函数定义体放在一起
    {
     ...
    } 

    所以说,C++ inline函数是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。一般地,用户可以阅读函数的声明,但是看不到函数的定义。尽管在大多数教科书中内联函数的声明、定义体前面都加了 inline 关键字,但我认为 inline 不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C 程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。

    定义在类声明之中的成员函数将自动地成为内联函数,例如:

    代码如下:

    class A
    {  
    public:
     void Foo(int x, int y) { ... }   // 自动地成为内联函数  
    } 

    但是编译器是否将它真正内联则要看 Foo函数如何定义

    内联函数应该在头文件中定义,这一点不同于其他函数。编译器在调用点内联展开函数的代码时,必须能够找到 inline 函数的定义才能将调用函数替换为函数代码,而对于在头文件中仅有函数声明是不够的。

    当然内联函数定义也可以放在源文件中,但此时只有定义的那个源文件可以用它,而且必须为每个源文件拷贝一份定义(即每个源文件里的定义必须是完全相同的),当然即使是放在头文件中,也是对每个定义做一份拷贝,只不过是编译器替你完成这种拷贝罢了。但相比于放在源文件中,放在头文件中既能够确保调用函数是定义是相同的,又能够保证在调用点能够找到函数定义从而完成内联(替换)。

    但是你会很奇怪,重复定义那么多次,不会产生链接错误?

    我们来看一个例子:

    A.h :

    代码如下:

    class A
    {
    public:
     A(int a, int b) : a(a),b(b){}
     int max();
    
    private:
     int a;
     int b;
    };

    A.cpp :

    代码如下:

    #include "A.h"
    
    inline int A::max()
    {
     return a > b ? a : b;
    }

    Main.cpp :

    代码如下:

    #include <iostream>
    #include "A.h"
    using namespace std;
    
    inline int A::max()
    {
     return a > b ? a : b;
    }
    
    int main()
    {
     A a(3, 5);
     cout<<a.max()<<endl;
     return 0;
    }

    一切正常编译,输出结果:5

    倘若你在Main.cpp中没有定义max内联函数,那么会出现链接错误:

    error LNK2001: unresolved external symbol "public: int __thiscall A::max(void)" (?max@A@@QAEHXZ)main.obj
    找不到函数的定义,所以内联函数可以在程序中定义不止一次,只要 inline 函数的定义在某个源文件中只出现一次,而且在所有源文件中,其定义必须是完全相同的就可以。

    在头文件中加入或修改 inline 函数时,使用了该头文件的所有源文件都必须重新编译。

    1. 慎用内联

    内联虽有它的好处,但是也要慎用

    而在Google C++编码规范中则规定得更加明确和详细。

    内联函数:

    Tip: 只有当函数只有 10 行甚至更少时才将其定义为内联函数.

    定义: 当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用.
    优点: 当函数体比较小的时候, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.
    缺点: 滥用内联将导致程序变慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。
    结论: 一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!
    另一个实用的经验准则: 内联那些包含循环或 switch 语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 switch 语句从不被执行).
    有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常内联. 通常, 递归函数不应该声明成内联函数.(递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数.

    -inl.h文件:
    Tip: 复杂的内联函数的定义, 应放在后缀名为 -inl.h 的头文件中.
    内联函数的定义必须放在头文件中, 编译器才能在调用点内联展开定义. 然而, 实现代码理论上应该放在 .cc 文件中, 我们不希望 .h 文件中有太多实现代码, 除非在可读性和性能上有明显优势.

    如果内联函数的定义比较短小, 逻辑比较简单, 实现代码放在 .h 文件里没有任何问题. 比如, 存取函数的实现理所当然都应该放在类定义内. 出于编写者和调用者的方便, 较复杂的内联函数也可以放到 .h 文件中, 如果你觉得这样会使头文件显得笨重, 也可以把它萃取到单独的 -inl.h 中. 这样把实现和类定义分离开来, 当需要时包含对应的 -inl.h 即可.

    展开全文
  • 主要介绍了Kotlin 内联函数详解及实例的相关资料,需要的朋友可以参考下
  • 主要介绍了Python 如何定义匿名或内联函数,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
  • 主要给大家介绍了关于kotlin Standard中内联函数的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用kotlin具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
  • 主要介绍了c++内联函数(inline)使用详解,需要的朋友可以参考下
  • MATLAB中的内联函数借鉴了C语言中的内联函数,在C语言中,内联函数是通过编译器控制来实现的,它只在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的时间和空间开销。在MATLAB中也...
  • C++——友元函数&内联函数

    千次阅读 多人点赞 2018-12-07 23:55:15
    友元函数 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。 友元可以是一个函数,该函数被...

    友元函数

    类的友元函数是定义在类外部但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数

    友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

    如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:

    class Box
    {
       double width;
    public:
       double length;
       friend void printWidth( Box box );
       void setWidth( double wid );
    };
    #include <iostream>
     
    using namespace std;
     
    class Box
    {
       double width;
    public:
       friend void printWidth( Box box );
       void setWidth( double wid );
    };
    
    // 成员函数定义
    void Box::setWidth( double wid )
    {
        width = wid;
    }
    
    // printWidth() 不是任何类的成员函数
    void printWidth( Box box )
    {
       /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
       cout << "Width of box : " << box.width <<endl;
    }
     
    // 程序的主函数
    int main( )
    {
       Box box;
     
       // 使用成员函数设置宽度
       box.setWidth(10.0);
       
       // 使用友元函数输出宽度
       printWidth( box );
     
       return 0;
    }
    /*输出结果是
    Width of box : 10
    */

    内联函数

    内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

    对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

    如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

    在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。

     

    #include <iostream>
     
    using namespace std;
    
    inline int Max(int x, int y)
    {
       return (x > y)? x : y;
    }
    
    // 程序的主函数
    int main( )
    {
    
       cout << "Max (20,10): " << Max(20,10) << endl;
       cout << "Max (0,200): " << Max(0,200) << endl;
       cout << "Max (100,1010): " << Max(100,1010) << endl;
       return 0;
    }

    以下内容转载自:https://blog.csdn.net/u011327981/article/details/50601800/

    1.  内联函数

    在C++中我们通常定义以下函数来求两个整数的最大值:

    复制代码 代码如下:


    int max(int a, int b)
    {
     return a > b ? a : b;
    }

    为这么一个小的操作定义一个函数的好处有:

    ① 阅读和理解函数 max 的调用,要比读一条等价的条件表达式并解释它的含义要容易得多

    ② 如果需要做任何修改,修改函数要比找出并修改每一处等价表达式容易得多

    ③ 使用函数可以确保统一的行为,每个测试都保证以相同的方式实现

    ④ 函数可以重用,不必为其他应用程序重写代码

    虽然有这么多好处,但是写成函数有一个潜在的缺点:调用函数比求解等价表达式要慢得多。在大多数的机器上,调用函数都要做很多工作:调用前要先保存寄存器,并在返回时恢复,复制实参,程序还必须转向一个新位置执行

    C++中支持内联函数,其目的是为了提高函数的执行效率,用关键字 inline 放在函数定义(注意是定义而非声明,下文继续讲到)的前面即可将函数指定为内联函数,内联函数通常就是将它在程序中的每个调用点上“内联地”展开,假设我们将 max 定义为内联函数:

    复制代码 代码如下:


    inline int max(int a, int b)
    {
     return a > b ? a : b;
    }

    则调用: cout<<max(a, b)<<endl;


    在编译时展开为: cout<<(a > b ? a : b)<<endl;

    从而消除了把 max写成函数的额外执行开销

    2.  内联函数和宏

    无论是《Effective C++》中的 “Prefer consts,enums,and inlines to #defines” 条款,还是《高质量程序设计指南——C++/C语言》中的“用函数内联取代宏”,宏在C++中基本是被废了,在书《高质量程序设计指南——C++/C语言》中这样解释到:

    3.  将内联函数放入头文件

    关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 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 与函数定义体放在一起
    {
     ...

    所以说,C++ inline函数是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。一般地,用户可以阅读函数的声明,但是看不到函数的定义。尽管在大多数教科书中内联函数的声明、定义体前面都加了 inline 关键字,但我认为 inline 不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C 程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。

    定义在类声明之中的成员函数将自动地成为内联函数,例如:

    复制代码 代码如下:


    class A
    {  
    public:
     void Foo(int x, int y) { ... }   // 自动地成为内联函数  

    但是编译器是否将它真正内联则要看 Foo函数如何定义

    内联函数应该在头文件中定义,这一点不同于其他函数。编译器在调用点内联展开函数的代码时,必须能够找到 inline 函数的定义才能将调用函数替换为函数代码,而对于在头文件中仅有函数声明是不够的。

    当然内联函数定义也可以放在源文件中,但此时只有定义的那个源文件可以用它,而且必须为每个源文件拷贝一份定义(即每个源文件里的定义必须是完全相同的),当然即使是放在头文件中,也是对每个定义做一份拷贝,只不过是编译器替你完成这种拷贝罢了。但相比于放在源文件中,放在头文件中既能够确保调用函数是定义是相同的,又能够保证在调用点能够找到函数定义从而完成内联(替换)。

    但是你会很奇怪,重复定义那么多次,不会产生链接错误?

    我们来看一个例子:

    A.h :

    复制代码 代码如下:


    class A
    {
    public:
     A(int a, int b) : a(a),b(b){}
     int max();

    private:
     int a;
     int b;
    };

    A.cpp : 

    复制代码 代码如下:


    #include "A.h"

    inline int A::max()
    {
     return a > b ? a : b;
    }

    Main.cpp : 

    复制代码 代码如下:


    #include <iostream>
    #include "A.h"
    using namespace std;

    inline int A::max()
    {
     return a > b ? a : b;
    }

    int main()
    {
     A a(3, 5);
     cout<<a.max()<<endl;
     return 0;
    }

    一切正常编译,输出结果:5

     


    倘若你在Main.cpp中没有定义max内联函数,那么会出现链接错误:

    error LNK2001: unresolved external symbol "public: int __thiscall A::max(void)" (?max@A@@QAEHXZ)main.obj
    找不到函数的定义,所以内联函数可以在程序中定义不止一次,只要 inline 函数的定义在某个源文件中只出现一次,而且在所有源文件中,其定义必须是完全相同的就可以。

    在头文件中加入或修改 inline 函数时,使用了该头文件的所有源文件都必须重新编译。

    4.  慎用内联

    内联虽有它的好处,但是也要慎用,以下摘自《高质量程序设计指南——C++/C语言》:

    而在Google C++编码规范中则规定得更加明确和详细:

    内联函数:

    Tip: 只有当函数只有 10 行甚至更少时才将其定义为内联函数.

    定义: 当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用.
    优点: 当函数体比较小的时候, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.
    缺点: 滥用内联将导致程序变慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。
    结论: 一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!
    另一个实用的经验准则: 内联那些包含循环或 switch 语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 switch 语句从不被执行).
    有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常内联. 通常, 递归函数不应该声明成内联函数.(递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数.

    -inl.h文件:


    Tip: 复杂的内联函数的定义, 应放在后缀名为 -inl.h 的头文件中.


    内联函数的定义必须放在头文件中, 编译器才能在调用点内联展开定义. 然而, 实现代码理论上应该放在 .cc 文件中, 我们不希望 .h 文件中有太多实现代码, 除非在可读性和性能上有明显优势.

    如果内联函数的定义比较短小, 逻辑比较简单, 实现代码放在 .h 文件里没有任何问题. 比如, 存取函数的实现理所当然都应该放在类定义内. 出于编写者和调用者的方便, 较复杂的内联函数也可以放到 .h 文件中, 如果你觉得这样会使头文件显得笨重, 也可以把它萃取到单独的 -inl.h 中. 这样把实现和类定义分离开来, 当需要时包含对应的 -inl.h 即可。

     

    部分资料来源于菜鸟教程

    展开全文
  • 检查v8引擎是否会将某个函数视为内联函数。 安装 您首先需要安装 : $ npm i eslint --save-dev 接下来,安装eslint-plugin-inlinecheck : $ npm install eslint-plugin-inlinecheck --save-dev 注意:如果全局...
  • 内联函数什么时候展开_内联函数

    千次阅读 2020-08-23 06:58:09
    内联函数什么时候展开You know all of those Util files you create with all sorts of small functions that you end up using a lot throughout your app? If your utility functions get other functions as ...

    内联函数什么时候展开

    You know all of those Util files you create with all sorts of small functions that you end up using a lot throughout your app? If your utility functions get other functions as parameters, chances are you can improve the performance of your app by saving some extra object allocations, that you might not even know you’re making, with one keyword: inline. Let’s see what happens when you pass these short functions around, what inline does under the hood and what you should be aware of when working with inline functions.

    您知道使用各种小功能创建的所有Util文件,最终在整个应用程序中大量使用这些文件吗? 如果您的实用程序函数将其他函数用作参数,则可以通过使用一个关键字inline来保存一些甚至可能不知道自己正在做的对象分配,从而提高应用程序的性能。 让我们看看当您传递这些短函数时会发生什么,内联在幕后做什么,以及在使用内联函数时应注意的事项。

    函数调用-内幕 (Function call — under the hood)

    Let’s say that you use SharedPreferences a lot in your app so you create this utility function to reduce the boilerplate every time you write something in your SharedPreferences:

    假设您在应用程序中使用了很多SharedPreferences ,因此您创建此实用程序函数可以在每次在SharedPreferences编写内容时减少样板:

    fun SharedPreferences.edit(
    commit: Boolean = false,
    action: SharedPreferences.Editor.() -> Unit
    ) {
    val editor = edit()
    action(editor)
    if (commit) {
    editor.commit()
    } else {
    editor.apply()
    }
    }

    Then, you can use it to save a String token:

    然后,您可以使用它保存String令牌:

    private const val KEY_TOKEN = “token”class PreferencesManager(private val preferences: SharedPreferences){
    fun saveToken(token: String) {
    preferences.edit { putString(KEY_TOKEN, token) }
    }
    }

    Now let’s see what’s going on under the hood when preferences.edit is called. If we look at the Kotlin bytecode (Tools > Kotlin > Decompiled Kotlin to Java) we see that there’s a NEW called, so a new object is being created, even if in our code we didn’t call any object constructor:

    现在,让我们看看调用preferences.edit情况。 如果查看Kotlin字节码(“工具”>“ Kotlin”>“将Kotlin编译为Java”),我们会看到有一个NEW调用,因此正在创建一个新对象,即使在我们的代码中我们没有调用任何对象构造函数:

    NEW com/example/inlinefun/PreferencesManager$saveToken$1

    Let’s check the decompiled code to make this a bit friendlier. Our saveToken decompiled function is as follows (comments and formatting mine):

    让我们检查一下反编译的代码,以使其更加友好。 我们的saveToken反编译功能如下(注释和格式化我的代码):

    Each high-order function we create leads to a Function object creation and memory allocation that introduces runtime overhead.

    我们创建的每个高阶函数都会导致Function对象的创建和内存分配,从而引入运行时开销。

    内联功能-引擎盖下 (Inline function — under the hood)

    To improve the performance of our app we can avoid the new function object creation, by using the inline keyword:

    为了提高应用程序的性能,我们可以使用inline关键字来避免创建新的函数对象:

    inline fun SharedPreferences.edit(
    commit: Boolean = false,
    action: SharedPreferences.Editor.() -> Unit
    ) { … }

    Now the Kotlin bytecode doesn’t contain any NEW calls and here’s how the decompiled java code looks like for our saveToken method (comments and formatting mine):

    现在Kotlin字节码不包含任何NEW调用,这是我们saveToken方法的反编译Java代码的样子(注释和格式化我的代码):

    Because of the inline keyword, the compiler copies the content of the inline function to the call site, avoiding creating a new Function object.

    由于使用inline关键字,编译器将inline函数的内容复制到调用站点,从而避免了创建新的Function对象。

    标记为内联的内容 (What to mark as inline)

    ⚠️ If you’re trying to mark as inline a function that doesn’t accept another function as a parameter, you won’t get significant performance benefits and the IDE will even tell you that, suggesting you to remove it:

    If️如果您尝试将不接受另一个函数作为参数的函数标记为内联函数,则不会获得明显的性能优势,IDE甚至会告诉您,建议您删除它:

    Image for post

    ⚠️ Because inlining may cause the generated code to grow, make sure that you avoid inlining large functions. For example, if you check the Kotlin Standard Library, you’ll see that most of the inlined functions have only 1–3 lines.

    ⚠️ 因为内联可能导致生成的代码增长,所以请确保您 避免内联大型函数 。 例如,如果您查看Kotlin标准库,您会看到大多数内联函数只有1-3行。

    ⚠️ Avoid inlining large functions!

    Avoid️ 避免内联大型函数!

    ⚠️ When using inline functions, you’re not allowed to keep a reference to the functions passed as parameter or pass it to a different function — you’ll get a compiler error saying Illegal usage of inline-parameter.

    using️使用内联函数时, 不允许保留对作为参数传递的函数的引用或将其传递给其他函数—会出现编译器错误,指出Illegal usage of inline-parameter

    So, for example, let’s modify the edit method and the saveToken method. edit method gets another parameter that is then passed to a different function. saveToken uses a dummy variable that gets updated in the new function:

    因此,例如,让我们修改edit方法和saveToken方法。 edit方法获取另一个参数,然后将其传递给另一个函数。 saveToken使用一个虚拟变量,该变量在新函数中进行更新:

    fun myFunction(importantAction: Int.() -> Unit) {
    importantAction(-1)
    }inline fun SharedPreferences.edit(
    commit: Boolean = false,importantAction: Int.() -> Unit = { },
    action: SharedPreferences.Editor.() -> Unit
    ) {myFunction(importantAction)
    ...

    }
    ...
    fun saveToken(token: String) {
    var dummy = 3
    preferences.edit(importantAction = { dummy = this}) {
    putString(KEY_TOKEN, token)
    }
    }

    We can see that myFunction(importantAction) produces an error:

    我们可以看到myFunction(importantAction)产生一个错误:

    Image for post

    Here’s how you can solve this, depending on how your function looks like:

    根据函数的外观,可以按照以下方法解决此问题:

    Case 1: If you have multiple functions as parameters and you only need to keep a reference to one of them, then you can mark it as noinline.

    情况1 :如果您有多个函数作为参数,而只需要保留对其中一个的引用,则可以将其标记为noinline

    By using noinline, the compiler will create a new Function object only for that specific function, but the rest will be inlined.

    通过使用noinline ,编译器将仅为该特定函数创建一个新的Function对象,而其余的将被内联。

    Our edit function will now be:

    现在,我们的edit功能将是:

    inline fun SharedPreferences.edit(
    commit: Boolean = false,noinline importantAction: Int.() -> Unit = { },
    action: SharedPreferences.Editor.() -> Unit
    ) {
    myFunction(importantAction)
    ...
    }

    If we check the bytecode, we see that a NEW call appeared:

    如果我们检查字节码,我们会看到出现了一个NEW调用:

    NEW com/example/inlinefun/PreferencesManager$saveToken$1

    In the decompiled code we can see the following (comments mine):

    在反编译的代码中,我们可以看到以下内容(我的评论):

    Case 2: If your function only has one function as a parameter, just prefer not using inline at all. If you do want to use inline, you’d have to mark your parameter with noinline, but like this you’ll have low performance benefits by inlining the method.

    情况2 :如果您的函数只有一个函数作为参数,则只希望根本不使用inline 。 如果确实要使用内联,则必须用noinline标记参数,但是这样,通过内联该方法将降低性能。

    To decrease the memory allocations caused by lambda expressions, use the inline keyword! Make sure you apply it to small functions that take a lambda as a parameter. If you need to keep a reference to a lambda or pass it as an argument to another function use the noinline keyword. Start inlining to start saving!

    要减少由lambda表达式引起的内存分配,请使用inline关键字! 确保将其应用于以lambda作为参数的 小函数 。 如果您需要保留对lambda的引用或将其作为参数传递给另一个函数,请使用noinline关键字。 开始内联以开始保存!

    翻译自: https://medium.com/androiddevelopers/inline-functions-under-the-hood-12ddcc0b3a56

    内联函数什么时候展开

    展开全文
  • Kotlin总结之内联函数

    千次阅读 2020-03-07 12:06:57
    但可以通过内联函数优化。 一. 优化Lambda开销 在Kotlin中每次声明一个Lambda表达式,就会在字节码中产生一个匿名类。该匿名类包含了一个invoke方法,作为Lambda的调用方法,每次调用的时候,还会创建一个新的对象...

    在Kotlin 中使用 Lambda表达式会带来一些额外的开销。但可以通过内联函数优化。

    一. 优化Lambda开销

    在Kotlin中每次声明一个Lambda表达式,就会在字节码中产生一个匿名类。该匿名类包含了一个invoke方法,作为Lambda的调用方法,每次调用的时候,还会创建一个新的对象。可想而知,Lambda虽然简洁,但是会增加额外的开销。Kotlin 采用内联函数来优化Lambda带来的额外开销。

    1.1 invokedynamic

    Java如何解决优化Lambda的问题的呢?与Kotlin这种在编译期通过硬编码生成Lambda转换类的机制不同,Java在SE7之后,通过invokedynamic技术实现了在运行期间才产生相应的翻译代码。在invokedynamic被首次调用的时候,就会触发产生一个匿名内部类来替换中间码invokedynamic,后续的调用会直接采用这个匿名类的代码。

    这样做的好处是:

    1.由于具体的转换实现是在运行时产生的,在字节码中能看到的只有一个固定的invokedynamic,所以需要静态生成的类的个数以及字节码大小显著减少。

    2.与编译时写死在字节码中的策略不同,利用invokedynamic可以把实际的翻译策略隐藏在JDK库的实现,提高了灵活性,在确保向后兼容性的同时,后期可以继续对翻译策略不断优化升级。

    3.JVM天然支持类针对该方式对Lambda表达式的翻译和优化,开发者不必考虑这个问题。

    1.2内联函数

    invokedynamic虽然不错,但是Kotlin需要兼容Android最主流的Java版本SE6,这导致Kotlin无法使用invokedynamic来解决android平台Lambda开销的问题。所以Kotlin使用内联函数来解决这个问题,在Kotlin中使用inline关键字来修饰函数,这些函数就成了内联函数。它们的函数体在编译的时期被嵌入到每一个调用的地方,以减少额外生成的匿名类数,以及函数执行的时间开销。

    2.内联函数具体语法

    声明一个高阶函数payFoo,可以接收一个类型为()->Unit的Lambda,然后在main函数中调用它。

    fun main() {
        payFoo {
            println("write kotlin...")
        }
    }
    fun payFoo(block: () -> Unit) {
        println("before block")
        block()
        println("end block")
    }

    通过字节码反编译的相关Java代码。

    public final class InlineDemoKt {
       public static final void main() {
          payFoo((Function0)null.INSTANCE);
       }
    
       // $FF: synthetic method
       public static void main(String[] var0) {
          main();
       }
    
       public static final void payFoo(@NotNull Function0 block) {
          Intrinsics.checkParameterIsNotNull(block, "block");
          String var1 = "before block";
          System.out.println(var1);
          block.invoke();
          var1 = "end block";
          System.out.println(var1);
       }
    }

    可以发现调用payFoo后就会产生一个Function0类型的block类,然后通过invoke 方法来执行,这会增加额外的生成类和函数调用开销。

    现在通过inline修饰payFoo函数:

    fun main() {
        payFoo {
            println("write kotlin...")
        }
    }
    inline fun payFoo(block: () -> Unit) {
        println("before block")
        block()
        println("end block")
    }
    public final class InlineDemoKt {
       public static final void main() {
          int $i$f$payFoo = false;
          String var1 = "before block";
          System.out.println(var1);
          int var2 = false;
          String var3 = "write kotlin...";
          System.out.println(var3);
          var1 = "end block";
          System.out.println(var1);
       }
    
       // $FF: synthetic method
       public static void main(String[] var0) {
          main();
       }
    
       public static final void payFoo(@NotNull Function0 block) {
          int $i$f$payFoo = 0;
          Intrinsics.checkParameterIsNotNull(block, "block");
          String var2 = "before block";
          System.out.println(var2);
          block.invoke();
          var2 = "end block";
          System.out.println(var2);
       }
    }

    可以发现通过inline修饰的函数,其函数体代码被调用的Lambda代码都粘贴到了相应调用的位置。试想一下,如果这是一个工程中公共的方法,或者被嵌套在一个循环调用的逻辑体中,这个方法必然会被调用多次。通过inline语法,可以彻底消除这种额外调用,从而节约了开销。

    内联函数典型的一个应用场景就是Kotlin的集合类。如果你看过 Kotlin的集合类API文档或者源码实现,可以发现,集合函数式API,如map、filter都被定义成了内联函数。

    /**
     * Returns a list containing the results of applying the given [transform] function
     * to each element in the original array.
     */
    public inline fun <T, R> Array<out T>.map(transform: (T) -> R): List<R> {
        return mapTo(ArrayList<R>(size), transform)
    }
    /**
     * Returns a list containing only elements matching the given [predicate].
     */
    public inline fun <T> Array<out T>.filter(predicate: (T) -> Boolean): List<T> {
        return filterTo(ArrayList<T>(), predicate)
    }

    因为上面的方法都接收Lambda作为参数,同时需要对集合元素进行遍历操作,所以会定义为内联函数。

    内联函数不是万能的,以下情况避免使用内联函数:

    1.由于JVM对普通函数已经能够根据实际情况智能地判断是否进行内联优化,所以我们并不需要对其使用Kotlin的inline语法,那只会让字节码变得更加复杂。

    2.尽量避免对具有大量函数体的函数进行内联,这样会导致过多的字节码数量。

    3.一旦一个函数被定义为内联函数,便不能获取闭包类的私有成员,除非你把它们声明为internal。

    下面就是错误写法,本质是private修饰的a没有get 方法。

    class TestPay {
          private var a = 1  //错误写法,会报错
    
         inline fun printNumber() {
            println(a)
        }
    }

     

    二.noinline :避免参数被内联

    如果一个函数的开头加上inline修饰符,那么它的函数体以及Lambda参数都会被内联。然而现实中的情况比较复杂,有一种可能是函数需要接收多个参数,但我们只想对其中部分Lambda参数内联,其他的则不内联,应该如何处理?

    Kotlin引入了noinline关键字,可以加在不想要内联的参数开头,该参数便不会有内联效果。

    fun main() {
        payFoo({
            println("I am inlined...")
        }, {
            println("I am not inlined...")
        })
    }
    
    inline fun payFoo(block1: () -> Unit, noinline block2: () -> Unit) {
        println("before block")
        block1()
        block2()
        println("end block")
    }
    
    before block
    I am inlined...
    I am not inlined...
    end block

    可以发现block1被内联了,block2没有被内联。

    public final class NoinlineDemoKt {
       public static final void main() {
          Function0 block2$iv = (Function0)null.INSTANCE;
          int $i$f$payFoo = false;
          String var2 = "before block";
          System.out.println(var2);
          int var3 = false;
          //可以发现block1被内联了。
          String var4 = "I am inlined...";
          System.out.println(var4);
          //block2没有被内联
          block2$iv.invoke();
          var2 = "end block";
          System.out.println(var2);
       }
    
       // $FF: synthetic method
       public static void main(String[] var0) {
          main();
       }
    
       public static final void payFoo(@NotNull Function0 block1, @NotNull Function0 block2) {
          int $i$f$payFoo = 0;
          Intrinsics.checkParameterIsNotNull(block1, "block1");
          Intrinsics.checkParameterIsNotNull(block2, "block2");
          String var3 = "before block";
          System.out.println(var3);
          block1.invoke();
          block2.invoke();
          var3 = "end block";
          System.out.println(var3);
       }
    }

     从上面的代码中可以看出,payFoo函数中block2函数在带上noinline之后,反编译后的Java代码中没有将其函数体代码在调用处进行替换。

     

    三.非局部返回

    Kotlin中内联函数除了优化Lambda开销之外,还带来了非局部返回和具体化参数类型。

    1.Kotlin如何支持非局部返回

    首先看常见的局部返回的例子

    fun main() {
        payFoo()
    }
    
    fun localReturn() {
        return
    }
    
    fun payFoo() {
        println("before local return")
        localReturn()
        println("after local return")
        return
    }
    
    before local return
    after local return

    从上面代码可以发现,localReturn执行之后,其函数体中的return只会在该函数的局部生效,所以localReturn()之后的println函数依旧生效。

    如何把函数换成Lambda版本,(存在问题的写法)

    fun main() {
        payFoo { return }
    }
    
    fun payFoo(returning: () -> Unit) {
        println("before local return")
        returning()
        println("after local return")
        return
    }
    
    'return' is not allowed here

    从上面的代码可以发现报错了,在Kotlin中,正常情况下,Lambda表达式不允许存在return关键字。

    通过内联函数修改

    fun main() {
        payFoo { return }
    }
    
    inline fun payFoo(returning: () -> Unit) {
        println("before local return")
        returning()
        println("after local return")
        return
    }
    
    before local return

    编译通过,但结果与局部返回不同,Lambda的return执行之后直接让foo函数退出了执行。因为内联函数payFoo的函数体及参数Lambda会直接替代具体的调用,所以实际产生的代码中,return相当于是直接暴露在main函数中的,所以returning之后的代码就不会执行了,这就是非局部返回。

    public final class Testinline3Kt {
       public static final void main() {
          int $i$f$payFoo = false;
          String var1 = "before local return";
          System.out.println(var1);
          int var2 = false;
       }
    
       // $FF: synthetic method
       public static void main(String[] var0) {
          main();
       }
    
       public static final void payFoo(@NotNull Function0 returning) {
          int $i$f$payFoo = 0;
          Intrinsics.checkParameterIsNotNull(returning, "returning");
          String var2 = "before local return";
          System.out.println(var2);
          returning.invoke();
          var2 = "after local return";
          System.out.println(var2);
       }
    }

    使用标签实现Lambda非局部返回

    另一种等效的方式,是通过标签利用@符号来实现Lambda非局部返回。可以在不声明inline修饰符的情况下,实现相同效果。

    fun main() {
        payFoo2 { return@payFoo2 }
    }
    
    fun payFoo2(returning: () -> Unit) {
        println("before local return")
        returning()
        println("after local return")
        return
    }
    
    before local return
    after local return

    非局部返回尤其在循环控制中特别有用,比如Kotlin的forEach接口,它接收一个Lambda参数,由于它也是一个内联函数,所以可以直接在它调用的Lambda中执行return退出上一层的程序。

    fun main() {
        println(hasZero(listOf(0,2,3)))
    }
    
    fun hasZero(list: List<Int>): Boolean {
        list.forEach {
            if (it == 0) return true //直接返回结果
        }
        return false
    }
    
    true

    crossinline

    非局部返回在某些场合下非常有用,但可能存在风险,内联的函数所接收的Lambda参数常常来自于上下文的其他地方。为了避免带有return的Lambda参数产生破坏,可以使用crossinline 关键字来修饰该参数。

    fun main() {
        payFoo3 {
            return
        }
    }
    
    inline fun payFoo3(crossinline returning: () -> Unit) {
        println("before local return")
        returning()
        println("after local return")
        return
    }
    
    'return' is not allowed here

     

    四.具体化参数类型

    内联函数可以帮助我们实现具体化参数类型,Kotlin与Java一样,由于运行时的类型擦除,我们不能直接获取一个参数的类型。然而,由于内联函数会直接在字节码中生成相应的函数体实现,这时候反而可以获得参数的具体类型。

    使用reified修饰符来实现这一效果。

    fun main() {
        getType<Int>()
    }
    
    
    inline fun <reified T> getType() {
        println(T::class)
    }
    
    class java.lang.Integer (Kotlin reflection is not available)

    这个特性在android开发中也特别有用,在Java中,需要调用startActivity时,通常需要把具体的目标视图类作为一个参数。但是在Kotlin中,可以使用reified来进行简化。

    import android.app.Activity
    import android.content.Intent
    
    inline fun <reified T : Activity> Activity.startActivity() {
        startActivity(Intent(this, T::class.java))
    }

     

    参考<<Kotlin核心编程>> 

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • kotlin 内联函数In this tutorial, we’ll be looking into Kotlin inline function. We’ll follow that with Reified Type Parameters. 在本教程中,我们将研究Kotlin内联函数。 我们将在后面加上Reified Type ...
  • 内联函数的执行过程与带参数宏定义很相似,但参数的处理不同。带参数的宏定义并不对参数进行运算,而是直接替换;内联函数首先是函数,这就意味着函数的很多性质都适用于内联函数,即内联函数先把参数表达式进行运算...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 125,886
精华内容 50,354
关键字:

内联函数