函数指针
指针即地址,因此函数指针就是函数的地址,这里所讲的地址是函数的入口地址.那么如何得到函数的地址呢?恩,没错就是&+函数名,但其实在C语言中函数名就是函数的地址!来看这样一段代码:
void test()
{
printf("hehe\n");
}
int main()
{
printf(" test=%p\n", test);
printf("&test=%p\n", &test);
printf("*test=%p\n", *test);
system("pause");
return 0;
}
程序运行结果: 这三个的值相等。对就是相等!其实分析一下也不难理解!首先C语言规定函数名就是函数的地址,&函数名也是函数的地址,因此第一个和第二个的值相等是意料之中的!但第三个呢?解引用加函数名,就是去找这个函数,但其实在这里编译器并不关心这个*号,加不加它是无所谓的!不妨再看这样一段代码
void test()
{
printf("hehe\n");
}
int main()
{
test();
(*test)();
(&test)();
system("pause");
return 0;
}
输出结果:
hehe hehe hehe.结果也是一样的!
总结:函数名就是函数的地址!调用函数用函数名就OK了!
那么如何定义一个函数指针呢?
函数指针定义: 下面哪一个是函数指针呢?
void* pfun1(int);
void (*pfun2)(int);
首先来分析操作符的优先级和结合性:第一个语句中括号的优先级高,pfun1先和()结合,因此它是一个函数,是函数就必然有返回值和参数,再一看函数的参数是int型,返回类型为void * 类型!
第二个语句pfun2先和*结合,因为括号提升了它们的结合性!所以pfun2是一个指针,是指针就必然指向,它指向一个返回值为void型,参数为整型的函数!
是否理解看一下这个语句表示啥?
int* (*p)(int, int);
答案:p是一个函数指针,指向返回类型为int*,有两个参数且都是整型的函数.
有兴趣的可以再看看这两个:
(*(void(*)())0)();
void(*signal(int, void(*)(int)))(int);
《c陷阱与缺陷》
函数指针数组
有关指针数组,数组指针的相关知识以前总结过,就不再说明!有兴趣的话可以浏览
https://blog.youkuaiyun.com/weixin_43213517/article/details/84194604
函数指针数组,顾名思义一个数组的元素 都是函数指针。
怎么定义函数指针数组呢?代码如下:
int (*p[10])(int);
分析一下:首先p和[]先结合,形成一个数组,那么这个数组的内容是什么呢?int(*)(int)是不是很熟悉,对它就是函数指针类型,因此p是一个函数指针数组。
说了这么多,那这东西到底怎么用呢?
虽然你不会每天都使用函数指针,它确实有自己的用武之地,最常见的用途就是转换表(jump take)和作为参数传递给另一个函数。
回调函数
回调函数:通过函数指针调用函数!即把一个函数的地址传给另一个函数,用这个指针实现对其指向的函数进行调用,我们就称这个函数为回调函数!
也许有人会问,那回调函数到底有什么意义呢?在需要该函数的时候直接调用不就了,为什么需要搞这么多花样呢?这其实和变量作为函数参数是一样的,是一种“以不变应万变的思想”,形参是不变的,但实参却可以变化,只要两者的类型一样即可,为了实现不论实参是什么,函数都可以完成相应的操作,因此一般将回调函数的形参设置为 void * 类型,因为 void * 可以接受任何指针类型的参数!
C语言函数库中有一个排序函数,qsort函数这个函数呢可以实现对任意类型的数据的排序,因为它其中的一个参数就是函数指针,来实现对里两个变量的比较,因此只要我们写好这个比较函数,就OK了!
qsort函数的原型
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
这个函数总共有四个参数,第一个参数是待排序数组的首地址,第二个参数是排序数组的元素个数,第三个参数是每个元素的大小(以字节为单位),第四个参数是一个函数指针,指向的是实现两个值比较的函数;
函数的使用方法:
#include<stdio.h>
#include<stdlib.h>
#include<search.h>
void show(int* arr, int len)
{
int i = 0;
for (i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmp_int(const void* x, const void* y)
{
return *(int*)x>*(int*)y;
}
int main()
{
int arr[10] = { 3, 5, 2, 5, 7, 8, 4, 8, 1, 0 };
int len = sizeof(arr) / sizeof(arr[0]);
show(arr,len);
qsort(arr, len, sizeof(int), cmp_int);
show(arr, len);
system("pause");
return 0;
}
输出结果:
3 5 2 5 7 8 4 8 1 0
0 1 2 3 4 5 5 7 8 8
请按任意键继续. . .
是不是很强大,只要我们写好比较函数就OK了,不用管内部的排序和交换之类的东西。
模拟实现qsort函数,实现对字符串的比较。
#define _CRT_SECURE_NO_WARNINGS 2
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void show(char** arr, int len)//输出函数,数组传参发生降级,因此形参用char**
{
int i = 0;
for (i =0 ; i < len; i++)
{
printf("%s ", arr[i]);
}
printf("\n");
}
int cmp_str(const void* str1, const void* str2)//比较连个字符串的大小
{
return (strcmp(*( char**)str1, *( char**)str2));//因为实参是字符串地址的地址,因此在强转之后还需要一次解引用。
}
void swap(void* x, void* y,int width)//交换函数
{
char* p = (char*)x;//这里的char没有类型的含义,只是为了实现一个字节一个字节的交换
char* q = (char*)y;
while (width--)
{
(*p) ^= (*q); //利用异或实现连个变量的交换,不必引入中间变量。
(*q) ^= (*p);
(*p) ^= (*q);
p++, q++;
}
}
void my_qsort(void* arr, int len, int sz, int cmp_str(const void*,const void*))//模拟实现qsort函数
{
int i = 0;
int j = 0;
char*p = (char*)arr;
for (i = 0; i < len; i++)
{
for (j = 0; j < len - i - 1; j++)//采用冒泡排序法进行排序
{
if (cmp_str((p + j*sz), p + (j + 1)*sz)>0)
{
swap(((p + j*sz)), ((p + (j + 1)*sz)), sz);
}
}
}
}
int main()
{
char* arr[5] = { "abcd", "lmnop", "efghijk", "qrstuvw","xyz" };
int len = sizeof(arr) / sizeof(arr[0]);
printf("排序前:");
show(arr, len);
my_qsort(arr,len,sizeof(char*),cmp_str);
printf("排序后:");
show(arr, len);
system("pause");
return 0;
}
注:交换函数并没有交换字符串本身,因为字符串存储在字符常量区,不允许进行修改,因此在这里我们通过交换两个字符串的地址实现了对字符串的交换,即通过传址的方式将字符串的地址的地址传进去,然后在函数里面进行交换。
函数指针数组的应用
#define _CRT_SECURE_NO_WARNINGS 2
#include<stdio.h>
#include<stdlib.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)//除
{
if (y == 0)
{
printf("Error\n");
return -1;
}
else
return x / y;
}
int main()
{
char op[5] = { '0', '+', '-', '*', '/' };
int(*p[5])(int x,int y) = {0, Add, Sub, Mul,Div};//函数指针数组,存放五个函数的地址
unsigned select = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("Please select: ");
scanf("%d", &select);
if (select <= 4 && select >= 1)
{
printf("请输入操作数 :<x,y>\n");
scanf("%d%d", &x, &y);
ret = (p[select])(x, y);
printf("%d %c %d = %d\n", x, op[select], y, ret);
}
if (select == 0)
{
printf("退出\n");
}
if (select>4)
{
printf("输入有误!\n");
}
} while (select);
system("pause");
return 0;
}
函数功能很简单!
指向函数指针数组的指针
概念:指向函数指针数组的指针是一个数组指针,且这个数组的元素全是是函数指针!
定义:
void fun(char* str)
{
printf("%s\n", str);
}
int main()
{
void(*pfun)(char*) = fun; //pfun是函数指针
void(*pfun_arr[])(char*) = { pfun }; //pfun_arr函数指针数组
void(*(*ppfun_arr)[])(char*) = &pfun_arr;//ppfun_arr函数指针数组指针
system("pause");
return 0;
}
如何理解?
首先 ppfun_arr和* 结合使得其成为一个指针,指向的类型为void( * ()[])(char*),这显然是一个函数指针数组。因此ppfun_arr是一个指向函数指针数组的指针。