【c语言】指针进阶(2)

目录

五、函数指针数组

六、指向函数指针数组的指针

七、回调函数

7.1 qsort

7.2 实现一个可以排序任何类型的冒泡函数


五、函数指针数组

数组是一个存放相同类型数据的存储空间,函数指针数组顾名思义存储的是函数指针。

int (*parr1[10])();

parr1先和[10]结合说明它是个数组有10个元素,元素类型是int(*)()函数指针。

用途:转移表。用经典的计算器来举例:

首先要有最基础的功能+,-,*,/

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;
}

我们该如何使用它们?可以使用switch来实现

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("***   0:exit          ***\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;
}

虽然功能实现了,但在使用四种功能时好像重复了几段代码,看起来有些冗余

printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("ret = %d\n", ret);
break;

它们只是调用了不同的函数给了ret,而这些函数的类型又是相同的,就可以放到一个数组内来使用。

int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { NULL, Add, Sub, Mul, Div }; //转移表
	while (input)
	{
		printf("*************************\n");
		printf("***   1:add    2:sub  ***\n");
		printf("***   3:mul    4:div  ***\n");
		printf("***   0:exit          ***\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 (*(*parr1)[10])();

定义:

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;
}

七、回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

7.1 qsort

先来看一个函数:

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

qsort可以对任何数组进行排序,它是使用快速排序来实现的。那又是如何对任何数组进行排序的?我们先来看它的类型,base:指向要排序的数组的第一个元素的指针;nitems:  base 指向的数组中元素的个数;size:数组中每个元素的大小,以字节为单位;compar:用来比较两个元素的函数。

其中base和compar的参数都是void*类型的(const只是表示不可*修改),void*是很特殊的。

void*可以指向任何类型的地址,但是带类型的指针不能指向void*的地址。

void*指针只有强制类型转换以后才可以正常取值和加减整数。

在实现qsort时,那个程序员并不知道后面人使用时会排序什么类型数组,而每个类型的比较又是不同的,所以需要我们自己实现compar(比较函数)。

现在来看看各个类型如何实现compar:

整形:

int compar(const void* x, const void* y)
{
	return *(int*)x - *(int*)y;
}

字符:

int compar(const void* x, const void* y)
{
	return *(char*)x - *(char*)y;
}

字符串:

int compar(const void* x, const void* y)
{
	return strcmp((char*)x , (char*)y);
}

浮点型:

int compar(const void* x, const void* y)
{
	return *(double*)x - *(double*)y;
}

结构体:

struct Stu
{
	char name[20];
	int age;
};
int compar1(const void* x, const void* y)
{
	return ((struct Stu*)x)->age-((struct Stu*)y)->age;
}
int compar1(const void* x, const void* y)
{
	return strcmp(((struct Stu*)x)->name, ((struct Stu*)y)->name);
}

7.2 实现一个可以排序任何类型的冒泡函数

qsort是使用快速排序来实现的,我们可以先用冒泡排序进行实现排序函数:

先写一个整形冒泡,在这个基础上来修改:

void My_sort(int* arr, int sz)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (*(arr + j) > *(arr + j + 1))
			{
				int tmp = *(arr + j);
				*(arr + j) = *(arr + j + 1);
				*(arr + j + 1) = tmp;
			}
		}
	}
}

这是的排序函数只能排整形,我们只需要让它也可以排序其他类型的数组就可以。

参数参考qsort来写:

void My_sort(void* base, size_t nitems, size_t size, int (*compar)(const void*, const void*))

参数是比我们原先的冒泡多了两个参数。

size是排序数组的元素大小,我们在排序是肯定是会对base进行访问和加减的,void*是不能进行的,它就是可以加减整数也不能确定跳过几个字节。

所以我们要对base强制类型转换,自然不能转换为int,如果base指向的是double类型该如何跳过一个元素,每次多+2?那char呢?它是无法通用的。

其实我们可以将它转成char*,我们已经知道元素的字节大小了(size),那每次访问跳过size个字节就可以,而char*每次加几也就是跳过几个字节,这样一来我们访问第几个元素就加几个size。

compar先以最熟悉的int*类型来实现。

int compar(const void* x, const void* y)
{
	return *(int*)x - *(int*)y;
}
void My_sort(void* base, size_t nitems, size_t size, int (*compar)(const void*, const void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < nitems - 1; i++)
	{
		for (j = 0; j < nitems - 1 - i; j++)
		{
			if (compar((char*)base+j*size,(char*)base+(j+1)*size)>0)
			{
				swap();//交换函数
			}
		}
	}
}

x-y,x大与y就交换它们两个,这时它是升序函数;y-x,y大于x就交换它们两个,这时它是降序函数。

再来看看swap:

在交换时也是不知道数组的类型,但每个类型它都是以字节为单位的,可以再次使用char*,每次只交换1个字节交换size次,也就刚好是这个数组的元素大小。

int compar(const void* x, const void* y)
{
	return *(int*)x - *(int*)y;
}
void swap(char* x, char* y, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *x;
		*x++ = *y;
		*y++ = tmp;
	}
}
void My_sort(void* base, size_t nitems, size_t size, int (*compar)(const void*, const void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < nitems - 1; i++)
	{
		for (j = 0; j < nitems - 1 - i; j++)
		{
			if (compar((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[10] = { 9,8,7,0,4,5,6,3,2,1 };
	My_sort(arr, 10,4,compar);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

改为y-x

int compar(const void* x, const void* y)
{
	return *(int*)y - *(int*)x;
}

再来试试结构体类型:

struct stu {
	char name[20];
	int age;
};
int compar(const void* x, const void* y)
{
	return *(int*)y - *(int*)x;
}

int compar1(const void* x, const void* y)
{
	return strcmp(((struct stu*)x)->name, ((struct stu*)y)->name);
}
int main()
{
	/*int arr[10] = { 9,8,7,0,4,5,6,3,2,1 };
	My_sort(arr, 10,4,compar);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}*/
	struct stu p[3] = { {"zhangsan",17}, {"lisi",20},{"wangwu",19} };
	My_sort(p, 3, sizeof(p[0]), compar1);
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s,%d\n", (p + i)->name, (p+i)->age);
	}
	return 0;
}

这里结构体定义要放到compar前,否则会报错。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值