目录
1.函数指针
函数指针,顾名思义就是指向函数的指针,存放函数地址。
int test(const char*)
{}
int (*pf)(const char*)=test;//pf就是test的函数指针。
//那么pf("abc")和(*pf)("abc")和test("abc");其实效果是一样的。
下面我们来看两个有趣却又让人头疼的两段代码(代码出处:《c陷阱和缺陷》)
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
代码1:首先抓住最关键的0,这个代码主要就是围绕0展开的。前面的括号中(void(*)())其实是一种数据类型,就是我们刚刚讲的函数指针,可能你刚看反应不过来,我们这样写:void(*p)(),这样你是不是就看出他是函数指针了。那么我们在0前面加上函数指针类型,这当然就是将0强制类型转换成无参,返回类型为void的函数地址。那么括号里放的就是一个函数指针,很显然,这个代码就是0函数的依次调用。这个代码其实是程序员想要调用地址为零的程序,其实地址为0一般是开机运行的。
代码2:啊这!!!第一眼看下去肯定会想搁这套娃是吧。我们仔细看,首先signal是啥?如果说是函数指针那么不应该是void(*signal)吗?这显然矛盾了。如果说是函数名,我们拆开来看。函数名前面的*看起来很诡异,其实这是signal函数的返回类型void(* )(int),对的他的返回类型是函数指针,这个最里面的括号实际上是函数的参数。
看到以上这两个代码,特别是第二个代码就感觉很繁琐。我们可以通过类型重命名的方式来简化它。
typedef int(* pft)(int);//将int(*)(int)类型重命名为pft
pft signal(int ,pft);//这不是就很好理解了嘛
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");
breark;
default:
printf( "选择错误\n" );
break;
}
} while (input);
return 0;
}
我们一眼就能看出问题,switch语句里面太冗余了,一样的代码写了几遍,虽然可以复制粘贴再修改,但是不美观。问题出在不同input对应不同情况,每个代码都得再写一遍,实际上涉及到input的函数就一个,但有好多种变化,如何使这几种函数可以通过input变化而相应变化便是关键。我们就可以将几个接口函数指针放入数组,通过改变编号,即可调用所有函数,就可实现简洁了。
#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(*(ppf)[5]))(int,int);//这里就随便举一例,其他照葫芦画瓢。
3.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
看完定义一头雾水,其实呢函数指针还是很有用的,下面我们举一个常用库函数qsort(快排)的例子。
提到排序很多人想,这不简单嘛,调用int* arr,int size呗,然到只有整形可以排序?浮点数不行?字符串不行?甚至结构体不行?那你说我们经常看到的:按姓氏排序是什么鬼?这么多种类型但程序员不知道我们排哪种呀。那么编写qsort函数的程序员是怎么解决这一问题的呢?
注意int(*compar)(const void*,const void*))这其实就是一个函数指针(回调函数)。
void*
这个compar函数的参数类型是void*。因为不知道待排序的类型是啥,说以使用void*类型。
对于void*类型,我认为他就是一个垃圾桶,啥都可以往里扔,什么类型的指针他都兼容。但鱼和熊掌,不可兼得。我们往里扔爽了,但拿出来可就费功夫了。首先不可以直接解引用,还有啥地址加加减减也不行,->操作还不行。那么怎么办呢?使用前需要强制类型转换。
书接上文啊,关于这个qsort函数的运用,我们需要针对不同的类型,自己分别写多个compar函数才可引用。这里我们给几种事例:
int compar_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}//int形
struct book
{
char name[20];
int price;
};
int compar_book_by_name(const void* e1, const void* e2)
{
return strcmp(((struct book*)e1)->name, ((struct book*)e2)->name);
}//char形,这个呢是结构体关于字符串的排序
然后我们把compar往qsort相应参数位置一填即可。
void test1()
{
int a1[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
int n=sizeof(a1) / sizeof(a1[0]);
qsort(a1, n, 4, compar_int);
for (int i = 0; i < n; i++)
{
printf("%d ", a1[i]);
}
printf("\n");
}
void test2()
{
struct book s[3] = { { "bca", 15 }, { "bac", 20 }, { "cab", 5 } };
int n = sizeof(s) / sizeof(s[0]);
qsort(s, n,sizeof(s[0]),compar_book_by_name);
}
最后,我们来个大手笔
普信型冒泡排序
对于所有种类型都通用的冒泡排序:
void Swap(char* e1, char* e2,int width)
{
for (int i = 0; i < width; i++)
{
char tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}//因为我们都采用的char*指针,所以对于int形等多字节需要一位一位的交换
}
void Bubble_Sort(void* a, int n,int width,int(*compar)(const void* ,const void*))
{
int i, j, flag = 0;
for (i = 0; i < n - 1; i++)
{
for (j = 0; j < n - i - 1; j++)
{//这里我们采用char*形强制转换,目的是因为char*占一个字节,指针增加减少单位为一个字节
//如果我们用(int*)指针一动就是4个字节,那么char类型将无法排序
if (compar((char*)a+j*width,(char*)a+(j+1)*width)>0)//比较大小用compar函数
{
Swap((char*)a + j*width, (char*)a+(j + 1)*width,width);//交换值
flag = 1;
}
}
if (flag == 0)
break;
}
}
如有收获,请给个大大的赞吧。好吧,最后呢祝大家端午节安康。
--2022.6.3