专栏首页leoayC++の函数——内联函数&函数指针

C++の函数——内联函数&函数指针

C++の函数

—— 内联函数&函数指针

今天我们继续讨论C++函数部分,剩下两个点,一个是内联函数,另一个是函数指针

内联函数

我们先看一下内联函数。内联函数也是C++中的一个重要特性。所谓内联函数,其实本质上也是一种函数,在形式上的表现就是在普通函数前面加上关键字"inline",然后相对于普通函数来说,它也比较短小。C++中"inline"的作用其实是为了优化代码的运行,降低代码的执行时间,就像在C语言中的宏函数一样,作用也是为了降低代码的执行时间。

当内联函数被调用时,并不会向普通函数一样从主函数跳转到函数,而是直接将内联函数中的代码逻辑替换进主函数,提高运行效率。而这个过程是在代码编译的过程即完成的,当我们将一个函数定义为内联函数时,编译器识别到内联函数的特征后,就将函数的定义替换到函数的调用。那么我们怎么定义内联函数呢?

如何定义内联函数

定义内联函数就要在函数的前面使用“inline”关键字。像下面这样:

inline int add(int a, int b);     //(1)
inline int add(int a, int b)      //(2)
{
    return (a + b);
}
void test()                 //test func
{
    int i = 4;
    int j = 6;
    add(i, j);
}
void test_f()             //equal to test()
{
    int i = 4;
    int j = 6;
    (i + j);
}

我们看到上面有两个部分,一个是add函数的声明,一个是add函数的定义,并且每个函数前都有“inline”,我们便将“add”函数定义为内联函数,那么在代码中调用时就是将add函数的定义替换为调用部分的代码,如上面的test(),在编译的时候就会自动转为test_f()。

注意:有一点需要注意,并不是每一个用inline标明的函数都会被编译器转为内联,内联的根本目的是优化程序的运行,因此对于使用较为频繁的短小的函数,才有明显的效果,如果函数较为庞大,编译器也会忽略掉函数前面的“inline”将其变为普通函数。

为什么要用内联函数

我们在代码中经常会用到一些小函数,它们逻辑简单,代码量少,但是如果考虑到这些函数被调用者调用的时候,我们会发现大部分的时间都耗费在调用这个过程,也就是程序从主函数跳转到被调用函数的过程,而不是在我们写的这个小函数中。实际上正常的函数调用指令时,程序立即在函数调用语句之后存储指令的内存地址,将被调用的函数加载到内存中复制参数值,跳转到被调用函数的内存位置,执行函数代码,存储函数的返回值,然后跳转回执行被调用函数之前保存的指令地址。这样会导致程序的运行时间开销太大。

而C++的内联函数则提供了一种替代的方法,使用inline关键字,编译器用函数代码本身替换函数调用语句,然后再编译整个代码。因此,对于内联函数,编译器不必跳到另一个位置去执行函数,然后再跳回去,因为被调用程序的代码已经是调用程序中代码的一部分了。

下面我们列举一下内联函数的优缺点:

优点:

1、内联函数通过避免函数调用开销从而加速了我们的程序

2、当函数调用发生时,内联函数节省了堆栈上变量push/pop的开销

3、内联函数节省了从函数返回调用开销

4、内联函数通过使用指令缓存来增加引用的局部性

5、通过将其标记为内联,您可以将函数定义放入头文件中

缺点:

1、由于代码扩展,它增加了可执行文件的大小

2、c++内联在编译时解决。这意味着如果您更改内联函数的代码,您将需要使用它重新编译所有代码,以确保它将被更新

3、当在头文件中使用时,它会使头文件变大,包含用户不关心的信息

4、如上所述,它增加了可执行文件的大小,这可能会导致内存抖动。更多的页面错误会降低程序性能

5、有时并不有用,例如在嵌入式系统中,由于内存限制,大的可执行文件大小根本不是首选

什么时候使用内联函数

函数可以根据程序员的需要进行内嵌,那么我们什么时候使用呢?

1、当性能优先时,应该使用内联函数

2、在宏上使用内嵌函数

3、优先在函数定义中使用类外的inline关键字来隐藏实现细节

函数指针

所谓函数指针,其实本质上还是指针,但是不同于我们之前提到的指针,函数指针是指向函数的指针。根据前面的文章,我们很容易声明一个函数,如下我们声明一个比较两个字符串长度的函数:

bool lengthCompare(const string &, const string &);

现在,我们再来看一下函数指针的声明方式吧:

bool (*pf)(const string &, const string &);

从上面我们可以看到pf前面有个*,因此pf是一个指针,右面是形参列表,所以pf是指向函数的指针,从前面的bool可以看出这个函数的返回值类型是bool类型。

注意: *pf两边的()是必须的,因为这代表*pf是一个整体,pf是一个指针,如果不加括号,就表示bool* 是一个整体,pf就成了函数名,那么它的含义就变成了返回值为bool类型指针的函数了,这样是不是很好理解?

