指针详解(冒泡排序、qsort、回调函数、转移表)(三)

简介: 指针详解(冒泡排序、qsort、回调函数、转移表)(三)

模拟计算器案例

//使用回调函数改造前
/*
实现一个计算器
这个计算器可以实现整数的加减乘除
*/
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)
{
  if (y == 0) {
    printf("除数不能为0\n");
    return -1;
  }
  else {
    return x / y;
  }
}
void menu()
{
  printf("*************************\n");
  printf("****  1.add   3.sub  ****\n");
  printf("****  3.mul   4.div  ****\n");
  printf("****  0.exit         ****\n");
  printf("*************************\n");
}
int main()
{
  int x, y;
  int input = 1;
  int ret = 0;
  do
  {
    menu();
    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;
}

这是一个基本的计算器程序,可以实现整数的加减乘除和退出功能。程序使用了一个简单的菜单驱动方式,让用户可以通过输入数字来选择要执行的操作。但是有大量的代码复用,在当前的代码中,加、减、乘、除的操作都是类似的,但是代码却是重复的。如果能够将这些操作封装到一个函数中,并通过参数来区分不同的操作,那么代码就会更加简洁和易于维护。

解决这些问题的思路如下:

1、使用转移表

2、使用回调函数

1、回调函数

回调函数是什么?

回调函数就是一个通过函数指针调用的函数。

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在

特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

相同(相似)的代码出现了多份,就显得有些冗余,有没有办法,简化一些呢?
我们可以把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能。

回调函数改造思路:

1、定义回调函数,该函数接受两个整数参数并返回一个整数。
2、在主函数中,创建一个数组,其中包含所有可能的操作符和对应的回调函数。
3、根据用户输入的操作符,查找相应的回调函数并调用它。
4、将结果存储在一个变量中,并将其打印出来。

1、先定义一个函数calc,这个函数接受一个函数指针pf作为参数。
2、在calc函数内部,首先定义了三个整数变量:x、y和ret。
3、然后,程序会输出"请输入两个操作数:",并使用scanf函数从用户处获取两个整数输入,分别赋值给x和y。
4、接着,使用函数指针pf调用函数,并将x和y作为参数传递。函数的返回值被赋值给ret。
5、最后,程序会输出这个返回值。

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)
{
  if (y == 0) {
    printf("除数不能为0\n");
    return -1;
  }
  else {
    return x / y;
  }
}
void menu()
{
  printf("*************************\n");
  printf("****  1.add   3.sub  ****\n");
  printf("****  3.mul   4.div  ****\n");
  printf("****  0.exit         ****\n");
  printf("*************************\n");
}
void calc(int(*pf)(int, int))
//通过函数指针调用函数
//把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,
//被调用的函数就是回调函数
{
  int x = 0, y = 0, ret = 0;
  printf("请输入两个操作数:");
  scanf("%d %d", &x, &y);
  ret = pf(x, y);
  printf("%d\n", ret);
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    menu();
    //用回调函数的方法解决switch太长的问题
    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;
}

2、转移表

转移表改造思路:
转移表是一种用于实现多路复用的数据结构,可以用来实现复杂的菜单驱动程序。使用转移表可以将用户输入的操作符映射到相应的操作上。

1、创建一个转移表,该表以操作符为键,以对应的操作函数为值。
2、在主函数中,使用scanf()函数读取用户输入的操作符。
3、使用转移表查找相应的操作函数,并将其调用。
4、将结果存储在一个变量中,并将其打印出来。

为什么要用NULL?

因为选项中0是exit,所以不能把Add放在第一个

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)
{
  if (y == 0) {
    printf("除数不能为0\n");
    return -1;
  }
  else {
    return x / y;
  }
}
void menu()
{
  printf("*************************\n");
  printf("****  1.add   3.sub  ****\n");
  printf("****  3.mul   4.div  ****\n");
  printf("****  0.exit         ****\n");
  printf("*************************\n");
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    menu();
    //函数指针数组的方法解决switch太长的问题
    //这里的函数指针数组,被称为转移表
    int (*pfArr[])(int, int) = { NULL,Add,Sub,Mul,Div };
    //              0    1   2   3
    printf("请选择:");
    scanf("%d", &input);
    if (input == 0)
    {
      printf("退出计算器\n");
    }
    else if (input >= 1 && input <= 4)
    {
      printf("请输入两个操作数:");
      scanf("%d %d", &x, &y);
      ret = pfArr[input](x, y);
      printf("%d\n", ret);
    }
    else {
      printf("选择错误,重新选择\n");
    }
  } while (input);
  return 0;
}

冒泡排序

