单片机/C/C++八股:(二十二)数组名,以及和指针的区别(一/二维数组)
2026/6/12 16:37:01 网站建设 项目流程
上一篇下一篇
include <> 和 include “” 的区别


数组名,以及和指针的区别(一/二维数组)

常见误区:

  1. 数据名是第一个元素的首地址,是个常量
  2. 数据名是一个指针(是个常量指针)
  3. 数据名当函数参数的时候,传递的是指针
  4. 数组名相当于指针
  5. 数组名有特殊含义,是特殊指针
  6. 数组名不可以是左值
  7. &arrarr地址相同,所以类型一样

这些描述和解释都不准确,为什么?

1)数组名详解

一个一维数组的类型是:类型[长度],一个二维数组的类型是:类型[行数][列数]不是简单的字符型、整型、浮点型……

而数组名是该数组对象的标识符(代表一块连续内存的标识符,可以理解成常量地址),其本身并不属于任何类型:

  • 在一些表达式中使用数组名时,它的类型通常被视为该数组的类型。
  • 但在大多数情况下,数组名会退化为指向首元素的指针,类型变为指针(例如int*)。

1.1)一维数组

① 声明与类型:

intarr[5]={1,2,3,4,5};
  • arr是一个包含 5 个int的数组。数组的类型是int[5]arr作为标识符,在不退化时代表整个数组。

② 关键行为:

表达式类型含义说明
arrint*(退化后)指向数组第一个元素arr[0]的指针,值=第一个元素的地址
&arrint (*)[5]指向整个数组的指针,值=数组首地址(注意:不是int**
sizeof(arr)size_t返回5 * sizeof(int),因为未退化
arr + 1int*指向arr[1],步长为sizeof(int)
&arr + 1int (*)[5]地址增加5 * sizeof(int),跳过整个数组

⚠️这里尤其要区分arr&arr,虽然他们的值都是数组的起始地址(第一个元素的地址就是数组起始地址),但是两者的类型和含义完全不同,所以会导致指针算数行为完全不同(参考上述表格的后两行)。

示例:

printf("%p\n", (void*)arr); // 如 0x1000 printf("%p\n", (void*)(arr + 1)); // 0x1004 (+4 字节) printf("%p\n", (void*)&arr); // 0x1000 (与 arr 相同) printf("%p\n", (void*)(&arr + 1)); // 0x1014 (+20 字节)

③ 函数传参(退化发生):

voidfunc(inta[]){/* 等价于 int* a */}// 或voidfunc(int*a);
  • 此时a是指针,sizeof(a)返回指针大小(如 8),无法得知原数组长度
  • 必须额外传递长度:func(arr, 5);

1.2)二维数组

① 声明与类型:

intmat[3][4];// 3 行,每行 4 个 int
  • 数组的类型是int[3][4]。它是一个一维数组的数组:外层数组有 3 个元素,每个元素是int[4]类型。

  • 二维数组的内存分布为以arr[2][5]为例:

② 数组名的含义:

表达式类型含义
matint (*)[4](退化后)指向第 0 行(即mat[0],类型为int[4])的指针
&matint (*)[3][4]指向整个二维数组的指针
sizeof(mat)3*4*sizeof(int)整个二维数组大小(未退化)
sizeof(mat[0])4*sizeof(int)第一行的大小(mat[0]int[4]
mat + 1int (*)[4]指向第 1 行(地址 + 16 字节)
&mat + 1int (*)[3][4]地址 + 整个数组大小(48 字节)

示例:

printf("%p\n", (void*)mat); // 如 0x2000 printf("%p\n", (void*)(mat + 1)); // 0x2010 (+16 字节 = 4*int) printf("%p\n", (void*)&mat); // 0x2000 printf("%p\n", (void*)(&mat + 1)); // 0x2030 (+48 字节)

③ 元素访问:

  • mat[i][j]等价于*(*(mat + i) + j)
  • mat[i]是第i行,类型为int[4],在表达式中退化为int*

④ 函数传参(二维数组):

必须指定列数(因为编译器需知道每行宽度以计算偏移):

voidfunc(intm[][4],introws);// √ 推荐voidfunc(int(*m)[4],introws);// √ 等价写法(m 是指向 int[4] 的指针)voidfunc(int**m);// × 错误!不能接收二维数组(除非是动态分配的指针数组)

💡 原因:mat退化为int (*)[4],不是int**int**表示“指针的指针”,而二维数组是“连续内存块”。

1.3)数组名什么时候能取地址?

表达式是否左值?能否取地址?
arr✔️ 是(数组对象)✔️&arrint (*)[3][4]
arr[0]✔️ 是(子数组对象)✔️&arr[0]int (*)[4]
arr[0][0]✔️ 是(int 对象)✔️&arr[0][0]int*
arr + 1❌ 否(右值)❌ 非法
arr[0] + 1❌ 否(右值)❌ 非法
&arr[1]✔️ 是(左值)✔️ 类型int (*)[4]

关键:只有代表内存中实际对象的表达式(左值)才能取地址。指针算术的结果(如p + n)是右值,不能取地址。

正确等价关系(二维数组) :

想表达的含义正确写法值(地址)
第 0 行首地址arr&arr[0]&arr[0][0]
第 1 行首地址arr + 1&arr[1]&arr[1][0]
第 0 行第 1 列地址arr[0] + 1&arr[0][1]&arr[0][1]
整个数组地址&arrarr,但类型不同

2)数组名与指针的区别

类型:

  • 数组名:是一个常量地址,代表整个数组的首地址
  • 指针:是一个变量,存储某个地址值

可修改性:

  • 数组名:不能被赋值或修改(如arr = p;非法)
  • 指针:可以被重新赋值(如p = arr; p++;合法)

地址与值的含义

  • 对数组名取地址(&arr):
    • 类型是int (*)[5](指向整个数组的指针)
    • &arr + 1会跳过整个数组(5 个 int)
  • 对指针取地址(&p):
    • 类型是int **
    • p + 1跳过一个元素(1 个 int)

3)什么时候数组名会/不会退化为(常量)指针

在这些情况下,才能勉强说数组名是个常量指针

3.1)不会退化

数组名不退化的三大场景(C 标准规定),无论一维还是多维,以下情况都不会触发数组到指针的退化:

表达式(场景)是否退化结果
sizeof(数组名)→ \rightarrow作为sizeof的操作数整个数组所占字节数
&数组名→ \rightarrow作为一元&(取地址)的操作数指向数组的指针
char s[] = "hello"→ \rightarrow用于初始化字符数组的字符串字面量

其中第一种场景:在编译期间,数组名的类型会被编译器当作类型[长度],即代表整个数组,而sizeof函数是编译时运算符,sizeof(数组名)等价于sizeof(类型[长度])=长度*sizeof(类型)

3.2)会退化

大多数场景下,数组名会退化为指向首元素的指针,类型变为指针(例如int*):

表达式(场景)是否退化结果
void func(int arr[]){}→ \rightarrow作为函数参数传递时✔️此时arr是指针,sizeof(arr)返回指针大小(如 8)
a✔️指针
a + 1✔️指针

4)案例

#include"stdio.h"intmain(){inta[2][5]={1,2,3,4,5,6,7,8,9,10}int*ptr1=(int*)(&a+1);int*ptr2=(int*)(*(a+1));printf("%d,%d",*(ptr1-1),*(ptr2-1));return0;}

运行结果为:

10,5

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询