回调函数和如何使用qsort函数以及最后如何运用冒泡排序完成一个各类型数据都适用的排序算法

文章介绍了回调函数的概念,通过简单计算器的示例说明其工作原理,并展示了如何优化代码以减少重复。接着,文章讲解了C语言中的qsort函数,包括其使用方法和自定义比较函数的需求。最后,通过冒泡排序的实现模拟了qsort的功能,强调了如何处理不同数据类型的比较。
摘要由CSDN通过智能技术生成

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

我们用简单计算器的方式来解释这个回调函数

对于实现一个计算器我们先看这个代码

#include<stdio.h>
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;
}
void menu()
{
  printf("***********************\n");
  printf("******简单计算器*******\n");
  printf("*****1.Add 2.Sub ******\n");
  printf("*****3.Mul 4.Div*******\n");
  printf("****0.exit    *********\n");
}
int main()
{
  int input = 0;
  menu();
  int x = 0, y = 0;
  int (*p[5])(int, int) = { NULL,Add,Sub,Mul,Div };//这里使用NULL是为了占位让菜单里的数字与数组数字对应
  do
  {
    printf("请选择");
    scanf("%d", &input);
    printf("请输入两个操作数\n");
    scanf("%d %d", &x, &y);
    switch (input)
    {
    case 1:
      printf("%d", p[1](x, y));
      break;
    case 2:
      printf("%d", p[2](x, y));
      break;
    case 3:
      printf("%d", p[3](x, y));
      break;
    case 4:
      printf("%d", p[4](x, y));
      break;
    default:
      printf("错误输入\n");
      break;
    case 0:
      printf("退出计算机\n");
      break;
    }
  } while (input);
  return 0;
}//使用这个方法的缺点在于每次进如case都有一个printf语句使用多造成了代码的重复

我们仔细看这些case语句内的代码很明显除了进行运算的方式不同其它都是相同的那么我们可不可以将重复的功能写成一个函数呢

#include<stdio.h>
void Calc(int(*p)(int, int))
{
  int x = 0,y = 0;
  printf("请输入两个操作数");
  scanf("%d %d", &x, &y);
  printf("%d\n", p(x, y));
}
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;
}
void menu()
{
  printf("***********************\n");
  printf("******简单计算器*******\n");
  printf("*****1.Add 2.Sub ******\n");
  printf("*****3.Mul 4.Div*******\n");
  printf("****0.exit    *********\n");
}
int main()
{
  int input = 0;
  menu();
  int (*p[5])(int, int) = { NULL,Add,Sub,Mul,Div };//这里使用NULL是为了占位让菜单里的数字与数组数字对应
  do
  {
    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;
    default:
      printf("错误输入\n");
      break;
    case 0:
      printf("退出计算机\n");
      break;
    }
  } while (input);
  return 0;
}//这样就完成了我们将打印和输入操作数的功能放入我们新建立的函数中同时在选择功能后将能完成特定功能函数的地址也传递给新建立的这个函数
//当新建立函数通过地址找到了需要使用的功能函数时,我们就认为这个功能函数被叫做回调函数。

代码运行结果

但是这么写还有一个弊端那就是以后要添加功能时这些case就会影响代码的观感,那么我们也可以这么改进

#include<stdio.h>
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;
}
void menu()
{
    printf("***********************\n");
    printf("******简单计算器*******\n");
    printf("*****1.Add 2.Sub ******\n");
    printf("*****3.Mul 4.Div*******\n");
    printf("****0.exit    *********\n");
}
int main()
{
    int input = 0;
    menu();
    int x = 0, y = 0;
    int (*p[5])(int, int) = { NULL,Add,Sub,Mul,Div };//这里使用NULL是为了占位让菜单里的数字与数组数字对应
    do
    {
        printf("请选择\n");
        scanf("%d", &input);
        if (input > 0 && input <= 5)
        {
            printf("请输入两个操作数\n");
            scanf("%d %d", &x, &y);
            int ret = p[input](x, y);
            printf("%d \n", ret);
        }
        else
        {
            printf("选择错误,请重新选择\n");
        }
     
    } while (input);
    if (input == 0)
    {
        printf("退出计算机\n");
    }
    return 0;
}

这样我们就能避免那么多的case语句同时要添加其它功能的时候也方便。

下面是关于qsort函数的概述和如何使用以及它的特点

首先qsort是一个库函数是用来排序的库函数qsort底层使用的是快速排序的方法,关于快速排序我暂时还没有了解过就不加以解释了。

qsort函数的特点就是它适用于所有数据类型的排序,你传给qsort一个整型它能排序,你传给它一个字符类型它也能排序

接下来我们学习qsort函数如何使用我们

我们先来看c++plus网站是怎么描述这个函数的

将其翻译出来就是下面的代码解释

