C语言——指针高阶运用

目录

1.函数指针

2.函数指针数组(转移表)

指向指针数组的指针

3.回调函数

void*

普信型冒泡排序


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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值