有希带你深入理解指针(4)

news2024/11/15 15:49:41

目录

  • 前言🥰
  • 1.回调函数😺
    • 1.1回调函数的概念😋
  • 2.qsort使用🤯
    • 2.1什么是qsort👻
    • 2.2 qsort函数的使用🧐
  • 3.模拟实现qsort😎

前言🥰

本篇文章是对指针知识的进一步讲解,如果对部分知识有不了解的地方可以移步前文进行学习!😶‍🌫️
在这里插入图片描述

1.回调函数😺

1.1回调函数的概念😋

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

现在我们用一个简单的例子来理解:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}

void test(int (*pf)(int, int))
{
	int ret = pf(4, 5);
	printf("%d\n", ret);
}

int main()
{
	test(Add);
	return 0;
}

这里我们有一个Add函数来实现加法,还有有一个test函数,它的参数部分我设置为pf(函数指针),里面的参数我设置为int,并且返回类型设置为int,在test函数内部我利用pf去调用一个函数(Add函数),并把两个值传过去,之后能计算出一个结果(存到ret中)。
在主函数部分我们调用了test函数,并把Add函数的地址传了过去,此时pf就指向了Add函数,相当于调用了Add函数完成4和5的相加。

注意:
在主函数部分我并没有直接调用Add函数,我把Add函数的地址传递给了pf,此时pf指向的就是Add函数。当我们用pf去调用函数时,调用的就是Add函数并且可以完成相应的计算。这里的Add就可以称为回调函数。这和函数的嵌套是不同的,函数的嵌套是直接通过函数名去调用。而今天我们所讲的是通过函数指针去调用的。

2.qsort使用🤯

2.1什么是qsort👻

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 - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int 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]);
	}
	printf("\n");
}

int main()
{
	int arr[10] = { 3,1,9,8,5,4,0,2,7,6 };
	//升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);

	bubble_sort(arr, sz);

	print_arr(arr, sz);
	return 0;
}

这里我对进行冒泡排序前后的数组进行了打印比较。我们这里的bubble_sort函数只能排序整型数组,不能对其他类型的数组进行处理,具有很大的局限性。现在我们想要对函数进行改造,使它可以对其他类型的数据进行排序。
首先对于趟数,我们并不需要进行改变,我们进行改变的部分如下:
在这里插入图片描述
我们有两个需要思考的地方:

  1. 两个变量怎么比较
  2. 两个变量怎么交换

注意:

  1. 不是所有的数据都来可以用>进行比较,例如:结构体变量、字符串。
  2. 这里的临时变量也不能直接定为int
  3. 参数的类型也是有问题的,如图:
    在这里插入图片描述

此时,我们先不着急改我们的代码,我们可以先学习qsort函数的使用。

2.2 qsort函数的使用🧐

函数的原型(函数名,参数,返回类型):

void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));

其中第四个参数最为复杂,是一个函数指针。
这么多的参数我们可以对比bubble_sort函数进行理解,我们肯定需要知道所要排序数据的类型等等,我们通过下图进行理解:
在这里插入图片描述

在这里插入图片描述
因为我们要对不同类型的数据进行比较,不同类型的数据的比较类型是不一样的。根据不同的数据,我们通过传不同的比较方法(自己写一个比较函数),把该比较函数的地址传进去,来实现比较。我们可以通过第四个参数传递该比较函数的地址。

现在我们要对一个整型数组arr使用qsort排序,这里我会对函数进行分装。我们需要根据qsort的参数,填写对应的内容。
对于qsort的实现与两个人有关,如图:
在这里插入图片描述
这里我们的任务是:提供一个函数,能实现两个整型元素的比较。我们需要把函数名传过去,为了实现这一点,我们写出的参数和返回类型一个和函数指针的参数和返回类型一致,才能将函数名传过去。

这里的e1和e2分别指向一个元素。void * 是无具体类型的指针,它可以存放地址,也可以指向对象。我们先进行两个整型元素的比较。我们需要进行解引用再进行整型元素的比较,如果我们采用下图,会报错。因为void * 是无具体类型指针,无法进行解引用操作。
在这里插入图片描述
注意
void* 类型的指针不能解引用操作符,也不能+/-整数的操作。这种指针变量一般是用来存放地址的。使用之前要强制类型转换成想要的类型。
现在我们进行修改:

