C语言之指针进阶篇(3)

2023-09-13 21:06:23

目录

思维导图

回调函数

案例1—计算器

案例2—qsort函数

关于qsort函数  

NO1. 

NO2.

NO3. 

NO4. 

演示qsort函数的使用

案例3—冒泡排序 

整型数据冒泡排序

回调函数搞定各类型冒泡排序

cmp_int比较大小

 cmp传参数

NO1.

NO2.

解决方案

交换swap

总代码


今天我们学习指针难点之回调函数🆗🆗🆗。

首先我们用思维导图回顾一下前面的内容。

思维导图

回调函数

回调函数就是一个通过函数指针调用的函数。

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

案例1—计算器

就前文我们学习的计算器,我们再用回调函数来解决一下!🆗🆗🆗

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

回调函数 

#define _CRT_SECURE_NO_WARNINGS 1
//计算器
#include<stdio.h>
void meau()
{
	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;
}
void calc(int(*p)(int, int))//函数指针传参
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数\n");
	scanf("%d %d", &x, &y);
	int ret = p(x, y);//函数调用
	printf("ret=%d\n", ret);
}
int main()
{
	int input = 0;
	do
	{
		meau();
		printf("请选择>\n");
		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("退出游戏");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

解释如下: 

在main函数中,没有直接去调用函数。而是把函数指针传参给另外的一个函数calc,在calc内部使用函数指针调用,通过函数指针就可以找到指针指向的函数,此刻被指向的函数就是回调函数。

像上图所示,通过calc函数调用Add函数指针,找到Add函数,就把Add函数称为回调函数

calc称为回调函数的机制

老板>>组长>>员工

案例2—qsort函数

那出了上面回调函数的案例,还有一个经典回调函数的案例:qsort

 qsort是一个库函数,底层使用的是快速排序的方式,对不同数据进行排序的。

这个函数可以直接使用。

这个函数可以用来排序任意类型的数据。

对数据进行排序方法很多:

冒泡排,序选择排序,插入排序,快速排序等等。 

关于qsort函数  

NO1. 

关于qsort函数的点--->qsort - C++ Reference (cplusplus.com)

需要包含头文件#include<stdlib.h>

  •  排序整型数组,两个整型可以直接使用>比较
  • 排序结构体数组,两个结构体的数据可能不能直接使用>比较

也就是不同类型的数据,比较大小的方法是有差异的

最后一个参数,排序不同数据的重要点,需要封装不同的函数去比较不同的数据的大小

void qsort(void* base, //指向了待排序数组第一个元素的首地址
	       size_t num, //待排序数组的元素个数
	       size_t size,//每个待排序数组元素的大小
	       int (*compar)(const void* e1, const void* e2));
//函数指针,compar指向了一个函数,这个函数是用来比较两个元素的大小,
//e1和e2存放的是两个元素的地址
//在qsort内部调用这个函数,指向这个函数,这个函数就被称为回调函数
// 
//qsort内部怎么排序我们不需要过多去探讨
//const也暂不做讲解

//因为不知道要比较的元素类型,所以我们使用void*指针的类型,来统一存放各种类型的指针
NO2.

那怎样通过元素地址,去比较两个整型元素数据的大小呢? 

int的数据:将void*类型的数据强制转化成(int*),再作差

当e1>e2,函数返回>0的值;

当e1<e2,函数返回<0的值;

当e1=e2,函数返回=0

//整型
void compar_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
NO3. 

那怎样通过元素地址,去比较两个字符元素数据的大小呢? 

 

 char的数据:strcmp库函数的使用,需要带头文件哦,#include<string.h>

strcmp - C++ Reference (cplusplus.com)

//字符串
void compar_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
	//return strcmp((*(struct Stu*)e1).name , (*(struct Stu*)e2).name);
}
NO4. 

那有人询问为什么不直接对元素地址const void* e1和 const void* e2解引用?

作为void*指针不能直接解引用。

void* 类型的指针—不能进行解引用操作符,也不能进行+-整数的操作
void* 类型的指针是用来存放任意类型数据的地址
void* 无具体类型的指针
void*和int*和char*一样都是指针类型

#include<stdio.h>
int main()
{
	char a = 'x';
	char* pa = &a;
	int b = 1;
	void* p = &b;//存放int*
	p = &a;//存放char*
	return 0;
}

演示qsort函数的使用

#include<stdio.h>
#include<stdlib.h>
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
}

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



void compar_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void test1()
{
	int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);
	printf("\n");
	qsort(arr, sz, sizeof(arr[0]), compar_int);
	print(arr, sz);
}

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

以上我只是以整型为例,结构体数据数组也是一样的逻辑,大家可以自行分析。

下面结构体:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>//strcmp的头文件

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

struct Stu
{
	char name[20];
	int age;
};
//结构体数据怎么比较呢?
//按照年龄比较
//按照名字比较

