C语言进阶第三课-----------指针的进阶----------后续版

news2025/2/24 22:25:23

作者前言

🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
​🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂


指针进阶后续

  • **作者前言**
  • 复习
    • 字符指针
    • 数组指针
    • 指针数组
    • 数组传参和指针传参
    • 函数指针
  • 函数指针数组
  • 指向函数指针数组的指针(可以理解为数组的指针,只是类型变了)
  • 回调函数
  • 总结

复习

在上一篇博客中我简单的介绍了一些,这里我们来复习一下

字符指针

#include<stdio.h>
int main()
{
	char* pc = "abcdef";
	printf("%p\n", pc);
	printf("%c", *pc);
	return 0;
}

在这里插入图片描述
pc存放的是字符a的地址,不是字符串
前面我们还学习到了const char * pa 和char *coust pa

数组指针

简单的讲就是指向数组的指针

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int(*parr)[10] = &arr;
	printf("%p\n", parr);
	printf("%p", parr + 1);
	return 0;
}

在这里插入图片描述
可以计算出两个地址相差40,也就是40个字节
这里我们又会引出一个知识点,就是数组名的理解,只有&arr 和sizeof(arr)中的arr是整个数组,不是数组首元素地址,其他的数组名都是首元素地址
如果是二维数组传参的话,传递的是第一行的地址

指针数组

简单的说还是数组,只不过元素的类型是指针类型

#include<stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	int* arr[] = { &a, &b,&c };
	printf("%p", arr[0]);

	return 0;
}

数组传参和指针传参

数组传参
1. 传递的是数组首元素的地址
2. 一维数组传参,传递的是第一个元素的地址
3. 二维数组传参,传递的是第一行的地址
4. 数组在传参的时候,形参可以写成数组或者指针
5. 如果是二维数组传参,形参为指针要使用数组指针

函数指针

顾名思义就是指向函数的指针

#include<stdio.h>
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 4;
	int b = 5;
	int (*pA)(int, int) = Add;
	printf("%d", pA(a, b));
	return 0;
}

在这里插入图片描述
在这里&Add和Add都是传递函数的地址过去,还有一点就是(*pA)(a, b)和(pA)(a, b)是一样的,

函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,可以理解是存放地址的数组。那函数指针数组就是存放函数指针的数组,类型就是函数指针类型,那我就以上面的为例

#include<stdio.h>
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 4;
	int b = 5;
	int (*parr[])(int, int) = { Add };
	printf("%d", parr[0](a, b));
	return 0;
}

在这里插入图片描述
看看是一样的效果
那我们来写一个计算器

#include<stdio.h>
void menu()
{
	printf("*************************************\n");
	printf("*****     0.exit       1.add   *****\n");
	printf("*****     2.sub        3.mul    ******\n");
	printf("*****           4.div          ******\n");
	printf("*************************************\n");
}
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a + b;
}
int catf(int(*pa)(int, int))
{
	printf("输入两个整数:>");
	int num1 = 0;
	int num2 = 0;
	scanf("%d %d", &num1, &num2);
	return pa(num1, num2);
}
int main()
{
	
	while (1)
	{
		menu();
		int input = 0;
		printf("请选择:>");
		scanf("%d", &input);
		int (*arr[])(int, int) = { NULL,add, sub, mul, div };
		if (!input)
		{
			printf("结束");
			break;
		}
		else if (input > 4)
		{
			printf("输入错误\n");
			continue;
		}
		printf("%d\n", catf(arr[input]));
		

	}
	return 0;
}

这里我运用了函数指针数组(转移表),可以节约很多的代码量,简单的说就是很好用

指向函数指针数组的指针(可以理解为数组的指针,只是类型变了)

前面我们学过了数组指针,这里的意思也是一样的只是叫法不一样,那这个类型就是函数指针类型
,函数指针类型是这样的 int (* )(), 那指向函数指针数组的指针的写法就是 int (* (*parr)[num])(),感觉很别扭
少说废话,直接演示

#include<stdio.h>
int main()
{
	int a = 5;
	int b = 2;
	int* arr[] = { &a, &b };
	int* (*parr)[2] = &arr;
	printf("%d", *(*parr[0]));
	return 0;
}

这是整形指针数组,指向整形指针数组的指针
同理函数指针数组的指针如下

#include<stdio.h>
int add(int a, int b)
{
	return a + b;
}
int main()
{
	int (*arr[])(int, int) = { add };
	int (*(*parr)[1])(int, int) = &arr;
	printf("%d", (*parr[0])(5, 6));
	return 0;
}

这里我只使用了一个函数,小可爱可以看看思路,和数组指针的写法是一样,只是这个类型不一样而已

