C语言取地址符什么时候加,什么时候不加?

新手上路,请多包涵

为什么前两个不加取地址符,后面两个加?

C 语言二维数组

// 看看二维数组的内存布局
printf("二维数组a的首地址=%p",a[0]);
printf("二维数组a[0]的地址=%p",a[0]);
printf("二维数组a[0][0]的地址=%p",&a[0][0]);
printf("二维数组a[0][1]的地址=%p",&a[0][1]);//int 类型加4,char类型加1
回复
阅读 521
2 个回答

需要取地址就加,不需要就不加。


数组不是指针。

类型为数组的值绝大部分情况下会被自动转换为指向其首元素的指针。(这让很多人误认为数组名就是指针。)


如果 a 是用 int a[2][2]; 定义的,那么:

1) a 是数组。它的元素的也是数组 (int[2])。它会被自动转换为指向第一个元素的指针,也就是指向一个一维数组的指针。所以它可以用 %p 打印,因为他会为自动转化为指针。

2) a[0]a 的首元素。它也是一个数组。a[0] 这个数组的元素是 int 。作为一个数组,它也会被转化为一个指针,这个指针指向其中的第一个 int ,因而它也可以被 %p 打印。

3) &a 是指针。它指向一个二维数组 a 。其值是 a 的地址。

4) &a[0] 是指针。它指向 a[0] (一个数组)。其值是 a[0] 的地址。

数组,数组的首元素在内存中是从同一个地址开始的,所以他们的地址通常是同一个数值。但是,指向他们的指针的类型是不同的。

对于数组 aa&a 在打印时数值通常是相同的,但是他们是不同的。a 是通过隐式类型转换得到的数组首元素的地址,&a 是通过取地址运算符得到的数组的地址。

要不要加 & ,就看是实际上想看的是哪一个地址。

更正说明:把 const int *const int ** 更正为 int * constint ** const。两种类型中的 const 修饰的东西不一样。把 const 放后面主要是声明变量本身的值不可更改。

如果 a 是一个二维数组,那么 a 可以看作是一个指针的指针类型。假如它是一个 int 型的二维数组,那可以看作 int ** const 类型。所以 a[0] 的类型是 int * const 指向第 1 行(序号 0)的地址,这也是是二维数组自己的地址。它和 printf("%p", a) 输出的值一样。

因为题上没有说 a1 是怎么声明的,也猜不出来,所以不清楚为什么需要取地址。


一般情况下,需要取地址的时候加 &,不需要取地址的时候不加。毕竟 & 就是一个取地址运算符。

然而,数组是一种比较特殊的数据类型,它可以自动转换为 const 指针,所以数组变量本身可以被看作一个指针,只不过这个指针是只读的,不可改写。

感谢 @fefe 指正。从来都直接把数组当指针用,虽然知道它和指针有所不同(比如 sizeof 的结果不同,数组带有维度和长度信息等),但是在写的时候很自然的就把它当指针了。回答使用用“看作”、“当作”、“可转换”来说明,请注意这不表示数组就是指针。具体请看 @fefe 的分析。

C/C++ 的数组是连续分配的空间,不管是几维数组。所以要初始化多维数组的时候,只有第一维可以省略,因为有其他维度的时候,第一维长度可以计算出来。

先说一维数组

那么,对于一维数组 a(比如 int a[] = {1, 2, 3, 4, 5, 6}) 来说:

  • 这个一维数组的地址是 a(它本身可看作 int * const) 或者 &a[0]a[0] 是第一个值,它的地址就是数组的地址)
  • 它的首元素是 a[0] 或者 *a
  • 它的第 3 个元素(序号 2)是 a[2] 或者 *(a + 2)
int a[] = { 1, 2, 3, 4, 5, 6};
printf("数组 a 的地址 = %p\n", a);
printf("数组 a 的首元素 = %d 或者 %d\n", a[0], *a);
printf("数组 a 的首元素地址 = %p\n", &a[0]);
printf("数组 a 的第 3 个元素 = %d 或者 %d\n", a[2], *(a + 2));

输出

数组 a 的地址 = 0x7fff338e2a90
数组 a 的首元素 = 1 或者 1
数组 a 的首元素地址 = 0x7fff338e2a90
数组 a 的第 3 个元素 = 3 或者 3

再说二维数组

如果初始化二维数组一个维度的长度都不给会报错

int a2[][] = { 1, 2, 3, 4, 5, 6 };
// main.cpp:16:5: error: declaration of 'a2' as multidimensional array must have bounds for all dimensions except the first
//   16 | int a2[][] = { 1, 2, 3, 4, 5, 6 };
//      |     ^~

上面的错误消息提示需要指定除第一维之外的其他维度的长度。下面几个都是是正确的

int a2[][2] = { { 1, 2}, {3, 4}, {5, 6} };   // 常用,不指定第一维长度
int a2[3][2] = { { 1, 2}, {3, 4}, {5, 6} };  // 两个维度都指定
int a2[][2] = { 1, 2, 3, 4, 5, 6 };   // 也常用,当一维数组来初始化,写起来简单

从正确的第三个例子可以看到二维(多维)数组和一维数组的关系。

这里 a2 可以看作一个 int**,但不全等,因为 int** 不含维度信息,但是 a2 含有第二维的长度是 2 这个信息。这样的话

int a2[][2] = { 1, 2, 3, 4, 5, 6 };
printf("1. 二维数组的地址 = %p\n", a2);
printf("2. 二维数组的首行地址 = %p\n", a2[0]);
printf("3. 二维数组的首元素地址 = %p\n", &a2[0][0]);
printf("4. 二维数组的首元素 = %d 或者 %d 或者 %d\n", a2[0][0], *a2[0], *(int*) a2);
printf("5. 二维数组第 3 个元素(或者说第 2 行第 1 个元素) =");
printf(" %d 或者 %d 或者 %d\n", a2[1][0], ((int*) a2)[2], *(((int*) a2) + 2));

输出

1. 二维数组的地址 = 0x7ffee47dc9a0
2. 二维数组的首行地址 = 0x7ffee47dc9a0
3. 二维数组的首元素地址 = 0x7ffee47dc9a0
4. 二维数组的首元素 = 1 或者 1 或者 1
5. 二维数组第 3 个元素(或者说第 2 行第 1 个元素) = 3 或者 3 或者 3

从 1~3 可以看出来,数组地址、首行地址和首元素地址其实都一样(数据起始位置一样,但类型不一样,第 1 个是 int ** const,后两个是 int *)。从 4、5 可以看出来对地址类型进行恰当的类型转换可以花样取值,毕竟内存地址都一样,只是用不用类型来做运算而已。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