指针的深入理解(五)

指针的深入理解(五)

文章目录

  • 指针的深入理解(五)
    • 前言
    • 一.函数指针数组
      • 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;
		}
		else
			printf("请重新输入~\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);
			}
		}
	}
}

后言

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣热题100_链表_142_环形链表 II

文章目录 题目链接解题思路解题代码 题目链接 142. 环形链表 II 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中…

网络以太网之(1)基础概念

网络以太网之(1)基础概念 Author: Once Day Date: 2024年4月1日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文档可参考专栏&#xff1a;通信网络技术_Once-Day的…

NB-IOT——浅谈NB-IOT及模块测试

浅谈NB-IOT及模块基本使用测试 介绍什么是NB-IOT&#xff1f;NB-IOT的特点 使用准备基本使用 总结 介绍 什么是NB-IOT&#xff1f; NB-IoT&#xff0c;即窄带物联网&#xff08;Narrowband Internet of Things&#xff09;&#xff0c;是一种低功耗广域物联网&#xff08;LPW…

计算机网络——数据链路层(流量传输与可靠传输机制)

计算机网络——数据链路层&#xff08;流量传输与可靠传输机制&#xff09; 流量传输与可靠传输机制流量控制可靠传输机制 停止-等待协议无差错情况接收并检测到差错状态确认丢失或迟到状态 停等协议的效率分析后退N帧协议&#xff08;Go-Back-N&#xff0c;简称GBN&#xff09…

在js中本地存储的数组如何转成对象

一、此方法仅适用一维数组&#xff1b; 二、效果图 使用后 三、js代码。 function gong(s){console.log(s);let data;let kk1;// 检查ask_id是否不为空 if (s.ask_id null ) { kk1}else{kk2let dd;dds.data;sessionStorage.setItem(wenda,JSON.stringify(dd[0]))window.l…

MyBatis主要的类层次结构(Mybatis工具类)

MyBatis主要的类层次结构 每一个MyBatis的应用程序都以一个SqlSessionFactory 对象的实例为核心 。 SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象来获得 。 SqlSessionFactoryBuilder对象可以从 XML 配置文件中构建 SqlSessionFactory对象。 package…

Linux grep和find命令常用类型

1. grep命令的使用。 查找文件中符合条件的字符串或正则表达式&#xff0c;然后将含有范本样式的那一列显示出来。若不指定任何文件名称&#xff0c;或是给的文件名为-&#xff0c;则gerp命令会从标准输入设备读取数据。 用于测试的文件目录结构如下&#xff1a; 1.1 在单个文…

Vue项目登录页实现获取短信验证码的功能

之前我们写过不需要调后端接口就获取验证码的方法,具体看《无需后端接口,用原生js轻松实现验证码》这个文章。现在我们管理后台有个需求,就是登录页面需要获取验证码,用户可以输入验证码后进行登录。效果如下,当我点击获取验证码后能获取短信验证码: 这里在用户点击获取…

如何利用Geoserver将矢量数据发布成伪3D服务

目录 1.1、前言1.2、伪3D服务效果图1.3、数据准备1.4、基本原理1.5、完整的样式文件1.6、Geoserver中的操作 1.1、前言 本篇文章需要的Geoserver环境&#xff0c;Geoserver的情况请参考博文Geoserver简介、Geoserver安装部署操作请参考博文Geoserver安装部署、Geoserver基本操作…

初识CSS

目录 前言&#xff1a; CSS的介绍&#xff1a; CSS的发展&#xff1a; 1&#xff09;CSS1.0&#xff1a; 2)CSS2.0: 3)CSS2.1: 4&#xff09;CSS3&#xff1a; CSS特点&#xff1a; 1&#xff09;丰富的样式定义&#xff1a; 2&#xff09;易于设置和修改&#xff1a; 3&…

Datacom HCIP笔记-ISIS协议

