这话题已经不新鲜了,但牢骚总归还是要发的。
C 语言并不适合作为从未接触过计算机程序设计的人的第一门语言,除非他们已经做好了万全的心理准备,以及有合适的人引路。抱怨这个并不是因为 C 语言难学,而是因为 C 语言实际上很难教。
编程虽然是数学,但教学的过程一点也不数学:教数学的时候你可以把一些难以在初级阶段严谨证明的东西当作公理硬塞给学生、把「不严谨」但是管用的结论先抛出来让学生先能把手上的问题解开(比如高中阶段是没法教
于是就造成了一个很严重的问题:要塞给新手强行背诵的公理太多了。
问题自第一行起
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
再熟悉不过的开学第一课,但我相信没有人的第一课会展开讲第一行的 #include 是个什么东西。怎么把这个玩意搪塞过去是一门艺术。
我们当然可以简单地说「这代表我们要使用 stdio 这个库」然后之后再考虑去理清什么是所谓的库;或者更进一步地去理清什么是宏指令、宏的各种操作和坑等等。当然,更常见的选择是永远不理清这个东西,把它当成模版一样的东西贴上去就完事了。
C 语言是一门十分特殊的语言,它的编译阶段至少有四层:预编译、编译、汇编和链接。这当然是因为各种历史包袱导致的必然结果。其他高级语言至少会把这四层藏成两个阶段;或者压根就不设计这么多的编译阶段;或者更干脆地通过解释器把整个过程都藏起来:
print("Hello, world!")
能不能绕开第一行的 #include 呢?当然可以:
extern int puts(const char* s);
int main() {
puts("Hello, world!\n");
return 0;
}
比起解释预编译器是什么,也许解释 extern 和 const char* 是什么来得更容易一些?
问题在第三行还有
我说的不是把 main 打成 mian 的那个问题。
其实 int main() 这个写法是不标准的:这样会把 main 声明成一个具有不定长参数的函数。C 语言标准中规定了 main 函数只允许有以下两种形式:
int main(int argc, char **argv)int main(void)
编译器不给你报错只有以下两种可能:
- 你在用一个 C++ 编译器编译 C 代码
- 比如 MSVC(或者说 Visual Studio)
- 其实 VC60 也算数的,而且更有意思的是猜猜多少学校还在用 VC60 教 C 语言?
- 这是一个十分十分常见的 C 语言扩展
- 以及你可以挑刺说:这个规则只适用于函数声明,函数定义下空括号
()和(void)等价
- 以及你可以挑刺说:这个规则只适用于函数声明,函数定义下空括号
以及第三种非常美好但是不太可能发生的可能:
- 你的编译器默认使用 C23 或者更新的标准
问题潜藏在无数细节当中
这当然只是一个无伤大雅的小问题。但无数个无伤大雅的小问题叠加起来就是灾难本身。比如:
char不一定是 1 字节int不一定是 4 字节- 传递参数不一定非得用到栈
- 程序不一定是从上到下从左到右执行的
- 而且我说的不是前向声明这种东西
- 写出来的程序它是可以不执行的
- 而且并不是因为你没主动调用它
- 这个列表还能继续列下去
这些问题之所以存在,是因为——
C 语言不是一门存在于真空中的语言
所谓「存在于真空中的语言」即可以超脱于具体实现、可以纯粹通过逻辑推算得出结论的语言。如果它有一个现实世界的实现,那当然再好不过。
在这个定义下,C 语言是一门非常具体的语言。而所有具体的语言都有一个共性:用它写程序将会不可避免地需要处理现实世界中的各种「不完美」,而处理这些问题又需要对应的经验。
注意这里用的是经验而不是知识。经验和知识的最大不同就是:经验是无法被教给别人的。
骑自行车就是一种经验。你尽可以把所有关于自行车的知识讲给学生听,而听课的学生经过足够多的死记硬背也可以通过关于自行车的任何知识点考察。但是除非花费时间和勇气双手握把两脚离地摔那么几次,他是永远也没法通过光听课就学会骑自行车的。
但并不是所有的编程语言都必须是非常具体的语言。高德纳在他的《计算机程序设计艺术》中刻意引入了一个虚拟汇编 MIX 作为书中算法的实现;麻省理工的《计算机程序的构造和解释》则一直使用 LISP 作为程序语言。甚至计算机导论中介绍的图灵机如果不是因为编程太麻烦,也是一个可以存在于真空中的语言。
但是存在于真空中的语言通常都只能存在于真空中。MIX 汇编需要借助模拟器来运行;LISP 的现实实现则有好几种风味需要选择。短时间内,我暂时看不到用 OCaml 或者 Haskell 写 STM32F103 固件的可能性。
除了 C 我们还能选择什么
绝大多数人其实没得选,C 是最好的答案,因为——
C 语言不仅仅只是一门编程语言
它是计算机界的英语,所以身在工科的我们无一例外都得学。但就跟英语一样,学得怎么样纯靠个人自觉。
尽管英语有这样那样的缺陷,它现在就是事实上的通行语言。所以为了能和其他人交流,你只能选择学英语;哪怕有人跳出来说 Esperanto, Ido 或者 Lojban 不秒杀你英语,那也只是学有余力的人才会考虑的事情。
Python 也许会拯救世界
当然,对于那些并不需要和计算机底层打交道的人来说,Python 或许可以拯救他们于水火之中。毕竟,并不是所有从事计算机工作的人都一定要对 x86 架构了如指掌或者周末的时候以排查编译器 Bug 为乐;也并不是所有的计算机工作都需要阅读固件的反汇编结果来排查问题。既然我们需要做的是解决更高层面上的问题,那么使用更高级的编程语言来入门,或许是一个更好的方案。
每一门图灵完备的语言都是等价的,但每一门语言都有它适应的领域。每一把锤子都能敲东西,但蜡杆锤和叩诊锤肯定不会拿来敲同一种东西。
也许这也是一种战略储备
当然,教育总是具有迟滞性的。毕竟就算我这辈子可能都不会再有机会动车床,但我还是得搞定金工实习这门每个工科生必修的基础课程。
或许你可以说,每个计算机专业的人都得会写 C 语言程序就跟每个工科生都得会做小锤子一样,这也是一种居安思危的人才战略储备——当那一天真的来临时,我们已经做好了最基础的训练。
这并不是一个比喻,而是切实的数学构建。感兴趣的同学可以搜索「柯里-霍华德同构 (Curry-Howard Correspondence)」 ↩︎
正在加载评论……