C/C++函数指针
函數(shù)指針
與數(shù)據(jù)項相似,函數(shù)也有地址。函數(shù)的地址是存儲其機(jī)器語言代碼的內(nèi)存的開始地址。我們可以編寫將另一個函數(shù)的地址作為參數(shù)的函數(shù)。這樣第一個函數(shù)將能夠找到第二個函數(shù),并運(yùn)行它。與直接調(diào)用一個函數(shù)相比,這種方法比較笨拙,但它允許在不同的時間傳遞不同函數(shù)的地址,這意味著可以在不同的時間使用不同的函數(shù)。
?
(1)獲取函數(shù)的地址
獲取函數(shù)的地址很簡單:只要使用函數(shù)名即可(后面不跟參數(shù))。也就是說,如果think()是一個函數(shù),則think就是該函數(shù)的地址。要將函數(shù)作為參數(shù)進(jìn)行傳遞,必須傳遞函數(shù)名。一定要區(qū)分傳遞的是函數(shù)的地址還是函數(shù)的返回值:
Process(think); ????//傳遞函數(shù)的地址 Process(think()); ??//傳遞函數(shù)的返回值?
(2)聲明函數(shù)指針
聲明指向某種數(shù)據(jù)類型的指針時,必須指定指針指向的類型。同樣,聲明指向函數(shù)的指針時,也必須指定指針指向的函數(shù)類型。這意味著聲明應(yīng)指定函數(shù)的返回類型以及函數(shù)的特征標(biāo)(參數(shù)列表)。也就是說,聲明應(yīng)像函數(shù)原型那樣指出有關(guān)函數(shù)的信息。例如一個函數(shù)原型如下:
double asd(int);則正確的指針類型聲明如下:
double (*pf)(int); ??//pf指向使用一個參數(shù)并返回double類型數(shù)據(jù)的函數(shù)正確地聲明pf后,便可以將相應(yīng)函數(shù)的地址賦給它: double asd(int); double (*pf)(int); pf = asd; ??????????//asd是函數(shù),因此(*pf)也是函數(shù)asd()的特征標(biāo)和返回類型必須與pf相同。????
?
(3)使用指針來調(diào)用函數(shù)
(*pf)扮演的角色和函數(shù)名相同,因此使用(*pf)時,只需將它看作函數(shù)名即可:
double asd(int); double (*pf)(int); pf = asd; ?????????? //asd是函數(shù),因此(*pf)也是函數(shù) double x = asd(4); double y = (*pf)(5);實際上,C++也允許像使用函數(shù)名那樣使用pf: double y = pf(5);第一種格式雖然不好看,但它給出了強(qiáng)有力的提示——代碼正在使用函數(shù)指針。
?
為何pf和(*pf)等價?一方認(rèn)為,由于pf是函數(shù)指針,而*pf是函數(shù),因此應(yīng)將(*pf)用作函數(shù)調(diào)用。另一方認(rèn)為,由于函數(shù)名是指向該函數(shù)的指針,指向函數(shù)的指針的行為應(yīng)與函數(shù)名相似,因此應(yīng)將pf()用作函數(shù)調(diào)用使用。C++進(jìn)行了折衷,這兩種方式都允許。
?
(4)函數(shù)指針實例
#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"; }?
深入探討函數(shù)指針
下面是一些函數(shù)的原型,它們的特征標(biāo)和返回類型相同:
const double *f1(cosnt double ar[], int n); const double *f1(cosnt double [], int n); const double *f1(cosnt double *, int n);在函數(shù)原型中,參數(shù)列表cosnt double ar[]與cosnt double *ar的含義完全相同。其次,在函數(shù)原型中,可以省略標(biāo)識符。因此,cosnt double ar[]可簡化為cosnt double [],而cosnt double *ar可簡化為cosnt double *。
接下來,假設(shè)要聲明一個指針,它可以指向這三個函數(shù)之一。假定該指針名為pa,則只需要將目標(biāo)函數(shù)原型中的函數(shù)名替換為(*pa):
const double *(*pa)(const double *, int);可在聲明的同時進(jìn)行初始化:
const double *(*pa)(const double *, int) = f1;使用C++的自動類型推斷功能時,代碼要簡單的多: auto p2 = f2; ????
現(xiàn)在來分析下面的語句:
cout << (*p1)(asd, 3) << ": "?<< *(*p1)(asd, 3) <<endl; cout << p2(asd, 3) << ": "?<< *p2(asd, 3) <<endl;根據(jù)前面的知識可知,(*p1)(asd, 3)和p2(asd, 3)都可以調(diào)用所指向的函數(shù)(本例中是f1()和f2()),并將asd和3作為參數(shù)。因此,顯示的是這兩個函數(shù)的返回值。返回值類型為const double *(即double值的地址),因此在每條cout語句中,前半部分顯示的都是一個double值的地址。為了查看存儲在這些地址處的實際值,需要將運(yùn)算符*應(yīng)用于這些地址,如表達(dá)式*(*p1)(asd, 3)和*p2(asd, 3)。
若需要用到三個函數(shù),可以創(chuàng)建一個函數(shù)指針數(shù)組。這樣,將可使用for循環(huán)通過指針依次調(diào)用每個函數(shù)。這種聲明應(yīng)類似于單個函數(shù)指針的聲明,但必須在某個地方加上[3],以指出這是一個包含三個函數(shù)指針的數(shù)組。問題是在什么地方加上[3],答案如下:
const double *(*pa[3])(const double *, int) = {f1, f2, f3};因為運(yùn)算符[]的優(yōu)先級高于*,因此*pa[3]表明pa是一個包含3個指針的數(shù)組。
*pd[3] ?????//3個指針的數(shù)組 (*pd)[3] ???//一個指向包含3個元素數(shù)組的指針?
上面創(chuàng)建的是函數(shù)指針的數(shù)組,還可以創(chuàng)建一個指向整個數(shù)組的指針。由于數(shù)組名pa是指向函數(shù)指針的指針,即所要創(chuàng)建的指針是指向指針的指針。使用單個值對其進(jìn)行初始化,可以使用auto:
auto pc = &pa;但為了更好理解函數(shù)指針,可以聲明pd指針,若它指向一個包含3個元素的數(shù)組,則聲明的核心部分是(*pd)[3]。由pa的聲明描述可得pd的聲明如下:
const double *(*(*pd)[3])(const double *, int) = &pa;聲明部分有3個*,看著就頭大,但仔細(xì)分析其實也沒有那么復(fù)雜。首先第一個*是告訴我們返回值是double*,而第三個*的意思是(*pd)[3]為一個指向包含3個元素數(shù)組的指針。第二個*的意思是創(chuàng)建了一個指向指針數(shù)組的指針。
要調(diào)用函數(shù),需要認(rèn)識到這樣一點(diǎn):既然pd指向數(shù)組,那么*pd就是數(shù)組,而(*pd)[i]是數(shù)組中的元素,即函數(shù)指針。因此較為簡單的函數(shù)調(diào)用是(*pd)[i](asd, 3),而*(*pd)[i](asd, 3)是返回的指針指向的值。也可以使用第二種使用指針調(diào)用函數(shù)的方法(前面提到的pf(5)和(*pf)(5)):使用(* (*pd)[i] )(asd, 3)來調(diào)用函數(shù),*(* (*pd)[i] )(asd, 3)是指向的double值。當(dāng)看到*(*pd)[i](asd, 3)和(* (*pd)[i] )(asd, 3)可能有點(diǎn)混,但是這樣看可能就能感覺到不一樣了,*[(*pd)[i](asd, 3)](返回指向的值)?和{* (*pd)[i] }(asd, 3)(調(diào)用函數(shù))。這里可以將(*pd)[i]代換成函數(shù)名,沒有那么多*,理解能容易點(diǎn)。
因為涉及到了&pa,所以多提一點(diǎn):pa(它是數(shù)組名,表示地址)和&pa之間的差別。從數(shù)字上說,pa和&pa的值相同,但它們的類型不同。一種差別是,pa+1為數(shù)組中下一個元素的地址,而&pa+1為數(shù)組pa后面一個12字節(jié)內(nèi)存塊的地址(假定地址為4字節(jié))。另一個差別是,要得到第一個元素的值,只需要對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; }總結(jié)
- 上一篇: C/C++ 指针和数组
- 下一篇: C/C++数组指针和指针数组