【C进阶】回调函数(指针进阶2,详解,小白必看)(下)

简介: 【C进阶】回调函数(指针进阶2,详解,小白必看)(下)

8.3冒泡排序思想实现qsort

使用冒泡排序的思想来实现一个类似于qsort这个功能的冒泡排序函数bubble_sort()

9da07a74ca304d7fb41be04860ad7af9.png

各参数组成:

base,num,width,cmp函数,elem1和elem2


接下来说明一下qsort各参数的设计思路:

①为什么base定义为void*类型?

void*是 C 语言中的一种通用指针类型可以指向任意类型的数据。在 qsort 函数中,由于需要对不同类型的数据进行排序,因此需要使用void*类型的指针,以便能够接受各种类型的数据,base参数是一个指向要排序数组第一个元素的指针。由于它要指向任意类型的数据,所以使用void*是最通用的。


举例:

int a = 10;
void* p = &a;


优点:

无具体类型的指针,所以它可以接收任何类型的地址

缺点:

*p  //-->  void*的指针不能解引用操作符
p++  //-->  因为不知道是什么数据类型,不能直接++


正确用法:

*(int*)p;//也可以转化成其它类型


②为什么num要设计成size_t?

  • size_t 在 C 语言中是一种无符号整数类型,用于表示大小、长度和索引值,使用它表示数组元素个数很合适。
  • 数组元素个数是一个非负的值,使用无符号类型size_t可以避免出现负数,更加合理。
  • 使用专门的 size_t 类型比简单的 unsigned int 更能表明这个参数的语义 - 它表示一个大小或长度,而不是一个整数。


③为什么width要设计成size_t

在qsort函数中,width参数表示每个数组元素的大小(以字节为单位)

width表示一个"大小"的概念,使用size_t类型可以更清楚地表达这个参数的语义。

width的单位是字节,size_t通常是无符号整数类型,不会有负数,所以更适合表示正的值。

width需要足够大的范围来表示任意数据类型的大小。size_t类型依赖平台,但通常是机器字大小,能满足大多数数据元素的大小需求。

在访问数组元素时,索引位置需要乘以元素大小才能获得地址偏移量。既然索引是size_t类型,那么两者相乘的结果也应该是size_t类型,以避免溢出问题。


④为什么要设计一个函数指针cmp?

qsort 本身是一个通用的排序函数,不能知道要排序的元素类型,也就无法直接比较两个元素。采用函数指针可以将比较操作的实现留给用户。

通过函数指针,用户可以自行实现针对自己数据类型的比较函数,将具体的比较逻辑封装起来。这提高了qsort的通用性和灵活性。

对于不同的数据类型,比较操作的逻辑可能不同。使用函数指针实现可以避免在qsort中写大量的条件分支逻辑。

函数指针提供了一种扩展机制。如果用户需要改变比较操作的逻辑,只需要传入一个新的函数指针就可以,而不需要改动qsort函数本身。


⑤为什么elem1和elem2类型是const void*类型?

qsort要对任意类型的数据进行排序,所以比较函数需要能处理任意类型的元素。使用void*可以指向任意类型的数据,const用于表示不应修改指针指向的内容。

qsort要对任意类型的数据进行排序,所以比较函数需要能处理任意类型的元素。使用void*可以指向任意类型的数据,const用于表示不应修改指针指向的内容。

在调用qsort时可以直接将数组元素的地址强制转换为const void* 类型传给比较函数,简化调用。

qsort函数本身不需要了解元素具体类型,只要把void*指针传给比较函数,由比较函数转换并解释指针内容即可。

开始模拟实现:冒泡排序大题思路不需要改变,只需要对排序方式进行即可

