深入探讨函数指针

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

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。
接下来,假设要声明一个指针,它可指向这三个函数之一。
假定该指针名为pa,则只需将目标函数原型中的函数名替换为
(*pa):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值的地址。为查看存储在这些地址处的实际值,需要将运算符应用于这些地址,如表达式(pa)(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
这里能否使用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是指向的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替代品。

//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 Vaule\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 dones work with list initialization
	//pb a pointer to first element of pa
	auto pb = pa;
	//pre-C++11 can use the foloowing code instead
	//const double *(**pb)(const double *,int) = pa;
	cout << "\nUsing an array of pointers to function:\n";
	cout << " Address Value\n";
	for (int i = 1; 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 apointer to an array of pointers:\n";
	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; }

在这里插入图片描述
显示的地址为数组av中double值的存储位置。
这个示例可能看起来比较深澳,但指向函数指针数组的指针并不少见。实际上,类的虚方法实现通常都采用了这种技术。所幸的是,这些细节由编译器处理。

感谢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
存在一个潜在的缺点。自动类型推断确保变量的类型与赋给它的初值的类型一致,但您提供的初值的类型可能不对:
auto pc = *pa; //oops! used pa instead of &pa
上述声明导致pc的类型与
pa一致,在程序清单7.19中,后面使用它时假定其类型与&pa相同,这将导致编译错误。

使用typedef进行简化

除auto外,C++还提供了其他简化声明的工具。关键字typedef让您能够创建类型别名:
typedef double real; //make real another name for double
这里采用的方法是,将别名当做表示标识符进行声明,并在开头使用关键字typedef。因此,可将p_fun声明为程序清单7.19使用的函数指针的别名:

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 an array of 3
function pointers p_fun (*pd)[3] = &pa; //pd points to an array of
3 function pointers

使用typedef可以减少输入量,让您编写代码时不容易犯错,并让程序更容易理解。

From 《C++ primer Plus》
By Suki

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章