指针的深入理解(3)(包括数组名的理解、一维数组传参的本质以及指针数组的相关知识及使用)


文章目录

  • 1 数组名的理解
  • 2 使用指针访问数组
  • 3 一维数组传参的本质
  • 4 指针数组
  • 5 指针数组的使用


1 数组名的理解

当我们运行以下代码:

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

在这里插入图片描述
会发现,arr[0]的地址和数组名arr的地址是一样的。

所以我们大致可以猜测一下:
数组名是数组首元素的地址

但是按照这样的理解,以下代码的结果应该是4才对

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%zd\n", sizeof(arr));
	return 0;
}

但是当我们运行就会发现,结果是40.
在这里插入图片描述
那说明之前的猜测有一些问题。

实际上的结论是:
数组名是数组首元素的地址,
但是有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,sizeof计算的是整个数组的大小,单位是字节。
2.&数组名,这里的数组名也表示整个数组,取出的是整个数组的地址

当我们运行代码,肯定会疑惑,根据结论的第二个例外,&数组名取出整个数组的地址,为什么得到的arr的地址和数组首元素的是一样的呢?

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", &arr);		//取出整个数组的地址
	
	printf("%p\n", &arr[0]);
	printf("%p\n", arr);
	return 0;
}

在这里插入图片描述
其实也不难理解,取出首元素的地址其实就相当于取出了整个元素的地址,如果我们得到了首元素的地址,就可以顺藤摸瓜得到剩下所有元素的地址。

我们可以通过以下的代码观察它们之间的区别;

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };

	printf("&arr   = %p\n", &arr);
	printf("&arr+1 = %p\n", &arr+1);
	printf("\n");

	printf("&arr[0]   = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0]+1);
	printf("\n");

	printf("arr   = %p\n", arr);
	printf("arr+1 = %p\n", arr+1);
	printf("\n");

	return 0;
}

在这里插入图片描述
可以发现,三种方式得到的地址都是一样的,但是由于arr和&arr[0]取出的都是首元素的地址,把它们地址加1,加的是一个整形元素,4个字节,但是如果是**&arr,加1后地址加了40个字节**(70到98,加了28,这是16进制表示,转换成十进制就是40),所以这个例子能很好的证明&arr其实取出的是整个数组的地址,但是由于我们可以通过首地址找到所以元素的地址,为了方便显示,我们观察到的地址还是首元素的地址。

2 使用指针访问数组

如果我们想要实现数组的输入和输出时,可以这样写代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* p = arr;
	//向数组输入数
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);	
//p最开始指向数组首元素的地址,每次输入完后要执行后面的地址进行下一次的输入
	}
	
	//输出数组的内容
	for (i = 0; i < sz; i++)
	{
		printf("%d", *(p + i));
	}
	return 0;
}

在这里插入图片描述
程序能够很好的运行,但是如果我们稍作修改呢?
假如我们把scanf函数中的p+i修改成p++,结果又会是什么呢?

代码如下:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* p = arr;
	//向数组输入数
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p++);
	}
	
	//输出数组的内容
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

在这里插入图片描述

运行后我们会发现,程序出现了问题,输出随机值,这是为什么呢?

仔细想想,最开始i=0的时候,指针指向数组首元素的地址,之后每输入一个值指针指向后一个元素的地址,但是当i=9,输入完第10个数后,p++,此时p++指向的是数组最后一个元素的后面的地址,当我们用printf输出数组时,我们自以为指针最开始还是指向数组首元素的地址,但是实际上指针指向的位置已经没有元素了,所以导致没有输出结果,如果我们想输出,就需要在输出数组之前把指针重新指向数组的首元素地址。

我们也可以通过调试来观察问题:
在调试时我们可以设置条件断点,避免无意义的重复输入,从i=8开始执行,按fn+f5开始调试后直接输入10个数组元素,然后打开监视,之后按fn+f10一直往下执行,直到执行到输入最后一个数,也就是i=9的时候,观察p所指向的地址
在这里插入图片描述
在这里插入图片描述
可以发现,当i等于9,并且输入完第10个数之后,p指向了arr[9]的后面一个元素(arr[10])的地址,但是我们创建的数组为arr[0]~arr[9],p最后指向越界了,所以在输出的时候会出现随机值。

我们可以对代码稍微进行一下修改,在打印数组内容之前使p重新指向数组的第一个元素地址,就加一句p=arr;
修改后结果如图:
修改代码如下:

3 一维数组传参的本质

当我们运行下面代码:分别在主函数和函数中求数组的大小

#include <stdio.h>

void test_len(int* arr)
{
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	return 0;
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	test_len(arr);
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
	return 0;
}

在这里插入图片描述
可以发现在函数中,数组的大小竟然是1,这是为什么呢?

我们可以尝试求出函数中arr和arr[0]的大小分别是多少
在这里插入图片描述
可以发现,传递的数组arr大小竟然也是4,所以之前求sz1的大小得到的是4/4=1.

