【进阶】C语言第三课:升级你的指针(2)

news2024/11/26 14:58:47

目录

🍇前言🍇:

一、数组参数🤠:

        1.一维数组传参🍈:

        2.二维数组传参🍉:

二、指针参数🤩:

        1.一级指针传参🍊:

         2.二级指针传参🍒:

三、函数指针🤯:

        1.函数指针🥝:

        2.函数指针数组🍍:

四、总结🥳:


🛰️博客主页:✈️努力学习的銮同学

🛰️欢迎关注:👍点赞🙌收藏✍️留言

🛰️系列专栏:💐【进阶】C语言学习

        家人们更新不易,你们的👍点赞👍和👉关注👈真的对我真重要,各位路过的友友麻烦多多点赞关注,欢迎你们的私信提问,感谢你们的转发!

        关注我,关注我,关注我,你们将会看到更多的优质内容!!!


🏡🏡本文重点 🏡🏡

🚅  数组参数  🚃  指针参数  🚃  函数指针🚏🚏

🍇前言🍇:

        有了前面的基础,在这节课中我们继续学习指针的进阶部分知识,继续向更高阶升级我们的指针,希望能对大家的学习有所帮助!

一、数组参数🤠:

        之前我们在编写【井字棋】与【扫雷游戏】时,我们发现常常会需要将【数组】或【指针】作为参数传递给函数,于是我们在复杂情况下还需要考虑函数参数的设计

        1.一维数组传参🍈:

        我们都知道,在对数组进行传参时并不会真实的在内存中创建临时数组,数组名的意义是作为数组内首元素的地址,因此当函数参数为数组名时,实际上传递的是数组中首元素的地址,于是我们可以发现下面三种传参方式都是可行的:

//方式1:标准传参方式
//完整的传递数组内容
void test1(int arr[3])
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//方式2:省略数组大小
//形参部分的数组大小可以省略
void test2(int arr[])
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//方式3:扩大形参数组大小
//实际仍为传递首元素地址,故可行,但不建议
void test3(int arr[100])
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main()
{
	int arr[3] = { 1,2,3 };

    //调用函数:
	test1(arr);
	test2(arr);
	test3(arr);

	return 0;
}

        这三种将形参写作数组形式的方式都是可行的,但是为了避免出现错误,推荐大家尽可能的使用第一种方式,其次是第二种方式。第三种方式虽然也可以运行,但有可能会出现难以预料的错误,不建议使用

        同时,以上三种将形式参数写成数组形式的写法,也可以改写为使用指针做形式参数的形式

//使用指针作为形式参数:
//一级指针:
void test1(int* p)
{
	int i;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
}

//二级指针_形式1:
void test2(int** p)
{
	int i;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
}

//二级指针_形式2:
void test3(int** p)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", **(p + i));
	}
}

int main()
{
	int arr[3] = { 1,2,3 };
    int* parr = &arr;

    int arrA[3] = { 1,2,3 };
	int arrB[3] = { 4,5,6 };
	int arrC[3] = { 7,8,9 };
	int* arrD[3] = { &arrA,&arrA,&arrC };

    //调用函数:
	test1(arr);
    test2(parr);
	test3(arrD);

	return 0;
}

        并且同理,使用指针作为形式参数的另外两种形式也是可以(但最后一种方式同样不建议)的:

void test(int* p[3])
{
    ...
}

void test(int* p[100])
//可以但不建议使用
{
    ...
}

        2.二维数组传参🍉:

        我们在前面初阶的部分学习二维数组传参时就知道了,二维数组在进行传参时可以不知道有多好行,但必须知道有多少列,这样计算机才知道应该在何时进行换行。如此只要知道了什么时候进行换行,对于行数就不需要再进行强制要求了,所以在数组进行传参时,允许写成以下三种形式:

//二维数组传参:
//方式1:标准传参
void test1(int arr[3][3])
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("arr[%d][%d] = %d ", i, j, arr[i][j]);
		}
		printf("\n");
	}
}

//方式2:行数可以省略
void test2(int arr[][3])
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("arr[%d][%d] = %d ", i, j, arr[i][j]);
		}
		printf("\n");
	}
}

