带你学C语言~指针(3)


目录

✍0.前言

🚀1.字符指针变量

🚅2.数组指针变量

🐱‍🏍2.1.数组指针变量是什么

🐱‍🏍2.2数组指针变量怎么初始化

🚢3.二维数组传参的本质

🚀4.函数指针变量

✈4.1函数指针变量创建

✈4.2函数指针变量的使用

🏹5.函数指针数组

🏆6.转移表

🌍结束语


✍0.前言

在上一节指针中我们讨论了一系列和指针相关的知识,包括二级指针,指针数组,但光靠这些知识还无法完全理解指针,因为指针还有函数指针这样一极其关键的类型,而我们上一章也没有具体聊如何存储一个字符串,以及如何访问它。好了下面就随着小赵的步伐一起来看看这些知识吧。

🚀1.字符指针变量

首先我们回顾一下我们的字符指针的定义

int main()
{
	char m = 'a';//定义一个字符变量
	char* ch = &m;//字符指针变量,接收m的地址
	*ch = 'w';//对ch解指针,对m里面的值进行修改
	return 0;
}

我们在上一章是讲过这种单个字符的指针的变量,但如果我们存入字符指针的是一个字符串而不是字符结果又会是怎么样的呢?

int main()
{
    const char* pstr = "abcdfe";
    printf("%s", pstr);
    return;
}

那这里的运行原理又是怎么样的呢?是不是我们就是把一个字符串放入了指针里呢?其实不是,因为指针也无法存储这种数据,它存储的毕竟是地址,那究竟是咋回事呢?其实这里我们的指针存放的是我们这个字符串的首地址, 可以看这样的一个代码

这样就证明了我们的我字符指针存储的其实是我们的字符串的首字母的地址。那么知道了这些知识我们就可以去看一道极其有意思的题目

int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char* str3 = "hello world";
	const char* str4 = "hello world";
	if (str1 == str2)
	{
		printf("str1 and str2 are same\n");
	}
	else
	{
		printf("str1 and str2 are not same\n");
	}
	if (str3 == str4)
	{
		printf("str3 and str4 are same\n");
	}
	else
	{
		printf("str3 and str4 are not same\n");
	}
	return 0;
}

大家可以先自己猜测一下这个代码运行的结果,然后和小赵这边的答案比对一下。

 从这个结果我们可以惊讶的发现我们的str3和str4居然是一样的,这又是为什么呢?

首先我们来说一下为什么str1和str2不一样,因为两个数组开辟的地址不一样,两个存了都在数组里存了一样的代码而已。

接着就是我们的str3和str4,首先是他们接收的地址来自字符串,而这个字符串其实是在我们的内存中开辟一块地区的,这块地区是这个字符串的,那么我们的地址去访问这个字符串的时候其实访问的地方是一样的,就是都用了一个字符串的首字母的地址。

上面这个题目来自我们的《剑指offer》这样一本书,各位如果有兴趣可以去看看哦。

🚅2.数组指针变量

在上一章小赵和大家聊了我们的指针数组,即在数组中存储指针,那么我们是否存在指针去存储我我们的数组呢?答案是有的那么我们来看看它长什么样子吧。

在这里我们可以对比一下我们的指针数组

在这里我们发现当我们的*与ptr结合时候,他就是个指针,那么其(*ptr)[10]也就是个数组指针,而另一个呢,我们发现*是与int结合那么它表示的就是我们数组里面存的是指针类类型也就是指针数组啦。

🚢3.二维数组传参的本质

在上一章,小赵与大家重点聊了,一维数组传参的本质,说一维数组传参其实传的是首元素的地址,那么我们二维数组根据这个推广其实大家也可以猜到其实传的就应该是二维数组首元素的地址,那么二维数组的首元素是什么呢?首先我要带大家回想的就是二维数组是由什么组成的,在前面小赵重点和各位聊过,二维数组其实就是由一个个的一维数组组合而成,那其里面的元素就是我们的一维数组那我们传入的首元素的地址其实不就是我们的二维数组里面首个一维数组的地址吗?那知道了这个,我们就可以试试去用我们上述的数组指针去代替我们原本的代码。

原本的代码