IS中间系统&#xff08;路由器/运行了ISIS协议的设备&#xff09; ES终端系统(PC,PAD,print) 网络功能模型 ISO定义 事实标准 OSI TCP/IP 网络层(CLNP) (IS-IS) 网络…

代码随想录算法训练营第39天|62.不同路径 |63. 不同路径 II

代码随想录算法训练营第39天|62.不同路径 |63. 不同路径 II 详细布置 62.不同路径 本题大家掌握动态规划的方法就可以。 数论方法 有点非主流&#xff0c;很难想到。 https://programmercarl.com/0062.%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.html 视频讲解&#xff1a;https…

linux编辑器——vim使用方法

文章目录 linux编辑器——vim使用方法1. vim的基本概念2. vim的基本操作3. vim正常模式命令集4. vim末行模式命令集5. vim操作总结6.简单vim配置7.参考资料 linux编辑器——vim使用方法 vi/vim的区别简单点来说&#xff0c;它们都是多模式编辑器&#xff0c;不同的是vim是vi的…

配置 施耐德 modbusTCP 分布式IO子站 RPA0100

1. 总体步骤 2. 软件组态&#xff1a;在 Unity Pro 软件中创建编辑 PRA 模块工程 2.1 新建项目 模块箱硬件型号如下 点击 Unity Pro 软件左上方【新建】按钮&#xff0c;选择正确的 DIO 模块型号、背板型号 2.2 模块组态 2.2.1 拖拽添加模块 双击【配置】菜单下的【0&…

理解 SQL 数据添加:从基础到实践

引言&#xff1a; 在现代软件开发中&#xff0c;数据库是不可或缺的一部分。而 SQL 作为结构化查询语言的代表&#xff0c;广泛应用于数据库管理系统中&#xff0c;为我们提供了强大的数据管理和查询能力。 主题&#xff1a; 我们将从基础的 SQL INSERT INTO 语句开始&…

基于FPGA的HDMI方块移动程序设计

前面写了一篇关于HDMI视频接口的文章《基于FPGA的HDMI视频接口的设计》&#xff0c;该文章对HDMI的相关知识点做了讲解&#xff0c;这里不再重复&#xff0c;本篇文章直接实现一个简单功能-方块的移动。 该系统程序主要实现的功能就是通过串口下发指令控制方块的位置移动&…

使用CMake搭建简单的Qt程序

目录结构 代码 CMakeLists.txt&#xff1a; cmake_minimum_required(VERSION 3.15)set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON)# set the project name project(xxx)# 设置Qt的路径 # 例如 E:/Qt/Qt/aaa/msvc2019_64 # aaa 为Qt的版本号 set(QT_PATH…

LCD TP触摸屏调试方法

一、硬件连接 I2C总线&#xff1a;I2C-SDA和i2C-SCL 中断信号&#xff1a;touch-gpio 复位信号&#xff1a;reset-gpio 电源信号&#xff1a;power-gpio 二、驱动调试 2.1 确认从设备地址 在给TP供电正常后&#xff0c;检测其I2C设备从地址&#xff0c;或者通过datashee…

怎么在UE游戏中加入原生振动效果

我是做振动触感的。人类的五感“视听嗅味触”&#xff0c;其中的“触”就是触觉&#xff0c;是指皮肤、毛发与物体接触时的感觉。触感可以带来更加逼真的沉浸式体验。但也许过于司空见惯&#xff0c;也是习以为常&#xff0c;很多人漠视了触感的价值。大家对触感的认知还远远不…

Suno音乐创作新时代:从歌词到MV的完整制作全流程

朋友们&#xff0c;期待已久的Suno直播分享终于来了&#xff01; 墨云首秀&#xff0c;本场直播全程只讲干货&#xff01;拒绝任何废话! 主要围绕下面六个主题分享: 如何快速撰写原创歌词&#xff1f;我将直接给大家分享我的歌词创作提示词以及使用技巧。 Suno歌词结构最佳实践…