如何使用函数指针

其实同数组一样,函数名就代表了函数入口的首地址,也就是我们说的函数指针。对于上面两个例子来说,由于他们具有相同的参数列表,因此我们可以得到下面的等价式:

pf = lengthCompare;
pf = &lengthCompare;

可以看到,函数名就是函数的首地址,也表示函数本身。

因此,我们也会有下面的调用方式:

bool b1 = pf("leoay", "learn C++");
bool b2 = (*pf)("leoay", "learn C++!");
bool b3 = lengthCompare("leoay", "learn C++!!");

可以看到,我们并不需要对函数指针进行解引用就能直接调用它,因为我们在调用函数的时候其实就是找函数在程序中的首地址,然后将参数传进去。

重载函数的指针

前面我们说到了函数的重载,就是说在同一个源文件中函数具有相同的名字,但是具有不同的参数列表时的情况,因此我们很容易延伸到函数指针里面,就是这里要说的重载函数指针。我们先来看一下怎么声明重载函数指针:

void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned  int) = ff;

从上面的代码,我们可以看出想要使用重载函数指针,我们就要先声明重载函数,然后我们在定义一个函数指针时,将重载函数的地址赋值给这个函数指针,这里有一点我们需要注意,既然重载函数有不同的列表,那么我们在定义重载函数指针时该怎么选择呢?当然是与我们想要使用的那个重载函数保持一致。就是说我们想用哪个重载函数定义函数指针,函数指针的参数列表就应该与哪个重载函数保持一致。

把函数指针当做参数

到这里,我们发现函数指针并没有什么神奇的地方,我们完全可以把它当做一个指针看待,只不过具备函数的一些特征。但是,回归根本,它还是一枚可爱的指针。因此,它应该具备指针的一些特征。比如,我们可以把它当做参数传递给其他的参数。以后我们会讲到,C++中常见的回调函数就是这样使用的。

下面我们就用代码演示一下这种骚操作吧:

void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &));
void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &));

可以看到上面的代码中两个函数的参数中分别有下面这两个参数:

bool pf(const string &, const string &)
bool (*pf)(const string &, const string &)

上面是一个函数,下面是一个函数指针。但是在这里实际上他们是等价的,当函数被作为参数传递给另一个参数的时候,是等价于函数指针的。所以上面两个声明其实是等价的。

对于函数指针与内联函数的说明这篇文章就到这里,由于是基础系列文章,先不详细展开,还有一些知识需要用实例和练习加以说明。以后如果出高级系列再详细展开讨论。

本文分享自微信公众号 - leoay(leoay_Do)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-01-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • C++基础系列の函数

    前几天我们讲了一下C++中的函数,不过还有一部分没有讲完,今天我们继续讨论一下。如果你没有看过那篇文章,可以点击右边的链接前往:C++の函数

    leoay
  • C++の函数

    说到函数,我们应该比较清楚了,不论哪一门语言都有这个概念的,其实本质上就是讲我们之前介绍的语句,表达式等封装起来,形成一个功能单元。在C/C++中它也是程序执行...

    leoay
  • C++の容器vector

    上一篇文章C++のstring类,我们讲了一下C++中的string类,简单梳理了string对象的创建方式,数据操作等,今天我们继续介绍C++中另外一个概念v...

    leoay
  • javascript中函数声明与函数表达式

    在javascript中,我们经常要声明函数,或者使用函数表达式,今天我们就来说说这两者的区别。

    从此null
  • JavaScript 匿名函数

    在上一篇写了行间事件提取之后,可以发现其实此时函数的名称并不重要,应该可以不写函数名称就不写。

    Devops海洋的渔夫
  • Python之函数编程(2)

    在上面的命令中,我们定义一个return_sum()的函数,这个函数返回一个sum的函数,sum函数的作用是求一个可迭代对象的所有元素的和,当我们直接调...

    AsiaYe
  • [高大上的DL] Activation function (激活函数)的初步认识

    今天简单认识一下什么激活函数以及都有那些激活函数。说到激活函数这里有几个比较容易混淆的概念,比如Pooling池化和Sampling采样,loss functi...

    用户1622570
  • 写 Python 代码不可不知的函数式编程技术

    近来,越来越多人使用函数式编程(functional programming)。因此,很多传统的命令式语言(如 Java 和 Python)开始支持函数式编程技...

    机器之心
  • 写 Python 代码不可不知的函数式编程技术

    近来,越来越多人使用函数式编程(functional programming)。因此,很多传统的命令式语言(如 Java 和 Python)开始支持函数式编程技...

    CDA数据分析师
  • 写 Python 代码不可不知的函数式编程技术

    近来,越来越多人使用函数式编程(functional programming)。因此,很多传统的命令式语言(如 Java 和 Python)开始支持函数式编程技...

    OpenCV学堂

扫码关注云+社区

领取腾讯云代金券