一.数组名的理解
在前面的文章中,有这样的代码
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",arr);//%p打印地址return 0;
}
通过代码的输出结果可以看出,这两者的地址是一样的,因此我们可以得到如下结论:
数组名就是数组第一个元素的地址。
但是,有两个例外,arr表示的不是首元素的地址。
1.sizeof(数组名)-->sizeof中单独放数组名,这里的数组名代表整个数组。
2.&数组名-->这里的数组名表示整个数组,取出的是整个数组的地址。(把数组空间的地址全部取出)
除了这二者之外,任何地方使用数组名,数组名都代表首元素的地址。
现在我们来观察一下这段代码
我们发现,这三行代码打印出来的结果一模一样,那么他们的区别在哪里呢?我们刚刚说了&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]); //8 9 A B Cprintf("&arr[0]+1 = %p\n", &arr[0] + 1);//首元素地址+1.走一个int类型的距离printf("arr = %p\n", arr); //8 9 A B Cprintf("arr+1 = %p\n", arr + 1); //首元素地址+1.走一个int类型的距离printf("&arr = %p\n", &arr); //78+16=88 88+16==98 98+8=A0printf("&arr+1 = %p\n", &arr + 1);//走一整个数组的距离,走40return 0;
}
这⾥我们发现&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;ing sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;for (i = 0; i < sz; i++){scanf("%d", p + i);//也可以这样写}for (i = 0; i < sz; i++){printf("%d", *(p + i));//也可以这样写}return 0;
}
scanf函数中的p+i将数组中元素的地址传给scanf,然后就可以连续输入。printf同理。
现在给大家拓展一下
首先,通过我们刚刚的代码,我们发现*(p+i)等价于arr[i],而我们又知道,arr+1是跳过了一个地址,指向了下一个地址,是不是说,我们将其解引用,*(arr+1)也就等价于我们的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;
}
我们发现了一个奇怪的现象,为什么sz2==2呢?
这就需要我们学习数组传参的本质了,刚刚我们已经说过了:数组名是数组首元素的地址。
而我们在函数传参的时候,传的参数是数组名,也就是说!我们函数传参,传的是数组的首元素的地址!
既然如此,那么我们的函数的形参部分理论上也需要用一个指针变量来接受这个地址。所以说,我们的形参部分其实是一个指针变量!而一个地址(指针)在64位环境下是8个字节的。也就是说,在函数内部我们写sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。 即8/4==2.
既然如此,是不是我们的函数的形参部分也可以写成指针形式呢?答案是肯定的,现在我们来修改一下我们刚刚的代码。
void test(int* arr)//形参写成指针的形式。
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}
现在我们总结一下:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
四.二级指针
指针变量也是变量,是变量就有地址,那么指向指针变量的变量的地址存放在哪里呢?
这就是二级指针! 为了方便表示,一级指针写一次解引用操作符*,二级指针就写两次解引用操作符*,以此类推,三级指针乃至于N次指针而不过尔尔。
下面给大家画个图分析分析。其中a保存的是10,pa保存的是a的地址,*pa指向的是a;ppa保存的是pa的地址,而*ppa指向的是pa的内容,即p的地址。
这里需要我们大家注意的是,ppa指向的内容是一个地址,我们可以将ppa的类型解析分开一下
五.指针数组
指针数组,顾名思义,即保存指针的数组。
也就是说,一个保存指向整型变量的指针的数组的每个类型都是int*,如图所示。
指针数组的每个元素都是地址,而每个元素又都可以指向一块区域。
下面我们来实战一下。
六.指针数组模拟二维数组
#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[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;
}
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组的首元素,parr[i][j]就是整型⼀维数组中的元素。
上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为这三个数组的空间并不是连续的。