我觉得这个问题问得不够好。
因为谭浩强是一个高校教材的编写者,同程序员群体交集很小,却与许多开设计算机专业的高校有很大的联系,而且在现在,谭浩强的教材不属于 C/C++ 语言学习的先决条件,有的人就算没接触过谭浩强的教材,也可以借助其他的方式达到学习的目的,从事相应的工作。
所以,讨论谭浩强的“口碑”如何,意义不大。
这个问题倒不如这样问:“为何有经验的程序员都不会推荐谭浩强的教材”。
说老实话,我不会否认谭浩强对中国高等教育计算机专业的教学贡献,他的贡献集中在 20 世纪的 90 年代和 21 世纪的头十年,从 Fortran 到 Pascal 到 C/C++ 都有涉猎。那个时候我国才刚刚跨上“信息高速公路”有一段时间,而且电脑的普及率不如现在那么高,所以能够写出一本教科书是一件非常不容易的事情。
但是,谭老的教材已经出到 2022 年了,最新版的教材,其作者名与上一版本没有变,依然是谭浩强,而且前言的落款名字是谭浩强,时间地点是“2022 年 1 月于清华园”。所以根本不是某些人想象的那样,谭老年事已高就对教材不管不顾了。

可是,对于他现在编著的教材,我无法给他如同 20 年前一样非常正面的评价。
就拿我说的这本最新版《C 程序设计教程(第 4 版)》(ISBN:9787302610250)[1]来讲吧,在本书的前言部分,谭老说明了此次修订版的特点是“按照 C99 标准进行介绍”。
然而翻到正文第 8 页第 1.5 节,直接让我看破防了:
在编好一个 C 语言源程序后,怎样上机运行呢?一般要经过以下几个步骤。
(1)上机输入和编辑源程序。先进入 C 语言编译系统(一般是集成环境 IDE,如 Visual C++ 6.0)。建立一个文件,文件名自己指定,扩展名为 .c(如 rest.c 或 f.c)。通过键盘向此文件输入程序,并且认真检查有无错误,如发现有错误,要及时改正。这一工作称为“对源程序的编辑”。完成编辑后,将此源程序存放在自己指定的文件夹内(如果自己不专门指定,系统一般会自动把它存放在用户当前目录下)。
发现问题了吗?
且不说 IDE 的正确译法是“集成开发环境”,Visual C++ 6.0(下称 VC 6.0)根本不是支持 C99 标准的 IDE!
这个致命的错误直接说明教材的修订是“换汤不换药”。
那么有人要问了:“为什么 C99 标准的代码能在它上面编译成功?”
答案是,C99 新增特性的代码在 VC 6.0 上根本不可能编译成功,但教材恰好介绍的是 C99 与往前标准之间的公共部分,当然没什么问题了。
看 VC 6.0 的版本信息就知道为什么了:这个软件是 1998 年推出的,距今已有 24 年了,发布的时间正好在 C99 的标准之前,何况它对 C89 的标准也只是大部分支持的,就连微软都已经不在官网开放最后 Service Pack 6 的下载渠道了。

另外,读者应该看到图中是用虚拟机呈现的,运行在 Windows XP 下(其实图里的是 Windows Server 2003,和 XP 是同一个内核)。
事实上 VC 6.0 在 Vista 及以上的 Windows 系统根本就不能正常安装,想要正常安装是要大费一番周折的(包括安装程序开启兼容性模式、修改安装文件、在安装开始前勾掉一些组件);
就算安装成功了,也有兼容性隐患,也就是在打开工程和编译代码的过程中会时不时出错,经常会出现因无法识别 Workspace 而整个工程报废的问题。
由于 VC 6.0 是一个为 C++ 而设计的 IDE,因此很多学校的老师在教学中经常忽略了这样一个事实:它所创建的 Win32 Console Application 项目是一个 C++ 项目,而不是 C 项目。
如果你用心看如下的提示,就知道它创建的代码文件的扩展名是 .cpp 而不是 .c,甚至如果创建空白项目,只有添加 .cpp 的源代码文件,就没给 .c 任何选项。

