一.函指針概述
函數(shù)的作用:完成某一特定功能的代碼塊。
指針的作用:一種特殊的變量,用來保存地址值,某類型的指針指向某類型的地址。
下面定義了一個求兩個數(shù)最大值的函數(shù):
int maxValue (int a, int b) {
return a > b ? a : b;
}
這段代碼編譯后生成的CPU指令存儲在代碼區(qū),而這段代碼其實是可以獲取其地址的,而其地址就是函數(shù)名,我們可以使用指針存儲這個函數(shù)的地址——函數(shù)指針。
函數(shù)指針其實就是一種特殊的指針——指向一個函數(shù)的指針。在很多高級語言中,它的思想是很重要的,尤其是它的“回調(diào)函數(shù)”。
二.函數(shù)指針定義與使用
任何變量定義都包含三部分: 變量類型 + 變量名 = 初值,那么定義一個函數(shù)指針,首先我們需要知道要定義一個什么樣的函數(shù)指針(指針類型),那么問題來了,函數(shù)的類型又是什么呢?我們繼續(xù)分析這段代碼:
int maxValue (int a, int b) {
return a > b ? a : b;
}
這個函數(shù)的類型是有兩個整型參數(shù),返回值是個整型。對應(yīng)的函數(shù)指針類型:
int (*) (int a, int b)
對應(yīng)的函數(shù)指針定義:
int (*p)(int x, int y);
參數(shù)名可以去掉,并且通常都是去掉的。這樣指針p就可以保存函數(shù)類型為兩個整型參數(shù),返回值是整型的函數(shù)地址了。
int (*p)(int, int);
通過函數(shù)指針調(diào)用函數(shù):
int (*p)(int, int) = NULL;
p = maxValue;
p(20, 45);
三.回調(diào)函數(shù)
現(xiàn)在我們有這樣一個需求:實現(xiàn)一個函數(shù),將一個整形數(shù)組中比50大的打印在控制臺,我們可能這樣實現(xiàn):
void compareNumberFunction(int *numberArray, int count, int compareNumber) {
for (int i = 0; i < count; i++) {
if (*(numberArray + i) > compareNumber) {
printf("%d\n", *(numberArray + i));
}
}
}
int main() {
int numberArray[5] = {15, 34, 44, 56, 64};
int compareNumber = 50;
compareNumberFunction(numberArray, 5, compareNumber);
return 0;
}
這樣實現(xiàn)是沒有問題的,然而現(xiàn)在我們又有這樣一個需求:實現(xiàn)一個函數(shù),將一個整形數(shù)組中比50小的打印在控制臺。"What the fuck!"對于提需求者,你可能此時的心情是這樣:

