目录
6. 函数指针数组
6.1简单计算器
6.2函数指针数组实现计算器
7. 指向函数指针数组的指针(仅作了解即可)
8.回调函数
8.1关于回调函数的理解编辑
8.1.1用回调函数改良简单计算器
8.2qsort库函数的使用
8.2.1冒泡排序
8.2.2qsort的概念
8.3冒泡排序思想实现qsort
这篇文章包括但不限于函数指针数组,指向函数指针数组的指针,回调函数等知识点的总结。承接着上文指针进阶(1)知识点总结,传送门-- > http://t.csdn.cn/mgVGJ
指针进阶(3):指针和数组笔试题解析总结,传送门--> http://t.csdn.cn/aKVsj
如有错误,欢迎大家指点与纠正,感谢你的来访!
int * arr [ 10 ];//数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int ( * parr1 [ 10 ])();//1int * parr2 [ 10 ]();//2int ( * )() parr3 [ 10 ];//3
答案是:parr1 //1
parr1 先和 [] 结合,说明 parr1 是数组,数组的内容是什么呢?是 int (*)() 类型的函数指针。-->对于一个变量来说去掉它的名字,就是它的类型。
int my_strlen(const char* str)
{
return 0;
}
int main()
{
//指针数组
char* arr[10];
//数组指针
int arr2[5] = { 0 };
int(*p)[5] = &arr2;//p是一个指向数组的指针变量
//函数指针
int (*pf)(const char*) = my_strlen;//pf是一个指向函数的函数指针变量
//函数指针数组-存放函数指针的数组
int (*pfArr[10])(const char*);
//1.pf先与[10]结合,代表这是一个数组,数组有10个元素;
//2.去掉pf[10],剩余int (*)(int,int)为数组每个元素的类型--函数指针类型
return 0;
}
以下三种写法均等价
1. *(*pf)("abcdef");
2. pf ("abcdef");
3. my_strlen("abcdef");
按照咱们正常的思路,写一个简单的计算器实现整数的加减乘除的功能,应该大致写成这种代码:
那么执行一下,看一下状况如何:
以上出现的问题是,输入0之后,没有立即打印"退出计算器"然后结束程序,而是要继续输入两个操作数之后才打印信息,还顺便打印了个6,这样的代码肯定存在问题,咱们稍微改进一下。
//写一个计算器能完成整数的+ - * /
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 menu()
{
printf("*************************\n");
printf("*** 1.Add 2.Sub **\n");
printf("*** 3.Mul 4.Div **\n");
printf("*** 0.Exit **\n");
printf("*************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do {
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:>");
scanf("%d %d",&x,&y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default :
printf("选择错误\n");
break;
}
}while(input);
return 0;
}
执行代码,继续测试:
问题是解决了,可是又发现新的问题:
以上的代码的switch case语句太多了,当我想要在这个计数器里面增加: << >> & | ^ && ||等功能,case语句的就会越来越多,那应该怎么去纠正呢?
这时候就要用到函数指针数组了。
例子:
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 main()
{
//存放函数指针的数组 - 函数指针数组
int (*pf[4])(int, int) = {Add,Sub,Mul,Div };
// 0 1 2 3
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = pf[i](8, 4);
printf("%d\n", ret);
}
return 0;
}
这段代码使用了函数指针数组,将四个运算函数的地址存储在数组中,然后通过循环遍历数组,依次调用四个运算函数进行计算并输出结果。这种方式可以减少代码的重复量,提高代码的可维护性。
//函数指针数组实现计算器
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 menu()
{
printf("*************************\n");
printf("*** 1.Add 2.Sub **\n");
printf("*** 3.Mul 4.Div **\n");
printf("*** 0.Exit **\n");
printf("*************************\n");
}
int main()
{
int input = 0;//选择菜单的功能
int x = 0;
int y = 0;
int ret = 0;//用于接收值
//转移表 - 函数指针的数组
int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
// 0 1 2 3 4
//NULL的作用是为了占用0下标,然后让函数指针数组的元素下标对应上菜单的功能
do {
//菜单
menu();
//进行选择
printf("请选择:>");
scanf("%d", &input);
//用函数指针数组代替switch
if (input == 0)
{
printf("退出计算器\n");
return 0;
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d %d",&x,&y);
ret = (***pfArr[input])(x, y);//其实比函数指针的用法就多了一个[input]
//跟函数指针一样,调用函数的时候 * 是没有任何意义的
printf("%d\n",ret);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
函数指针数组是一个数组,其中的元素是函数指针。每个函数指针指向一个特定的函数,可以通过函数指针调用相应的函数。 函数指针数组可以用来解决代码冗余的问题,可以方便地遍历、调用不同的函数,从而简化代码的结构和逻辑,提高代码的可维护性和可扩展性。
函数指针数组的用途:转移表
//指向函数指针数组的指针
int Add(int a, int b)
{
return a + b;
}
int Sub(int a, int b)
{
return a - b;
}
int main()
{ //函数指针
int(*pf)(int, int) = Add;
//函数指针数组
int (*pfArr[4])(int, int) = { Add,Sub };
//指向函数指针数组的指针
int (*(*ppfArr)[4])(int,int) = &pfArr;
return 0;
}
如何理解指向函数指针数组的指针:
概念
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
存在着较多逻辑相同,代码冗余的部分。
这时候就需要用到回调函数解决问题了:
#include
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;
}
//将冗余重复部分用Calc函数封装(只用了一个函数),通过这个函数里面传入的函数指针调用计算器函数
void Calc(int(*pf)(int, int))//pf函数指针,传过来哪个函数地址就用哪种类型计算
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
void menu()
{
printf("*************************\n");
printf("*** 1.Add 2.Sub **\n");
printf("*** 3.Mul 4.Div **\n");
printf("*** 0.Exit **\n");
printf("*************************\n");
}
int main()
{
int input = 0;
int ret = 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);
return 0;
}
总结:
回调函数:被作为参数传递的函数,Add、Sub、Mul、Div四个函数就是回调函数,
而Calc函数则是工具人
回顾一下冒泡排序的过程:
怎么去写冒泡排序的代码:
①由元素个数确定趟数②由趟数确定比较对数
③两个元素两两交换排成升序
//冒泡排序
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;
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0};
//排序
//使用
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
print_arr(arr, sz);
return 0;
}
不足:
但是这个冒泡排序的缺点也很明显,就是只能排整型int
如果遇到浮点型、结构体等类型的数据,就排不了,那么怎么可以解决呢?
这时候就要用到qsort了。
qsort-- quicksort
是一个库函数,是用来排序的库函数使用快速排序的方法
qsort的好处是
1.现成的
2.可以排序任意类型的数据
qsort是可以排序任意类型的数据
1.比较2个整数的大小,> < ==
//qsort函数的使用者提供这个函数
//qsort 默认是升序
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
//排成倒序的话,什么都不用动,只需要return *(int*)p2 - *(int*)p1;//调换顺序即可
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
test1()
{
int arr[] = { 3,1,5,2,4,9,8,6,5,7 };
int sz = sizeof(arr) / sizeof(arr[0]);
//使用qsort来排序整型数组,这里就要提供一个比较函数,这个比较函数能够比较2个整数的大小
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
test1();
}
2.比较2个字符串,strcmp -->回顾字符串知识:http://t.csdn.cn/V7E9a
3.比较2个结构体数据(学生:张三、李四)指定比较的标准
回顾结构体对象访问成员的知识:http://t.csdn.cn/DVEVj
//测试qsort 排序结构体数据
struct Stu {
char name[20];
int age;
};
void print_arr1(struct Stu* arr, int sz)//打印年龄
{
int i = 0;
for (i = 0; i < sz; i++)
{
//printf("%d ", (*(arr + i)).age);
//printf("%d ", (arr+i)->age);
printf("%d ", arr[i].age);
}
printf("\n");
}
void print_arr2(struct Stu*arr, int sz)//打印姓名
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ", (arr+i)->name);
//printf("%s ", (*(arr+i)).name);
//printf("%s ", arr[i].name);
}
printf("\n");
}
//按照年龄来比较
int cmp_stu_by_age(const void*p1,const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{
struct Stu s[] = { {"zhangsan",30},{"lisi",25},{"wangwu",50} };
int sz = sizeof(s) / sizeof(s[0]);
//测试按照年龄来排序
print_arr1(s, sz);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
print_arr1(s, sz);
//测试按照名字来排序
/*print_arr2(s, sz);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
print_arr2(s, sz);*/
}
打印年龄:
打印性别:
使用冒泡排序的思想来实现一个类似于qsort这个功能的冒泡排序函数bubble_sort()
各参数组成:
base,num,width,cmp函数,elem1和elem2
接下来说明一下qsort各参数的设计思路:
①为什么base定义为void*类型?
void*是 C 语言中的一种通用指针类型,可以指向任意类型的数据。在 qsort 函数中,由于需要对不同类型的数据进行排序,因此需要使用void*类型的指针,以便能够接受各种类型的数据,base参数是一个指向要排序数组第一个元素的指针。由于它要指向任意类型的数据,所以使用void*是最通用的。
举例:
int a = 10;
void* p = &a;
优点:
无具体类型的指针,所以它可以接收任何类型的地址
缺点:
*p //--> void*的指针不能解引用操作符
p++ //--> 因为不知道是什么数据类型,不能直接++
正确用法:
*(int*)p;//也可以转化成其它类型
②为什么num要设计成size_t?
- size_t 在 C 语言中是一种无符号整数类型,用于表示大小、长度和索引值,使用它表示数组元素个数很合适。
数组元素个数是一个非负的值,使用无符号类型size_t可以避免出现负数,更加合理。
- 使用专门的 size_t 类型比简单的 unsigned int 更能表明这个参数的语义 - 它表示一个大小或长度,而不是一个整数。
③为什么width要设计成size_t
在qsort函数中,width参数表示每个数组元素的大小(以字节为单位)
width表示一个"大小"的概念,使用size_t类型可以更清楚地表达这个参数的语义。
width的单位是字节,size_t通常是无符号整数类型,不会有负数,所以更适合表示正的值。
width需要足够大的范围来表示任意数据类型的大小。size_t类型依赖平台,但通常是机器字大小,能满足大多数数据元素的大小需求。
在访问数组元素时,索引位置需要乘以元素大小才能获得地址偏移量。既然索引是size_t类型,那么两者相乘的结果也应该是size_t类型,以避免溢出问题。
④为什么要设计一个函数指针cmp?
qsort 本身是一个通用的排序函数,不能知道要排序的元素类型,也就无法直接比较两个元素。采用函数指针可以将比较操作的实现留给用户。
通过函数指针,用户可以自行实现针对自己数据类型的比较函数,将具体的比较逻辑封装起来。这提高了qsort的通用性和灵活性。
对于不同的数据类型,比较操作的逻辑可能不同。使用函数指针实现可以避免在qsort中写大量的条件分支逻辑。
函数指针提供了一种扩展机制。如果用户需要改变比较操作的逻辑,只需要传入一个新的函数指针就可以,而不需要改动qsort函数本身。
⑤为什么elem1和elem2类型是const void*类型?
qsort要对任意类型的数据进行排序,所以比较函数需要能处理任意类型的元素。使用void*可以指向任意类型的数据,const用于表示不应修改指针指向的内容。
qsort要对任意类型的数据进行排序,所以比较函数需要能处理任意类型的元素。使用void*可以指向任意类型的数据,const用于表示不应修改指针指向的内容。
在调用qsort时可以直接将数组元素的地址强制转换为const void* 类型传给比较函数,简化调用。
qsort函数本身不需要了解元素具体类型,只要把void*指针传给比较函数,由比较函数转换并解释指针内容即可。
开始模拟实现:冒泡排序大题思路不需要改变,只需要对排序方式进行即可
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
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++;
}
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void bubble_sort(void* base, size_t num, size_t width,
int (*cmp)(const void* p1, const void* p2))
{
//冒泡排序大题思路不需要改变,只需要对排序方式进行即可
//要确定趟数
size_t i = 0;
for (i = 0; i < num - 1; i++)
{
int flag = 0;//假设已经有序了
//一趟冒泡排序的过程
size_t j = 0;
for (j = 0; j < num - 1 - i; j++)
{
//两个相邻的元素比较
//arr[j] arr[j+1]
if (cmp((char*)base + j * width, (char*)base+(j+1)*width)>0)
{
//交换
flag = 0;
Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test3()
{
int arr[] = { 3,1,5,2,4,9,8,6,5,7 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("排序前:");
print_arr(arr, sz);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
printf("排序后:");
print_arr(arr, sz);
}
qsort对于代码的解析:
如何交换数据:
为什么这个地方写成char*?
在bubble_sort函数中,由于不知道base指向的数据类型,因此需要将其强制转换为char*类型,从而让指针的步长为1。这样,在进行指针的加减运算时,就可以根据width参数来控制指针的步长,从而实现对任意数据类型的排序。
注意:width对于char来说是1个字节的意思,但是对于其它类型来说就不是了,width是根据参数不同来设置不同的数据类型,控制指针步长的。
交换的流程:1,2,3,4表示顺序
代码的整体流程:
排序:
通过新定义的冒泡排序排序年龄以及姓名
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;
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
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++;
}
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void bubble_sort(void* base, size_t num, size_t width,
int (*cmp)(const void* p1, const void* p2))
{
//要确定趟数
size_t i = 0;
for (i = 0; i < num - 1; i++)
{
int flag = 0;//假设已经有序了
//一趟冒泡排序的过程
size_t j = 0;
for (j = 0; j < num - 1 - i; j++)
{
//两个相邻的元素比较
//arr[j] arr[j+1]
if (cmp((char*)base + j * width, (char*)base+(j+1)*width)>0)
{
//交换
flag = 0;
Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
}
}
}
}
void print_arr3(struct Stu* s, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", (s + i)->age);
//printf("%s ", (*(arr+i)).name);
//printf("%s ", arr[i].name);
}
printf("\n");
}
void print_arr4(struct Stu* s, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ", (s+i)->name);
//printf("%s ", (*(arr+i)).name);
//printf("%s ", arr[i].name);
}
printf("\n");
}
void test4()
{
struct Stu s[] = { {"zhangsan",30} ,{"lisi",25},{"wangwu",50}};
int sz = sizeof(s) / sizeof(s[0]);
//测试按年龄来排序
/*print_arr3(s, sz);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
print_arr3(s, sz);*/
//测试按照名字来排序
print_arr4(s, sz);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
print_arr4(s, sz);
}
按姓名排序:
按年龄排序
这篇文章到这里就结束了,如有错误欢迎大家指正,然后下来就是这篇关于sizeof和strlen的详细总结:,指针和数组笔试题解析总结,传送门--> http://t.csdn.cn/aKVsj
欢迎大家来访。