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 *a
和int a[]
在什么情况下可以互换?空的
[]
可以在什么地方使用,它又代表什么意思呢?
本书的编写就是为了回答以上这样的问题。
很坦白地说,我也是在使用了 C 语言好几年之后,才对 C 的声明语法大彻大悟的。
世间的人们大多不愿意承认自己比别人愚笨,所以总是习惯性地认为“实际上只有极少的人才能够精通 C 语言指针”,以此安慰一下自己那颗脆弱的心。
例如,你知道下面的事实吗?
在引用数组中的元素时,其实
a[i]
中的[]
和数组毫无关系。C 里面不存在多维数组。
如果你在书店里拿起这本书,翻看几页后心想:“什么呀?简直是奇谈怪论!”然后照原样把书轻轻地放回书架。那么你恰恰需要阅读这本书。
有人说:“因为 C 语言是模仿汇编语言的,要想理解指针,就必须理解内存和地址等概念。”你可能会认为:
“指针”是 C 语言所特有的、底层而邪恶的功能。
其实并不是这样的。确实,“C 指针”有着底层而邪恶的一面,但是,它又是构造链表和树等“数据结构”不可缺少的概念。如果没有指针,我们是做不出像样的应用程序的。所以,凡是真正成熟的开发语言,必定会存在指针,如 Pascal、Delphi、Lisp 和 Smalltalk 等,就连 Visual Basic 也存在指针。早期的 Perl 因为没有指针而饱受批评,从版本 5 开始也引入了指针的概念。当然,Java 也是有指针的。很遗憾,世上好像对此还存有根深蒂固的误解。
在本书中,我们将体验如何将指针真正地用于构造数据结构。
“指针”是成熟的编程语言必须具有的概念。
尽管如此,为什么 C 的指针却让人感觉格外地纠结呢?理由就是,C 语言混乱的语法,以及指针和数组之间奇怪的兼容性。
本书旨在阐明 C 语言混乱的语法,不但讲解了“C 特有的指针用法”,还针对和其他语言共有的“普遍的指针用法”进行了论述。
下面,让我们来看一下本书的具体结构。