news 2026/8/25 16:00:35

指针(4):C 语言数组与指针深度剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
指针(4):C 语言数组与指针深度剖析

指针(4):C 语言数组与指针深度剖析

  • 前言
  • 1. 数组名
    • 1.1 数组名的理解
    • 1.2 例外
      • 1.2.1 sizeof(数组名)
      • 1.2.2 &数组名
      • 1.2.3 总结
  • 2.使用指针访问数组
  • 3.一维数组传参本质
  • 总结

前言

很多人学 C 语言,数组和指针总是容易混淆,尤其是数组名到底是什么、指针怎么访问数组、数组传参到底发生了什么,这三块是 C 语言的重点,也是面试高频考点。本文围绕这三点把底层原理讲清楚。

1. 数组名

1.1 数组名的理解

我们知道,**“&”**能够取出一个元素的地址,那么如果我们想要取出一个数组中首元素的地址,我们就能够写出这样一个代码:

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

通过这段代码,我们就能够将数组arr首元素的地址取出。但其实根本不需要这么麻烦,数组名它本来就是地址,并且还是数组首元素的地址,我们可以通过代码来观察一下:

#include<stdio.h>intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};printf("&arr[0] = %p\n",&arr[0]);printf("arr = %p\n",arr);return0;}


通过代码及运行结果我们可以发现,数组首元素和数字名的地址的打印结果相同,这也就说明了数组名就是数组首元素的地址。

1.2 例外

1.2.1 sizeof(数组名)

通过上面的学习,我们知道了数组名就是数组首元素的地址,那么我们来看这样一段代码:

#include<stdio.h>intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};printf("%d",sizeof(arr));return0;}

这段代码的输出结果会是什么呢?,根据上面所学,数组名就是数组首元素的地址,那么它的输出结果就应该是4/8(一个地址的大小只可能是4或8),让我们把代码运行起来看看是否与我们的判断相符


这段代码的输出结果是40,并不是我们所预料中的4/8,那么这是为什么呢?难道说数组名其实并不是数组首元素的地址

其实数组名就是数组首元素的地址,只不过这是个例外:sizeof(数组名),sizeof 中放数组名,这里的数组名表示的是整个数组,计算的是整个数组的大小,单位是字节,因此上面代码的输出结果为40(整个数组10个 int 类型整数的大小)。

此外还有一个例外,那就是“&数组名”

1.2.2 &数组名

&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。(整个数组的地址和数组首元素的地址是有区别的)

通过文字描述可能不太好理解,接下来让我们通过一段代码来深入了解一下:

#include<stdio.h>intmain(){intarr[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);return0;}

让我们运行一下:


通过运行,我们可以发现,这三个的地址一摸一样,难道说整个数组的的地址和数组首元素的地址其实是一样的? 那么让我们再来看一段代码:

#include<stdio.h>intmain(){intarr[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);return0;}

这段代码的运行结果会是什么呢?让我们来观察一下


这⾥我们发现&arr[0]&arr[0]+1相差4个字节,arrarr+1相差4个字节,是因为&arr[0]arr都是⾸元素的地址,+1就是跳过个元素。

但是&arr&arr+1相差40个字节,这就是因为&arr取出的是数组的地址,+1操作是跳过整个数组的。

因此我们可以得出结论,&arrarr所对应的地址值是相同的,但他们的类型是完全不同的,&arr所对应的是整个地址,而arr所对应的是数组首元素的地址。

1.2.3 总结

数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:

  1. sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节

  2. &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)

除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地址。

2.使用指针访问数组

有了前面知识的学习,接下来我们就能够轻松的实现用指针来访问数组。

数组下标访问本质就是指针运算。

让我们通过代码来深入了解一下:

#include<stdio.h>intmain(){intarr[10]={0};int*p=arr;inti=0;intsz=sizeof(arr)/sizeof(arr[0]);for(i=0;i<sz;i++){scanf("%d",p+i);}for(i=0;i<sz;i++){printf("%d ",*(p+i));}return0;}

分析:

  1. 首先创建一个拥有10个元素的整形数组,并将其每个元素初始值赋值为0
  2. 创建一个int类型的指针p,将数组arr首元素的地址赋值给该指针p
  3. 创建一个int类型的变量sz,用来统计数组arr中元素的个数,sizeof(arr)算出数组整体的大小 ,sizeof(arr[0])计算数组首元素的大小(即数组每个元素的大小),两者相除算出数组中元素的个数。
  4. 创建循环,p+i(地址向后偏移i个元素类型大小)这本质上是一个地址,scanf(“%d”, p + i); 从键盘上读取一个数字存放在p+i地址所对应的内存单元当中,相当于存储在数组arr[ i ]中,循环总共读取10个数字。
  5. 循环打印 ,*(p+i)得到p+i地址所对应内存单元中存储的数据,其实也就等价于arr[ i ]。将数组arr中的元素全部打印出来。