//方式3:行数可以超出原数组上限
void test3(int arr[100][3])
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("arr[%d][%d] = %d ", i, j, arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };

	//函数调用:
	test1(arr);
	test2(arr);
	test3(arr);
	return 0;
}

        同样的,二维数组除了可以使用数组作为函数的参数以外,也可以使用指针作为函数的参数进行传参,区别于一维数组,二维数组在传参时传递的不是首元素的地址而是首行元素的地址

void test(int(*p)[3])
//此处的数组大小3为列数
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("%d ", (*p + i * 3)[j]);
            //这里注意p+(i*3)是因为i为行号,跳过行时需要跳过i*3个数据元素
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };

	test(arr);

	return 0;
}

        但是要格外注意,二维数组与一维数组不同,不可以使用二级指针进行传参。其原理是,二级指针的作用是用于存储一级指针的的地址,而传递过来的参数是二维数组第一行(这里可以简单理解为一个一维数组,但本质上不是)的地址,无法使用二级指针进行存储。

二、指针参数🤩:

        1.一级指针传参🍊:

        当我们在函数调用,并使用一级指针作为参数时,很容易理解:一级指针 p 中存放的是数组 arr 中首元素的地址,即传址做参,于是我们就可以在函数参数设计时,使用一级指针进行接收,就可以达到我们的目的。

void test(int* p)
//传递的是一级指针,存储的是arr首元素的地址,使用一级指针进行接收
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *p + i);
	}
}

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;
	//数组名为首元素地址

	test(p);
    //等价于:test(arr);

	return 0;
}

         2.二级指针传参🍒:

        首先最基础的用法很好理解,无非是传递二级指针,就使用二级指针进行接收,无需过多阐述。我们直接上示例即可:

void test(int** p)
//传递二级指针,使用二级指针进行接收
{
	printf("%c", **p);
}

int main()
{
	char a = 'w';
	char* pa = &a;
	char** ppa = &pa;

	test(ppa);

	return 0;
}

        作为进阶部分的知识,我们就要去研究更深层的东西。我们想一想,当函数参数为二级指针时,都可以接收什么参数?

        我们都知道,二级指针是用来存储一级指针的地址的,那么除了定义二级指针存储一级指针地址,我们在前面还学过一个知识点也是用来存储地址的——指针数组。那么指针数组可以做为函数参数进行传递吗?答案是,可以:

//指针数组做函数参数:
void test(int** p)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%c ", **p + i);
	}
}

int main()
{
	char a = 'a';
	char b = 'b';
	char c = 'c';
	char* arr[3] = { &a,&b,&c };

	test(arr);

	return 0;
}

三、函数指针🤯:

        1.函数指针🥝:

        我们都知道,在我们的程序中,各种值和组成成分都有自己的一片空间,我们的自定函数也不例外:

void test()
{
	printf("hehe\n");
}

int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

        我们看到哪怕是我们的自定函数,也有着自己的储存空间

        那么当我们想要将函数的地址储存起来时,又该如何进行处理呢?函数指针给出了答案。其定义格式为:

🦑🦑函数返回类型( * + 函数指针名 )(函数参数类型)= 函数名;🦑🦑

        例如:

int Add(int x, int y)
{
	int z = x + y;
	return z;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int ret = Add(a, b);
	printf("a + b = %d\n", ret);
	printf("\n");

	int(*p)(int, int) = Add;
    //Add函数的返回类型为int类型,函数指针名为p,两个参数类型分别为int类型、int类型
	printf("%p\n", p);

	return 0;
}

        我们很清楚的看到,通过使用函数指针就可以将函数的地址存储起来

        并且我们可以通过使用函数指针优化我们的代码,大幅度提升我们代码的可读性

🍱我们先来看一段代码:

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

🍝很明显这段代码的可读性非常差,理解起来非常麻烦,于是我们可以通过使用函数指针来提升我们代码的可读性:

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

        2.函数指针数组🍍:

        对于函数指针,同样可以使用函数指针数组存储多个函数指针

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 main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);

	int ret1 = Add(a, b);
	int ret2 = Sub(a, b);
	int ret3 = Mul(a, b);
	printf("ADD = %d SUB = %d Mui = %d\n", ret1, ret2, ret3);

    //使用函数指针从存放函数地址:
	int(*padd)(int, int) = Add;
	int(*psub)(int, int) = Sub;
	int(*pmul)(int, int) = Mul;

	//函数指针数组:
	int(*p[3])(int, int) = { padd,psub,pmul };

	//通过函数指针数组打印函数地址:
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("指针p[%d]中存放的地址为:%p\n", i, p[i]);
	}

    //通过函数指针数组调用函数:
	for (i = 0; i < 3; i++)
	{
		int RET = p[i](a, b);
		printf("%d ", RET);
	}
	printf("\n");

    return 0;
}

四、总结🥳:

        今天我们学习了数组参数、指针参数以及函数指针的相关知识⛵⛵,希望我的文章和讲解能对大家的学习提供一些帮助🌞🌞。各位小伙伴们下去以后可不要疏于练习喔!!!

        🔥🔥通向梦想的路上的确有一道高墙,但它只阻挡不够热爱的人!!!🔥🔥

        辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~  最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

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

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

相关文章

【论文写作】课程总结

文章目录1、前言2、概述3、摘要与关键字4、引言5、相关工作6、理论7、实验8、总结1、前言 《论文写作》不仅是本人认为的在本学期收获较大的一门&#xff0c;也是最重要的课程之一。因为作为研究生&#xff0c;论文是必不可少的一部分。论文是就自己研究方向中所得到的成果的一…

网络设备的运行隐患怎么排除?日常的例行维护绝对不能少,收藏本文,轻松拿捏各种场景!

设备稳定运行一方面依赖于完备的网络规划&#xff0c;另一方面&#xff0c;也需要通过日常的维护发现并消除设备的运行隐患。 日常维护怎么才能进行呢&#xff1f;有哪些必要的步骤呢&#xff1f; 记住这五步&#xff1a; 1、设备环境检查 设备运行环境正常是保证设备正常运…

PreScan快速入门到精通第四十二讲点云传感器

点云传感器(PCS)是一种理想化的传感器,用于构建高数据率和高更新率的点云数据。该传感器的实际应用包括检测算法的开发、激光雷达系统的设计和验证或HIL验证。同时具备竖直方向的FOV相关信息,支持4D成像雷达系统的仿真开发。 该传感器具有固定但可配置的模式,并针对性能(…

分享一些冷门但却很实用的css样式

在平常的代码工作中&#xff0c;有很多冷门不常用的css样式标签。有些偏门、冷门的标签一般都记不住&#xff0c;想起来的时候就又会去现找&#xff0c;很影响工作效率&#xff0c;现在&#xff0c;把这些标签都统一整理一下用的少但是超级实用的css样式。 ::-Webkit-Input-Pla…

0基础转行学软件测试,哪些技术是必须要掌握的?

作为近些年非常热门的IT岗位&#xff0c;软件测试-受到越来越多应届毕业生和诸多转行群体的青睐。为了满足同学们对软件测试的学习要求&#xff0c;测试猿课堂将在本文为大家详细讲述成为自动化软件测试工程师必须要具备的能力体系。 软件测试的学习体系总的来讲可以分为五个阶…

Redis框架(十一):大众点评项目 乐观锁解决超卖问题 悲观锁解决一人一单问题

大众点评项目 基于Session的短信登录需求&#xff1a;乐观锁解决超卖问题 悲观锁解决一人一单问题业务代码总结SpringCloud章节复习已经过去&#xff0c;新的章节Redis开始了&#xff0c;这个章节中将会回顾Redis实战项目 大众点评 主要依照以下几个原则 基础实战的Demo和Codi…

数字IC后端设计如何快速入门?(内附学习视频)

虽然2022年IC行业门槛有所提高&#xff0c;但这也抵挡不住同学们对转行IC行业的热情&#xff0c;数字后端设计的发展前景和高薪也在众多岗位中脱颖而出&#xff0c;那么数字IC后端设计如何快速入门&#xff1f;下面IC修真院就带大家来了解一下。 数字后端工程师是做什么的&…

Docker:自定义镜像上传阿里云

目录 一.jdkv.1.0的制作 启动虚拟机&#xff0c;进入centos 创建文件夹上传jdk的安装包,和在同级目录下编写Dockerfile文件 执行Dockerfile文件&#xff0c;初次依赖镜像的时候会下载相应镜像​​​​​​​ 二.jdk2.0的制作 三.jdk3.0的制作 四.将制作好的镜像上传阿里云…

一文解读机密容器的崛起和发展

在 2022 云栖大会龙蜥峰会云原生专场上&#xff0c;来自阿里云操作系统技术专家冯世舫和Intel 系统软件工程部高级研发经理朱江云分享了《机密容器的崛起和发展》技术演讲&#xff0c;以下为本次演讲内容&#xff1a; 机密容器是 CNCF 的 一个 Sandbox 项目&#xff0c;用于解…

第一章 linux的发展

第一章 linux的发展一、操作系统的出现二、linux的出现三、linux的发展一、操作系统的出现 大部分先进产品的出现必定是为了军事服务的&#xff0c;起初的大型计算机也同样是为了军事服务的&#xff0c;而操作计算机的人也不是程序员&#xff0c;而是科学家。二战时期&#xf…

DVWA靶场中SQL注入

DVWA靶场中SQL注入1.DVWA靶场中SQL注入1.1.SQL Injection1.1.1.Low级别1.1.2.Medium级别1.1.3.High级别1.2.SQL Injection(Blind)1.2.1.方式1.2.2.Low级别1.2.3.Medium级别1.2.4.High级别1.DVWA靶场中SQL注入 1.1.SQL Injection 1.1.1.Low级别 1&#xff09;判断注入类型当输…

高中数理化杂志高中数理化杂志社高中数理化编辑部2022年第21期目录

高考全关注《高中数理化》投稿&#xff1a;cn7kantougao163.com 直线与圆的方程高考热点赏析 廖永福; 1-4 一道课本例题到一道高考试题的衍变之路 高磊; 4-8 圆的多种定义形式在解题中的应用 李光彬;邵建凤; 9-10 从2021年全国新高考Ⅰ卷第21题说起 王菊;张琥;…

碳交易机制下考虑需求响应的综合能源系统优化运行(Matlab代码实现)

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

接口测试(五)—— PyMySQL增删改查、数据库工具类封装

目录 数据库操作应用场景 一、PyMySQL操作数据库 1、安装PyMySQL 2、PyMySQL操作步骤 3、事务的概念 4、PyMySQL连接数据库 4.1 建立连接方法 4.2 入门案例 5、PyMySQL操作数据库 5.1 SQL 语法 5.2 数据库查询 5.3 案例&#xff08;查询&#xff09; 5.4 数据库UI…

代码随想录训练营第七天

专题&#xff1a;哈希表 题目&#xff1a;四数相加 题目简单&#xff1a;把四个数组分成两队&#xff0c;然后用map&#xff0c;保存前两个数组的元素之和&#xff0c;&#xff08;key,val&#xff09;key保存的是前两个数组的元素之和的数值&#xff0c;val保存的是数值对应…

PDF设置密码保护的两种方法

PDF文件可以根据需要&#xff0c;设置两种密码来保护文件。 需要保护文件内容&#xff0c;不想PDF被随意打开&#xff0c;我们可以设置打开密码&#xff0c;这样只有输入正确的密码才能打开文件。 在编辑器中打开PDF后&#xff0c;找到菜单中【保护】选项下的【密码加密】&am…

SpringCloud01

1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 1.0.学习目标 了解微服务架构的优缺点 1.1.单体架构 单体架构&#xff1a;将业务的所有功能集…

33-98-spark-核心编程-RDD算子和任务阶段等

33-spark-核心编程-RDD&#xff1a; 1、RDD的创建&#xff0c;4中方式。分别是从内存中创建&#xff0c;从文件中创建&#xff0c;从其他RDD创建和new RDD&#xff0c;后两者不常用。 创建&#xff1a;big-data-study\Spark-demo\src\main\java\spark\core\com\zh\rdd\builde…

PTA-基础编程题目集(函数题)

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【[PTA刷题训练营]】 目录6-1 简单输出整数6-2 多项式求值&#xff08;重点掌握&#xff09;6-3 简单求和6-4 求自定类型元素的平均6-5 求…

医院陪诊小程序怎么开发-医院陪诊小程序源码功能

目前医院陪诊新型行业已经占据了很大的市场所在&#xff0c;我们去医院看病找医生挂号帮忙取药有时候去外地人生地不熟的 自己转半天摸索不过来浪费时间 而且有时候一个人需要陪同比较放心&#xff0c;所以呢衍生出来了 热门的陪诊师 如何开发&#xff1f; 开发无非就是几种 …