目錄
1、函式指標
1.2、兩段有趣代碼
2、函式指標陣列
2.2、用途(轉移表)->計算器
3、指向函式指標陣列的指標
4、回呼函式
4.1、回顧冒泡排序
4.2、使用qsort函式
4.3、使用回呼函式,模擬實作qsort(采用冒泡的方式)
1、函式指標
- 簡要概念:既然陣列指標是指向陣列的指標,那么函式指標就是指向函式的指標,用來存放函式地址的一個指標,
- 我們先寫一個加法函式,并且列印出其地址看看,
#include<stdio.h> int Add(int x, int y) { return x + y; } int main() { printf("%p\n", &Add); printf("%p\n", Add); //&函式名 和 函式名 都是函式的地址 return 0; }
不難發現,&函式名 和 函式名 都是函式的地址
- 當我們拿到函式地址了,該怎么存起來呢?先回顧下陣列地址的存法:
int arr[10] = { 0 }; int (*p)[10] = &arr;
- 仿照陣列指標的寫法,寫一下函式指標:
#include<stdio.h> int Add(int x, int y) { return x + y; } int main() { int (*pa)(int, int) = Add; printf("%d\n", (*pa)(2, 3)); // 5 return 0; }
- 不同函式的地址存起來定義的指標也不相同
#include<stdio.h> void Print(char* str) { printf("%s\n", str); } int main() { void(*p)(char*) = Print; (*p)("hello bit"); p("hello bit"); return 0; }
- 下面解釋下為什么在存的時候*p要加括號,
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪個有能力存放test函式的地址? void (*pfun1)(); void *pfun2();首先,能給存盤地址,就要求pfun1或者pfun2是指標,那哪個是指標?
答案是:
pfun1可以存放,這里涉及到優先級的問題,因為在不加括號的情況下,pfun1會先和右邊的括號也就是函式相結合,后和*結合,那就不是指標了,繼而存不了地址,所以要在*pfun1先整個用()括起來,確保pfun1先和*結合,說明pfun1是指標,指標指向的是一個函式,指向的函式無引數,回傳值型別為void,
- 注意:
#include<stdio.h> int Add(int x, int y) { return x + y; } int main() { int (*pa)(int, int) = Add; printf("%d\n", (*pa)(2, 3)); // 5 printf("%d\n", (**pa)(2, 3)); // 5 printf("%d\n", (***pa)(2, 3)); // 5 printf("%d\n", (pa)(2, 3)); // 5 printf("%d\n", Add(2, 3)); // 5 return 0; }如果pa是個函式指標,我去呼叫這個函式的時候,我可以解參考,也可以不解參考,*號可以看成一個擺設
1.2、兩段有趣代碼
//代碼1 (*(void (*)())0)(); //代碼2 void (*signal(int , void(*)(int)))(int);
- 解釋代碼1:
(*(void (*)())0)();
void (*)() -> 函式指標型別. 0前面有個括號,(void (*)())0,把0進行強制型別轉換,此時0就是一個函式地址,此時再加個*進行解參考,再加一個括號去呼叫函式,不需要傳參,因為指向的函式是無參的 總結:把0進行強制轉換成函式指標型別,該指標指向的函式是無參,回傳型別是void,當0變成一個函式地址時,對它進行解參考操作,去呼叫以0為地址的該函式,- 解釋代碼2:
void (*signal(int, void(*)(int)))(int);
- 可以將上述代碼用typedef關鍵字進行簡化:
typedef void(*pfun_t)(int);//就是說將void(*)(int)整個換為*pfun_t
- 但是萬萬不可寫成:
typedef void(*)(int) pfun_t; // err
- 此時上述代碼就可優化為:
pfun_t signal(int, pfun_t);
- 總結:
- signal是一個函式宣告,
- signal函式的引數有2個,第一個是int,第二個是函式指標,該函式指標指向的函式的引數是int,回傳型別是void
- signal函式的回傳型別也是一個函式指標,該函式指標指向的函式的引數是int,回傳型別是void
2、函式指標陣列
- 前面我們學習到了指標陣列
#include<stdio.h> int Add(int x, int y) {} int Sub(int x, int y) {} int Mul(int x, int y) {} int Div(int x, int y) {} int main() { // 指標陣列 int* arr[5]; //陣列的每個元素是int* int(*pa)(int, int) = Add;// 存了Add,也可以存Sub,Mul,Div return 0; }這里我們創建了4個函式Add、Sub、Mul、Div,然后創建了函式指標來存放Add函式的地址,如果想要存其它的,只需要將Add換成其它的就可,但若都想存起來,則要創建4個不同的函式指標,繼而有4個不同的變數,但duck不必這么麻煩,只需要用到函式指標陣列即可:
//需要一個陣列,這個陣列可以存放4個函式的地址 - 函式指標的陣列 int(*parr[4])(int, int) = { Add,Sub,Mul,Div };//函式指標陣列
- 重新回顧下函式指標陣列定義程序:
int *arr[10]; int (*parr1[10])();parr1 先和 [ ] 結合,說明 parr1是陣列,陣列的內容是什么呢?
是 int (*)() 型別的函式指標,
- 練習:
char* my_strcpy(char* dest, const char* src);
- 1.寫一個函式指標 pf ,能夠存放my_strcpy
char* (*pf)(char*, const char*);
- 2.寫一個函式指標陣列 pfArr ,能夠存放4個my_strcpy函式的地址
char* (*pfArr[4])(char*, const char*);
2.2、用途(轉移表)->計算器
- 先寫一個不用函式指標陣列版本的簡易計算器:
#include<stdio.h> void menu() { printf("****************************************\n"); printf("***** 1. add 2. sub *****\n"); printf("***** 3. mull 4. div *****\n"); printf("***** 0. exit *****\n"); printf("****************************************\n"); } int Add(int x, int y) { return (x + y); } int Sub(int x, int y) { return (x - y); } int Mull(int x, int y) { return (x * y); } int Div(int x, int y) { return (x / y); } int main() { int input = 0; int x = 0; int y = 0; do { menu(); printf("請選擇\n"); scanf("%d", &input); switch (input) { case 1: printf("請輸入兩個運算元:>\n"); scanf("%d %d", &x, &y); printf("%d\n", Add(x, y)); break; case 2: printf("請輸入兩個運算元:>\n"); scanf("%d %d", &x, &y); printf("%d\n", Sub(x, y)); break; case 3: printf("請輸入兩個運算元:>\n"); scanf("%d %d", &x, &y); printf("%d\n", Mull(x, y)); break; case 4: printf("請輸入兩個運算元:>\n"); scanf("%d %d", &x, &y); printf("%d\n", Div(x, y)); break; case 0: printf("退出\n"); break; default: printf("選擇錯誤\n"); break; } } while (input); return 0; }
- 上述代碼過于繁瑣,可以使用函式指標陣列進行簡化
- 函式指標陣列的用途->轉移表
int main() { int input = 0; int x = 0; int y = 0; // pfArr是一個函式指標陣列,用途->轉移表 int(*pfArr[])(int, int) = { 0 ,Add,Sub,Mull,Div }; do { menu(); printf("請選擇\n"); scanf("%d", &input); if (input >= 1 && input <= 4) { printf("請輸入運算元\n"); scanf("%d %d", &x, &y); int ret = pfArr[input](x, y); printf("%d\n", ret); } else if(input == 0) { printf("退出\n"); } else { printf("選擇錯誤\n"); } } while (input); return 0; }主體定義函式的內容跟上份代碼一樣,這里就先省略,直接寫出主要使用函式指標陣列的內容,使用函式指標陣列就可以不需要再使用這么多次的switch case陳述句了,避免了代碼的過多重復性
- 注意:
- 在上上份的switch case法計算器中,有一段代碼出現多次:
printf("請輸入兩個運算元:>\n"); scanf("%d %d", &x, &y);
- 解決方法如下:需要用到回呼函式,這里簡單使用下,后續會詳細介紹,
#include<stdio.h> void menu() { printf("***** 1. add 2. sub *****\n"); } int Add(int x, int y) { return (x + y); } int Sub(int x, int y) { return (x - y); } void Calc(int(*pf)(int, int)) { int x = 0; int y = 0; printf("請輸入兩個運算元:>"); scanf("d %d", &x, &y); printf("%d\n", pf(x, y)); } int main() { int input = 0; do { menu(); printf("請選擇\n"); scanf("%d", &input); switch (input) { case 1: Calc(Add); break; case 2: Calc(Sub); break; default: printf("選擇錯誤\n"); break; } } while (input); return 0; }
- 這里簡單拿Add和Sub這兩個函式舉例子,后續會詳細介紹回呼函式,
3、指向函式指標陣列的指標
- 代碼解釋:
#include<stdio.h> int Add(int x, int y) { return x + y; } int main() { int arr[10] = { 0 }; //陣列指標p int(*p)[10] = &arr; //函式指標pf int(*pf)(int, int); //函式指標陣列pfArr int(*pfArr[4])(int, int); //函式指標陣列指標ppfArr int(*(*ppfArr)[4])(int, int) = &pfArr; //ppfArr首先和*結合說明是指標,指標指向的是陣列,陣列有4個元素,每個元素的型別是函式指標 /*正解: ppfArr是一個陣列指標,指標指向的陣列有4個元素 指向的陣列的每個元素的型別是一個函式指標 int(*)(int,int)*/ return 0; }
4、回呼函式
- 概念:
回呼函式就是一個通過函式指標呼叫的函式,如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回呼函式,回呼函式不是由該函式的實作方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用于對該事件或條件進行回應,
4.1、回顧冒泡排序
void bubble_sort(int arr[], int sz) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; for (j = 0; j < sz - 1 - i; j++) { if(arr[j]>arr[j+1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } #include<stdio.h> int main() { int arr[10] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); //0 1 2 3 4 5 6 7 8 9 } return 0; }
- 我們都知道冒泡排序是專門用來排序整形資料的,那么浮點型資料和結構體該如何排序呢?
#include<stdio.h> struct stu { char name[20]; int age; }; int main() { struct stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} }; float f[] = { 9.0,8.0,7.0,6.0,5.0,4.0 }; return 0; }
- 此時就引出萬能函式qsort
4.2、使用qsort函式
- qsort - 庫函式 - 排序
- 演算法思想: 快速排序 quick sort
- 格式:
void qsort( void* base, ---> 目標陣列 arr size_t num, ---> 待排序元素的個數 n size_t width,---> 元素的位元組大小 int(* cmp)(const void* elem1, const void* elem2) ---> 函式比較 cmp_int );
- 講解下void*
void*型別的指標 可以接收任意型別的地址,且不報警告
- 正文:
#include<stdio.h> #include<stdlib.h> #include<string.h> int cmp_int(const void* e1, const void* e2) { //比較整形值 return *(int*)e1 - *(int*)e2; } void test1() { int arr[10] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } } int cmp_float(const void* e1, const void* e2) { // 比較浮點數值 /*if (*(float*)e1 == *(float*)e2) return 0; else if (*(float*)e1 > *(float*)e2) return 1; else return -1;*/ // 或者int強轉 return (int)(*(float*)e1 - *(float*)e2); } void test2() { float f[] = { 9.0,8.0,7.0,6.0,5.0,4.0 }; int sz = sizeof(f) / sizeof(f[0]); qsort(f, sz, sizeof(f[0]), cmp_float); int j = 0; for (j = 0; j < sz; j++) { printf("%.1f ", f[j]); } } struct stu { char name[20]; int age; }; int cmp_stu_by_age(const void* e1, const void* e2) { // 比較年齡 return ((struct stu*)e1)->age - ((struct stu*)e2)->age; } void test3() { struct stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_age); } int cmp_stu_by_name(const void* e1, const void* e2) { // 比較名字就是比較字串 // 字串比較不能直接用><=來比較,應該用strcmp函式 return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name); } void test4() { struct stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_name); } int main() { //test1(); //排序整形陣列 //test2(); //排序浮點數 //test3(); //排序結構體 通過年齡比較 test4(); //排序結構體 通過名字比較 return 0; }
4.3、使用回呼函式,模擬實作qsort(采用冒泡的方式)
#include<stdio.h> struct stu { char name[20]; int age; }; void Swap(char* buf1, char* buf2, int width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base, int sz, int width, int(*cmp)(void* e1, void* e2)) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; for (j = 0; j < sz - 1 - i; j++) { //兩個元素比較 if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { //交換 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } int cmp_int(const void* e1, const void* e2) { //比較整形值 return *(int*)e1 - *(int*)e2; } int cmp_stu_by_age(const void* e1, const void* e2) { // 比較年齡 return ((struct stu*)e1)->age - ((struct stu*)e2)->age; } int cmp_stu_by_name(const void* e1, const void* e2) { // 比較名字就是比較字串 // 字串比較不能直接用><=來比較,應該用strcmp函式 return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name); } void test5() { int arr[10] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_int); } void test6() { struct stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} }; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age); } void test7() { struct stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} }; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name); } int main() { //test5(); //排序整形陣列 //test6(); //排序結構體 通過年齡比較 test7(); //排序結構體 通過名字比較 return 0; }
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/413431.html
標籤:其他

