0.1 本书的目标

在 C 语言的学习中,指针的运用被认为是最大的难关。

关于指针的学习,我们经常听到下面这样的建议:

“如果理解了计算机的内存和地址等概念,指针什么的就简单了。”

“因为 C 是低级语言,所以先学习汇编语言比较好。”

果真如此吗?

正如那些 C 语言入门书籍中提到的那样,变量被保存在内存的“某个地方”。为了标记变量在内存中的具体场所,C 语言在内存中给这些场所分配了编号(地址)。因此,大多数运行环境中,所谓的“指针变量”就是指保存变量地址的变量。

到此为止的说明,所有人都应该觉得很简单吧。

理解“指针就是地址”,可能是指针学习的必要条件,但不是充分条件。现在,我们只不过刚刚迈出了“万里长征的第一步”。

如果观察一下菜鸟们实际使用 C 指针的过程,就会发现他们往往会有如下困惑。

  • 声明指针变量 int *a;……到这里还挺像样的,可是当将这个变量作为指针使用时,依然悲剧地写成了*a

  • 给出 int &a;这样的声明(这里不是指 C++编程)。

  • 啥是“指向 int 的指针”?不是说指针就是地址吗?怎么还有“指向 int 的指针”,“指向 char 的指针”,难到它们还有什么不同吗?

  • 当学习到“给指针加 1,指针会前进 2 个字节或者 4 个字节”时,你可能会有这种疑问:“不是说指针是地址吗?这种情况下,难道指针不应该是前进 1 个字节吗?”

  • scanf()中,在使用%d 的情况下,变量之前需要加上&才能进行传递。为什么在使用%s 的时候,就可以不加&

  • 学习到将数组名赋给指针的时候,将指针和数组完全混为一谈,犯下“将没有分配内存区域的指针当做数组进行访问”或者“将指针赋给数组”这样的错误。

出现以上混乱的情形,并不是因为没有理解“指针就是地址”这样的概念。其实,真正导演这些悲剧的幕后黑手是:

  • C 语言奇怪的语法

  • 数组和指针之间微妙的兼容性

某些有一定经验的 C 程序员会觉得 C 的声明还是比较奇怪的。当然也有一些人可能并没有这种体会,但或多或少都有过下面的疑问。

  • C 的声明中,[]*的优先级高。因此,char *s[10]这样的声明意为“指向 char 的指针的数组”——搞反了吧?

  • 搞不明白 double (*p)[3];void (*func)(int a);这样的声明到底应该怎样阅读。

  • int *a 中,声明 a 为“指向 int 的指针”。可是表达式中的指针变量前*却代表其他意思。明明是同样的符号,意义为什么不同?

  • int *aint a[]在什么情况下可以互换?

  • 空的[]可以在什么地方使用,它又代表什么意思呢?

本书的编写就是为了回答以上这样的问题。

很坦白地说,我也是在使用了 C 语言好几年之后,才对 C 的声明语法大彻大悟的。

世间的人们大多不愿意承认自己比别人愚笨,所以总是习惯性地认为“实际上只有极少的人才能够精通 C 语言指针”,以此安慰一下自己那颗脆弱的心。

例如,你知道下面的事实吗?

  • 在引用数组中的元素时,其实 a[i]中的[]和数组毫无关系。

  • C 里面不存在多维数组。

如果你在书店里拿起这本书,翻看几页后心想:“什么呀?简直是奇谈怪论!”然后照原样把书轻轻地放回书架。那么你恰恰需要阅读这本书。

有人说:“因为 C 语言是模仿汇编语言的,要想理解指针,就必须理解内存和地址等概念。”你可能会认为:

“指针”是 C 语言所特有的、底层而邪恶的功能。

其实并不是这样的。确实,“C 指针”有着底层而邪恶的一面,但是,它又是构造链表和树等“数据结构”不可缺少的概念。如果没有指针,我们是做不出像样的应用程序的。所以,凡是真正成熟的开发语言,必定会存在指针,如 Pascal、Delphi、Lisp 和 Smalltalk 等,就连 Visual Basic 也存在指针。早期的 Perl 因为没有指针而饱受批评,从版本 5 开始也引入了指针的概念。当然,Java 也是有指针的。很遗憾,世上好像对此还存有根深蒂固的误解。

在本书中,我们将体验如何将指针真正地用于构造数据结构。

“指针”是成熟的编程语言必须具有的概念。

尽管如此,为什么 C 的指针却让人感觉格外地纠结呢?理由就是,C 语言混乱的语法,以及指针和数组之间奇怪的兼容性。

本书旨在阐明 C 语言混乱的语法,不但讲解了“C 特有的指针用法”,还针对和其他语言共有的“普遍的指针用法”进行了论述。

下面,让我们来看一下本书的具体结构。