C语言之指针进阶篇(3)

简介: C语言之指针进阶篇(3)

今天我们学习指针难点之回调函数🆗🆗🆗。

首先我们用思维导图回顾一下前面的内容。

思维导图


回调函数

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

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。


案例1—计算器

就前文我们学习的计算器,我们再用回调函数来解决一下!🆗🆗🆗

#define _CRT_SECURE_NO_WARNINGS 1
//计算器
#include<stdio.h>
void meau()
{
  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;
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    meau();
    printf("请选择>");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      printf("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = Add(x, y);
      printf("ret=%d\n", ret);
      break;
    case 2:
      printf("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = Sub(x, y);
      printf("ret=%d\n", ret);
      break;
    case 3:
      printf("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = Mul(x, y);
      printf("ret=%d\n", ret);
      break;
    case 4:
      printf("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = Div(x, y);
      printf("ret=%d\n", ret);
      break;
    case 0:
      printf("退出游戏");
      break;
    default:
      printf("选择错误,重新选择\n");
      break;
    }
  } while (input);
  return 0;
}

回调函数

#define _CRT_SECURE_NO_WARNINGS 1
//计算器
#include<stdio.h>
void meau()
{
  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(*p)(int, int))//函数指针传参
{
  int x = 0;
  int y = 0;
  printf("请输入两个操作数\n");
  scanf("%d %d", &x, &y);
  int ret = p(x, y);//函数调用
  printf("ret=%d\n", ret);
}
int main()
{
  int input = 0;
  do
  {
    meau();
    printf("请选择>\n");
    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("退出游戏");
      break;
    default:
      printf("选择错误,重新选择\n");
      break;
    }
  } while (input);
  return 0;
}

解释如下:

在main函数中,没有直接去调用函数。而是把函数指针传参给另外的一个函数calc,在calc内部使用函数指针调用,通过函数指针就可以找到指针指向的函数,此刻被指向的函数就是回调函数。


像上图所示,通过calc函数调用Add函数指针,找到Add函数,就把Add函数称为回调函数。


calc称为回调函数的机制


老板>>组长>>员工


案例2—qsort函数

那出了上面回调函数的案例,还有一个经典回调函数的案例:qsort


qsort是一个库函数,底层使用的是快速排序的方式,对不同数据进行排序的。

这个函数可以直接使用。

这个函数可以用来排序任意类型的数据。

对数据进行排序方法很多:

冒泡排,序选择排序,插入排序,快速排序等等。


关于qsort函数  

NO1.

关于qsort函数的点--->qsort - C++ Reference (cplusplus.com)



需要包含头文件#include


  • 排序整型数组,两个整型可以直接使用>比较
  • 排序结构体数组,两个结构体的数据可能不能直接使用>比较

也就是不同类型的数据,比较大小的方法是有差异的


最后一个参数,排序不同数据的重要点,需要封装不同的函数去比较不同的数据的大小


void qsort(void* base, //指向了待排序数组第一个元素的首地址
         size_t num, //待排序数组的元素个数
         size_t size,//每个待排序数组元素的大小
         int (*compar)(const void* e1, const void* e2));
//函数指针,compar指向了一个函数,这个函数是用来比较两个元素的大小,
//e1和e2存放的是两个元素的地址
//在qsort内部调用这个函数,指向这个函数,这个函数就被称为回调函数
// 
//qsort内部怎么排序我们不需要过多去探讨
//const也暂不做讲解
//因为不知道要比较的元素类型,所以我们使用void*指针的类型,来统一存放各种类型的指针
NO2.

那怎样通过元素地址,去比较两个整型元素数据的大小呢?

int的数据:将void*类型的数据强制转化成(int*),再作差

当e1>e2,函数返回>0的值;

当e1

当e1=e2,函数返回=0

//整型
void compar_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
NO3.

那怎样通过元素地址,去比较两个字符元素数据的大小呢?


//字符串
void compar_stu_by_name(const void* e1, const void* e2)
{
  return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
  //return strcmp((*(struct Stu*)e1).name , (*(struct Stu*)e2).name);
}

NO4.

那有人询问为什么不直接对元素地址const void* e1和 const void* e2解引用?


作为void*指针不能直接解引用。


void* 类型的指针—不能进行解引用操作符,也不能进行+-整数的操作

void* 类型的指针是用来存放任意类型数据的地址

void* 无具体类型的指针

void*和int*和char*一样都是指针类型

#include<stdio.h>
int main()
{
  char a = 'x';
  char* pa = &a;
  int b = 1;
  void* p = &b;//存放int*
  p = &a;//存放char*
  return 0;
}

演示qsort函数的使用

#include<stdio.h>
#include<stdlib.h>
void print(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    printf("%d ", arr[i]);
  }
}
void qsort(void* base, 
           size_t num,
         size_t size,
         int (*compar)(const void*, const void*));
