1. 程式人生 > >C++ Primer Plus書之--C++函式指標

C++ Primer Plus書之--C++函式指標

 

函式指標

使用函式指標, 能完成這樣的工作:可以編寫將另一個函式的地址作為引數的函式, 這樣第一個函式就能找到第二個函式, 並且執行它, 與直接呼叫第一個函式相比, 這種方法很笨拙, 但他允許在不同的時間傳遞不同函式的地址, 這意味著可以再不同的時間使用不同的函式.

基本需要包含以下步驟:

1.獲取函式的地址

2.宣告一個函式指標

3.使用函式指標來呼叫函式

 

第一步: 獲取函式的地址:

獲取函式地址很簡單, 只需要使用函式名(後面不要跟引數)即可. 例如:think 是一個函式

// 這樣就會把函式think的地址傳遞給函式process
process(think);

// 這樣是先呼叫think函式, 將think的返回值傳遞給process函式
thought(think());

第二步:宣告函式指標

宣告指向某種資料型別的指標時, 必須指定指標指向的型別. 同樣宣告指向函式的指標時, 也必須指定指標指向的函式型別. 這意味著宣告指定函式的返回型別以及函式的引數列表, 例如:

// 函式原型, 原型中可以不指定形參變數
double test(int);

// 正確的指標型別宣告如下:
// 意思是pfun指向一個需要一個int型引數的返回double型別的函式
double (*pfun)(int);

注意:與test()函式原型類似, 就是將test替換為(*pfun), 由於test是函式, 因此(*pfun)也是函式, 而如果(*pfun)是函式, 則pfun就是指向函式的指標.

通常, 要宣告指向特定型別的函式的指標的時候, 可以先寫這種函式的原型, 然後用(*pfun)替換函式名, 則pfun就是指向函式的指標.

注意: 必須在宣告中使用()將*pfun括起來, 括號的優先順序比*高, 因此*pfun(int), 表示pfun是一個返回指標的函式, 而(*pfun)(int)表示fpun是指向函式的指標.

正確宣告pfun後, 就可以將相應函式的地址賦給pfun:

pfun = test;

注意test()的引數列表和返回值, 必須和pfun的相同, 否則會報錯.

現在來寫一個完整函式呼叫另一個函式的函式宣告:

// 宣告estimate接收一個指向一個接收int型引數,並且返回double型別的函式的指標
void estimate(int lines, double (*pfun)(int));

// 使用estimate很簡單:
// pam是函式名, 就是需要傳遞進函式estimate的函式的函式名
estimate(50, pam);

 

第三步:使用指標來呼叫函式.

前面說過(*pfun)與函式名的作用相同, 因此使用時只需將(*pfun)看做函式名即可.

// 這種寫法雖然不太好看, 但是明確的顯示出來, 正在使用的是函式指標
double x = (*pfun)(2);

// c++也支援這種寫法, 就像使用函式名那樣使用pfun
double y = pfun(3);

 

來看一個完整的例子:

// 函式指標例子
#include <iostream>

using namespace std;

// 函式原型
double betsy(int);
double pam(int);

// 第二個引數值一個函式指標, 指向一個需要一個int型引數, 並且返回double的函式
void estimate(int lines, double (*pfun)(int));

int main() 
{
	int code;
	cout << "How many lines of code do you need? ";
	cin >> code;
	cout << "Here's Betsy's time : " << endl;
	// 函式名就是函式的地址, 所以直接將函式名傳進去
	estimate(code, betsy);
	cout << "Here's pam's time : " << endl;
	estimate(code, pam);
	return 0;
}

double betsy(int lines)
{
	return 0.02 * lines;
}

double pam(int lines)
{
	return 0.03 * lines;
}

void estimate(int lines, double (*pfun)(int))
{
	cout << lines << " lines will take : ";
	// 或者寫成這樣也行: cout << pfun(lines) << " hours " << endl;
	cout << (*pfun)(lines) << " hours " << endl;
}

看一下執行結果:

 

深入探討函式指標

 

1.宣告三個函式原型, 他們的引數列表和返回型別都相同:

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

const double arr[] 和 const double *arr相同而變數名可以省略, 因此上面三個函式原型的引數列表和返回值都一樣

 

2.宣告一個函式指標, 指向上面宣告的三種函式其中的任何一種

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

並且可以在宣告的同事進行初始化:

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

使用c++11的自動型別推斷功能時, 程式碼要簡單的多:

auto p2 = f2;

 

3.再來看下面這句:

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作為引數, 因此這兩個顯示的是函式的返回值, 也就是double值得地址, 而要顯示double值則使用後面的程式碼即*(*p1)(av, 3) 和 *p2(av, 3)

 

鑑於需要使用三個函式, 如果有一個函式的指標陣列將會很方便, 如何宣告這樣的陣列呢?

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

為何要將[3]放在這個地方呢? pf是一個包含了三個元素的陣列, 因此要宣告這樣的陣列, 首先要pf[3], 該宣告的其他部分指出了陣列包含的元素是什麼樣的. 運算子[]的優先順序高於*, 因此*pf[3]是一個包含三個指標的陣列, 上述宣告的其他部分指出了每個指標指向的是什麼, 引數列表為const double *, int且返回值是double *的函式.