回调函数

这个知识点主要涉及到函数指针的,比较重要,我们来学习
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。
我们可以简单理解为回调函数的地址作为参数传递给另一个函数,也就是说回调函数通过通过传递本身地址给另外一个函数
上面我们写的计算器的代码就是使用到了回调函数
在这里插入图片描述
这里我简单的画了一下,回调函数就是add()函数,直接调用就是通过函数名直接调用

如果这里还不明白那就举一个函数qsort(),这是一个排序函数,底层是快速排序

#include<stdio.h>
#include<stdlib.h>
void print_arr(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
}
int cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
int main()
{
	int arr[] = { 9,8,7,4,5,6,1,2,3 };
	int sz = (sizeof arr / sizeof(int));
	print_arr(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp);
	print_arr(arr, sz);

	return 0;
}

可能一些小可爱不懂这个函数,那我就来介绍一下这个函数

在这里插入图片描述
void* :我们可以理解为一个垃圾桶,可以接收任何指针类型的数据,比如可以接收 int* 、char* 等类型的数据
base:就是我们要从哪里开始排序的第一个元素地址(待排序数据的起始地址)
size_t: 在一些编译器中 是unsigned int,而在一些编译器中就不是,这里就是unsigned int
num:我们要排序的元素个数
width:我们排序的元素中一个元素的大小,单位是字节
compare:是一个函数地址
我们可以想想前面我们的冒泡排序算法

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int arr[] = { 1,5,4,8,26,8,5,2,15,9 };
	int sz = sizeof arr / sizeof(arr[0]);
	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])
			{
				arr[j] = arr[j] ^ arr[j + 1];
				arr[j + 1] = arr[j] ^ arr[j + 1];
				arr[j] = arr[j] ^ arr[j + 1];
				

			}
		}
	}

	return 0;
}

主要核心就是两两相邻的元素进行比较,我们要理清排几次,每次怎么比较,冒泡排序有一些缺点,就是有点死板,只能排序某一类型,而qsort()可以排序任何类型,主要是在于compare函数的写法;这个函数是用来比较两个元素的,这两个元素可能是整形、字符、结构体等类型,使用的方法也会不同,但最终要返回 >0 <0 ==0的结果,简单的说就是我们要比较啥类型的这个函数就要写成该类型的比较方法
废话少说,直接演示
int ( *compare )(const void *e1, const void *e2 )
这是qsort函数里 的参数,我来解释一下,
可能有一些小可爱不明白void*,那我来解释一下
平时我们写一个int*类型的指针,我们加1会跳过4个字节,char* 类型加1跳过一个字节,如果我们在写比较两个元素比较的方法,不指明类型就会不懂加1跳过几个字节,所以规定了void *类型 不能解引用,也不能加减操作

#include<stdio.h>
#include<stdlib.h>
struct test
{
	char arr[20];
	int age;

};
int cmp2(const void* e1, const void* e2)
{
	return ((struct test*)e1)->age - ((struct test*)e2)->age;
}

int main()
{
	
	struct test arr[] = { {"ksdads", 15},{"ksdajk", 18} ,{"ksdadgjhjs", 10} };
	qsort(arr, sizeof arr / sizeof arr[0], sizeof(arr[0]), cmp2);

	return 0;
}

这是结构体比较年龄,如果要比较名字的话

int cmp3(const void* e1, const void* e2)
{
	return strcmp(((struct test*)e1)->arr, ((struct test*)e2)->arr);
}

那我们可不可以写一个以冒泡排序为逻辑的qsort函数呢?
我们来试试看

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

// 比较两个元素
int cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
//比较字符
int cmp1(const void* e1, const void* e2)
{
	return *(char*)e1 - *(char*)e2;
}
//两个元素进行交换
void exchange(char* e1, char* e2, size_t byt)
{
	int i = 0;
	for (i = 0; i < byt; i++)
	{
		char a = *(e1 + i);
		*(e1 + i) = *(e2 + i);
		*(e2 + i) = a;
	}

}
void my_qsort(void* base, size_t sz, size_t byt, int (*conpi)(const void*, const void*))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (conpi((char*)base + j * byt, (char*)base + (j + 1) * byt) > 0)
			{

				exchange((char*)base + j * byt, (char*)base + (j + 1) * byt, byt);
			}
			
		}
	}

}
void test(void *arr, int sz, size_t by)
{
	print(arr, sz);
	my_qsort(arr, sz, by, cmp);
	print(arr,sz);
}
int main()
{
	int arr[] = { 8,3,1,5,2,3,6,9,7,4,8,9,2,4};
	char arr1[] = "dsfdfgfdfgfdf";
	// 整形
	//test(arr, sizeof arr/ sizeof(arr[0]), sizeof (arr[0]));
	// 字符串
	printf("%s\n", arr1);
	my_qsort(arr1, sizeof arr1 / sizeof(arr1[0]), sizeof(arr1[0]), cmp1);
	printf("%s\n", arr1);



	return 0;
}

