【c语言进阶】还在自己写排序的函数吗?快来通过回调函数学习并模拟库函数 qsort 的实现把

简介: 【c语言进阶】还在自己写排序的函数吗?快来通过回调函数学习并模拟库函数 qsort 的实现把

目录

一.回调函数:

       1.回调函数的定义:

       2.回调函数的使用:

       3.qsort函数的使用:

       4.利用回调函数模拟实现qsort函数:

二.总结:  

博客主页:张栩睿的博客主页

欢迎关注:点赞+收藏+留言

系列专栏:c语言学习

       家人们写博客真的很花时间的,你们的点赞和关注对我真的很重要,希望各位路过的朋友们能多多点赞并关注我,我会随时互关的,欢迎你们的私信提问,也期待你们的转发!

       希望大家关注我,你们将会看到更多精彩的内容!!!

一.回调函数:

       1.回调函数的定义:

我们都知道,函数的调用除了我们最基础的调用方式之外,通过函数指针我们也可以实现对函数的调用。函数指针调用函数的函数,就称为回调函数。

       如果你把函数的地址作为参数(即使用指针)传递给另一个函数 ,且当这个指针调用了它指向的函数时,我们就把这样的使用方式称作回调函数。

  2.回调函数的使用:

实例1:

//函数1:
//函数1使用了这样的调用方式,就被称为回调函数
void test()
{
  printf("test\n");
}
//函数2:
//使用函数指针接收,并对函数test进行调用:
void TEST(void(*p)())
{
  //使用函数指指针调用test函数:
  p();
  printf("TEST\n");
}
int main()
{
  //将函数地址传递给另一个函数:
  TEST(test);
  return 0;
}

在这个过程中,函数 test 就被作为函数参数传递了出去,并且在函数 TEST 中被函数指针接收,且该指针调用了它指向的 test 函数,于是我们就可以说 test 函数是回调函数。

实例2:

       我们之前通过转移表简化了计算器代码的书写计算器的书写,我们是否可以用回调函数简化呢?

