第 0 章 本书目标与读者对象
0-1 本书目标
在 C 语言的学习中,指针被认为是最大的难点。
在学习指针时,我们经常会听到下面这样的建议。
“只要理解了计算机的内存和地址的概念,指针什么的就不在话下了。”
“因为 C 是低级语言,所以先学习汇编语言比较好。”
的确,在理解 C 指针时,如果事先对内存和地址的概念有所了解,就会快很多(至于是否需要学习汇编语言,我表示怀疑)。但是,仅懂得内存和地址的概念,是无法掌握指针的。理解内存和地址的概念可能是理解指针的必要条件,但并不是充分条件。这只是“万里长征”的第一步。
观察一下初学者实际使用 C 指针的过程,就会发现很多下面这样的问题。
- 用
int *a;
声明指针变量……到这里还挺像样的,可是在将这个指针变量当作指针使用时,依然悲剧地写成了*a
。 - 写出了
int &a;
这样的声明(←拜托,这又不是 C++)。 - 什么是“指向
int
的指针”?指针不就是地址吗?怎么还有“指向int
的指针”“指向char
的指针”,难道它们还有什么不同吗?” 当学习到“给指针加 1,指针会前进*2 个字节或 4 个字节”时,可能会有这样的疑问:“指针不就是地址吗?在这种情况下,难道指针不应该是前进 1 个字节吗?”
* 这里所谓的“前进”,严格来说是指指针向高地址方向移动。——译者注
“对于
scanf()
,在使用%d
的情况下,需要在变量前加上&
才能传递参数。可是,为什么在使用%s
时就可以不加&
呢?”- 当学习到将数组名赋给指针时,将数组和指针混为一谈,犯下“把未分配内存空间的指针当作数组访问”或者“试图把指针赋给数组名”这样的错误。
出现以上混乱情形,并不是因为没有理解“指针就是地址”,真正的原因是:
- 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 语言声明之前,已经勤勤恳恳地码了好几年代码了。即便是自认为“C 语言很简单嘛,指针我也已经完全掌握了”的各位看官,其实也可能只是知其一不知其二。
例如,你知道下面这些事实吗?
- 在引用数组中的元素时,
a[i]
中的[]
其实跟数组没半点关系。 - C 语言中不存在多维数组。
如果你在书店看到这本书,翻看几页后心想“什么呀?简直是奇谈怪论!”而默默地又把书放回书架了,那么你恰恰需要阅读本书。
因为 C 语言是模仿汇编语言的低级语言,所以要想理解指针,就必须理解内存和地址的概念——当听到这种说法时,你可能会认为:指针是 C 语言所特有的、底层而邪恶的功能。
事实并非如此。C 指针的确有其底层而邪恶的一面,但一般来说,指针也是构造链表、树形结构等数据结构所不可或缺的概念。如果没有它,就没法写出像样的应用程序。因此,只要是成熟的编程语言,就毫无疑问地存在指针。Pascal、Java、C#、Lisp、Ruby 和 Python 都是如此。虽然 Java 在一开始的时候宣称“Java 没有指针”,不过那只是以讹传讹而已,如今已经没什么人相信这种话了。
本书也会涉及指针的真正用法——构造数据结构。
指针是成熟的编程语言中必不可少的一个概念。
那么,为什么 C 语言的指针格外地晦涩难懂呢?这是拜 C 语言混乱的声明语法,以及指针和数组之间微妙的兼容性所赐。
本书将阐明 C 语言混乱的语法,先讲解 C 语言特有的指针用法,然后讨论 C 语言和其他语言通用的普遍的指针用法。