//按照年龄
void compar_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
	//return (*(struct Stu*)e1).age - (*(struct Stu*)e2).age;
}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), compar_stu_by_age);

}
//按照名字
void compar_stu_by_name(const void* e1, const void* e2)
{
	return strcmp((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
	//return (*(struct Stu*)e1).name - (*(struct Stu*)e2).name;
}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), compar_stu_by_name);

}

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

只要qsort函数使用得当,可以对任何数据进行排序!🆗🆗 

案例3—冒泡排序 

(使用回调函数,模拟实现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 - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = 0;
				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]);
	}
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print_arr(arr, sz);
}
回调函数搞定各类型冒泡排序

 经过分析冒泡排序,我们得到

void bubble_sort(void* base, size_t num, size_t size,

                            int (*cmp)(const void* e1, const void* e2))

cmp_int比较大小

以整型为例 

int (*cmp)(const void* e1, const void* e2)

e1是一个指针,存放了一个要比较的元素的地址。

e2是一个指针,存放了一个要比较的元素的地址。

e1指向的元素>e2指向的元素,返回>0的数字。

e1指向的元素<e2指向的元素,返回>0的数字。

e1指向的元素==e2指向的元素,返回>0的数字。

cmp是函数指针指向一个我们程序想要待排序的数组。

将比较函数cmp_int的地址传给cmp即可。

//比较大小
void cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
//这里就是将cmp_int的地址在调用函数bubble_sort时将其传过去即可。
 cmp传参数
NO1.

有同学提出直接对待排序的数组首元素地址解引用找到e1的地址,然后通过一个元素的大小或者+1可以找到e2的地址,可以吗?当然不可以

  • 作为void*指针不能直接解引用。

    void* 类型的指针—不能进行解引用操作符,也不能进行+-整数的操作
    void* 类型的指针是用来存放任意类型数据的地址
    void* 无具体类型的指针
    void*和int*和char*一样都是指针类型

NO2.

有同学又提出那将void*的指针强制转换成我们想要的int*或double*等,再+1可以吗?           不可以,理由就是,强制转换存在在于我们公共的bubble_sort排序函数中时不能随着待排序的数组数据类型不同而改变,我们只能改变不同数据类型的不同比较方法。

解决方案

 

			//if(arr[j]>arr[j+1])
			if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
			{
				int tmp = 0;
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		
交换swap

当我们只知道元素的起始地址,并不知道元素的类型所以我们并没有合适的中间值类型tmp创建。所以我们换一种方法。

我们已知元素e1和e2的起始地址每个元素的大小

那我们可以用一个一个char类型的数据交换用for循环

直到每个元素的大小size结束,也就是元素交换完成。

 

//交换数据
void change(char* buf1, char* buf2,size_t size)
{
	char i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = 0;
		tmp = *buf1;
		*buf1=*buf2;
		*buf2 = tmp;
		buf1++;//*buf1++
		buf2++;//*buf2++
	}
}
总代码
//冒泡排序
#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]);
	}
}

void bubble_sort(void* base, size_t num, size_t size,
	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(arr[j]>arr[j+1])
			if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
			{
				change((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}
//交换数据
void change(char* buf1, char* buf2,size_t size)
{
	char i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = 0;
		tmp = *buf1;
		*buf1=*buf2;
		*buf2 = tmp;
		buf1++;//*e1++
		buf2++;//*e2++
	}
}
//比较大小
void cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//>0
}

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

int main()
{
	test1();
}

当然我们也可以用结构体类型去测试一下! 🆗🆗试试

//冒泡排序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//打印函数
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

//排序函数
void bubble_sort(void* base, size_t num, size_t size,
	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(arr[j]>arr[j+1])
			if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
			{
				change((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

//交换数据函数
void change(char* buf1, char* buf2,size_t size)
{
	char i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = 0;
		tmp = *buf1;
		*buf1=*buf2;
		*buf2 = tmp;
		buf1++;//*e1++
		buf2++;//*e2++
	}
}

//结构体
struct Stu
{
	char name[20];//20
	int age;//4
};
//结构体数据怎么比较呢?
//按照年龄比较
//按照名字比较

//按照年龄
void compar_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
	//return (*(struct Stu*)e1).age - (*(struct Stu*)e2).age;
}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), compar_stu_by_age);

}
//按照名字
void compar_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
	//return strcmp((*(struct Stu*)e1).name , (*(struct Stu*)e2).name);
}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), compar_stu_by_name);

}

int main()
{
	test2();
}

我们把回调函数这种情况叫做泛型编程。即便泛型编程在C语言中比较牵强。 

最后,提出一个问题上面的题目我们都是正序排序。那如果我们想要倒序排序,代码又要怎样去修改呢?什么代码可以修改,什么代码不能修改呢?

