【C语言】指针深入讲解(下)

ops/2024/9/20 3:54:40/ 标签: c语言, 开发语言

目录

  • 前言
  • 回调函数
    • 回调函数的概念
    • 回调函数的使用
  • qsort函数的使用和模拟实现
    • qsort函数的介绍
    • qsort函数的使用
    • qsort函数模拟实现

前言

今天我们来学习指针最后一个知识点回调函数,这个知识点也很重要,希望大家能坚持学习下去。
没学习之前指针知识内容的,可以点击这里进行学习。

回调函数

回调函数的概念

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

回调函数的使用

在指针深入讲解(中)篇中我们写的计算机的实现的代码中。
我们设计了实现功能的函数。

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}

在没学习回调函数之前,主函数中有很多冗余的地方。
下面代码,scanf和printf函数大量重复出现。

int main()
{int x, y;int input = 1;int ret = 0;
do
{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");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;
}

我们观察功能实现的函数可以发现,函数的结构都是 int 函数名 (int,int) 的形式,这样就可以把函数的地址作为参数传给这样类型的函数指针,指针指向那个函数就调用那个函数,这就是使用的回调函数

用回调函数的形式,简化计算器实现的主函数代码。

void calc(int(*pf)(int, int))
{int ret = 0;int x, y;printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}
int main()
{int x, y;int input = 1;int ret = 0;
do
{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");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;
}

这样代码就简化了很多,pf是指向函数的指针,当我们选择哪个功能时,会把实现该功能的函数地址传给pf指针,当条件满足,就会通过pf调用该函数。

qsort函数的使用和模拟实现

qsort函数的介绍

在这里插入图片描述

参数介绍

参数名含义
base指向要排序的数组的第一个对象的指针
num指向的数组中的元素数。是无符号整型,size_t
size数组中每个元素的大小(以字节为单位)。是无符号整型。size_t
compar指向比较两个元素的函数的指针。此函数被重复调用以比较两个元素。

最后一个参数很重要单独说明一下
在这里插入图片描述
compar是指向比较函数的指针,返回值为int型。
返回 <0说明p1指向的元素小于p2指向的元素。
返回 =0说明p1指向的元素等于p2指向的元素。
返回 >0说明p1指向的元素大于p2指向的元素。

qsort函数的使用

使⽤qsort函数排序整型数据

#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}
int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}

使⽤qsort排序结构数据

struct Stu //学⽣
{char name[20];//名字int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{test2();test3();return 0;
}

qsort函数模拟实现

用回调函数,模拟实现qsort函数。
qsost底层采用的是快速排序的方法,在这里我们使用更简单的冒泡排序的排序算法来模拟实现qsort函数,快排会在学习数据结构时讲解。
我们要实现的qsort是可以针对任何数据进行排序,那想一下我们知道用户使用这个函数的时候是拿来排序什么数据吗?显然是不知道的,所以在内部实现时,我们需要更改什么呢?分析如下:

  1. 比较的方法
    由于不知道用户排序的数据类型,传过来的数组首元素地址我们必须使用void*指针接收,是不能进行解引用的,且数据类型是不能传参的,那我们该怎么找到相邻元素比较呢?
    于是我们在参数中添加了数组元素的大小(即宽度,一个元素占几个字节,这是用户可以传参的),这样就能找到相邻元素了
    通过首元素地址加减上一个数组元素占几个字节就可以找到相邻元素。
    公式为:元素地址=首元素地址+数组元素的宽度 * 数组元素下标
    在这里插入图片描述

注意:要把base强制类型转化为char型指针

接下来就是如何比较,由于我们不知道用户排序什么数据,所以没办法实现两个数据的比较,例如整数可以直接使用关系操作符,而字符串需要strcmp函数等等,于是我们把比较两个数据大小的函数交给用户去实现,所以在参数中使用了一个函数指针。

if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)

这里我们默认还是qsort的比较规则,用户实现compare函数时遵守:当第一个元素大于第二个元素时,就返回大于0的数字,此时我们交换,按这个规则排序出来为升序,反之为降序。

2.交换数据的方式
同样的是,我们不知道数据类型,但我们知道数据的大小,所以我们可以一个一个字节的交换