#include <stdio.h>
 void test(int a[3][5], int r, int c)
 {
	 int i = 0;
	 int j = 0;
	for (i = 0; i < r; i++)
		 {
		for (j = 0; j < c; j++)
			 {
			 printf("%d ", a[i][j]);
			 }
		printf("\n");
		 }
	 }
 int main()
 {
	 int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	 test(arr, 3, 5);
	 return 0;
}

用指针后的

#include <stdio.h>
void test(int(*p)[5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}

 其实区别并不大,小赵在这里只是解释了一里面的本质,方便大家更进一步的的理解之前我们的代码,在使用方面各位可以自由选择。

🚀4.函数指针变量

在开始我们的函数指针变量之前啊,我们要先看看我们的函数是否存在地址,然后就是它是否和数组一样是名字就是他们的地址呢?只有知道了这两点,我们才能更好地开展我们下面的函数指针。

void test()
{
	printf("hehe\n");
}
int main()
{
	printf("test:%p\n", test);
	printf("&test:%p\n", &test);
}

在这里我们看到我们两个地址是一样的那其实也就说明了1函数有地址,2函数名就是他们的地址

🐱‍🏍4.1函数指针变量创建

其实函数指针的创建我们可以参考一下数组指针,我们发现在数组指针的创建过程中大体并没有大的变化只是变化了数组的名字,改为了指针的名字,那么我们的函数指针创建也是如此。

void test()
{
	printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{
	return x + y;
}

int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

 

🐱‍🏍4.2函数指针变量的使用

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

	printf("%d\n", (*pf3)(2, 3));
	printf("%d\n", pf3(3, 5));
	return 0;
}

🐱‍🏍4.3两段有趣的代码

好了学习了上面的一系列知识后,我们下面给各位上盘大菜,就是我们的这样两段代码,也是小赵发在我们的博客上求助的两段代码。

(*(void (*)())0)();

先看第一个如何理解这样一段代码呢

小赵在这里为大家画个图,这个图是由里面向外看的。对这个代码做个较为清晰的解释。

第二段代码是

void (*signal(int , void(*)(int)))(int);

在这里小赵也是给大家画一个图方便大家理解

这两段代码出自我们的《C陷阱与缺陷》这本书,有兴趣的小伙伴可以看看,这本书可以提升大家对于C语言的理解和认知,让大家更好地学习和使用我们的C语言。 

 🎇补充typedef关键字

看了上面两段代码,我们有时候可能会感觉我们这个返回类型好大一块,还要分开写,这样好像并不方便我们的C语言读者的阅读和使用那有木有办法让我们的代码缩短呢,让返回类型只在函数名前面也好啊,答案是有的,这里就要引入我们的typedef这个关键字,这个关键字是干嘛的呢?我们可以先上网搜搜看,小赵这里用的网站是C 库函数 – srand() | 菜鸟教程

 

我们可以看到它的主要作用其实就是改名,把我们原本的类型改掉 名字有了这个我们就可以优化前面的代码了。

 

当然还可以用来修改其他的如int 

注意一下修改名字的位置就可以了。

🏹5.函数指针数组

上一章我们讨论了指针数组,那这一章我们学了函数指针是否可以定义一个函数指针数组呢?,来看下面。

int (*parr[3])();//parr 先和 [] 结合,说明 parr1是数组,而数组的元素就是外面这一圈int (*)() 类型的函数指针

接着我们可以试着创建自己的函数指针数组 

int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int main()
{
	int(*p[2])(int x, int y) = { add, sub };
}

这里要注意的是我们函数指针数组定义好的元素类型是不能改的,不能有的是传入一个元素有的是传入两个,也不能一个有返回值一个没有,或者返回类型不行。 

🏆6.转移表

好了,小赵下面带大家做一个转移表,,同时使用一下我们的函数指针数组

#include <stdio.h>
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 main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add             2:sub \n");
		printf(" 3:mul             4:div \n");
		printf(" 0:exit                  \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

在这个转移表中我们其实就是奖我们的各个功能能够灵活调用有点像我们的计算机,那我们能否用我们的函数指针数组对其进行改造呢?答案是可以的。

#include <stdio.h>
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 main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输⼊有误\n");
		}
	} while (input);
		return 0;
		
}