这个代码搞明⽩后,我们再分析⼀下,数组名arr是数组⾸元素的地址,可以赋值给p,其实数组名arrp在这⾥是等价的。那我们可以使⽤arr[ i ]可以访问数组的元素,那p[ i ]是否也可以访问数组呢?

让我们来看下面一段代码:

#include<stdio.h>intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};int*p=arr;inti=0;intsz=sizeof(arr)/sizeof(arr[0]);for(i=0;i<sz;i++){printf("%d ",arr[i]);}printf("\n");for(i=0;i<sz;i++){printf("%d ",p[i]);}return0;}

运行结果:


通过观察我们可以发现,p[ i ]和 *(p + i )是等价的,因此同理我们可以得到arr[i]应该等价于 *(arr+i)

总结:
数组下标访问本质就是指针运算。
arr[i]等价于*(arr+i),也等价于*(i+arr),甚至可以写成i[arr]

3.一维数组传参本质

之前我们学习过了,数组是可以传递给函数的(比如说sizeof(数组名)),本小节我们来探讨一下数组传参的本质。

⾸先从⼀个问题开始,我们之前都是在函数外部计算数组的元素个数,那么我们可以把数组传给⼀个函数后,从函数内部求数组的元素个数吗?

要想完成上面的问题,我们可能会写出这样一段代码:

#include<stdio.h>voidtest(intarr[]){intsz2=sizeof(arr)/sizeof(arr[0]);printf("sz2 = %d\n",sz2);}intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};intsz1=sizeof(arr)/sizeof(arr[0]);printf("sz1 = %d\n",sz1);test(arr);return0;}

我们来简单分析一下这个函数:

  1. 首先创建一个 int 类型的数组 arr ,里面存放十个元素,然后通过 sizeof 在主函数中算出 sz1(即数组总元素) 的大小,并将其打印。
  2. 将数组 arr 传参给 test 函数,test 函数进行与 “1” 中同样的计算,算出 sz2 的大小,并打印。

根据上述分析,我们得到的结果应该是 sz1 = 10,sz2 = 10,那么事实果真如此吗,让我们将代码运行起来观察一下:


这个结果出乎我们的意料,那么这个结果是怎么来的呢?

这就要学习数组传参的本质了,上个小节我们学习了:数组名是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。

接下来让我们详细分析一下这段代码:

  1. 首先创建一个int类型的数组arr,里面存放十个元素,然后通过sizeof在主函数中算出sz1(即数组总元素) 的大小,并将其打印。
  2. 将数组 arr 传递给函数test(本质是将arr的首元素的地址传递给函数test)。
  3. test函数内部,sizeof (arr)计算的其实是 arr 这个指针的大小(在64位下大小为 8),sizeof( arr[0])计算的其实是arr [0]它自身类型的大小(即int类型的大小为4).
  4. 因此sizeof(arr) / sizeof(arr[0])结果应为8 / 4 = 2.

总结:函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr)计算的是⼀个地址的大小(单位字节)⽽不是数组的大小(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。

总结

  1. 数组名:绝大多数场景代表首元素地址;仅sizeof(数组名)&数组名代表整个数组;
  2. 指针访问数组arr[i]等价*(arr+i),下标是指针运算语法糖;数组≠指针,内存模型不一样;
  3. 一维数组传参:数组名退化为首元素地址,形参本质是指针,必须手动传入元素个数。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 15:54:59

Spark大数据分析与实战笔记(第九章 综合案例—Spark实时交易数据统计-02)

文章目录每日一句正能量第9章 综合案例—Spark实时交易数据统计章节概要9.3 模块开发—构建工程结构9.4 模块开发—构建订单系统9.4.1 模拟订单数据9.4.2 向Kafka集群发送订单数据9.5 模块开发 — 分析订单数据每日一句正能量 活在自己的热爱里&#xff0c;而不是别人的眼光里。…

作者头像 李华
网站建设 2026/8/25 15:44:37

rocketMQ proxy 延迟队列

Proxy 本身不做延迟队列的存储和调度 &#xff08;那是 Broker 端 ScheduleMessageService / TimerMessageStore 的职责&#xff09;&#xff0c;Proxy 只负责 延迟消息的「发送端属性填充 延迟级别换算」以及消费端识别 。相关实现集中在 gRPC 发送链路和配置里。 发送端&…

作者头像 李华
网站建设 2026/8/25 15:24:30

机器人破人类百米纪录:四足机器人的运动控制与工业落地

2026年&#xff0c;机器人再次刷新公众对运动能力的认知&#xff1a;一台四足机器人以10米/秒级别的速度完成百米冲刺&#xff0c;将“机器人破人类百米纪录”从实验室话题推向工程现实。对政企采购决策者而言&#xff0c;真正值得关注的不是单次赛跑成绩&#xff0c;而是这类高…

作者头像 李华