c++ 数组名和地址的理解

181 阅读3分钟

首先,数组名就是变量名,比如定义一个数组 int a[3],数组名是 a,变量名也是 a,说到底也就是一个标识符,一个变量拥有标识符和内存中的一片空间。

1、变量和数组在内存上的分布

一个整形变量 int n = 6,假设从地址为 1 的位置开始存储,在内存上的空间如下图所示,占有 4 个字节的存储空间。

变量 int n = 6 在内存空间的分布示意图

一个整形数组 int a[3] = {1, 2, 3},假设从地址为 1 的位置开始存储,占有 12 个字节的存储空间,在内存上的空间如下图所示。我们不难发现数组 a 的首地址和和 a[0] 的首地址都是地址 1。

变量 int a[3] = {1, 2, 3} 在内存空间的分别示意图

2、数组名和数组地址

int main()
{
    int a[3] = {1, 2, 3};
    auto p1 = a;
    auto p2 = &a;
    auto p3 = &a[0];
}
 

在上面代码中,定义了一个数组 int a[3] = {1, 2, 3},使用了三个 auto 类型的变量 p1, p2, p3,以此用来查看 a,&a,&a[0] 的类型。

各变量的值和类型

数组名 a 的理解

a 的类型是 int[3],跟我们的认知一样。第二列“值”,a 的值是 0x00eff748,是一个地址,从该地址解读出来的内容是 {1, 2, 3},a 的工作方式显然是间接取值,类似于指针,那么 a 类似于一个什么类型的指针呢?

p1 = a,编译器推断出的 p1 的类型为 int*,那么 a 应该也类似于 int* 类型,这一点可以通过查看 a+1 的值确定,a+1 的值是 0x00eff74c,比 a 的值大 4,恰好是一个 int 的大小。

a+1 的值

看来 a 类似于 int* 的指针是无疑了,但我们发现,对于地址 0x00eff748,通过 a 解读出来的是 {1, 2, 3},通过 p1 解读出来的却是 1,看来 a 也不完全是按照 int* 来解读的,毕竟 a 的类型是 int[3]。系统首先会从 a 的值里按照 int* 的方式解读出第一个元素的值,然后根据 int[3] 的 3,知道了后续的元素个数,将 a 的值加一个 int 的大小,得到了数组里第二个元素的地址,再次按照 int* 的方式解读出第二个元素,... 最终解读出了整个数组的内容 {1, 2, 3}。

&a 的理解

&a 的类型是 int[3] *, 这就是常说的指向数组的指针了,指向了整个数组的首地址,p2 的值、类型完全和 &a 的对应。它们解读出来的内容都是 {1, 2, 3},通过地址读取值的过程和通过数组名取值的过程是相似的,不再展开。

&a + 1 的值是 0x00eff754,而 &a = 0x00eff748,二者相差 12 = 4*3,正好是 int[3] 的大小。

&a + 1 的值和对应的内容

&a 执行 +1 后,指向的地址直接移动了一个数组的空间。如图,红框部分为数组 a[3],&a + 1 对应的是绿框部分的首地址。这与 a + 1 只是移动一个 int 的大小有很大的区别。

数组 a[3] 在内存中的分布

&a[0] 的理解

a[0] 就是数组的第一个元素,&a[0] 的地址就是数组第一个元素的地址,其类型就是 int*,这里就比较简单。

根据下面这张图,数组的地址和数组第一个元素的地址其实都是地址 1,二者是相同的,我们上面的实例也证实了这一点。但他们的类型不同,从首地址解读出来的结果就不相同,前者解读出来是 {1, 2, 3},而后者解读出的是第一个元素 1。

添加图片注释,不超过 140 字(可选)