C/C++函数指针

函数指针

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

 

(1)获取函数的地址

获取函数的地址很简单:只要使用函数名即可(后面不跟参数)。也就是说,如果think()是一个函数,则think就是该函数的地址。要将函数作为参数进行传递,必须传递函数名。一定要区分传递的是函数的地址还是函数的返回值:

Process(think);     //传递函数的地址
Process(think());   //传递函数的返回值

 

(2)声明函数指针

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

double asd(int);

则正确的指针类型声明如下:

double (*pf)(int);   //pf指向使用一个参数并返回double类型数据的函数

正确地声明pf后,便可以将相应函数的地址赋给它:
double asd(int);
double (*pf)(int);
pf = asd;           //asd是函数,因此(*pf)也是函数

asd()的特征标和返回类型必须与pf相同。    

 

(3)使用指针来调用函数

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

double asd(int);
double (*pf)(int);
pf = asd;                //asd是函数,因此(*pf)也是函数
double x = asd(4);
double y = (*pf)(5);

实际上,C++也允许像使用函数名那样使用pf:
double y = pf(5);

第一种格式虽然不好看,但它给出了强有力的提示——代码正在使用函数指针。

 

为何pf和(*pf)等价?一方认为,由于pf是函数指针,而*pf是函数,因此应将(*pf)用作函数调用。另一方认为,由于函数名是指向该函数的指针,指向函数的指针的行为应与函数名相似,因此应将pf()用作函数调用使用。C++进行了折衷,这两种方式都允许。

 

(4)函数指针实例

#include <iostream>
using namespace std;

double betsy(int);
double pam(int);
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 Bets's estimate:\n";
	estimate(code, betsy);
	cout << "Here's Pam's estimate:\n";
	estimate(code, pam);
	return 0;
}

double betsy(int lns)
{
    return 0.05 * 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";
}

C/C++函数指针

 

深入探讨函数指针

下面是一些函数的原型,它们的特征标和返回类型相同:

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

在函数原型中,参数列表cosnt double ar[]与cosnt double *ar的含义完全相同。其次,在函数原型中,可以省略标识符。因此,cosnt double ar[]可简化为cosnt double [],而cosnt double *ar可简化为cosnt double *。

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

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

可在声明的同时进行初始化:

const double *(*pa)(const double *, int) = f1;

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

 

现在来分析下面的语句:

cout << (*p1)(asd, 3) << ": " << *(*p1)(asd, 3) <<endl;
cout << p2(asd, 3) << ": " << *p2(asd, 3) <<endl;

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

若需要用到三个函数,可以创建一个函数指针数组。这样,将可使用for循环通过指针依次调用每个函数。这种声明应类似于单个函数指针的声明,但必须在某个地方加上[3],以指出这是一个包含三个函数指针的数组。问题是在什么地方加上[3],答案如下:

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

因为运算符[]的优先级高于*,因此*pa[3]表明pa是一个包含3个指针的数组。

*pd[3]      //3个指针的数组
(*pd)[3]    //一个指向包含3个元素数组的指针

 

上面创建的是函数指针的数组,还可以创建一个指向整个数组的指针。由于数组名pa是指向函数指针的指针,即所要创建的指针是指向指针的指针。使用单个值对其进行初始化,可以使用auto:

auto pc = &pa;

但为了更好理解函数指针,可以声明pd指针,若它指向一个包含3个元素的数组,则声明的核心部分是(*pd)[3]由pa的声明描述可得pd的声明如下:

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

声明部分有3个*,看着就头大,但仔细分析其实也没有那么复杂。首先第一个*是告诉我们返回值是double*,而第三个*的意思是(*pd)[3]为一个指向包含3个元素数组的指针。第二个*的意思是创建了一个指向指针数组的指针。

要调用函数,需要认识到这样一点:既然pd指向数组,那么*pd就是数组,而(*pd)[i]是数组中的元素,即函数指针因此较为简单的函数调用是(*pd)[i](asd, 3),而*(*pd)[i](asd, 3)是返回的指针指向的值。也可以使用第二种使用指针调用函数的方法(前面提到的pf(5)和(*pf)(5)):使用(* (*pd)[i] )(asd, 3)来调用函数,*(* (*pd)[i] )(asd, 3)是指向的double值。当看到*(*pd)[i](asd, 3)和(* (*pd)[i] )(asd, 3)可能有点混,但是这样看可能就能感觉到不一样了,*[(*pd)[i](asd, 3)](返回指向的值) 和{* (*pd)[i] }(asd, 3)(调用函数)。这里可以将(*pd)[i]代换成函数名,没有那么多*,理解能容易点。

因为涉及到了&pa,所以多提一点:pa(它是数组名,表示地址)和&pa之间的差别。从数字上说,pa和&pa的值相同,但它们的类型不同。一种差别是,pa+1为数组中下一个元素的地址,而&pa+1为数组pa后面一个12字节内存块的地址(假定地址为4字节)。另一个差别是,要得到第一个元素的值,只需要对pa解除一次引用,而对&pa要解除两次引用:

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

 

实例代码如下:

#include <iostream>
using namespace std;

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

int main()
{
    double asd[3] = {111.1, 222.2, 333.3};
    const double *(*p1)(const double *, int) = f1;
    const double *(*p2)(const double *, int) = f2;

    cout << "Using pointers to functions:\n";
    cout << "Address   Value\n";
    cout << (*p1)(asd, 3) << ": " << *(*p1)(asd, 3) <<endl;
    cout << p2(asd, 3) << ": " << *p2(asd, 3) <<endl;

    const double *(*pa[3])(const double *, int) = {f1, f2, f3};
    cout << "\nUsing an array of pointers to functions:\n";
    cout << "Address   Value\n";
    for(int i = 0; i < 3; i++)
        cout << pa[i](asd, 3) << ": " << *pa[i](asd, 3) <<endl;

    cout << "\nUsing a pointer to an array of pointers:\n";
    cout << "Address   Value\n";
    const double *(*(*pc)[3])(const double *, int) = &pa;
    cout << (*pc)[0](asd, 3) << ": " << *(*pc)[0](asd, 3) <<endl;

    const double *(*(*pd)[3])(const double *, int) = &pa;
    const double *pdb = (*pd)[1](asd, 3);
    cout << pdb << ": " << *pdb <<endl;
    cout << (*(*pd)[2])(asd, 3) << ": " << *(*(*pd)[2])(asd, 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;
}

C/C++函数指针