指针与数组:深入C语言的内存操作艺术

数组名的理解


在上⼀个章节我们在使⽤指针访问数组的内容时,有这样的代码:                                                                

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];

这⾥我们使⽤ &arr[0] 的⽅式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址,⽽且
是数组⾸元素的地址,我们来做个测试。

#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);return 0;
}

运行后你会发现,数组名和数组首元素的地址打印出来的结果是一模一样的呀,这就再次印证了数组名就是数组首元素(也就是第一个元素)的地址这一说法。

不过,这时候可能有的同学就会产生疑问啦。要是数组名是数组首元素的地址,那下面这段代码又该怎么理解呢?

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

这段代码输出的结果是 40 呢。按之前说的,如果 arr 仅仅是数组首元素的地址,那输出的应该是 4 或者 8 才对啊。
其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:

  •    sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节。
  • 当用 &数组名 时,这里的数组名同样表示的是整个数组,取出的是整个数组的地址(要知道整个数组的地址和数组首元素的地址可是有区别的哟)。

除了这两个例外情况之外,在其他任何地方使用数组名时,数组名所表示的就是首元素的地址啦。

这时候呀,可能又有好奇的同学会进一步尝试下面这段代码呢。

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


你看,这三个打印结果又是一模一样的,这下可能又让人纳闷了,那 arr 和 &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("&arr = %p\n", &arr);printf("&arr+1 = %p\n", &arr+1);return 0;
}

 输出结果如下

&arr[0] = 0077F820                                                                                                                    &arr[0]+1 = 0077F824

arr = 0077F820

arr+1 = 0077F824

&arr = 0077F820

&arr+1 = 0077F848

从这里我们可以发现,&arr[0] 和 &arr[0]+1 相差 4 个字节,arr 和 arr+1 也相差 4 个字节,这是因为 &arr[0] 和 arr 都是首元素的地址呀,当进行 +1 操作时,就是跳过一个元素哦。而 &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]);//输入int* p = arr;for(i=0; i<sz; i++){scanf("%d", p+i);//scanf("%d", arr+i); 也可以这样写哦}//输出for(i=0; i<sz; i++){printf("%d ", *(p+i));}return 0;
}

 把这段代码弄明白后,咱们再思考一个问题哈。既然数组名 arr 是数组首元素的地址,还可以赋值给 p,在这里它们其实是等价的。我们知道可以用 arr[i] 来访问数组的元素,那 p[i] 是不是也可以访问数组呢?咱们再来看看下面这段代码哦。  

#include <stdio.h>
int main()
{int arr[10] = {0};//输入int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);//输入int* p = arr;for(i=0; i<sz; i++){scanf("%d", p+i);//scanf("%d", arr+i);  也可以这样写}//输出for(i=0; i<sz; i++){printf("%d ", p[i]);}return 0;
}

 你瞧,在代码的第 18 行那里,把 *(p+i) 换成 p[i] 也是能够正常打印输出的哦。所以呀,本质上 p[i] 是等价于 *(p+i) 的呢。同理,arr[i] 也应该等价于 *(arr+i) 哦。在编译器处理数组元素访问的时候,实际上就是将其转换成首元素的地址加上偏移量求出元素的地址,然后再通过解引用的方式来访问元素的哟。

一维数组传参的本质

咱们已经学习过数组了,也知道数组是可以传递给函数的呀。那在这个小节呢,咱们就来探讨一下数组传参的本质到底是什么。

咱们先从一个问题入手哈,之前我们都是在函数外部去计算数组的元素个数,那能不能把数组传给一个函数后,在函数内部去求数组的元素个数呢?咱们看看下面这段代码以及它的运行情况哦。

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

我们会发现呀,在函数内部并没有正确地获得数组的元素个数呢。这就需要深入了解一下数组传参的本质啦。在上个小节咱们学习过,数组名其实就是数组首元素的地址哦。那么在数组传参的时候,传递的就是数组名呀,也就是说从本质上讲,数组传参传递的就是数组首元素的地址呢。

所以呀,函数形参的部分理论上应该使用指针变量来接收这个首元素的地址哦。这样一来,在函数内部写 sizeof(arr) 时,计算的其实是一个地址的大小(单位是字节),而不是数组的大小(单位字节)啦。正是因为函数的参数部分本质上是指针,所以在函数内部是没办法求得数组元素个数的哟。咱们再看看下面这两种函数定义形式哦。

void test(int arr[])  //参数写成数组形式,本质上还是指针
{printf("%d\n", sizeof(arr));
}
void test(int* arr)  //参数写成指针形式
{printf("%d\n", sizeof(arr));  //计算一个指针变量的大小
}
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};test(arr);return 0;
}

总结一下哈,一维数组传参的时候,形参的部分既可以写成数组的形式,也可以写成指针的形式哟。

冒泡排序

冒泡排序的核心思想呢,就是让两两相邻的元素进行比较哦。下面给大家介绍两种实现冒泡排序的方法呢。

方法 1

