函数指针和指针函数以及数组指针和指针数组区别

函数指针(指向函数的指针)

与数据项类似,函数也有自己的地址。函数的地址是存储其机器代码的内存的开始地址。例如,可以编写将另一个函数的地址作为参数的函数,这样第一个函数将能够找到第二个函数,并运行它。与直接调用另一个函数相比,这种方法显得很笨重,但它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。

函数指针的用法需要以下三个步骤:

  • 获取函数的地址
  • 声明一个函数指针
  • 使用函数指针来调用函数

1.获取函数的地址

只要使用函数名(后面不跟参数)就可以获取到函数地址,即函数名就是该函数的地址。要想将函数作为参数进行传递,必须传递函数名,例如 Func()是一个函数,则 Func 就是该函数的地址。同时读者一定要着重区分传递的是函数地址还是函数的返回值:

one(Func);    //传递的是 Func 函数的地址
two(Func());  //传递的是 Func() 函数的返回值

注:one() 调用使得 one() 函数能够在其内部调用 Func() 函数。two() 调用首先调用 Func() 函数,然后将Func() 函数的返回值传递给 two() 函数。

2.声明函数指针

声明某种数据类型的指针时,必须指定指针指向的类型。同样,声明指向函数的指针时,也必须指定指针指向的函数类型。这意味着声明应指定函数的返回类型以及函数的参数列表。也就是说,声明应像函数原型那样指出有关函数的信息。例如:

有一个函数:double pam(int);

则正确的指针类型声明为:double (*pf)(int);  //注意 * 的优先级。

从上面可以看出,这与 pam() 声明类似,只是将 pam 替换为了 (*pf)。由于 pam 是函数,因此 (*pf) 也是函数,则 pf 就是函数指针。通常,要声明指向特定类型的函数指针,可以首先编写这样的函数原型,然后用(*pf)替换函数名。这样 pf 就是函数指针。

注:为提供正确的运算符优先级,必须在声明中使用括号将 *pf 括起。括号的优先级比 * 运算符高,因此 *pf(int) 意味着 pf() 是一个返回指针的函数,而 (*pf)(int) 意味着 pf 是一个指向函数的指针。

double (*pf)(int);  // pf 是一个指向返回值为 double 有一个 int 参数函数的指针
double* pf(int);    // pf 是有一个 int 参数返回值为 double 型指针的函数。

正确声明后,便可将相应的函数地址赋给它:

double pam(int);
double (*pf)(int);
pf = pam;

//其他声明方式
double (*pf)(int) = pam;  //直接初始化
auto pf = pam;            //自动获取类型并初始化(C++11)
//注意,pam()的形参和返回值类型必须与 pf 相同。如果不相同,编译器将拒绝这样赋值。

例如有如下函数原型:

void time(int line, double (*pf)(int));
//该声明指出,第二个参数是一个函数指针,它指向的函数接受一个 int 参数,并返回一个 double 值。

要想让 time() 使用 pam() 函数,需要将 pam() 的地址传递给它:

time(50, pam);

3.使用指针来调用函数

(*pf)扮演的角色和函数名相同,因此使用(*pf)时,只需要将它看做函数名即可:

double pam(int);
double (*pf)(int);
pf = pam;
double x = pam(4);
double y = (*pf)(5);
double y = pf(5);    //C++允许这样调用函数和第 5 行语句等效

综合举例

//
//  main.cpp
//  fun_ptr
//
//  Created by 刘一丁 on 2019/9/21.
//  Copyright © 2019年 LYD. All rights reserved.
//
#include <iostream>
using namespace std;

double betsy(int);
double pam(int);

//second argument is pointer to a type double function that
//takes a type int argument
void estimate(int lines, double (*pf)(int));

int main()
{
    int code;
    cout << "How many lines of code do you need? ";
    cin >> code;
    cout << "Here's Betsy's estimate:\n";
    estimate(code, betsy);
    cout << "Here's Pam's estimate:\n";
    estimate(code , pam);
    return 0;
}

double betsy(int lns) { return 0.5 * lns; }

double pam(int lns) { return 0.03 * lns + 0.0004 * lns * lns; }

void estimate(int lines, double (*pf)(int))
{
    cout << lines << " lines will take ";
    cout << (*pf)(lines) << " hour(s)\n";
}

//output:
//How many lines of code do you need? 100
//Here's Betsy's estimate:
//100 lines will take 50 hour(s)
//Here's Pam's estimate:
//100 lines will take 7 hour(s)

4.函数指针数组(数组指针和指针数组)

先解释下数组指针(指向数组的指针)和指针数组(存储指针的数组)

