news 2026/9/26 11:15:38

C语言指针痛点--->指针数组与数组指针

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言指针痛点--->指针数组与数组指针

C语言指针痛点—>指针数组与数组指针

一、痛点之情景再现

我们学习C语言到了指针进阶阶段时,虽然知道这两个指针是什么名字,但是可能会分不清它们存的是指针(地址)还是元素,或者指向的是什么?

char*p1[4];// 指针数组char(*p2)[4];// 数组指针

我们根据上方代码片段可知,指针数组与数组指针这两个代码写法只差一对括号,但它们的含义、内存布局天差地别。我们在写代码时很容易将它们混淆。

二、破局核心:掌握“优先级”这把金钥匙

  1. C语言运算符优先级:在C语言中,[](数组下标)的优先级高于*(指针解引用)。
    2.记忆口诀:“谁离变量名近,变量就是什么类型”。带括号的,变量先和 * 结合,就是指针;不带括号的,变量先和 [] 结合,就是数组。

三、 指针数组(Array of Pointers)—— 本质是数组

//代码示例:char*p[4];

1.拆解分析:

因为 [] 优先级高,所以 p 先和 [4] 结合。结论:p 是一个数组。

然后看数组里装的是什么?是 char *(字符指针)。

2.本质:它是一个数组,里面存放了 4 个指针。
3.内存模型:想象一排连续的格子(数组),每个格子里装的不是字符,而是地址(指向其他字符串或变量的首地址)。它占用的内存是 4 * sizeof(char *)(32位系统是16字节,64位系统是32字节)。
图例:

4.完整可运行示例:下面这段代码演示了指针数组的定义、初始化与访问方式。

#include<stdio.h>intmain(){// 定义并初始化一个指针数组:数组里存放 3 个 char* 指针char*p[3]={"Hello","World","C"};// 访问:p[i] 取出第 i 个指针,再解引用得到字符串for(inti=0;i<3;i++){printf("p[%d] = %s\n",i,p[i]);}// 内存布局:p 是连续 3 个格子,每个格子存一个地址(指向字符串首字符)printf("p 占用的总内存 = %zu 字节\n",sizeof(p));// 3 * sizeof(char*)printf("p[0] 指向的地址 = %p\n",(void*)p[0]);printf("p[1] 指向的地址 = %p\n",(void*)p[1]);return0;}

运行输出示例:

p[0] = Hello p[1] = World p[2] = C p 占用的总内存 = 24 字节 p[0] 指向的地址 = 0x55a1c2d3e010 p[1] 指向的地址 = 0x55a1c2d3e016

说明:p本身是数组,p[i]取出的是第i个指针;sizeof(p)得到的是整个数组占用的字节数(3 * sizeof(char *),64 位系统下为 24 字节)。地址的具体数值每次运行可能不同,但相邻指针指向的地址通常相差 6 个字节(对应字符串长度)。

四、 数组指针(Pointer to an Array)—— 本质是指针

//代码示例:char(*p)[4];

1.拆解分析:

因为有 () 强制结合,p 先和 * 结合,形成(*p)。结论:p 是一个指针。

然后看指针指向什么?指向 char [4] 这个数组类型。

2.本质:它是一个指针,存放的是一个数组的地址(即整个数组的起始地址)。

3.内存模型:它不是一排格子,它只有一个格子(一个变量),里面存了一个地址,这个地址指向一个长度为4的字符数组。对它进行 p + 1,它不是移动1个字节,而是跨越整整4个字节(跳过一个完整的数组)。
图例:

4.完整可运行示例:下面这段代码演示了数组指针的定义、初始化与访问方式。

#include<stdio.h>intmain(){// 定义一个长度为 4 的整型数组intarr[4]={10,20,30,40};// 定义数组指针:p 指向整个 int[4] 数组int(*p)[4]=&arr;// 访问:(*p)[i] 先解引用得到数组,再按下标取元素for(inti=0;i<4;i++){printf("(*p)[%d] = %d\n",i,(*p)[i]);}// 指针运算:p + 1 会跨越整个数组(4 个 int),而不是 1 个 intprintf("p 指向的地址 = %p\n",(void*)p);printf("p + 1 指向的地址 = %p\n",(void*)(p+1));printf("地址差 = %ld 字节(即跨越 4 个 int)\n",(char*)(p+1)-(char*)p);return0;}

运行输出示例:

(*p)[0] = 10 (*p)[1] = 20 (*p)[2] = 30 (*p)[3] = 40 p 指向的地址 = 0x7ffd4a2b3c00 p + 1 指向的地址 = 0x7ffd4a2b3c10 地址差 = 16 字节(即跨越 4 个 int)

说明:p本身是一个指针,存放的是整个数组的起始地址;(*p)[i]等价于arr[i]。对p做p + 1,指针会跨越整个int[4]数组(64 位系统下int为 4 字节,共跨越 16 字节),这正是数组指针与普通指针最大的区别。

五、常见错误与排查

初学者在指针数组与数组指针上最容易踩坑,下面列举 3 个典型错误,每个都给出错误代码、运行结果、原因分析和正确写法。