void bubble_sort(int arr[], int sz)  //参数接收数组元素个数
{int i = 0;for(i=0; i<sz-1; i++){int j = 0;for(j=0; j<sz-i-1; j++){if(arr[j] > arr[j+1]){int tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp;}}}
}
int main()
{int arr[] = {3,1,7,5,8,9,0,2,4,6};int sz = sizeof(arr)/sizeof(arr[0]);bubble_sort(arr, sz);   for(i=0; i<sz; i++){printf("%d ", arr[i]);}return 0;
}

 方法 2  优化

void bubble_sort(int arr[], int sz)  //参数接收数组元素个数
{int i = 0;for(i=0; i<sz-1; i++){int flag = 1;  //假设这一趟已经有序了int j = 0;for(j=0; j<sz-i-1; j++){if(arr[j] > arr[j+1]){flag = 0;  //发生交换就说明,无序int tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp;}}if(flag == 1)  //这一趟没交换就说明已经有序,后续无序排序了break;}
}
int main()
{int arr[] = {3,1,7,5,8,9,0,2,4,6};int sz = sizeof(arr)/sizeof(arr[0]);bubble_sort(arr, sz);for(i=0; i<sz; i++){printf("%d ", arr[i]);}return 0;
}

二级指针

要知道呀,指针变量它本身也是变量呢,既然是变量那就有地址呀。那指针变量的地址存放在哪儿呢?

对于二级指针的运算呀,有下面这些情况哦:

  • *ppa 呢,是通过对 ppa 中的地址进行解引用操作,这样就能找到 pa 啦,所以 *ppa 其实访问的就是 pa 哦。比如下面这样的代码:
    int b = 20;
    *ppa = &b;  //等价于 pa = &b;
  • **ppa 呢,先是通过 *ppa 找到 pa,然后再对 pa 进行解引用操作,也就是 *pa,这样找到的就是 a 啦。像下面这样:
    **ppa = 30;
    //等价于*pa = 30;

    指针数组

那指针数组到底是指针还是数组呀?咱们可以类比一下哦,整型数组呢,是用来存放整型数据的数组,字符数组是存放字符的数组。那指针数组呀,就是存放指针的数组哦。

指针数组模拟二维数组

咱们来看看下面这段代码哈。

#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]);}printf("\n");}return 0;
}

 在这里呀,parr[i] 是用来访问 parr 数组的元素哦,而 parr[i] 找到的数组元素指向了整型一维数组,那么 parr[i][j] 就是整型一维数组中的元素啦。不过要注意哦,上述代码虽然模拟出了二维数组的效果,但实际上它并非完全等同于二维数组呢,因为每一行的数据在内存中并非是连续存放的哟。

通过以上对数组名、指针访问数组、数组传参、冒泡排序以及二级指针、指针数组等多方面知识的详细讲解与探讨,相信大家对这些 C 语言中重要的知识点有了更深入且清晰的理解。希望大家在后续的编程学习与实践中,能够灵活运用这些知识,不断提升自己的编程能力哦。                                                                                                                                                                           

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

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

相关文章

使用RabbitMQ

一、MQ是什么 MQ全称 Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信&#xff0c;主要功能业务解耦。 二、市面上常见的MQ产品 RabbitMQ、RocketMQ&#xff08;阿里的&#xff09;、Kafka 、…

大模型的实践应用33-关于大模型中的Qwen2与Llama3具体架构的差异全解析

大家好,我是微学AI,今天给大家介绍一下大模型的实践应用33-关于大模型中的Qwen2与Llama3具体架构的差异全解析。Qwen2模型与Llama3模型在架构上存在一些细微的差异,这些差异主要体现在注意力机制、模型尺寸相关参数以及嵌入层处理等方面。以下是对这些差异的详细分析。 文章…

NAT 技术如何解决 IP 地址短缺问题?

NAT 技术如何解决 IP 地址短缺问题&#xff1f; 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 随着互联网的普及和发展&#xff0c;IP 地址的需求量迅速增加。尤其是 IPv4 地址&…

kafka的备份策略:从备份到恢复

文章目录 一、全量备份二、增量备份三、全量恢复四、增量恢复 前言&#xff1a;Kafka的备份的单元是partition&#xff0c;也就是每个partition都都会有leader partiton和follow partiton。其中leader partition是用来进行和producer进行写交互&#xff0c;follow从leader副本进…

使用sam进行零样本、零学习的分割实践

参照&#xff1a;利用SAM实现自动标注_sam标注-CSDN博客&#xff0c;以及SAM&#xff08;分割一切模型&#xff09;的简单调用_sam使用-CSDN博客 sam简介&#xff1a; Segment Anything Model&#xff08;SAM&#xff09;是Meta公司于2023年发布的一种AI模型&#xff0c;它打破…

【Git】—— 使用git操作远程仓库(gitee)

目录 一、远程仓库常用命令 1、从远程仓库克隆项目 2、查看关联的远程仓库 3、添加关联的远程仓库 4、移除关联的远程仓库 5、将本地仓库推送到远程仓库 6、从远程仓库拉取项目 二、分支命令 1、查询分支 2、创建分支 3、切换分支 4、推送到远程分支 5、合并分支 …

