深入理解指针(6)

1.函数指针数组

我们学了整型数组,字符数组,指针数组,我们还学过整型指针,字符指针,函数指针,数组指针而今天我们要学函数指针数组属于是多重buff叠一起了。  那今天就来学习一下。

指针数组是这样的 int *arr[10],函数指针是这样的char *test(int n,char* s)那要是把函数地址写道数组中就是函数指针数组了。那怎么来定义呢?

这里有三个情况:

int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];

我们可以来看看,结果呢就是parr1,parr1先和[ ]结合,说明parr1是数组,那数组的内容是什么呢?对啦就是int (*)()类型的函数指针。这个函数指针数组空括号里是函数指针的参数。

我们给一个函数指针数组的代码:char *(*pfArr[4])(int,char*),这里我们还可以拓展一下:

那p怎么去书写呢?

char*(*(*p)[4])(int,char*) = &pfArr    p在括号里面放了一个 就说明p是指针,然后加上[4]就说明p指向的是数组,有4个元素。这里的p就是是指向函数指针数组的指针,那p指向的4个数组的元素他类型是什么呢?

char*(*)(int,char*) = &pfArr

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

我们可以看一下这串代码,在case1234中你不断地在调用前面的函数,而且很多都是重复的代码是不是感觉很麻烦,不简明,所以我们利用今天所学来解决一下。

下面的代码的转移表就是用函数指针数组来完成的。转移表顾名思义就是充当一个跳板。

#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 }; //转移表
//这里的的数组为什么是五个元素呢?因为你要对应下面的菜单中的数字加上一个0之后,add下标就是1,在依次是234

 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);
 if ((input <= 4 && input >= 1))
 {
 printf( " 输入操作数:" );
 scanf( "%d %d", &x, &y);
 ret = (p[input])(x, y);//这里的input正好对应转移表的下标,x和y是计算的两个参数
 printf( "ret = %d\n", ret);
 }
 else if(input == 0)
 {
 printf("退出计算机 \n");
 }
 else
 {
 printf( " 输入有误\n" ); 
 }

 }while (input); 
 return 0;
 }

3.回调函数

在进行这节的讲解,还会用到上述的代码,但是呢,我们要对最原始的代码,使用函数指针进行简化。代码如下:

#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;
}
void Calc(int (*pf)(int,int))//这里便采用了函数指针,
{
int a = 0;
int b = 0;
int ret = 0;
printf("请输入2个操作数:");
 scanf( "%d %d", &x, &y);
ret = pf(x, y);
 printf( "ret = %d\n", ret);
}
int main()
{
 
 int input = 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);
swith(input)
{
case 1:
Calc(Add);//我们可以看到在add sub mul div这四个函数中,都不是直接通过函数名调用函数,而都是用calc实现的。我们把这四个函数中的地址传给calc,然后再回头使用函数指针调用,这就是函数调用.就是回调了。
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
case 0;
printf("退出计算机\n");
break;
default:
printf("选择错误,重新选择\n");
break;

}
}while (input);
  return 0;
 }

这里我们用到了函数指针,代码中我已经标明了。函数指针的作用就是下面的两点:

参数化函数调用:函数指针可以作为参数传递给其他函数,使得被调用的函数能够根据需要调用不同的函数。
回调函数:函数指针常用于实现回调机制即一个函数在完成某些操作后调用另一个作为参数传递给它的函数

看到了没有,这就是回调函数。

4.qsort使用举例

qsort 是一个广泛使用的排序函数,它在 C 语言标准库中实现,并被许多其他编程语言的库所借鉴。qsort 是快速排序算法的一种实现,它对数组或列表中的数据进行原地排序,即不使用额外的存储空间。

下面是qsort函数的原型如下:

void*base是指向要排序的数组的第一个元素的指针。

size_t num是数组中元素的数量。

size_t size是数组中的每个元素的大小*(以字节为单位)

int (*compar)(const void*,const void*))是一个指向比较函数指针,这个函数用于比较两个元素并返回它们的顺序比较函数需要接受两个指向元素的指针,并返回一个整数,表示第一个元素与第二个元素
的相对顺序。

下图就是他们的比较的返回值对应的意义:

那下面就用qsort练习一下: 

4.1使用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;
}
 4.2使用qsort排序结构数据

和个上面同样的道理

