再再论指针

上传人:小** 文档编号:89252951 上传时间:2019-05-22 格式:PDF 页数:20 大小:310.99KB
返回 下载 相关 举报
再再论指针_第1页
第1页 / 共20页
再再论指针_第2页
第2页 / 共20页
再再论指针_第3页
第3页 / 共20页
再再论指针_第4页
第4页 / 共20页
再再论指针_第5页
第5页 / 共20页
点击查看更多>>
资源描述

《再再论指针》由会员分享,可在线阅读,更多相关《再再论指针(20页珍藏版)》请在金锄头文库上搜索。

1、 再再论指针再再论指针-篇首语篇首语 指针是 C 语言规范里面一项核心内容,指针具有与生俱来的优势,利用指针可以写出许 多短小精悍、效率极高的代码,它是 C 语言一把无可替代的利器,凭着这把利器,C 语言与其 它高级语言相比至少在效率方面高人一筹。 但是, 由于指针的原理与使用方式跟人们通常的思维 习惯有较大的差别,造成了指针比 C 语言其它概念难理解得多,这使得对指针认识不足成为了 一种在 C 程序员中普遍存在的现象,这种不足必然导致程序员在指针的使用过程中不断遭受挫 折,挫折多了,指针俨然变成一道无法逾越的难关,恐惧感也就油然而生了。在恐惧感面前,某 些程序员甚至产生了要避免使用指针的念头

2、,这是非常不可取的。指针是如此犀利,正是它才使 得 C 语言威猛无比,如果就这样把它放弃了,那么 C 语言就算是白学了。我们应当让指针成为 你手中那把砍掉索伦手指上魔戒的举世无双的纳西尔圣剑,而不是成为你心中永远的魔戒。 本文的目的, 是希望通过跟各位朋友一起讨论关于指针的几个关键概念及常见问题, 以加 深对指针的理解。因此,本文并不是讲述形如 int *p、struct int i;float j; *p 等这些东西 是什么的文章,阅读本文的朋友最好对指针已经具有一定的使用经验,正因如此,笔者才给文章 起名叫再再论指针。笔者不敢奢望能够完全解开你心中的魔结,但如果通过阅读本文,能够 让你在日

3、后的指针使用过程中减少失误,那笔者就心满意足了。本文将讨论如下十个主题,读者 最好按主题的顺序一个一个地阅读,当然,如果你只对其中某个或某几个主题感兴趣,只看那几 个也未尝不可。 当你阅读本文后: 如果你有不同的意见,欢迎你在评论里留下自己的见解,笔者很乐意跟你一起讨论,共同 进步。 如果你觉得我说的全都是废话,那么恭喜你,你的指针已经毕业了。 如果你有太多不明白的地方, 那么我介绍你先找一些关于数组与指针的读物看看, 笔者推 荐你阅读一本叫C 与指针的书,看完后再回来继续思考你的问题。 1、什么是数组名?-一个让你吃惊的事实! 2、再一次吃惊!-数组的数组与多维数组的区别 3、数组的解剖学

4、4、 运算符的本质 5、指向数组的指针 6、“另类”数组 7、C语言声明详解 8、右左法则-复杂指针解析 9、指针与const 10、围绕p()与(*p)()的争论 11、再再论指针后记 2005.10.17 日补充 第一章第一章 什么是数组名?什么是数组名?-一个让你吃惊的事实!一个让你吃惊的事实! 数组是指针的基础, 多数人就是从数组的学习开始指针的旅程的。 下面我节选一些在各种 论坛和文章里经常见到的关于数组的文字: “一维数组是一级指针” “二维数组是二级指针” “数组名可以作为指针使用” “数组名就是的常量指针” “数组名就是的指针常量” 这些文字看起来非常熟悉吧?类似的文字还有许多

5、, 或许你就是经常说这些话的人呢。 不过非常 遗憾,这些文字都是错误的,实际上数组名永远都不会是指针!这个结论也许会让你震惊,但它 的确是事实。数组名、指针、地址这几个概念虽然是基础中的基础,但它们恰恰是被混淆和滥用 得最多的概念,把数组名说成指针,是一个概念性的错误,实质是混淆了指针与地址两个概念的 本质。俗话说得好:浅水淹死人。因此,在讨论数组之前,有必要先回过头来澄清一下什么是指 针,什么是地址,什么是数组名。 指针是 C 语言具有低级语言特征的最直接的证据。在汇编语言里面,指针的概念随处可 见。比如 SP,SP 寄存器又叫堆栈指针,它的值是地址,由于 SP 保存的是地址,并且 SP 的

