深入探討函數指針

首先下面是一些函數的原型,它們的特徵標和返回類型相同:

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

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