函数指针
先看一段代码:
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
结果:
0000000000401550
0000000000401550
我们可以看到函数名可以表示成地址,那么对于函数的地址,我们就可以用函数指针来存储。
void test() { printf("hehe\n"); } void (*pfun1)();
星号先与变量名结合,表示指针,后面加上小括号就表示指针指向的是函数的地址。
当函数里面有参数时,我们也需要跟着写进去。
void test(char* pc, int arr[10]) { } int main() { void (*pf)(char *, int [10]) = test; return 0; }
只需要写数据类型即可,可以不跟变量名。
下面看一下函数指针的用例:
int Add(int x, int y) { return x + y; } int main() { int (*pf)(int, int) = Add; int r = Add(3, 5); printf("%d\n", r); int m = (*pf)(4, 5); printf("%d\n", m); return 0; }
8
9
这里我们可以直接调用函数的地址,对它进行解引用,还需要对其函数进行传参,就能实现一个函数的调用;这里*和pf需要用下括号括起来,**倘若没有下括号,那么pf会先与(4,5)先结合,**那么星号在这里就会变成无效的引用,程序将会报错。
函数指针数组
这是一个存放函数地址的数组;
int (*parr1[10])();
我们可以先写出一个函数指针int(*parr1)(),然后通过优先级将【】与parr1结合,就变成了函数指针数组。
下面我们用计算器这个例子,分别用switch和函数指针数组进行对比;
#include <stdio.h> int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a*b; } int div(int a, int b) { return a / b; } int main() { int x, y; int input = 1; int ret = 0; do { printf( "*************************\n" ); printf( " 1:add 2:sub \n" ); printf( " 3:mul 4:div \n" ); printf( "*************************\n" ); printf( "请选择:" ); scanf( "%d", &input); switch (input) { case 1: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = add(x, y); printf( "ret = %d\n", ret); break; case 2: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = sub(x, y); printf( "ret = %d\n", ret); break; case 3: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = mul(x, y); printf( "ret = %d\n", ret); break; case 4: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = div(x, y); printf( "ret = %d\n", ret); break; case 0: printf("退出程序\n"); break; default: printf( "选择错误\n" ); break; } } while (input); return 0; }
这是用switch写出来的计算器,可以实现加减乘除的功能,但我们会发现,倘若我们要对该程序进行补充功能时,那么就得重复写case中的语句,当然我们可以直接粘贴复制,但我们看起来就会比较冗余,认为重复的地方出现过太多次了;
函数指针数组的方式:
#include <stdio.h> int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a*b; } int div(int a, int b) { return a / b; } int main() { int x, y; int input = 1; int ret = 0; int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表 while (input) { printf( "*************************\n" ); printf( " 1:add 2:sub \n" ); printf( " 3:mul 4:div \n" ); printf( "*************************\n" ); printf( "请选择:" ); scanf( "%d", &input); if ((input <= 4 && input >= 1)) { printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = (*p[input])(x, y); } else { printf( "输入有误\n" ); printf( "ret = %d\n", ret); } } return 0; }
我们先在主函数创建一个函数指针数组int(*p[5])(int x, int y) = { 0, add, sub, mul, div };调用时,只需要对指针p解引用,输入正确的下标和参数,就能实现不必要的冗余。
指向函数指针数组的指针
void test(const char* str) { printf("%s\n", str); } int main() { //函数指针pfun void (*pfun)(const char*) = test; //函数指针的数组pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; //指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[5])(const char*) = &pfunArr; return 0; }
对于这么长的描述,我们可以从函数指针下手,然后拓展到函数指针数组,最后用指针指向函数指针数组即可,我们在写的时候要注意,各符号的优先级,当有[]和星号时,如果没有小括号将变量名和星号括起来,那么它最终就是一个数组,且星号放在变量名前面,[]放在变量名后面,不能随意乱放。
总的来说,对于数组和指针,我们可以组成很长数组指针数组,对于这种写法,我们重点在于要学会看懂怎么读,懂得各符号的优先级,明白哪个与哪个相结合,拆开分解后就一目了然了。
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
qsort函数各类的使用
void qsort (void* base, size_t num, size_t size,
int (compar)(const void,const void*))
这是一个快排函数,我们可以看到,它的参数包含函数指针,当我们调用这个函数时,必须对函数指针指向的函数进行调用完,该函数才得以实现,表明qsort函数是一个回调函数。下面我们来看看qsort函数各种类型数据的使用。
整型类型数据:
#include <stdio.h> int int_cmp(const void * p1, const void * p2) { return (*( int *)p1 - *(int *) p2); } void test1() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; int i = 0; qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp); for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++) { printf( "%d ", arr[i]); } printf("\n"); } int main() { test1(); return 0; }
结果:0 1 2 3 4 5 6 7 8 9
在这里,对于qsort函数的最后一个参数,需要我们自己创建一个符合条件的函数,使它对应的返回值能够判断不同元素的大小即可。
浮点类型的:
int float_cmp(const void* p1,const void* p2) { if((*(float*)p1-*(float*)p2)>0.000000) { return 1; } else if((*(float*)p1-*(float*)p2)<0.000000) { return -1; } else { return 0; } } void test2() { float arr[]={3.14,2.56,6,78,5.32,4.02}; int i=0; qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(float),float_cmp); for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++) { printf( "%.2f ", arr[i]); } printf("\n"); } int main() { // test1(); test2(); return 0; }
2.56 3.14 4.02 5.32 6.00 78.00
在这里,对于最后一个参数,浮点数由于具有精度,函数类型为整型,所以需要与具有精度的0进行判断,所以需要加上条件判断语句。倘若使用第一种方法的强制换类型,那么将会丢失精度。
字符串:
//字符串的大小 int str_cmp_size(const void* p1,const void* p2) { return strcmp(*(char**)p1,*(char**)p2); } //字符串的长度 int str_cmp_len(const void* p1,const void* p2) { return strlen(*(char**)p1)-strlen(*(char**)p2); } void test3() { char* arr[]={"abc","VPN","RTx","Rtx","rtxbbb","abbb"}; int i=0; qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(arr[0]),str_cmp_len); for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++) { printf( "%s ", arr[i]); } printf("\n"); } int main() { // test1(); //test2(); test3(); return 0; }
大小:RTx Rtx VPN abbb abc rtxbbb
长度:VPN RTx Rtx abc abbb rtxbbb
在这里,为何不直接调用(char*)p1,而要用*(char**),这是因为如果用(char*)的话,对于形参p1来说,存的是arr数组名的地址,strcmp()会将p地址对应的内容转换为字符串,这就不符合我们的想法,所以应该是将数组中对应的字符串的地址传过去,再对其解引用,这样才能得到真正的字符串。
struct Stu { char name[20]; int age; }; int cmp_stu_by_age(const void* p1, const void* p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } void test4() { struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); for (int i = 0; i< sizeof(arr) / sizeof(arr[0]); i++) { printf( "%d ", arr[i].age); } printf("\n"); } int cmp_stu_by_name(const void* p1, const void* p2) { return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name); } void test5() { struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu",15}}; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); for (int i = 0; i< sizeof(arr) / sizeof(arr[0]); i++) { printf( "%s ", arr[i].name); } printf("\n"); } int main() { // test1(); //test2(); //test3(); test4(); test5(); return 0; }
15 20 50
lisi wangwu zhangsan
对于结构体的使用,要用结构体指针,指向对应的比较变量,就能得到最终答案。
模拟实现sqort函数
在这里,我们用的是冒泡排序实现sqort函数的思想。
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; } } } }
这是一个整型类型的冒泡排序,当我们要用到浮点型,结构体类型时,就无法使用该函数,所以,我们根据sqort函数的参数进行模拟实现。
我们可以对上面函数进行改装,首先不能改变冒泡排序的思想,也就是两层循环不变,而我们要改变的是,对于不同类型的比较方式,还有不同类型是怎么交换的。
对于不同类型的比较,我们用到sqort的第四个参数,我们要解决的是怎么传参?我们要清楚,我们传的是每个元素的地址,不同类型意味着它们每个元素所占字节不一样,那么我们可以利用元素的大小乘上对应的j,就能实现元素地址的传递了。
(cmp((char*)base+jsize, (char)base+(j+1)size)>0)
我们用char强制类型转换,因为char*指针移动时,刚好是一个字节一个字节过去的;
对于转换,我们已经知道每个不同类型元素的地址了,我们只需要交换它们的地址即可,对于char*类型的,我们可以一个字节一个字节循环转换;那么就写一个交换函数来进行实现;
void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素 { int i = 0; char tmp = 0; for (i = 0; i < size; i++) { tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } }
原代码:
void Swap(char* buf1, char* buf2, int size) { int i = 0; char tmp = 0; for (i = 0; i < size; i++) { tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*)) { int i = 0; for (i = 0; i < num - 1; i++) { int j = 0; for (j = 0; j < num - 1 - i; j++) { if (cmp((char*)base+j*size, (char*)base+(j+1)*size)>0) { Swap((char*)base + j * size, (char*)base + (j + 1) * size, size); } } } }
这就是利用冒泡排序实现sqort函数的思想,对于如何使用,使用方法和上面的sqort函数是一模一样的,这里就不在过多展示。