提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
函数指针是指向函数的指针变量,它可以用来存储和调用函数的地址。函数指针在C语言中有着广泛的应用,以下是几种常见的用途:
回调函数:函数指针可以作为参数传递给其他函数,使得被调用的函数能够在特定条件或事件发生时,调用传入的函数指针进行回调操作。这样可以实现函数之间的灵活交互和扩展,常见于事件处理、消息传递等场景。
函数指针数组:可以使用函数指针数组来实现多态性或动态分派。通过在数组中存储不同的函数指针,并根据需要调用其中的函数,可以在运行时决定调用哪个函数,从而实现动态切换功能。
函数指针作为返回值:函数指针也可以作为函数的返回值,这样可以根据函数的不同条件或逻辑,在运行时返回不同的函数指针,实现动态的函数调用。
函数指针作为数据结构的成员:函数指针可以作为结构体或对象的成员,用来保存某个特定的函数地址。这样可以实现一些高级的数据结构,如函数回调链表、状态机等,提供更灵活的功能扩展。
动态加载库函数:在程序运行时,可以使用函数指针动态地加载和调用外部的共享库函数。这样可以实现插件式开发、动态扩展等功能。
提示:以下是本篇文章正文内容,下面案例可供参考
一、初步探讨
在C语言中,函数指针的声明和使用可以使用以下格式:
*返回类型 (指针变量名)(参数列表);其中,返回类型是函数的返回类型,指针变量名是函数指针的名称,参数列表是函数的参数列表。
我们先看一段代码,体会一下函数指针的指哪打哪:
#include <stdio.h>
void sayHello() {
printf("Hello!\n");
}
void sayGoodbye() {
printf("Goodbye!\n");
}
int main() {
// 声明一个函数指针,指向无返回值且无参数的函数
void (*functionPtr)();
// 将函数指针指向sayHello函数
functionPtr = sayHello;
// 通过函数指针调用sayHello函数
functionPtr();
// 将函数指针指向sayGoodbye函数
functionPtr = sayGoodbye;
// 通过函数指针调用sayGoodbye函数
functionPtr();
return 0;
}
在上面的示例中,我们声明了一个函数指针**functionPtr
**,它指向无返回值且无参数的函数。我们将functionPtr
分别指向sayHello
和sayGoodbye
函数,并使用函数指针调用这两个函数。
2,函数指针数组:
我们来看一下一个函数指针数组的案例,这里函数指针的用途是转移表,应用于一个计算器:
我们先来看一段代码:
#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;
}
上面代码中,我们不难看到,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)
,这里的p先和[]
结合,说明是p是一个数组,而数组的内容是int (*)(int ,int )
类型的函数指针。
3。指向函数指针数组的指针
我们知道函数指针数组的指针是一个指针
,那么,指针指向一个数组,数组里面的元素都是函数指针
,那么我们怎么定义这个指函数指针数组的指针
呢?
我们来看一段代码就知道了:
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;
}
4,这里还可以记录一下回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。我们通过对比以下两个函数来体会一下回调函数。
首先演示一下qsort函数的使用:
#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
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");
return 0;
}
接下来我们使用回调函数,模拟实现qsort(采用冒泡的方式)。
我们这里有用到void*
的指针,
void * 以定义一个指针变量,但不说明它指向哪一种类型数据.
1.传参:通用类型
可以作为函数模板,链表等参数的通用参数。在使用时,只需要强制类型转换就可以。
2.强制类型转换
有时候由于重载等的干扰,导致需要转换成void *,来进行取地址。
例如,(void *)obj.member,就可以取到member的地址;直接&(obj.member)取到的实际上是obj的开始地址。
3.指向0的地址
(void *)0,指向全是0的地址,相当于NULL。(参考至:
https://blog.csdn.net/b1480521874/article/details/83010304
#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
void _swap(void *p1, void * p2, int size)
{
int i = 0;
for (i = 0; i< size; i++)
{
char tmp = *((char *)p1 + i);
*(( char *)p1 + i) = *((char *) p2 + i);
*(( char *)p2 + i) = tmp;
}
}
void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{
int i = 0;
int j = 0;
for (i = 0; i< count - 1; i++)
{
for (j = 0; j<count-i-1; j++)
{
if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)
{
_swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
}
}
}
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
int i = 0;
bubble(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");
return 0;
}
最后的结果是一样的,口头上比较难解析,感兴趣的可以观看:比特鹏哥
总结:
浅谈,印象不深,但应该能懂,后面补一个文章以简单的输出来区分一下,帮助理解。