在这里插入图片描述
可能有一些小可爱不明白这里,那我来讲讲,因为,我们要构造一个qsort函数,那我们就该要传入对应的base(起始地址)、unm(长度)、wigth(一个元素的字节长度) 和函数cmp(用于比较两个的函数),
我们不知道要传入啥数据类型,我就用void*进行接收,使用上面这个方法是为了让对于的存储的内存进行交换,转换成char*是为了更好的计算
在这里插入图片描述
这幅图是我以两个元素进行交换的情况一个元素的内存可以拆分成内存单元(一个字节),转换成char*,加1,我们访问一个字节,这样就可以解决类型不同,访问的字节也不同的问题了

在这里插入图片描述

总结

到这里我就介绍结束了,有不懂的小可爱可以私聊我

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

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

相关文章

构建自动化测试环境:使用Docker和Selenium!

随着软件开发的日益复杂和迭代速度的加快&#xff0c;自动化测试被越来越广泛地应用于软件开发流程中。它能够提高测试效率、减少测试成本&#xff0c;并保证软件质量的稳定性。在构建自动化测试环境方面&#xff0c;Docker 和 Selenium 是两个非常有用的工具。下面将介绍如何使…

肖sir__mysql中数据库后端无法展示

mysql中数据库后端无法展示&#xff1a; 错误现象 解决方法&#xff1a; mysql中数据库后端无法展示&#xff1a;my.cnf (5,7数据库) 在 mysql 配置文件中加入&#xff1a; sql_modeNO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES 或者重启数据库

一文读懂 Redis 缓存系统

【摘要】本文介绍了Redis缓存原理、详细解析了缓存模型、缓存一致性和缓存异常场景。 【作者】李杰&#xff0c;专注于Java虚拟机技术、云原生技术领域的探索与研究。 尽管&#xff08;关系型&#xff09;数据库系统 (SQL) 带来了许多出色的属性&#xff0c;例如 ACID&#x…

APP产品经理岗位的具体内容(合集)

APP产品经理岗位的具体内容1 1、负责项目产品团队的管理工作&#xff0c;对项目产品团队考核目标负责; 2、全面负责“工务园”所有产品&#xff0c;全方位负责其生命周期管理; 3、按照产品管理相关的计划和规范&#xff0c;对产品版本的更新及发布负责&#xff0c;完善产品的…

MyBatis 反射模块

文章目录 前言反射模块实现ReflectorReflectorFactoryInvokerMetaClassMetaObject 反射模块应用SqlSessionFactory执行SQL 前言 MyBatis在进行参数处理、结果集映射等操作时会使用到大量的反射操作&#xff0c;Java中的反射功能虽然强大&#xff0c;但是代码编写起来比较复杂且…

MSTP+VRRP vlan接口作为网关(2)

SW2 g0/0/2 g0/0/5 g0/0/3 g0/0/4 shutdow 链路失效, vlan 3 的 根桥、master 依然是sw2 PC3的数据包会什么还会到达外部环回口&#xff1f; SW2: dis stp instance 2 brief dis vrrp brief vlan3的主机PC3访问3.3.3.3.数据包发给网关(master)Sw2 pc3 : tracert …

go语言unsafe.Pointer与uintptr

以下内容来源go语言圣经 1、unsafe.Pointer&#xff0c;相当于c语言中的void *类型的指针&#xff0c;如果需要运算需要转成uintptr类型的指针 2. uintptr uintptr是一个无符号的整型&#xff0c;它可以保存一个指针地址。 它可以进行指针运算。 uintptr无法持有对象, GC不把…

新能源汽车运行安全性能检验规程需要哪些CAN数据才符合标准

新能源汽车的前生命周期包括了整车制造、使用、转让市场及报废回收这几个主要阶段&#xff0c;在政策大力扶持下&#xff0c;国内新能源汽车的制造产业链完善&#xff0c;补贴培育市场取得丰硕的果实。目前来说&#xff0c;我国新能源汽车有着技术领先、设计先进、低成本优势&a…

Android集成微信支付

​ 打开微信开放平台登录账户后点击创建应用 根据提示填写完相应的内容提交审核通过后&#xff0c;需要开通支付权限。 1.接着在你的项目工程build.gradle文件中添加微信支付依赖库 ​api com.tencent.mm.opensdk:wechat-sdk-android: 2.在你的包的根目录下&#xff0c;创建w…