int cmp_int(const void* p1, const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
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++;
  }
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void bubble_sort(void* base, size_t num, size_t width,
  int (*cmp)(const void* p1, const void* p2))
{
    //冒泡排序大题思路不需要改变,只需要对排序方式进行即可
  //要确定趟数
  size_t i = 0;
  for (i = 0; i < num - 1; i++)
  {
    int flag = 0;//假设已经有序了
    //一趟冒泡排序的过程
    size_t j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      //两个相邻的元素比较
      //arr[j] arr[j+1]
      if (cmp((char*)base + j * width, (char*)base+(j+1)*width)>0)
      {
        //交换
        flag = 0;
        Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
      }
    }
  }
}
void print_arr(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
void test3()
{
  int arr[] = { 3,1,5,2,4,9,8,6,5,7 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  printf("排序前:");
  print_arr(arr, sz);
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
  printf("排序后:");
  print_arr(arr, sz);
}


qsort对于代码的解析:

d79889caa210422cb466483dfce4256b.png

如何交换数据:

为什么这个地方写成char*?

在bubble_sort函数中,由于不知道base指向的数据类型,因此需要将其强制转换为char*类型,从而让指针的步长为1。这样,在进行指针的加减运算时,就可以根据width参数来控制指针的步长,从而实现对任意数据类型的排序。

注意:width对于char来说是1个字节的意思,但是对于其它类型来说就不是了,width是根据参数不同来设置不同的数据类型,控制指针步长的。


交换的流程:1,2,3,4表示顺序

baee772ae5d94ba5b139a65e775ac0fb.png

代码的整体流程:

9ad0722e229c44ca81912d62b8019d6f.png

排序:

28e15e68379d40da94e38b2b3e90c48f.png

通过新定义的冒泡排序排序年龄以及姓名

struct Stu {
  char name[20];
  int age;
};
int cmp_stu_by_age(const void*p1,const void* p2)
{
  return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
  return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
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++;
  }
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void bubble_sort(void* base, size_t num, size_t width,
  int (*cmp)(const void* p1, const void* p2))
{
  //要确定趟数
  size_t i = 0;
  for (i = 0; i < num - 1; i++)
  {
    int flag = 0;//假设已经有序了
    //一趟冒泡排序的过程
    size_t j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      //两个相邻的元素比较
      //arr[j] arr[j+1]
      if (cmp((char*)base + j * width, (char*)base+(j+1)*width)>0)
      {
        //交换
        flag = 0;
        Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
      }
    }
  }
}
void print_arr3(struct Stu* s, int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", (s + i)->age);
    //printf("%s ", (*(arr+i)).name);
    //printf("%s ", arr[i].name);
  }
  printf("\n");
}
void print_arr4(struct Stu* s, int sz)
{
  int i = 0;
    for (i = 0; i < sz; i++)
    {
      printf("%s ", (s+i)->name);
      //printf("%s ", (*(arr+i)).name);
      //printf("%s ", arr[i].name);
    }
    printf("\n");
}
void test4()
{
  struct Stu s[] = { {"zhangsan",30} ,{"lisi",25},{"wangwu",50}};
  int sz = sizeof(s) / sizeof(s[0]);
  //测试按年龄来排序
  /*print_arr3(s, sz);
  bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
  print_arr3(s, sz);*/
  //测试按照名字来排序
  print_arr4(s, sz);
  bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
  print_arr4(s, sz);
}


按姓名排序:

687a7718eb674eb8931dcc0999c46f45.png

按年龄排序

0370439448de4ec0ab8f8489f4ff843a.png

这篇文章到这里就结束了,如有错误欢迎大家指正,然后下来就是这篇关于sizeof和strlen的详细总结:,指针和数组笔试题解析总结,传送门--> http://t.csdn.cn/aKVsj

欢迎大家来访。

相关文章
|
5月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
1月前
|
C++
指针中的回调函数与qsort的深度理解与模拟
本文详细介绍了回调函数的概念及其在计算器简化中的应用,以及C++标准库函数qsort的原理和使用示例,包括冒泡排序的模拟实现。
17 1
|
1月前
魔法指针 之 函数指针 回调函数
魔法指针 之 函数指针 回调函数
13 0
|
5月前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
5月前
|
存储 C语言 C++
指针进阶(函数指针)(C语言)
指针进阶(函数指针)(C语言)
|
5月前
|
编译器 C语言
指针进阶(数组指针 )(C语言)
指针进阶(数组指针 )(C语言)
|
5月前
|
搜索推荐
指针进阶(2)
指针进阶(2)
47 4
|
5月前
指针进阶(3)
指针进阶(3)
41 1
|
5月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
49 0
|
1月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
22 0