C语言指针相关知识(第四篇章)(非常详细版)

发布于:2024-05-24 ⋅ 阅读:(66) ⋅ 点赞:(0)


前言

本文介绍了回调函数,qsort函数的使用,以用冒泡排序来模拟实现qsort函数


提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是回调函数

  • 前面的博客里面我介绍了函数指针变量的相关概念,而回调函数就是通过一个函数指针调用的函数。进一步说,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数,注意哈,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生的时候由另一方调用哒,用于对该事件或条件进行响应。
  • 我们使用回调函数其实可以简化代码,省去一些冗余重复的操作
    以下是实现一个计算器的代码,我们在没有使用回调函数之前:
#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");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    return 0;
 }

在实现计算器的过程中,我们可以发现在case语句中输入输出较为冗余,重复次数过多,这里我们就可以用回调函数来简化代码,设置一个操作函数,参数为函数指针变量来简化代码。具体操作如下:

#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 ret = 0;
int x,y;
printf("输入操作数:");
scanf("%d %d",&x,&y);
ret = pf(x,y);//这里我们通过函数指针调用相关函数,所被调用的函数即为回调函数。
printf("ret=%d\n",ret);
}
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:
            calc(add);
            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;
 }

很明显我重新定义了一个函数calc,而其参数为函数指针变量,我们将我们要执行的加减乘除函数传递到函数中,就可以减少重复的输入和输出代码,从而做到了简化代码的功效。

二、qsort函数的介绍(默认升序排序)

  • qsort函数是我们C语言库中用来专门用来排序的库函数(头文件为:stdlib.h)
  • 定义声明为:
  • void qsort (void* base, size_t num, size_t size, int (compar)(const void,const void*));
  • base代表待排序序列,num,为序列中元素个数,size,代表每个元素所代表的字节大小,最后一个参数为函数指针类型,是一个比较函数,用来阐述比较规则的。
  • 对于最后一个参数函数指针类型的参数,我们通过它的返回值来确定具体那个元素在前,那个元素在后:
    若返回值<0,则第一个指针指向的元素在前,第二个指针指向的元素在后;若返回值=0,则默认第一个指针指向的元素在前,第二个指向的元素在后;若返回值>0,则第一个指针指向的元素在后,第二个指针指向的元素在前。
  • 使用qsort函数来排序整型数据:
    代码显示:
#include<stdio.h>
#include<stdlib.h>
int int_cmp(const void *p1,const void*p2)
//实现泛式编程,我们定义void*指针,这样就可以接受任何类型的数据。后面只需要强制类型转换成我们所需要的数据类型即可。
{
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(int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
printf("%d ",arr[i] );
}
printf("\n");
}
  • 使用qsort函数排序结构数据
  • 我们来进行结构体的排序以学生结构体为例,我们进行分别以学生的名字为依据和学生的年龄为依据进行比较。
    以年龄为依据进行排序:
#include<stdio.h>
#include<stdlib.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;
}
int main()
{
struct Stu arr[]={{"zhangsan,20"},{"lisi,30"},{"wangwu,35"}};//这里我们定义结构体序列
qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(s[0]),cmp_stu_by_age);
}
return 0;

这里的排序结果我们通过调试来显示:
排序前:
在这里插入图片描述
排序后:
在这里插入图片描述
以名字为依据进行排序:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>//我们需要调用字符串函数strcmp函数进行字符串的比较
struct Stu //定义学生结构体变量
{
char name[20];//学生名字
int age;//年龄
}int cmp_stu_by_name(const void*e1,const void * e2)

{
return strcmp(  ((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}

int main()
{
struct Stu arr[]={{"zhangsan,20"},{"lisi,30"},{"wangwu,35"}};//这里我们定义结构体序列
qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(s[0]),cmp_stu_by_age);
}
return 0;

我们通过调试来显示排序结果:
排序前:

在这里插入图片描述
排序后:
在这里插入图片描述
这里strcmp字符串比较函数的返回值正好符合我们qsort对于比较函数返回值的要求,二者可谓是不谋而合呀。

三、qsort函数的模拟实现(通过冒泡排序)

  • 我在之前的博客里面已经实现过我们所熟悉的冒泡排序代码算法:
#include<stdio.h>
void input(int* arr, int sz)//输入待排序序列
{
	for (int i = 0; i < sz; i++)
	{
		scanf("%d", arr + i);
	}
}

void bubble_sort(int* arr, int sz)//冒泡排序算法
{
	for (int i = 0; i < sz-1; i++)//sz-1趟比较
	{
		int change = 1;//小优化节省时间
		for (int 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;
				change = 0;
			}
		}
		if (change == 1)//说明已经有序 
		{
			break;
		}
	}
}
void print(int* arr, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(arr + i));
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 0};
	int sz = sizeof(arr) /sizeof(arr[0]);
	input(arr, sz);
	bubble_sort(arr, sz);
	print(arr, sz);
	return 0;
}

但是在这里我们为了响应qsort算法,我们应该根据qort函数中的参数来重新改编冒泡排序。
前面已经提到过qsort函数的函数声明:
void qsort (void* base, size_t num, size_t size, int (compar)(const void,const void*));
这里我们用void指针来接受待排序序列,是一种泛式的编程,这里就可以接受任何数据类型的排序,这便是void指针的最大优势。

我们用冒泡排序模拟实现qsort函数的代码如下(这里我们以排序整型数据为例):

#include<stdio.h>
int int_cmp(const void*p1,const void * p2)//定义比较函数
{ 
 return (*( int *)p1 - *(int *) p2);
}
void _swap(void*p1,void*p2,int size)
{
for(int i=0;i<size;i++)
{
//每一位字节都进行交换,从而做到整个数据类型进行交换。
 char tmp = *((char *)p1 + i);//我们转换成char*类型可以理解为转换成单位字节,然后乘上数据类型字节大小就可以表示任意数据类型
 *(( char *)p1 + i) = *((char *) p2 + i);
 *(( char *)p2 + i) = tmp;
}


}
void bubble(void*base,int count ,int size,int(*cmp)(void*,void*))
//这里完全模仿qsort函数来定义的
{
for(int i=0;i<count-1;i++)
{
for(int j=0;j<count-1-i)
{
if(cmp((char*)base+j*size,(char*)base+(j+1)*size)>0)//我们转换成char*类型可以理解为转换成单位字节,然后乘上数据类型字节大小就可以表示任意数据类型
{
 _swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
}
}
}
}
int main()
 {
 int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
 int i = 0;
 bubble(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;
 }

总结

本文主要介绍了一个崭新的概念回调函数,并分析了qsort函数的使用,以及用冒泡排序来模拟qsort函数,如有错误,请批评指正,感谢支持