void compar_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
void test1()
{
  int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  print(arr, sz);
  printf("\n");
  qsort(arr, sz, sizeof(arr[0]), compar_int);
  print(arr, sz);
}
int main()
{
  test1();
  test2();
  return 0;
}

以上我只是以整型为例,结构体数据数组也是一样的逻辑,大家可以自行分析。

下面结构体:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>//strcmp的头文件
void qsort(void* base,
  size_t num,
  size_t size,
  int (*compar)(const void*, const void*));
struct Stu
{
  char name[20];
  int age;
};
//结构体数据怎么比较呢?
//按照年龄比较
//按照名字比较
//按照年龄
int compar_stu_by_age(const void* e1, const void* e2)
{
  return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
  //return (*(struct Stu*)e1).age - (*(struct Stu*)e2).age;
}
void test2()
{
  struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), compar_stu_by_age);
}
//按照名字
int compar_stu_by_name(const void* e1, const void* e2)
{
  return strcmp((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
  return strcmp((*(struct Stu*)e1).name ,(*(struct Stu*)e2).name);
  //return (*(struct Stu*)e1).name - (*(struct Stu*)e2).name;//❌❌
}
void test2()
{
  struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), compar_stu_by_name);
}
int main()
{
  test2();
  return 0;
}
  • 记住字符串的比较必须用strcmp函数
  • 两种方式去访问结构体
  • 所有要使用的变量必须在它被使用之前就声明了!!例如结构体必须在比较函数之前声明。
  • 有返回值return 就不能是void!!

只要qsort函数使用得当,可以对任何数据进行排序!🆗🆗

案例3—冒泡排序

(使用回调函数,模拟实现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 - 1 - i; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        int tmp = 0;
        tmp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = tmp;
      }
    }
  }
}
void print_arr(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
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);
  print_arr(arr, sz);
}
回调函数搞定各类型冒泡排序

经过分析冒泡排序,我们得到


void bubble_sort(void* base, size_t num, size_t size,


                           int (*cmp)(const void* e1, const void* e2))


cmp_int比较大小

以整型为例

int (*cmp)(const void* e1, const void* e2)e1是一个指针,存放了一个要比较的元素的地址。


e2是一个指针,存放了一个要比较的元素的地址。


e1指向的元素>e2指向的元素,返回>0的数字。


e1指向的元素0的数字。


e1指向的元素==e2指向的元素,返回>0的数字。


cmp是函数指针指向一个我们程序想要待排序的数组。


将比较函数cmp_int的地址传给cmp即可。

//比较大小
void cmp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
//这里就是将cmp_int的地址在调用函数bubble_sort时将其传过去即可。
cmp传参数
NO1.

有同学提出直接对待排序的数组首元素地址解引用找到e1的地址,然后通过一个元素的大小或者+1可以找到e2的地址,可以吗?当然不可以


  • 作为void*指针不能直接解引用。


void* 类型的指针—不能进行解引用操作符,也不能进行+-整数的操作

void* 类型的指针是用来存放任意类型数据的地址

void* 无具体类型的指针

void*和int*和char*一样都是指针类型


NO2.

有同学又提出那将void*的指针强制转换成我们想要的int*或double*等,再+1可以吗?           不可以,理由就是,强制转换存在在于我们公共的bubble_sort排序函数中时,不能随着待排序的数组数据类型不同而改变,我们只能改变不同数据类型的不同比较方法。


解决方案

      //if(arr[j]>arr[j+1])
      if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
      {
        int tmp = 0;
        tmp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = tmp;
      }
交换swap