然而回到現(xiàn)實,這種需求是不可避免的,你可能想過復(fù)制粘貼,更改一下判斷條件,然而作為開發(fā)者,我們要未雨綢繆,要考慮到將來可能添加更多類似的需求,那么你將會有大量的重復(fù)代碼,使你的項目變得臃腫,所以這個時候我們需要冷靜下來思考,其實這兩個需求很多代碼都是相同的,只要更改一下判斷條件即可,而判斷條件我們?nèi)绾巫兊酶屿`活呢?這時候我們就用到回調(diào)函數(shù)的知識了,我們可以定義一個函數(shù),這個函數(shù)需要兩個int型參數(shù),函數(shù)內(nèi)部實現(xiàn)代碼是將兩個整形數(shù)字做比較,將比較結(jié)果的bool值作為函數(shù)的返回值返回出來,以大于被比較數(shù)字的情況為例:
BOOL compareGreater(int number, int compareNumber) {
return number > compareNumber;
}
同理,小于被比較的數(shù)字函數(shù)定義如下:
BOOL compareLess(int number, int compareNumber) {
return number < compareNumber;
}
接下來,我們可以將這個函數(shù)作為compareNumberFunction的一個參數(shù)進(jìn)行傳遞(沒錯,函數(shù)可以作為參數(shù)),那么我們就需要一個函數(shù)指針獲取函數(shù)的地址,從而在compareNumberFunction內(nèi)部進(jìn)行對函數(shù)的調(diào)用,于是,compareNumberFunction函數(shù)的定義變成了這樣:
void compareNumberFunction(int *numberArray, int count, int compareNumber, BOOL (*p)(int, int)) {
for (int i = 0; i < count; i++) {
if (p(*(numberArray + i), compareNumber)) {
printf("%d\n", *(numberArray + i));
}
}
}
具體使用時代嗎如下:
int main() {
int numberArray[5] = {15, 34, 44, 56, 64};
int compareNumber = 50;
// 大于被比較數(shù)字情況:
compareNumberFunction(numberArray, 5, compareNumber,compareGreater);
// 小于被比較數(shù)字情況:
compareNumberFunction(numberArray, 5, compareNumber, compareLess);
return 0;
}
根據(jù)上述案例,我們可以得出結(jié)論:函數(shù)回調(diào)本質(zhì)為函數(shù)指針作為函數(shù)參數(shù),函數(shù)調(diào)用時傳入函數(shù)地址,這使我們的代碼變得更加靈活,可復(fù)用性更強(qiáng)。
四.動態(tài)排序
需求: 有30個學(xué)生需要排序
按成績排
按年齡排
…
這種無法預(yù)測的需求變更,就是我們上文說的動態(tài)場景,那么解決方案就是函數(shù)回調(diào):
typedef struct student{
char name[20];
int age;
float score;
}Student;
//比較兩個學(xué)生的年齡
BOOL compareByAge(Student stu1, Student stu2) {
return stu1.age > stu2.age ? YES : NO;
}
//比較兩個學(xué)生的成績
BOOL compareByScore(Student stu1, Student stu2) {
return stu1.score > stu2.score ? YES : NO;
}
void sortStudents(Student *array, int n, BOOL(*p)(Student, Student)){
Student temp;
int flag = 0;
for (int i = 0; i < n - 1 && flag == 0; i++) {
flag = 1;
for (int j = 0; j < n - i - 1; j++) {
if (p(array[j], array[j + 1])) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
flag = 0;
}
}
}
}
int main() {
Student stu1 = {"小明", 19, 98};
Student stu2 = {"小紅", 20, 78};
Student stu3 = {"小白", 21, 88};
Student stuArray[3] = {stu1, stu2, stu3};
sortStudents(stuArray, 3, compareByScore);
return 0;
}
五.函數(shù)指針作為函數(shù)返回值
沒錯,既然函數(shù)指針可以作為參數(shù),自然也可以作為返回值。
需求:定義一個函數(shù),通過傳入功能的名稱獲取到對應(yīng)的函數(shù)。

整理一下發(fā)現(xiàn),然后我們分析下需求,當(dāng)前我們需要定義一個叫做findFunction的函數(shù),這個函數(shù)傳入一個字符串之后會返回一個
int (*)(int, int)類型的函數(shù)指針,那么我們這個函數(shù)的聲明是不是可以寫成這樣呢?
int (*)(int, int) findFunction(char *);
//這看起來很符合我們的理解
//然而,這并不正確
//編譯器無法識別兩個完全并行的包含形參的括號(int, int)和(char *)
//真正的形式其實是這樣:
int (*findFunction(char *))(int, int);
這種聲明從外觀上看更像是臉滾鍵盤出來的結(jié)果,現(xiàn)在讓我們來逐步的分析一下這個聲明的組成步驟:
findFunction是一個標(biāo)識符
findFunction()是一個函數(shù)
findFunction(char *)函數(shù)接受一個類型為char *的參數(shù)
*findFunction(char *)函數(shù)返回一個指針
(*findFunction(char *))()這個指針指向一個函數(shù)
(*findFunction(char *))(int, int)指針指向的函數(shù)接受兩個整形參數(shù)
int (*findFunction(char *))(int, int)指針指向的函數(shù)返回一個整形
現(xiàn)在我們的分析已經(jīng)完成了,編譯器可以通過了,現(xiàn)在程序員瘋了,這對我們來說就像鯡魚罐頭一樣難以下咽,那么我們是不是有更好的書寫方式呢?(老司機(jī)友情提示:typedef)
最終代碼演變成了這樣:
// 重定義函數(shù)指針類型
typedef int (*FUNC)(int, int);
// 求最大值函數(shù)
int maxValue(int a, int b) {
return a > b ? a : b;
}
// 求最小值函數(shù)
int minValue(int a, int b) {
return a < b ? a : b;
}
// findFunction函數(shù)定義
FUNC findFunction(char *name) {
if (0 == strcmp(name, "max")) {
return maxValue;
} else if (0 == strcmp(name, "min")) {
return minValue;
}
printf("Function name error");
return NULL;
}
int main() {
int (*p)(int, int) = findFunction("max");
printf("%d\n", p(3, 5));
int (*p1)(int, int) = findFunction("min");
printf("min = %d\n", p1(3, 5));
return 0;
}
到了這里,函數(shù)指針的內(nèi)容已經(jīng)結(jié)束了,有的同學(xué)還有可能困惑,為什么我要以函數(shù)去獲取函數(shù)呢,直接使用maxValue和minValue不就好了么,其實在以后的編程過程中,很有可能maxValue和minValue被封裝了起來,類的外部是不能直接使用的,那么我們就需要這種方式,如果你學(xué)習(xí)了Objective-C你會發(fā)現(xiàn),所有的方法調(diào)用的實現(xiàn)原理都是如此。
補(bǔ)充:strcmp
原型:int strcmp(const char *s1, const char *s2);
頭文件:#include <string.h>
功能:用來比較兩個字符串
參數(shù):s1、s2為兩個進(jìn)行比較的字符串
返回值:若s1、s2字符串相等,則返回零;若s1大于s2,則返回大于零的數(shù);否則,則返回小于零的數(shù)。
說明:strcmp()函數(shù)是根據(jù)ACSII碼的值來比較兩個字符串的;strcmp()函數(shù)首先將s1字符串的第一個字符值減去s2第一個字符,若差值為零則繼續(xù)比較下去;若差值不為零,則返回差值。