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

news2024/11/23 22:30:11

文章目录

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

在这里插入图片描述

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

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

相关文章

踩坑!被node-sass折磨的一天

文章目录 被node-sass折磨的一天折磨过程了解原因注意事项 被node-sass折磨的一天 折磨过程 起因是要开发一个老项目&#xff0c;照常拉代码、下依赖、启动三步走 依赖开始下载不对了&#xff0c;以为是node版本问题&#xff0c;寻找node-sass对应的node版本 利用nvm&#…

IP地址乱成一团?用Shell一键搞定!

在日常的运维工作中&#xff0c;我们经常需要对各种数据进行处理和分析&#xff0c;其中包括对IP地址的管理和排序。排序后的IP地址列表可以帮助我们更好地进行日志分析、网络流量监控和故障排除。 本文将模拟一个运维场景&#xff0c;展示如何对IP地址进行排序&#xff0c;并探…

云原生应用开发培训,开启云计算时代的新征程

在云计算时代&#xff0c;云原生应用开发技术已经成为IT领域的热门话题。如果您想要转型至云原生领域&#xff0c;我们的云原生应用开发培训将帮助您开启新征程。 我们的课程内容涵盖了云原生技术的基础概念、容器技术、微服务架构、持续集成与持续发布&#xff08;CI/CD&#…

传统零售运营模式有什么缺点?新零售模式下的运营思维好在哪里?

随着科技的进步、消费者需求的多样化以及市场竞争的加剧&#xff0c;传统零售模式已逐渐难以满足现代消费者对购物体验的期待。新零售&#xff0c;作为一个创新的概念&#xff0c;应运而生&#xff0c;它不仅代表着零售行业的未来趋势&#xff0c;更是一种全新的商业思维和运营…

pytest中一个场景测试的demo

注意点1&#xff1a; allure.severity 是一个装饰器&#xff0c;用于设置测试用例的严重性级别。 allure.severity_level.CRITICAL 是Allure提供的严重性级别之一&#xff0c;表示这个测试用例极为重要。allure.severity_level.BLOCKER&#xff1a;阻塞级别的问题&#xff0c…

python发送http请求

python有个内置模块发送请求urllib的request的 openurl()方法&#xff0c;打开一个链接&#xff0c;就是发送一个请求&#xff0c;&#xff0c; 因为内置模块发请求不是那么好用&#xff0c;一般都会使用第三方的请求包&#xff0c;requests模块&#xff0c; 请求头 User-Ag…

Unity基础(一)unity的下载与安装

目录 一:下载与安装 1.官网下载地址 2.推荐直接下载UnityHub 3.选择编辑器版本(推荐长期支持版) 4.在UnityHub安装选择相应的模块 二:创建项目 简介: Unity 是一款广泛应用的跨平台游戏开发引擎。 它具有以下显著特点&#xff1a; 强大的跨平台能力&#xff1a;能将开发的游…

CPI降温仍猛砍降息预期!美联储继续按兵不动,预计今年仅降息一次

要点&#xff1a; 美联储继续保持利率不变&#xff0c;符合市场预期。 决议声明不再说降通胀缺乏进一步进展&#xff0c;改称取得适度的进... 要点&#xff1a; 美联储继续保持利率不变&#xff0c;符合市场预期。 决议声明不再说降通胀缺乏进一步进展&#xff0c;改称取得适度…

Spring Boot集成antlr实现词法和语法分析

1.什么是antlr&#xff1f; Antlr4 是一款强大的语法生成器工具&#xff0c;可用于读取、处理、执行和翻译结构化的文本或二进制文件。基本上是当前 Java 语言中使用最为广泛的语法生成器工具。Twitter搜索使用ANTLR进行语法分析&#xff0c;每天处理超过20亿次查询&#xff1…

【Redis】Redis常见问题——缓存更新/内存淘汰机制/缓存一致性