这是因为数组传参的本质传递的是数组首元素的地址。

当我们想得到数组的大小并在函数中使用时,最好先在主函数中先计算结果,再通过参数的形式传递给函数。
在这里插入图片描述

4 指针数组

我们可以进行类比:
整形数组:存放整形的数组,例如int arr[5] = { 1, 2, 3, 4, 5}; //数组里的每一个元素都是int类型
字符数组:存放字符的数组,例如char ch[5] = { ‘a’, ‘b’, ‘c’, ‘d’, ‘e’}; //数组每个元素都是char类型
因此我们可以推断:
指针数组:存放指针的数组,数组里的每一个元素都是存放指针(地址)的
例如假设我们已知a,b,c,d,e里面存放的都是整数。
指针数组里面的元素依次存放a,b,c,d,e的地址,我们可以这样表示:int * arr[5] = { &a, &b, &c, &d, &e};
如图所示:
在这里插入图片描述

5 指针数组的使用

我们可以使用指针来模拟二维数组。
代码如下:

#include <stdio.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* ptr[3] = { arr1,arr2,arr3 };
	int i;
	for (i = 0; i < 3; i++)
	{
		int j;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", ptr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

首先我们创建指针数组ptr,用来存放三个数组的数组名(本质存放的是数组首元素的地址),再通过双重循环找到对应的值,外层循环用来实现我们找的是哪一个数组的首元素的地址,内存循环我们可以顺着数组首元素的地址找到数组后面的所以元素并打印出来。

指向关系如图:
在这里插入图片描述

在这里插入图片描述
可以发现,三个一维数组通过指针数组的方法,模拟出了二维数组的样子,但是不等同二维数组,因为我们知道二维数组的n行在内存中是连续存放的,但是我们创建的3个一维数组并不一定在同一块空间内,我们可以得到三个数组首元素的地址,通过地址分别找到三块空间再顺藤摸瓜找到数组所有元素并打印出来。

我们知道ptr[i]在运行时会被编译器转换成指针的形式*(ptr+i),ptr[i][j]也类似,我们可以把ptr[i]当做ptr,把[j]当做[i],替换之前的指针形式就能得到ptr[i][j] = * (ptr[i]+j) = *( *(ptr+i)+j) ,所以我们在打印的时候也可以使用指针的形式,也能得到正确结果.
代码如下:

#include <stdio.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* ptr[3] = { arr1,arr2,arr3 };
	int i;
	for (i = 0; i < 3; i++)
	{
		int j;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(ptr+i)+j));
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述

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

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

相关文章

矩阵中严格递增的单元格数

