指针4C 语言数组与指针深度剖析前言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*parr[0];通过这段代码我们就能够将数组arr首元素的地址取出。但其实根本不需要这么麻烦数组名它本来就是地址并且还是数组首元素的地址我们可以通过代码来观察一下#includestdio.hintmain(){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数组名通过上面的学习我们知道了数组名就是数组首元素的地址那么我们来看这样一段代码#includestdio.hintmain(){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 数组名数组名这里的数组名表示整个数组取出的是整个数组的地址。整个数组的地址和数组首元素的地址是有区别的通过文字描述可能不太好理解接下来让我们通过一段代码来深入了解一下#includestdio.hintmain(){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;}让我们运行一下通过运行我们可以发现这三个的地址一摸一样难道说整个数组的的地址和数组首元素的地址其实是一样的 那么让我们再来看一段代码#includestdio.hintmain(){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(arr1 %p\n,arr1);printf(arr %p\n,arr);printf(arr1 %p\n,arr1);return0;}这段代码的运行结果会是什么呢让我们来观察一下这⾥我们发现arr[0]和arr[0]1相差4个字节arr和arr1相差4个字节是因为arr[0]和arr都是⾸元素的地址1就是跳过⼀个元素。但是arr和arr1相差40个字节这就是因为arr取出的是数组的地址1操作是跳过整个数组的。因此我们可以得出结论arr和arr所对应的地址值是相同的但他们的类型是完全不同的arr所对应的是整个地址而arr所对应的是数组首元素的地址。1.2.3 总结数组名就是数组⾸元素(第⼀个元素)的地址是对的但是有两个例外sizeof(数组名)sizeof中单独放数组名这⾥的数组名表⽰整个数组计算的是整个数组的⼤⼩单位是字节数组名这⾥的数组名表⽰整个数组取出的是整个数组的地址整个数组的地址和数组⾸元素的地址是有区别的除此之外任何地⽅使⽤数组名数组名都表⽰⾸元素的地址。2.使用指针访问数组有了前面知识的学习接下来我们就能够轻松的实现用指针来访问数组。数组下标访问本质就是指针运算。让我们通过代码来深入了解一下#includestdio.hintmain(){intarr[10]{0};int*parr;inti0;intszsizeof(arr)/sizeof(arr[0]);for(i0;isz;i){scanf(%d,pi);}for(i0;isz;i){printf(%d ,*(pi));}return0;}分析首先创建一个拥有10个元素的整形数组并将其每个元素初始值赋值为0。创建一个int类型的指针p将数组arr首元素的地址赋值给该指针p。创建一个int类型的变量sz用来统计数组arr中元素的个数sizeofarr算出数组整体的大小 sizeofarr[0]计算数组首元素的大小即数组每个元素的大小两者相除算出数组中元素的个数。创建循环pi地址向后偏移i个元素类型大小这本质上是一个地址scanf(“%d”, p i); 从键盘上读取一个数字存放在pi地址所对应的内存单元当中相当于存储在数组arr[ i ]中循环总共读取10个数字。循环打印 *pi得到pi地址所对应内存单元中存储的数据其实也就等价于arr[ i ]。将数组arr中的元素全部打印出来。这个代码搞明⽩后我们再分析⼀下数组名arr是数组⾸元素的地址可以赋值给p其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[ i ]可以访问数组的元素那p[ i ]是否也可以访问数组呢让我们来看下面一段代码#includestdio.hintmain(){intarr[10]{1,2,3,4,5,6,7,8,9,10};int*parr;inti0;intszsizeof(arr)/sizeof(arr[0]);for(i0;isz;i){printf(%d ,arr[i]);}printf(\n);for(i0;isz;i){printf(%d ,p[i]);}return0;}运行结果通过观察我们可以发现p[ i ]和 *p i 是等价的因此同理我们可以得到arr[i]应该等价于 *(arri)总结数组下标访问本质就是指针运算。arr[i]等价于*(arri)也等价于*(iarr)甚至可以写成i[arr]。3.一维数组传参本质之前我们学习过了数组是可以传递给函数的比如说sizeof数组名本小节我们来探讨一下数组传参的本质。⾸先从⼀个问题开始我们之前都是在函数外部计算数组的元素个数那么我们可以把数组传给⼀个函数后从函数内部求数组的元素个数吗要想完成上面的问题我们可能会写出这样一段代码#includestdio.hvoidtest(intarr[]){intsz2sizeof(arr)/sizeof(arr[0]);printf(sz2 %d\n,sz2);}intmain(){intarr[10]{1,2,3,4,5,6,7,8,9,10};intsz1sizeof(arr)/sizeof(arr[0]);printf(sz1 %d\n,sz1);test(arr);return0;}我们来简单分析一下这个函数首先创建一个 int 类型的数组 arr 里面存放十个元素然后通过 sizeof 在主函数中算出 sz1即数组总元素 的大小并将其打印。将数组 arr 传参给 test 函数test 函数进行与 “1” 中同样的计算算出 sz2 的大小并打印。根据上述分析我们得到的结果应该是 sz1 10sz2 10那么事实果真如此吗让我们将代码运行起来观察一下这个结果出乎我们的意料那么这个结果是怎么来的呢这就要学习数组传参的本质了上个小节我们学习了数组名是数组首元素的地址那么在数组传参的时候传递的是数组名也就是说本质上数组传参传递的是数组首元素的地址。接下来让我们详细分析一下这段代码首先创建一个int类型的数组arr里面存放十个元素然后通过sizeof在主函数中算出sz1即数组总元素 的大小并将其打印。将数组 arr 传递给函数test本质是将arr的首元素的地址传递给函数test。在test函数内部sizeof (arr)计算的其实是 arr 这个指针的大小在64位下大小为 8sizeof arr[0]计算的其实是arr [0]它自身类型的大小即int类型的大小为4.因此sizeof(arr) / sizeof(arr[0])结果应为8 / 4 2.总结函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr)计算的是⼀个地址的大小单位字节⽽不是数组的大小单位字节。正是因为函数的参数部分是本质是指针所以在函数内部是没办法求的数组元素个数的。总结数组名绝大多数场景代表首元素地址仅sizeof(数组名)、数组名代表整个数组指针访问数组arr[i]等价*(arri)下标是指针运算语法糖数组≠指针内存模型不一样一维数组传参数组名退化为首元素地址形参本质是指针必须手动传入元素个数。