那其实我们也在上面提到过,我们的排序函数代码bubble_sort是不能修改的。

以整型为例,所以我们只能修改比较大小的函数cmp_int

✔✔✔

//倒叙
//比较大小
void cmp_int(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e1;//>0
}
//正序
//比较大小
void cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//>0
}

✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!旗鼓相当

代码------→【gitee:唐棣棣 (TSQXG) - Gitee.com

联系------→【邮箱:2784139418@qq.com】

更多推荐

Js中一些数组常用API总结

前端面试题库(面试必备)推荐:★★★★★地址:前端面试题库【国庆头像】-国庆爱国程序员头像!总有一款适合你!前言Js中数组是一个重要的数据结构,它相比于字符串有更多的方法,在一些算法题中我们经常需要将字符串转化为数组,使用数组里面的API进行操作。本篇文章总结了一些数组中常用的API,我们把它们分成两类,一类是会改变原

数据信息会有哪些风险,云数据库如何保护?

数据信息安风险是多种多样的,那么,云数据库如何规避并保护数据信息安全?今天安策带大家具体来了解数据信息会有哪些风险,云数据库将如何保护:数据泄露:不管是内部人员疏忽,还是恶意攻击、系统漏洞等等原因,数据泄露会导致敏感信息的暴露,损害其品牌的声誉和利益。数据损坏:损坏原因主要由自然灾害、人为或恶意攻击构成,从而导致无法预

容器核心技术之Namespace与Cgroup

容器是一种流行的虚拟化技术,它允许我们在同一台计算机上与其他进程在独立环境中运行进程。那么容器是如何做到这一点的呢?为此,容器是从Linux内核的一些新功能构建的,其中两个主要功能是“namespace”和“cgroup”。1.Namespace1.1Namespace简介Namespace(命名空间)技术是一种内核级

Nvme Spec 第一章节学习

@NvmeExpressBaseSpecification第一章简介1.1概述NVMExpressTM(NVMeTM)接口允许主机软件与非易失性存储器子系统通信。此接口针对企业和客户端固态驱动器进行了优化,通常作为寄存器级接口连接到PCIExpress接口。注:在开发过程中,本规范被称为企业NVMHCI。然而,在完成之

YOLOv8『小目标』检测指南

前言目前博主课题组在进行物体部件的异常检测项目,项目中需要先使用YOLOv8进行目标检测,然后进行图像切割,最后采用WinCLIP模型进行部件异常检测但是在实际操作过程中出现问题,YOLOv8模型目标检测在大目标精确度不错,但是在小目标检测中效果极差我们之前的解决方案是扩大异常部件的目标检测范围,易于检测。但是缺点是会

stable diffusion在建筑行业应用

AI建筑研究室-帆哥投稿视频-AI建筑研究室-帆哥视频分享-哔哩哔哩视频点击观看AI建筑研究室-帆哥的全部投稿视频,在这里可以查看AI建筑研究室-帆哥最新发布、最多播放和最多收藏的视频。https://space.bilibili.com/2161614/videoHD▎ChatGPT接入自研AI工具集,为设计生产赋能

wkeOnDownload2与mbOnDownloadInBlinkThread

背景:最近开始实习(打工)生涯。需求:使用miniblink,显示网页,点击下载链接,可以实现下载。寻求大佬帮助,得到了wke.h版本的下载相关的代码。这里进行一些简单的分析:实际语句:wkeOnDownload2(webView,onDownloadCallback,nullptr);通过调用该函数实现:点击链接,实

SpringMVC之自定义注解

目录前言一、自定义注解1.Java注解简介2.注解的用处3.为什么要用注解4.自定义注解的应用场景5.注解的分类6.如何定义并使用自定义注解7.自定义注解三种使用案例案例一:案例二:案例三:二、Aop自定义注解的应用1.自定义注解类2.切面类3.Controller层前言随着Web开发的发展,越来越多的企业开始使用Sp

基于springboot+vue的便利店信息管理系统

博主主页:猫头鹰源码博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万+、专注Java技术领域和毕业设计项目实战主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询文末联系获取项目介绍:本系统适合选题:便利店、便利店管理、商店管理等。系统采用springb

C++之operator()和构造函数区别与总结(二百三十)

简介:CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.更多原创,欢迎关注:Android系统攻城狮1.前言本篇目的:理解C+

MYSQL--事务

目录一、事物的概念:二、事务的ACID特点:1.原子性:2.一致性:3.隔离性:4.持久性:三、隔离性:1.事务之间的相互影响:(1)脏读:(2)不可重复读:(3)幻读:(4)丢失更新:2.Mysql的隔离级别:3.隔离级别作用范围:四、事务控制语句:1.commit提交事务:2.ROLLBACK回滚:3.使用回滚点:

热文推荐