【C语言进阶(五)】指针进阶详解(下)

指针详解-下

  • 1. 前言
  • 2. 函数指针数组
    • 2.1 函数指针数组的用途
  • 3. 指向函数指针数组的指针
    • 3.1 回调函数
  • 4. 库函数中qsort分析
    • 4.1 为什么这个函数需要我们自己实现?
    • 4.2 库函数qsort的使用
  • 5. qsort函数的模拟实现
    • 5.1 大框架的实现
    • 5.2 比较函数的实现
    • 5.3 对于交换函数的思考
    • 5.4 交换函数的实现
    • 5.5 qsort函数所有代码
  • 6. 总结

1. 前言

本篇文章将接着指针详解(上)
继续深入介绍指针的详细细节

本篇的任务以介绍函数指针数组
和介绍指向函数指针数组的指针
为基础,为大家着重讲解库函数
qsort的实现!

在这里插入图片描述


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 main()
{
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;
	//函数指针数组
	int (*pfArr1[4])(int, int) = {Add, Sub, Mul, Div};
	int (*pfArr2[4])(int, int) = {pf1, pf2, pf3, pf4};//这两个数组存储的内容是一样的
	//
	return 0;
}

这里的pfArr1和pfArr2就是函数指针数组

可以这样理解这段代码:

在这里插入图片描述


2.1 函数指针数组的用途

一个非常经典的用途就是计算器!

如果不使用函数指针数组的话
用户想要的加减乘除法
需要switch语句来实现
然而switch语句常常很冗杂

使用函数指针数组可以简化代码量!

比如:

int (* pfArr[5])(int, int) = {NULL, Add, Sub, Mul, Div};
                               0     1    2    3    4

定义一个函数指针数组的话
可以直接使用下标访问加减乘除函数
假如这里的计算器功能很多,那么!
使用数组显然比用冗杂的switch语句好

拓展:

函数指针数组的应用场景:
指针应用之转移表


3. 指向函数指针数组的指针

已经开始套娃环节了
刚刚介绍的函数指针数组
它本质上还是一个数组,数组就有地址
而指向函数指针数组的指针就是一个指针
指针指向一个数组
数组的元素都是函数指针

比如:

void test(const char* str)
{
    printf("%s\n", str);
}
int main()
{
 //函数指针pfun
 void (*pfun)(const char*) = test;
 
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 
 return 0;
}

这段代码可以这样理解:

在这里插入图片描述


3.1 回调函数

先了解一下回调函数的概念
为后面自我实现qsort做准备:

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


4. 库函数中qsort分析

首先qsort是一个用于排序的函数!

先来看官方的定义:

在这里插入图片描述

阅读文档可以知道以下内容:

  1. base指针是传待排序的空间
  2. num是指待排序元素个数
  3. size是指每一个元素所占字节大小
  4. 而最后一个参数是一个函数指针

经过仔细查阅资料可得:
函数指针指向的函数需要程序员自己实现
而又两个参数: 我称为A和B

A小于B: 返回值小于一
A大于B: 返回值大于一
A等于B: 返回值等于一


4.1 为什么这个函数需要我们自己实现?

因为每一个类型的数据的比较方式不同
并且操作者想要比较的数据可以有多个

比如:

  1. 整型的比较
int compar(void* x1,void* x2)
{
	return *(int*)x1 - *(*int)x2);
}

对代码的解释:

void*类型的数据不能直接解引用
所以要强制类型转换为int * 类型
假如你想要比较char类型的数据
这里就需要强制转换为char *

  1. 浮点型的比较
int compar(void* x1,void* x2)
{
	return strcmp( (char*)x1, (char*)x2 );
}

对代码的解释:

字符不能单纯使用大于小于号比较
应该使用字符串操作函数比较大小

  1. 结构体的比较
struct stu 
{
	char name[20]={0};//学生姓名
	int height =0;//学生身高
	int grade =0;//学生成绩
};

compar(void* x1,void* x2)//以学生身高作为比较基准
{
	return ((struct stu*)x1)->height -((struct stu*)x2)->height;
	       //强制转换为结构体指针   比较身高,指向height
}

