指针之旅(3)—— 指针 与 数组

news2024/9/24 13:19:43

目录

1. 数组名的两种意义

2. 指针访问数组(指针也能下标引用)

3. 一维数组传参的本质 和 sizeof在函数中失效的原因

4. 指针数组

4.1 指针数组的概念

4.2 一级指针数组

4.3 一级指针数组模拟实现二维数组

5. 数组、指针 与 字符串

6. 数组指针(变量)

6.1 数组指针的概念

6.2 一维数组指针(变量)

6.3 行指针(变量)

7. 二维数组传参的本质


1. 数组名的两种意义

数组名在三种不同的情况下共有2种意义:

❶数组名代表首元素【一种】

❷数组名代表整个数组【两种】

情况1 ---- 一般情况:数组名就是数组⾸元素的地址

情况2 ---- sizeof 数组名 或 sizeof(数组名):sizeof中单独放数组名时,这⾥的数组名表示整个数组,计算的是整个数组的大小, 单位是字节。

情况3 ---- &数组名:此时数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)


虽然说数组名有2种意义,但是我们又发现有这样一个等价式:

数组名的值 == 整个数组的地址 == 数组首元素的地址

即:arr == &arr == &arr[0]  

那到底arr,&arr和&arr[0]有什么区别呢?让我们看一下下面的代码:

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("&arr	  = %p\n", &arr);
	printf("&arr+1    = %p\n", &arr + 1);
	return 0;
}

运行结果: 

可以看到arr的变化 与 &arr[0]的变化是一样的,加1后都是相差了4个字节(1个int型),这也侧面说明了在一般情况下,数组名就是数组⾸元素的地址。而&arr加1前后相差了40个字节(1个int[10]型),而该数组arr的数据类型就是int[10]型,所以“&数组名”代表的是整个数组。

【没错,数组也是有数据类型的,详细请看《数组基础知识和冷知识(超详细总结)》中的“一维数组数据类型(需知)”和“二维数组数据类型(需知)”的知识点内容】


前面提到,sizeof中单独放数组名时,数组名才代表整个数组。所以如果sizeof中不仅仅只有数组名时,此时数组名还是代表首元素

比如:

sizeof中的是arr+1,不是单独1个arr,所以此时的arr属于一般情况:arr等于&arr[0]。而arr[0]的数据类型是int型,是4个字节,根据“指针+-整数”的运算规则(即“地址+-整数”的规则),+1等于加上4个字节,所以sizeof(arr+1)的结果是8。


2. 指针访问数组(指针也能下标引用)

在指针之旅(1)中,我们就学过解引用操作符*的知识了。

如果写一个程序来打印数组元素,要求使用指针访问数组,我们会这样写:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//输出
	int* p = arr;
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

而在指针之旅(1)中,我们也学过:我们对数组arr进行下标引用,其实在计算机中会自动转换成解引用操作,即arr[i]会先被计算机解读成*(arr+i)。

那我们能不能反其道而行,用指针p访问数组时写成p[i]呢?:

可以看到是可以这样操作的。于是就有了下面的等式:

*(p + i) == arr[i] == *(arr + i) == p[i]


3. 一维数组传参的本质 和 sizeof在函数中失效的原因

在《函数(子程序)的常见、易混淆概念详解》中,我们学习了一维数组传参的本质

  • 一维数组传参的时候,本质上传递的是一维数组首元素arr[0]的地址,即&arr[0]
  • 函数形参数组是一个伪(一维)数组,本质上是一个一级指针

正因为它是个指针,所以sizeof对形参数组计算出来的结果是指针的大小(4或8个字节),而不是数组的大小

代码举例:


4. 指针数组

4.1 指针数组的概念

指针数组是指针还是数组?

我们类⽐⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。 所以指针数组是存放指针的数组

指针数组的定义标准:

  • 如果数组中每个元素都是用来存放同级别地址或指针的,那么该数组是一个指针数组。

代码举例:

int main()
{
	int a = 1, b = 2;
	int* pi = NULL;
	int* arr[5] = { &a,&b,pi };
	return 0;
}