void menu()
{
  printf("*******************************\n");
  printf("****** 1. add   2. sub    *****\n");
  printf("****** 3. mul   4. div    *****\n");
  printf("****** 0. exit            *****\n");
  printf("*******************************\n");
}
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 calc(int (*pf)(int, int))
{
  int x = 0;
  int y = 0;
  int ret = 0;
  printf("请输入两个操作数:>");
  scanf("%d %d", &x, &y);
  ret = pf(x, y);
  printf("%d\n", ret);
}
int main()
{
  int input = 0;
  do 
  {
    menu();
    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;
}

 3.qsort函数的使用:

我们之前学过使用冒泡排序,用冒泡排序写一个函数来对整形数组进行升序降序,但是局限就是只能对整形,对其他类型无法排序。而qsort函数对于任意类型都可以排序,整形,字符型,甚至结构体型都可以,那么我们该如何·使用·qsotr函数呢?

       库函数 qsort 是基于快速排序法实现的一个排序函数。

我们在cplusplus上看看用法:

函数括号里到底传入了什么参数呢?

这里的compare函数如何书写呢?

不难发现,在升序排序中,compare是将两个要排序的元素的地址作为参数,将他们比较大小的差作为返回值。

如果结果>0,返回正数

如果结果=0.返回0

如果结果<0,返回负数  

那么就会有同学问了,那么降序怎么办?

如果降序的话就让两个值交换位置相减即可。

那么就会有同学问了,那个void*是什么类型,不同类型如何转化?

下面我们就来看看结构体的排序吧!

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 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[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
  int sz = sizeof(s) / sizeof(s[0]);
  //qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
  qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}

这里需要注意的是strcmp的使用,他对于两个字符串的每个字符一一对比,如果大于返回正数,等于就往后继续比,小于就返回负数。这和我们要返回的数值正好对应。

下面,我们就通过冒泡排序来实现qsort函数吧!

4.利用回调函数模拟实现qsort函数:

首先我们回忆一下冒泡排序:

#include <stdio.h>
void bubble_sort(int arr[],int sz)
{
    int i = 0;
    for (i = 0; i < sz - 1; i++)
    {
        int j = 0;
        for (j = 0; j < sz - i - 1; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}
int main()
{
    int arr[] = { 1,2,3,4,0,8,9,7,6,5 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, sz);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

这样的冒泡排序已经写死了整形,不能比较其他类型。

我们设计一个兼容可以排序整型,又可以排字符型和结构体的呢

模拟qsort的思想,利用冒泡的形式

void swap(char* buf1, char* buf2, int width)
{
  int i = 0;
  for (i = 0; i < width; i++)
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}
int cmp_int(void* e1, void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
  int i = 0, j = 0;
  for (i = 0; i < sz - 1; i++)
  {
    for (j = 0; j < sz - 1 - i; 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[10] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
  for (int i = 0; i < sz; i++)
    printf("%d ",arr[i]);
}

这里有两处非常重要的点:

首先是这个冒泡排序:

for (i = 0; i < sz - 1; i++)
  {
    for (j = 0; j < sz - 1 - i; j++)
    {
      if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
      {
        swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
      }
    }
  }

因为我们不知道我们会对什么类型的变量排序,所以我们用char*强转指针,使这个指针的步长为一,后面就可以显现出width(类型大小)的作用了:

       因为我们不知道类型,不能用[]下标访问传过来的数组,就只能用指针的形式,通过指针+-整数来访问这个数组的每个元素。我们根据+width的倍数,正好就是这个类型的步长。

其次是这个swap交换函数:

void swap(char* buf1, char* buf2, int width)
{
  int i = 0;
  for (i = 0; i < width; i++)
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}

  由于我们不知道要交换元素类型,所以我们通过变量中拥有最小单位:一个字节的char类型来依次交换每个字节来实现元素的交换,这样是不是很简便呢?

我们在来看一看结构体类型的交换:

void swap(char* buf1, char* buf2, int width)
{
  for (int i = 0; i < width; i++)
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}
struct stu
{
  char name[20];
  int age;
};
int cmp_stu_by_name(void* e1, void* e2)
{
  return (strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name));
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(void* e1, void* e2))//这里等下看一下
{
  int i, j;
  for (i = 0; i < sz; i++)
  {
    for (j = 0; j < sz - 1 - i; 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()
{
  //结构体排序
  struct stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
  int sz = sizeof(s) / sizeof(s[0]);
  //bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
  bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}

结果:

       不难发现,bubble_sort函数,swap函数都一模一样,都是写好了的,所以我们只要自己写出那个回调函数cmp函数(比较大小) 即可,这就是qsort的方便之处。

二.总结:

     通过qsort函数的学习,我们对回调函数有了更深的理解。这里我们在复习一下qsort里的参数。

今天我们算是给我们的指针升级之路画上了一个完美的句号,至此我们关于指针的进阶的学习就告一段落了。 辛苦各位小伙伴们动动小手,三连走一波 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

目录
相关文章
|
3天前
|
存储 API C语言
C语言函数大全--e开头的函数
【6月更文挑战第6天】本篇介绍 C语言中 e开头的函数【C语言函数大全】
26 16
C语言函数大全--e开头的函数
|
5天前
|
存储 Linux Serverless
C语言函数大全--d开头的函数
【6月更文挑战第5天】本篇介绍 C语言中 d开头的函数【C语言函数大全】
13 1
C语言函数大全--d开头的函数
|
5天前
|
API C语言 开发者
C语言中抽象函数与具体实现的命名与组织
在C语言的嵌入式系统和开源软件开发中,良好地处理抽象函数与实现对于代码质量至关重要。建议将API作为接口定义操作,使用函数指针实现动态替换。避免使用`Impl`后缀,推荐用`Callback`或`Handler`表示具体实现。回调函数用于异步事件处理,通过指针传递。示例展示了C语言中函数指针的用法,嵌入式项目常通过目录结构区分平台相关代码。清晰的命名和组织能提升代码可读性和团队协作效率。
|
6天前
|
C语言
C语言进阶——文件的读写(文件使用方式、文件的顺序读写、常用函数、fprintf、fscanf)
C语言进阶——文件的读写(文件使用方式、文件的顺序读写、常用函数、fprintf、fscanf)
5 0
|
C# C语言 C++
从头开始学习c语言
以前的时候学习C语言时候认为C语言不过是一个学习的工具,学习一些理论知识就达到目的了,谁会用这么傻的语言啊,连个界面也没有,不像vb一下子就做出一个窗体来,放上几个按钮就可以了 后来学习C++的时候,认为C++与C是一种完全不同的语言,两者基本没有什么兼容性,当时在学校里看书的时候,一直比较纳闷为会c++的书上会写C++/C语言教程,到了现在才明白,我去C++与C本来就是一体是一脉相承的
1037 0
|
6天前
|
存储 C语言
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
14 1
|
6天前
|
C语言 C++
C语言学习记录——内存函数(memcpy、memmove、memcmp、memset、模拟实现memcpy、模拟实现memmove)
C语言学习记录——内存函数(memcpy、memmove、memcmp、memset、模拟实现memcpy、模拟实现memmove)
15 3
|
6天前
|
C语言
C语言学习记录——鹏哥字符分类函数、字符转换函数
C语言学习记录——鹏哥字符分类函数、字符转换函数
10 2
|
6天前
|
安全 编译器 C语言
C语言学习记录——字符串相关函数及部分模拟(strcmp、strncmp、strncat、strncpy、strstr、strtok、strerror)
C语言学习记录——字符串相关函数及部分模拟(strcmp、strncmp、strncat、strncpy、strstr、strtok、strerror)
11 1
|
6天前
|
C语言
C语言学习记录——模拟字符串相关函数(strcpy、strlen、strcat)相关知识-const、typedef
C语言学习记录——模拟字符串相关函数(strcpy、strlen、strcat)相关知识-const、typedef
9 1