6、值 是不断变化的,因此可以看作一个变量,而且是一个地址变量。地址也是 C 语言指针的值,C 语言的指针跟 SP 这样的寄存器虽然不完全一样,但原理却是相通的。C 语言的指针也是一种地 址变量,C89 明确规定,指针是一个保存对象地址的变量。这里要注意的是,指针跟地址概念 的不同,指针是一种地址变量,通常也叫指针变量,统称指针。而地址则是地址变量的值。 看到这里, 也许你会觉得, 这么简单的东西还用你来说吗?的确, 对于 p 与 我们可以用一个整数变量 int n 作实参来调用 fun,就是 fun(n);当然,也正如大家所熟悉的 那样,可以用一个整数常量例如 10 来做实参,就是 fun(10

7、);那么,按照第二个疑问的看法, 由于形参是一个整数变量,而 10 可以作为实参传递给 i,岂不就说明 10 是一个整数变量吗? 这显然是谬误。实际上,对于形参 i 来说,用来声明 i 的类型说明符 int,所起的作用是用来说 明需要传递给 i 一个整数,并非要求实参也是一个整数变量,i 真正所期望的,只是一个整数, 仅此而已,至于实参是什么,跟 i 没有任何关系,它才不管呢,只要能正确给 i 传递一个整数就 OK 了。当形参是指针的时候,所发生的事情跟这个是相同的。指针形参并没有要求实参也是一 个指针,它需要的是一个地址,谁能给予它一个地址?显然指针、地址常量和符号地址常量都能 满足这个要求

8、, 而数组名作为符号地址常量正是指针形参所需要的地址, 这个过程就跟把一个整 数赋值给一个整数变量一样简单! 在后面的章节中,笔者将严格地使用地址这一概念,该是地址时就用地址,该是指针时就 用指针,以免象其它教材那样给读者一个错误的暗示。 第二章第二章 再一次吃惊再一次吃惊-数组的数组与多维数组的区别数组的数组与多维数组的区别 看见这个题目,也许有些人就会嘀咕了:难道两者不是一样的吗?C 语言的多维数组不就 是数组的数组吗?不!两者是有区别的,而且还不小呢。首先看看两者的共同点: 1。内存映象一样。 2。数组引用方式一样,都是“数组名下标下标”。 3。数组名都是数组的首地址,都是一个符号地址常

9、量、一个右值。 由于两者的共同点主要反映在外部表现形式上,因此,从外部看来,数组的数组跟多维数 组似乎是一样的,这造成了 C 程序员对两者的区别长期以来模糊不清。但实际上,c 语言限于本 身的语言特性,实现的并非真正的多维数组,而是数组的数组。 数组的数组与多维数组的主要区别, 就在于数组的数组各维之间的内在关系是一种鲜明的 层级关系。上一维把下一维看作下一级数组,也就是数组嵌套。数组引用时需要层层解析,直到 最后一维。举个例,对于数组: int a789; 如果要访问元素 a456,首先就要计算第一维元素 4 的地址,也就是 a+4,由于是数组的 数组,元素 4 的值代表了一个数组,因此元素

10、 4 的值就是它所代表的那个数组的首地址,我们 用一个符号 address1 代表它,也就是 address1=*(a+4),接着计算第二维,显然元素 5 的 地址是 address1+5,其值也是一个数组的首地址,用 address2 表示它,就是 address2=*(address1+5),最后一维,由于已经到达了具体的元素,因此这个元素的地址是 address2+6,其值*(address2+6)是一个整数,把 address1 和 address2 分别代入相应表 达式,就成了: *(*(*(a+4)+5)+6); 这就是我们熟知的运算符的等价表达式。 而真正的多维数组并没有这么多“

11、束缚”,相比之下简单得多,由于各维之间不是这种复杂 的层级关系,元素 a456的偏移量可以这样直接获得:(4x8x9+5x9+6)xsizeof(int), 再加上数组的首地址 a 就是元素 a456的地址了。但是,c 语言的数组能够这样用首地址 加上(4x8x9+5x9+6)xsizeof(int)的形式来访问元素吗?显然是不行的。归根到底就在于 C 语言的地址数据类型不但有类型,还具有级别。就是这种层级关系造成了 C 语言只能用数组的 数组当作多维数组。如果 C 语言非得要实现真正的多维数组,那么地址与指针的概念就得重新 改写了。 第三章第三章 数组的解剖学数组的解剖学 这一章我们来讨论一