这里的(整型一级)指针数组,既装有地址,如arr[1]的&a和arr[2]的&b;又装有一级指针,如arr[3]的pi。剩下的元素默认初始化为0,即NULL。

对于整型数组和字符型数组,它们的每一个元素的类型是这样的:

而指针数组的每一个元素的类型是类似下面这样的:

4.2 一级指针数组

(多级指针数组不常用,这里只讲解一级指针数组)

一级指针数组的数据类型:

  • 去掉数组名,就是一级指针数组的类型:
  • 即“ 内置类型 * [数组大小] ”   

例如:

int* arr[7]的类型就是int* [7]。

4.3 一级指针数组模拟实现二维数组

我们先设置一个正常的(整型)二维数组

int main()
{
	//二维数组的创建
	int arr[3][5] = { {1,1,1,1,1}, {2,2,2,2,2}, {3,3,3,3,3} };
	//输出
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
			printf("%d ", arr[i][j]);
		printf("\n");
	}
	return 0;
}

该二维数组的打印结果: 

                

现在我们用一个(整型一级)指针数组来模拟这个二维数组:

int main()
{
	//指针数组的创建
	int arr1[] = { 1,1,1,1,1 };
	int arr2[] = { 2,2,2,2,2 };
	int arr3[] = { 3,3,3,3,3 };
	int* parr[3] = { arr1, arr2, arr3 };
	//输出
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
			printf("%d ", parr[i][j]);
		printf("\n");
	}
	return 0;
}

该指针数组打印的结果:

可以看到,两次打印的结果是一样的,这就是指针数组模拟实现二维数组。


虽然模拟出来了,但是两者的内存分布是不一样的。

这是二维数组的分布情况:

二维数组内部是连续的空间,且数组名arr指向的是数组首元素arr[0][0]

 指针数组属于数组,所以parr中的每个元素都是连续,即parr[0]、parr[1]、parr[2]是连续的,但是arr1,arr2,arr3的内存是相互独立的。数组名parr指向的是数组首元素parr[0]

5. 数组、指针 与 字符串

当我们想用一个载体来装载所需要的字符串时,我们有两种方法。一种是用字符数组装载,比如char str[5] = "abc"; 另一种是用字符型指针装载,比如char* p = "abc";

其实这两种装载方式本质上是不一样的

先说结论:

(1)以字符数组为载体保存字符串,该字符串是可改变的。(可变字符串)

(2)以字符指针为载体保存字符串,该字符串是不可被更改的。(常量字符串)

 i. 字符数组 与 可变字符串

我们先看这个代码:

int main()
{
	char str[10] = "hello";
	printf("str原来是%s\n", str);
	str[2] = 'X';
	printf("str后来是%s\n", str);
	return 0;
}

运行结果:

可以看到,hello变成了heXlo。

如果是指针的话,会发生什么?

 ii. 字符指针(变量) 与 常量字符串

再看这个代码:

int main()
{
	char* pstr = "hello";
	printf("pstr原来是%s\n", pstr);
	*(pstr + 2) = 'X';
	printf("pstr是%s", pstr);
	return 0;
}

运行结果:

可以看到,程序运行到“*(pstr + 2) = 'X';”就异常退出了。

我们调试起来看看:

可以发现,我们没有权限去修改这个字符串。

深层知识补充:(了解即可)

  • 用数组保存字符串时,系统会开辟新的空间
  • 用指针保存字符串时,不会开辟新的空间,常量字符串是本来就存在的。对指针输入字符串时(如“char* str = "hello";”),本质上是返回常量字符串的地址给指针
  • 常量字符串不可被修改的原因:常量字符串是存在于常量区的,进程看到的地址都是虚拟地址。虚拟地址空间经过页表的映射才能找到物理内存,而常量区经过页表映射的时候,权限就是只读的, 所以不允许修改

6. 数组指针(变量)

6.1 数组指针的概念

根据前⾯学习的整型指针和字符指针,我们列出类⽐推理:

  • 字符指针-->是指针变量-->存放的是字符变量的地址
  • 整型指针-->是指针变量-->存放的是整型变量的地址
  • 数组指针-->是指针变量-->存放的是数组的地址

