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