先解析一下void bubbleSort(int arr[], int sz)

冒泡排序的核心思想就是:两两相邻的元素进行比较先写一个基本框架再实现函数定义部分 ,先外层循环确定趟数,再内层循环确定每趟交换的对数,最后判断相邻元素大小,如果不满足顺序就交换

void bubble_sort(int* arr, int sz)
{
  //趟数
  int i = 0, j = 0;
  for (i = 0; i < sz - 1; i++)
  {
    //一趟冒泡排序的过程
    //两两元素相邻比较
    for (j = 0; j < sz - i - 1; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        int t = arr[j];
        arr[j] = arr[j + 1];
        arr[j+1] = t;
      }
    }
  }
}
void print(int* arr, int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
int main()
{
  //将一组整数排序为升序
  int arr[10] = { 3,1,2,4,5,6,7,8,9,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz);
  print(arr, sz);
  return 0;
}

但是上述代码还可以再进行优化,试想一下,如果要排序的数组是

9,0,1,2,3,4,5,6,7,8  第一趟排序完便已经升序了 ,但是还在不停的循环。所以,我们可以这样优化。

加入flag变量,表示数组当前是否有序。而判断有序的方法,则是如果一趟冒泡排序下来,没有一对交换,则证明有序。 反之,如果有交换,则flag置为0,表示无序,则继续下一趟冒泡排序。这样,就可以节省时间

strcmp函数

#include <stdio.h>
#include <string.h>
int main ()
{
   char str1[15];
   char str2[15];
   int ret;
   strcpy(str1, "abcdef");
   strcpy(str2, "ABCDEF");
   ret = strcmp(str1, str2);
   if(ret < 0)
   {
      printf("str1 小于 str2");
   }
   else if(ret > 0) 
   {
      printf("str1 大于 str2");
   }
   else 
   {
      printf("str1 等于 str2");
   }
   return(0);
}

特别注意:strcmp(const char *s1,const char * s2) 这里面只能比较字符串,即可用于比较两个字符串常量,或比较数组和字符串常量,不能比较数字等其他形式的参数。

ANSI 标准规定,返回值为正数,负数,0 。而确切数值是依赖不同的C实现的。

当两个字符串不相等时,C 标准没有规定返回值会是 1 或 -1,只规定了正数和负数。

有些会把两个字符的 ASCII 码之差作为比较结果由函数值返回。

qsort函数

函数调用的使用:qsort    quick sort

qsort 是库函数,这个函数可以完成任意类型的排序

1.qsort确实可以排序任意的数据类型

2.使用的时候,需要使用者传递一个函数的地址,

这个函数用来比较待排序数组中的两元素

测试qsort函数排序整型数据

正常使用冒泡排序

void bubbleSort(int arr[], int sz)
{
  int i = 0;
  //趟
  for (i = 0; i < sz - 1; i++)
  {
    //每一趟冒泡排序的过程
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        int t = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = t;
      }
    }
  }
}
***通过qsort实现冒泡排序***(重点)

void bubbleSort(int arr[], int sz)

解析在冒泡中,以下为用模拟qsort的解析

此为模拟void bubbleSort(int arr[], int sz)的函数

arr进入void bubbleSort2函数后

执行以下模拟冒泡的语句

每两个元素依次进入cmp进行比较

为什么要用强制转换:因为void*类型是方便输入的数据为任意类型,进入后不是int型无法计算,强制类型转换后才可以进行运算

返回值大于0执行Swap交换语句

交换后继续循环判断,直到结束