class Solution { public:int maxIncreasingCells(vector<vector<int>>& mat) {int m mat.size(), n mat[0].size();// 开辟用来记录每个值对应的位置&#xff08;i,j&#xff09;map<int, vector<pair<int, int>>> mp;vector<int> …

收银系统源码-千呼新零售2.0【线下促销】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货等连锁店使用。 详细介绍请查看下…

文件重命名 一键批量重命名10万+文件 简单效率高!

单个文件重命名大家应该都会操作&#xff0c;但是有一些人由于工作的场景等的情况&#xff0c;需要做大量的文件重命名&#xff0c;比如影楼、电商、仓库管理、图片处理等各种行业&#xff0c;都经常需要把一批文件&#xff0c;按一定的格式和规律给文件重命名。 一、批量文件重…

创建Docker容器与外部机通信(独立IP的方式)

需求&#xff1a;希望外部可以直接通过不同IP地址访问宿主机上的Docker容器&#xff0c;而不需要端口映射&#xff08;同一个IP不同的端口与外部通讯&#xff09;&#xff0c;这通常涉及到在宿主机的网络层面进行更高级的配置&#xff0c;比如使用IP伪装&#xff08;IP masquer…

HTML李峋同款跳动的爱心代码(双爱心版)

目录 写在前面 跳动的爱心 完整代码 代码分析 系列推荐 最后想说 写在前面 在浩瀚的网络世界中&#xff0c;总有一些小惊喜能触动我们的心弦。今天&#xff0c;就让我们用HTML语言&#xff0c;探索既神秘又浪漫的李峋同款跳动的爱心代码吧。 首先&#xff0c;让我们一起…

Linux系统编程——进程信号

目录 一&#xff0c;信号预备 1.1 生活中的信号 1.2 技术应用中的信号 1.3 signal函数捕捉信号 1.3 信号的发送与记录 1.4 信号的常见处理方式 二&#xff0c;信号的产生 2.1 核心转储 2.1.1 环境配置 2.1.2 利用core文件进行调试 2.1.3 core dump标志 2.2 通过系统…

【C语言】解决C语言报错:Null Pointer Dereference

文章目录 简介什么是Null Pointer DereferenceNull Pointer Dereference的常见原因如何检测和调试Null Pointer Dereference解决Null Pointer Dereference的最佳实践详细实例解析示例1&#xff1a;未初始化的指针示例2&#xff1a;释放内存后未将指针置为NULL示例3&#xff1a;…

STM32HAL库--NVIC和EXTI

1. 外部中断实验 1.1 NVIC和EXTI简介 1.1.1 NVIC简介 NVIC 即嵌套向量中断控制器&#xff0c;全称 Nested vectored interrupt controller。是ARM Cortex-M处理器中用于管理中断的重要组件。负责处理中断请求&#xff0c;分配优先级&#xff0c;并协调中断的触发和响应。 它是…

【会议征稿,IEEE出版】第四届电气工程与机电一体化技术国际学术会议(ICEEMT 2024,7月5-7)

第四届电气工程与机电一体化技术国际学术会议&#xff08;ICEEMT 2024&#xff09;定于2024年7月5-7日在浙江省杭州市隆重举行 。会议主要围绕“电气工程”、“机电一体化” 等研究领域展开讨论&#xff0c;旨在为电气工程、机电一体化等领域的专家学者、工程技术人员、技术研发…

STM32项目分享:智慧农业(机智云)系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 1.PCB图 2.PCB板打样焊接图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.c…

如何灵活运用keil工具进行问题分析(2)— 定位FreeRTOS的栈溢出导致hardfault问题

前言 &#xff08;1&#xff09;如果有嵌入式企业需要招聘湖南区域日常实习生&#xff0c;任何区域的暑假Linux驱动实习岗位&#xff0c;可C站直接私聊&#xff0c;或者邮件&#xff1a;zhangyixu02gmail.com&#xff0c;此消息至2025年1月1日前均有效 &#xff08;2&#xff0…

【会议征稿,JPCS出版】第四届测量控制与仪器仪表国际学术会议(MCAI 2024,7月19-21)

随着各行各业向智能化、自动化、信息化方向发展&#xff0c;对于精密测量与控制的需求日益增加。同时该专业人才在科学研究、制造业、互联网等领域就业前景也非常广阔。测控技术与仪器是现代工程与科技的重要领域之一&#xff0c;为各行各业提供精准、可靠的测量、控制和检测手…

LuckySheet导入报错file.match is not a function解决方法

最近在研究有什么类Excel的表格库。目前看到的话就是luckysheet和univer。 univer是luckysheet的升级版&#xff0c;问题是开源版没有提供导入导出功能。 在尝试luckysheet的导入功能的时候&#xff0c;会发现官方demo是没有报错的。而在自己的项目里就会有报错 导入呢是官方提…

jsp运行提示_jsp.java某行存在错误问题的解决

jsp运行提示XXX_jsp.java某行存在错误问题的解决 在编译运行jsp文件时&#xff0c;出现类似如下提示&#xff1a; 49行发生错误&#xff0c;要注意&#xff1a; 这里所指的49行并非jsp文件的第49行&#xff0c;而是编译后的jsp.java文件的第49行。 因此&#xff1a;解决问题…

FreeRTOS,使用SDIO外设会进入Hard FaultHandler

解决方法&#xff1a; 1.读写函数中&#xff0c;要使能所有中断。 2.读写缓冲数组为全局变量 3.任务堆栈开辟的大点

process is not defined

最近在开发项目中莫名遇到这个问题&#xff0c;网上查阅很多资料&#xff0c;上面的解决办法都不管用。最后在node_modules中找到path-browserify模块&#xff0c;修改index.js文件&#xff1a; 将process.cwd()注释&#xff0c;改为cwd __dirname,然后重启项目就好了。

教育与学习助手:Kompas AI革新学习体验

一、引言 在个性化学习需求日益增长的今天&#xff0c;教育领域正经历着一场技术革命。Kompas AI&#xff0c;作为一款先进的人工智能助手&#xff0c;正以其独特的功能和应用&#xff0c;为学习者提供个性化的学习支持&#xff0c;满足不同背景和需求的学生。 二、功能介绍 Ko…

kotlin区间

1、创建 fun main() {// 全闭区间val intRange 1..3 // int 区间val charRange a..c // 字符区间// 打印println(intRange.joinToString()) // 1,2,3println(charRange.joinToString()) // a,b,c// 左闭右开区间val intRangeExclusive 1 until 3// 倒叙全闭区间val intDown…

网络层 IP协议【计算机网络】【协议格式 || 分片 || 网段划分 || 子网掩码】

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;Linux_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 一&#xff0c;前提 二&…

【NOI-题解】1431. 迷宫的第一条出路

文章目录 一、前言二、问题问题&#xff1a;1431. 迷宫的第一条出路 三、感谢 一、前言 二、问题 问题&#xff1a;1431. 迷宫的第一条出路 类型&#xff1a;深度搜索、回溯、路径打印 题目描述&#xff1a; 已知一 NN 的迷宫&#xff0c;允许往上、下、左、右四个方向行走…