目录
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 回调函数
前言:
在C语言:指针详解【进阶】前篇中我们深入学习了
字符指针
,数组指针
,指针数组
以及数组传参和指针传参
。我们对指针的应用有了较为深刻的认识,今天这里我们将更加深入的进行对更复杂的指针的探究。
函数指针
在前面我们知道一个指针变量可以指向一块内存的地址,我们也知道函数在使用时也要向内存申请一块内存空间,那我们不妨想一想我们能不能创建一个指针变量来指向一个函数呢?
我们先来看看函数的地址到底是什么?
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
test();
printf("%p\n", &test);
printf("%p\n", test);
return 0;
}
这里我们分别打印&test
和test
的值,结果两者相同,那我们可以认为&test
和test
都代表的是函数test的地址,这里是两者没有区别的。
当我们知道函数的地址后,我们就可以创建一个指针来指向这个函数了,但问题是函数的形式有很多种,我们的函数指针的数据类型该如何定义呢?
这里的定义其实与数组指针的定义是类似的,在数组指针的定义时,我们把数组指针的数组名取出来后,剩下的就是数组的数据类型了。
那么这里我们把函数的函数名取出来,那剩下的就是函数的数据类型,这样就可以来定义函数指针了。
函数指针在使用时可以先解引用指针pf,再在后边带上参数就行:
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int , int) = &Add;
//int (*pf)(int x, int y) = &Add;
//int (*pf)() = &Add;
//这三种定义方法都可行
int ret1 = (*pf)(3, 5);
printf("%d\n", ret1);
return 0;
}
我们前面得知&test
和test
都代表的是函数test的地址,这里是两者没有区别的。那么这里我们的函数指针的使用就还有另一种方式,不用解引用pf,直接使用:
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int , int) = &Add;
int ret1 = (*pf)(3, 5);
printf("%d\n", ret1);
int ret2 = pf(3, 5);
printf("%d\n", ret2);
return 0;
}
其实这里的语句 int ret1 = (*pf)(3, 5);
中的*
就是一个摆设,没有实质性的用途,这里的*
只是为了帮助我们来理解这个语句的。
所以这里我们可以任意的增加和删减
*
, 都是不影响结果的。
但是注意:如果你要使用*
,那就必须把*
和pf
用()
括起来。
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int , int) = &Add;
int ret1 = (*****pf)(3, 5);
printf("%d\n", ret1);
return 0;
}
当我们学会函数指针后,你可能会疑惑,函数指针的使用场景是什么?我们为何不直接调用函数来使用呢?
其实函数指针本质是一个指针,我们指针的使用场景就是将数据传递到另一个函数中去使用,那当我们需要传递一个函数作为参数到另一个函数中时,就需要用到函数指针了。
这里关于函数指针的使用就放在回调函数的板块讲解了。
这里我们分析两个有趣
的的函数指针:
int main()
{
//代码1
(*(void(*)())0)();
//代码2
void (*signal(int, void(*)(int)))(int);
return 0;
}
我们将代码1进行分解一下就好理解了:
代码1其实就是一次函数调用。
我们再对代码2进行分解:
代码2其实就是一次函数声明。
其实对于代码2,可以进行优化一下,使我们能更好的读懂代码;
这里我们要使用一个自定义关键词typedef
(重命名)来操作。
我们将这个函数的返回值void(*)(int)
进行重命名为ptr_t
来进行简化代码。
//typedef void (*)(int) ptr_t; //错误写法
typedef void (*ptr_t)(int);
int main()
{
ptr_t signal(int, void(*)(int));
return 0;
}
注意:
在对返回值是函数指针的类型重命名时,新名字要放在函数指针的内部,不能放在后边,这样才符合语法。
函数指针数组
前面我们学习了指针数组,该数组内部可以放置相同类型的指针,我们今天又学习了函数指针,那我们是否可以创建一个函数指针数组来存放函数指针呢?答案是可以的。
函数指针数组的定义与指针数组的定义是一样的,都要有:数组元素类型,数组名,数组元素个数。
void test1()
{}
void test2()
{}
void test3()
{}
void test4()
{}
int main()
{
void (*pf1)() = &test1;
void (*pf2)() = &test2;
void (*pf3)() = &test3;
void (*pf4)() = &test4;
void *parr[4]() = { pf1,pf2,pf3,pf4 };
void (*)() parr[4] = { pf1,pf2,pf3,pf4 };
void (*parr[4])() = { pf1,pf2,pf3,pf4 };
//这三种函数指针数组的定义写法,哪种是正确的?
return 0;
}
这里和函数指针的声明类似,变量名要紧挨着*
,只是这里变量名先和[]
结合,作为一个数组。
所以这里的第三种定义是正确的。
void (*parr[4])() = { pf1,pf2,pf3,pf4 };
关于函数指针数组的用途:
我们知道函数指针数组中存放的是统一类型的函数指针,那么我们对于某一类数据进行相似操作时,就可以使用到函数指针数组来方便的调用一些函数了,同时有助于减少相似代码的书写,简化程序。
这里就举例实现一个简易计算器来说明函数指针数组的用途:
这里是函数的编写及头文件的声明:
#include <stdio.h>
void menu()
{
printf("*********************************\n");
printf("******* 1.Add 2.Sub *******\n");
printf("******* 3.Mul 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 Mul(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("请选择:> ");
scanf("%d", &input);
switch (input)
{
case 0:
{
printf("退出计算器\n");
break;
}
case 1:
{
printf("请输入操作数:> ");
scanf("%d %d", &x, &y);
printf("%d\n", Add(x, y));
break;
}
case 2:
{
printf("请输入操作数:> ");
scanf("%d %d", &x, &y);
printf("%d\n", Sub(x, y));
break;
}
case 3:
{
printf("请输入操作数:> ");
scanf("%d %d", &x, &y);
printf("%d\n", Mul(x, y));
break;
}
case 4:
{
printf("请输入操作数:> ");
scanf("%d %d", &x, &y);
printf("%d\n", Div(x, y));
break;
}
default:
{
printf("输入错误,请重新输入!\n");
}
}
} while (input);
return 0;
}
版本二(使用函数指针数组):
int main()
{
int input = 0;
int x = 0;
int y = 0;
int (*parr[5])(int, int) = { NULL, Add, Sub, Mul, Div };
do
{
menu();
printf("请选择:> ");
scanf("%d", &input);
if(0 < input && input < 5)
{
printf("请输入操作数:> ");
scanf("%d %d", &x, &y);
printf("%d\n", parr[input](x, y));
}
else if (input == 0)
{
printf("退出计算器\n");
break;
}
else
{
printf("输入错误,请重新输入!\n");
}
} while (input);
return 0;
}
两者的代码一比较就可以发现,使用函数指针数组后,代码中的重复代码大大下降,代码整体简洁清晰了不少。
这里就体现了函数指针数组的用途:转移表
指向函数指针数组的指针
我们刚刚学习了函数指针数组,我们是否可以用一个指针来指向这个数组?那这个指向函数指针数组的指针又该如何定义呢?
#include <stdio.h>
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;//&test 和 test 是一样的
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
return 0;
}
这里的定义其实与指针的定义是类似的,在数组指针的定义时,我们把数组指针的数组名取出来后,剩下的就是数组的数据类型了。
那么这里我们把函数指针数组的函数名取出来,那剩下的就是函数指针数组的数据类型,这样就可以来定义函数指针数组的指针了。
#include <stdio.h>
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;//&test 和 test 是一样的
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
回调函数
回调函数就是一个通过函数指针调用的函数。
就是上面所说的使用函数指针进行传参的应用。
解释:
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
这里我们来进行分析一个qsort函数
(库函数),进行对回调函数的了解。
这里我们需要先去了解一下qsort函数
, 明白这个函数是什么用处,又该如何使用?
我们打开cplusplus网站,进行搜索。
得知qsort函数
是一个排序的函数。
注意:它可以排序任意类型的数据。
函数使用:
在使用qsort函数
时,我们需要只知道要排序的第一个元素的地址,要排序元素的个数,每个元素的大小,以及一个能比较两个元素的大小的函数。
这里我们唯一要设计的就是编写一个能比较两个元素的大小的函数。
注意:设计的函数要和库里给定的该比较函数模板格式要一致。
#include <stdio.h>
#include <stdlib.h>
int cmp_int(const int* p1, const int* p2)
{
return *p1 - *p2;
}
int main()
{
int arr[10] = { 7,6,1,2,8,9,3,5,0,4 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(int), cmp_int);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这里我们设计的cmp_int函数
就是回调函数。
讲到这里指针详解【进阶】后篇的知识讲解就结束了。
关于指针的知识是重点,也是难点,不仅仅是知识的了解,更要进行大量的练习才能巩固知识。
这几天我会出一期关于指针进阶习题的练习和讲解,来进行加深对指针的更进一步的记忆。
同时对于qsort函数
,我们不仅仅只是会使用它,还要学会去自己实现一个qsort函数
。同样会放在指针进阶习题的练习和讲解文章之后马上更新。
感兴趣的的小伙伴点点赞,点点关注,谢谢大家的阅读哦!!!
点点关注,后期不错过哦。😘
你们的鼓励就是我的动力,欢迎下次继续阅读!!!😘😘😘