指针的深入理解(五)

指针的深入理解(五)

文章目录

  • 指针的深入理解(五)
    • 前言
    • 一.函数指针数组
      • 1.1函数指针的理解
      • 1.2函数指针的类型
    • 二.转移表
      • 2.1转移表的概念
      • 2.2计算器
      • 2.3函数指针数组的应用
    • 三.回调函数
      • 3.1回调函数的概念
      • 3.2回调函数的应用
    • 四.指针知识梳理
    • 五.qsort的模拟实现
      • 5.1冒泡排序
      • 5.2qsort函数介绍
      • 5.3冒泡排序实现qsort
      • 5.4整形比较函数
      • 5.5 结构体字符串函数
      • 5.6结构体整型比较函数
      • 5.7计算指针位置
      • 5.8交换函数
      • 5.9qsort源码
    • 后言

前言

哈喽,各位小伙伴大家好!经过前面对指针的学习,我们已经将指针的内容基本学完了。今天小编就带大家学以致用,用所学的指针知识完成转移表和qsort函数的模拟实现。话不多说,咱们进入正题!向大厂冲锋!

一.函数指针数组

1.1函数指针的理解

函数指针数组听名字好长,他到底是个啥?咱们前面学了很多指针和数组。我们不妨进行类比一下

类比得出结论,函数指针数组是数组,里面存放的是函数指针

  1. 结论一:函数指针数组是每个元素为函数指针的数组,每个指针指向一个函数。

1.2函数指针的类型

我们现在知道了函数指针数组是用来存放函数指针的数组。那函数指针数组是怎样初始化的呢?

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 (*pf1)(int,int) = Add;
int (*pf2)(int, int) = Sub;
int (*pf3)(int, int) = Mul;
int (*pf4)(int, int) = Div;

以这段代码为例。我们把四个加减乘除的函数分别用四个指针指向。那初始化的话我们只需把四个指针存在一个数组里面,这个数组就是函数指针数组

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 (*pf1)(int,int) = Add;
int (*pf2)(int, int) = Sub;
int (*pf3)(int, int) = Mul;
int (*pf4)(int, int) = Div;
ptr[4] = {pf1,pf2,pf3,pf4};//未写出函数指针类型。

这里我们就完成了函数指针数组的初始化,把函数指针存放到数组里。但是我们还没写出函数指针数组的类型。那函数指针数组类型该怎么写呢?

