当前位置:嗨网首页>书籍在线阅读

30-深入探讨函数指针

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

7.10.3 深入探讨函数指针

函数指针的表示可能非常恐怖。下面通过一个示例演示使用函数指针时面临的一些挑战。首先,下面是一些函数的原型,它们的特征标和返回类型相同:

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

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

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

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

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

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

使用C++11的自动类型推断功能时,代码要简单得多:

auto p2 = f2; // C++11 automatic type deduction

现在来看下面的语句:

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

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

鉴于需要使用三个函数,如果有一个函数指针数组将很方便。这样,将可使用for循环通过指针依次调用每个函数。如何声明这样的数组呢?显然,这种声明应类似于单个函数指针的声明,但必须在某个地方加上[3],以指出这是一个包含三个函数指针的数组。问题是在什么地方加上[3],答案如下(包含初始化):

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

为何将[3]放在这个地方呢?pa是一个包含三个元素的数组,而要声明这样的数组,首先需要使用pa[3]。该声明的其他部分指出了数组包含的元素是什么样的。运算符[]的优先级高于,因此pa[3]表明pa是一个包含三个指针的数组。上述声明的其他部分指出了每个指针指向的是什么:特征标为const double , int,且返回类型为const double 的函数。因此,pa是一个包含三个指针的数组,其中每个指针都指向这样的函数,即将const double 和int作为参数,并返回一个const double

这里能否使用auto呢?不能。自动类型推断只能用于单值初始化,而不能用于初始化列表。但声明数组pa后,声明同样类型的数组就很简单了:

auto pb = pa;

本书前面说过,数组名是指向第一个元素的指针,因此pa和pb都是指向函数指针的指针。

如何使用它们来调用函数呢?pa[i]和pb[i]都表示数组中的指针,因此可将任何一种函数调用表示法用于它们:

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);

可做的另一件事是创建指向整个数组的指针。由于数组名pa是指向函数指针的指针,因此指向数组的指针将是这样的指针,即它指向数组指针的指针。这听起来令人恐怖,但由于可使用单个值对其进行初始化,因此可使用auto:

auto pc = &pa; // C++11 automatic type deduction

如果您喜欢自己声明,该如何办呢?显然,这种声明应类似于pa的声明,但由于增加了一层间接,因此需要在某个地方添加一个。具体地说,如果这个指针名为pd,则需要指出它是一个指针,而不是数组。这意味着声明的核心部分应为(pd)[3],其中的括号让标识符pd与*先结合:

*pd[3]    // an array of 3 pointers
(*pd)[3] // a pointer to an array of 3 elements

换句话说,pd是一个指针,它指向一个包含三个元素的数组。这些元素是什么呢?由pa的声明的其他部分描述,结果如下:

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

要调用函数,需认识到这样一点:既然pd指向数组,那么pd就是数组,而(pd)[i]是数组中的元素,即函数指针。因此,较简单的函数调用是(pd)i,而(pd)i是返回的指针指向的值。也可以使用第二种使用指针调用函数的语法:使用((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]

程序清单7.19使用了这里讨论的知识。出于演示的目的,函数f1()等都非常简单。正如注释指出的,这个程序演示了auto的C++98替代品。

程序清单7.19 arfupt.cpp

// arfupt.cpp -- an array of function pointers
#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] = {1112.3, 1542.6, 2227.9};
    // pointer to a function
    const double *(*p1)(const double *, int) = f1;
    auto p2 = f2; // C++11 automatic type deduction
    // pre-C++11 can use the following code instead
    // const double *(*p2)(const double *, int) = f2;
    cout << "Using pointers to functions:\n";
    cout << " Address Value\n";
    cout << (*p1)(av,3) << ": " << *(*p1)(av,3) << endl;
    cout << p2(av,3) << ": " << *p2(av,3) << endl;
    // pa an array of pointers
    // auto doesn't work with list initialization
    const double *(*pa[3])(const double *, int) = {f1,f2,f3};
    // but it does work for initializing to a single value
    // pb a pointer to first element of pa
    auto pb = pa;
    // pre-C++11 can use the following code instead
    // const double *(**pb)(const double *, int) = pa;
    cout << "\nUsing an array of pointers to functions:\n";
    cout << " Address Value\n";
    for (int i = 0; i < 3; i++)
        cout << pa[i](av,3) << ": " << *pa[i](av,3) << endl;
    cout << "\nUsing a pointer to a pointer to a function:\n";
    cout << " Address Value\n";
    for (int i = 0; i < 3; i++)
        cout << pb[i](av,3) << ": " << *pb[i](av,3) << endl;
    // what about a pointer to an array of function pointers
    cout << "\nUsing pointers to an array of pointers:\n";
    cout << " Address Value\n";
    // easy way to declare pc
    auto pc = &pa;
    // pre-C++11 can use the following code instead
    // const double *(*(*pc)[3])(const double *, int) = &pa;
    cout << (*pc)[0](av,3) << ": " << *(*pc)[0](av,3) << endl;
    // hard way to declare pd
    const double *(*(*pd)[3])(const double *, int) = &pa;
    // store return value in pdb
    const double * pdb = (*pd)[1](av,3);
    cout << pdb << ": " << *pdb << endl;
    // alternative notation
    cout << (*(*pd)[2])(av,3) << ": " << *(*(*pd)[2])(av,3) << endl;
    // cin.get();
    return 0;
}
// some rather dull functions
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;
}

该程序的输出如下:

Using pointers to functions:
 Address Value
002AF9E0: 1112.3
002AF9E8: 1542.6
Using an array of pointers to functions:
 Address Value
002AF9E0: 1112.3
002AF9E8: 1542.6
002AF9F0: 2227.9
Using a pointer to a pointer to a function:
 Address Value
002AF9E0: 1112.3
002AF9E8: 1542.6
002AF9F0: 2227.9
Using pointers to an array of pointers:
 Address Value
002AF9E0: 1112.3
002AF9E8: 1542.6
002AF9F0: 2227.9

显示的地址为数组av中double值的存储位置。

这个示例可能看起来比较深奥,但指向函数指针数组的指针并不少见。实际上,类的虚方法实现通常都采用了这种技术(参见第13章)。所幸的是,这些细节由编译器处理。

感谢auto

C++11的目标之一是让C++更容易使用,从而让程序员将主要精力放在设计而不是细节上。程序清单7.19演示了这一点:

auto pc = &pa;                                                // C++11 automatic type deduction
const double *(*(*pd)[3])(const double *, int) = &pa; // C++98, do it yourself

自动类型推断功能表明,编译器的角色发生了改变。在C++98中,编译器利用其知识帮助您发现错误,而在C++11中,编译器利用其知识帮助您进行正确的声明。

存在一个潜在的缺点。自动类型推断确保变量的类型与赋给它的初值的类型一致,但您提供的初值的类型可能不对:

auto pc = *pa; // oops! used *pa instead of &pa

上述声明导致pc的类型与*pa一致,在程序清单7.19中,后面使用它时假定其类型与&pa相同,这将导致编译错误。