int cmp_int(const void* e1, const void* e2) 
{
	if (*(int*)e1 > *(int*)e2)
		return 1;
	else if (*(int*)e1 < *(int*)e2)
		return -1;
	else
		return 0;
}

现在,我们对该代码进行简化:

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

现在我们可以打印看看我们写的代码的结果(不要忘记头文件):

#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]);
	}
	printf("\n");
}

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}


void test1()
{
	int arr[10] = { 3,1,9,8,5,4,0,2,7,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

int main()
{
	test1();
	return 0;
}

在这里插入图片描述
此时我们可以看到是默认升序的,我们可以改为降序,改以下部分即可:

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e1;
}

我们现在也可以举一些其他例子,我们现在排列一下结构体数据。我们现在假设学生有名字和年龄,如下:

struct Stu
{
	char name[20];//名字
	int age;//年龄
};

我们需要确定比较的方式,通过年龄还是名字。不能盲目的进行比较。先进行年龄的比较。此时的e1,e2分别指向结构体对象。根据上述知识,我们可以实现以下代码:

#include<stdio.h>
#include<stdlib.h>
struct Stu
{
	char name[20];//名字
	int age;//年龄
};
//cmp_stu_by_age用来比较结构体对象

int cmp_stu_by_age(const void* e1, const void* e2)
{
	return (*(struct Stu*)e1).age - (*(struct Stu*)e2).age;
}

void test2()
{
	struct Stu s[3] = { {"zhangsan",18},{"lisi",25},{"wangwu",28} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int main()
{
	test2();
	return 0;
}

但是用名字来比较大小的时候,就不能直接减。字符串的比较要利用strcmp函数。在这里插入图片描述
代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
	char name[20];//名字
	int age;//年龄
};

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[3] = { {"zhangsan",18},{"lisi",25},{"wangwu",28} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
	test2();
	return 0;
}

现在,我们已经会使用qsort函数了。

3.模拟实现qsort😎

现在我们接着对冒泡排序的函数进行改造。前面我说过趟数不用改,需要改的部分是比较的方法、怎么交换和参数。
我们可以先想想qsort函数为什么设计成那样?
void * base
设计为void * ,是因为我们可能排序不同类型的数据,不能写死。void * 类型的指针可以接收任意类型变量的地址。
size_t num
是base指向的数组中元素的个数。
size_t szie:
是base指向数组的一个元素的长度,单位是字节。大家可能认为这个参数是不必要的,但是它决定了在访问时,一步走多远。
int ( * compar) (const void * ,const void * )
第四个参数,是因为对于不同类型的数据比较方法不同,我们需要自己造一个函数,通过函数指针传地址过去,进行比较。

代码:

void bubble_sort(void*base, int num,int width,int(*cmp)(const void*e1, const void* e2))

前面我们在参数部分设置为size_t,这里我写了int,但是size_t的写法更好,int类型可正可负,这里的num和size并不会出现该情况,所以接下来我改为size_t。

void bubble_sort(void* base, size_t  num, size_t  width, int(*cmp)(const void* e1, const void* e2))

在比较时,我们需要借助cmp这个函数进行比较(此时用升序),cmp这里的返回值如果大于0,就交换。现在我们需要将arr[j]和arr[j+1]的地址传过去。
这里的关键是用base计算出arr[j]和arr[j+1]的地址 。现在我们通过width知道了一个元素的宽度,但是我们不知道base里面存了什么类型的数据。这里的base不能直接+j或+j+1。base是void * 类型。我们还需要将base进行强制类型转换,转换为char *,此时+1跳过一个字节。
在比较之后怎么进行交换呢?
这里我们分装一个Swap函数来完成。之前我们创建了一个临时变量tmp,这个临时变量的类型比较棘手,不能定死。
在满足交换的条件时,我们把两个指针指向的元素进行交换。不要忘记把width(类型设置为size_t)传给Swap函数。Swap函数的参数部分我们写为char * 就行。
在写代码之前我们需要知道当我们需要交换40个字节的数据时,我们可以把空间切为40份,一对字节一对字节的进行交换。所以在Swap函数内部我们写一个循环就行。因为交换的是字节,我们创建一个一个字节大小的临时变量就行交换,并通过++不断交换。现在我们的理论知识已经充分了,现在完成代码。


#include<stdio.h>
#include<string.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void Swap(char* buf1, char* buf2, size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void bubble_sort(void* base, size_t  num, size_t  width, 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(cmp((char*)base+j*width,(char*)base+(j + 1)*width)>0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

void test3()
{
	int arr[10] = { 3,1,9,8,5,4,0,2,7,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

int main()
{
	test3();
	return 0;
}

在这里插入图片描述
现在我们已经完成qsort的模拟了,这就是泛型编程,适配各种类型的数据。

好了,今天我们的指针知识就讲到这里。如果文章内容有误,请大佬在评论区斧正!谢谢大家!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无人机飞手执照:模拟训练技术详解

随着无人机技术的飞速发展&#xff0c;其在航拍、农业、物流、救援等多个领域的应用日益广泛&#xff0c;对无人机飞手的技能与安全意识要求也随之提高。为了确保无人机操作的安全性与效率&#xff0c;获取无人机飞手执照成为专业从业者不可或缺的步骤。模拟训练作为执照考取过…

第一个搭建SpringBoot项目(连接mysql)

首先新建项目找到Spring Initializr 我使用的URL是https://start.spring.io这里最低的JDK版本是17&#xff0c;而且当网速不好的时候可能会显示超时&#xff0c;这里可以选用阿里云的镜像https://start.aliyun.com可以更快一些但是里面还是有一些区别的 我们这里选择Java语言&a…

【OpenCV3】图像的翻转、图像的旋转、仿射变换之图像平移、仿射变换之获取变换矩阵、透视变换

1 图像的放大与缩小 2 图像的翻转 3 图像的旋转 4 仿射变换之图像平移 5 仿射变换之获取变换矩阵 6 透视变换 1 图像的放大与缩小 resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) src: 要缩放的图片dsize: 缩放之后的图片大小, 元组和列表表示均可.dst: 可选参数, 缩…

AIGC大模型智能抠图(清除背景):Sanster/IOPaint,python(2)

AIGC大模型智能抠图&#xff08;清除背景&#xff09;&#xff1a;Sanster/IOPaint&#xff0c;python&#xff08;2&#xff09; 在文章&#xff08;1&#xff09;的基础上&#xff0c;尝试用大模型扣除图中的某些主要景物。 1、首先&#xff0c;安装插件&#xff1a; pip i…

Qt项目使用Inno Setup打包(关于打包中文乱码的解决)

​ 关于打包好的文件乱码解决方法 打包好的文件中文乱码&#xff0c;就是编码格式出现了问题&#xff0c;更改一下中文脚本编码格式&#xff0c;在官网Inno Setup Translations下载好中文脚本 点击下载&#xff0c;然后另存为 得到ChineseSimplified.isl.txt文件后&#…

使用3DUNet训练自己的数据集(pytorch)— 医疗影像分割

代码:lee-zq/3DUNet-Pytorch: 3DUNet implemented with pytorch (github.com) 文章<cicek16miccai.pdf (uni-freiburg.de)3D U-Net: Learning Dense Volumetric Segmentation

贷款利率高低跟什么有关?仅凭身份证就能贷到款?额度是多少?

在金融的广阔舞台上&#xff0c;借款人的“信用基石”——即其综合资质&#xff0c;是决定贷款利率高低的决定性因素。这并非偶然&#xff0c;而是银行基于详尽的风险评估与收益预期所做出的精准判断。 需明确的是&#xff0c;贷款的易得性并不意味着无门槛的放任。它更像是设置…

【动手学深度学习】04 数据操作 + 数据预处理(个人向笔记)

数据操作 N维数组是机器学习和神经网络的主要数据结构其中 2-d 矩阵中每一行表示每一行表示一个样本 当维度来到三维的时候则可以表示成一张图片&#xff0c;再加一维就可以变成多张图片&#xff0c;再加一维则可以变成一个视频 访问元素 冒号表示从冒号左边的元素到冒号右…

巨魔商店2安装教程,支持最新iOS 17.0的所有型号

支持iOS15.0到16.6.1、16.7 RC (20H18)和17.0的所有iPhone/iPad。 工具下载地址&#xff1a; https://wwi.lanzoup.com/b0knd3q5g 密码:czhi Windows安装教程&#xff1a; 1&#xff0c;电脑必须安装ITunes&#xff0c;关闭查找我的 iPhone&#xff0c;手机上必须要有提示&…

python imshow报错怎么解决

实例如下所示&#xff1a; import matplotlib.pyplot as plt plt.imshow(img) #控制台打印出图像对象的信息&#xff0c;而图像没有显示 解决方法&#xff1a; #引入pylab解决 import matplotlib.pyplot as plt import pylab plt.imshow(img) pylab.show()

MES管理系统助力企业车间管理可视化

MES管理系统作为连接计划层与现场自动化系统的桥梁&#xff0c;在助力企业车间管理可视化方面发挥着重要作用。以下是MES管理系统如何助力企业车间管理可视化的详细阐述&#xff1a; 一、MES管理系统的概述 MES&#xff08;制造执行系统&#xff09;是一种用于生产车间的实时信…

点云数据常见的坐标系有哪些,如何进行转换?

文章目录 一、点云坐标系分类1. 世界坐标系2. 相机坐标系3. 极坐标系4. 笛卡尔坐标系(直角坐标系):5. 传感器坐标系6. 地理坐标系二、坐标系转换方法1. 地理坐标系与投影坐标系之间的转换2. 投影坐标系与局部坐标系之间的转换3. 局部坐标系与3D模型坐标系之间的转换4. 相机坐…

访问修饰符public、protected、private,基于C++

一、基本概念 公有&#xff08;public&#xff09;成员 公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值&#xff0c; 私有&#xff08;private&#xff09;成员 私有成员变量或函数在类的外部是不可访问的&#xff0c;甚至是不…

部落冲突:精通五个流派,部落战传奇杯横着走,但前提是能精通!

部落冲突&#xff1a;精通五个流派&#xff0c;部落战传奇杯横着走&#xff0c;但前提是能精通&#xff01; 在部落冲突当中有很多的流派&#xff0c;每个流派都有着属于自己的特点&#xff0c;但是速途同归的每个流派的最终目的都是用来打部落战和传奇杯&#xff0c;日常打资…

MAT:一款针对MSSQL服务器的安全检测与审计工具

关于MAT MAT是一款针对MSSQL服务器的安全检测与审计工具&#xff0c;该工具使用C#开发&#xff0c;可以帮助广大研究人员快速识别和发现MSSQL 服务器中的安全问题&#xff0c;并实现安全检测与审计目的。 功能介绍 1、执行自动检查并识别安全问题&#xff1b; 2、允许通过 Win…

驾驭冰雪 安全无忧,韩泰高性能冬季轮胎新品上市

- 韩泰轮胎推出冬季轮胎新产品Winter i*cept iZ3和SUV专用的Winter i*cept iZ3 X - 新轮胎采用了V型花纹&#xff0c;冰雪路面安全性极佳&#xff0c;而且具有操控性好、续航里程长的优点 - 新轮胎在位于北极圈以北300km的韩泰轮胎芬兰伊瓦洛测试场进行了严苛测试&#xff0c…

将用户证书导入到系统证书

现在大部分app已经不信任用户证书,通过传统charles安装证书抓包的方式已经不行,今天就来说一个将系统证书移动到系统目录的方法,系统证书的目录只有可读权限,有时候挂在目录会不成功 我们先下载如下模块 GitHub - ys1231/MoveCertificate: 支持Android7-14移动证书&#xff0…

【观察者】设计模式:构建灵活且响应式的软件系统

引言 在软件开发中&#xff0c;我们经常面临需要在多个对象之间进行通信的挑战。特别是当一个对象的状态发生变化时&#xff0c;我们希望所有依赖于这个状态的对象都能自动更新。这就是观察者设计模式大显身手的地方。 简介 观察者模式是一种行为设计模式&#xff0c;它定义…

【机器学习】XGBoost的用法和参数解释

一、XGBoost的用法 流程&#xff1a; 代码案例&#xff1a; 二、XGBoost的几大参数 1、一般参数&#xff0c;用于集成算法本身 ①n_estimators 集成算法通过在数据上构建多个弱 评估器&#xff0c;汇总所有弱评估器的建模结果&#xff0c;以获取比单个模型更好的回归或分类…

C语言蓝桥杯

一、语言基础 竞赛常用库函数 最值查询 min_element和max_element在vector(迭代器的使用) nth_element函数的使用 例题lanqiao OJ 497成绩分析 第一种用min_element和max_element函数的写法 第二种用min和max的写法 二分查找 二分查找只能对数组操作 binary_search函数&…