我們既然已經聲明瞭pa, 那麼再宣告同樣的陣列就可以使用atuo了

auto pb = pa;

因為陣列名是指向陣列的第一個元素的指標, 因此pb和pa都是指向函式指標的指標
可以使用如下的方法來呼叫函式:

const double * px = pa[0](test, 3);
const double * py = (*pb[1])(test, 3);

要獲得對應的double值, 可以使用運算子*

double x = *(pa[0](test, 3));
double y = *((*pb[1])(test, 3));

再看下面這種宣告: 

auto pc = &pa;

如果我們想自己宣告pc的話, 需要這樣做: 
// 這是一個指標, 指向了一個包含三個元素的陣列
(*pd)[3]
// 這是一個數組, 裡面包含了三個指標
*pd[3]
主要是涉及到優先順序的問題, *的優先順序比[]低, 所以第二個首先是個陣列, 然後前面的*指明瞭它裡面的內容是指標, 第一個首先是(*pd)說明是個指標, 然後[]說明指標指向了一個包含三個元素的陣列.

換句話說, pd是一個指標, 它指向一個包含三個元素的陣列:

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

要呼叫函式, 需要明確的是既然pd是指向陣列, 那麼*pd就是陣列, 而(*pd)[i]就是陣列中的元素即函式指標, 因此簡單的呼叫方式是(*pd)[i](arr, 3), 而*(*pd)[i](arr, 3)就是返回的指標指向的值. 也可以使用指標呼叫函式的語法(*(*pd)[i])(arr, 3)來呼叫函式(按照面介紹的既然(*pd)[i], 相當於函式名, 那麼函式指標就可以按照在函式名前加*來呼叫函式, 也就是(*(*pd)[i])),    *(*(*pd)[i])(arr, 3)就是指向的double的值.

 

下面看一下完整的例子
 

// 深究函式指標
#include <iostream>
using namespace std;
// 以三種不同的方式生命了三個函式原型, 實際他們的函式格式都是一樣的, 只是函式名不同
// 就是函式的引數列表和函式的返回值都是一樣的
const double* f1(const double arr[], int n);
const double* f2(const double [], int n);
const double* f3(const double *, int n);

int main()
{
	double d_arr[3] = {1.23, 2.34, 3.45};
	
	// 定義了一個函式指標
	// 指向一個接收const double *, 和int型 兩個引數的, 並且返回double *的函式
	// 然後把函式f1賦值給了p1這個指標
	const double* (*p1)(const double *, int) = f1;
	// 利用auto聲明瞭一個函式指標
	// 這個函式指標實際和宣告p1的時候一樣, 只不過auto比較省事
	auto p2 = f2;
	// const double* (*p2)(const double *, int) = f2; 上面auto和這行意義一樣
	
	cout << "Using pointers to functions: " << endl;
	cout << "Address Value" << endl;
	cout << p1(d_arr, 3) << " : " << *(p1(d_arr, 3)) << endl;
	cout << (*p2)(d_arr, 3) << " : " << *((*p2)(d_arr, 3)) << endl;
	
	// 聲明瞭一個包含三個指標的陣列
	// 而數組裡的指標是指向接收const double *和int型引數, 並且返回double *的函式
	// 這句話不能直接使用auto, auto不適用列表初始化
	const double * (*pa[3])(const double *, int) = {f1, f2, f3};
	
	// 但是仍然適用於單個值的初始化
	// pb是一個指標, 指向pa的第一個元素
	auto pb = pa;
	// c++11 之前可以用下面這行, pb是一個指標的指標
	// const double * (**pb)(const double *, int) = pa;
		
	cout << "Using an array of pointers" << endl;
	cout << "Anddress Value" << endl;
	
	for (int i = 0; i < 3; i++)
		cout << (pa[i])(d_arr, 3) << " : " << (*(pa[i])(d_arr, 3)) << endl;
	
	cout << "Using a pointer to a pointer to a function : " << endl;
	cout << "Anddress Value" << endl;
	for(int i = 0; i < 3; i++)
		cout << pb[i](d_arr, 3) << " : " << *pb[i](d_arr, 3) << endl;
	
	cout << "Using pointers to an array of pointers: " << endl;
	cout << "Address  Value" << endl;
	auto pc = &pa;
	// const double * (*(*pc)[3])(const double *, int) = &pa;
	
	cout << (*pc)[0](d_arr, 3) << " : " << *(*pc)[0](d_arr, 3) << endl;
	
	
	const double * (*(*pd)[3])(const double *, int) = &pa;
	const double * pdb = (*pd)[1](d_arr, 3);
	cout << pdb << " : " << *pdb << endl;
	
	cout << (*(*pd)[2])(d_arr, 3) << " : " << *(*(*pd)[2])(d_arr, 3) << endl;

	return 0;
}

const double* f1(const double arr[], int n)
{
	return arr;
}

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

const double* f3(const double * arr, int n)
{
	return arr + 2;
}

看一下執行結果: