C语言的灵魂——指针(3)

news2025/2/9 14:07:57

前言:上期我们介绍了const修饰指针,saaert断言都是针对指针本身的,文章后面我们用指针与数组建立了联系,这种联系或者是关系就是这篇文章所要介绍的。上一篇文章的传送门:指针2

指针3

  • 一,数组名的含义及理解
  • 二,使用指针访问数组
  • 三,一维数组传参的本质
  • 四,二级指针
  • 五,指针数组
  • 六,指针数组模拟二维数组

一,数组名的含义及理解

谈到数组想必大家都知道一个点就是:数组名就是数组的首地址,也是首元素的地址。这就是数组名的含义我=我们不妨写一个代码来验证一下:

#include<stdio.h>
int main()
{
	int arr[10]={1,2,3,4,5,6,7,8,9,10};
	printf("%p\n",arr);//%p打印地址的格式占位符
	printf("%p\n",&arr[0]);//打印首元素的地址
	return 0;
}

在这里插入图片描述
通过运行结果我们能很明显的看到数组名就是数组首元素的地址。 既然已经知道它的含义了那我们为什么还要重点来讲它呢?如果没有例外那就不会在这里讲它了,下面我们来看代码:

#include<stdio.h>
int main()
{
	int arr[10]={1,2,3,4,5,6,7,8,9,10};
	printf("%zd\n",sizeof(arr));
	return 0;
}

这段代码的运行结果会是什么呢?如果我们认为arr是首元素的地址,而数组元素的类型是int类型那么打印数来的大小应该就是4个字节或8个字节才对(4个字节还是8个字节由平台大小决定)。下面我们来看结果:
在这里插入图片描述
结果明显与我们所想的不符,那为什么会出现这种情况呢?其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有两个例外:

1.sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小, 单位是字节。

2.&数组名,这⾥的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的。

除此之外,任何地方使用数组名,数组名都表示首元素的地址.


那我们再来看一段代码:

#include <stdio.h> 
int main() 
{ 
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 
	printf("&arr[0] = %p\n", &arr[0]); 
	printf("arr = %p\n", arr); 
	printf("&arr = %p\n", &arr); 
	return 0; 
}

在这里插入图片描述
看到这里有些人可能又会懵了,前面不是说 **&数组名** 取出的是整个数组的地址吗?那为什么这里 &arrarr &arr[0] 打印出来的地址是一样的呢?难道整个数组的地址与首地址是一样的吗?
这里就要引用我在指针(1)指针变量和地址中介绍&这个操作符所说的了:

但有一点需要特别注意取地址取出来的是较小的地址,因为我们知道数据类型占几个字节又知道较小的地址剩下的顺藤摸瓜直接往后推算即可。

在这里插入图片描述
要想探究 &arr 我们只需要加上偏移量就可以很明显的看出来了:

#include <stdio.h> 
int main() 
{ 
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 
	printf("&arr[0]   = %p\n", &arr[0]); 
	printf("&arr[0]+1 = %p\n", &arr[0]+1); 
	printf("arr       = %p\n", arr); 
	printf("arr+1     = %p\n", arr+1); 
	printf("\n");
	printf("&arr      = %p\n", &arr); 
	printf("&arr+1    = %p\n", &arr+1); 
	return 0; 
}

在这里插入图片描述
这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是 ⾸元素的地址,+1就是跳过⼀个元素。

但我们看到&arr——>&arr+116进制从A8变成了D0,我们用D0减去A8看看是不是40个字节就能验证&arr是不是整个数组的地址了。
在这里插入图片描述
我们发现&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。
看到这里大家应该搞清楚数组名的意义了吧。

二,使用指针访问数组

弄个清楚了数组名,这就让我们有了使用指针来访问数组的基础。我们来看一个常规访问数组的代码:

#include<stdio.h>
int main()
{
	int arr[10]={0};
	int i=0;
	int sz=sizeof(arr)/sizeof(arr[0]);
	for(i=0;i<sz;i++)
	{
		scanf("%d",&arr[i]);
	}
	int j=0;
	for(j=0;j<sz;j++)
	{
		printf("%d\n",arr[j]);
	}
	return 0;
}

在这里插入图片描述
这是我们最原始的访问数组的方法,不知大家有没有注意到一个点,其实我们以前求数组元素长度用的就是 sizeof(arr)/sizeof(arr[0]) 只是当时我们不知道。这也说明了上面说所的点sizeof(数组名)求的是整个数组的大小。

言归正传,既然数组名就是地址,而地址就是指针那么能不能使用一个指针变量指向数组,然后使用指针来访问数组呢?当然可以:

#include<stdio.h>
int main()
{
	int arr[10] = {0};
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0;i < sz;i++)
	{
		scanf("%d", p+i);
		//替换scanf("%d",&arr[i]);
	}
	int j = 0;
	for (j = 0;j < sz;j++)
	{
		printf("%d ", *(p + j));
		//替换printf("%d\n",arr[j]);
	}	
	return 0;
}

这个代码搞明⽩后,我们再试⼀下,如果我们再分析⼀下,数组名arr是数组⾸元素的地址,可以赋值 给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可 以访问数组呢

#include<stdio.h>
void test(int arr[], int sz1)//参数本质上就是int *arr 
{                    //要想求数组元素个数需要传sz1过来
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz2);
	printf("%d\n", sz1);
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz1);
	test(arr,sz1);

	
	return 0;
}

这样也是可行的,将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i] 是等价于 *(p+i),实际上我们写出数组下标的形式来访问数组元素的时候,编译器在处理的时候还是转换成指针来处理。

那我们想象一下 *(i+arr) 是否能写成 i[arr] 呢?答案是可以的只不过代码的可读性不高,其实 i[arr] 也不难理解[ ]这个操作符是下标引用操作符,i和arr只是它的两个两个操作数而已并不影响实际的结果。

同理arr[i] 应该等价于 *(arr+i),数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移 量求出元素的地址,然后解引用来访问的。

接着我们来看看一维数组传参

三,一维数组传参的本质

前面在讲函数传参的时候我们也讲过数组传参,学完了指针后我们便可以来探索一下它的本质,先来看一段代码:

#include<stdiio.h>
//一维数组传参的时候形参是可以写成数组的形式,但是即使写成数组的形式本质其实是指针变量。
void test(int arr[],int sz1)//参数本质上就是int *arr 
{                    //要想求数组元素个数需要传sz1过来
	int sz2=sizeof(arr)/sizeof(arr[0]);
	printf("%d",sz2);
	printf("%d",sz1);
}
int main()
{
	int arr[10]={1,2,3,4,5,6,7,8,9,10};
	int sz1=sizeof(arr)/sizeof(arr[0]);
	printf("%d",sz1);
	test(arr,sz1);
	return 0;
}

在这里插入图片描述
通过运行结果我们有个疑惑为什么sz2的结果是2呢?首先我们能够确定的一点是 sizeof(arr[0]) 的大小就是4个字节(一个整型类型)这点是毫无疑问的,那么谁/4等于2呢?小学生都会,答案是8。那为什么是8呢?
注意sz2的值也可能是1这而取决于平台大小!

还记得最开始举的第二个例子吗?
如果我们认为arr是首元素的地址,地址就是指针;而数组元素的类型是int类型那么打印数来的大小应该就是4个字节或8个字节才对(4个字节还是8个字节由平台大小决定)。

所以我们得出一个非常重要的结论,数组传参本质上传过去的就数组元素的首地址(是一个指针)!

