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

news2025/1/20 1:44:30

目录

思维导图

回调函数

案例1—计算器

案例2—qsort函数

关于qsort函数  

演示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函数  

关于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*指针的类型,来统一存放各种类型的指针

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

以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;
}

那有人询问为什么不直接对元素地址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>//?

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 ((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>
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();
}

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

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

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

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

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

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

相关文章

网站搭建从零开始(0)--域名的选择与解析

目录 确定用途 购买域名 使用可靠的注册商购买域名 想好域名关键词 检查域名是否可用 添加域名到购物车并完成购买 域名的解析 登录注册商账户 选择要配置的域名 进入DNS解析设置 添加DNS记录 保存配置 检查解析是否生效 提示 确定用途 在购买域名之前&#xf…

基于 MATLAB 的电力系统动态分析研究【IEEE9、IEEE68系节点】

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

TypeScript接口和类

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 接口 类 在 TypeScript 中&#xff0c;接口&#xff08;Interfaces&#xff09;和类&#xff08;Classes&#xff…

长尾关键词挖掘软件-免费的百度搜索关键词挖掘

嗨&#xff0c;大家好&#xff01;今天&#xff0c;我想和大家聊一聊长尾关键词挖掘工具。作为一个在网络世界里摸爬滚打多年的人&#xff0c;我对这个话题有着一些个人的感悟和见解&#xff0c;希望能与大家分享。 首先&#xff0c;让我坦白一点&#xff0c;长尾关键词挖掘工具…

零基础学前端(二)用简单案例去理解 HTML 、CSS 、JavaScript 概念

该篇适用于从零基础学习前端的小白 初学者不懂代码得含义也要坚持模仿逐行敲代码&#xff0c;以身体感悟带动头脑去理解新知识 一、导言 HTML&#xff0c;CSS&#xff0c;JavaScript 都是单独的语言&#xff1b;他们构成前端技术基础&#xff1b; &#xff08;1&#xff09;HTM…

【操作系统】进程控制

进程控制&#xff1a;创建新进程&#xff0c;撤销已有进程&#xff0c;实现进程状态转换等。 原语&#xff1a;进程控制用的程序段。执行期间不允许中断&#xff0c;用&#xff02;关中断&#xff02;和&#xff02;开中断&#xff02;指令&#xff08;特权指令&#xff09;实…

图片如何变小kb?分享最新图片压缩技巧

有时候&#xff0c;我们在上传图片时可能会遇到“图片太大&#xff0c;请压缩后再上传”的提示&#xff0c;这时就需要将图片大小进行压缩。那么&#xff0c;有哪些方法可以解决图片变小kb的问题呢&#xff1f;下面将给大家介绍三种实用的方法&#xff0c;帮助您轻松解决这个问…

Leetcode刷题_堆相关_c++版

&#xff08;1&#xff09;215数组中的第k个最大元素–中等 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O…

<C++>类和对象-中

目录 前言 一、类的6个默认成员函数 二、构造函数 2.1 概念 2.2 特性 三、析构函数 1. 概念 2. 特性 四、拷贝构造函数 1. 概念 2. 特征 五、赋值运算符重载 1. 运算符重载 2. 赋值运算符重载 六、实现一个完整的日期类 Date.h Date.cpp 总结 前言 上一节&#xff0c;我们…

【C语言】每日一题(半月斩)——day1

目录 &#x1f60a;前言 一.选择题 1.执行下面程序&#xff0c;正确的输出是&#xff08;c&#xff09; 2.以下不正确的定义语句是&#xff08; &#xff09; 3.test.c 文件中包括如下语句&#xff0c;文件中定义的四个变量中&#xff0c;是指针类型的变量为【多选】&a…

Acwing 2816. 判断子序列

Acwing 2816. 判断子序列 题目描述代码展示 题目描述 代码展示 #include<iostream>using namespace std;const int N 1e5 10;int a[N], b[N];int main() {int n , m;cin >> n >> m;for (int i 0; i < n; i ) scanf("%d", &a[i]);for (…

使用IO操作文件

一、File类操作文件或目录 1.File操作文件或目录 java.io.File 类是文件和目录 路径名 的抽象表示&#xff0c;主要用于文件和目录的创建、查找和删除等操作。 Java虚拟机中的一个 File 实例表示一个路径&#xff0c;这个路径可能对应一个文件或一个目录。 File 类的实例所表…

论文阅读 - Outlier detection in social networks leveraging community structure

目录 摘要 1. Introduction 2. Related works 3. Preliminaries 3.1. 模块化度量 3.2. Classes of outliers 3.2.1. 点异常 3.2.2. Contextual anomalies 3.2.3. Collective anomalies 3.3. Problem definition 3.4. Outliers score 4. Methodology 4.1. Proposed appr…

ESP32编译出现Cannot establish a connection to the component registry.报错

前言 &#xff08;1&#xff09;在对ESP32开发使用的时候&#xff0c;很容易踩坑&#xff0c;但是总是找不到问题所在。很多时候只能去外网查资料&#xff0c;语言不通&#xff0c;而且有墙特别的难受。就算能够找到乐鑫的工作人员询问&#xff0c;也整个过程也感觉有些许麻烦。…

Vue2电商前台项目——完成Detail详情页模块业务

Vue2电商前台项目——完成Detail详情页模块业务 Vue基础知识点击此处——Vue.js 文章目录 Vue2电商前台项目——完成Detail详情页模块业务一、项目开发步骤二、配置路由规则滚动行为1、配置路由规则2、滚动行为 三、请求详情页数据并展示数据1、写接口2、写Vuex仓库3、派发acti…

ESP32S3的ESP_LOGx()控制台输出详细介绍

前言 &#xff08;1&#xff09;接触一款新的芯片&#xff0c;第一步要么是点灯&#xff0c;要么是进行串口的输出。现在我将介绍如何快速的利用ESP32S3进行控制台的日志输出。 &#xff08;2&#xff09;对于ESP32进行日志输出还是相对简单的&#xff0c;不像其他芯片需要配置…

三维模型3DTile格式轻量化压缩的遇到常见问题与处理方法分析

三维模型3DTile格式轻量化压缩的遇到常见问题与处理方法分析 三维模型的轻量化压缩是一项技术挑战&#xff0c;特别是在处理复杂的3DTile格式时。下面列举了一些处理过程中可能遇到的常见问题以及相应的处理方法&#xff1a; 模型精度损失&#xff1a;在进行压缩处理时&#x…

C语言入门Day_19 初识函数

目录 1.函数的定义 2.函数的调用 3.易错点 4.思维导图 前言&#xff1a; printf()我们已经很熟悉了&#xff0c;它有一个特定的功能&#xff0c;就是在屏幕上输出一行文字。之前的课程我们都称呼printf()为一个功能&#xff0c;实际上ta在编程中有个特定的名字——函数。 …

IO和进程day07(IPC、管道、信号)

今日任务 1.代码&#xff08;一次只能发一次消息&#xff0c;不然会存在一些小问题&#xff0c;在接受消息之后会立刻输出上次的第二次发送的消息&#xff09; read.c文件&#xff08;先读取消息&#xff09; #include <stdio.h> #include <string.h> #include &…

org.apache.ibatis.binding.BindingException: Invalid bound statement

在学习黑马头条遇到的关于使用mapper执行方法出现的错误 完整报错信息为&#xff1a;org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.heima.wemedia.mapper.WmNewsMaterialMapper.saveRelations 译为&#xff1a;无效的绑定语句&…