//定义一个指针数组,该数组中每个元素是一个指针,每个指针指向哪里就需要程序中后续再定义了。
int *p[4]; 
//定义一个数组指针,该指针指向含4个元素的一维数组(数组中每个元素是int型)。
int (*p)[4]; 

//注,[]优先级高于*

下面是一些函数的原型,他们的形参和返回值相同:

const double* f1(const double ar[], int n);
const double* f2(const double [], int n);
const double* f3(const double *, int n);

这些函数的形参看起来不同,但实际是相同的。在函数原型中,参数列表 const double ar[],与 const double *ar 的含义完全相同。其次在函数原型中,可以省略标识符。因此,const double ar[] 可简化为 const double[],而 const double *ar 可以简化为const double *。因此,上述所有函数形参的含义都相同,另一方面,函数定义必须提供标识符,因此需要使用  const double ar[] 和const double *ar。

现要声明一个指针,他可以指向这三个函数之一。假定该指针名为 pa,则只需将目标函数原型中的函数名替换为(*pa):

const double* (*p1)(const double *, int); 

//可在声明的时候进行初始化
const double* (*p1)(const double *, int) = f1;

//使用 C++11 的自动类型推断功能是,代码要简单的多:
auto p2 = f2;

//调用语句
cout << (*p1)(av, 3) << ": " << *(*p1)(av, 3) << endl;
cout << p2(av, 3) << " " << *p2(av, 3) << endl;

根据前 3 点,可以知道,(*p1)(av, 3) 和 p2(av, 3) 都调用指向的函数(这里为 f1() 和 f2()),并将 av 和 3 作为参数传递。因此,显示的是这两个函数的返回值。返回值的类型为 const double*(即 double 值的地址),因此在每条 cout 语句中,前半部分显示的都是一个 double 值的地址。想要查看存储在这些地址的实际值,需要将运算符*应用于这些地址,如表达式 *(*p1)(av, 3) 和 *p2(av, 3)。

函数指针数组

const double* (*pa[3])(const double *, int) = {f1, f2, f3};

/*
上述声明中 pa[3],表示 pa 是一个包含三个元素的数组,运算符 [] 优先级高于 * ,因此 *pa[3] 表示 
pa 是一个包含三个指针的数组。 
声明的其他部分表示:形参为 const double *, int 且返回类型为 const double* 的函数。因此,
pa 是一个包含三个指针的数组,其中每个指针都指向这样的函数,即将 const double *, int 作为形参,
并返回一个 const double*。
*/

auto pb = pa; //数组名是指向第一个元素的指针,因此 pa 和 pb 都是指向函数指针的指针。

调用方法

const double* px = pa[0](av, 3);
const double* py = (*pb)[1](av, 3);
//要获得指向 double 值,可使用运算符*:
double x = *pa[0](av, 3);
double y = *(*pb[1])(av, 3);

指向整个数组的指针。

如果这个指针名为 pd,则需要指出它是一个指针,而不是数组。这意味着应该声明为 (*pd)[3] ,其中的括号让标识符 pd 与 * 现结合。换句话说 pd 是一个指针,它指向一个包含三个元素的数组。具体声明如下:

const double* (*(*pd)[3])(const double*, int) = &pa;

调用函数时,需要知道:既然 pd 指向数组,那么 *pd 就是数组,而 (*pd)[i] 是数组中的元素,即函数指针。因此,较简单的函数调用是 (*pd)[i](av, 3),而 *(*pd)[i](av, 3) 是返回的指针指向的值。也可以使用第二种使用指针调用函数的语法:使用 (*(*pd)[i])(av, 3) 来调用函数,而 *(*(*pd)[i])(av, 3) 是指向的 double 值。

注意 pa(数组名,表示地址)和&pa 之间的差别。在大多数情况下, pa 都是数组第一个元素的地址,即 &pa[0]。因此,他是单个指针的地址。但 &pa 是整个数组(即三个指针块)的地址。从数字上说, pa 和 &pa 的值相同,但他们的类型不同。一种差别是, pa + 1 为数组中下一个元素的地址,而 &pa + 1 为数组 pa 后面一个 12 字节内存块的地址(假定地址为 4 字节)。另一个差别是,要得到第一个元素的值,只需对 pa 解除一次引用,但需要对 &pa 解除两次引用:

**&pa == *pa == pa[0];

5.为什么会用到函数指针

(1)实现运行时的多态性

(2)实现函数回调

具体可以参考其他博主写的这篇博文https://blog.csdn.net/zhangsan_3/article/details/53535551

    原文作者:一丁_
    原文地址: https://blog.csdn.net/qq_41291253/article/details/95185849
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