compar(void* x1,void* x2)//以学生名字作为比较基准
{
	return strcmp(((struct stu*)x1)->name,((struct stu*)x2)->name);
	             强制类型转换为结构体指针  指向结构体中的name数组
}

对代码的解释:

在结构体中可以比较不同的成员
而比较前都应该先进行强制类型转换
并且指向对应的结构体成员


4.2 库函数qsort的使用

假设我们需要排序一个整型数组
下面来使用一下qsort函数

int compar(void* x1,void*x2)
{
	return *(int*)x1 - *(int*)x2;
}

int main()
{
	int a[6]={6,2,9,4,3,5};
	qsort(a, 6, sizeof(int), compar);
	return 0;
}

5. qsort函数的模拟实现

由于库函数中的qsort函数
的内部是由快速排序实现的
比较难理解
所以我们把内部简化为冒泡排序!

基本框架:

  1. 两层 if 语句实现冒泡大框架
  2. 比较大小时使用自定义函数compar
  3. 交换函数使用自定义函数Swap

5.1 大框架的实现

bubble_qsort(void* s, int sz, int width, int (*cmp)(void* x1, void* x2))
{
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - i - 1; j++)
		{
			if (比较条件函数)
			{
				Swap函数
			}
		}
	}
}

模仿库函数中的参数
用冒泡函数原理自己实现一个qsort!


5.2 比较函数的实现

在本章的4.2部分已经模拟了很多情况
假设我们想要排序整型数组:

int cmp(void* x1, void* x2)//整数比较
{
	return *(int*)x1 - *(int*)x2;
}

5.3 对于交换函数的思考

在使用冒泡排序时
我们通常已知待排序的数组的类型
所以能够使用不同的方法来交换不同类型

然而
qsort函数需要满足所有类型的交换

解决方法:

可以把数据一个字节一个字节的交换
任何类型的大小都大于等于一个字节
所以只需要知道数据所占字节数
就能解决这个问题!


5.4 交换函数的实现

void Swap_by_bite(char* s1, char* s2, int width)
{
	for (int i = 0; i < width; i++)//一个字节一个字节的拷贝过去
	{
		char tmp = *s1;
		*s1 = *s2;
		*s2 = tmp;
		s1++;
		s2++;
	}
}

对代码的解释:

函数参数使用char*是因为
将数据单位化,方便以字节为单位交换
width参数是指数据所占字节大小


5.5 qsort函数所有代码

int cmp(void* x1, void* x2)//整数比较
{
	return *(int*)x1 - *(int*)x2;
}

void Swap_by_bite(char* s1, char* s2, int width)
{
	for (int i = 0; i < width; i++)//一个字节一个字节的拷贝过去
	{
		char tmp = *s1;
		*s1 = *s2;
		*s2 = tmp;
		s1++;
		s2++;
	}
}

bubble_sort(void* s, int sz, int width, int (*cmp)(void* x1, void* x2))
{
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - i - 1; j++)
		{
			if (cmp((char*)s+j*width,(char*)s+(j+1)*width)>0)
			{
				Swap_by_bite((char*)s + j * width, (char*)s + (j + 1) * width, width);
			}
		}
	}
}

对代码的解释:

给cmp函数传参:s+j*width和
s+(j+1)*width可以这样理解:

在这里插入图片描述

而交换函数传参也是一个意思
并且Swap函数的参数是char*
所以需要我们强制类型转换传入的数据


6. 总结

指针是C语言的一项利器
某些使用C语言编写的项目
使用高阶指针解决问题是家常便饭!
看似这种不断套娃的过程很鸡肋
实际上有一定的用途

在这里插入图片描述


🔎 下期预告:自定义类型详解 🔍

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

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

相关文章

18.Word:数据库培训课程❗【34】

目录 题目 NO1.2.3.4 NO5设置文档内容的格式与样式 NO6 NO7 NO8.9 NO10.11标签邮件合并 题目 NO1.2.3.4 FnF12&#xff1a;打开"Word素材.docx”文件,将其另存为"Word.docx”在考生文件夹下之后到任务9的所有操作均基于此文件&#xff1a;"Word.docx”…