Nodejs+vue体育用品商城商品购物推荐系统_t81xg

本课题基于协同过滤算法&#xff0c;主要采用nodejs技术和MySQL数据库技术以及vue框架进行开发。功能主要包括首页、个人中心、用户管理、商品分类管理、商品信息管理、交流论坛、留言板、系统管理、订单管理等功能&#xff0c;从而实现个性化智能体育商品推荐方式&#xff0c;…

MyBatis基础之动态SQL

文章目录 动态 SQLif 元素choose-when-otherwise 元素where 元素set 元素foreach 元素 动态 SQL 简而言之&#xff0c;动态 SQL 就是在 Mapper 中使用分支、循环等逻辑。常见的动态 SQL 元素包括&#xff1a; if 元素choose-when-otherwise 元素where 元素set 元素foreach 元…

2024年中国计量大学MBA最新招生计划公布:有哪些看点?

中国计量大学MBA项目立项于2023年&#xff0c;第一年招生就顺利完成开班任务&#xff0c;虽然人数不多&#xff0c;但是因为其有自身的项目培养定位&#xff0c;因此未来的市场中还是可以保持自身的优势。从2024年最新的招生计划来看&#xff0c;中国计量大学的总体计划依然采取…

大数据-离线项目

第一章 需求分析 需求分析与设计 项目需求背景 "某APP上线后 经营得当 使用户 日活量增多 出现以下问题""营销分析断层&#xff1a;"市场营销成本居高不下&#xff0c;投放拉新的效果追踪出现断层&#xff0c;无法追踪各渠道实际转化率&#xff0c;难以…

应用在摄像头对焦镜头中的马达驱动芯片

摄像头&#xff08;CAMERA或WEBCAM&#xff09;又称为电脑相机、电脑眼、电子眼等&#xff0c;是一种视频输入设备&#xff0c;被广泛的运用于视频会议、远程医疗及实时监控等方面。普通的人也可以彼此通过摄像头在网络进行有影像、有声音的交谈和沟通。另外&#xff0c;人们还…

记录uniapp 微信小程序胶囊分享bug处理 (第一次点击分享的时候不能修改标题或者路径)

问题复现情况: 以下为博主代码&#xff1a; 问题原因: 因为博主的业务需求&#xff0c;需要在分享的时候调用后端的接口来实现分享挂载推广关系。也就是因为调用了接口导致 重新给分享赋值标题或者路径的时候赋值不上。&#xff08;刚开始怀疑是微信分享的问题&#xff0c;后面…

HarmonyOS创作激励计划启动:助力技术创作突破边界

即日起推出HarmonyOS创作激励计划&#xff0c;成功投稿并入选的文章将在HarmonyOS开发者公众号上线&#xff0c;9大技术社区同步宣发&#xff0c;不仅有丰厚稿酬&#xff0c;还有机会赢取创作奖品&#xff01; 活动时间 即日起-2024年12月31日&#xff0c;每季度按照活动规则评…

RabbitMQ 几种模式

一、Hello World 模式 在这一部分中&#xff0c;我们将用 Java 编写两个程序。发送单个消息的生产者和接收消息并打印出来的消费者。模型如下所示&#xff1a; 在下图中&#xff0c;“ P” 是我们的生产者&#xff0c;“ C” 是我们的消费者。中间的框是一个队列 RabbitMQ 代表…

灞桥论“健” 共话康养 灞桥康养论坛取得圆满成功

随着我国“老龄化”的加速&#xff0c;养老资源的匮乏已经成为一个十分严峻的社会问题。同时随着生活水平的大幅提高&#xff0c;康养产业应势而生。涵盖了养老、医疗、体育、养生、旅游等多个领域的康养产业&#xff0c;不仅要为老人们实现“老有所乐、身体健康”的理想&#…

【数据结构】二叉树的前序遍历(七)

题目&#xff1a;二叉树的前序遍历 题目详情&#xff1a;给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历&#xff1b; 我们先来看几个示例&#xff1a; 输入&#xff1a;root [ 1&#xff0c;null&#xff0c;2&#xff0c;3 ] 输出&#xff1a;[ 1&#xf…

【送书】从不了解用户画像,到用画像数据赋能业务看这一本书就够了丨《用户画像:平台构建与业务实践》

文章目录 内容了解本书目录参与方式 &#x1f308;hello&#xff01; 各位铁汁们大家好啊&#xff0c;今天给大家推荐的的是机械工业出版社的 《用户画像&#xff1a;平台构建与业务实践》这本书&#xff01;   ⛳️大数据时代&#xff0c;如何有效地挖掘数据价值并通过画像数…