思考一下,那我们应该怎样得到数组的地址?

  1. 使用&arr[0]?错:&arr[0]是数组首元素的地址
  2. 使用arr?错:arr仍然是首元素的地址
  3. 使用&arr,这才是代表数组的地址。

由此,我们来总结一下

数组指针的定义:

  1. 数组指针也是一种指针变量,全称是“指向数组的指针变量”
  2. 数组指针(变量)存放的是数组的地址

6.2 一维数组指针(变量)

(因为多维数组指针变量用不上,这里只讲一维数组指针变量)

(一)一维数组指针变量的初始化:

  1. 类型  一维数组名[k];                                             //先要有一维数组
  2. 同类型  (*数组指针名)[同k] = &同一维数组名;     //再有一维数组指针

(二)一维数组指针变量的数据类型:

  • 去掉指针名后,中括号[ ]与小括号()的位置交换一下:
  • “  同类型[同k] (*) 或 同类型[同k]*  ”

注意:下标引用[ ]的优先级 比 解引用*的优先级高。如果不写括号,会变成(一级)指针数组,其本质就成数组了。

比如:                       

1. int  arr[5] = {0,1,2,3,4};

2. int  (*p)[5] = &arr;

一维数组指针p的数据类型是:int[5] (*)

拆分解析:

int  (*p)  [5]  =  &arr; 

|       |      |

|       |      p所指向的数组arr 的元素个数

|       *p声明p是一个指针     

p所指向的数组arr的 单个元素的类型   


不能不写或写错方括号中的数值,因为指针类型决定了指针+-整数的步长。


思考一下,&arr的数据类型是不是二级指针型?

调试举例:

可以发现,此处&arr的数据类型并不是二级指针型int**,而是数组指针型int[10]*

(arr[0]的类型是int,加上&后,&arr[0]的类型变成了int*;arr的类型是int[10],加上&后,&arr的类型变成了int[10]*。【总结:加&添* 】)

6.3 行指针(变量)

行指针是一种特殊的数组指针,它与一维数组指针相似,但行指针并不是一维数组指针

(一)行指针的初始化:

  1. 类型  二维数组名[k1][k2];
  2. 同类型 (*数组指针名)[同k2] = 二维数组名;

(二)行指针的数据类型: 

  • 去掉指针名后,中括号[ ]与小括号()的位置交换一下:(这与一维数组指针类似)
  •   即“  同类型[同k2] (*) 或 同类型[同k2]*  ”

例如:

int  arr[3][5] = { 0 };

int  (*p)[5] = arr;

行指针p的数据类型是:int[3]*

拆分解析:

int  (*p)  [5]  =  &arr; 

|       |      |

|       |     p所指向的数组arr中,一行的元素个数或列数(变了)

|      *p声明p是一个指针(没变)     

p所指向的数组arr的单个元素的类型(没变)   

为什么行指针不是一维数组指针?

原因如下:(假设指针都称作p,一维数组叫arr1,二维数组叫arr2)

