C++知识点-函数指针

初识C++中函数指针时感觉其就是一种函数式编程的实现,主要实现的功能是可以将函数当作参数传入其他函数中。
与数据项类似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。 这句话可能不太好理解,但是学过汇编后大致可以猜到函数指针就指向了函数汇编后一系列指令中的第一条指令。学好编程真是万丈高楼平地起,一定要把地基打好,即计算机的基础知识学好。

使用函数指针可以在不同的时间传递不同函数的地址,因此可以在不同的时间使用不同的函数,有点类似于策略模式的思想。

基础知识

要使用函数指针,大致需要完成以下的动作:

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

获取函数地址

获取函数地址很简单,假如 think() 是一个函数,那么 think 就是该函数的地址

声明函数指针

声明函数指针时必须指定函数的返回类型和函数的特征标(参数列表),例如有如下函数原型

double pam(int); // prototype
double (*pf)(int) // pf是一个函数指针
double *pf(int) // pf()是一个返回double指针的函数

其中 pf 就是函数指针,其中特别需要注意括号不能省略。正确声明函数指针后,就可以将函数地址赋给它。

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

使用指针调用函数

当完成对函数指针的赋值后,我们就能用它来进行函数调用。

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

double x = pam(4);
double y = (*pf)(4); // ok
double z = pf(4); // ok

代码示例

以下代码展示了函数指针的简单使用,其中最关键的是使用函数指针的函数的函数原型。

#include <iostream>

double func1(int);
double func2(int);

// the 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 = 5;
    std::cout << "Here's func1's estimate:\n";
    estimate(code, func1);
    std::cout << "Here's func2's estimate:\n";
    estimate(code, func2);
    return 0;
}

double func1(int lns) {
    return 0.05 * lns;
}

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

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

output

Here's func1's estimate:
5 lines will take 0.25 hour(s)
Here's func2's estimate:
5 lines will take 0.16 hour(s)

函数指针数组

函数指针稍微复杂一点的应用是函数指针数组,这里有点绕,我们一步一步记录。

函数原型

首先是理解一下三个函数原型,它们的特征标和返回类型都相同,返回值都是double类型的指针,入参都是double数组和int值。

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

函数指针数组定义

接着看函数指针数组的定义

const double* (*pa[3])(const double ar[], int n) = {f1, f2, f3};
auto pb = pa;// ok

上述语句我们要由外而内地看,首先 pa[3] 声明了一个数组,至于这个数组包含什么元素就要看其他部分。接着看 *pa[3],因为[]优先级高于 *, 所以该表达式表示的是一个含有3个元素的指针数组。最后再看剩下的部分,剩下的部分表示该数组中的元素为 函数指针我们要注意pa的赋值不能使用auto,因为auto只能进行单值初始化,不能初始化列表。

函数指针数组的使用

接着看函数指针数组的使用。
数组名是指向第一个元素的指针,因此上述声明的 pa 和 pb,都是指向函数指针的指针。 我们可以这样使用。

const double * x = pa[0](av, 4); // ok
const double * y = (*pb[1])(av, 4) // ok

创建指向整个数组的指针

这一步应该是最绕的,即我们要创建指向函数指针数组的指针,这听着就有点绕,还是请耐心看下去。

const double * (*pa[3])(const double ar[], int n) = {f1, f2, f3};
auto pb = pa;// ok

在这里我们还是以上面定义的函数指针数组 pa 举例。首先我们要理解 pa&pa 之间的区别。

  • pa 是数组名,表示数组第一个元素的地址,即 pa == &pa[0],是单个元素的地址,在指针数组里就是单个指针的地址。
  • &pa 是整个数组(即三个指针块)的地址,pa 和 &pa 的值是相同的,但是类型不同
  • pa+1 表示数组中下一个元素的地址,&pa+1 表示的是整个数组后面12个字节(假设一个指针4字节)的地址
  • *pa == pa[0] == **&pa

在这里我们要创建指向pa数组的指针可以用以下代码

auto pc = &pa;
const double * (*(*pd)[3])(const double * ar, int n) = &pa;

其中一定要注意括号怎么放,主要是区分指针数组和指向数组的指针写法的区别。 记忆的方法是看优先级,如果[]在前面则是数组,如果*在前面则是指针。

*pd[3] // pd 是含有三个指针的数组,是数组
(*pd)[3] // pd 是指向含有三个元素数组的指针,是指针

代码示例

#include <iostream>

// various notations, same signatures
const double *f1(const double ar[], int n);
const double *f2(const double [], int);
const double *f3(const double *, int);

int main() {
    using namespace std;
    double av[3] = {1.1, 2.2, 3.3};

    // define a pointer to a function, pre-C++11
    const double * (*p1)(const double * ar, int n) = f1;
    // C++11
    auto p2 = f2;
    cout << "The example of using pointers to functions:\n";
    cout << (*p1)(av, 3) << ": " << *(*p1)(av, 3) << endl;
    cout << p2(av, 3) << ": " << *p2(av, 3) << endl;

    // define an array of pointers
    const double * (*pa[3])(const double * ar, int n) = {f1, f2, f3};
    auto pb = pa;
    cout  << "The example of using the array of function pointers:\n";
    for (int i = 0; i < 3; i++) {
        cout << pa[i](av, 3) << ": " << *pa[i](av, 3) << endl;
    }

    // define a pointer to an array of function pointers
    auto pc = &pa;
    const double * (*(*pd)[3])(const double * ar, int n) = &pa;
    cout << "The example of using a pointer to an array of functions:\n";
    cout << (*pc)[0](av, 3) << ": " << *(*pc)[0](av, 3) << endl;
    const double *pdd = (*pd)[1](av, 3);
    cout << pdd << ": " << *pdd << endl;
    cout << (*(*pd)[2])(av, 3) << ": " << *(*(*pd)[2])(av, 3) << endl;
    return 0;
}

const double *f1(const double *ar, int n) {
    return ar;
}

const double *f2(const double ar[], int n) {
    return ar + 1;
}

const double *f3(const double ar[], int n) {
    return ar + 2;
}

output

The example of using pointers to functions:
0x7ffee2f83800: 1.1
0x7ffee2f83808: 2.2
The example of using the array of function pointers:
0x7ffee2f83800: 1.1
0x7ffee2f83808: 2.2
0x7ffee2f83810: 3.3
The example of using a pointer to an array of functions:
0x7ffee2f83800: 1.1
0x7ffee2f83808: 2.2
0x7ffee2f83810: 3.3

这些代码很绕,但却很有用,比如在类的虚方法实现上就使用了指向函数指针数组的指针这一技术。 当然我们也可以使用 typedef关键字来进行代码简化。

typedef const double * (*p_fun)(const double *, int); // p_fun now a type name
p_fun p1 = f1; // p1 points to the f1() function

p_fun pa[3] = {f1, f2, f3}; // pa is an array of 3 function pointers
p_fun (*pd)[3] = &pa; // pd is a pointer that points to an array of 3 function pointers

Reference

1.《C++ Primer Plus 6th》


版权声明:本文为EJoft原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。