6.7 函數指針 (這一章節比較難)
函數指針指向的是函數而非對象。和其他指針一樣,函數指針指向某種特定類型。函數的類型由它的返回類型和形參類型共同決定,與函數名無關。例如:
//比較兩個 string 對象的長度
bool lengthCompare(const string &,const string &);
該函數的類型是 bool(conststring&,conststring&)。要想聲明一個可以指向該函數的指針,只需要用指針替換函數名即可:
//pf 指向一個函數,該函數的參數是兩個const string的引用,返回值是bool 類型
bool(*pf)(const string&,const string&);//未初始化
從我們聲明的名字開始觀察,pf前面有個 *,因此pf是指針;右側是形參列表,表示pf指向的是函數;再觀察左側,發現函數的返回類型是布爾值。因此,pf就是一個指向函數的指針,其中該函數的參數是兩個const string的引用,返回值是boo1類型。
*pf 兩端的括號必不可少。如果不寫這對括號,則pf是一個返回值為 bool指針的函數:
//聲明一個名為pf的函數,該函數返回 bool *
bool * pf(const string &,const string &);
使用函數指針
當我們把函數名作為一個值使用時,該函數自動地轉換成指針。例如,按照如下形式我們可以將 lengthCompare的地址賦給pf:
pf = lengthCompare;//pf 指向名為 lengthCompare 的函數
pf = &lengthCompare;//等價的賦值語句:取地址符是可選的
此外,我們還能直接使用指向函數的指針調用該函數,無須提前解引用指針:
bool bl =pf("hello","goodbye");//調用lengthCompare 函數
bool b2=(*pf)("hello","goodbye");//一個等價的調用
bool b3=lengthCompare("hello","goodbye");// 另一個等價的調用
在指向不同函數類型的指針間不存在轉換規則。但是和往常一樣,我們可以為函數指針賦一個nullptr(參見2.3.2節,第48頁)或者值為0的整型常量表達式,表示該指針沒有指向任何一個函數:
string::size_type sumLength(const string &,const string &);
bool cstringCompare(const char*,const char*);
pf = 0;//正確:pf 不指向任何函數
pf = sumLength;//錯誤:返回類型不匹配
pf = cstringCompare;//錯誤:形參類型不匹配
pf = lengthCompare;//正確:函數和指針的類型精確匹配
重載函數的指針
當我們使用重載函數時,上下文必須清晰地界定到底應該選用哪個函數。如果定義了指向重載函數的指針
void ff(int*);
void ff(unsigned int);
void(*pfl)(unsigned int)=ff;//pfl指向ff(unsigned)
編譯器通過指針類型決定選用哪個函數,指針類型必須與重載函數中的某一個精確匹配
void(*pf2)(int)=ff;//錯誤:沒有任何一個ff與該形參列表匹配
double(*pf3)(int*)=ff;//錯誤:ff和pf3的返回類型不匹配
函數指針形參
和數組類似(參見6.2.4節,第193頁),雖然不能定義函數類型的形參,但是形參可以是指向函數的指針。此時,形參看起來是函數類型,實際上卻是當成指針使用:
//第三個形參是函數類型,它會自動地轉換成指向函數的指針
void useBigger(const string & s1,const string &s2,
bool pf(const string &,const string &));
//等價的聲明:顯式地將形參定義成指向函數的指針
void useBigger(const string &s1,const string &s2,bool(*pf)(const string &,const string &));
我們可以直接把函數作為實參使用,此時它會自動轉換成指針:
//自動將函數lengthCompare轉換成指向該函數的指針
useBigger(s1,s2,lengthCompare);
正如useBigger的聲明語句所示,直接使用函數指針類型顯得冗長而煩瑣。類型別名(參見2.5.1節,第60頁)和decltype(參見2.5.3節,第62頁)能讓我們簡化使用了函數指針的代碼:
//Func和Func2是函數類型
typedef bool Func(const string&,const string&);
typedef decltype(lengthCompare) Func2;//等價的類型
//FuncP 和FuncP2是指向函數的指針
typedef bool(*FuncP)(const string&,const string&);
typedef decltype(lengthCompare) *FuncP2;//等價的類型
我們使用 typedef定義自己的類型。Func和Func2是函數類型,而Funcp和FuncP2是指針類型。需要注意的是,decltype返回函數類型,此時不會將函數類型自動轉換成指針類型。因為decltype的結果是函數類型,所以只有在結果前面加上*才能得到指針。可以使用如下的形式重新聲明useBigger:
//useBigger的等價聲明,其中使用了類型別名
void useBigger(const string&,const string&,Func);
void useBigger(const string&,const string&,FuncP2);
這兩個聲明語句聲明的是同一個函數,在第一條語句中,編譯器自動地將Func 表示的函數類型轉換成指針。
返回指向函數的指針
和數組類似(參見6.3.3節,第205頁),雖然不能返回一個函數,但是能返回指向函數類型的指針。然而,我們必須把返回類型寫成指針形式,編譯器不會自動地將函數返回類型當成對應的指針類型處理。與往常一樣,要想聲明一個返回函數指針的函數,最簡單的辦法是使用類型別名:
using F=int(int*,int);//F是函數類型,不是指針
using PF=int(*)(int*,int);//PF 是指針類型
其中我們使用類型別名(參見2.51節,第60頁)將F定義成函數類型,將PF定義成指向函數類型的指針。必須時刻注意的是,和函數類型的形參不一樣,返回類型不會自動地轉換成指針。我們必須顯式地將返回類型指定為指針:
PF f1(int);//正確:PF是指向函數的指針,f1返回指向函數的指針
F fl(int);//錯誤:F是函數類型,f1不能返回一個函數
F *f1(int);//正確:顯式地指定返回類型是指向函數的指針
當然,我們也能用下面的形式直接聲明f1:
int (*fl(int))(int*,int);
按照由內向外的順序閱讀這條聲明語句:我們看到f1有形參列表,所以f1是個函數:f1前面有 *,所以f1返回一個指針;進一步觀察發現,指針的類型本身也包含形參列表,因此指針指向函數,該函數的返回類型是int。
出于完整性的考慮,有必要提醒讀者我們還可以使用尾置返回類型的方式(參見6.3.3節,第206頁)聲明一個返回函數指針的函數:
auto fl(int)->int(*)(int*,int);
將 auto 和 decltype 用于函數指針類型
如果我們明確知道返回的函數是哪一個,就能使用decltype 簡化書寫函數指針返回類型的過程。例如假定有兩個函數,它們的返回類型都是string::size_type,并且各有兩個 const string&類型的形參,此時我們可以編寫第三個函數,它接受一個string類型的參數,返回一個指針,該指針指向前兩個函數中的一個:
string::size_type sumlength(const string&,const string&);
string::size_type largerlength(const string&,const string&);
//根據其形參的取值,getFcn函數返回指向sumLength或者largerLength的指針
decltype(sumLength)*getFcn(const string &);
聲明 getFcn 唯一需要注意的地方是,牢記當我們將 decltype 作用于某個函數時,它返回函數類型而非指針類型。因此,我們顯式地加上* 以表明我們需要返回指針,而非函數本身。
重新學一遍這些內容:
在 C++ 中,函數指針是一種特殊的指針類型,它指向函數而非對象。函數指針在實現回調機制、動態調用函數等方面有著重要的應用。以下從定義、聲明、使用方法、應用場景等方面進行詳細介紹:
定義和聲明
函數指針的聲明需要指定函數的返回類型、參數列表,語法格式如下:
返回類型 (*指針名)(參數列表);
下面是一個簡單的示例:
#include <iostream>
// 定義一個函數
int add(int a, int b)
{return a + b;
}
int main()
{// 聲明一個函數指針,指向返回類型為 int,參數為兩個 int 類型的函數int (*funcPtr)(int, int);// 將函數指針指向 add 函數funcPtr = add;return 0;
}
在上述代碼中,int (*funcPtr)(int, int); 聲明了一個函數指針 funcPtr,它可以指向返回類型為 int,參數為兩個 int 類型的函數。然后將其指向了 add 函數。
使用函數指針調用函數
通過函數指針調用函數的方式和直接調用函數類似,使用 (*指針名) 或者直接使用指針名來調用函數,示例如下:
#include <iostream>
// 定義一個函數
int add(int a, int b)
{return a + b;
}
int main()
{// 聲明一個函數指針,指向返回類型為 int,參數為兩個 int 類型的函數int (*funcPtr)(int, int);// 將函數指針指向 add 函數funcPtr = add;// 使用函數指針調用函數int result = (*funcPtr)(3, 5); // 方式一// 或者 int result = funcPtr(3, 5); // 方式二std::cout << "Result: " << result << std::endl;return 0;
}
函數指針作為參數傳遞
函數指針可以作為參數傳遞給其他函數,這樣可以實現回調機制,使得函數的行為更加靈活。示例如下:
#include <iostream>
// 定義一個函數
int add(int a, int b)
{return a + b;
}int subtract(int a, int b)
{return a - b;
}
// 一個接受函數指針作為參數的函數
int calculate(int (*operation)(int, int), int a, int b)
{return operation(a, b);
}int main()
{int num1 = 10, num2 = 5;// 調用 calculate 函數,傳遞 add 函數指針int result1 = calculate(add, num1, num2);std::cout << "Addition result: " << result1 << std::endl;// 調用 calculate 函數,傳遞 subtract 函數指針int result2 = calculate(subtract, num1, num2);std::cout << "Subtraction result: " << result2 << std::endl;return 0;
}
在上述代碼中,calculate 函數接受一個函數指針 operation 作為參數,通過這個函數指針可以動態地選擇不同的操作(加法或減法)。
函數指針數組
可以創建函數指針數組,將多個函數指針存儲在數組中,方便根據需要選擇調用不同的函數。示例如下:
#include <iostream>
// 定義幾個函數
int add(int a, int b)
{return a + b;
}int subtract(int a, int b)
{return a - b;
}int multiply(int a, int b)
{return a * b;
}int main()
{// 聲明一個函數指針數組int (*funcArray[3])(int, int) = {add, subtract, multiply};int num1 = 10, num2 = 5;// 遍歷函數指針數組并調用函數for (int i = 0; i < 3; ++i) {int result = funcArray[i](num1, num2);std::cout << "Result of operation " << i + 1 << ": " << result << std::endl;}return 0;
}
注意事項
函數簽名匹配:函數指針的返回類型和參數列表必須與所指向的函數完全匹配,否則會導致編譯錯誤。
指針空值檢查:在使用函數指針之前,最好檢查其是否為 nullptr,避免調用空指針導致程序崩潰。例如:
if (funcPtr != nullptr)
{int result = funcPtr(3, 5);
}
返回指向函數的指針
聲明返回指向函數的指針時,語法較為復雜,一般形式如下:
返回類型 (*函數名(參數列表))(返回類型, 參數列表);
下面逐步解釋這個復雜的聲明:
最外層的 函數名(參數列表) 是一個普通的函數聲明,表示這是一個函數,括號內是該函數的參數列表。
(*函數名(參數列表)) 表明這個函數返回的是一個指針。
最后的 (返回類型, 參數列表) 是該指針所指向的函數的參數列表,而整個聲明的開頭的 返回類型 是該指針所指向的函數的返回類型。
#include <iostream>
// 定義幾個簡單的函數
int add(int a, int b)
{return a + b;
}int subtract(int a, int b)
{return a - b;
}// 返回指向函數的指針的函數
int (*getOperation(int choice))(int, int)
{if (choice == 1) {return add;} else {return subtract;}
}int main()
{int choice;std::cout << "Enter 1 for addition, 2 for subtraction: ";std::cin >> choice;// 獲取函數指針int (*operation)(int, int) = getOperation(choice);int num1 = 10, num2 = 5;int result = operation(num1, num2);std::cout << "Result: " << result << std::endl;return 0;
}
在上述代碼中:
add 和 subtract 是兩個簡單的函數,分別實現加法和減法操作。
getOperation 函數根據用戶輸入的 choice 值,返回指向 add 或 subtract 函數的指針。
在 main 函數中,調用 getOperation 函數獲取函數指針,然后使用該指針調用相應的函數進行計算。
使用 typedef 簡化聲明
由于返回指向函數的指針的聲明語法較為復雜,使用 typedef 可以簡化聲明。示例如下:
#include <iostream>
// 定義幾個簡單的函數
int add(int a, int b)
{return a + b;
}int subtract(int a, int b)
{return a - b;
}
// 使用typedef簡化函數指針類型的聲明
typedef int (*Operation)(int, int);
// 返回指向函數的指針的函數,使用簡化后的類型
Operation getOperation(int choice)
{if (choice == 1) {return add;} else {return subtract;}
}int main()
{int choice;std::cout << "Enter 1 for addition, 2 for subtraction: ";std::cin >> choice;// 獲取函數指針Operation operation = getOperation(choice);int num1 = 10, num2 = 5;int result = operation(num1, num2);std::cout << "Result: " << result << std::endl;return 0;
}
空指針檢查:在使用返回的函數指針之前,最好檢查其是否為 nullptr,避免調用空指針導致程序崩潰。例如:
Operation operation = getOperation(choice);
if (operation != nullptr)
{int result = operation(num1, num2);
}