虽然我知道有些人会杠,觉得就算这么做又不影响,只要能编译成功并运行就够了。
但计算机科学是要讲究严谨的,C 与 C++ 根本就不是一回事,一匹披着羊皮的狼远比一匹真正的狼更可怕。尤其是如果还用它再学 C++ 的话,会很容易产生一种“C++ 包括了 C 的全部”、“先学 C 才能学 C++”的错误认识。
事实上 C++ 之父早就对这一点解释过[2],这句话同时也侧面说明了 C 与 C++ 在设计上是有距离的:
C is almost a subset of C++, but it is not the best subset to learn first because C lacks the notational support, the type safety, and the easier-to-use standard library offered by C++ to simplify simple tasks.
(C 几乎是 C++ 的一个子集,但并不是首先学习的最佳子集,因为 C 缺乏符号支持、类型安全性和 C++ 提供的更易于使用的标准库来简化简单的任务。)
然后就是 IDE 的使用:VC 6.0 相比许多现代的 IDE,没有智能补全的特性,只有非常有限而且观感很难看的语法高亮。
如果出了错,也得要把构建信息的窗格调大,然后翻之前的文字才能看得清错误提示在哪里,不像某些 IDE 可以直接从错误中定位,告诉你错误在哪。

对比一下其他的 IDE,高下立判。
当然,你要问我 VC 6.0 的意义是什么,当然是怀旧了。因为可以保留给 B 站一些古董电脑的爱好者收藏,用来在老旧的计算机上编程。
但是在大学课堂里,这种东西我实在看不出有什么进步的意义,甚至与现在广泛采用的 C11 标准过于脱节。
所以,有这样一个问题:“为什么学习计算机 C 语言都建议使用 VC 6.0 呢?”
答案是这个问题的讨论前提根本就不成立。这样一个老掉牙而体验差的古董 IDE,怎么可能让学生学到真正的 C 语言呢?
说起来谭浩强已经用了这么多年的 VC 6.0,直到现在还没有剔除,而且本书已经提及了它至少 13 页(实际的应该更多,因为是在 macOS 的“预览”里用系统内置的 OCR),可见谭老是打算把它用到入土了。
不改这种“基本盘”也就算了,比起以往的教材,这次修订版倒是提了那么一点 Visual Studio 2010,算是一种进步,但在本书正文第 10 页看到了让我血压升高的内容:
为了编译、连接和运行 C 程序,必须要有相应的编译系统。目前使用的大多是集成环境(IDE)的。Visual C++ 是使用较广泛的集成环境,它把程序的编辑、编译、连接和运行等操作全部集中在一个界面上进行,功能丰富,使用方便,直观易用。如果用 Windows 操作系统,可以用 Visual Studio 2010 来对 C 程序进行编译和运行。本书的配套教材《C 程序设计教程(第 4 版)学习铺导》介绍了怎样对 C 程序进行编辑、编译和运行。请读者参照它上机运行本章中介绍的 3 个 C 程序,初步掌握上机的方法。
学会使用一种编译系统之后,在需要时学习和使用其他编译系统是不困难的。
也就是说,为了知道怎么去使用 Visual Studio 2010,还得要再买一本配套用书(当然配套的书我没买,但阅读了若干页感觉很一般)。
身为教材的编者,明明可以与时俱进地在教材里更新一下推荐的 IDE,却很鸡贼地放在了使用频率非常低的配套用书里,且不说有几个学生真的会为了一个教材的补充多花钱,这么做难道不是为师生徒增负担么……
另外,在阅读这本书时,会发现谭老自造了一些无厘头的概念。
比方说浮点型数(floating point number),谭老倒是提了正确的结论“浮点型数就是实数”,但往后的叙述里,却把浮点型数称为“实数”。
这种说法当然是有问题的:因为从后往前推就不成立。
谭老倒是让本书回避了高等数学的题目——
由于许多学校把 C 语言的教学安排在一年级,而学生还未学完高等数学,因此本书不包括有关高等数学知识的例题。
但是“实数”这个词,就算不是高等数学的重要概念,初等数学课堂里早就不知提了它多少遍了,很明显,实数的范围远比浮点型数据表示的数要大多了。
对于无限位数的小数,如果在 C 语言中按浮点型数表示,存储的是一个近似的结果,不是对这个数真值的直接反映。所以,虽然我可以用sqrt()函数计算\sqrt{2} 的值约为 1.414214,但我绝对不能说 1.414214 = \sqrt{2},这是一个最基本的常识!
更进一步,IEEE 754 标准规定了对于浮点型数的几种舍入处理方式,但无论是怎样形式的舍入,对于无限小数来说,可以在精度很高的情况下接近真值,但永远不会等于真值。
(经过考证,Fortran 语言里倒是有“实数型”这一类型,其关键字为 REAL,其余的语言里没有,所以严重怀疑谭老是搞混了,或者直接拿教 Fortran 的思维方式教 C 语言)
比方说宏定义(Macro Definition),谭老虽然提到了这个字眼,但在正文中一直没有废除“符号常量”的说法。
殊不知宏定义的内涵远比“符号常量”的字面意思要多得多,“符号常量”这种说法没有很好地体现宏定义的“预处理”作用,而且也不会考虑到初学者会不会把它和常量这个概念混淆。我举个例子:
#include <stdio.h>
#define pow(x) x * x
int main(int argc, char *argv[]) {
int num = 3;
printf("%d", pow(num));
return 0;
}这个代码运行的结果是 9。显然pow(x)是宏定义,编译器在编译时,将pow() 替换为x * x的运算 ,将x替换为对应的值num,也就是 3。这种宏定义虽然有“符号”的意味,但不能称之为“常量”。
还有,谭老把键盘上经常输入的半角双引号" 叫做“双撇号”。
然而对照 Unicode 官网发布的 Unicode 15.0 代码表[3],真正符合“双撇号”意思的英文名称是“double prime”,符号为′′, 其 Unicode 编号为U+2033。
之所以“prime”有符号“撇”的意思,这里贴出韦氏词典(Merriam-Webster Dictionary)中的其中一个释义[4]:
the symbol ′ used to distinguish arbitrary characters (such as a and a′), to indicate a specific unit (such as feet or minutes of time or angular measure), or to indicate the derivative of a function (such as p′ or f′(x))
此外韦氏词典还解释了什么是“double prime”[5]:
the symbol ″ used to distinguish arbitrary characters (such as a, a′, and a″), to indicate a specific unit (such as inches), or to indicate the second derivative of a function (such as p″ or f″(x))
但是平常键盘上打的那个半角双引号,其 Unicode 编号是U+0022, 无论如何和“双撇号”完全不是一个东西,而且就算把“双撇号”复制进 IDE 里当引号使用,也会让编译器报错而不能通过。