99.24 金融难点通俗解释:MLF(中期借贷便利)vs LPR(贷款市场报价利率)

目录 0. 承前1. 什么是MLF&#xff1f;1.1 专业解释1.2 通俗解释1.3 MLF的三个关键点&#xff1a; 2. 什么是LPR&#xff1f;2.1 专业解释2.2 通俗解释2.3 LPR的三个关键点&#xff1a; 3. MLF和LPR的关系4. 传导机制4.1 第一步&#xff1a;央行调整MLF4.2 第二步&#xff1a;银…

五. Redis 配置内容(详细配置说明)

五. Redis 配置内容(详细配置说明) 文章目录 五. Redis 配置内容(详细配置说明)1. Units 单位配置2. INCLUDES (包含)配置3. NETWORK (网络)配置3.1 bind(配置访问内容)3.2 protected-mode (保护模式)3.3 port(端口)配置3.4 timeout(客户端超时时间)配置3.5 tcp-keepalive()配置…

用 HTML、CSS 和 JavaScript 实现抽奖转盘效果

顺序抽奖 前言 这段代码实现了一个简单的抽奖转盘效果。页面上有一个九宫格布局的抽奖区域&#xff0c;周围八个格子分别放置了不同的奖品名称&#xff0c;中间是一个 “开始抽奖” 的按钮。点击按钮后&#xff0c;抽奖区域的格子会快速滚动&#xff0c;颜色不断变化&#xf…

【Linux系统】计算机世界的基石:冯诺依曼架构与操作系统设计

文章目录 一.冯诺依曼体系结构1.1 为什么体系结构中要存在内存&#xff1f;1.2 冯诺依曼瓶颈 二.操作系统2.1 设计目的2.2 系统调用与库函数 一.冯诺依曼体系结构 冯诺依曼体系结构&#xff08;Von Neumann Architecture&#xff09;是计算机的基本设计理念之一&#xff0c;由…

说说Redis的内存淘汰策略?

大家好&#xff0c;我是锋哥。今天分享关于【说说Redis的内存淘汰策略?】面试题。希望对大家有帮助&#xff1b; 说说Redis的内存淘汰策略? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Redis 提供了多种内存淘汰策略&#xff0c;用于在内存达到限制时决定如何…

