征服C指针(第2版)
上QQ阅读APP看书,第一时间看更新

第 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 *aint a[] 在什么情况下可以互换?
  • 空的 [] 可以在什么地方使用,代表的又是什么意思?

本书就是为了对这样的疑问给出解答而编写的。

坦白地说,我也是在使用 C 语言好几年之后,才真正明白声明的语法的。

我不愿意承认自己技不如人,所以总是认为实际上只有极少的人能够精通 C 语言中的声明。毕竟,我自己在掌握 C 语言声明之前,已经勤勤恳恳地码了好几年代码了。即便是自认为“C 语言很简单嘛,指针我也已经完全掌握了”的各位看官,其实也可能只是知其一不知其二。

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

  • 在引用数组中的元素时,a[i] 中的 [] 其实跟数组没半点关系。
  • C 语言中不存在多维数组。

如果你在书店看到这本书,翻看几页后心想“什么呀?简直是奇谈怪论!”而默默地又把书放回书架了,那么你恰恰需要阅读本书。

因为 C 语言是模仿汇编语言的低级语言,所以要想理解指针,就必须理解内存和地址的概念——当听到这种说法时,你可能会认为:指针是 C 语言所特有的、底层而邪恶的功能。

事实并非如此。C 指针的确有其底层而邪恶的一面,但一般来说,指针也是构造链表、树形结构等数据结构所不可或缺的概念。如果没有它,就没法写出像样的应用程序。因此,只要是成熟的编程语言,就毫无疑问地存在指针。Pascal、Java、C#、Lisp、Ruby 和 Python 都是如此。虽然 Java 在一开始的时候宣称“Java 没有指针”,不过那只是以讹传讹而已,如今已经没什么人相信这种话了。

本书也会涉及指针的真正用法——构造数据结构。

指针是成熟的编程语言中必不可少的一个概念。

那么,为什么 C 语言的指针格外地晦涩难懂呢?这是拜 C 语言混乱的声明语法,以及指针和数组之间微妙的兼容性所赐。

本书将阐明 C 语言混乱的语法,先讲解 C 语言特有的指针用法,然后讨论 C 语言和其他语言通用的普遍的指针用法。