当我们只知道元素的起始地址,并不知道元素的类型所以我们并没有合适的中间值类型tmp创建。所以我们换一种方法。


我们已知元素e1和e2的起始地址每个元素的大小

那我们可以用一个一个char类型的数据交换用for循环

直到每个元素的大小size结束,也就是元素交换完成。


//交换数据
void change(char* buf1, char* buf2,size_t size)
{
  char i = 0;
  for (i = 0; i < size; i++)
  {
    char tmp = 0;
    tmp = *buf1;
    *buf1=*buf2;
    *buf2 = tmp;
    buf1++;//*buf1++
    buf2++;//*buf2++
  }
}
总代码
//冒泡排序
#include<stdio.h>
#include<stdlib.h>
void print_arr(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
void bubble_sort(void* base, size_t num, size_t size,
  int (*cmp)(const void* e1, const void* e2))
{
  int i = 0;
  for (i = 0; i < num - 1; i++)
  {
    int j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      //if(arr[j]>arr[j+1])
      if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
      {
        change((char*)base + j * size, (char*)base + (j + 1) * size, size);
      }
    }
  }
}
//交换数据
void change(char* buf1, char* buf2,size_t size)
{
  char i = 0;
  for (i = 0; i < size; i++)
  {
    char tmp = 0;
    tmp = *buf1;
    *buf1=*buf2;
    *buf2 = tmp;
    buf1++;//*e1++
    buf2++;//*e2++
  }
}
//比较大小
void cmp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;//>0
}
void test1()
{
  int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  print_arr(arr, sz);
  printf("\n");
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
  print_arr(arr, sz);
}
int main()
{
  test1();
}

当然我们也可以用结构体类型去测试一下! 🆗🆗试试

//冒泡排序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//打印函数
void print_arr(int arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
//排序函数
void bubble_sort(void* base, size_t num, size_t size,
  int (*cmp)(const void* e1, const void* e2))
{
  int i = 0;
  for (i = 0; i < num - 1; i++)
  {
    int j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      //if(arr[j]>arr[j+1])
      if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
      {
        change((char*)base + j * size, (char*)base + (j + 1) * size, size);
      }
    }
  }
}
//交换数据函数
void change(char* buf1, char* buf2,size_t size)
{
  char i = 0;
  for (i = 0; i < size; i++)
  {
    char tmp = 0;
    tmp = *buf1;
    *buf1=*buf2;
    *buf2 = tmp;
    buf1++;//*e1++
    buf2++;//*e2++
  }
}
//结构体
struct Stu
{
  char name[20];//20
  int age;//4
};
//结构体数据怎么比较呢?
//按照年龄比较
//按照名字比较
//按照年龄
void compar_stu_by_age(const void* e1, const void* e2)
{
  return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
  //return (*(struct Stu*)e1).age - (*(struct Stu*)e2).age;
}
void test2()
{
  struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), compar_stu_by_age);
}
//按照名字
void compar_stu_by_name(const void* e1, const void* e2)
{
  return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
  //return strcmp((*(struct Stu*)e1).name , (*(struct Stu*)e2).name);
}
void test2()
{
  struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), compar_stu_by_name);
}
int main()
{
  test2();
}

我们把回调函数这种情况叫做泛型编程。即便泛型编程在C语言中比较牵强。


最后,提出一个问题上面的题目我们都是正序排序。那如果我们想要倒序排序,代码又要怎样去修改呢?什么代码可以修改,什么代码不能修改呢?


那其实我们也在上面提到过,我们的排序函数代码bubble_sort是不能修改的。


以整型为例,所以我们只能修改比较大小的函数cmp_int

✔✔✔

//倒叙
//比较大小
void cmp_int(const void* e1, const void* e2)
{
  return *(int*)e2 - *(int*)e1;//>0
}
//正序
//比较大小
void cmp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;//>0
}

✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!旗鼓相当

代码------→【gitee:唐棣棣 (TSQXG) - Gitee.com

联系------→【邮箱:2784139418@qq.com】

目录
相关文章
|
19天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
72 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
19天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
44 9
|
19天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
40 7
|
29天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
101 13
|
22天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
22天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
70 3
|
23天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
22天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
34 1
|
26天前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
26天前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。