我们会发现我们是用我们的函数指针数组让我们的代码更加美观优化,更加好阅读。 

🌍结束语

好了小赵今天的分享就到这里了,如果大家有什么不明白的地方可以在小赵的下方留言哦,同时如果小赵有什么地方说得不对也希望得到大家的指点,谢谢各位家人们的支持。你们的支持是小赵创作的动力,加油。

如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持小赵,如有不足还请指点,小赵及时改正,感谢大家支持!!!

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

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

相关文章

Ubuntu-22.04编译安装FLTK

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、FLTK是什么&#xff1f;二、下载源代码三、准备编译环境四、编译五、运行测试程序六、Demo总结 前言 最近在研究FLTK&#xff0c;突然对它感了兴趣&#x…

猫粮选择困难?5款性价比高的主食冻干品牌推荐

近年来&#xff0c;冻干猫粮作为热门的高品质猫粮&#xff0c;受到了许多追求纯天然、健康食品的铲屎官的关注。萌新铲屎官就很疑惑了冻干猫粮可以代替猫粮作为主食吗&#xff1f;冻干猫粮真就那么好吗&#xff1f; 那么今天作为一个宠物店店长&#xff0c;跟大家一起聊聊各种…

UG模型的显示与隐藏

在UG中&#xff0c;除了通过图层的方式控制模型的显示与隐藏外&#xff0c;还可以直接通过显示与隐藏命令&#xff0c;位置在菜单-编辑-显示与隐藏&#xff0c;需要注意的是这些命令只能对可视图层中的模型进行控制 显示与隐藏&#xff1a;ctrl w 可以通过模型的类别&#xf…

系统架构设计师笔记

第1章计算机组成与体系结构 1.1.1计算机硬件的组成 &#xff08;1&#xff09;控制器。控制器是分析和执行指令的部件&#xff0c;也是统一指挥并控制计算机各部件协调工作的中心部件&#xff0c;所依据的是机器指令。控制器的组成包含如下。 ①程序计数器PC&#xff1a;存储下…

忻府区政协主席郭新和带队视察洁晋发电环保教育基地中国流动科技博物馆展厅

忻府区政协主席郭新和带队视察洁晋发电环保教育基地中国流动科技博物馆展厅 2023年12月5日&#xff0c;忻府区政协主席郭新和带队视察中国流动科技博物馆展览情况。 郭新和一行来到位于忻州市洁晋发电有限公司环保教育基地的中国流动科技馆忻府区展厅。展厅内配备了科普知识展…

2023 年备受瞩目的向量数据库赛道盘点出炉

近日&#xff0c;Zilliz 捷报频传&#xff0c;先后斩获来自极客公园、量子位、界面科技、OSCHINA 等行业头部媒体所颁发的奖项以及荣登相应榜单。 接下来&#xff0c;就让我们一起来了解一下这些颇受业界认可的奖项及榜单&#xff5e; 2023 中国创新力量 50 榜单 【2023 中国创…

托盘在线缠绕机

托盘在线式缠绕机是适应流水线作业的包装机械&#xff0c;很适合现代化企业的自动化包装的需要&#xff0c;对提高包装效率&#xff0c;降低劳动强度&#xff0c;有效利用人力资源&#xff0c;起到较好的作用。目前&#xff0c;托盘在线缠绕机已经在化工、电子、食品、饮料、造…

小李的2023年度读书盘点

小李的2023年度读书盘点 主题读书 1、纪实文学 和去年一样&#xff0c;依然以上海译文出版社的“译文纪实”系列图书为主。该系列大部分图书质量说得过去&#xff0c;今年读的几本中&#xff0c;一本不及格&#xff08;3/5星以下&#xff09;&#xff0c;《打工女孩》&#…

Facebook企业户与个人户的区别是什么?一文带你看懂Facebook企业户!

Facebook 作为最大的社交平台之一&#xff0c;吸引了大多数的卖家进行投放广告&#xff0c;并且投放广告的效果也是很不错的。Facebook个人账户大家都不陌生&#xff0c;那么 Facebook 有企业户吗&#xff1f;当然是有的&#xff0c;Facebook 有针对企业和品牌的专门广告账户&a…

10 种最佳排序算法原理及代码

