文章目錄
- 數組指針/指針數組
- 函數指針
- 函數指針數組
- 函數指針數組用途(轉移表)
- 回調函數
- qsort函數
- 基于qsort改造冒泡排序
- 源碼
數組指針/指針數組
int arr1[5] = { 1,2,3,4,5 };int (*p1)[5] = &arr1; //p1是數組指針變量int* arr2[5] = { 0 }; //arr2是指針數組
指針數組是存放指針的數組,本質是數組。
數組指針是指向數組的指針,本質是指針。
數組指針類型:(去掉變量名,剩下的就是類型)
int (*)[5] //數組指針類型
函數指針
函數名本身就是函數指針。
int Add(int x, int y)
{return x + y;
}int (*pf)(int, int) = &Add; //pf就是存放函數地址(指針)的變量,函數指針變量
函數指針類型和數組指針類型相似,如下:
int (*)(int, int) //函數指針類型
通過函數指針調用函數:
int x = (*pf)(10, 20);
printf("%d\n", x);
因為函數名本身就是函數地址,所以前面的&Add 和 調用函數時的(*p) 中的取地址符和*不加也可以達到一樣的效果。
函數指針數組
int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}int (*padd)(int, int) = &Add;
int (*psub)(int, int) = ⋐
int (*pmul)(int, int) = &Mul;
int (*pdiv)(int, int) = &Div;
// 類比 int arr[5]
int (*pfarr[4])(int, int) = { padd ,psub,pmul,pdiv };
//下面這樣這可以
int (*pfarr[4])(int, int) = { Add, Sub, Mul, Div };
上面的pfarr就是函數指針數組的變量名,函數指針數組最好在函數指針變量的基礎上改造得到,也就是在函數指針變量的變量名后面加[ ]。
函數指針數組的理解可以類比普通的數組,比如 int
arr[5],一個存放5個整型變量的數組,把它的變量名和中括號(arr[5])去掉剩余的就是數組存放變量的類型,放在函數指針數組里也一樣,把pfarr[4]去掉剩余的int(*)(int, int)就是數組里存放的變量類型。
函數指針數組用途(轉移表)
我們先來看下面實現的一個簡易計算器:
void menu()
{printf("********************\n");printf("*** 1.add 2.sub ***\n");printf("*** 3.mul 4.div ***\n");printf("*** 0.exit ***\n");printf("********************\n");
}int input = 0;
int a = 0;
int b = 0;
int r = 0;do{menu();printf("請選擇:");scanf("%d", &input);switch (input){case 1:printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = Add(a, b);printf("r = %d\n", r);break;case 2:printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = Sub(a, b);printf("r = %d\n", r);break;case 3:printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = Mul(a, b);printf("r = %d\n", r);break;case 4:printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = Div(a, b);printf("r = %d\n", r);break;case 0:printf("退出計算器\n");break;default:printf("選擇錯誤,重新選擇\n");break;}} while (input);
我們可以看到雖然可以實現計算器的功能,但是代碼非常冗長,并且如果要增加更多運算函數的話還會增加更多的case,想要簡化代碼就可以用函數指針數組,把函數存放在函數指針數組里,想要調用函數直接下標訪問這個函數指針數組就行了,這里我們想要讓數組下標和菜單選擇數字對應的話就需要在數組開頭增加一個空指針,讓下標依次向后挪一位。
int (*fparr[])(int, int) = {NULL,Add,Sub,Mul,Div};do{menu();printf("請選擇:");scanf("%d", &input);if (input > 0 && input <= 4){printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);//fparr[input]是數組下標訪問函數對象printf("r = %d\n", fparr[input](a, b)); }else if (input == 0){printf("退出計算器\n");break;}else{printf("選擇錯誤,重新選擇\n");}}while(input);
這樣的方法又名轉移表。
回調函數
回調函數就是?個通過函數指針調?的函數。
如果你把函數的指針(地址)作為參數傳遞給另?個函數,當這個指針被?來調?其所指向的函數時,被調?的函數就是回調函數。回調函數不是由該函數的實現?直接調?,?是在特定的事件或條件發?時由另外的??調?的,?于對該事件或條件進?響應。
補充:至于回調函數為什么只能傳函數指針類型而不能直接傳遞函數類型,因為這是C語言語法特性決定的,就算傳遞的是函數類型,編譯器也會將它隱式轉化為函數指針類型。
下面我們舉個例子:
void calc(int (*pf)(int, int))
{int a = 0;int b = 0;int r = 0;printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = pf(a, b);printf("r = %d\n", r);
}int input = 0;do{menu();printf("請選擇:");scanf("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;case 0:printf("退出計算器\n");break;default:printf("選擇錯誤,重新選擇\n");break;}} while (input);
之前我們實現的計算器switch-case部分非常冗余,有很多重復的代碼,case1-4的四行代碼只有調用運算函數那一行有區別,但是我們直接把代碼封裝成函數那又需要寫四個函數,所以這里就可以用回調函數。
首先寫一個回調函數calc,參數是運算函數對應的函數指針,所以每一個case只用去調用calc函數,根據不同的函數指針參數再在calc函數體中調用對應的運算函數。
qsort函數
qsort函數是C語言的一個庫函數,它是基于快速排序算法實現的,這個函數可以用來排序任意類型數據。
我們來分析一下它的四個參數:
其中compar函數需要我們重點關注,這是我們自己設計的比較函數,C標準對這個函數是有約定的:
compar函數返回值有三類,當參數p1指向的元素大于參數p2指向的元素時,返回大于0的數字,當參數p1指向的元素等于于參數p2指向的元素時,返回0,參數p1指向的元素小于參數p2指向的元素時,返回小于0的數字。
因為qsort默認是排升序的,所以我們按照上面的規定實現比較函數傳給qsort后最后結果為升序,如果要排降序就需要實現比較函數時把大小于號反號。
下面我們就來使用一下qsort,比較函數需要我們自己實現:
int cmp_int(const void* e1, const void* e2)
{//e1 e2 是void*,需要強制類型轉換后才能使用(解引用)return *(int*)e1 - *(int*)e2;
}void test02()
{int arr[] = { 4, 1, 2, 3, 6, 8, 7 };size_t sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_int);for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}
}
比較結構體類型數據也是可以的:
typedef struct stu
{char name[20];size_t age;
}stu;cmp_struct_by_name(const void* e1, const void* e2)
{return strcmp(((stu*)e1)->name, ((stu*)e2)->name);
}cmp_struct_by_age(const void* e1, const void* e2)
{return ((stu*)e1)->age - ((stu*)e2)->age;
}void test03()
{stu arr[] = { {"jiyi", 7}, {"xiaoba", 8}, {"wusaqi", 6}, };size_t sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_struct_by_age);
}
按名字比較時需要調用strcmp函數,我們查文檔可以看到strcmp函數的返回值正好和我們要實現的比較函數邏輯一致,所以比較函數直接返回strcmp函數的返回值就行了。
基于qsort改造冒泡排序
我們以前實現的冒泡排序只能排整型數據,我們想讓它適配更多類型的數據,就需要對它進行改造,這里模擬qsort的方式對我們自己的冒泡排序進行改造,我們先分析需要改造哪些地方:
一、首先是參數,我們要比較不同類型數據就需要按照qsort的參數實現方式進行傳遞。
第一個參數傳遞數組首元素的地址,有了地址才能知道待排序數據在哪里和第一個元素的地址,方便以第一個元素的地址為基準指針加數字訪問到數組后面的元素(要配合第三個參數使用,因為第一個參數的類型的void*,無法直接加減數字操作,也無法確定強制類型轉換的類型)。
第二個參數傳遞數組的元素個數,用于確定循環躺數。
第三個參數傳遞單個元素的單位大小,因為無法直接傳遞參數類型,所以傳遞單個元素的單位大小來間接確定元素類型。因為首元素地址是void*,無法直接以首元素地址為基準指針加數字訪問到數組后面的元素,這里有一個思路就是將首元素地址強轉成char*,用它加單位數乘以第三個參數大小就可以訪問到單位位置的元素,比如(int*)base + j * (width) 就可以訪問到指向int數組的第j個元素的指針,然后再把指針作為參數傳給對應的比較函數,在比較函數內部再對指針解引用訪問數據大小并比較。
第四個參數是我們自己實現的比較函數。
二、然后是比較函數部分傳遞我們自己實現的比較函數。
三、最后是swap,因為不知道數據的類型,所以不能直接交換,在前面參數部分已經將首元素指針類型強轉成了char*,不如將計就計,在交換部分還是以char類型來交換,不管你的數據類型大小有多少字節,以char類型形式一個字節一個字節的交換,一共交換width次。
int cmp_int(const void* e1, const void* e2)
{//e1 e2 是void*,需要強制類型轉換后才能使用 return *(int*)e1 - *(int*)e2;
}swap(char* p1, char* p2, int width)
{for (width; width > 0; width--){char tmp = *p1;*p1 = *p2;*p2 = tmp;p1++;p2++;}
}Bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{int flag = 1; //默認有序for (int i = 0; i < sz - 1; i++) //躺數{for (int j = 0; j < sz - 1 - i; j++){//(char*)arr + j * widthif (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){swap((char*)base + j * width, (char*)base + (j + 1) * width, width);flag = 0;}}if (flag == 1)break;}
}print(int* arr , int sz)
{for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}void test04()
{int arr[] = { 4, 1, 2, 3, 6, 8, 7 };size_t sz = sizeof(arr) / sizeof(arr[0]);Bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);print(arr, sz);
}
源碼
p1:
#include <stdio.h>int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}
void test01()
{int arr1[5] = { 1,2,3,4,5 };int (*p1)[5] = &arr1; //p1是數組指針變量//int (*)[5] //數組指針類型int* arr2[5] = { 0 }; //arr2是指針數組int (*pf)(int, int) = &Add; //pf就是存放函數地址(指針)的變量,函數指針變量//int (*)(int, int) //函數指針類型int x = (*pf)(10, 20);printf("%d\n", x);
}void test02()
{int (*padd)(int, int) = &Add;int (*psub)(int, int) = ⋐int (*pmul)(int, int) = &Mul;int (*pdiv)(int, int) = &Div;// 類比 int arr[5]int (*pfarr[4])(int, int) = { Add, Sub, Mul, Div };
}void menu()
{printf("********************\n");printf("*** 1.add 2.sub ***\n");printf("*** 3.mul 4.div ***\n");printf("*** 0.exit ***\n");printf("********************\n");
}void test03()
{int input = 0;int a = 0;int b = 0;int r = 0;do{menu();printf("請選擇:");scanf("%d", &input);switch(input){case 1:printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = Add(a, b);printf("r = %d\n", r);break;case 2:printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = Sub(a, b);printf("r = %d\n", r);break;case 3:printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = Mul(a, b);printf("r = %d\n", r);break;case 4:printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = Div(a, b);printf("r = %d\n", r);break;case 0:printf("退出計算器\n");break;default:printf("選擇錯誤,重新選擇\n");break;}} while (input);
}void test04()
{int input = 0;int a = 0;int b = 0;int r = 0;int (*fparr[])(int, int) = {NULL,Add,Sub,Mul,Div};do{menu();printf("請選擇:");scanf("%d", &input);if (input > 0 && input <= 4){printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);printf("r = %d\n", fparr[input](a, b));}else if (input == 0){printf("退出計算器\n");break;}else{printf("選擇錯誤,重新選擇\n");}}while(input);//do//{// menu();// printf("請選擇:");// scanf("%d", &input);// switch (input)// {// case 1:// printf("請輸入兩個操作數:");// scanf("%d %d", &a, &b);// r = Add(a, b);// printf("r = %d\n", r);// break;// case 2:// printf("請輸入兩個操作數:");// scanf("%d %d", &a, &b);// r = Sub(a, b);// printf("r = %d\n", r);// break;// case 3:// printf("請輸入兩個操作數:");// scanf("%d %d", &a, &b);// r = Mul(a, b);// printf("r = %d\n", r);// break;// case 4:// printf("請輸入兩個操作數:");// scanf("%d %d", &a, &b);// r = Div(a, b);// printf("r = %d\n", r);// break;// case 0:// printf("退出計算器\n");// break;// default:// printf("選擇錯誤,重新選擇\n");// break;// }//} while (input);
}void calc(int (*pf)(int, int))
{int a = 0;int b = 0;int r = 0;printf("請輸入兩個操作數:");scanf("%d %d", &a, &b);r = pf(a, b);printf("r = %d\n", r);
}void test05()
{int input = 0;do{menu();printf("請選擇:");scanf("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;case 0:printf("退出計算器\n");break;default:printf("選擇錯誤,重新選擇\n");break;}} while (input);
}int main()
{//test01();//test02();//test03();//test04();test05();return 0;
}
p2:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>//Bubble_sort(int arr[], int sz)
//{
// int flag = 1; //默認有序
// for (int i = 0; i < sz - 1; i++) //躺數
// {
// for (int 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;
// flag = 0;
// }
//
// }
// if (flag == 1)
// break;
// }
//}
//test01()
//{
// int arr[] = { 4, 1, 2, 3, 6, 8, 7 };
// Bubble_sort(arr, 7);
// for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
// {
// printf("%d ", arr[i]);
// }
//}int cmp_int(const void* e1, const void* e2)
{//e1 e2 是void*,需要強制類型轉換后才能使用 return *(int*)e1 - *(int*)e2;
}//print(int* arr)
//{
// for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
// {
// printf("%d ", arr[i]);
// }
//}//void test02()
//{
// int arr[] = { 4, 1, 2, 3, 6, 8, 7 };
// size_t sz = sizeof(arr) / sizeof(arr[0]);
// qsort(arr, sz, sizeof(arr[0]), cmp_int);
//
// for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
// {
// printf("%d ", arr[i]);
// }
//}typedef struct stu
{char name[20];size_t age;
}stu;cmp_struct_by_name(const void* e1, const void* e2)
{return strcmp(((stu*)e1)->name, ((stu*)e2)->name);
}cmp_struct_by_age(const void* e1, const void* e2)
{return ((stu*)e1)->age - ((stu*)e2)->age;
}//void test03()
//{
// stu arr[] = { {"jiyi", 7}, {"xiaoba", 8}, {"wusaqi", 6}, };
// size_t sz = sizeof(arr) / sizeof(arr[0]);
// qsort(arr, sz, sizeof(arr[0]), cmp_struct_by_age);
//}swap(char* p1, char* p2, int width)
{for (width; width > 0; width--){char tmp = *p1;*p1 = *p2;*p2 = tmp;p1++;p2++;}
}Bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{int flag = 1; //默認有序for (int i = 0; i < sz - 1; i++) //躺數{for (int j = 0; j < sz - 1 - i; j++){//(char*)arr + j * widthif (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){swap((char*)base + j * width, (char*)base + (j + 1) * width, width);flag = 0;}}if (flag == 1)break;}
}//void test_func(void* p1)
//{
// p1[1];
//}print(int* arr , int sz)
{for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}void test04()
{int arr[] = { 4, 1, 2, 3, 6, 8, 7 };size_t sz = sizeof(arr) / sizeof(arr[0]);Bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);print(arr, sz);
}int main()
{//test01();//test02();//test03();test04();return 0;
}
以上就是小編分享的全部內容了,如果覺得不錯還請留下免費的關注和收藏 如果有建議歡迎通過評論區或私信留言,感謝您的大力支持。
一鍵三連好運連連哦~~