攻防世界web新手第五题supersqli

这是题目&#xff0c;题目看起来像是sql注入的题&#xff0c;先试一下最常规的&#xff0c;输入1&#xff0c;回显正常 输入1‘&#xff0c;显示错误 尝试加上注释符号#或者–或者%23&#xff08;注释掉后面语句&#xff0c;使1后面的单引号与前面的单引号成功匹配就不会报错…

【MySQL】SQL 优化经验

1. 表的设计优化 参考依据&#xff1a;参考阿里开发手册嵩山版&#xff0c;其中有很多关于MySQL表设计的内容。类型选择&#xff1a;根据存储内容选择合适类型&#xff0c;如数值存储可选tinyint、bigint等&#xff0c;字符串可选varchar或text&#xff0c;根据内容长短选择合…

使用 .NET 6 或 .NET 8 上传大文件

如果您正在使用 .NET 6&#xff0c;并且它拒绝上传大文件&#xff0c;那么本文适合您。 我分享了一些处理大文件时需要牢记的建议&#xff0c;以及如何根据我们的需求配置我们的服务&#xff0c;并提供无限制的服务。 本文与 https://blog.csdn.net/hefeng_aspnet/arti…

STM32使用UART发送字符串与printf输出重定向

首先我们先看STM32F103C8T6的电路图 由图可知&#xff0c;其PA9和PA10引脚分别为UART的TX和RX(注意&#xff1a;这个电路图是错误的&#xff0c;应该是PA9是X而PA9是RX&#xff0c;我们看下图的官方文件可以看出)&#xff0c;那么接下来我们应该找到该引脚的定义是什么&#xf…

转运机器人推动制造业智能化转型升级

​在当今制造业智能化转型的浪潮中&#xff0c;技术创新成为企业脱颖而出的关键。富唯转运机器人凭借一系列先进技术&#xff0c;成为智能转型的卓越之选。 一体化 AMR 控制系统是富唯的一大亮点。它采用低代码流程搭建和配置模式&#xff0c;极大地缩短了部署时间。企业无需耗…

深度分析java 使用 proguard 如何解析混淆后的堆栈

经过proguard混淆过后&#xff0c;发生异常时堆栈也进行了混淆&#xff0c;那么如果获取的原始的堆栈呢&#xff1f;我们下面来看下 使用proguard 根据mapping文件直接解析 import proguard.obfuscate.MappingReader; import proguard.retrace.FrameInfo; import proguard.re…

基于JAVA+SpringBoot+Vue的影院订票系统

基于JAVASpringBootVue的影院订票系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1f345; 哈喽兄…

LeetCode 83 :删除排链表中的重复元素

题目&#xff1a; 地址&#xff1a;https://leetcode.cn/problems/remove-duplicates-from-sorted-list/ 方法一&#xff1a; 方法二&#xff1a; package com.zy.leetcode.LeetCode_04;/*** Author: zy* Date: 2024-12-25-15:19* Description: 删除排链表中的里复元素* …

金仓数据库-用户与角色对象权限访问的查看

数据库用户 创建用户 创建用户且设置密码 create user user01 password 123;\du 查看用户user01&#xff0c;可以看见创建成功 创建用户设置密码和不可继承 create user02 password 123 noinherit;修改用户的属性 设置用户的连接数 设置为1个 alter user user01 connect…

理解神经网络

神经网络是一种模拟人类大脑工作方式的计算模型&#xff0c;是深度学习和机器学习领域的基础。 基本原理 神经网络的基本原理是模拟人脑神经系统的功能&#xff0c;通过多个节点&#xff08;也叫神经元&#xff09;的连接和计算&#xff0c;实现非线性模型的组合和输出。每个…

联通光猫怎么自己改桥接模式?

环境&#xff1a; 联通光猫 ZXHN F677V9 硬件版本号 V9.0 软件版本号 V9.0.0P1T3 问题描述&#xff1a; 联通光猫怎么自己改桥接模式 家里用的是ZXHN F677V9 光猫&#xff0c;最近又搞了个软路由&#xff0c;想改桥接模式 解决方案&#xff1a; 1.拿到最新超级密码&…

Matrix-Breakout 2 Morpheus(找到第一个flag)

第一步 信息收集 (1)寻找靶场真实ip arp-scan -l 靶场真实 ip 为192.168.152.154 (2)探测端口及服务 nmap -p- -sV 192.168.52.135 第二步 开始渗透 (1)访问web服务 http://192.168.152.154and http://192.168.52.135:81 发现 81 端口的页面要登录 我们使用 dirb 扫描…

【CSS in Depth 2 精译_094】16.2:CSS 变换在动效中的应用(下)——导航菜单的文本标签“飞入”特效与交错渲染效果的实现

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第五部分 添加动效 ✔️【第 16 章 变换】 ✔️ 16.1 旋转、平移、缩放与倾斜 16.1.1 变换原点的更改16.1.2 多重变换的设置16.1.3 单个变换属性的设置 16.2 变换在动效中的应用 16.2.1 放大图标&am…