什么是排序算法? 从本质上讲&#xff0c;排序算法是一种计算机程序&#xff0c;它将数据组织成特定的顺序&#xff0c;例如字母顺序或数字顺序&#xff0c;通常是升序或降序。 排序算法能干啥? 排序算法主要用于以有效的方式重新排列大量数据&#xff0c;以便更容易地进行搜…

围栏中心点

后端返回的数据格式是 [{height: 0,lat: 30.864277169098443,lng:114.35252972024682}{height: 1,lat: 30.864277169098443,lng:114.35252972024682}.........]我们要转换成 33.00494857612568,112.53886564762979;33.00307854503083,112.53728973842954;33.00170296814311,11…

MySQL日期查询 今天、明天、本月、下月、星期、本周第一天、本周最后一天、本周七天日期

文章目录 今天日期明天日期本月第一天本月最后一天下个月第一天当前月已过几天当前月天数当前月所有日期获取星期本周第一天本周最后一天获取本周的七天日期今天日期 select curdate()明天日期 select DATE_SUB(curdate(),INTERVAL -1 DAY) AS tomorrow本月第一天 select da…

阿春的450MT售价提前大曝光,符合你的预期吗?

最新消息&#xff0c;阿春的450MT在国外的官网上已经曝光&#xff0c;售价8990澳元&#xff0c;折合RMB 4.35万元&#xff0c;毕竟对他们来说这也是进口车嘛。那么从这个售价可以看出一些端倪&#xff0c;同平台的450SR海外售价8290澳元&#xff0c;450MT和450SR差价700澳元&am…

UI自动化测试的痛点

当我们找工作的时候查看招聘信息发现都需要有自动化测试经验&#xff0c;由此看来测试人员不会一点自动化测试技术都不好意思说自己是做软件测试的。大部分测试人员也都是从使用自动化测试工具、录制回放、测试脚本、开发小工具入门自动化测试的&#xff0c;然后在慢慢的接触 U…

idea设置自定义快捷键定义代码块

文章目录 idea设置自定义快捷键定义代码块1、首先打开setting然后找到editor中的live templates2、点击加号先新建一个Mygroup3、然后就可以按下图添加自己的group进行代码块快捷键的设置了 idea设置自定义快捷键定义代码块 1、首先打开setting然后找到editor中的live templat…

RK3568驱动指南|第八篇 设备树插件-第84章设备树插件参考资料介绍

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

基于合宙Air700E的4G环境监测节点(温湿度、气压等数据),通过MQTT上传阿里云物联网平台

基于合宙Air700E的4G环境监测节点&#xff08;温度、湿度、气压等数据&#xff09;&#xff0c;通过MQTT上传阿里云物联网平台。 介绍 合宙Air700E 4G模块读取传感器&#xff08;温湿度、气压等&#xff09;数据并通过MQTT协议上传阿里云物联网平台&#xff0c;数据也会同时显…

最新9.9付费进群saas系统源码已修复定位及已知bug

距上版&#xff0c;优化一下功能&#xff0c;修复了已知问题及定位功能 1.九块九加群微信裂变人脉吃瓜宝妈同城相亲交友取图表情包 2.支持创建各种付费群&#xff0c;表情&#xff0c;吃瓜&#xff0c;创业&#xff0c;资源等等 3.支付对接第三方易支付&#xff0c;随便一家…

探索 WebRTC:数字世界的实时通信魔法

前言 在当今日常生活中&#xff0c;我们期望能够随时随地与朋友、同事或家人进行实时沟通。WebRTC&#xff08;Web实时通信&#xff09;技术就像一种魔法&#xff0c;让这些交流变得无比便捷&#xff0c;而且完全在浏览器中实现&#xff0c;无需下载任何额外应用或插件。 Web…

《Vue2.X 进阶知识点》- 防 ElementUI Divider 分割线

前言 使用 el-divider 背景为白色是没问题的。 但当背景换成其它颜色&#xff0c;问题就出现了&#xff01;&#xff01; 仔细看原来是两层&#xff0c;默认背景色是白色。 想着把背景色改为透明应该能用&#xff0c;结果发现背面是一条实线&#xff0c;难怪要用白色遮挡…不符…