总之我是真不知道他到底是怎么想出来的这个名字……
另外,本书虽然修订了一些错误(因此已经改正的错误在本回答中不再提及),特别是被许多人津津乐道的自增自减运算符,可喜的是这一版中,谭老不再强调它的重要性,而是以如下的话叙述:
专业人员喜欢在使用 ++ 和 -- 运算符时,采取一些技巧,以体现程序的专业性,但使用 ++ 和 -- 运算符时,常常会出现一些人们想不到的副作用,最好只用最简单的形式,如 i++,i--,++i,--i,而且把它作为独立的表达式,不要把它作为一个表达式的组成部分。
严格地说,“专业人员”是不会在自增自减运算符上采用“技巧”的,会使得代码的可读性降低,业务逻辑混乱,也不容易维护,而且谭老对“未定义行为”并没有一个科学的解释,没把“副作用”是什么展现出来(因为“未定义行为”这个问题与编译器的处理方式有关),但这个改变依然值得一些肯定。
同样的,本书仍然有一些细节错误,而且有的是好几个版本都不改的。
比如作者虽然说:
在选择变量名和其他标识符时,应注意做到“见名知义”,即选有含义的英文单词(或其缩写)作标识符,如 count,day,month,class,total,country 等。
然而全书看遍后,我只发现了极少数的例题做到了这一要求,大部分的变量名称都非常“简洁”,比如“a”、“b”、“c”、“i”。这种变量名在生产环境下是很不受欢迎的。
又比如:
经过编译所得到的二进制目标文件(扩展名为 .obj)还不能供计算机直接执行。前已说明:一个程序可能包含若干源程序文件,而编译是以源程序文件为对象的,一次编译只能得到与一个源程序文件相对应的目标文件(也称目标模块),它只是整个程序的一部分。必须把所有编译后得到的目标模块连接装配起来,再与函数库等系统资源相连接成一个整体,生成一个可供计算机执行的目标程序,称为可执行程序(executive program),其文件扩展名一般为 .exe,如 test.exe 或 f.exe 等。
事实上这段话只呈现了 VC 6.0 编译器的编译过程,既然要本着与时俱进的原则,更应该介绍的是 gcc 编译器如何编译 C 程序。
假如要编译一个文件叫做demo.c,这个文件被 gcc 编译器编译的时候,会经历如下四个阶段:
①预处理(pre-processing)阶段:对原有的 .c 代码包含头文件,进行宏定义的替换,并删去注释,如有条件代码,则做相应的准备,最后生成一个预处理文件 .i。以下为执行该阶段的命令:
gcc -E demo.c -o demo.i②编译(compiling)阶段:对代码进行词法分析和语法分析,在确认代码没有语法问题后,将其翻译成等价的中间代码表示或汇编代码。以下为执行该阶段的命令:
gcc -S demo.i -o demo.s③汇编(assembling)阶段:将编译阶段生成的 .s 文件转换成目标的机器语言代码。以下为执行该阶段的命令:
gcc -c demo.s -o demo.o④链接(linking)阶段:生成的目标机器语言代码不能直接执行,因为可能调用了某个库函数,或者使用了其他的变量或者函数。
可执行文件中的函数调用有动态链接和静态链接两种。动态链接是让程序执行的过程中去寻找要链接的内容,文件较小,占用内存低;而静态链接会把库文件的代码全部加入到可执行文件中,文件较大,但静态链接库的执行速度较快。
以下为执行该过程的命令,Windows 平台需要指定扩展名为 .exe,如果是静态链接,则命令最末尾要加上-static:
gcc demo.o -o demo至此,整个编译过程才算完整结束。
而且,这段话还有一个问题,并不是所有的可执行文件都是 .exe,因为这里有个大前提:Windows 平台,.exe 的格式为 PE 格式。如果用过 macOS 和 Linux,就知道它们也有自己的可执行格式,分别为 Mach-O 和 ELF。
正文第 115 页有个低级错误:
程序分析:根据常识,偶数不是素数,所以只对奇数进行测试,在外层的 for 语句中,用 m = m + 2 使 m 每次增值 2。n 的作用是累计输出素数的个数,控制每行输出 10 个数据。
“偶数不是素数”?数字 2 觉得很淦。
关于浮点型数的介绍,正文第 42 页的这句话有点鬼扯了:
C 编译系统把所有的浮点型常量都按双精度处理,分配 8 字节。这是为了使运算能得到较高的精度。
无独有偶,正文第 46 页也有:
即使是两个 float 型数据相加,也先都转换为 double 型,然后再相加
首先既然是按 C99 标准介绍,那么就应该知道 C99 引入了浮点复数的类型,分为float complex、double complex和 long double complex,它们是定义在complex.h头文件里的。
虽然浮点复数常量属于浮点型常量,但根本不会按双精度处理。而且 float 类型转为 double 类型运算这个规则,早在 C89 就已经被废止了,但是万恶的 VC 6.0 当然没有体现这一点。
我寻思既然作者在正文第 41 页提到了 ANSI C,那么就应该知道 ANSI C 手册的这样一个规定:只有当操作数类型不同的时候,编译器会考虑朝着精度更高、长度更长的方向转换。[6]
但事实上,两个同为float类型的数(当然都属于浮点型数)相加减乘除运算时,由于根本不存在格式转换,所以编译器不可能把它们转换为double类型!
……
写得太累了,大概就整理到这里吧。当然,这本书存在的问题肯定不止这些,期待有高人能够继续补充。
所以,回到开头,为什么有经验的程序员不会推荐谭浩强编著的教材来入门 C 语言,原因有如下几点:
- 书中仍存在不严谨的漏洞
- 通篇并未贯彻 C99 的标准,不利于新手对 C 语言形成一个完整的认识
- 代码的风格较粗劣,与工作实际相脱节
再回答一下这个问题:“为什么现在国内各大高校仍选用谭浩强的《C 程序设计》为教材?”
实际上好的学校早已不采用谭氏教材,虽然很多计算机专业或者计算机相关专业还是在用的,甚至有的与计算机不相关的专业(比如某些学校的机械专业)也在用。
能用谭氏教材,很大一部分是为了任课老师的教学任务图方便,其次是为了保证作为中文教材的“权威”而不翻车——谭氏教材的特点是为应试教育服务的,所以对任课老师来说,这个教材的教案和课后习题答案是全网最好找的,考点清晰,出考卷也非常容易,不会在教学的时候讲崩。
如果换成国外的教材当然也可以,但如果不是北大、清华那种顶尖的环境,可能会吃力不讨好,因为现在功利的学术环境,决定了对任课老师而言,只有躺平和过好自己的生活才是好的选择,那么这份矛盾,必然会无条件转移到学生身上来——课堂上学的云里雾里,课后得花时间去补。
对大一的学生来说,读到这里可能会打破你对大学生活的美好幻想,可是这就是现实,而且越往后你越会发现这一点。
但不管遇到什么样的老师,无论课堂上是否真的对你负责,该怎样应付平时成绩和考试是一方面,如何为自己的未来做准备更是一方面。这方面要坚持两点论与重点论的统一,课堂上的东西非常基础,如果学不好,可能会让成绩单不够光彩,对以后的深造是有影响的;另外,如果只把自己的一切押在课堂上,没有在课外掌握些什么,恐怕四年学出来也不会有什么好的结果。
所以我的建议是,如果手上的教材不适合自己,那么就去看那些有用的参考书和网络资源(比如 B 站的网课),顺带锻炼自己的自学能力。毕竟现在的信息还是很发达的,还有搜索引擎方面不建议用某度,因为搜出来的很多都是广告,用必应可能会有更好的体验。
另外,不要误会了,我说这些话不是来为偷懒找借口的——尽管我说了这么多教材本身有问题的地方,但你的学习能力不能因为教材而堕落。
这个回答本来到此结束的,但我还想补充下,以免有些人曲解我的动机:
中国高校的教材编排体系,相比国外落后是个不争的事实,这一点很多学者和博士研究生都体会过。
我实在难以置信的是,某些人羡慕着像稚辉君那样的“天才少年”取得的过人成绩,感叹现在的大学教育相比社会的脱节之处,但一碰到给高校教材挑刺的言论却坐不住了,尤其是谭氏 C 语言教材,更是辩解说,“教材即使有错,他老人家没错”、“错的不是教材,是学校的教法问题”。
可是,这样的言论在逻辑上就讲不通。本来学生在入学之前交了学费,其中就有教材费,那么一个教材要想更接地气,变得更完善,不接受批评是不行的。
更何况谭浩强的教材都已经发展到了 2022 年,已经不再是那种本可以、本应该直接陈列在图书馆里的书,而是被许多高校采用的教材。
最后摘录一下谭老在书的前言里的话:
许多读者称赞我处处为读者着想,开创了计算机书籍贴近大众的新风,称我是计算机界的“平民作家” ,我很珍惜群众给我的这一称谓,这是对我的莫大鞭策。希望所有的教师和作者共同努力,把每一本书、每一门课程都做成精品,得到千万学生和读者的肯定和赞扬,这才是对我们的最高奖赏。
熟悉我的都知道,我是个性子很直的人,天生不会拿权威的观点左右自己的想法。
但我想说的是,挑战权威不是我的最终目的,求真务实才是。
我只希望传达这样一个主旨思想:作为一个写教材的人,如果深知自己积累的口碑来之不易,那就应该在教材中为现在的学生作出表率,拿出自己应有的真诚。
欲戴皇冠,必承其重。
这是我今年的最后一篇回答,感谢阅读~

参考
- ^谭浩强.C程序设计教程(第4版):高等院校计算机基础教育课程体系特色教材系列[M].北京:清华大学出版社,2022.
- ^Five Popular Myths about C++, Part 1 : Standard C++ https://isocpp.org/blog/2014/12/myths-1
- ^Components of Unicode 15.0.0 https://www.unicode.org/versions/components-15.0.0.html
- ^Prime Definition & Meaning - Merriam-Webster https://www.merriam-webster.com/dictionary/prime
- ^Double prime Definition & Meaning - Merriam-Webster https://www.merriam-webster.com/dictionary/double%20prime
- ^Linden, Peter van der. “Expert C Programming.” (1994).