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

news2025/2/5 14:58:34


目录

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

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

🌍结束语

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

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

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

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

相关文章

15个主流设计灵感网站,激发你的创作灵感!

即时设计 即时设计是一种强大的云设计工具&#xff0c;已成为许多设计师、产品经理和开发人员的首选工具之一。即时设计用户可以使用内置的工具和功能快速创建和编辑设计&#xff0c;或与其他用户共享和合作。此外&#xff0c;即时设计还有一个丰富的资源社区&#xff0c;为用…

制造业数字化转型的核心不止是技术

一、制造业的数字化转型意味着什么&#xff1f; 在当今的制造业领域&#xff0c;数字化转型意味着通过集成数字技术来增强传统的制造方法、产品和劳动力的过程。这些技术包括一系列创新&#xff0c;如自动化软件、电子商务系统、传感器、工业机器人等。 二、制造业数字化转型的…

ubuntu 安装apisix -亲测可用

官方未提供在ubuntu系统中安装apisix的方式&#xff0c;似乎只能通过源码方式安装&#xff0c;但是并不推荐&#xff0c;非常容易失败&#xff0c; 具体操作方式如下&#xff1a; ubuntu和Debian其实类似的&#xff0c;可使用DEB方式安装&#xff0c;如下截图 注意&#xff1…

22000mAh 电池,这款国产新机来了场「续航」震撼

见惯了主流智能手机&#xff0c;是时候上一波离谱新机震撼了。 三防手机这一细分类型&#xff0c;咱们普通用户可能接触得比较少&#xff1b; 但对于极限运动、野外探险爱好者来说&#xff0c;这玩意儿可是关键时候能救命的必备神器。 在真正严苛环境面前&#xff0c;性能啥的…

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

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

使用LLaMA-Factory微调ChatGLM3

1、创建虚拟环境 略 2、部署LLaMA-Factory &#xff08;1&#xff09;下载LLaMA-Factory https://github.com/hiyouga/LLaMA-Factory &#xff08;2&#xff09;安装依赖 pip3 install -r requirements.txt&#xff08;3&#xff09;启动LLaMA-Factory的web页面 CUDA_VI…

HarmonyOS4.0系统性深入开发05ArkTS卡片运行机制

ArkTS卡片运行机制 实现原理 图1 ArkTS卡片实现原理 卡片使用方&#xff1a;显示卡片内容的宿主应用&#xff0c;控制卡片在宿主中展示的位置&#xff0c;当前仅系统应用可以作为卡片使用方。卡片提供方&#xff1a;提供卡片显示内容的应用&#xff0c;控制卡片的显示内容、…

ElasticSearch 聚合统计

聚合统计 度量聚合&#xff1a;求字段的平均值&#xff0c;最小值&#xff0c;最大值&#xff0c;总和等 桶聚合&#xff1a;将文档分成不同的桶&#xff0c;桶的划分可以根据字段的值&#xff0c;范围&#xff0c;日期间隔 管道聚合&#xff1a;在桶聚合的结果上执行进一步计…

【MYSQL】MYSQL 的学习教程(七)之 慢 SQL 优化思路

1. 慢 SQL 优化思路 慢查询日志记录慢 SQLexplain 分析 SQL 的执行计划profile 分析执行耗时Optimizer Trace 分析详情确定问题并采用相应的措施 1. 慢查询日志记录慢 SQL 如何定位慢SQL呢&#xff1f; 我们可以通过 慢查询日志 来查看慢 SQL。 ①&#xff1a;开启慢查询日志…

围栏中心点

后端返回的数据格式是 [{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…

labuladong日常刷题-递归魔法 | LeetCode 206反转链表 92反转链表-ii

递归魔法 LeetCode 206 反转链表 2023.12.26 题目链接labuladong讲解[链接] ListNode* reverseList(ListNode* head) {//递归退出条件if(head NULL || head->next NULL)return head;//递归ListNode* last reverseList(head->next);//处理head->next->next …

腾讯云轻量服务器4核8G12M有三年优惠价表

腾讯云轻量服务器4核8G12M有三年优惠价吗&#xff1f;有&#xff0c;但是不怎么优势&#xff0c;相对于云轻量2核2G4M带宽三年价格是540元、2核4G5M带宽3年优惠价756元&#xff0c;4核8G12M轻量应用服务器三年价格是5292元&#xff0c;怎么样&#xff1f;还想买吗&#xff1f;阿…

9.独立看门狗IWDG窗口看门狗WWDG编码思路

前言&#xff1a; 看门狗是维护系统稳定性的一向技术&#xff0c;可以让代码跑飞及时复位&#xff0c;在产品中非常常用&#xff0c;俗话说&#xff0c;重启能解决90%的问题&#xff0c;作为产品来说&#xff0c;你总不能因为一次bug就让程序卡死不动了&#xff0c;肯定要试着重…

微信小程序云开发-下载云存储中的文件

一、前言 很多时候我们需要实现用户在客户端下载服务端的文件&#xff08;图片、视频、pdf等&#xff09;到用户本地并保存起来&#xff0c;小程序也经常需要实现这样的需求。 在传统服务器开发下网上已经有很多关于小程序下载服务端文件的资料了&#xff0c;但是基于云开发的…

【滑动窗口】LeetCode:30串联所有单词的子串

作者推荐 【二叉树】【单调双向队列】LeetCode239:滑动窗口最大值 相关知识点 滑动窗口 题目 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0…

GBASE南大通用-管理函数

包括创建函数&#xff0c;修改函数过程定义和删除函数功能。这些操作在 GBASE南大通用数据源节点展开后的 Stored Functions 节点上进行。 创建函数 在 Stored Functions 节点上点击右键选择‚创建函数‛命令或者执行 Visual Studio 的‚数据‛菜单的‚新增‛子菜单下的‚函…

大创项目推荐 深度学习YOLO图像视频足球和人体检测 - python opencv

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov5算法5 数据集6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习YOLO图像视频足球和人体检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非…

效果图渲染电脑渲染好?还是云渲染更好?

效果图的渲染是建筑和室内设计领域中不可或缺的一步&#xff0c;随着技术的发展&#xff0c;云渲染作为一项新技术&#xff0c;正逐渐受到人们关注。今天&#xff0c;让我们深入探讨电脑渲染和云渲染这两种方法的优缺点以及它们的适用场景。 本地电脑渲染 本地电脑渲染是利用用…

麒麟信安桌面操作系统顺利上线长沙职业技术学院,深度促进产教融合,赋能信创人才培养

随着信息基础设施国产化进程的加快&#xff0c;信息技术创新产业对人才的需求量激增&#xff0c;为解决信创人才培养难题、深度促进产教融合&#xff0c;近日&#xff0c;麒麟信安、湖南欧拉生态创新中心携手长沙职业技术学院共同组建的“麒麟信安&欧拉(openEuler)国产操作…

apisix 路由转发成功 但响应502异常(转发导致客户端来源发生变化)

访问报如下异常 这种情况通常是通过apisix转发后&#xff0c;导致丢失原有域名&#xff08;也可以理解为客户端来源变了&#xff09;导致最终程序端某些安全检查不通过 此时有两种解决方法 路由中修改 操作路径&#xff1a; 路由-域名改写 如下图 上游&#xff08;upstream…