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

前言:上期我们介绍了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=&mprintf("%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.rhkb.cn/news/14740.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业FTP替代升级,实现传输大文件提升100倍!

随着信息技术的飞速发展&#xff0c;网络安全环境也变得越来越复杂。在这种背景下&#xff0c;传统的FTP&#xff08;文件传输协议&#xff09;已经很难满足现代企业对文件传输的需求了。FTP虽然用起来简单&#xff0c;但它的局限性和安全漏洞让它在面对高效、安全的数据交换时…

树和二叉树_7

树和二叉树_7 一、leetcode-102二、题解1.引库2.代码 一、leetcode-102 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 样例输入&#xff1a;root [3,9,20,null,nu…

2.8作业

作业 优化登录框&#xff1a; 当用户点击取消按钮&#xff0c;弹出问题对话框&#xff0c;询问是否要确定退出登录&#xff0c;并提供两个按钮&#xff0c;yes|No&#xff0c;如果用户点击的Yes&#xff0c;则关闭对话框&#xff0c;如果用户点击的No&#xff0c;则继续登录 当…

【WB 深度学习实验管理】使用 PyTorch Lightning 实现高效的图像分类实验跟踪

本文使用到的 Jupyter Notebook 可在GitHub仓库002文件夹找到&#xff0c;别忘了给仓库点个小心心~~~ https://github.com/LFF8888/FF-Studio-Resources 在机器学习项目中&#xff0c;实验跟踪和结果可视化是至关重要的环节。无论是调整超参数、优化模型架构&#xff0c;还是监…

人工智能入门 数学基础 线性代数 笔记

必备的数学知识是理解人工智能不可或缺的要素&#xff0c;今天的种种人工智能技术归根到底都建立在数学模型之上&#xff0c;而这些数学模型又都离不开线性代数&#xff08;linear algebra&#xff09;的理论框架。 线性代数的核心意义&#xff1a;世间万事万物都可以被抽象成某…

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以后的则需…

unity碰撞的监测和监听

1.创建一个地面 2.去资源商店下载一个火焰素材 3.把procedural fire导入到自己的项目包管理器中 4.给magic fire 0 挂在碰撞组件Rigidbody , Sphere Collider 5.创建脚本test 并挂在magic fire 0 脚本代码 using System.Collections; using System.Collections.Generic; usi…

使用云效解决docker官方镜像拉取不到的问题

目录 前言原文地址测试jenkins构建结果:后续使用说明 前言 最近经常出现docker镜像进行拉取不了&#xff0c;流水线挂掉的问题&#xff0c;看到一个解决方案: 《借助阿里个人版镜像仓库云效实现全免费同步docker官方镜像到国内》 原文地址 https://developer.aliyun.com/artic…

element-plus+vue3前端如何根据name进行搜索查到符合条件的数据

界面如图&#xff0c;下面的区域是接口给的所有的&#xff0c;希望前端根据输入的内容自己去匹配。 我是使用的element-plusvue3ts的写法。 <el-input v-model"filters.region" placeholder"输入区域搜索" keyup"filterRegion(filters.region)&q…

电路研究9.3——合宙Air780EP中的AT开发指南(含TCP 示例)

根据合宙的AT研发推荐&#xff0c; AT指令基本上也简单看完了&#xff0c;这里开始转到AT的开发了。 AT 命令采用标准串口进行数据收发&#xff0c;将以前复杂的设备通讯方式转换成简单的串口编程&#xff0c; 大大简化了产品的硬件设计和软件开发成本&#xff0c;这使得几乎所…

cursor指令工具

Cursor 工具使用指南与实例 工具概览 Cursor 提供了一系列强大的工具来帮助开发者提高工作效率。本指南将通过具体实例来展示这些工具的使用方法。 1. 目录文件操作 1.1 查看目录内容 (list_dir) 使用 list_dir 命令可以查看指定目录下的文件结构: 示例: list_dir log…

AI安全最佳实践:AI应用开发安全评估矩阵(上)

生成式AI开发安全范围矩阵简介 生成式AI目前可以说是当下最热门的技术&#xff0c;吸引各大全球企业的关注&#xff0c;并在全球各行各业中带来浪潮般的编个。随时AI能力的飞跃&#xff0c;大语言模型LLM参数达到千亿级别&#xff0c;它和Transformer神经网络共同驱动了我们工…

Java继承简介

继承的本质&#xff1a;是代码的复用&#xff0c;重复使用已经定义好的方法和域&#xff08;即全局变量&#xff09; 要掌握继承首先要了解Java方法的重载和重写 方法的重载和重写 方法的重载 当前方法名相同&#xff0c;但是参数类型不同&#xff0c;发生重载 类比数学函…

【redis】缓存设计规范

本文是 Redis 键值设计的 14 个核心规范与最佳实践&#xff0c;按重要程度分层说明&#xff1a; 一、通用数据类型选择 这里我们先给出常规的选择路径图。 以下是对每个步骤的分析&#xff1a; 是否需要排序&#xff1f;&#xff1a; zset&#xff08;有序集合&#xff09;用…

Unity抖音云启动测试:如何用cmd命令行启动exe

相关资料&#xff1a;弹幕云启动&#xff08;原“玩法云启动能力”&#xff09;_直播小玩法_抖音开放平台 1&#xff0c;操作方法 在做云启动的时候&#xff0c;接完发现需要命令行模拟云环境测试启动&#xff0c;所以研究了下。 首先进入cmd命令&#xff0c;CD进入对应包的文件…

Android studio怎么创建assets目录

在Android Studio中创建assets文件夹是一个简单的步骤&#xff0c;通常用于存储不需要编译的资源文件&#xff0c;如文本文件、图片、音频等 main文件夹&#xff0c;邮件new->folder-assets folder

第26场蓝桥入门赛

5.扑克较量【算法赛】 - 蓝桥云课 C&#xff1a; #include <iostream> #include <algorithm> using namespace std;int a[100005];int main() {int n,k;cin>>n>>k;for (int i1; i<n; i)cin>>a[i], a[i] % k;sort(a1, a1n);int mx a[1]k-a…

公司配置内网穿透方法笔记

一、目的 公司内部有局域网&#xff0c;局域网上有ftp服务器&#xff0c;有windows桌面服务器&#xff1b; 在内网环境下&#xff0c;是可以访问ftp服务器以及用远程桌面登录windows桌面服务器的&#xff1b; 现在想居家办公时&#xff0c;也能访问到公司内网的ftp服务器和win…

c++:list

1.list的使用 1.1构造 1.2迭代器遍历 &#xff08;1&#xff09;迭代器是算法和容器链接起来的桥梁 容器就是链表&#xff0c;顺序表等数据结构&#xff0c;他们有各自的特点&#xff0c;所以底层结构是不同的。在不用迭代器的前提下&#xff0c;如果我们的算法要作用在容器上…

《Wiki.js知识库部署实践 + CNB Git数据同步方案解析》

一、wiki.js 知识库简介 基本概述 定义 &#xff1a;Wiki.js 是一个开源、现代、轻量且功能强大的 Wiki 应用程序&#xff0c;基于 Node.js 构建&#xff0c;旨在帮助个人和团队轻松创建、管理和共享知识。开源性质 &#xff1a;它遵循 AGPLv3 许可证&#xff0c;任何人都可以…