#include<stdio.h>
int main()
{
  void qsort(void* base,//这个参数是void类型的一个指针这个指针指向了待排序的那个数组的第一个元素
    size_t num,//这是一个无符号的数据指的是待排序数组的元素个数
    size_t size,//这个与上同然后指的是待排数组的一个元素所占用的字节数
    int(*cmp)(const void*, const void*)//这个参数就是一个函数指针
    //这个指针所指向的函数的返回值为int,参数为void*,加上const的目的是防止数据被修改
    //这个函数的目的是比较两个元素的大小
  );//从这里我们能看到qsort函数的返回值为void 然后还有三个参数
  return 0;
}

在这里我们自己需要设计的函数就是如何比较数组里的两个元素大小,之所以要我们自己设计就是因为这个qsort函数它是能排序所有

数据类型的函数,如果是整型我们运用一个if就可以完成,但如果是字符数组呢?我们就需要用strcmp由此所以我们需要自己去设计比较两个元素大小的函数。简言之我们在使用qsort函数时我们需要先给qsort函数提供比较两个数组元素大小的方法。

#include<stdio.h>
#include<stdlib.h>//qsort函数需要包含的头文件
int Int_com(const void* p1, const void* p2)//要求我们的函数必须使用void*类型的参数和返回值要为int且还要求如果前面大于后面就返回大于0的数等于返回0小于返回小于0的数字
{
  return *(int*)p1 - *(int*)p2;//在这里我们不能直接对void*的指针进行解引用操作因为void*的指针是个无具体类型的指针所以如果解引用计算机并不知道一次要从地址处取出多少数据
    //同理也不能对void*类型的地址进行加一或减一操作因为计算机不知道一次要跳过几个字节
  //但是也意味着这个void*指针能接受任意的指针
  //所以在这里我们先对p1进行强制类型转换就可以了,这里若p1大于p2返回值大于0等于返回值为0小于返回值为小于0完美符合要求
}
void printfg(int arr[10], int sz)
{
  int i = 0;
  for (i = 0; i < sz ; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
 test()
{
  int arr[] = { 8,5,2 ,7,4,1,9,6,3,0 };
  //下面使用qsort函数 
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr[0]), Int_com);//在这里我们自己需要完成一个函数这个函数能完成两个整型的比较
  printfg(arr, sz);
}
int main()
{
  test();
  return 0;
}

接下来我们比较一下结构体

#include<stdio.h>
#include<stdlib.h>//qsort函数需要的头文件
int Int_com(const void* p1, const void* p2)//要求我们的函数必须使用void*类型的参数和返回值要为int且还要求如果前面大于后面就返回大于0的数等于返回0小于返回小于0的数字
{
  return *(int*)p1 - *(int*)p2;//在这里我们不能直接对void*的指针进行解引用操作因为void*的指针是个无具体类型的指针所以如果解引用计算机并不知道一次要从地址处取出多少数据
    //同理也不能对void*类型的地址进行加一或减一操作因为计算机不知道一次要跳过几个字节
  //但是也意味着这个void*指针能接受任意的指针
  //所以在这里我们先对p1进行强制类型转换就可以了,这里若p1大于p2返回值大于0等于返回值为0小于返回值为小于0完美符合要求
  //qsort函数默认排成的是升序,如果要排成降序只需要将p1和p2的位置互换就可以了至于是为什么,下面我会解释。
}
void printfg(int arr[10], int sz)
{
  int i = 0;
  for (i = 0; i < sz ; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
 test()
{
  int arr[] = { 8,5,2 ,7,4,1,9,6,3,0 };
  //下面使用qsort函数 
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr[0]), Int_com);//在这里我们自己需要完成一个函数这个函数能完成两个整型的比较
  printfg(arr, sz);
}
 struct stu
 {
   char name[20];
   int age;
 };
 int com_age(const void* p1, const void* p2)//
 {
   return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
 }//比较方式和整型比较差不多
 int com_name(void* p1, void* p2)
 {
   return strcmp(((struct stu*)p1)->name,((struct stu*)p2)->name);
 }
 void printfg_stu(struct stu* s,struct stu* p)
 {
   for (; s < p; s++)
   {
     printf("%s %d\n", s->name,s->age);
   }
 }
 test2()//测试qsort函数排序结构体数据
 {
   struct stu s[] = { {"zhangsan",20},{"lisi",15},{"wangwu",50} };
   //qsort(s,sizeof(s)/sizeof(s[0]),sizeof(s[0]), com_age);//这里我们需要先规定是按照名字还是年龄来排这里我们按照年龄来排序,那么我们现在就需要一个比较两个年龄大小的函数
   qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), com_name);//这里我们运用名字来比较名字为字符串所以我们就需要运用strcmp函数来比较
   printfg_stu(s,&s[3]);
 }
int main()
{
  //test();
  test2();
  return 0;
}