int cmp_int(const void*p1,const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}
void print_arr(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
void Swap(char* buf1, char* buf2, unsigned int width)
{
  int i = 0;
  for (i = 0; i < width; i++)//一个一个字节交换
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}
void bubbleSort2(void* base, unsigned int sz, unsigned int width, int (*cmp)(const void* p1,const  void* p2))
/*
void* base - 这是要排序的内存块的起始地址。
unsigned int sz - 这是内存块的大小,以字节为单位。
unsigned int width - 这是每个元素的大小,以字节为单位。
int (*cmp)(const void* p1, const void* p2) - 这个函数应该返回一个整数,表示两个元素的相对顺序。
如果第一个元素应该排在第二个元素之前,那么这个函数应该返回负数。如果两个元素相等,
那么这个函数应该返回0。如果第一个元素应该排在第二个元素之后,那么这个函数应该返回正数。
*/
{
  int i = 0;
  //趟
  for (i = 0; i < sz - 1; i++)
  {
    //每一趟冒泡排序的过程
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      //if (arr[j] > arr[j + 1])
      if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
        //这里为什么要用char*
        //如果用int*,则需要跳过24/4个字节,才能表示为一个结构体元素
        //如果要计算结构体的大小,转换为char*最好算
      {
        /*int t = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = t;*/
        //交换
        Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
      }
    }
  }
}
void test3()
{
  int arr[] = { 3,1,5,7,9,2,4,0,8,6 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  //设计并实现bubbleSort2(),这个函数能够排序任意类型的数据
  bubbleSort2(arr, sz, sizeof(arr[0]), cmp_int);
  print_arr(arr, sz);
}
int main()
{
  test3();
  return 0;
}

测试qsort函数排序结构体数据
int cmp_int(const void*p1,const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}
void print_arr(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
void Swap(char* buf1, char* buf2, unsigned int width)
{
  int i = 0;
  for (i = 0; i < width; i++)//一个一个字节交换
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}
void bubbleSort2(void* base, unsigned int sz, unsigned int width, int (*cmp)(const void* p1,const  void* p2))
/*
void* base - 这是要排序的内存块的起始地址。
unsigned int sz - 这是内存块的大小,以字节为单位。
unsigned int width - 这是每个元素的大小,以字节为单位。
int (*cmp)(const void* p1, const void* p2) - 这个函数应该返回一个整数,表示两个元素的相对顺序。
如果第一个元素应该排在第二个元素之前,那么这个函数应该返回负数。如果两个元素相等,
那么这个函数应该返回0。如果第一个元素应该排在第二个元素之后,那么这个函数应该返回正数。
*/
{
  int i = 0;
  //趟
  for (i = 0; i < sz - 1; i++)
  {
    //每一趟冒泡排序的过程
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      //if (arr[j] > arr[j + 1])
      if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
        //这里为什么要用char*
        //如果用int*,则需要跳过24/4个字节,才能表示为一个结构体元素
        //如果要计算结构体的大小,转换为char*最好算
      {
        /*int t = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = t;*/
        //交换
        Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
      }
    }
  }
}
struct Stu
{
  char name[20];
  int age;
};
int cmp_stu_by_name(const void* p1,const void* p2)
{
  return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int cmp_stu_by_age(const void* p1, const void* p2)
{
  return (((struct Stu*)p1)->age - ((struct Stu*)p2)->age);
}
void test4()
{
  struct Stu arr[] = { {"zhangsan",18},{"list",35}, {"wangwu",15} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  //sizeof(arr[0])
  //一个结构体元素24个字节
  bubbleSort2(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
  //打印arr数组的内容
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%s %d\n", arr[i].name, arr[i].age);
  }
}
int main()
{
  test4();
  return 0;
}

这是按年龄排序的结果

这是按名字排序的结果

void qsort(
   void* base,//base 指向了要排序的数组的第一个元素    (待排序数组的起始位置)    
 //qsort可能排序任意类型的数据,为了能够接收任意的可能的指针类型,设计成void*
   size_t num,//base指向的数组中的元素个数(待排序的数组的元素个数)
   size_t size,//base指向的数组中元素的大小(待排序的数组的元素大小,单位是字节)
   int(*compar)(const void*p1, const void*p2)
   //该函数指针指向的是一个函数
   //指向的函数是用来比较待排序数组中的两个元素的
 //函数的使用者提供一个函数
   //函数指针 - 指针指向的函数是用来比较数组中的2个元素的
   //p1指向一个元素,p2也指向一个元素
);

如果你感觉上述的代码对你有帮助,可以给我点个赞吗?

创作不易,谢谢各位的点赞,咱们下期见!

相关文章
|
1月前
|
C++
指针中的回调函数与qsort的深度理解与模拟
本文详细介绍了回调函数的概念及其在计算器简化中的应用,以及C++标准库函数qsort的原理和使用示例,包括冒泡排序的模拟实现。
15 1
|
1月前
|
算法 搜索推荐 C语言
【C语言篇】深入理解指针4(模拟实现qsort函数)
【C语言篇】深入理解指针4(模拟实现qsort函数)
21 2
|
1月前
魔法指针 之 函数指针 回调函数
魔法指针 之 函数指针 回调函数
12 0
|
5月前
|
机器学习/深度学习 搜索推荐 算法
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
|
6月前
|
算法
指针(6)---qsort函数
指针(6)---qsort函数
35 0
|
6月前
指针(5)---回调函数
指针(5)---回调函数
29 0
|
6月前
|
C语言
指针(4)---转移表
指针(4)---转移表
34 0
|
5月前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
6月前
|
算法 搜索推荐 C语言
c函数指针与回调函数
c函数指针与回调函数
43 2
|
1月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
22 0