【C语言进阶】“数组指针”和“指针数组”都是啥跟啥?

描述

相信学习过C语言的童鞋,一定被这2个东西折腾过吧?究竟它们都是何方神圣呢?带着这个问题,笔者想通过本文给你一个清晰的答案。通过阅读本文,你将了解到以下内容:
 

  • 什么是数组指针?
  • 什么是指针数组?
  • 数组指针和指针数组有什么区别?
  • 使用指针数组的注意事项

什么是数组指针?


​ 【数组指针】,从字面意思上理解,就是一个【指针】;“数组”只是起到了修饰“指针”的作用,所以连起来的意思就是【指向数组的指针】。这一点与上一篇文章介绍 【函数指针】有异曲同工的含义。

​ 从C语言的语法上理解,数组指针的表示形式为:

//定义一个一维数组
int a[3];

//定义一个指针,指向一维数组的首地址
int *q = a;

//定义一个3行4列的二维数组
int b[3][4]; 

//定义一个数组指针,它指向二维数组的首地址
int (*p)[4] = b;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eQIadh0d-1661923181862)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ 从数组指针的形式上看,因为()运算符拥有最高优先级,所以整个语句优先被解释成一个指针;接着,这个指针再指向另一个数组的首地址,[x]接上该数组的列数,即得到如上的数组指针的定义。

​ 经过这个例子,我们可以看到,数组指针一般用于表达多维数组,对比起多维数组的表示,采用数组指针的形式可以在一定程度上理解难度减小了。比如,有了如上的数组指针定义后,这里b是个二维数组的数组名,相当于一个二级指针常量;p是一个指针变量,它指向包含5个int元素的一维数组,此时p的增量以它所指向的**一维数组长度为单位;*(**p+i)是一维数组b[i][0]的地址;(p+2)+3表示b[2][3]地址(第一行为0行,第一列为0列),(*(p+2)+3)表示b[2][3]的值。


什么是指针数组?


​ 【指针数组】,从字面意思上理解,它就是一个数组,只不过“指针”是用于修饰“数组”的,所以合起来理解就是:【一个数组元素存放的是指针的数组】。

​ 从C语言的语法上理解,【指针数组】的定义形式如下所示:

//定义一个char *的指针数组
char *p[5];

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qud5CpbF-1661923181865)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ 这里,它表示的含义是,一个由5个元素组成的一维数组,每个数组元素都是一个指针(地址)。访问数组的元素,我们都是采用 数组名[数组下标] 的形式访问的,那么【指针数组】也不例外,访问第一个元素,则是p[0];同理,p[2]表示的是第3个指针。


数组指针和指针数组有什么区别?


​ 从字面上看,确实很容易混淆两者的概念;我们理解的时候,需要注意名词谁先谁后。一般来说,在前面的名词是用于修饰后面的名词,而后面的名词决定了整个词组的性质。

【数组指针】:数组修饰指针,它的本质是一个指针;一般这个指针指向一个二维数组,形式为: int (*p)[M]。

【指针数组】:指针修饰数组,它的本质是一个数组;这个数组里面的元素,存放的都是指针,形式为: int *p[M]。

​ 如上定义中,第一个M表示二维数组的列数,第二个M表示的指针数组(一维数组)的元素个数。

​ 数据访问方面:

  • (*p)[0] 表示的是二维数组中的第一行的首地址;
  • p[0] 表示的指针数组的第一个元素,即第一个指针地址。

使用指针数组的注意事项


​ 这两个概念不但容易混淆,而且在使用过程中也是十分容易出错,曾经笔者在【指针数组】上摘过跟头。现将出错的教训分享给大家:

  • 求一个指针数组的中元素的个数,不是简单地使用sizeof

​ 比如有一个指针数组的定义:

char *p[5];

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRsvrkY7-1661923181868)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ 假设在编程平台上,一个指针所占用的地址空间是4个字节,即sizeof(char ) = 4;那么如果使用sizeof§去求这个指针数组所占用的地址空间时,求得的大小是45=20;而每个元素都是char *类型,所以求得指针元素的个数为: 20 / 4 = 5。

​ 于是,我们得出一个公式,求指针数组的元素个数:

//直接求得指针数组p的元素个数
cnt = sizeof(p) / sizeof(p[0]);

//很多时候,我们会定义一个宏来表示,形式如下:

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a)  (sizeof(a) / sizeof(a[0]))
#endif

//使用ARRAY_SIZE宏求指针数组p的元素个数
cnt = ARRAY_SIZE(p);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qwfXvoOo-1661923181869)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

  • 定义字符串类型的指针数组时,每个元素(字符串)注意用分号隔开,否则可能出现意想不到的事情

​ 假设有以下2个指针数组的定义:

const char *p1[] = 
{
    "12345",
    "23456",
    "34567",
    "45678",
    "56789",
};

const char *p2[] = 
{
    "12345",
    "23456"
    "34567",
    "45678"
    "56789",
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wt3RK9bs-1661923181874)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ 如果你不仔细看,你可能觉得p1和p2的定义是一致;仔细一看,原来p2中少了2个分号;而这2个分号一少,直接就导致p2的最终被编译器识别成的定义为:

//最终被识别的定义
const char *p2[] = 
{
    "12345",
    "2345634567",
    "4567856789",
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNeQ6PqX-1661923181875)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

​ 看到区别了吗?由于分号的缺失导致前后相邻的字符串被结合在一块,被连接成一个更长的字符串,而这种【拼接】是编译器自动识别完成的,它不会提示任何错误,因为在它看来根本就不是错误。对使用者而言,这样定义一改变,原本本应该为5个元素的字符串数组,就变成了3个字符串的数组,这简直就是灾难啊!!!


​ 以上就是笔者对【数组指针】和【指针数组】的实践,得出的切实理解,希望能够帮助大家更近一步地理解它们。以上提及观点,均为笔者本人的观点,如有纰漏之处,还望指正。感激不尽。
 

 审核编辑:汤梓红
 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分