內容提要
- 指針
- 函數指針與指針函數
- 二級指針
指針
函數指針與指針函數
函數指針
定義
函數指針本質上是指針,是一個指向函數的指針。函數都有一個入口地址,所謂指向函數的指針,就是指向函數的入口地址。(這里的函數名就代表入口地址)
函數指針存在的意義:
- 讓函數多了一種調用方式
- 函數指針可以作為形式,可以形式調用(回調函數)
語法:
返回值類型(*指針變量名)(形參列表);
舉例
int (*p)(int a, int b);
函數指針的初始化
①定義的同時賦值
// 函數指針需要依賴于函數,先有函數,后有指針// 定義一個普通函數
// 普通函數
int add(int a, int b){return a + b};// 定義一個函數指針,并初始化
// 觀察,函數指針的返回類型和指向函數的返回類型一致,函數的形參列表個數、類型、順序跟指向函數的形參列表一致
int (*p)(int a, int b) = add; // 函數指針p指向函數add,這里的add不能帶(),add就是該函數的入口地址
②先定義后賦值
// 函數指針需要依賴于函數,先有函數,后有指針// 定義一個普通函數
// 普通函數
int add(int a, int b){return a + b};// 定義一個函數指針,并初始化
// 觀察,函數指針的返回類型和指向函數的返回類型一致,函數的形參列表個數、類型、順序跟指向函數的形參列表一致
int (*p)(int, int) = add; //形參列表的參數名可以省略p = add; //此時是將add的入口地址賦值給指針
注意:
1.函數指針指向的函數要和函數指針定義的返回值類型,形參列表對應,否則編譯報錯2.函數指針是指針,但不能指針運算,如p++等,沒有實際意義
3.函數指針作為形參,可以形成回調
4.函數指針作為形參,函數調用時的實參只能是與之對應的函數名,不能帶小括號()
5.函數指針的形參列表中的變量名可以省略
注意:函數不能作為函數的形參,但是指向函數的函數指針是可以作為函數的形參的。
案例
- 需求:求a,b兩個數的最大值
- 代碼:
/**
* 定義一個函數,求兩個數的最大值
**/
int ger_max(int a, int b)
{return a > b ? a : b;
}int main()
{// 定義測試數據int a = 3, b = 4, max;// 直接調用函數max = get_max(a,b);printf("%d,%d中的最大值是%d\n",a,b,max);// 定義一個函數指針int (*p)(int,int) = get_max;// 間接調用函數:方式1max = p(a,b); //直接將指針名作為函數名printf("%d,%d中的最大值是%d\n",a,b,max);// 間接調用函數:方式2max = (*p)(a,b); //直接將指針名作為函數名printf("%d,%d中的最大值是%d\n",a,b,max);return 0;
}
回調函數
定義
回調函數就是一個通過函數指針調用的函數。如果你把函數的指針作為參數傳遞給另一個函數,當這個指針備用來調用其所指向的函數時。我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應。
為什么要用回調函數
因為可以把調用者與被調用者分開,所以調用者不關心誰是被調用者。它只需知道存在一個具有特定原型和限制條件的被調用函數。
簡而言之,回調函數就是允許用戶把需要調用的方法的指針作為參數傳遞給一個函數,以便該函數在處理相似事件的時候可以靈活的使用不同的方法。
/**
*回調函數1
*/
int callback_1(int a)
{printf("hello, this is callback 1:a=%d\n", a),return a;
}/**
*回調函數2
*/
int callback_2(int b)
{printf("hello, this is callback_2:b=%d\n", b);return b;
}/**
*實現回調函數(函數的參數是函數指針)
*/
int handle(int x, int(*callback)(int))
{printf("日志:開始執行任務!\n");int res = callback(x);printf("日志:執行結果:%d\n", res);printf("日志:結束執行任務!\n");
}int main(int argc,char *argv[])
{handle(100,callback_1);handle(200,callback_2); return 0;
}
指針函數
定義
本質上是函數,這個函數的返回值類型是指針,整個函數稱之為指針函數。
int *p
:普通指針。int (*p)[3]
:數組指針。int *p[]
:指針數組。int (*p)()
:函數指針。int *p()
:指針函數。
語法:
// 寫法1
數據類型* 函數名(形參列表)
{函數體;return 指針變量;
}
// 寫法2
數據類型 *函數名(形參列表)
{函數體;return 指針變量;
}
舉例:
int *get(int a)
{int *p = &a;return 0;
}
int main()
{int *a= get(5);printf("%d\n",*a);
}
注意:
在函數中不要直接返回一個局部變量的地址,因為函數調用完畢后,局部變量會被回收,使得返回的地址就不明確,此時返回的指針就是野指針。
解決方案:
如果非要訪問,可以給這個局部變量添加(定義的時候添加)static
,可以延長它的生命周期,從而避免野指針(盡量少用,因為存在內存泄漏)
演示案例:
int *add(int a, int b)
{static int sum; // 使用static修飾局部變量,會提升生命周期,但是作用域不會發生改變,不建議sum = a + b;return ∑ // 執行完return 作為函數作用域的布局變量sum的空間被釋放
}int main()
{int *res = add(5,3); // 接收到了地址,但是地址對應的空間已經被釋放printf("%d\n", *res);return 0;
}
案例
- 需求:有若干個學生,每個學生有4門成績,要求在用戶輸入學號(int id)后,能輸出該學生的全部成績(float scores[4]),用指針函數實現。
/**
* 定義一個函數,要求輸入學生序號,返回該學生所有成績
* @param all:所有人的成績
* @param id:要檢索學生的序號
* @return id:對應血行的成績數組(指針)
*/
float* search(float (*all)[4], int id)
{// 定義一個指針變量,用來接受查詢到的某個學生的所有成績float *pt;pt = *(all + id); //行偏移return pt; // 賦值運算中 float pt[4] == float *pt;
}int main()
{// 準備一個二維數組,存儲3個學生的成績float scores[][4] = {{60,70,80,90},{66,77,88,99},{61,71,81,91}};// 定義一個變量,用來接收學生序號int m;printf("請輸入學生序號(0~2):\n");scanf("%d", &m);printf("第%d個學生的成績:\n", m);// 創建一個指針,用來接收成績float *p = search(scores, m);// 遍歷成績for(; p < scores[m] + 4; p++){printf("%5.2f\t", *p);}printf("\n");return 0;
}
二級指針
定義
二級指針(多重指針)用于存儲一級指針的地址,需要兩次解引用才能訪問原始數據,其他多級指針的用法類似,實際開發中最常見的多級指針是二級指針
int a = 10; // a是普通變量
int *p = &a; // 一級指針(p指向a,p存儲的是a的地址)
int **w = &p; // 二級指針(w指向p,w存儲的是p的地址)
int ***x = &w // 三級指針(x指向w,x存儲的是w的地址)
語法
數據類型 **指針變量名 = 指針數組的數組名 | 一級指針的地址
特點
①與指針數組的等效性二級指針與指針數組等效性,但與二維數組不等效。二維數組名是數組指針類型,如int (*)[3]
,而非二級指針。
// 指針數組
int arr[] = {11,22,33};
int* arr_[] = {&arr[0], &arr[1], &arr[2]}; // 正確的指針數組的定義// 二級指針接受指針數組
char* str[3] = {"abc","aaa034","12a12"}; // str存儲的是三個字符串的首地址
char **p = str;
②與二維數組的差異二維數組名是數組指針類型,直接賦值給二級指針會導致類型不匹配
int arr[2][3] = {{1,3,5}{11,33,55}};
int (*p)[3] = arr; //arr這個數組名就是數組指針類型int **k = arr; // 編譯報錯,arr類型 int(*)[3] 不兼容 k類型 int**
解引用
①字符型二級指針可直接遍歷字符串數組,類似一維數組
void fun1()
{// 定義一個字符類型的指針數組char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i <len; i++){printf("%s\n",arr[i]);}printf("\n");
}void fun2()
{char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);// 二級指針等效于指針數組char **p = arr;for(int i = 0; i < len; i++){printf("%s\n",p[i]); //下標法printf("%s\n",*(p+i)); //指針法}printf("\n");
}void fun3()
{char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);// 二級指針等效于指針數組char **p;// 定義循環變量int i = 0;// 遍歷指針數組do{p = arr + i; // p = arr ----> p = arr + 0printf("%s\n", *p);i++;}while(i < len);
}int main()
{fun1();fun2();fun3();return 0;
}
②其他類型的二級指針需要兩次解引用訪問數據,常用于操作指針數組
int main()
{// 普通的一維數組int arr1[] = {11,22,33,44,55,66};// 創建一個指針數組int *arr[] = {&arr1[0],&arr1[1],&arr1[2],&arr2[3],&arr1[4],&arr1[5]};// 用一個二級指針接收指針數組int **p = arr;// 遍歷數組for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%-6d", *p[i]); //下標法printf("%-6d", **(p + i)); // 指針法 等價于*(*(p+i))}pritnf("\n");return 0;
}
總結
①二級制指針與指針數組等效,可簡化指針數組的遍歷操作
②二維數組名是數組指針類型(如: int(*)[3]
),與二級指針( int**)
類型不兼容。
③操作非字符型二級指針時,須通過兩次解引用訪問實際數據。