12、下数组的内涵, 对数组的内部构造进行一次解剖, 看看里面究竟隐藏 了什么秘密。 有了前面两章对数组名和 C 语言数组本质的澄清,再来理解这一章的内容,就容 易多了。 在下面的叙述中,笔者会用到一个运算符 sizeof,由于在不同的编译器和编译模式下, 对一个地址进行 sizeof 运算的结果有可能是不同的,为了方便讨论,我都假设地址长度为 4 个 字节。 多数教材在讲述数组的时候,都是把重点放在外部表现形式上,很少涉及数组的内部,只 告诉你如何做, 却忽视了为什么要这样做。 在解释的过程中, 还会列出各种各样的表达式, 例如: a、a+1、a0、a00、 (p+1)2这样就是合法的,因为 p+

13、1 的结果仍然是一个指针。 要注意的是, 虽然后缀表达式是一个“指向某类型的指针”, 但不要被这里所说的指针一词搞混了, 上面的规定不能反过来使用。还是以上面的例子为例,我们可以 pi这样使用 p,这是符合上述 规定的,但并不能因为指针 p 能够以 pi这种形式使用就认为 p 是一个数组,这就错误了,不 能反过来应用上述规则。 最后说一下编译器对 这个声明的形式跟人们所熟悉的 int *p 的形式大相庭径,初学者通常会感到迷惑,不理解的地 方大致有四个: 1。为什么会以这种形式声明? 2。(*p)应该如何理解? 3。为什么必须把第二维显式地声明? 4。为什么忽略第一维? 下面我们就一起逐个讨论

14、这四个问题: 1。这种形式是 C 标准的声明语法规定的,由于本章不是对标准的解释,只是对标准的应用,因 此笔者尽量以简洁的方式解释这个声明, 详细的讨论将在第七章进行。 C 标准的声明包含了两部 分: 声明: 声明说明符 初始化声明符表 opt (opt 的意思是可选) 在声明说明符里面有一项类型说明符,int 就是这种类型说明符。而初始化声明符表里面的其中 一种形式,就是: 直接声明符 常量表达式 opt (*p)9就是这种直接声明符加的形式。 2。p 左边的*在这里不是取值运算符,而是一个声明符,它指出 p 是一个指针。而()括号是不 能去掉的,如果去掉了,由于运算符优先级比*高,p 就会

15、先跟结合,这样 p 就变成了一个 指针数组,而不是指向数组的指针。 题外话: *p 还有一种用法,就是当*是取值运算符的时候,*p 是一个左值,表示一个变量,为什么*p 是 一个变量呢?也许有人会说,因为 int i, *p= 同样可以忽略第一维,而其它维必须指定上界。 最后再讨论一种很常见的对多维数组的错误理解, 有些人常常会以为, 二维数组就是二级 指针,这种错误的根源,来自于可以把一个二级指针 int *p 以 pij这种形式使用。首先把 数组称为指针就是错误的,第一章笔者已经说明了数组名是地址,不能理解为指针。第二,并非 能以 pij这种形式使用,那么 p 就是一个二维数组了,C 标准

16、对数组引用的规定,并没有指 定数组引用时运算符的左边必须是数组名,而可以是一个表达式。第三,这是一种“巧合”,归 根到底是由于 C 语言的数组实现是数组的嵌套同时 C 标准把运算符转换为类似*(*(a+i)+j) 这样的等价表达式造成的,那两个取值运算符“恰好”可以用于一个二级指针。第四,p 与 pi 并不具有数组类型,sizeof(p)和 sizeof(pi)的结果只是一个指针的大小 4 字节。而对于一个 真正的数组,p 与 pi都是具有数组类型的地址。 实际上,int *p 只是一个指向一维指针数组的指针,而不是指向二维数组的指针。同样 地,对于 n 级指针,都可以看作一个指向一维指针数组的指针,这个指针数组的元素都是 n-1 级指针。 第六章第六章 “另类另类”数组数组 动态数组与字符串常量可算是两种“另类”数组。 VLA 可变长数组并不为 C89 所支持,

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 商业/管理/HR > 管理学资料

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号