#include <stdio.h>
struct Stu //学生
{
	char name[20];//名字        //首先先定义这个结构体
	int age;//年龄
};
//假设按照年龄来比较
int cmp_stu_by_age(const void* e1, const void* e2)
{//
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专门用来比较的两个字符串的大小的
//假设按照名字进行比较
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{
	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{
	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
	
	test2();
	test3();
	return 0;
}

在这串代码中使用了 ->操作符在C语言中,“->” 是一个运算符,它是成员访问运算符,用于指针访问结构体中的成员。当有一个结构体的指针时,你可以使用这个运算符来直接访问该结构体中的成员,而不需要先解引用指针。

 5.qsort函数的模拟实现

使用回调函数,采用冒泡的方式,对qsort的模拟实现

注意:这里第一次使用void*的指针,讲解void*的作用(在代码内部进行讲解)

#include <stdio.h>
int int_cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);//这里为什么要进行强制类型转换呢?因为void*类型的指针是无具体类型的指针,这种类型的指针不能直接进行解引用,也不能加减整数。
}
//这里的_swap汉书为什么给他传入width参数呢?因为想要交换只知道位置还不够,还要知道位置所在的宽度
void _swap(char*buf1,char*buf2,size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf1= tmp;
        buf1++;//这里为什么要++呢?因为你要进行两两的交换元素,这俩个交换完成之后,要进行下面两个的交换。直到完成交换的整体的宽度为止。
        buf2++;
	}
}           //起始位置    元素个数   元素大小(宽度)       比较函数
void bubble(void* base, size_t sz,size_t width, int(*cmp)(const void*p1, const void*p2))
{
	int i = 0;
	int j = 0;
//趟数
	for (i = 0; i < count - 1; i++)
	{
//一趟内部的两两比较
		for (j = 0; j < count - i - 1; j++)
		{      //比较arr[j]和arr[j+1]
//要比较两个元素的大小就要找到两个元素的地址,base是起始地址,又是void*的指针,他没有办法加减整数去找到下一个元素,所以在这里进行强制类型转换,为什么要转换成char*因为加减整数就会加减一个字节,比较精确。
//这里的+j*width就是跳过的字节数,width就是每个元素的字节个数,就是宽度。而j就是跳过的元素个数
			if (cmp((char*)base + j *width, (char*)base + (j + 1) * width) > 0)
			{  //交换两个元素
				_swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}
int main()

{
 int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
 int i = 0;
 int sz = sizeof(arr) / sizeof(arr[0]);
 bubble(arr, sz, sizeof(arr[0]), int_cmp);
 for (i = 0; i < sz; i++)
 {
 printf("%d ", arr[i]);
 }
 printf("\n");
 return 0;
}

为了使用回调函数模拟实现   qsort   函数对数组进行排序,我们将创建一个名为   bubble  的冒泡排序函数,该函数将接受一个回调函数作为参数。这个回调函数将用于比较数组中的两个元素,并根据比较结果决定是否交换它们的位置。

既然是模拟qsort函数,我们定义的bubble(气泡的意思)函数肯定要在参数上类似。

arr 是指向要排序的数组的第一个元素的指针。

sizeof(arr)/sizeof(arr[0]) 是数组中元素的数量。

sizeof(int) 是数组中的每个元素的大小*(以字节为单位)

int (*compar)(const void*,const void*))   是一个指向比较函数指针,这个函数用于比较两个元素并返回它们的顺序比较函数需要接受两个指向元素的指针,并返回一个整数,表示第一个元素与第二个元素 的相对顺序。

在这个函数中,int_cmp是一个指向比较函数指针,这个函数用于比较两个元素并返回它们的顺序比较函数需要接受两个指向元素的指针,并返回一个整数,表示第一个元素与第二个元素 的相对顺序。

int_cmp的定义就是这样 

 讲完模拟qsort函数的参数,我们再来讲讲排序的趟数和每趟的次数。在之前没有讲qsort时候,我们看到的排序是这样

但是现在为什么换成了这样?因为 上面的方法只适合整型之间的排序,我们如果想把它改造成能够排序任意类型的函数,就是下面的样子。

 把两个元素比较的方法,封装成函数然后把函数的地址传给排序函数。就是下图中的

 

上面的解释只是讲了个调用的大概,关于精确的函数比较和交换的解释,在上述的代码中就可以找到。

  

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值