int(*ptr)(int,int= {pf1,pf2,pf3,pf4};函数指针类型

我们先把函数指针数组写成函数指针类型先,我们在对他进行改造。现在ptr是个指针,我们不希望他是个指针,而是个数组。那我们该怎么做呢?

int (*ptr[4])(int, int) = { pf1,pf2,pf3,pf4};//函数指针数组类型

我们在ptr后面写个方括号,ptr先和方块结合说明他是个数组,把ptr【】去掉后,剩下的就是数组元素类型。类型未函数指针类型,说明这个数组的每个元素是函数指针,所以这个数组就是函数指针数组。

二.转移表

2.1转移表的概念

现在我们懂了什么是函数指针数组,真所谓学以致用。现在我们用我们刚学到的热乎知识来写一个转移表。
什么是转移表?

简单来说转移表就是使用一个函数指针数组来访问函数,这个函数指针数组就像一个踏板一样,可以帮你跳转到其他函数,这就是转移表。

2.2计算器

现在假如我们想写一个计算器,这个计算器能完成两个数的加减乘除运算。我们的代码简单粗暴的话一般会这么写。

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

我们的思路就是先把菜单函数写出来,在把实现运算的加减乘除代码封装成一个函数。再用do_while循环实现循环计算,switch根据输入选择调用哪个函数,分情况调整即可。
但是大家是不是发现了一个问题,就是这样写出来的代码非常的长。那为什么会这么长呢?

原因在于这段代码有着许多重复性的代码,他们之间唯一不同的就是调用的函数。那我们有没有什么办法既能完成运算又能是代码简洁精炼呢?这就需要用到我们刚学到的函数指针数组的知识啦!

2.3函数指针数组的应用

现在我们想把这个代码简化。这段代码冗余部分区别就在于调用的函数不同,那我们前面说了转移表可以帮我们跳转到不同的函数。所以我们可以用转移表帮我们跳转到不同的函数,之后调用即可,这样一来就不用把每种函数调用的情况列举,直接把冗余代码合并成一段代码即可。


int (*ptr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//为了与函数的菜单
//                                                 序号对齐,多加入Null
int main()
{int n = 0;do{menu();printf("请选择:");scanf("%d", &n);if (n < 5 && n>0){int x, y;printf("请输入两个操作数~");//输出两个操作数scanf("%d%d", &x, &y);int res = ptr[n](x, y);//调用转移表调用函数printf("%d\n", res);}else if (n == 0){printf("退出计算器~");//退出计算器break;}elseprintf("请重新输入~\n");//输入错误} while (n);return 0;
}

这里我们思路和之前差不多,但是我们创建了一个函数指针数组,为了函数指针数组的下标与菜单函数的数字对其,我们在数组前加入NULL。接着把冗余部分用转移表整合成一段代码即可。之后对输入数用 if分情况修改即可。这就是转移表。
大家可以看到我们学的这些指针的知识还是很有用的,只是我们还不能熟练运用,所以我们更加需要好好学习!

三.回调函数

3.1回调函数的概念


简单来说,回调函数就是一个通过函数指针调用的函数.
如果你把函数指针作为参数传给另一个函数。当这个函数被用来调用其指向的函数时,被调用的函数就是回调函数。如果没听懂也没关系,接下来我们写一段代码来感受一下什么是回调函数。

3.2回调函数的应用

其实上面那个代码还有一种改造方式。

大家看重复部分都非常相似,那我们是不是可以把这些重复部分封装成一个函数呢?但是他们调用的函数又不同。那怎么办?那我们是不是把先要调用的函数地址作为参数传给新函数,这样新函数再通过传过来的地址调用即可,这样这个函数就能实现调用不同的函数,完成不同的运算。那代码我们就应该这样写。

void Calc(int (*pf)(int , int ))//回调函数
{int x, y;printf("请输入两个操作数~\n");scanf("%d%d", &x, &y);printf("%d\n", pf(x, y));//调用传参函数
}
int main()
{int n = 0;do{menu();printf("请选择:");scanf("%d", &n);switch (n){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("输入错误,请重新输入~");break;}} while (n);return 0;
}

回调函数的思路大概是这样。

siwtch根据输入操作数,将不同的运算函数作为参数传给回调函数,回调函数再通过传来的指针调用运算函数,之后break跳出继续下一次选择。

四.指针知识梳理

现在我们已经把指针的内容全部学完了。那大家还记得我们学了那些指针和指针数组吗?正所谓,好记性也怕烂笔头,我们对学完的指针要及时梳理才能加深理解和记忆。比如小编现在写的博客就是经过复习的加深理解后整理知识,把学到的内容讲解给大家听。这个过程是对知识点的加深理解。那我们现在就来回顾一下我们学了那些内容吧。

我们大概学了这么多的内容,这是我写的一个简单的思维导图。大家可以看看,之后也可以写出自己的思维导图。这里给大家区分一下当名字里有指针和数组时怎么区分是数组还是指针。我们只需看最后的名词是啥,是指针就是指针,前面的内容就说明这个指针指向什么。是数组就是数组,前面的内柔在说明这个数组的每个元素是一个什么类型的指针。

五.qsort的模拟实现

5.1冒泡排序

冒泡排序

冒泡排序相信很多小伙伴都已经耳熟能详了。这里简单讲一下。冒泡排序的思想就是,进行n躺比较,每趟比较进行相邻两数的比较,满足条件就交换位置。每次比较后比较数移动。每趟比较将待排序数中最大或最小的数排序好。具体代码如下:

for (int i = 0; i < n - 1; i++)//趟数
{for (int j = 0; j < n - 1 - i; j++)//待排序数{if (a[j] > a[j + 1])//比较{int tmp = a[j];//交换a[j] = a[j + 1];a[j + 1] = tmp;}}
}

5.2qsort函数介绍


qsort函数是用来排序的库函数,直接可以用来排序数据,并且最厉害的地方可以排序任意类型的数据。底层的采用的是快速排序的方式。函数有四个参数

  • void* base 指针,指向待排序数组的第一个元素
  • size_t num 正整数,代表待排序数组元素个数
  • size_t size 正整数,代表待排序数组元素的大小,单位是字节
  • int (compar)(const void,const void*) 比较函数指针,由这个函数完成数据的比较

5.3冒泡排序实现qsort

现在我们想用冒泡排序算法实现qsort的功能。如果按照原来的冒泡排序写法,只能比较整型,可是qsort函数需要完成任意类型数组的比较。那我们就需要对原来的代码进行改造,那怎么改造呢?我们来思考一下。

现在我们知道比较和交换的地方需要改造,我们把它封装成函数之后再调用这些函数来完成比较和交换的功能。
排序的数据可能时整型数组,还可能是结构体。所以我们就写出多个对应比较函数。

5.4整形比较函数

我们这里统一以函数的返回值作为判断大小的标准,qsort函数对比较函数的返回直接也是这么要求的。

如果返回值为大于0说明前一个数大于后一个数,等于说明两个数相等,小于0说明前一个数小于后一个数。那我们可以直接让两个数作差,大于的话作差之后返回的是大于0的数,等于作差返回0,小于的话作差返回的数小于0的数。那函数就可以这样写。

int cmp_int(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;//p1p2分别指向//                             两个比较数
}

因为我们这里比较整型,所以我们直接强制类型转化为int*的指针即可。

5.5 结构体字符串函数

如果我们比较结构体字符串的话,比如比较名字,名字就是字符串。字符串的比较可以用库函数strcmp比较。

strcmp的返回值也是根据字符串的大小来决定返回值是大于,等于,还是小于0。那这个函数是怎么比较字符串的大小?

知道这些我们直接调用strcmp函数比较字符串即可,又因为它是根据字符串大小返回值。所以我们直接return strcmp的返回值即可。那比较结构体字符串的话,我们就把指针强制类型转化结构体指针,再用间接访问操作符访问结构体成员即可。

int cmp_stu_by_name(const void* p1, const void* p2)//结构体字符串比较函数
{return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}

5.6结构体整型比较函数

如果我们要比较结构体年龄的话,年龄用整型表示,那就是比较整形。那思路和我们的整型比较函数一样。但是结构体的话,就把指针强制类型转化为结构体类型,再用间接访问操作符访问结构体成员即可。

int cmp_stu_by_age(const void* p1, const void* p2)//结构体整形比较函数
{return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}

5.7计算指针位置

我们要比较两个元素,需要把指针位置传给比较函数。所以我们需要根据base确定出指针位置。那该怎么算呢?

这里给大家分析后知道,指针应该写成base+(j)*width的形式。

5.8交换函数

比较后,如果满足条件就需要交换元素位置。那我们就把元素指针位置传给函数。

那我们已经把base转为char类型的指针,我们就需要把元素的每个字节交换即可。
以这个整型为例,我们只需把前四个字节和后四个字节交换,就能完成9和8的交换。
所以我们还需要把width传给函数,再用 for循环交换每个元素字节即可。

void swap(char* p1, char* p2, int with)//交换函数
{for (int i = 0; i < with; i++){char tmp = *p1;*p1 = *p2;*p2 = tmp;p1++;p2++;}
}

5.9qsort源码

之后我们就把这些函数放在一起,再修改下参数就可完成qsort函数的模拟啦。

int cmp_int(const void* p1, const void* p2)//整形比较函数
{return *(int*)p1 - *(int*)p2;//p1p2分别指向//                             两个比较数
}
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 swap(char* p1, char* p2, int with)//交换函数
{for (int i = 0; i < with; i++){char tmp = *p1;*p1 = *p2;*p2 = tmp;p1++;p2++;}
}
void bubble_sort(void* base, int n, int width, int(*p1)(const void*, const void*))
{for (int i = 0; i < n; i++){for (int j = 0; j < n - 1 - i; j++){if (p1((char*)base + j * width, (char*)base + (j + 1) * width) > 0){swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}

后言

到这里咱们就把指针的内容全部学完啦!虽然过程艰辛,但我始终相信能让你变优秀的事情没有一件是轻松的!大家回去好好消化理解下指针的内容,今天就分享到这里,咱们下期见!拜拜~
共勉:请不要相信胜利就像山上的蒲公英一样唾手可得。但请相信,世界上总有一些美好
值得我们全力以赴,哪怕粉身碎骨! ———贺炜

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/585424.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用Node.js实现拉勾网数据爬取

引言 拉勾网作为中国领先的互联网招聘平台&#xff0c;汇集了丰富的职位信息&#xff0c;对于求职者和人力资源专业人士来说是一个宝贵的数据源。通过编写网络爬虫程序&#xff0c;我们可以自动化地收集这些信息&#xff0c;为求职决策和市场研究提供数据支持。Node.js以其非阻…

无问芯穹 MaaS AI 平台公测免费试用笔记:一

本篇文章聊聊正在公开测试的平台&#xff0c;无问芯穹的 MaaS 服务&#xff0c;包含了平台使用体验和一些小技巧。 因为测试给的免费卡时比较少&#xff0c;估计想完成完整测试或许需要一些时间&#xff0c;额外用一些账号进行。就先记录下常规折腾过程吧&#xff0c;让再次“…

SSM框架学习——了解Spring与Eclipse创建Maven项目

了解Spring 什么是Spring Spirng是分层的JavaSE/EE全栈轻量级开源框架&#xff0c;以控制反转IoC和面向切面编程AOP为内核&#xff0c;使用基本的JavaBean来完成EJB的工作。 Spring框架采用分层架构&#xff0c;它的一些列功能被分为若干个模块。 上图中的红色背景模块为本…

WSL Ubuntu20 使用1panelSSH连接失败(SSH服务初始化配置)

文章目录 安装网络工具ssh配置ssh服务安装 配置信息&#xff08;命令行&#xff09;配置信息&#xff08;可视化&#xff09;基础配置&#xff08;可省过&#xff09;高级配置&#xff08;必须&#xff09; 面板中终端配置SSH连接 安装网络工具 安装net工具apt install net-to…

MotionBuilder 脚本执行

目录 MediaPipe_Pose_in_MotionBuilder 你可以用以下几种方式执行你的脚本&#xff1a; MediaPipe_Pose_in_MotionBuilder https://github.com/Ndgt/MediaPipe_Pose_in_MotionBuilder/blob/main/PoseLandmark.py tcp通信 https://github.com/nils-soderman/motionbuilder-s…

自定义 Unity Scene 的界面工具

介绍 文档中会进行SceneView的自定义扩展&#xff0c;实现显示常驻GUI和添加自定义叠加层&#xff08;Custom Overlay&#xff09;。 最近项目开发用回了原生的Unity UI相关内容。对于之前常用的FairyGUI来说&#xff0c;原生的UGUI对于UI同学来讲有些不太方便。再加上这次会…

『Apisix安全篇』APISIX 加密传输实践:SSL/TLS证书的配置与管理实战指南

&#x1f4e3;读完这篇文章里你能收获到 &#x1f31f; 了解SSL/TLS证书对于网络通信安全的重要性和基础概念。&#x1f527; 掌握在APISIX中配置SSL/TLS证书的基本步骤和方法。&#x1f4dd; 学习如何通过修改监听端口&#xff0c;使HTTPS请求更加便捷。&#x1f6e0;️ 认识…

C++提高编程之STL初始

1.STL的诞生 长久以来&#xff0c;软件界一直希望建立重复性的东西C的面向对象和泛型编程思想&#xff0c;目的就是复用性的提升大多情况下&#xff0c;数据结构和算法都未能有一套标准,导致被迫从事大量重复工作为了建立数据结构和算法的一套标准,诞生了STL 2&#xff0c;ST…

HashMap 集合源码分析

系列文章目录 文章目录 系列文章目录前言谈一谈HashMap的红黑树节点类 TreeNode 设计一、字段分析二、构造方法分析三、内部类分析四、方法分析五、扩容分析六、总结 前言 HashMap 底层是使用了 哈希表&#xff08;数组实现的哈希表&#xff09; 链表 红黑树 实现的&#xff…

3.30学习日志

数值稳定性 神经网络的梯度 t表示层&#xff0c;h^t是隐藏层&#xff0c;y是要优化的目标函数&#xff0c;不是预测还包括了损失函数 损失函数l关于参数Wt的梯度&#xff1a;由链式法则&#xff0c;损失函数l关于最后一层隐藏层求导*最后一层隐藏层对倒数第二层隐藏层求导*……

全排列问题(输入整数N,生成从1~N所有整数的全排列)

【问题描述】输入整数N( 1 < N < 10 )&#xff0c;生成从1~N所有整数的全排列。 【输入形式】输入整数N。 【输出形式】输出有N!行&#xff0c;每行都是从1~N所有整数的一个全排列&#xff0c;各整数之间以空格分隔。各行上的全排列不重复。输出各行遵循"小数优先&q…

sql注入---Union注入

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 学习目标 了解union注入过程中用到的关键数据库&#xff0c;数据表&#xff0c;数据列sql查询中group_concat的作用使用union注入拿到靶机中数据库里的所有用户名和密码 一. 获得数据库表名和列…