剖析C语言回调函数

简介: 剖析C语言回调函数

概念:

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

实际的应用

简单的应用(计算器):

在我上一篇文章中,提到计算器的实现http://t.csdn.cn/X75YU

第一种方式过于冗余,我们现在用函数指针传递函数参数,然后用回调函数进行优化代码。

观察这部分冗余的代码,发现这4行除了传递的函数不一样,其余都一致,因此我们可以封装一个函数,用函数指针作为参数,调用回调函数实现!

int Cal(int (*p)(int, int),int a,int b)
{
  printf("请输入两个操作数\n");
  scanf("%d %d", &a, &b);
  printf("%d\n", p(a, b));
}

上面是Cal函数接收函数指针参数的实现,我们只需要用一个函数,就可以多功能的实现各种运算法则。我们只需要将运算法则的函数传入Cal里面,这时,各种运算法则的函数就是回调函数,Cal函数会在特定的条件去调用这些回调函数。

复杂的应用(模拟实现qsort函数)

在讲qsort函数之前,我们先回忆一下冒泡排序

//冒泡排序
int main()
{
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  //printf("%d\n", sz);
  for (int i=0;i<sz-1;i++)
  {
    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;
      }
    }
  }
  for (int i=0;i<sz;i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

我们发现冒泡排序有这样的缺点,在不考虑算法效率的前提下,当前的冒泡排序只能排序整型数据,不能排序浮点型,甚至结构体类型。

这时,就需要qsort函数出马了

qsort函数有两个特点:

1、q代表quick,是快速排序

2、可以排任意类型的数据


接下来,我们真正认识一下qsort函数。

头文件:stdlib.h

qsort函数一共有4个参数:

第一个参数是一个指针,指向我们需要排序的一串数字中,最起始的地址

第二个参数表示我们需要排序元素的个数

第三个参数表示每个元素的字节大小

第四个参数是接收比较大小函数的函数指针,本质上就是调用回调函数!

他的参数分别是比较大小两个数的地址,返回类型是int,如果左边大于右边,返回正数,如果左边等于右边返回0,如果左边小于右边返回负数。

实操qsort函数

qsort函数排序默认升序

认识void *

在此之前,我们首先了解void * 这种类型的指针。

它是一种无具体类型的指针

好处:它可以接收任意类型的地址,这也就解释了为何qsort函数可以排序任意类型的数据

缺点:正因为我们不知道他是什么类型的指针,所以不能直接解引用操作,也不能做指针运算(不知道步长是多少)。

因此我们在使用这种指针时,往往需要强制类型转换

对整型数据的qsort排序:

int cmp_int(const void* p1, const void* p2)
{
  return *(int*)p2 - *(int*)p1;
}
void test1()
{
  int arr[] = { 3,6,1,2,9,8,7,4,5,10};
  int num = sizeof(arr) / sizeof(arr[0]);
  int sz = sizeof(arr[0]);
  qsort(arr, num, sz, cmp_int);
  for (int i=0;i<num;i++)
  {
    printf("%d ", arr[i]);
  }
}
int main()
{
  test1();
  return 0;
}

使用qsort函数时,最关键在于第四个函数指针参数,需要我们自己书写一个函数进行传参,保证参数为两个const void*,返回类型是int,并且当左值大于右边的值返回正数……

对结构体类型数据的排序

#include<string.h>
struct stu
{
  char name[10];
  int age;
};
int cmp_stu_age(const void* p1, const void* p2)
{
  return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
void test2()
{
  struct stu arr[] = { {"wanger",20},{"zhangsan",30},{"lisi",15} };
  int num = sizeof(arr) / sizeof(arr[0]);
  int sz = sizeof(arr[0]);
  qsort(arr, num, sz, cmp_stu_age);
}
int cmp_stu_name(const void* p1, const void* p2)
{
  return strcmp((((struct stu*)p1))->name, ((struct stu*)p2)->name);
  //返回值要求正好与strcmp一致
}
void test3()
{
  struct stu arr[] = { {"wanger",20},{"zhangsan",30},{"lisi",15} };
  int num = sizeof(arr) / sizeof(arr[0]);
  int sz = sizeof(arr[0]);
  qsort(arr, num, sz, cmp_stu_name);
}
int main()
{
  //test2();//按照年龄进行排序
  test3();//按照名字进行排序
  return 0;
}

当我们对结构体类型数据排序时,要明确根据结构体里面的哪种数据排序,若是对字符串排序,可以运用strcmp函数,并且strcmp函数的返回值与qsort要求一致!

值得注意在对p1/p2进行结构体强制类型转换时,因为最后结果指针形式,直接用->操作符就能找到结构体的元素,但一定还要用括号括起来!然后再-> .涉及到结合性的问题!

手撕qsort函数

这里我们采用冒泡排序的思想,去实现一个适用于任意类型数据的排序

我们之前讨论到冒泡排序的缺点在于不能满足任意类型数据的排序,因此我们现在亟需解决这个问题。

问题一:

比较大小时,不能单纯使用大于,小于,考虑到结构体的因素。

解决方式:

运用到我们的自定义比较函数,然后用回调函数去调用。

问题二:

两两交换值时,不能单纯的交换,不同类型的数据略有差异

解决方式:

我们把所有数据转换为char*类型,交换数据时,是一个字节,一个字节进行交换,然后for循环满足一个元素的字节数大小,让整个元素交换

问题三:

参数只能接收整型的数据

解决方法:

仿照qsort原函数,用void* base接收,然后调用时,再强制类型转换为我们需要的类型

源码:

#include<stdio.h>
#include<string.h>
void Swap(char* p1, char* p2, int sz)
{
//全部转换为char*数据,然后循环一个字节一个字节去交换
  char tmp = 0;
  for (int i=0;i<sz;i++)
  {
    tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
    p1++;
    p2++;
  }
}
void bubble_qsort(void* base, int num, int sz, int (*cmp)(const void*, const void*))
{
  for (int i=0;i<num-1;i++)
  {
    for (int j=0;j<num-1-i;j++)
    {
      if (cmp((char*)base + j * sz, (char*)base + (j + 1) * sz) > 0)
        Swap((char*)base + j * sz, (char*)base + (j + 1) * sz, sz);
    }
  }
}
int cmp_int(const void* p1, const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}
void test1()
{
  int arr[] = { 3,1,5,2,9,6,10,4,7,8 };
  int num = sizeof(arr) / sizeof(arr[0]);
  int sz = sizeof(arr[0]);
  bubble_qsort(arr, num, sz, cmp_int);
}
struct stu
{
  char name[10];
  int age;
};
int cmp_stu_name(const void* p1, const void* p2)
{
  return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
void test2()
{
  struct stu arr[] = { {"wanger",30},{"zhangsan",25},{"lisi",40} };
  int num = sizeof(arr) / sizeof(arr[0]);
  int sz = sizeof(arr[0]);
  bubble_qsort(arr, num, sz, cmp_stu_name);
}
int cmp_stu_age(const void* p1, const void* p2)
{
  return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
void test3()
{
  struct stu arr[] = { {"wanger",30},{"zhangsan",25},{"lisi",40} };
  int num = sizeof(arr) / sizeof(arr[0]);
  int sz = sizeof(arr[0]);
  bubble_qsort(arr, num, sz, cmp_stu_age);
}
int main()
{
  //test1();
  test2();
  //test3();
  return 0;
}


相关文章
|
搜索推荐 C语言
c语言从入门到实战——回调函数与qsort的讲解和模拟实现
回调函数是一个函数,它作为参数传递给另一个函数,并且能够在该函数内部被调用。在C语言中,回调函数通常被用于实现事件处理和排序算法中。 `qsort`是C标准库中的一个排序函数,它可以对任意类型的数组进行排序。`qsort`需要三个参数:要排序的数组、数组元素的个数和一个指向回调函数的指针。回调函数必须满足两个条件:能够比较数组中的元素,返回一个整数表示它们之间的大小关系;并且它应该能够被`qsort`函数调用。 回调函数是一种在编程中广泛使用的技术,它允许一个函数作为参数传递给另一个函数,并在需要时被调用。这种机制使得代码更加灵活和可重用。
174 0
|
C语言
c语言进阶部分详解(经典回调函数qsort()详解及模拟实现)
c语言进阶部分详解(经典回调函数qsort()详解及模拟实现)
152 0
|
Unix Linux API
深入浅出剖析C语言函数指针与回调函数
深入浅出剖析C语言函数指针与回调函数
257 1
|
C语言
通过模拟实现计算器介绍函数指针数组和回调函数的用法【C语言/指针/进阶】
通过模拟实现计算器介绍函数指针数组和回调函数的用法【C语言/指针/进阶】
165 0
|
Linux C语言 开发者
深入浅出剖析C语言函数指针与回调函数(二)
深入浅出剖析C语言函数指针与回调函数(二)
115 0
|
搜索推荐 程序员 编译器
神奇的库函数qsort【详解指向函数指针数组的指针、回调函数、模拟实现qsort函数】【C语言/指针/进阶/程序员内功修炼】【下】
神奇的库函数qsort【详解指向函数指针数组的指针、回调函数、模拟实现qsort函数】【C语言/指针/进阶/程序员内功修炼】【下】
167 0
|
编译器 C语言
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。(下)
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。
179 0
|
安全 搜索推荐 Unix
【C语言】《回调函数》详细解析
回调函数是指一个通过函数指针调用的函数。它允许将一个函数作为参数传递给另一个函数,并在特定事件发生时执行。这种技术使得编程更加灵活,可以动态决定在何时调用哪个函数。
797 1
|
SQL 数据库 C语言
【sqlite的C语言访问接口】执行SQL语句的接口------sqlite3_exec回调函数的使用
【sqlite的C语言访问接口】执行SQL语句的接口------sqlite3_exec回调函数的使用
|
算法 搜索推荐 程序员
C语言中的函数指针和回调函数
C语言中的函数指针和回调函数
194 2