在C语言里,虽然数组名表示数组的地址,但在大多数情况下,数组名会自动转换为指向数组首元素的指针。
当对其进行&取地址时不发生退化,arr此刻表示的是数组整体,而&符进行取地址时会产生一个指针。
&arr 产生一个 “指向整个数组的指针”,类型为 int (*)[N]。
那么我们定义一个指向arr整个数组的指针。
int(*p)[3]=&arr;
int arr[3]={100,200,300};
int(*p)[3]=&arr;
printf("获取地址%p\\n", arr); // 获取地址00000000005FFE9C
printf("获取地址%p\\n", &arr); // 获取地址00000000005FFE9C
printf("获取地址%p\\n", p); //获取地址00000000005FFE9C
即:p是一个指针,指向一个存储三个int类型元素的数组(arr整体);
如果我想访问数组里面的元素,就需要一个运算符 * 解引用。
例如:
int a=100;
int *prt=&a;
printf("获取变量a里面的元素:%d",*prt);//100;
所以想获取就需要对指针解引用,即*p。
但是当我打印*p的值,获取的却是一串胡乱的数字,对其进行地址打印,输出的值和arr地址一样。
int arr[3]={100,200,300};
int(*p)[3]=&arr;
printf("获取地址%p\\n", arr); // 获取地址00000000005FFE9C
printf("获取地址%p\\n", &arr); // 获取地址00000000005FFE9C
printf("获取地址%p\\n", *p); // 获取地址00000000005FFE9C
printf("解引用获取内容%d\\n", *p);//解引用获取内容6291100
但是*prt就成功访问a变量里面元素,所以 * 对数组不起作用?
不,上面我们讲了&对arr会产生一个指针,arr此刻遇见&不会退回,而p就是这个指针。
*p 代表的是 p 所指向的那个完整的数组对象。
对比一下他们的字节长度
int arr[3]={100,200,300};
int(*p)[3]=&arr;
printf("sizeof获取长度是:%zu\\n",sizeof p);//8
printf("sizeof获取长度是:%zu\\n", sizeof *p);//12
printf("sizeof获取长度是:%zu\\n", sizeof arr);//12
printf("sizeof获取长度是:%zu\\n", sizeof &arr);//8
由此我们可以看出*p和arr长度一致,3(元素个数)X4字节(int类型)=12
所以:*p 在类型和内存布局上完全等价于数组名 arr。
单独看类型
-
p 的类型是 int (*)[5](这是一个指针类型)。
-
对 p 解引用,得到的 *p,其类型是 int[5](这是一个数组类型)。
所以此刻解引用*p已经不再是指针,而是一个数据类型。
那么兜兜转转一大圈,最后*p获取的是数组首元素地址,和p打印的值一样,不是多此一举吗?
别急,我们看两个例子!
1:* 改变了 +1 的步长(从 12 字节变为 4 字节)
int arr[3] = {10, 20, 30};
int (*p)[3] = &arr;
printf("p = %p\\n", p);
printf("p + 1 = %p\\n", p + 1);
// p+1 跳跃 12 字节(跳过整个数组)
printf("*p = %p\\n", *p);
printf("(*p) + 1 = %p\\n", (*p) + 1);
printf("第二个元素地址%p\\n", &arr[1]);
// (*p)+1 跳跃 4 字节(跳过 1 个 int 元素)
/* 输出示例:
p = 0x7ffc12345670
p + 1 = 0x7ffc1234567c (相差 12 字节)
*p = 0x7ffc12345670
(*p) + 1 = 0x7ffc12345674 (相差 4 字节)
第二个元素地址0x7ffc12345674
*/
// 如果不加 *,p+1 指向下一整个数组(越界);加了 *,(*p)+1 指向数组内的第 2 个元素。
*p+1的地址和arr数组元素第二个地址相同。
所以 * 不是没起作用,数组从一个整体地址,变成首元素地址,只是因为计算机对每一个产生连续地址的区域,起始地址规定为第一个元素的物理区域。
*p相当于打开数组这个区域总大门,来到首元素地址。
2:* 让下标访问变得有意义((*p)[1] 正确,p[1] 错误)
因为 p 指向整个数组,直接对 p 使用下标 p[1] 相当于 *(p+1),即“跳过整个数组去访问下一个不存在的数组”,这是越界错误。
例子2
要访问数组内的第 2 个元素(值为 20),必须先用 * 拿到数组本身,再下标:
int arr[3] = {10, 20, 30};
int (*p)[3] = &arr;
printf("%d\\n", (*p)[1]); // 输出:20 (正确,访问 arr[1])
// printf("%d\\n", p[1]); // 编译能通过,但实际访问的是 arr[3] 之后的越界内存,运行结果不可控(未定义行为)
// * 的存在,让你能从“数组指针”进入“数组内部”去取具体的元素。
* 符号在这个场景下的核心作用只有一个:改变“寻址的步长”,让你能够访问数组内部的元素。
具体来说,p 和 *p 虽然地址值相同,但它们的基类型不同,导致 +1 跳跃的距离天差地别。而 * 的作用,就是把“大块头指针”降级为“小块头指针”,从而让你能操作具体的元素。
到底怎么获取元素值?
对*p再进行解印就可以了啊!!!!
根据语法糖arr[下标]与*(指针+下标),去获取元素值。
int arr[3]={100,200,300};
int(*p)[3]=&arr;
printf("获取第一个元素值:%d\\n", *(*p + 0));//100
printf("获取第二个元素值:%d\\n",*(*p+1));//200
printf("获取第三个元素值:%d\\n", *(*p + 2));//300
总结:*p 在这里到底是什么身份?
-
身份:*p 就是原始数组 arr 的“替身”或“别名”。
-
为什么需要它:因为 p 自己太大(一步跨 12 字节),无法直接用于访问内部的单个 int。* 把这个“大指针”解引用成数组,然后数组在表达式中退化回 int*(一步跨 4 字节),这样你才能正确地遍历或取出里面的数值。
一句话总结:在这个语境下,* 符号的作用是 “降维” ——把“指向整个块的指针”变成“块内首元素的指针”,从而让后续的 +1 或 [] 操作作用于单个元素,而不是整个数组。
&与*之间的关系
在C语言里,“引用”一词通常作动词使用,意为“取地址”或“指向某块内存”。
在这个前提下,C语言中的“引用(取地址)”和“解引用(*)”是完全相反的两个操作:
-
引用(取地址 &):给一块内存空间“贴上门牌号”(获取地址)。
-
解引用(*):根据门牌号“敲门进屋”(根据地址访问内存)。
一、用代码事实证明“互逆关系”
1:引用(&)和解引用(*)互相抵消。
int a = 100; // 一块内存空间,存放数值 100
int *p = &a; // 引用(取地址):p 指向了 a 的内存空间
printf("%d\\n", *p); // 解引用:根据 p 存的门牌号,取出值 100
printf("%d\\n", *(&a)); // &a 先引用(取地址),* 再解引用,互为抵消,直接输出 100
// 验证互逆:对地址解引用,再取地址,回到原点
int *q = &(*p); // *p 取出 a 的值(100)?不对!注意优先级!
// 实际上:*p 是 a,&(*p) 就是 &a,所以 q 又等于 p
printf("%p\\n", p);
printf("%p\\n",q); // 输出完全相同
/* 输出:
100
100
0x7ffc12345670
0x7ffc12345670
*/
// & 和 * 在表达式中连续使用时,效果互相抵消。
2:引用(取地址)生成指针,解引用(*)消费指针。
int a = 10;
int *p; // 定义一个指针变量,准备存放地址
p = &a; // 引用(取地址)产生了一个地址值,存入 p
printf("%d\\n", *p); // 解引用(*)消费了 p 中的地址,取出了 10
// 若只有引用没有解引用,无法拿到数据
// 若只有解引用没有引用(野指针),程序崩溃
// & 负责“制造”地址,* 负责“使用”地址,二者成对出现才有意义。
二、“解引用”这个名字本身就揭示了联系
“解引用”的“解”字,在中文里意为“解开、拆解”。
-
你先通过 & 引用了内存(给了门牌号)。
-
然后通过 * 解开这个引用关系(顺着门牌号找到屋里)。
代码事实(用强制类型转换证明“解”的含义):
int a = 0x12345678;
int *p = &a; // p 引用(指向)了 a
// *p 解引用:把“门牌号”解开,得到了整块 int 空间
*p = 0xAAAAAAAA; // 通过解引用修改了 a
// 如果不用 *(不解引用),p 只是一个地址值,无法修改 a
// p = 0xBBBBBBBB; // 这改的是 p 自己存放的地址,跟 a 无关
printf("%X\\n", a); // 输出:AAAAAAAA
// “解引用”就是打破地址和内存之间的间接层,直接操作内存本体。
三、与之前“数组指针”话题的串联
int (*p)[3]:
-
p 是一个指针,它“引用”(指向)了整个数组 int[3] 的内存空间。
-
*p 就是“解引用”,它解开了这个“整块引用”,让你直接面对数组本身。
把引用/解引用概念套入数组指针:
int arr[3] = {10, 20, 30};
int (*p)[3] = &arr; // &arr 是“引用”整个数组,产生数组指针
// *p 是“解引用”,解开了对整块的引用,得到了数组本体(int[3])
printf("%zu\\n", sizeof(*p)); // 输出 12,证明解引用后拿到了整个数组
// 进一步验证:对解引用后的数组再取下标
printf("%d\\n", (*p)[1]); // 解引用后拿到数组,再取下标访问元素 20
return 0;
}
在整个指针链条中,“引用”负责产生指向大块的指针,“解引用”负责拆开指针,让你操作块内部。
一个小总结:根据上面的一通分析,可以得到几个等式;
&(*p) =p; *p=(*p+0)(语法糖) ;**p=第一个元素的值。

![P11246 [GESP202409 六级] 小杨和整数拆分-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261108100631-6af049a75855c-220x150.png)
![打卡信奥刷题(3678)用C++实现信奥题 P12274 [蓝桥杯 2024 国 Python B] 设计-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261108053830-6af00ad6bdf8d-220x150.png)