所以函数形参的部分理论上应该使用指针变量来接收⾸元素的地址。那么在函数内部我们写 sizeof(arr) 计算的是⼀个地址的大小(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函 数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。

四,二级指针

前面我们介绍了一级指针变量,指针变量就是用来存放普通变量的地址;那以此类推指针变量的地址当然就是用二级指针来存储。
二级指针与一级指针一样无非就是对指针变量本身的使用以及解引用:

#include<stdio.h>
int main()
{
//使用指针对象
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
//解引用
	*pa = 20;
	printf("*pa=%d\n", *pa);

	int m = 100;
	*ppa = &m;//pa=&m
	printf("%p\n", &m);
	printf("%p\n", pa);

	printf("%d\n", **ppa);//对Pa解//printf("*pa=%d\n,*pa");

	** ppa = 30;//直接对m进行操作
	printf("**ppa=%d\n",**ppa);
}

在这里插入图片描述

在这里插入图片描述

所以 *pa pa解引用就是改变a的值,改变 *ppa 对就是改变 *pa 的所存的地址,**ppa 对解引用就是改变a的值。就像有三个抽屉第一第二个抽屉都有锁,第三个抽屉放着第二个抽屉的钥匙,第二个抽屉放着第一个抽屉的钥匙。
在这里插入图片描述

五,指针数组

前面我们学过
字符数组——是数组,是用来存放字符的数组;char arr[5]
整型数组——是数组,是用来存放整型的数组;int arr[5]

以此类推:
指针数组——是数组,是用来存放指针的数组;int*arr[5] (存放整型指针的数组)

#include<stdio.h>
int main()
{
	int a=10;
	int b=20;
	int c=30;
	int d=40;
	int e=50;
	//与其一个个用指针变量来存放abcde的地址还不如直接用一个指针数组来存放
	int *arr[5]={&a,&b,&c,&d,&e};
	return 0;
}

指针数组的每个元素都是用来存放地址(指针)的.
在这里插入图片描述
而指针数组的每个元素是地址,⼜可以指向⼀块区域:
在这里插入图片描述

六,指针数组模拟二维数组

明白上面的基础知识后我们就可以用指针数组来模拟一下二维数组了:

#include <stdio.h> 
int main() 
{ 
	int arr1[] = {1,2,3,4,5}; 
	int arr2[] = {2,3,4,5,6}; 
	int arr3[] = {3,4,5,6,7}; 
	//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中 
	int* parr[3] = {arr1, arr2, arr3}; 
	int i = 0; 
	int j = 0; 
	for(i=0; i<3; i++) 
	{ 
		for(j=0; j<5; j++) 
		{ 
			printf("%d ", parr[i][j]); 
			//parr[i]其实就是代表arr1/arr2/arr3
			//parr[][j]其实就是输出每一个数组内的所有元素
		}
	printf("\n"); 
	}
	return 0;
}

在这里插入图片描述
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数 组中的元素。 上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每一行并非是连续的。

好了以上就是本章的全部内容啦!
感谢能够看到这里的读者,如果我的文章能够帮到你那我甚是荣幸,文章有任何问题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!

在这里插入图片描述

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

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

相关文章

SSD1306 128*32屏幕驱动

最近在做一个小项目&#xff0c;使用合宙air001做主控&#xff0c;arduino开发环境&#xff0c;项目设计一个小屏作为显示&#xff0c;本身使用u8g2库&#xff0c;奈何这个air001空间太小&#xff0c;没写多少就把程序储存空间占满了&#xff0c;log也没办法打印&#xff0c;对…

【GitHub】GitHub 2FA 双因素认证 ( 使用 Microsoft Authenticator 应用进行二次验证 )

文章目录 一、GitHub 的 2FA 双因素认证二、使用 Microsoft Authenticator 应用进行二次验证1、TOTP 应用2、下载 Microsoft Authenticator 应用3、安装使用 Authenticator 应用 三、恢复码重要性 一、GitHub 的 2FA 双因素认证 现在登录 GitHub 需要进行二次身份验证 ; 先登录…

AI绘画社区:解锁艺术共创的无限可能(9/10)

AI 绘画&#xff1a;不只是技术&#xff0c;更是社交新潮流 在科技飞速发展的今天&#xff0c;AI 绘画早已不再仅仅是一项孤立的技术&#xff0c;它正以惊人的速度融入我们的社交生活&#xff0c;成为艺术爱好者们交流互动的全新方式&#xff0c;构建起一个充满活力与创意的社…

一种基于Leaflet.Legend的图例动态更新方法

目录 前言 一、场景再现 1、需求描述 2、核心方法介绍 3、存在的问题 二、问题解决 1、重复解决办法 2、图例不展示解决办法 3、成果展示 三、总结 前言 在当今数字化时代&#xff0c;地理信息系统&#xff08;GIS&#xff09;技术已经广泛应用于各个领域&#xff0c;…

【韩顺平linux】部分上课笔记整理

整理一下一些韩顺平老师上课时候的笔记 课程&#xff1a;【小白入门 通俗易懂】韩顺平 一周学会Linux linux环境&#xff1a;使用阿里云服务器 笔记参考 &#xff1a; [学习笔记]2021韩顺平一周学会Linux 一、自定义函数 基本语法 应用实例&#xff1a; 计算两个参数的和…

CPP集群聊天服务器开发实践(一):用户注册与登录

目录 1 客户端用户注册与登录 1.1 主要思想 1.2 网络层 1.3 业务层 1.4 数据层 1.5 测试结果 1 客户端用户注册与登录 1.1 主要思想 实现网络层、业务层、数据层的解耦&#xff0c;提高系统的可维护性。 网络层&#xff1a;主要实现对客户端连接、客户端读写请求的捕获…

学JDBC 第二日

数据库连接池 作用 使数据库连接达到重用的效果&#xff0c;较少的消耗资源 原理 在创建连接池对象时&#xff0c;创建好指定个数的连接对象 之后直接获取连接对象使用即可&#xff0c;不用每次都创建连接对象 从数据库连接池中获取的对象的close方法真的关闭连接对象了吗…

【Uniapp-Vue3】z-paging插件组件实现触底和下拉加载数据

一、下载z-paing插件 注意下载下载量最多的这个 进入Hbuilder以后点击“确定” 插件的官方文档地址&#xff1a; https://z-paging.zxlee.cn 二、z-paging插件的使用 在文档中向下滑动&#xff0c;会有使用方法。 使用z-paging标签将所有的内容包起来 配置标签中的属性 在s…

【C语言标准库函数】三角函数

目录 一、头文件 二、函数简介 2.1. 正弦函数&#xff1a;sin(double angle) 2.2. 余弦函数&#xff1a;cos(double angle) 2.3. 正切函数&#xff1a;tan(double angle) 2.4. 反正弦函数&#xff1a;asin(double value) 2.5. 反余弦函数&#xff1a;acos(double value)…

Redisson全面解析:从使用方法到工作原理的深度探索

文章目录 写在文章开头详解Redisson基本数据类型基础配置字符串操作列表操作映射集阻塞队列延迟队列更多关于Redisson详解Redisson 中的原子类详解redisson中的发布订阅模型小结参考写在文章开头 Redisson是基于原生redis操作指令上进一步的封装,屏蔽了redis数据结构的实现细…

声明式导航,编程式导航,导航传参,下拉刷新

1.页面导航 1.声明式导航 1.1跳转到tabBar页面 1.2跳转到非tabBar页面 1.2后退导航 、 2.编程式导航 2.1跳转到tabBar页面 2.1跳转到非tabBar页面 2.3后退导航 3.导航传参 3.1声名式导航传参 3.2编程式导航传参 3.3在onLoad中接受参数 4.下拉刷新 4.1回顾下拉刷新…

金和OA C6 DownLoadBgImage任意文件读取漏洞

金和OA C6 DownLoadBgImage任意文件读取漏洞 漏洞描述 金和C6数据库是一款针对企业信息化管理而设计的高级数据库管理系统&#xff0c;主要应用于企业资源规划&#xff08;ERP&#xff09;、客户关系管理&#xff08;CRM&#xff09;以及办公自动化&#xff08;OA&#xff09…

激活函数篇 03 —— ReLU、LeakyReLU、ELU

本篇文章收录于专栏【机器学习】 以下是激活函数系列的相关的所有内容: 一文搞懂激活函数在神经网络中的关键作用 逻辑回归&#xff1a;Sigmoid函数在分类问题中的应用 整流线性单位函数&#xff08;Rectified Linear Unit, ReLU&#xff09;&#xff0c;又称修正线性单元&a…

Kafka 入门与实战

一、Kafka 基础 1.1 创建topic kafka-topics.bat --bootstrap-server localhost:9092 --topic test --create 1.2 查看消费者偏移量位置 kafka-consumer-groups.bat --bootstrap-server localhost:9092 --describe --group test 1.3 消息的生产与发送 #生产者 kafka-cons…

5 计算机网络

5 计算机网络 5.1 OSI/RM七层模型 5.2 TCP/IP协议簇 5.2.1:常见协议基础 一、 TCP是可靠的&#xff0c;效率低的&#xff1b; 1.HTTP协议端口默认80&#xff0c;HTTPSSL之后成为HTTPS协议默认端口443。 2.对于0~1023一般是默认的公共端口不需要注册&#xff0c;1024以后的则需…

VMware虚拟机安装、创建Ubuntu虚拟机及汉化设置全流程详细教程

一、安装VMware Workstation 下载VMware 访问官网&#xff1a;https://www.vmware.com 选择适合的版本&#xff08;如 Workstation Pro 或 VMware Player&#xff0c;后者免费&#xff09;。完成下载后运行安装程序。 网盘下载&#xff1a; 链接: https://pan.baidu.com/s/1MQ…

21.2.7 综合示例

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 【例 21.7】【项目&#xff1a;code21-007】填充职员表并打印。 本例使用到的Excel文件为&#xff1a;职员信息登记表.xlsx&#x…

【大模型】DeepSeek与chatGPT的区别以及自身的优势

目录 一、前言二、核心技术对比2.1 模型架构设计2.1.1 ChatGPT的Transformer架构2.1.2 DeepSeek的混合架构 2.2 训练数据体系2.2.1 ChatGPT的数据特征2.2.2 DeepSeek的数据策略 三、应用场景对比3.1 通用场景表现3.1.1 ChatGPT的强项领域3.2.2 DeepSeek的专项突破 3.3 响应效率…

burpsuite抓取html登陆和上传数据包

一、burpsuite抓取html登陆数据包 1、先写一个html格式的登陆页面 <!doctype html> <html lang"en"> <head><meta charset"UTF-8"><title>这是标签</title></head> <body> <hr><!-- 登陆表单 …

Linux 安装 Ollama

1、下载地址 Download Ollama on Linux 2、有网络直接执行 curl -fsSL https://ollama.com/install.sh | sh 命令 3、下载慢的解决方法 1、curl -fsSL https://ollama.com/install.sh -o ollama_install.sh 2、sed -i s|https://ollama.com/download/ollama-linux|https://…