void _swap(void *p1, void * p2, int size)
{int i = 0;for (i = 0; i< size; i++){char tmp = *((char *)p1 + i);*(( char *)p1 + i) = *((char *) p2 + i);*(( char *)p2 + i) = tmp;}
}

这样我们就完成了qsort函数的模拟实现。
源代码如下(排序整数):

#include<stdio.h>
void my_squrt(void* base, size_t num, size_t size, int (*compar)(void* p1, void* p2))
{int i = 0;int j = 1;for (i = 0; i < num - 1; i++){for (j = 0; j < num - i-1; j++){//判断大小if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0){//交换数据for (int k = 0; k < size; k++){char tmp = *((char*)base + j * size + k);*((char*)base + j * size + k) = *((char*)base + (j + 1) * size + k);*((char*)base + (j + 1) * size + k) = tmp;}}}}
}
//整数的比较函数
int int_cmp(void* p1, void* p2)
{return *((int*)p2) - *((int*)p1);
}int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; \int sz = sizeof(arr) / sizeof(arr[0]);my_squrt(arr,sz,sizeof(arr[0]), int_cmp);int i = 0;//打印for (i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;
}

总结:
qsout函数是典型的回调函数的例子,排序时我们不知道用户传进来的数据是什么类型,所以使用void *,把比较函数的实现方法交给用户实现,把实现函数通过函数指针传给qsout函数,在qsout函数内部比较时调用该函数。这就是回调函数。


感谢大家的观看, 大家可以在评论区留言,你们的支持就是我最大的动力。

http://www.ppmy.cn/ops/112587.html

相关文章

基于Qt的串口调试工具串口常见问题

1.项目地址 https://github.com/zhangjiechina001/SerialPortTool 2.使用注意 串口的所有参数波特率、数据位、校验位、停止位、控制流都需要设置正确&#xff0c;设置错了有时会连接上但是传输的数据会很奇怪&#xff0c;有时直接连接不上了 3.串口通讯参数解释 串口通讯&a…

【北京迅为】《STM32MP157开发板使用手册》- 第二十八章Cortex-M4外部中断实验

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

【pytorch学习笔记,利用Anaconda安装pytorch和paddle深度学习环境+pycharm安装---免额外安装CUDA和cudnn】

作者链接: link 一、安装pytorch环境 1.打开打开anaconda的终端后 conda env list然后创建一个名字叫pytorch&#xff0c;python是3.8版本的环境 conda create -n pytorch python3.8再次看环境 conda env list# conda environments: #显示如下环境 base …

项目测试用例:

项目概述 该项目是一款网上点餐系统&#xff0c;满足普通商家和普通用户的基本需求&#xff0c;主要有两大功能模块&#xff0c;分别是管理员模块&#xff08;商家端&#xff09;和用户模块&#xff08;客户端&#xff09;。系统供管理员登录和普通用户&#xff0c;登录进去会有…

ROS 编程入门的介绍

2.1 创建 ROS 功能包 ROS&#xff08;Robot Operating System&#xff09;是一种开源的机器人软件框架&#xff0c;广泛用于机器人开发中。通过使用 ROS&#xff0c;开发者可以轻松创建和管理机器人应用程序。在本节中&#xff0c;我们将介绍如何创建一个 ROS 功能包并实现一些…

python-简单的数据结构

题目描述 小理有一天在网上冲浪的时候发现了一道很有意思的数据结构题。 该数据结构形如长条形。 一开始该容器为空&#xff0c;有以下七种操作。 1 a从前面插入元素 a ; 2 从前面删除一个元素; 3 a从后面插入一个元素; 4 从后面删除一个元素; 5 将整个容器头尾翻转; 6 输出个…

qt-creator-10.0.2之后版本的jom.exe编译速度慢下来了

1、Qt的IDE一直在升级&#xff0c;qt-creator的新版本下载地址 https://download.qt.io/official_releases/qtcreator/ 2、本人一直用的是qt-creator-10.0.2版本&#xff0c;官网历史仓库可以下载安装包qt-creator-opensource-windows-x86_64-10.0.2.exe https://download.qt…

【二十】【QT开发应用】listwidget右键菜单和删除item

创建项目&#xff0c;添加资源文件 在项目文件夹中创建resources资源文件夹。 在vs中打开qrc文件&#xff0c;选择添加资源文件。 选择我们resources资源文件中的所有文件作为资源文件。 最后不要忘记点击保存。 向ListWidget控件添加item 右键菜单 在.h文件中添加QMenu头…

Git常用命令(记录)

提交代码 git status 查看状态git add .或者git add xx选择提交全部或者某文件git commit -m “提交信息”git push 创建新分支提交到新的分支 git checkout -b [branch-name] 创建并切换到新分支git add [file-name] 将要上传的文件添加到暂存区git commit -m “commit mes…

JavaScript - 异步编程

1. 前言 在 JavaScript 中&#xff0c;异步编程是一种处理需要等待操作&#xff08;如网络请求、文件读取或计时器&#xff09;的编程方式。由于 JavaScript 是单线程的&#xff0c;意味着它一次只能执行一个任务。异步编程允许你在等待某些操作完成时&#xff0c;继续执行其他…

【原创】java+swing+mysql长途客车售票管理系统设计与实现

个人主页&#xff1a;程序员杨工 个人简介&#xff1a;从事软件开发多年&#xff0c;前后端均有涉猎&#xff0c;具有丰富的开发经验 博客内容&#xff1a;全栈开发&#xff0c;分享Java、Python、Php、小程序、前后端、数据库经验和实战 文末有本人名片&#xff0c;希望和大家…

C#泛型(Generic)

泛型&#xff08;Generic&#xff09;允许延迟编写类或方法中的编程元素的数据类型的规范&#xff0c;直到实际在程序中使用它的时候。换句话说&#xff0c;泛型允许编写一个可以与任何数据类型一起工作的类或方法。 可以通过数据类型的替代参数编写类或方法的规范。当编译器遇…

【JavaScript】数据结构之堆

对数据结构像树&#xff0c;但是&#xff0c;是通过数组来实现的&#xff08;不是通过链表&#xff09;

基于SSM的在线家用电器销售系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSSMVueMySQL的在线家…

Docker部署Joplin Server教程

Joplin Server 是 Joplin 应用的后端服务,提供笔记和待办事项的同步功能。它允许用户在不同设备之间同步笔记,同时支持多用户和协作功能。Joplin Server使用现代技术栈,数据库使用的是 PostgreSQL 。 主要功能 同步:在桌面、移动设备和网页应用之间同步笔记。多用户支持:允…

【梯度消失|梯度爆炸】Vanishing Gradient|Exploding Gradient——为什么我的卷积神经网络会不好呢?

【梯度消失|梯度爆炸】Vanishing Gradient|Exploding Gradient——为什么我的卷积神经网络会不好呢&#xff1f; 【梯度消失|梯度爆炸】Vanishing Gradient|Exploding Gradient——为什么我的卷积神经网络会不好呢&#xff1f; 文章目录 【梯度消失|梯度爆炸】Vanishing Gradi…

【限流算法】常见的限流算法有哪些,怎么做限流操作

【限流算法】常见的限流算法有哪些&#xff0c;怎么做限流操作 在Java应用中实现限流&#xff08;Rate Limiting&#xff09;通常是为了控制对资源或服务的访问速率&#xff0c;防止因过载而导致的服务不可用。Java中实现限流的方法有多种&#xff0c;以下是一些常见的方法&…

Spring Boot- 配置文件问题

Spring Boot 配置文件问题探讨 Spring Boot 是目前主流的 Java 开发框架之一&#xff0c;其核心特性之一便是“约定优于配置”&#xff08;Convention over Configuration&#xff09;。在此基础上&#xff0c;Spring Boot 提供了灵活而强大的配置文件机制&#xff0c;帮助开发…

HTML5好看的水果蔬菜在线商城网站源码系列模板2

文章目录 1.设计来源1.1 主界面1.2 商品列表界面1.3 商品详情界面1.4 其他界面效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/142059220 HTML5好看的水果蔬菜在线商城…

【佳学基因检测】在EXCEL中,如何获取A列的第9-29个字符,将其填入另一列中

【佳学基因检测】在EXCEL中&#xff0c;如何获取A列的第9-29个字符&#xff0c;将其填入另一列中 在 Excel 中&#xff0c;如果你需要从 A 列的单元格中提取第 9 到第 29 个字符&#xff0c;你可以使用 MID 函数来实现。这是一个非常实用的函数&#xff0c;用于从文本字符串中…