错误 1:混淆声明写法

错误代码:

#include<stdio.h>intmain(){char*p[4];// 想定义数组指针,却写成了指针数组charstr[4]="abc";p=&str;// 编译报错:类型不匹配return0;}

运行结果:

error: assignment to expression with array type

原因分析:char *p[4]中[]优先级高于*,p先和[4]结合,本质是数组,不能作为指针被赋值。想定义数组指针必须加括号char (*p)[4]。

正确写法:

#include<stdio.h>intmain(){char(*p)[4];// 数组指针:p 指向整个 char[4] 数组charstr[4]="abc";p=&str;// 正确:p 存放数组的起始地址printf("%s\n",(*p));// 输出 abcreturn0;}

错误 2:错误解引用

错误代码:

#include<stdio.h>intmain(){intarr[4]={10,20,30,40};int(*p)[4]=&arr;printf("%d\n",*p);// 错误:*p 是数组,不是 intprintf("%d\n",p[0]);// 错误:p[0] 是数组,不是 intreturn0;}

运行结果:

warning: format ‘%d’ expects argument of type ‘int’, but argument has type ‘int *’

原因分析:数组指针解引用一次得到的是整个数组,而不是单个元素。要访问元素必须再按下标取,即(*p)[i]或p[0][i]。

正确写法:

#include<stdio.h>intmain(){intarr[4]={10,20,30,40};int(*p)[4]=&arr;for(inti=0;i<4;i++){printf("%d ",(*p)[i]);// 正确:先解引用得到数组,再按下标取元素}printf("\n");return0;}

错误 3:指针运算越界

错误代码:

#include<stdio.h>intmain(){intarr[4]={10,20,30,40};int(*p)[4]=&arr;// 错误:p + 1 已经越过整个数组,再解引用访问越界printf("%d\n",(*(p+1))[0]);return0;}

运行结果:

(*(p + 1))[0] = 0 // 未定义行为,可能输出垃圾值或崩溃

原因分析:数组指针做p + 1会跨越整个int[4]数组(16 字节),此时p + 1已指向数组末尾之后的内存,解引用属于越界访问,结果是未定义行为。

正确写法:

#include<stdio.h>intmain(){intarr[4]={10,20,30,40};int(*p)[4]=&arr;// 只在数组范围内访问for(inti=0;i<4;i++){printf("%d ",(*p)[i]);}printf("\n");// 若确实需要遍历多个数组,应使用数组指针数组intarr2[4]={50,60,70,80};int(*pa[2])[4]={&arr,&arr2};// 指针数组,每个元素指向一个 int[4]for(intj=0;j<2;j++){for(inti=0;i<4;i++){printf("%d ",(*pa[j])[i]);}printf("\n");}return0;}

小结:写代码时先按“谁离变量名近”判断类型,再决定如何解引用;做指针运算前务必确认指针是否仍在数组边界内。

六、总结

名称定义写法本质占用的内存谁先结合
指针数组char *p[4]数组4个指针的大小p 先和 [4] 结合
数组指针char (*p)[4]指针1个指针的大小p 先和 * 结合

指针数组本质上是数组,数组指针本质上是指针,做这类题画图还是很有必要的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 11:14:48

最高赢取价值万元大奖!仓颉生态创新开发挑战赛等你来挑战

从一个真实需求出发&#xff0c;你想用仓颉做出怎样的作品&#xff1f; 「仓颉开发者生态共建季」第三站——仓颉生态创新开发挑战赛正在进行中&#xff0c;报名及初赛作品提交进入最后两周。 2026 年 10 月 7 日 23:59:59&#xff08;北京时间&#xff09;&#xff0c;报名及…

作者头像 李华
网站建设 2026/9/26 11:12:40

2026年微生物取样瓶行业应用选型合规白皮书

2026年微生物取样瓶行业应用选型合规白皮书本白皮书基于2026年全行业微生物质控采样的实际落地需求&#xff0c;所有内容均来自各行业一线实验室、生产质控部门的实测反馈与合规文件要求&#xff0c;无夸大宣传内容&#xff0c;所有选型建议均以降低客户返工成本、保障检测结果…

作者头像 李华
网站建设 2026/9/26 11:12:35

初次了解c语言的自我感受

我是一名大一新生&#xff0c;通过对c语言的历史和它产生的作用的了解&#xff0c;我对它产生了浓厚的学习兴趣&#xff0c;因此想说一下个人看法 1.目标:希望日后能够熟练的掌握c语言 2.学习感受:随着我从一个完全不懂电脑的小白慢慢走进编程这个世界&#xff0c;我渐渐的有了…

作者头像 李华
网站建设 2026/9/26 11:10:37

Python agora-api-internal 包实战案例与常见错误

1. 引言agora-api-internal 是声网&#xff08;Agora&#xff09;内部生态中一个面向 Python 开发者的 API 封装包&#xff0c;主要用于简化对声网服务端接口的调用。它把鉴权、请求签名、参数校验、响应解析等重复性工作封装成统一入口&#xff0c;让开发者可以更专注于业务逻…

作者头像 李华