目录 回顾数据库的问题如何提高 mysql 能承担的并发量&#xff1f;缓存解决方案应对的场景 缓存更新问题定期生成如何定期统计定期生成的优缺点 实时生成maxmemory 设置成多少合适呢&#xff1f;项目类型上来说 新的问题 内存淘汰策略Redis淘汰策略为什么redis要内存淘汰内存淘…

乡村振兴的科技创新引领:加强农业科技研发,推广先进适用技术,提高农业生产效率,助力美丽乡村建设

目录 一、引言 二、农业科技研发的重要性 &#xff08;一&#xff09;提升农业生产效率 &#xff08;二&#xff09;促进农业产业升级 &#xff08;三&#xff09;保障粮食安全 三、加强农业科技研发的策略 &#xff08;一&#xff09;加大投入力度 &#xff08;二&…

9.8k star!一款小而美的开源物联网操作系统:RT-Thread

介绍 RT-Thread是一款主要由中国开源社区主导开发的开源实时操作系统&#xff08;RTOS&#xff09;。它不仅是一个实时操作系统内核&#xff0c;也是一个完整的应用系统&#xff0c;包含了实时、嵌入式系统相关的各个组件&#xff0c;如TCP/IP协议栈、文件系统、libc接口、图形…

Sass实战运用,如何利用好Sass

Sass&#xff08;Syntactically Awesome Stylesheets&#xff09;是一种CSS预处理器&#xff0c;它提供了许多强大的功能&#xff0c;如变量、嵌套规则、混合&#xff08;Mixins&#xff09;、函数等&#xff0c;使得CSS的编写更加高效、灵活和易于维护。以下是关于Sass实战运用…

配置ISCSI共享服务端

在 system1-上执行&#xff1a; 1、配置hosts文件和domain域名 [rootsystem1 ~]# echo echo "172.24.8.11 system1" >>/etc/hosts [rootsystem1 ~]# echo echo "172.24.8.12 system2" >>/etc/hosts [rootsystem1 ~]# nmcli connection mod…

Linux时间子系统2: clock_gettime的VDSO机制分析

在之前分析clock_gettime的文章中接触到了VDSO&#xff0c;本篇文章是对VDSO的学习总结&#xff0c;借鉴了很多前人的经验。 1. 什么是VDSO vDSO:virtual DSO(Dynamic Shared Object)&#xff0c;虚拟动态共享库&#xff0c;内核向用户态提供了一个虚拟的动态共享库。在 Linux …

52.Python-web框架-Django - 多语言编译-fuzzy错误

目录 1.起因 2.原因 3.解决方法 3.1手动移除fuzzy标记 3.2重新生成po文件&#xff0c;并检查是否还存在fuzzy标记 3.3重新编译生成mo文件 1.起因 在Django的国际化和本地化过程中&#xff0c;当你发现某些字段仅显示msgid&#xff0c;而不显示msgstr时&#xff0c;可能是…

如何购买代码签名证书?

在Gworg网站上快速轻松地购买代码签名证书&#xff1a; 1.根据需要选择合适的代码签名证书&#xff1a; OV代码签名、EV代码签名 2.选择结算周期 代码签名证书可购买&#xff0c;有效期最长为3年。 注意&#xff1a;建议一次性3年&#xff0c;因为代码签名证书不能之前续费…

2024.6.12 作业 xyt

今日课堂练习&#xff1a;vector构造函数 #include <iostream> #include <vector> using namespace std;void printVector(vector<int> &v) {vector<int>::iterator iter;for(iterv.begin(); iter ! v.end(); iter){cout << *iter <<…

765432221

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

深入探究MySQL游标(Cursor)

前言 MySQL游标&#xff08;Cursor&#xff09;是MySQL中用于处理查询结果的一种机制。游标允许我们在查询结果集中逐行处理数据&#xff0c;而不是一次性获取所有数据。这对于处理大量数据非常有用&#xff0c;因为它可以减少内存消耗并提高性能。在MySQL中&#xff0c;游标主…