深度理解数组在内存中的存储以及指针数组和数组指针的概念

指针和数组我们之前也谈到了,我们说指针就是地址,对指针解引用就是找到这个地址所指向空间存储的数据。数组就是将相同数据类型的数据存储在一块连续的内存空间中的数据类型,指针和数组都是自定义数据类型。但这只是最基本的定义,如果想要理解的更深刻,我们必须深挖其中的底层原理。所以今天我们就来深挖一下,他们之间的区别和联系。

数组在内存中的存储方式是从低地址往高地址使用,定义一个数组会一次开辟一大块空间,然后将数据放进去。那么假如我们在main函数中定义一个数组int a[10],那么数组a肯定是存储在栈帧结构之中,而栈帧对空间是使用是从高地址往低地址使用,那么会有冲突和矛盾吗?

当然不会,毕竟你写了这么久程序,按照从低地址往高地址的访问次序从未出错。数组在栈帧中的开辟空间一次开辟40个字节(不强调都是Win32小端的系统环境)的空间,然后由低往高的使用,比如你开辟一个int类型的数据,它总共占4个字节,但是你访问它总是从低地址往高地址读取四个字节访问其中的数据,而不是栈帧中的从高地址向低地址使用。

既然一维数组搞清楚了,那么我们来聊聊二维数组。在这里首先澄清一个概念,数组在内存中是线性连续存储的,不管他是几维数组。为什么要聊聊这个呢?因为在早期接触二维数组这个概念的时候,我看到的书上对二维数组的图形描述是一个类似于矩阵的排布方式,这虽然在短时间内令我对数组的使用得到了一些方便,但是随着我的深入学习,这个概念越来越使人感到迷惑,如果二维数组的排布方式是矩阵,那么难道三维数组的排布是立体结构吗?那四维呢?五维呢?甚至更多维数组呢?所以这里特变强调,数组是线性连续排列的,不管是几维数组,就像下面这张图。
这里写图片描述

从这张图我们可以理解为不管是几维数组,都可以理解为一维数组。只是其中的元素发生了变化罢了,二维数组的每一个元素是一维数组,三维数组的每一个元素是二维数组。那么既然是数组可以存放数组,那么数组可以存放别的自定义类型吗?当然可以!往数组中存储结构体,那就是结构体数组,往数组中存放指针,那就是指针数组。所以由此我们可以看出,数组也是一个很灵活的自定义类型。

接下来,我们来看一段代码,加深一下对上面概念的理解:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int a[3][4];
    printf("%d\n", sizeof(a));//48
    printf("%d\n", sizeof(a[0][0]));//4
    printf("%d\n", sizeof(a[0]));//16
    printf("%d\n", sizeof(a[0] + 1));//4
    printf("%d\n", sizeof(a + 1));//4
    printf("%d\n", sizeof(&a[0] + 1));//4
    printf("%d\n", sizeof(*a));//16
    printf("%d\n", sizeof(a[3]));//16
    system("pause");
    return 0;
}

我已经用注释把运行结果写在后面了,接下来我们解释一下,为什么会出现这样的运行结果。
第一个,求出的是整个数组的大小,12个元素,每个元素四个字节,一共48字节。
第二个,求出的是第一个元素(数组)的第一个元素,4字节。
第三个,求出的是一个元素(数组)的大小,我们上面说过,把所有数组都当做一维数组来理解,那么他的第一个元素就是一个一维数组,而这个一维数组的大小是16字节。
第四个,求出的是第一个元素(数组)+1后的大小,我们在一维数组中,给一个数组名+1指的是第二个元素,所以这里指的就是第一个元素(数组)的第二个元素。
第五个,a+1指向的是第二个元素,但这里表示的是地址,所以大小仍然是4。
第六个,依然是一个地址,这里指向的是第二个元素(数组),也就是第二个一维数组。
第七个,对数组名解引用,得到的是第一个元素(数组),所以大小自然是16。
第八个,这里其实已经越界访问了,但是sizeof是关键字而并不是函数,关键字sizeof的求值是在编译的时候,所以这里虽然并不存在a[3]这个元素,但是这里也并没有真正访问a[3],而是仅仅根据数组的元素类型来确定其值。

在这里,强调两种情况,sizeof(数组名)&数组名时数组名被看成是整个数组,其他情况数组名都当做是数组首地址。

再澄清一个概念,单纯的数组和指针之间有什么联系吗?答案是没有,完全没有,虽然他们在某些访问方式上可以替换,但是数组是数组,指针是指针,两者完全是两个不同的自定义数据类型。

上面我们有提到指针数组,那么指针数组是什么?指针数组当然是数组啊!只是他里面存储的数据类型是指针罢了。他的表示形式如下:

int *arr[10];   //存储着整形指针的指针数组
char *arr1[10]; //存储着字符指针的指针数组
char **arr2[2]; //存储这二级字符指针的指针数组

那么什么是数组指针呢?既然数组也是一个类型,那么总有存储他的地址空间吧!所以就有了如下数据类型:数组指针。

int (*p)arr[10];

是不是有一点晕?刚接触到这两个概念的时候,我也是很懵逼的,这都什么玩意???但其实这些类型中的表述方法是有规律可寻的。

如果中间有文字表述,那么最后两个字就是他的数据类型,数组指针就是指针,指针数组就是数组。如果给出的是表达式,那么我们就看他的优先级,最先跟谁结合,那么他的数据类型就是什么类型。[]的优先级是要高于*的优先级,所以写数组指针的时候要加上()

既然有了指针数组这个东西,那么我们该怎么使用他呢?比如,在给函数传参的时候,指针数组该如何传参?

首先所有数组传参的时候都当做一维数组,其次,一维数组在传参的时候会降级成指向其内部类型的指针。

那么,指针数组传参自然就是一个二级指针喽~比如,这样:

void printp(int **p)
{
    printf("%p", p);
}
int main()
{
    int *arr[10];
    printp(arr);
    return 0;
}

二维数组的传参也是相同的,将其当做一维数组,指向其内部元素类型是一维数组。大概长这样:

void printp(int (*p)[5])
{
    printf("%p", p);
}

int main()
{
    int arr[10][5];
    printp(arr);
    system("pause");
    return 0;
}

还有最后一点,这里的数组传参虽然降级成指针,但是在传参过程中也是形成了临时变量的,这一点,大家千万不要忘记了~

猜你喜欢

转载自blog.csdn.net/zym1348010959/article/details/79714071