【python】python基于机器学习与数据分析的手机特性关联与分类预测(源码+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;专__注&#x1f448;&#xff1a;专注主流机器人、人工智能等相关领域的开发、测试技术。 python基于机器学习与数据分析的手机特性关联与分类…

Flutter_学习记录_Tab的简单Demo~真的很简单

1. Tab的简单使用了解 要实现tab(选项卡或者标签视图)需要用到三个组件&#xff1a; TabBarTabBarViewTabController 这一块&#xff0c;我也不知道怎么整理了&#xff0c;直接提供代码吧&#xff1a; import package:flutter/material.dart;void main() {runApp(MyApp());…

JavaScript中的数组方法总结+详解

在JS中,数组方法是非常重要且常用的方法.在此整理总结一番. 1. javaScript常用数组方法 2.方法详解 1.push(); 功能: 在数组最后一位添加一个或多个元素,并返回新数组的长度,改变原数组.(添加多个元素用逗号隔开) var arr [1, 2, "c"];var rel arr.push(&q…

蓝桥杯之c++入门(二)【输入输出(上)】

目录 前言1&#xff0e;getchar和 putchar1.1 getchar()1.2 putchar() 2&#xff0e;scanf和 printf2.1 printf2.1.1基本用法2.1.2占位符2.1.3格式化输出2.1.3.1 限定宽度2.1.3.2 限定小数位数 2.2 scanf2.2.1基本用法2.2.2 占位符2.2.3 scanf的返回值 2.3练习练习1&#xff1a…

[EAI-028] Diffusion-VLA,能够进行多模态推理和机器人动作预测的VLA模型

Paper Card 论文标题&#xff1a;Diffusion-VLA: Scaling Robot Foundation Models via Unified Diffusion and Autoregression 论文作者&#xff1a;Junjie Wen, Minjie Zhu, Yichen Zhu, Zhibin Tang, Jinming Li, Zhongyi Zhou, Chengmeng Li, Xiaoyu Liu, Yaxin Peng, Chao…

3.5.5 基于横盘结构的分析体系——缠论(走势类型)

走势 缠论中走势的定义如下&#xff1a; 包含一个中枢的走势——盘整 包含两个或多个中枢的走势——趋势 方向 趋势&#xff08;两中枢或多中枢&#xff09; 盘整&#xff08;一中枢&#xff09; 上涨 下跌 表1-8 盘整和趋势类型的走势理论图。 趋势和中枢 …

使用PyQt5绘制带有刻度的温度计控件

前言&#xff1a;进入学习Python开发上位机界面的第二阶段&#xff0c;学习如何开发自定义控件&#xff0c;从常用的控件入手学习&#xff0c;本期主要学习如何使用PyQt5绘制带有刻度的温度计控件。 1. 先找到一篇参考文章 参考文章&#xff1a;Qt编写自定义控件5-柱状温度计…

DIFY源码解析

偶然发现Github上某位大佬开源的DIFY源码注释和解析&#xff0c;目前还处于陆续不断更新地更新过程中&#xff0c;为大佬的专业和开源贡献精神点赞。先收藏链接&#xff0c;后续慢慢学习。 相关链接如下&#xff1a; DIFY源码解析

赛博算卦之周易六十四卦JAVA实现:六幺算尽天下事,梅花化解天下苦。

佬们过年好呀~新年第一篇博客让我们来场赛博算命吧&#xff01; 更多文章&#xff1a;个人主页 系列文章&#xff1a;JAVA专栏 欢迎各位大佬来访哦~互三必回&#xff01;&#xff01;&#xff01; 文章目录 #一、文化背景概述1.文化起源2.起卦步骤 #二、卦象解读#三、just do i…

「AI学习笔记」深度学习的起源与发展:从神经网络到大数据(二)

深度学习&#xff08;DL&#xff09;是现代人工智能&#xff08;AI&#xff09;的核心之一&#xff0c;但它并不是一夜之间出现的技术。从最初的理论提出到如今的广泛应用&#xff0c;深度学习经历了几乎一个世纪的不断探索与发展。今天&#xff0c;我们一起回顾深度学习的历史…

AIGC技术中常提到的 “嵌入转换到同一个向量空间中”该如何理解

在AIGC&#xff08;人工智能生成内容&#xff09;技术中&#xff0c;“嵌入转换到同一个向量空间中”是一个核心概念&#xff0c;其主要目的是将不同类型的输入数据&#xff08;如文本、图像、音频等&#xff09;映射到一个统一的连续向量空间中&#xff0c;从而实现数据之间的…

单细胞分析基础-第一节 数据质控、降维聚类

scRNA_pipeline\1.Seurat 生物技能树 可进官网查询 添加链接描述 分析流程 准备:R包安装 options("repos"="https://mirrors.ustc.edu.cn/CRAN/") if(!require("BiocManager")) install.packages("BiocManager",update = F,ask =…

【13】WLC HA介绍和配置

1.概述 本文对AireOS WLC的HA进行介绍,和大多数网络架构设计一样,单台的WLC是无法保证设备的冗余性的,而且WLC也不是双引擎的设备,所以需要依靠High Available的技术来为WLC提供高可用性。 2.WLC HA类型 AireOS WLC的高可用性技术可以分为N+1的SSO的HA。不是所有的设备都…

Alibaba开发规范_编程规约之命名风格

文章目录 命名风格的基本原则1. 命名不能以下划线或美元符号开始或结束2. 严禁使用拼音与英文混合或直接使用中文3. 类名使用 UpperCamelCase 风格&#xff0c;但以下情形例外&#xff1a;DO / BO / DTO / VO / AO / PO / UID 等4. 方法名、参数名、成员变量、局部变量使用 low…