需要知道void*指针在使用前需要强制转换为你需要的指针类型再解引用

比较年龄的运行图

比较名字的运行图

接下来我们运用冒泡排序也来模拟一下qsort函数

既然要使用冒泡排序来模拟我们就必须先知道冒泡排序的算法。

假设这里有9 8 7 6 5 4 3 2 1这9个数且我们要升序排列这些数,那么冒泡排序首先就会让9和8比较发现不符合升序让9和8换位,之后9和7继续比较发现不符合升序那么继续让7和9换位直到最后这个数组会变成8 7 6 5 4 3 2 1 9。这就成功让9到了它应该在的地方,而这只是冒泡排序的第一趟之后让8重复9的操作直到8到了9的前面,这时候计算机判断满足升序那么就不会交换。而趟数的规律也就是元素个数减1

上面的就是冒泡排序的思路下面我们运用代码来实现它

#include<stdio.h>
void printfg(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
void buttle_sort(int arr[], int sz)
{
  //对于冒泡排序我们必须先确定趟数
  for (int i = 0; i < sz - 1; i++)//元素个数-1躺
  {
    //下面就是一个元素一趟要交换多少次
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)//这里要-i是因为在完成一趟后下一趟的那个元素就不用再一直移到最后在987654321的数组里9移动到最后以后8就只用移动7次比9少了1次而移动7的时候又比8少了1次,
      //所以每经过一次下一个元素要移动的次数就会减1所以我们这里-i,开始i为0,-i不影响而之后i为1,为2恰好就符合我们需要的要求。
    {
      if (arr[j] > arr[j + 1])
      {
        int tmp = arr[j + 1];
        arr[j + 1] = arr[j];
        arr[j] = tmp;
      }
    }
  }
}
int main()
{
  int arr[] = { 1,4,7,8,5,2,9,6,3,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  buttle_sort(arr, sz);
  printfg(arr, sz);

  return 0;
}

运行截图

然后我们便运用冒泡排序的思想来模拟实现一个和qsort函数类似的函数

#include<stdio.h>
void printfg(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
void swap(char* p1,char* p2, size_t wieth)//因为在前面我们已经将base的类型强制转化为了char*类型所以我们这里也用char*类型接受
{
  for (int i = 0; i < wieth; i++)
  {
    char tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
    p1++; p2++;
  }//这里我们虽然不知道使用者要交换的数据类型是什么但是我们知道一个数据占据多少字节那么我们便一个一个字节的交换。要交换的次数也就是一个数据的大小wieth
}
void bubbls_sort(void* base, size_t num, size_t wieth, int(*com)(const void* p1, const void* p2))//因为qsort函数的特点之一就是能够接受所有的数据类型所以我们接受别人传过来的数据就可以用void*base来接受
size_t的类型是无符号整型即没有负数
{
  //使用冒泡排序所以我们先确定趟数
  size_t i = 0;
  for (i = 0; i < num - 1; i++)//趟数确定
  {
    size_t j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      //里面就是一趟要交换的次数
      if (com((char*)base + j * wieth, (char*)base + (j + 1) * wieth )> 0)//这里为什么交换p1与p2会让升序变为降序我们可以通过例子说明假设我们这里传过来的是9 8按照升序9-8大于0所以
        //就实行了交换函数但当我们交换了位置也就变更成了8-9值为-1不大于0所以不实行交换函数
        //这里就需要使用我们这个函数的人提供如何比较两个数的大小,所以当我们模拟实现就只用在这里调用一下这个函数,但同时我们也就遇到了一个问题那就是我们不知道
        //使用这个函数的人会排什么类型的数据导致我们无法直接用arr[j],和arr[j+1]的方式来代表要比较的那两个数
        // 现在我们只知道base指向的是首元素但不知道类型是什么
        //这个时候我们就可以将base的类型转化为char*因为char*的特点就是读取和跳过的都是一个字节而恰好我们又知道要排序的数组一个数据的大小那么我们就可以
        //让首地址+要跳过的元素个数*wieth这样我们就能读取到任意一个类型的数据了,且不会出错。
      {
        swap((char*)base + j * wieth, (char*)base + (j + 1) * wieth,wieth);//这个函数的目的就是交换这两个数
      }
    }
  }
}   
int com_int(const void* p1, const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}
void test3()//sisz_t的类型是无符号整型即没有负数
{
  int arr[] = { 1,4,7,8,5,2,9,6,3,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  printf("排序前:");
  printfg(arr, sz);
  bubbls_sort(arr, sz, sizeof(arr[0]), com_int);
  printf("排序后:");
  printfg(arr,sz);
}
int main()
{
  test3();
  return 0;
}

上面的代码运行结果如图

希望这篇文章能对你有所帮助,如果你发现了问题,希望你能告诉我,我一定去改正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值