(1)一维数组指针的定义是:一维数组指针(变量)存放的是整个一维数组的地址,即p = &arr1。(指针的数据类型决定指针+-整数的步长,一维数组指针p的一个步长是整个一维数组arr1的长度

(2)行指针的定义是:行指针(变量)存放的是二维数组的第一行的地址,即p = arr2。(指针的数据类型决定指针+-整数的步长,行指针p的一个步长就是二维数组arr2一行的长度

7. 二维数组传参的本质

思考一下,二维数组形参的数据类型是二级指针?还是数组指针?

代码演示:

//打印二维数组的函数
void f(int arr[][3], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
			printf("%d ", arr[i][j]);
		printf("\n");
	}
}
//mian函数
int main()
{
	int* a[4][3] = { {1},{2},{3} };
	f(a, 3, 3);
	return 0;
}

从中可以发现,二维数组的形参是一个行指针。

总结,二维数组传参的本质

  •   二维数组传参的时候,本质上传递的是二维数组第一行arr[0]的地址,即&arr[0]。(不是&arr[0][0])
  • 函数形参数组是一个伪(二维)数组,本质上是一个行指针。(不是二级指针,也不是一维数组指针。)


本期分享完毕,多多点赞,多多支持哦~Thanks♪(・ω・)ノ

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

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

相关文章

微信小程序实现文件的预览

1&#xff0c;最简单的直接使用<web-view src网络文件地址><web-view>文件如果有在线地址&#xff0c;直接用web&#xff0c;但是要在小程序的管理平台中增加文件地址到业务域名当中 2、使用微信本身自带方法 图片预览 wx.previewImage({urls:[this.data.ossPath…

【数据结构】【java】leetcode刷题记录--链表

简介 链表是一种常见的基础数据结构&#xff0c;它由一系列节点组成&#xff0c;每个节点包含数据域和指向下一个节点的指针。在Java中&#xff0c;链表通常用于实现动态数据结构&#xff0c;因为它可以根据需要动态地增加或减少节点。 链表简介&#xff1a; 节点结构&#…

如何为 DigitalOcean 静态路由操作员设置故障转移

静态路由操作器的主要目的是提供更大的灵活性&#xff0c;并在 Kubernetes 环境中控制网络流量。它使你能够根据应用程序的需求自定义路由配置&#xff0c;从而优化网络性能。该操作器作为 DaemonSet 部署&#xff0c;因此将在你的 DigitalOcean Managed Kubernetes 集群的每个…

以太坊基金会AMA总结:面对ETH价格疲软,团队的应对策略与展望

2024年9月5日晚&#xff0c;以太坊基金会举行了第12届AMA&#xff08;Ask Me Anything&#xff09;活动。本次活动在Twitter Space上进行&#xff0c;由以太坊联合创始人Vitalik Buterin、基金会成员Justin Drake和Dankrad Feist等核心成员参与&#xff0c;针对社区关心的多个问…

【YashanDB知识库】表数据量不多,lob数据段有大量空间,插入数据报错

问题现象 clob段异常增长&#xff0c;导致磁盘空间满&#xff0c;应用无法使用数据库。 问题风险及影响 lob段空间未复用&#xff0c;lob段空间扩张很大&#xff0c;影响磁盘占用合理分配。 空间不够&#xff0c;插入报错&#xff0c;影响业务。 问题影响的版本 所有版本…

全视通智慧病房系统旧病房改造方案

一、背景介绍 在当今医疗技术日新月异的时代&#xff0c;智慧病房作为医院现代化建设的重要一环&#xff0c;正逐步从概念走向现实&#xff0c;深刻改变着患者的就医体验与医护人员的工作模式。智慧病房的改造背景&#xff0c;根植于医疗需求的日益增长、技术创新的不断推动以及…

自定义事件分发

一、在C中创建可接收事件的接口类EventInterface&#xff0c;继承自UInterface 1、EventInterface.h #pragma once #include "CoreMinimal.h" #include "UObject/Interface.h" #include "EventInterface.generated.h" UINTERFACE(MinimalAPI) c…

页面小组件-搜索栏(二)-未经项目验证,慎重!!!

前言说明 这一版是未经过项目验证的&#xff0c;可能会有地方需要自行调整&#xff0c;如需使用&#xff0c;请慎重、慎重、再慎重&#xff01;&#xff01;&#xff01; 前言追溯 前面分享过的搜索栏组件是一个临时产物&#xff0c;经历了一两个项目之后就被淘汰了。后续在…

Aloudata AIR :国内首个 Data Fabric 逻辑数据平台

AIR 的寓意是“极致轻盈的数据交付”&#xff1a;A - Adaptive 自适应&#xff0c;I - Integration 集成&#xff0c;R - Resilience 弹性 News&#xff1a;Aloudata AIR 发布 作为国内首个 Data Fabric 逻辑数据平台&#xff0c;Aloudata AIR 通过自研的数据虚拟化技术&#…

使用树莓派学习——Linux库编程

树莓派开发——Linux静态动态库 文章目录 树莓派开发——Linux静态动态库一、分文件编程1.1 分文件编程的优点&#xff1a;1.2 分文件编程的步骤&#xff1a; 二、Linux的库2.1 函数库的概念&#xff1a;2.2 静态库和动态库的比较&#xff1a;静态数据库&#xff08;libXXX.a&a…

奖项再+1!通义灵码智能编码助手通过可信 AI 智能编码工具评估,获当前最高等级

阿里云的通义灵码智能编码助手参与中国信通院组织的可信AI智能编码工具首轮评估&#xff0c;最终获得 4 级评级&#xff0c;成为国内首批通过该项评估并获得当前最高评级的企业之一。 此次评估以《智能化软件工程技术和应用要求 第 2 部分&#xff1a;智能开发能力》为依据&…

C/C++的自由落体运动

目录 1. 前言 2. 正文 2.1 问题 2.2 解决办法 2.2.1 思路 2.2.2 代码实现 2.2.3 测试结果 3. 备注 1. 前言 这个题目非常有意思&#xff0c;可以活跃自己的思维&#xff0c;毕竟代码来源于生活&#xff0c;又返回给生活。 2. 正文 2.1 问题 题目描述&#xff1a; …

携手共建云安全未来 |“集美大学服云实习基地”授牌仪式圆满举行

集美大学服云实习基地授牌仪式 9月3日&#xff0c;集美大学服云实习基地授牌仪式在厦门成功举行。集美大学科研处副处长茅剑、计算机工程学院副院长刘晋明&#xff0c;以及安全狗副总裁&CTO陈荣有、副总裁刘春辉共同出席此次的授牌仪式。 01 会上&#xff0c;安全狗副总裁刘…

梨花声音教育退费普通话学习技巧之了解文化背景

在学习普通话的过程中&#xff0c;了解中国的文化背景是不可或缺的一环。语言不仅是交流的工具&#xff0c;更是文化的载体。通过深入了解中国的历史、文化和社会背景&#xff0c;学习者可以更好地理解和掌握普通话&#xff0c;使语言学习变得更加生动有趣。本文将从几个方面详…

监控平台总结之面试常问答案

思路 延伸的面试题总结及答案&#xff1a; 1.说说前端监控平台/监控SDK架构设计和难点亮点&#xff1f; 架构设计 数据采集层: SDK: 在前端集成的 SDK 负责采集数据&#xff0c;包括性能指标、用户行为、错误日志等。 数据收集: 实现高效的数据采集机制&#xff0c;支持实时…

C++_多态详解

多态的概念 概念&#xff1a;需要去完成某个行为时&#xff0c;当 不同的对象去完成 会产生出不同的状态。通俗点说就是 不同类型的对象去做同一个行为&#xff0c;产生的结果不同。 多态的定义及实现 虚函数 定义&#xff1a;即被virtual修饰的类成员函数称为虚函数 虚函…

多门店管理下的高效IT运维策略与实战指南

连锁门店作为直接面向消费者的服务点&#xff0c;是企业与顾客建立联系的关键触点。随着商业竞争的加剧&#xff0c;连锁门店企业纷纷通过扩大实体店面的规模来抢占市场份额。随着门店数量的激增&#xff0c;门店IT运维管理的复杂性和挑战也日益凸显。本文将深入剖析门店IT运维…

828华为云征文|采用Flexus云服务器X实例部署RTSP直播服务器

一、前言 这篇文章讲解&#xff1a; 采用华为云最新推出的Flexus云服务器X实例搭建RTSP服务器&#xff0c;完成视频直播需求。 随着实时视频流传输需求的增长&#xff0c;RTSP&#xff08;实时流协议&#xff09;服务器成为了许多视频监控、直播和多媒体应用的核心组件。在当…

有趣的手机端见缝插针游戏源码

有趣的手机端见缝插针游戏源码下载&#xff0c;注&#xff1a;本地预览请用火狐浏览器模拟移动端&#xff0c;chrome本地预览存在跨域问题。 微信扫码获取源码

vue3 响应式 API:shallowRef()和shallowReactive()

shallowRef() shallowRef()是一个用于创建浅层响应式引用的函数。它创建一个响应式数据&#xff0c;但只对顶层属性进行响应式处理。 特点&#xff1a; 只跟踪引用值的变化&#xff0c;不关心值内部的属性变化。 <template><div>{{ shallowRefObj }}</div>…