指针详解(冒泡排序、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也指向一个元素
);

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

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

相关文章
|
23天前
|
搜索推荐 C语言 C++
【C指针(五)】6种转移表实现整合longjmp()/setjmp()函数和qsort函数详解分析&&模拟实现3
【C指针(五)】6种转移表实现整合longjmp()/setjmp()函数和qsort函数详解分析&&模拟实现
|
3月前
|
Unix
网络编程之 信号捕捉器(函数指针与回调函数)(2)
sigaction()函数 前面我们讲到的内容已经足以用来防止僵尸进程生成的代码。之所以博主还要介绍sigaction()函数是因为它类似于signal()函数,而且完全可以代替后者,也更稳定(主要是书上介绍到了
31 1
|
3月前
|
Linux
网络编程之 信号捕捉器(函数指针与回调函数)(1)
接着我们的信号说下去 之前博主给大家分享到了信号的概念和初步介绍signal函数的形式后就没有继续往下介绍了,实在是因为时间不够,那个时候博主还要上课,现在博主放假了就好好给大家分享一下如何注册信号捕捉,以及信号捕捉器的妙用。
34 1
|
3月前
|
存储 C++
C生万物 | 从浅入深理解指针【第三部分】(转移表的实现)
C生万物 | 从浅入深理解指针【第三部分】(转移表的实现)
|
1月前
|
存储 C语言
c语言函数指针和指针函数的区别,以及回调函数的使用。
c语言函数指针和指针函数的区别,以及回调函数的使用。
9 0
|
1月前
|
存储 程序员 API
C函数指针与回调函数
C函数指针与回调函数
25 0
|
2月前
|
C语言 C++
C语言之指针进阶篇_回调函数(3)
C语言之指针进阶篇_回调函数(3)
23 0
|
3月前
|
编译器 C++
函数指针和回调函数对函数取地址和直接使用函数名的差距
函数指针和回调函数对函数取地址和直接使用函数名的差距
40 0
C生万物 | 从浅入深理解指针【第四部分】(qsort的使用和模拟实现)
C生万物 | 从浅入深理解指针【第四部分】(qsort的使用和模拟实现)
|
17天前
|
存储 C语言
C语言 — 指针进阶篇(下)
C语言 — 指针进阶篇(下)
20 0