谭浩强《C程序设计》(第四版)错误不完全汇集

前言
p12
① 数据类型介绍中,增加了C99扩充的双长整型(long long int)、复数浮点型(float complex,double complex ,long long complex)、布尔型(bool)等,使读者有所了解。
② C99要求,main函数的类型一律指定为int型,并在函数的末尾加一个返回语句“return 0;”。

评:long long complex,bool根本是子虚乌有的。数据类型都整不明白,还谈什么语言、算法呢?
C99并没有要求main函数的类型一律指定为int型
main函数的类型一律指定为int型其实是C89要求的

p16
“位运算”是C语言区别于其他高级语言的一个重要特点。

不顾常识,胡说八道

第一章 程序设计和C语言
1.2 什么是计算机语言
p1
计算机发展的初期,一般计算机的指令长度为16,

不晓得老谭把什么时代算做“计算机发展的初期”

p2
符号语言……LD代表“传送”等————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p2
LD一般表示赋值
p2
高级语言……用到的语句和指令是用英文单词表示的————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p2
高级语言这个层次没有指令这个概念

p3
C(系统描述语言)————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p3

这是把张飞当张良了

p3
C++(支持面向对象程序设计的大型语言)————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p3

C++是一种支持多重编程范式的通用程序设计语言

1.3 C语言的发展及其特点
p4
……增加了一些功能,尤其是C++中的一些功能,命名为ISO/IEC 9899:1999。

又在信口开河

p4
1983年,美国国家标准协会(ANSI)成立了一个委员会,根据C语言问世以来各种版本对C语言的发展和扩充,制定了第一个C语言标准草案(‘83 ANSI C)

曾经的子虚乌有的“87 ANSI C”不见了,很好。但这个‘83 ANSI C是哪来的呢?

p4
1990年,国际标准化组织ISO(International Standard Orgnization)接受C89……
评:ISO啥时候改名了?
我记得这个组织的正式名称是International Organization for Standardization
难怪书这么“水”
ISO都被山寨了

p5
C语言的运算符包含的范围很广泛,共有34种运算符(见附录C)。

评:这个恐怕要麻烦老谭自己去亲自数数了。34种究竟是怎么数出来的呢?再说附录C也不是关于运算符的啊

p5
C语言把括号、赋值和强制类型转换等都作为运算符处理,从而使C语言的运算类型极其丰富,表达式类型多样化。

评:怎么就“从而使”了呢?哪来的这种因果关系?
“运算类型极其丰富”应该是“运算种类极其丰富”
“表达式类型多样化”,不知所云

p5
C语言是完全模块化和结构化的语言。

评:什么叫“完全模块化”?什么叫完全结构化?这两个概念的定义是什么?
p5
例如,整型量与字符型数据以及逻辑型数据可以通用。

评:这是对初学者很严重的误导。另外“量”与“数据”并列,从小学生作文的角度也是说不过去的

p5
C语言……是成功的系统描述语言……

评:请问是描述什么“系统”?如何“描述”?知不知道什么叫“系统描述语言”

p5
而且C编译系统在新的系统上运行时,可以直接编译“标准链接库”中的大部分功能,不需要修改源代码,因为标准链接库是用可移植的C语言写的。

评:C编译系统在新的系统上运行:估计老谭在linux下成功地运行了他的VC6
可移植的C语言:说实话,没见过这种C语言
可以直接编译“标准链接库”中的大部分功能:看不懂这是说什么哪

1.4 最简单的C语言程序
p6
int main()

这是一种过时的写法,不符合C99的精神。C99只是为了兼容现有代码才允许这种写法
p6
而返回程序窗口

实际上恰恰是关闭程序窗口,返回IDE界面

p6
C99建议把main函数指定为int型(整型)

其实是C89标准的规定,老谭大概是为以前不规范的void main找台阶
p6
在main函数中,在执行的最后设置一个“return 0;”语句。当主函数正常结束时,得到的函数值为0,当执行main函数过程中出现异常或错误时,函数值为一个非0的整数。

评:写了“return 0;”居然还可能得到“一个非0的整数”?太神奇了吧

p7
文件后缀.h的意思是头文件(head file),因为这些文件都是放在程序各文件模块开头的。

评:head未必是以文件形式存在。
文件放在“文件模块开头”是莫名其妙的说法
p7
则表示从到本行结束是“注释”。

语文问题

p8
printf(“sum is %d\n”, sum);
……在执行printf函数时,将sum变量的值(以十进制整数表示)取代双撇号中的%d。

执行printf函数:怎么读怎么别扭,应该是调用printf函数
将sum变量的值(以十进制整数表示)取代双撇号中的%d:变量的值是取代不了%d的
p9
第2行输出“大数为8”。

输出的是“max=8”
p9
程序第4行是……

那是第5行

p9
执行scanf函数,从键盘读入两个整数,送到变量a和b的地址处,然后把这两个整数分别赋给变量a和变量b。

哪来的什么“然后”?

p10
%d由变量c的值取代之。

不是由“变量c的值”取代之

p10
例如可以把例1.2程序中的“int a,b,sum”放到main函数前面,这就是全局声明,在函数外面声明的变量称为全局变量

这是教唆,多数情况下是很糟糕的写法。
此外C没有全局变量这种说法,只有外部变量

p10
源积程序……

p11
①函数首部……包括函数名、函数类型、函数属性、函数参数(形式参数)名、参数类型。

“函数类型”:错误使用术语
“函数属性”:莫名其妙的说法

p11
函数体一般包括以下两部分。
 声明部分
 执行部分

作为一本号称“按照C99标准”的教科书来说,这种说法显然是错误的
p12
在每个数据声明和语句的最后必须有一个分号。分号是C语言的必要组成部分。

武断+无知
此外“数据声明”也很荒唐,C语言没有这样的概念

p10~12
“1.4.2 C语言程序的结构”这一小节下面的小标题是

1.4.2 C语言程序的结构
(1) 一个程序由一个或多个源程序文件组成。
(2) 函数是C语言的主要组成部分。
(3) 一个函数包括两个部分
(4) 程序总是从main函数开始执行的
(5) 程序中对计算机的操作是由函数中的C语句完成的。
(6) 在每个数据声明和语句的最后必须有一个分号。
(7) C语言本身不提供输入输出语句。
( 8 ) 程序应当包括注释。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p10~12
仔细回味一下,很有喜感。
即使当作小学生作文,恐怕也不合格吧
讲C语言的程序结构,怎么冒出来“C语言本身不提供输入输出语句”了呢?
程序结构和“程序总是从main函数开始执行的”又有什么关系呢
说老谭离题万里、云山雾罩不冤枉他吧

1.5 运行C程序的步骤与方法
p12
1.5 运行C程序的步骤与方法
这一小节讲的实际上是用C语言开发程序的过程与步骤(编辑、编译、链接及运行)
从内容上看,标题文不对题

这一小节的重大疏失是
只讲了编译和运行时发现错误应该返回修改源程序
但对链接时可能也会发生错误却只字未提
事实上链接错误也是编程最常见的错误之一
对这样常见的一大类错误怎么可以忽视呢

语言罗嗦和概念不清的毛病在这一小节同样存在
譬如

(1)上机输入和编辑源程序。通过键盘向计算机输入程序,……————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p12
什么叫“通过键盘向计算机输入程序”?难道这不属于“编辑”的范畴吗?
试问老谭,“输入“和”编辑”之间的区分到底应该如何界定?

p13
经过编译得到目标程序文件f.obj,再将所有目标模块输入计算机————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p13

“将所有目标模块输入计算机”:老谭能否演示一下怎么输入?
p14
集成环境(IDE)————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p13

直接无视D字的含义

1.6 程序设计的任务
p14
1.6 程序设计的任务————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p14
这是第一章最糟糕的一个小节
首先是文不对题
程序设计的任务只能是解决问题,不可能是别的什么
但这一小节的内容其实是对完整的程序设计过程和步骤的描述
如果仅仅如此
问题并不严重
可是书中对完整的程序设计过程和步骤的描述居然是

从确定问题到最后完成任务,一般经历以下几个工作阶段:
(1)问题分析。……
(2)设计算法。……
(3)编写程序。……
(4)对源程序进行编辑、编译和连接。……
(5)运行程序,分析结果。……
(6)编写文档。……
这种“谭式”工作流程几乎是置几十年来人类程序设计的经验和思考于不顾,全然自创的
不过说实话,我倒更希望老谭的这一小节能找本象样的书抄一抄,因为那样至少不会荒谬的如此离谱

首先,轻飘飘的一句“从确定问题到最后完成任务”
把确定问题当成了轻而易举的一件事
事实上除了判断一个正整数是否是素数这样的问题
“确定问题”的难度常常并不亚于解决问题

其次,老谭的“问题分析”居然是

对于接手的任务要进行认真的分析,研究所给定的条件,分析最后应该到达的目标,找出解决问题的规律,选择解题的方法。在此过程中可以忽律一些次要的因素,使问题抽象化,例如用数学式子表示问题的内在特性。这就是建立模型。
我的印象,这不过是中学数学解题方法与数据建模方法的一种杂交而已,不伦不类四不象。
“对于接手的任务要进行认真的分析,研究所给定的条件”:空对空的废话
“分析最后应该到达的目标”,目标都不明就编程?
“找出解决问题的规律,选择解题的方法”,耳熟,很有亲切感。后来想起来中学数学老师总这么说
“在此过程中可以忽律一些次要的因素”:从胆量方面来说不得不赞一个。复杂的东西咱都推还给客户好了
“使问题抽象化”:不知所云
“用数学式子表示问题的内在特性”,软件设计解决的问题有几个有数学式子?
“这就是建立模型”:不打自招

第三,“设计算法”中只字未提数据结构。老谭是强调“算法—程序的灵魂”的,可惜他一点不懂什么叫数据结构
在老谭的影响和带动下,经常看到一群整天把算法挂在嘴边但却连最基本的数据类型都搞不懂的家伙,根本就不能指望他们能懂得数据结构

第四,(3)的“编写程序”和(4)的“对源程序进行编辑”到底有什么分别

第五,(6)编写文档。程序开发完了,终于想起来“文档”了。任何一个软件工程专业的二年级本科生都能指出为什么这很荒谬。更荒谬的是,在老谭那里,“文档”居然不过是“程序说明书”、“帮助(help)”或“readme”。

习题
p15

这一章的习题也很糟糕

习题
6.编写一个C程序,输入a,b,c三个值,输出其中最大者。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p15

这道题不具备程序设计习题的最基本要求,是错误的题目。没有人可以做出这种题目。这反映了作者对软件开发基本上一窍不通

第二章 算法——程序的灵魂
p16
第二章 算法——程序的灵魂
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16

这一章总的来说就如同阑尾,而且是发炎的阑尾。
p16

首先映入眼帘让人感到眼前一亮的是
著名计算机科学家沃思(Nikiklause Wirth)……
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16

又来了。有人做过实验,google Nikiklause Wirth搜来的全是中文网页。不要问我为什么,只有智商约等于0的人才会问这样的问题。
由此可见,垃圾也是会疯狂地繁殖的。有人总喜欢沾沾自喜地拿“1100万”说事儿, 我倒想问一句,“1100万”个错误是个什么概念,“1100万”堆垃圾又会造成多大的污染。

p16
对于 Wirth 提出的
算法+数据结构=程序
老谭评论道:

直到今天,这个公式对于过程化程序来说依然是适用的。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16
没有任何理由和依据,而敢于指责大师的公式具有局限性,不能不承认谭大师很有“勇气”。
接着,老谭提出

实际上,一个过程化的程序除了以上两个主要因素之外,还应当采用结构化程序设计方法进行程序设计,并且用某一种计算机语言表示。因此,算法、数据结构、程序设计方法和语言工具4个方面是一个程序设计人员所应具备的知识,在设计一个程序时要综合运用这几方面的知识。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16
感慨:大师总是一针见骨地道出事物的本质;“伪大师”总是点金成铁狗尾续貂并且把水搅混。不妨再续狗尾:小学数学也是“程序设计人员所应具备的知识”。
2.1 什么是算法
p16~17
2.1 什么是算法————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16

本小节前半部分介绍什么是算法,后半部分讨论了数值算法与非数值算法。主要毛病在后半部分。
后半部分实际上毫无必要;数值运算算法和非数值运算算法属于作者闭门造车自创的不规范用语(估计是不怎么读文献所导致);
对各种数值运算都有比较成熟的算法可供使用————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p17
乃极其武断的信口开河,表明作者对数值计算领域惊人的无知。

2.2 简单的算法举例
p17
2.2 简单的算法举例
这一小节读起来犹如嚼棉花的感觉,只好快些扫过。
p18
例 2.1
…由于计算机是高速运算的自动机器,实现循环是轻而易举的……
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p18

脑残是怎样炼成的,这种似是而非的“由于”句型功不可没。“循环”居然是由于“高速”,咄咄怪事。

p19~20
例 2.2
…从图2.1可以看出:“其他”这一部分,包括不能被4整除的年份,以及能被4整除,又能被100整除,但不能被400整除的那些年份(如1900年),它们都是非闰年。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p19

敢问大师有没有亲自看过图2.1中“其他”这部分写的究竟是什么?要是看过的话,我们可就知道什么叫睁着眼睛胡说了
p20
例 2.4
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p20
其中描述的算法必然导致拖泥带水的代码。
p21
例 2.5……
所谓素数(prime),是指除了1和该数本身之外,不能被其他任何整数整除的数
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p21

厉害!把素数的定义都给改了,数学家们该感到汗颜了吧。
另外作者连“整数”和“正整数”的区别都搞不清楚
p21
判断一个数n(n≥3)是否为素数……
实际上,n不必被2n-1的整数除,只须被2n/2间的整数除即可,甚至只须被2~n之间的整数除既可。例如,判断13是否为素数,只须将13被2,3除即可,如都除不尽,n必为素数。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p21

2~n/2 , 2~n 这两个区间到底哪个大?怎么后者竟然成了“甚至” 了呢
2.3 算法的特性
p21
2.3 算法的特性
……
(2)确定性。算法中的每一个步骤都应当是确定的,而不不应当是含糊的、模棱两可的。…… 也就是说,算法的含义应当是唯一的,而不应当产生“歧义性”。所谓“歧义性”,是指可以被理解为两种(或多种)的可能含义。 ————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p21
用老谭自己的矛攻一下他自己的盾
前一章的一道习题

6.编写一个C程序,输入a,b,c三个值,输出其中最大者。
这个题目本身是否“含糊”,是否具有“歧义性”?
问题本身就“模棱两可”,又何谈算法的“确定性”呢?

2.4 怎样表示一个算法
p22
2.4 怎样表示一个算法
流程图是用一些图框来表示各种操作。用图形表示算法,直观形象,易于理解。美国国家标准化委员会 ANSI(American National Standard Institute)规定了一些常用的流程图符号(见图2.3),已为世界各国程序工作者普遍采用。 ————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p22
首先ANSI是 American National Standards Institute.
其次,老谭把ANSI搬出来是无知的卖弄。理由是:
这个标准有ISO版本,ANSI采纳了这个ISO标准。而且对应于ISO标准,我国有自己的GB。这些标准的内容是一致的。老谭不谈GB,不谈ISO,却舍近求远地扯什么ANSI标准,又有什么特殊的理由呢?只能说他自己并不熟悉这些标准及其相互的关系。
而且书中的流程图根本不符合无论是 ISO标准还是GB还是ANSI标准,流线乱窜,甚至全书中压根就没有标准用来表示循环的基本图形。
既然连这些标准读都没读过,把ANSI搬出来不是唬人又能是什么呢?

p26
2.4.3 三种基本结构和改进的流程图
1.传统流程图的弊端————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p26
成功地把滥用goto的危害移花接木地指误为传统流程图的弊端。

……人们规定出几种基本结构,然后由这些基本结构按一定规律组成一个算法结构(如同用一些基本预制件来搭成房屋一样),如果能做到这一点,算法的质量就能得到保证和提高。
说的象真的似的。C语言再好也没耽误老谭写出那么多垃圾代码呀。由“基本结构”“按一定规律”就一定能保证算法的质量?!

p26
2.三种基本结构
1966年,Bohra和Jacopini提出了以下3种基本结构,用这3种基本结构作为表示一个良好算法的基本单元。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p26
首先把Böhm的名字错误地写成了Bohra。
其次更严重的是Böhm和Jacopini根本就没有提出什么3种基本结构,更没有提出“用这3种基本结构作为表示一个良好算法的基本单元”。3种基本结构在之前的高级语言中普遍存在。
Böhm和Jacopini仅仅不很严格地证实了向前的goto语句可以用其他语句实现,他们的论文发表的时候,Dijkstra反对goto语句的论文(1968)还没发表呢

p28
2.4.4用N-S流程图表示算法————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p28
N-S图废除了流线,哪里还是什么“流程图”?
事实上通用的说法是Nassi–Shneiderman diagram (NSD)
老谭的一个毛病就是喜欢发明一些狗屁不通的新概念
比如二级指针,行指针,数值运算算法
这些都是他自己根本没学懂的表现

p29
例2.15 ……
图2.31————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p29

图2.31的算法其蠢无比
明明一次循环就可以解决非要用两次循环
明明一个变量就可以解决非要用一个数组
而且那个50基本没有用处
因为可以轻易写出人数为一正整数时的算法

三鹿往奶粉里掺加大量三聚氰胺使婴儿身体受到摧残而被判刑
老谭往教科书里掺加的无知、错误和愚蠢使初学者大脑变残为什么不被判刑?
我看刑法应该参照食品卫生法考虑一下这个问题
在教科书中传播愚蠢败坏民族的智力难道不是犯罪?

p31
2.4.5 用伪代码表示算法————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p31
两道例题都是用for循环可以很漂亮完成
却莫名其妙的选择了用罗嗦的while循环描述
误导初学者

p32
2.4.6 用计算机语言表示算法
……用计算机语言表示的算法是计算机能够执行的算法。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p32
胡扯,计算机能够执行只有机器语言

p33
例2.19……
double deno=2.0,sum,term;
while(deno<=100)
{
……
deno=deno+1;
}————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p33
明显的错误,缺乏最基本的编程素质

2.5 结构化程序设计方法
p34
2.5 结构化程序设计方法
……关键是算法,有了正确的算法,用任何语言进行编码都不是什么困难的事情。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p34
这种对算法的片面强调是对初学者绝对的误导。误导的结果是,许多初学者往往只会用最拙劣数据结构绞尽脑汁地苦思复杂无比的“算法”。
此外,有经验的人都知道,即使懂得了算法,学习用另一种语言编程往往也不是那么容易的事,比如C++。

第三章最简单的C程序设计——顺序程序设计
P37

对第3章的点评

第3章 最简单的C程序设计——顺序程序设计
开场白讲了两件事:作者对如何学习编程的看法,介绍该书的做法。与该章的主题没什么关系。前者在第二章已经说过,后者应该是前言或第一章的内容,写在这里有没话找话自吹自擂之嫌。
作者对如何学习编程的看法基本是片面、偏颇及错误的,是对Wirth“算法+数据结构=程序”的曲解和错误发挥。这对学习者是一种根本上的误导。

3.1 顺序程序设计举例
两个例子从问题的提法到最后的代码都糟糕透顶、漏洞百出。这两个例题给学习者带来的负面影响有很多。从这两个例题中学习者不可能领悟到究竟什么是顺序程序设计,更不可能领悟到究竟什么是程序设计。他们很可能误以为编程就是把代数式写成表达式 + 进行函数调用。
更严重的是,谭书居然告诉读者“警告”可以作为正常来接受,这是教唆式的误导。
关于例3.1
例3.1 有人用温度计测量出用华氏法表示的温度(如69°F),今要求把它转换为以摄氏法表示的温度(如20°C)。

这个题目看似描述的平易近人通俗易懂。但我想小学语文老师会评价说,温度就是温度,和“有人”或没人、用或不“用温度计测量”有什么关系?“有人用温度计测量出”完全是废话。而小学数学老师会说,谭同学,69°F根本不是20°C。
这就是老谭。有人说他的书通俗易懂,其实多半只是似是而非且毫无内容的废话甚至病句而已。如果阅读不仔细很容易被他的似是而非唬过去,但只要稍微一推敲,立刻就能把他戳出几个洞。赞赏他的书的人,通常小学数学和小学语文都不咋样
此外,在题解中有这样一些毛病:
把算法说成是找公式
流程图写输入,但代码中是赋值运算
代码使用不合适的数据类型
容易出错的代码风格
……

3.2 数据的表现形式及其运算
这部分总的来说就是概念不清,逻辑混乱。
3.2.1 常量和变量
1.常量
“常用的常量有以下几类:(1)整型常量……(2)实型常量……(3)字符常量……(4)字符串常量……(5)符号常量”
2.变量
3.常变量
4.标识符
从这个标题结构很容易发现其荒谬,总体来说就是不伦不类:
把标识符和常量、变量并列;把“常变量”和“变量”并列;
对常量的分类同样是不伦不类
先介绍“符号常量”后介绍“标识符”是本末倒置

此外还有如下错误
3.2.1 之 1.常量 部分
“整型常量。如1000,12345,0,-345等都是整型常量。”,这里把“-345”这个常量表达式说成了常量
刚说完“十进制小数形式,由数字和小数点组成”,立刻给出了“-56.79”这个例子,明显自相矛盾。试问,“-”是数字还是小数点?
对字符常量和符号常量的讲解均存在错误。

3.2.1 之 2.变量 部分
对编程最重要的概念之一——变量的介绍存在惊人的致命错误(“变量名实际上是以一个名字代表的一个存储地址”)
“C99允许在函数中的复合语句(用一句花括号括起来)中定义变量。”属于用C89的知识冒充C99

3.2.1 之 3.常变量 部分
把const 变量想当然地说成是C99的,实际上C89就有const 这个关键字

3.2.1 之 4.标识符 部分
介绍的是过时(对于C99来说)的说法
P37
第3章 最简单的C程序设计——顺序程序设计
……
为了能编写出C语言程序,必须具备以下的知识和能力:
(1) 要有正确的解题思路,即学会设计算法,否则无从下手。
(2) 掌握C语言的语法,知道怎样使用C语言所提供的功能编写出一个完整的、正确的程序。也就是在设计好算法之后,能用C语言正确表示此算法。
(3) 在写算法和编写程序时,要采用结构化程序设计方法,编写出结构化的程序。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p37
这段总的、指导性的叙述,由于并不全面,具有本质性的缺失,因而是对初学者不折不扣的误导。
事实上仅有这些是不够的,当初学者仅仅注意这几点的时候是绝对不可能学好程序设计的。
众所周知,程序由数据、运算、控制和传输这四个基本成分。谭的叙述中完全看不到“数据”这个最基本成分的影子,这就造成了这样一种现象,许多学习谭书的人往往津津乐道地把“算法才是王道”挂在嘴边,但却对数据缺乏起码的必要了解。谭书本身最致命的一个根本缺陷就是对数据缺乏重视,不甚了了甚至存在大量的根本性的错误。
此外不难发现,谭的这段叙述和Wirth所说的“算法+数据结构=程序”在精神上是背道而驰的。

3.1 顺序程序设计举例
P37
3.1 顺序程序设计举例
例3.1 有人用温度计测量出用华氏法表示的温度(如69°F),今要求把它转换为以摄氏法表示的温度(如20°C)。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p37
这个题目看似描述的平易近人通俗易懂。但我想小学语文老师会评价说,温度就是温度,和“有人”或没人、用或不“用温度计测量”有什么关系?“有人用温度计测量出”完全是废话。而小学数学老师会说,谭同学,69°F根本不是20°C。
这就是老谭。有人说他的书通俗易懂,其实多半只是似是而非且毫无内容的废话甚至病句而已。如果阅读不仔细很容易被他的似是而非唬过去,但只要稍微一推敲,立刻就能把他戳出几个洞。赞赏他的书的人,通常小学数学和小学语文都不咋样。
P37~38
例3.1的讲解存在这样几个问题
1.

解题思路:这个问题的算法很简单,关键在于找到二者间的转换公式……
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p37~38
找个简单的公式也是算法?
2.

图3.1(N-S图)中
输入f的值
但代码中却是

f = 64.0;
这是“输入”吗
3.
代码中出现了从来没介绍过的关键字float
4.
代码中计算摄氏温度的语句是

c=(5.0/9)*(f-32);
这个风格怪异不说
初学者不可能明白为什么原来公式中的5写成了5.,而原公式中的9和32却没变
5.
老谭最后说

读者应能看懂这个简单的程序。
我看读者根本看不懂
P38

例 3.2 计算存款利息。有1000元,想存一年。有3种方法可选:(1)活期,年利率为r1;(2)一年定期,年利率为r2;(3)村两次半年定期,年利率为r3。请分别计算出一年后按三种方法所得到的本息和。

这也叫例题?谁知道你r1,r2,r3是神马东西啊
谁能做出来?我出1000元悬赏!
在公司里搞出这样的需求分析老板不抽他才怪

1000明明是问题中的常量
却非要设个变量(float p0=1000,)
难道不是脱裤子放屁吗?
既丑陋无比又容易弄出差错
这难道不是教小朋友们学坏吗
P38

例 3.2————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p38
再次出现了“N-S流程图”这个词
在书中一会儿“N-S流程图”,一会儿“N-S图”
明显的缺乏概念的统一
是形式逻辑中所说的“概念不清”的一种
N-S图中写的是“输入p0,r1,r2,r3的值”
代码中却是赋初值“float p0=1000,r1=0.0036,r2=0.0225,r3=0.0198,p1,p2,p3;”
P38

更可笑的是

运行结果:
p1=1003.599976
p2=1022.500000
p3=1019.898010
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p38

竟然给出了小数点后面的第6位
银行会按这个给你付钱吗?
不懂银行的业务写出这种代码只能是一个笑柄
更惊人的是
1000元按照年利率0.0036计
小学生用心算都能脱口而出本息是1003.6元
而你这个程序给出的结果竟然是1003.599976
这不是糟蹋C语言是什么
所以读老谭的书会变傻
这是确凿无疑的
P38

程序分析:第4行,在定义实型变量p0,p1,p2,p3,r1,r2,r3的同时,对变量p0,r1,r2,r3赋予初值。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p38
啥叫“实型变量”啊?
再说,在N-S图里不是“输入p0,r1,r2,r3的值”吗?
怎么到这里又成了“赋予初值”了呢?
P38

第8行,在输出p1,p2,p3的值之后,用\n使输出换行。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p38
这段文字出现在第38页
到这里为止,代码中的\n至少出现过20次左右
老谭已经讲过十几次\n是换行了
车轱辘话来回说是老谭的主要作文技巧
P37~39
#include <stdio.h>
int main()
{
float f,c;
f=64.0;
c=(5.0/9)*(f-32);
printf(“f=%f\nc=%f\n”,f,c);
return 0;
}
在使用Visual C++6.0编译系统时,……,会显示出“警告”信息:“在初始化时把一个双精度常量赋给一个float型变量”。

评:这条警告信息是捏造的,在这段代码中根本不存在“初始化”
P39

注意:……这是因为有的C编译系统(如Visual C++)把所有的实数都作为双精度处理。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39
毫无根据的臆断,信口开河
P39
对这类“警告”,用户知道是怎么回事就可以了。承认此现实,让程序继续进行连接和运行,不影响运行结果。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39

为自己的垃圾代码辩解
教唆初学者放过警告
严重地误导初学者
3.2数据的表现形式及其运算

P39
3.2 数据的表现形式及其运算
……
3.2.1 常量和变量
……
(1)整型常量。如1000,12345,0,-345等都是整型常量。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39

首先“整型”这个词含义是不明确的,在谭书中一直有n种并存的含义
其次,-345不是常量,而是常量表达式。如果可以把-345说成常量,那么3-4也可以说成是常量。岂不荒谬

P39
(2)实型常量。有两种表示形式:————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39
老谭既然号称“按照C语言的新标准C99进行介绍”,怎么会写出实型常量有两种表示形式这样的胡话?
这只能说明老谭对C99缺乏最基本的了解
还是老作风:信口开河
P39

(3)字符常量。有两种形式的字符常量:
①普通字符,用单撇号括起来的一个字符,如:‘a’,‘Z’,‘3’,‘?’,‘#’。不能写成’ab’或’12’……字符常量只能是一个字符,……。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39
这一页上的第6个硬伤。

P37

评:开场白讲了两件事:作者对如何学习编程的看法,介绍该书的做法。与该章的主题没什么关系。前者在第二章已经说过,后者应该是前言或第一章的内容,写在这里有没话找话自吹自擂之嫌。
作者对如何学习编程的看法基本是片面、偏颇及错误的,是对Wirth“算法+数据结构=程序”的曲解和错误发挥。这对学习者是一种根本上的误导。

评:两个例子从问题的提法到最后的代码都糟糕透顶、漏洞百出。这两个例题给学习者带来的负面影响有很多。从这两个例题中学习者不可能领悟到究竟什么是顺序程序设计,更不可能领悟到究竟什么是程序设计。他们很可能误以为编程就是把代数式写成表达式 + 进行函数调用。
更严重的是,谭书居然告诉读者“警告”可以作为正常来接受,这是教唆式的误导。

例3.1 有人用温度计测量出用华氏法表示的温度(如69°F),今要求把它转换为以摄氏法表示的温度(如20°C)。
评:这个题目看似描述的平易近人通俗易懂。但我想小学语文老师会评价说,温度就是温度,和“有人”或没人、用或不“用温度计测量”有什么关系?“有人用温度计测量出”完全是废话。而小学数学老师会说,谭同学,69°F根本不是20°C。
这就是老谭。有人说他的书通俗易懂,其实多半只是似是而非且毫无内容的废话甚至病句而已。如果阅读不仔细很容易被他的似是而非唬过去,但只要稍微一推敲,立刻就能把他戳出几个洞。赞赏他的书的人,通常小学数学和小学语文都不咋样
此外,在题解中有这样一些毛病:
把算法说成是找公式
流程图写输入,但代码中是赋值运算
代码使用不合适的数据类型
容易出错的代码风格
……

3.2 数据的表现形式及其运算
评:这部分总的来说就是概念不清,逻辑混乱

3.2.1 常量和变量
1.常量
“常用的常量有以下几类:(1)整型常量……(2)实型常量……(3)字符常量……(4)字符串常量……(5)符号常量”
2.变量
3.常变量
4.标识符
评:从这个标题结构很容易发现其荒谬,总体来说就是不伦不类:
把标识符和常量、变量并列;把“常变量”和“变量”并列;
对常量的分类同样是不伦不类
先介绍“符号常量”后介绍“标识符”是本末倒置

此外还有如下错误
3.2.1 之 1.常量 部分
“整型常量。如1000,12345,0,-345等都是整型常量。”,这里把“-345”这个常量表达式说成了常量
刚说完“十进制小数形式,由数字和小数点组成”,立刻给出了“-56.79”这个例子,明显自相矛盾。试问,“-”是数字还是小数点?
对字符常量和符号常量的讲解均存在错误。

3.2.1 之 2.变量 部分
对编程最重要的概念之一——变量的介绍存在惊人的致命错误(“变量名实际上是以一个名字代表的一个存储地址”)
“C99允许在函数中的复合语句(用一句花括号括起来)中定义变量。”属于用C89的知识冒充C99

3.2.1 之 3.常变量 部分
把const 变量想当然地说成是C99的,实际上C89就有const 这个关键字

3.2.1 之 4.标识符 部分
介绍的是过时(对于C99来说)的说法
P39~41
评:这一小节讲的是“常量”
除了前面提到的错误、概念或知识缺失、含糊不清、误导等
回过头来看还会因为分类错乱而感觉很滑稽
(1)整型常量
(2)实型常量
(3)字符常量
(4)字符串常量
(5)符号常量
这完全是一脚天上一脚地下,关公战秦琼式的写法
p40
③ 转义字符……'\t’代表将输出的位置跳到下一个tab位置(制表位置),一个tab位置为8列
评:tab位置是实现定义的
表3.1 转义字符及其作用


转义字符 │ 字符值 | 输出结果


\'           |     一个单撇号(')   |      具有此八进制码的字符   

\"          |     一个双撇号(“)   |     输出此字符   

……

评:对“’ ”输出结果的描述不知所云

p40
字符串常量是双撇号中的全部字符(但不包括双撇号本身)
评:这个表述很似是而非
没有讲清String literal的本质
如果是讲述String literal中的字符,那么忽视了还有一个’\0’

单撇号内只能有包含一个字符, 双撇号内可以包含一个字符串。

前半句是错的,后半句看不懂究竟想说什么。
p41
(5)符号常量。……
#define PI 3.1416
经过以上指定后,本文件中此行开始的所有的PI都代表3.1416。在对程序进行编译前,预处理器先对PI进行处理,把所有的PI全部置换为3.1416。
评:成问题的地方在“所有的”那三个字,这给人以严重的误导。至少我见过读了这句话之后试图替换“……PI ……” 或v_PI中的PI的学习者。
p41
2.变量
评:这一小节错得很不靠谱
已经突破了信口开河的境界达到胡说八道的高度了
例如
C99允许在函数中的复合语句(用一句花括号括起来)中定义变量。
C99允许使用常变量,如:
const int 3=3;

我注意到本书到此为止关于C99的论述至少有九成都是错误的
而且这些错误并没涉及到什么复杂的概念,只涉及一些简单的基本常识
在这些常识性的地方居然大错特错
那么就有理由怀疑老谭究竟看过C99没有(哪怕是粗略地看过)
根本没看过C99就在书里自称“按照C语言的新标准C99进行介绍”
这不是学识问题
是品质问题
p41
请注意变量名和变量值这两个不同的概念,……
评:实际上这两个概念有时是同一的,是区分不开的。
而且老谭根本也没有讲清楚这两个概念的区别究竟何在
p41
变量名实际上是以一个名字代表的一个存储地址。
评:这是简直是胡扯!
假如有
int i;
如果 i 代表地址,那么 &i 又是什么呢?

p41
C99允许使用常变量

评:且不谈“常变量”这个翻译有多么拙劣
const关键字早在C89中就有了
关C99什么事
P42
常量是没有名字的不变量。
评:常量就一定没有名字吗?显然不是
P42
定义符号常量是用#define指令,它是预编译指令,它只是用符号常量代表一个字符串,……
评:“字符串”这个词在C语言中是有特定含义的,这里使用这个词明显不当
P42
C语言规定标识符只能由字母、数字和下划线3种字符组成,
评:不是号称“按照C99介绍”的吗?
C99是这样规定的吗?!
麻烦老谭自己亲自去看看
不要把几十年前的规定拿来冒充C99
P42
3.2.2 数据类型
评:数据类型是C语言最基础最根本性的东西。对数据类型的理解程度基本上决定了一个人C语言的水平。同样一本书对数据类型的介绍是否准确全面也在很大程度上决定了书的优劣。下面来看看谭书对数据类型的介绍。
P42
所谓类型,就是数据分配存储单元的安排,包括存储单元的长度(占多少字节)以及数据的存储形式。不同的类型分配不同的长度和存储形式。
评:除了最后一句是病句以外,这段话最大的毛病在于并不全面,仅仅把数据类型理解为数据在存储单元中的表示形式大约只理解了数据类型全部含义的45%。纵观全书,这个毛病始终存在。这一点我认为是谭书一个致命的硬伤。由于这个致命的硬伤,所以这本书的错误并非象有些天真的人们以为的那样可以通过修修补补改好——因为根本性的基调就是不准确和错误的。

该书把数据类型分为“基本类型”、“枚举类型”、“空类型”和“派生类型”。(表 3.4)
这也是要命的,反映了作者对数据类型的一知半解并且根本不懂得数据类型的重要性。
首先“枚举类型”与“基本类型”、“派生类型”并列是不伦不类的半调子分类。回顾本书的前几版,这个“枚举类型”的位置始终很尴尬,有时根本没有,有时与“整型”、“字符型”、“实型”并列。作者一直把“枚举类型”当作临时工一样今天放这,明天放那,后天还可能给解雇了。这些说明作者这几十年一直没能把数据类型搞清。

表 3.4中的另外几个错误是
缺乏不完全数据类型,只写了一个void类型
*布尔型(bool) ,这个写错了,再次表明老谭根本没读过C99,但他却宣称“按照C99进行介绍”,品质问题。
浮点类型中缺少long double。
复数浮点型(float_complex,double_complex,long long_complex):这个非但不全,而且全错。

仅仅这一张表上就有如此多的严重错误,这本书绝对是垃圾到家了

P42
3.2.2 数据类型
评:“为什么在用计算机运算时,要指定数据的类型呢?”这个设问的可笑之处在于后面根本没有回答,而是驴唇不对马嘴地瞎扯了一通。

“所谓类型,就是数据分配存储单元的安排,包括存储单元的长度(占多少字节)以及数据的存储形式。不同的类型分配不同的长度和存储形式。”
表明作者根本不懂得数据类型的含义。

把数据类型分为“基本类型”、“枚举类型”、“空类型”和“派生类型”(表 3.4)。很荒唐
更荒唐的是一口气写错了4个描述类型的关键字

3.2.3 整型数据
评:概念不清,逻辑混乱。(比如,整型变量的符号属性)纠结于具体编译器下各数据类型的空间(范围)和所谓的“补码”,并没有真正讲清楚数据类型本质性的东西。
一些原则性的错误:
在将一个变量定义为无符号整型后,不应向它赋予一个负值,否则就会得到错误的结果。
变量值在存储单元中都是以补码形式存储的

3.2.4 字符型数据
评:“因此C99把字符型数据作为整数类型的一种”,信口开河。
p43
例如,Visual C++ 6.0为char型(字符型)数据分配1个字节,
评:居然是“例如”!
敢问老谭你能否找到一个“例外”?
p44
3.2.3 整型数据
1.整型数据的分类
本节介绍最基本的整型类型。
(1)基本整型(int型)
编译系统分配给int型数据2个字节或4个字节(由具体的C编译系统自行决定)。如Turbo C2.0为每一个整型数据分配2个字节(16个二进位),而Visual C++为每一个整型数据分配4个字节(32位)。在存储单元中的存储方式是:用整数的补码(complement)形式存放。
评:这段中“整型”的含义是前后不一的,这是最基本的逻辑错误:概念不清,其后果必然是逻辑错乱
“编译系统分配给int型数据2个字节或4个字节”:字面意思是int类型只可能有两种可能,这也是错误的。
“在存储单元中的存储方式是:用整数的补码(complement)形式存放”:这是一相情愿的想当然
p44
在存放整数的存储单元中,最左面一位是用用来表示符号的,如果该位为0,表示数值为正;如果该位为1,表示为负。
评:1. unsigned 算不算整数? 哪位表示符号?
2. 什么叫“最左面一位”,拜托,那叫“高位”
3. 0000 0000 “表示数值为正”吗?乱
p44
1.整数类型的分类
(1)基本整型(int型)
(2)短整型(short int)
(3)长整型(long int)
(4)双长整型(long long int)
评:最多是对Turbo C2.0和Visual C++6.0的情况进行反复举例而已,并且错误地把补码、一个字节8位这一特殊情况作为了唯一情况
p45
编译系统分配给long数据4个字节

评:又是想当然
p45
2.整型变量的符号属性
评:这是个不通的标题。暂且不说“符号属性”这个概念是否恰当,即使真有“符号属性”这个概念,那么它也是数据类型的性质,而不是“变量”的性质
p45
以上介绍的几种数据类型,变量值在存储单元中都是以补码形式存储的
评:胡扯!
p46
因此无符号整型变量中可以存放的正数的范围比一般整型变量中正数的范围扩大一倍。
评:抛开语文等方面的问题不谈
就以两个字节的补码表示形式的int以及unsigned来说
int:1~32767
unsigned:1~65535
这难道是扩大一倍吗?
老谭显然掰不开正数和非负数这两个概念的区别
p46
(2)对无符号整型数据用%u输出。%u表示用无符号十进制数的格式输出。如:
unsigned short price = 50 ;
printf(“%u\n”,price);
评:不否认能输出正确的结果
但是“对无符号整型数据用%u输出”显然是荒谬的
这里的“整型”是个集合概念还是个体概念?
如果是集合概念,陈述显然是错误的
如果是个体概念(unsigned int)怎么可以用short举例呢
老谭显然根本不清楚printf(“%u\n”,price)函数调用中的price表达式的真实类型
输出正确结果其实是张冠李戴之后瞎蒙碰出来的结果

“无符号十进制数”也属于莫名其妙的说法
老谭好像是在自言自语地说着自己才仿佛懂得的话
p47
在将一个变量定义为无符号整型后,不应向它赋予一个负值,否则就会得到错误的结果。如:
unsigned short price = - 1 ;//不能把一个负整数存储在无符号变量中
printf(“%d\n”,price);
得到的结果为65535.显然于原意不符。
思考:这是为什么?
原因是:系统对-1先转换成补码形式。就是全部二进制位都是1(见图3.8),然后把它存入变量price中。由于price是无符号短整型变量,其左面第一位不代表符号,按%d格式输出,就是65536.

price
1111111111111111
图 3.8
评:这段短短的文字至少有四个错误
找出3个C语言算入门了

p47
3.2.4 字符型数据
由于字符是按其代码(整数)形式存储的,因此C99把字符型数据作为整数类型的一种。但是,字符型数据在使用上有自己的特点,因此把它单独列为一节介绍。
评:1.代码、编码,两个概念。老谭这个“代码”大概是翻译软件翻译的吧
2.字符类型作为整数类型的一种,C89就已然如此,和C99有什么关系。老谭这是在不懂装懂地蒙人
3.每种数据类型都有自己的特点。在这点上,字符型数据没什么特殊的
p47
字符与字符代码并不是任意写一个字符,程序都能识别的。
评:这是人话吗
主语是什么
谓语是什么
宾语又是什么
程序识别什么
p47
例如圆周率π在程序中是不能识别的,只能使用系统的字符集中的字符集,目前大多数系统采用ASCII字符集。
评:谁说π就是圆周率的?
什么叫“识别”
什么“系统”
“字符集”有几套?
“大多数系统采用ASCII字符集”吗?
前面有网友说老谭无论对C89还是C99都还没入门
确实一针见血
p47
在将一个变量定义为无符号整型后,不应向它赋予一个负值,否则就会得到错误的结果。如:
unsigned short price = - 1 ;//不能把一个负整数存储在无符号变量中
printf(“%d\n”,price);
得到的结果为65535.显然于原意不符。
思考:这是为什么?
原因是:系统对-1先转换成补码形式。就是全部二进制位都是1(见图3.8),然后把它存入变量price中。由于price是无符号短整型变量,其左面第一位不代表符号,按%d格式输出,就是65536.

price
1111111111111111
图 3.8
评:向无符号整数类型变量赋值,C语言是有定义的。不是什么错误的结果。不能因为自己不理解C语言的真正语义就说那是错误的
printf(“%d\n”,price);得到的结果为65535 。不是什么与“原意不符”,而是作者根本不懂得那段代码的真正含义(尤其是其中存在的类型转换)
C语言没说一定采用补码
那个price其实并不是unsigned short,而是一个int类型
……
此外很多不严谨的地方就不多说了

p48
字符是以整数形式(字符的ASCII代码)存放在内存单元中的。
评:C语言从来没说过只处理ASCII码
p48
所以在C中,指定用1个字节(8位)存储一个字符(所有系统都不例外)
评:据我所知,有的系统的1字节是9位
老谭你怎么解释这个“所有系统都不例外”
p48
2. 字符变量
字符变量是用类型符char定义字符变量。
评:这是人话么?

p48
字符变量是用类型符char定义字符变量

这是话么?
这就是简介说的“文字流畅”?
当然
与以前的“字符型变量用来存放字符常量”还是天壤之别
以前的是错话
现在的是废话

(1)+、-、、/运算的两个数中有一个数为float或double型,结果是double型,因为系统将所有的float型数据都先转换为double型,然后进行运算。
评:一句不长的话错了两次。
1.“+、-、
、/运算的两个数中有一个数为float或double型,结果是double型”
老谭既然在前面吹嘘这本书是“按照C语言的新标准C99进行介绍”
难道不知道C语言有复数类型吗?
如果“+、-、*、/运算的两个数中有一个数为float或double型”,另一个是复数类型,“结果是double型”吗?

就算你根本不懂得C99
虚张声势地出来蒙人
long double 类型可是几十年前就有了
如果“+、-、*、/运算的两个数中有一个数为float或double型”,另一个是long double,“结果是double型”吗?

2.“系统将所有的float型数据都先转换为double型,然后进行运算。”,简直是胡扯

(2)如果int型与float或double型数据进行运算,先把int型和float型数据转换为double型,然后进行运算。结果是double型
评:第一次读完这个(2),我愣住了,简直不敢相信
再仔细看一遍,发现这个(2)真的是很(2)
因为什么呢
因为(1)已经说过“+、-、*、/运算的两个数中有一个数为float或double型”
现在(2)居然又开始大谈特谈“如果int型与float或double型数据进行运算”
车轱辘话来回说是老谭的一个特点
但刚说完就repeat一遍还是罕见的
中等程度的神经错乱都写不出来这种东西

而且,char 、long等与float或double型数据进行运算的情况,阙如

这段中的严重错误就不谈了
与错乱相比,错误简直不算什么了不起的事情

(3)字符(char)型数据与整型数据进行运算,就是把字符的ASCII代码与整型数据进行运算。如:12 + ‘A’,由于……
评:前面说的是“字符(char)型数据与整型数据进行运算”
立刻就拿出个两个 int 类型数据的加法来举例
很明显
老谭根本不知道 ‘A’ 的类型究竟是什么

评:暂且不谈C语言的执行环境未必使用ASCII码的问题)
这里的“整型数据”是个莫名其妙的概念

在该书的43页写到
| 基本整型 (int)
| 短整型 (short int)
| 长整型 (long int)
整型类型 | *双长整型 (long long int)
| 字符型 (char)
| *布尔型 (bool) [注:这里的bool也是错误的]

所谓“字符(char)型数据与整型数据进行运算”
说的到底是哪种"整型类型"呢?
况且这根本就是一个不必要的条件限制
难道 char类型数据 与 double类型数据 做加法运算时那个char类型数据不是字符编码吗?
它不是字符的编码还能是什么别的东西吗?

字符数据可以直接与整型数据进行运算。
评:不知所云。
什么叫“直接”进行运算?
是否还有“间接”进行运算?

⑤将10+‘a’+i*f的结果与d/3的商2.5相减,……
评:1. “实型数据”,应为(实)浮点型数据。因为在C语言中real types是整数类型与(实)浮点类型的合称。
2.“字符的ASCII” ,运行环境中的字符不一定是用ASCII码表示的。况且,即使运行环境也使用ASCII码,字符数据中存储的也不一定是ASCII码。比如,字符数据可能是一个负值
3.“转换为double型数据” ,完全无视了integer promotions。而且最终可能转换成三种而不是只有double这一种。
紧接着

以上的转换是编译系统自动完成的,用户不必过问。
评:不知所云。

分析下面的表达式,假设已指定i为整型变量,值为3,f为float型变量,值为2.5,d为double型变量,值为7.5 。
10+‘a’+if-d/3
编译时,从左到右扫描,运算次序为:
①进行10+'a’的运算,……
②由于“
”比“+”优先级高,先进行if的运算。先将i和f都转成double型,……
③整数107与i
f的积相加。
④进行d/3的运算……
⑤将10+‘a’+i*f的结果与d/3的商2.5相减,……
评:最雷人的是: “编译时……运算次序为”
编译时就运算,那运行时计算机干些什么呢?

“②由于“”比“+”优先级高,先进行if的运算”
可是“进行10+'a’的运算”为什么先做了呢,
难道“*”比第1个“+”优先级低、比第二个“+”优先级高吗?

“先将i和f都转成double型”
错的。

小学语文问题:
“if的积”,
“将10+‘a’+i
f的结果与d/3的商2.5相减”

4.不同类型数据的混合运算
……如果一个运算符的两侧的数据类型不同,则先自动进行类型转换,使两者具有同一类型,然后进行运算。
评:若有
int a[1];
那么对于表达式
*( a + 0 )
显然“+”“两侧的数据类型不同”。
老谭能否给解释一下,如何“自动进行类型转换”?使两者具有了什么样的“同一类型”?

3.算术表达式和运算符的优先级和结合性
用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,
称为C算术表达式。运算对象包括常量、变量、函数等。例如,下面是一个合法的C算术表
达式:
ab/c-1.5+‘a’
C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先
按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-b
c,b的左侧为减号,右
侧为乘号,而乘号的优先级高于减号,因此,相当于a-(b*c)。
如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方
向”处理。C语言规定了各种运算符的结合方向(结合性),算术运算符的结合方向都是“自
左至右”,即先左后右,因此b先与减号相结合,执行a-b的运算,然后再执行加c的运算。
“自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符结合。以后可以看
到有些运算符的结合方向为“自右至左”,即右结合性(例如,赋值运算符,若有a=b=c,按
从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。关于“结合性”
的概念在其他一些高级语言中是没有的,是C语言的特点之一,希望能弄清楚。附录D列
出了所有运算符以及它们的优先级别和结合性。
评:首先,来看一下引用部分小标题里的第一个词——“算术表达式”。
这个词很给人一种“亲切感”特别有迷惑力,然而它确是一个模糊的、似是而非而且毫无用处的概念。
据我所知,这个词是老谭自创的。C语言中并没有这样的概念。
C语言中只有算术类型(arithmetic types)和算术运算符(arithmetic operators)这样的概念,并没有“算术表达式”这种概念。
没有这样的概念,难道不可以自己创造概念吗?当然可以。但必须知道的是,创造概念是有前提的:

创造者要给出概念的定义;

概念要科学严谨;

这个概念有用,或者方便简洁地描述了一个道理,或者帮助别人认识了一类现象或规律。

这样才可以创造新概念。
不满足这三个前提,自创概念不是吃饱了撑的就是假行家故弄玄虚的蒙人行为。考察一下“算术表达式”这个概念。
作者给出了定义:“用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式。”
(很好,老谭自创概念不给定义的例子比比皆是,这次很有进步)
然而,这个概念并不科学,也不严谨。为什么这么说呢?简单地考察一下下面的表达式就会知道了:
1+(2+3)
在这里,如果把“+”说成是“连接”“操作数”还是勉强说得过去的,但是“()”的意义则绝对不是为了“连接”“操作数”。“()”的意义是为了表明其内部的子表达式作为一个整体——仿佛一个独立的操作数参与运算,这与“连接”是八竿子打不着的。再比如“(1)”这个表达式中,“()”连接了什么呢?其实它什么也没连接,它只表明它与其扩起来的部分是一个完整的整体而已。
所以说,这里的“()”是一种“界定”(to delimit)范围的符号,把它与表示运算的运算符并列在一起是很荒唐的。
作者接着补充道:“运算对象包括常量、变量、函数等”。
这就让人迷惑不解了,这里的“函数”究竟是指的什么呢?是“函数调用”还是“函数名”本身呢?如果是“函数调用”,就成了自己打自己耳光,因为函数调用一定是通过函数调用运算符“()”实现的(注意这里这个"()"不是括号),这和作者在前面说的“用算术运算符和括号将运算对象(也称操作数)连接起来的”自相矛盾。如果这里的所谓“函数”是“函数名”的话,总所周知,“函数名”这种数据类型怎么可能进行算术运算呢?
所以必须感谢作者的及时补充,他成功地使用“函数”这个词戳穿了他自己创造的伪概念——“算术表达式”,这个概念是经不起推敲的。
紧接着的“例如”也很成问题。权且承认他给出“a*b/c-1.5+‘a’ ”是合法的,但是这个表达式即不是“用算术运算符和括号将运算对象(也称操作数)连接起来的”(因为没有括号),“运算对象”也不“包括常量、变量、函数”(“函数”在哪?),作者举例到底想说明什么呢?无厘头么!
或许,有人很不以为然:“括号”和“函数”都是随手写的,不算大错。好,至少现在你已经承认这书很不严谨了。问题是我没提到这些错误之前,你发现这些错误了吗?就算你也和我一样发现了这些错误,那些把这本书作为教材的初学者怎么可能发现这些错误呢?你老人家胡写一通不要紧,你让那些初学者情何以堪呢?
又或许有人觉得这段文字只要像下面那样修改一下就可以了

“用算术运算符和将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式。运算对象包括常量、变量等。例如,下面是一个合法的C算术表达式:
  a*b/c-1.5+'a'  ”

 Sound good!这段文字似乎基本没什么问题了。然而我们还是要问,这个自创的概念有什么用吗?继续往下看。下面一段文字是

“C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-bc,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(bc)。”
我在读这一段第一句话的时候被骇了一大跳。这句型,简直太惊人了。前面压根没提过“优先级”,抽冷子就来了一句“C语言除了规定了运算符的优先级外,还……”,有这么说话的吗?如果连话都说不利索,但却非要写在教科书里,您老人家又成天把这个“1100万”挂在嘴上,您说这和一只著名的苍蝇成天炫耀自己污染过很多食物有区别吗?
也许有人觉得这只是语文问题,不是C语言方面的问题。好吧,我让步!问题是这段文字和前面作者自创的“算术表达式”没有任何关系,没有人能否认这一点吧?!
再看引用文字的最后一段。这段文字同样和“算术表达式”这个伪概念没有任何关系。唯一可能牵强地和“算术表达式”扯上一点关系的是“算术运算符的结合方向都是‘自左至右’”,然而这句话本身就是错误的,因为作者在书中的第52页明确地说明一元“+”、“-”都是算术运算符,然而我们都知道一元“+”、“-”的结合性是“自右至左”,更何况“算术运算符”和“算术表达式”分明就是两个不同的概念。
而且,优先级和结合性的规律是对所有运算符而言的,算术运算符并没有什么特殊的优先级和结合性规律;而且所谓的“算术表达式”在这方面也没有任何特殊性而言,创造“算术表达式”比画蛇添足还要无聊。不仅无聊,而且有害。这个概念甚至无法比喻成懒婆娘的裹脚布,最多只能称得上是懒婆娘丢弃的裹脚布而已。
问题出来了,谭大爷为什么要自创这个不伦不类而且漏洞百出的伪概念——“算术表达式”,他到底想说什么?我估计他自己也不清楚。
话说到这里,我想我已经证明了这段引文的“胡扯性”。但是,根据我的经验,这时候一定会有无耻之徒跳出来无赖地狡辩:“毕竟还介绍了优先级和结合性么”——没办法不让他们跳出来,“卑鄙是卑鄙者的通行证”。然而,在这里,即使是这种最无耻的狡辩也是不可能得逞的,因为这段引文对“优先级”和“结合性”的讲解也是错的!
关于优先级,作者是这样写的:
“在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-bc,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(bc)。”
这段文字的错误在于作者把运算符的优先级和表达式求值的执行次序混为了一谈。尤其是“先乘除后加减”更是对优先级的曲解。实际上,优先级和表达式求值的执行次序基本上是不相干的两回事情。
优先级的意义在于表明表达式的含义(结合性也是),而求值的执行次序则是编译器的自选动作。只要不违反表达式的含义,编译器可以按照自己的爱好安排求值次序,编译器也没有义务告诉你它是按照什么次序求值的。这种自选动作叫implementation-defined behavior。
举例来说,对于表达式“ 1+2-34 ” ,“”的优先级高于“-”,其意义在于表明减去的是(34)而不是减3。只要你清楚地表明了这一点,你的任务就完成了。至于计算次序,编译器至少有两种选择:
1) 1+2-3
4 => 1+2-12 => 3-12 => -9
2) 1+2-34 => 3-34 => 3-12 => -9
按照外交部的惯用句型来说就是,怎么选择求值次序是编译器自己的事,程序员无权干涉(甚至无权了解,好奇也不行,编译器没义务告诉你)。 程序员的责任在于把表达式的含义写清楚,写正确;编译器的义务仅仅在于给出正确的结果而已。至于算出结果的过程(次序),对不起,关您什么事啊?您管的也太宽了吧。
总之,“优先级”的意思就是优先级高的运算符先选“对象”——运算对象。谭大爷明显是不明白这点小道理,可笑地把优先级和求值次序“绑定”到了一起,一相情愿地拉郎配。
如果优先级高的运算符选完了“对象”,剩下的运算符优先级相同,怎么办呢?
答案也简单,按照结合性挑选“对象”,如果是从左到右的结合方向,就左边的先挑,依次向右进行;如果是从右到左,则顺序相反。例如:
1+2-34
由于“
”的优先级最高,所以先挑运算对象,表达式的含义为
1+2-(34)
剩下的两个运算符“+”和“-”的优先级相同,所以看结合性,这两个运算符的结合性是从左到右,因此左面的先挑,表达式的含义可以进一步明确为
(1+2) - (3
4)
最后,可以确定“-”的运算对象分别为 (1+2) 和 (3*4)。
这就是优先级和结合性的全部含义。如此而已,非常简单。再来看看老谭是怎么讲的。
“如果在一个运算对象两侧的运算符的优先级别相同”,看到这句我差点没吐出来。老谭居然给来了个“如果”“运算对象”“两侧”,这跟优先级、结合性有什么关系吗?如果运算符在运算对象的同一侧怎么办?比如 - - i (不是"–“,两个“-”之间有空格),这应该怎么办呢?再比如 a[3],那个3应该算是在运算符的哪一侧呢?
所以看老谭的书,如果你不能发现他的愚蠢,那就只剩下了两种可能:1.你自己愚蠢;2.你正在变得愚蠢。
再往下看:
“算术运算符的结合方向都是’自左至右”,即先左后右’”,这句话本身就是错误的,前面已经提到过。
“因此b先与减号相结合”,这句更可笑,纯粹是望文生义式的妄断臆测。老谭把左结合性理解成了“运算对象先与左面的运算符结合”,简直是荒唐透顶。结合性,即使从字面上来说,也不是简单地指操作数与左面或右面的运算符相“结合”。简单举个例子就说明老谭的荒谬,”[]"运算符的结合性是从左至右,那么表达式 a [ b[0] ] 中 ,无论是a或是b,怎么与左面的运算符结合?所以“运算对象先与左面的运算符结合”这样的昏话绝对不可能是荒谬,只可能是荒谬透顶。
“即右结合性(例如,赋值运算符,若有a=b=c,按从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。”,这里也是错的。根据结合性,a=b=c 的含义是 a=(b=c) ,也就是说是把b=c的值赋给a,而不是老谭所说的“然后变量b的值赋给a”。
“关于“结合性”的概念在其他一些高级语言中是没有的,是C语言的特点之一”,这是井底之蛙坐井观天式思考的结果,根本不用反驳。
“希望能弄清楚”,希望很好,问题是您老人家自己弄清楚没有啊?您总这么瞎忽悠,让学习者怎么弄清楚啊?
“附录D列出了所有运算符以及它们的优先级别和结合性。”,第一,附录D并没有列出“所有”的运算符,第二,列出的部分不少是错误的。

如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方向”处理。
评:这句话蠢就蠢在那句“如果”
它好像是在暗示如果在一个运算对象一侧的运算符的优先级别相同,则不按规定的“结合方向”处理
实际上这个问题和运算符在运算对象的两侧还是一侧根本没有关系
只要是优先级相同,那么总是按结合性处理的

也就是说
那句蠢话相当于说:如果明天下雨,那么明天就是星期一
但就连弱智都懂得,明天是不是星期一和明天是否下雨是没什么关系的

算术运算符的结合方向都是“自左至右”
评:这简直是自己往自己脸上扇了一记大耳光
因为在该书的52页
表3.5 最常用的算术运算符

老谭明确无误地把一元 + 和一元 - 运算符都列为了“最常用的算术运算符”
难道一元+和一元-的结合性也是“自左至右”的吗?
(顺便说一句,不知道在老谭眼里,哪些算术运算符不是“最常用的”算术运算符)
p49
可以用以下方法测定本系统的处理方式:
char c=255;
printf(“%d\n”,c);

在Visual C++系统上进行编译时,系统给出“警告”(warning):“把一个整常数赋给char变量”。表示255已超过char变量的数值允许值,在运行时输出-1.说明Visual C++是把char默认为signed char类型的。如果把第1行改为“unsigned char c=255;”,则不出现“警告”,且输出255
评:如果一个初学者这样测,是应该夸奖一下的,尽管这是错的
但作为“大师”,居然如此,呵呵,……
p49
用后面的7位存放0到127,即127个字符的代码。
评:老谭究竟会不会数数?
p49
把可用的字符由127个扩展为255个,即扩大了一倍。
评:老谭真的是不识数
p49
……把本来不用的第一位用起来。把char变量改为unsigned char,即第一位并不固定设为0,而是把8位都用来存放字符代码。这样,可以存放(2^8-1)即255个字符代码。
评:还是不会数数的问题
p49
……为什么在C中把实数称为浮点数呢?在C语言中,实数是以指数形式存放在存储单元中的。一个实数表示为指数可以有不止一种形式,
评:第一句的设问简直太莫名其妙了,什么时候在C中把实数称为浮点数了呢?
整数难道不是实数?是以指数形式存放在存储单元中的吗?
至于“一个实数表示为指数”这简直就不是话。哪怕作为小学生的作文这样写也不合格呀
p49~51
“3.2.5 浮点型数据”小节的主要错误
评:1.
浮点型数据是用来表示具有小数点的实数的 。

C99中浮点类型包括实数浮点类型和复数浮点类型
2.
为什么在C中把实数称为浮点数呢?

没有的事情
在C中实数类型是指实数浮点类型和整数类型
3.
实数是以指数形式存放在存储单元中的

错。理由同上
4.
由于小数点的位置可以浮动,所以实数的指数形式称为浮点数

臆测。
5.
小数点后第一位数字不为0的表示形式称为规范化的指数形式

规范化的指数形式是作者随心臆造的一个似是而非且含混不清的概念。
浮点数中有“normalized floating-point number”这样的概念,但和作者所谓的“规范化的指数形式”无关。
6.
在程序以指数形式输出一个实数时,必然以规范化的指数形式输出,如0.314159e001。

无中生有,信口开河
7.
编译系统为每一个float变量分配4个字节,
用8个字节存储一个double型数据

C语言并没有规定浮点数据的存储空间的大小
8.
Turbo C对long double 型分配16个字节。

无中生有,信口开河
9.
表3.4 实型数据的有关情况
……
2.310^-308 ……1.710^308

“实型数据”:概念错误
这个表实际是IEEE浮点数的特征
精度差的惊人:
2.310^-308应为 2.22510^-308
1.710^308 应为 1.7976910^308
10.
例如float型变量能存储的最小正数为1.2*10-38,不能存放绝对值小于此值的数,例如10-40

不准确且自相矛盾。浮点数0.F的绝对值就小于1.2*10^-38。
11.
在C语言中进行浮点数的算术运算时,将float型数据都转换为double类型,然后进行计算。

错!

P50
编译系统为每一个float型变量分配4个字节。
评:毫无根据的说法
P50
浮点数类型包括float(单精度浮点型)、double(双精度浮点型)、long double(长双精度浮点型)。
评:这个说法证明老谭对C99还处于根本没入门的地步。
然而他居然在内容简介中号称“按照C语言的新标准C99进行介绍。”
P50
Turbo C对long double 型分配16个字节。
评:信口开河
P50
3.2.5 浮点型数据
……编译系统为每一个float型变量分配4个字节,……
……为了扩大能表示的数值的范围,用8个字节存储一个double型数据,……
评:C语言并没有规定浮点数据的存储空间的大小
P51
float型变量能存储的范围见图3.12。
……
即数值可以在3个范围内:(1)-3.4×1038到-1.2×10-38;(2)0;(3)1.2×10-38到3.4×1038;
评:图3.12画的很蠢
它和这段文字给了读者一个误导,就是0是孤立的而另外两段区间是连续的
P51
3.2.6 怎样确定常量的类型
在程序中出现的常量是要存放在内存单元中的
评:至少是不严格的。
P51
从常量的表示形式可以判断其类型
评:123456是什么类型单从其表示形式上是判断不出的

此外,这本书从头到尾没有介绍各种常量的写法
这也算是个“独创”了
如果常量都不会写
读者怎么学习写代码?
P51
不带小数点的数值是整型常量
评:"123E4"带不带小数点?是整型常量吗
P51
整型常量。不带小数点的数值是整型常量,但应注意其有效范围。如在Turbo C中,系统为整型数据分配2个字节,其表值范围为-32768~32767,如果在程序中出现数值常量23456,系统把它作为int型处理,用2个字节存放。如果出现49875,由于超过32768,,2个字节放不下,系统会把它作为长整型(long int)处理,分配4个字节。在Visual C++中,凡在-2147483648~2147483647之间的不带小数点的数都作为int型,分配4个字节,在此范围外的整数,而又在long long型数的范围内的整数,作为long long型处理。
评:一系列的逻辑错乱
首先,“整型常量”中的“整型”显然是一个集合名词,而“整型数据”中的“整型”是另一个概念,似乎只能理解为int类型。这违反了形式逻辑中的同一律
其次,int类型的表示范围和它为2个字节没有必然的因果关系
“由于超过32768”更是胡说八道
“2个字节放不下,系统会把它作为长整型(long int)处理,分配4个字节”表明作者根本不清楚整数常量的类型划定原则
“在Visual C++中,凡在-2147483648~2147483647之间的不带小数点的数都作为int型”,事实上根本不存在负常量
“在此范围外的整数,而又在long long型数的范围内的整数,作为long long型处理”,这是胡扯

P51
C编译系统把浮点型常量都按双精度处理,分配8个字节。
注意:C程序中的实型常量都是双精度浮点型常量。
评:从这里可以看出,老谭根本不懂得“浮点型”、“双精度”、“实型”这三个概念的区别
概念混乱不清是老谭这本书的一个重要特色

至于“分配8个字节”云云更是属于不懂得C语言的基本原理
P51~52
float a=3.14159;
……可以容忍这样的“警告”,使程序接着进行连接和运行。
评:容忍“警告”的程序员是不可容忍的
3.2.7 运算符和表达式
P52
1.基本的算术运算符
P53
……两个实数相除的结果是双精度实数。
评:没这回事。
3.F/5.F 的值就不是double
3.L/5. 也不可能是double

更荒谬的是
这句话明显地把整数从实数中给开除了
老谭征求过数学家的意见没
人家答应不
P53
两个整数相除的结果是整数,如5/3的结果值为1,舍去小数部分。但是如果除数或被除数中有一个为负值,则舍入的方向是不固定的。
评:老谭要是在前面不吹嘘他这本书是按照C99标准介绍的
我还真不好批他
P53
%运算符要求参加运算的运算对象(即操作数)为整数,结果也是整数。如8%3,结果为2。
评:除数或被除数中有负值的情况这回压根给忘了
P53
2. 自增、自减运算符
评:最主要的是两个问题
1.对运算的介绍不全面(“作用是使变量的值加1或减1”)
2.四个运算符当成了两个来介绍
P53
自增运算符(++)和自减运算符(–)只能用于变量,而不能用于常量或表达式
评:变量本身就是表达式
怎么可以说不能用于表达式呢

再给老谭看一个
int *p = (int *)malloc( sizeof (int) );
*p=6;
++*p;

"*p"难道不是表达式?
P53
使用++和–运算符时,常常会出现一些人们想不到的副作用,如i+++j,是理解为(i++)+j呢?还是i+(++j)呢?
评:1.没出现过什么想不到的副作用啊。如果想不到,是没有正确理解这些运算。而没能正确理解,那首先是因为教科书本身就没讲正确,没讲清楚
2.后面的例子并不能支持“出现一些人们想不到的副作用”
3.“副作用”这个术语在C语言中是有特定含义的,用在这里显然是概念糊涂错乱
P53
熟练的程序开发人员喜欢在使用++和–运算符时,采取一些技巧,以体现程序的专业性,建议初学者慎用。
评:听起来专业人员特别喜欢炫耀技巧。没这回事情。专业人员只是恰当地使用运算符罢了
要初学者慎用,是在掩饰作者自己的一知半解,逃避向初学者讲明白这些运算的责任。
这是一种不懂装懂式的装腔作势
P53
自增(减)运算符常用于循环语句中,使循环变量自动加1;也用于指针变量,使指针指向下一个地址。
评:没有的事情。大概是老谭自己只晓得这两种用法
“循环变量自动加1”的说法很搞笑,什么叫“自动加1”?怎么“自动”了?老谭给说说什么样不是“自动加1”
指针指向地址的说法也是错误的
P54
3.算术表达式和运算符的优先级与结合性
……在表达式求值时,先按运算符的优先级别顺序进行,例如先乘除后加减。
评:这完全是对优先级的歪曲
C语言里根本就没有先乘除后加减这回事
运算次序是一种Unspecified behavior
P54
“自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符相结合。
评:这就不是一般的胡扯了
估计是喝高了才敢这么扯
P54
若有a=b=c,按从右到左顺序,先把变量c的值赋给变量b,然后把变量b的值赋给a
评:这是对表达式“a=b=c”的错误解释
完全不懂得C语言最基本的运算规则
这会使学习者误入歧途,永远学不懂C语言
P54
关于“结合性”的概念是在其他一些高级语言中是没有的,是C语言的特点之一
评:把优先级和结合性都讲错了不说
又信口雌黄地胡说其他高级语言没有“结合性”的概念
居然把“结合性”说成了C语言的特点,简直是胡扯

不过这段胡扯倒是和把“a=b=c的意思是a=(b=c)”说成“是C++的观念”有异曲同工之妙
P54
如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方向”处理。
评:这句话蠢就蠢在那句“如果”
它好像是在暗示如果在一个运算对象一侧的运算符的优先级别相同,则不按规定的“结合方向”处理
实际上这个问题和运算符在运算对象的两侧还是一侧根本没有关系
只要是优先级相同,那么总是按结合性处理的

也就是说
那句蠢话相当于说:如果明天下雨,那么明天就是星期一
但就连弱智都懂得,明天是不是星期一和明天是否下雨是没什么关系的
P54
算术运算符的结合方向都是“自左至右”
评:这简直是自己往自己脸上扇了一记大耳光
因为在该书的52页
表3.5 最常用的算术运算符

老谭明确无误地把一元 + 和一元 - 运算符都列为了“最常用的算术运算符”
难道一元+和一元-的结合性也是“自左至右”的吗?
(顺便说一句,不知道在老谭眼里,哪些算术运算符不是“最常用的”算术运算符)
P54
3.算术表达式和运算符的优先级和结合性
用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,
称为C算术表达式。运算对象包括常量、变量、函数等。例如,下面是一个合法的C算术表
达式:
ab/c-1.5+‘a’
C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先
按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-b
c,b的左侧为减号,右
侧为乘号,而乘号的优先级高于减号,因此,相当于a-(b*c)。
如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方
向”处理。C语言规定了各种运算符的结合方向(结合性),算术运算符的结合方向都是“自
左至右”,即先左后右,因此b先与减号相结合,执行a-b的运算,然后再执行加c的运算。
“自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符结合。以后可以看
到有些运算符的结合方向为“自右至左”,即右结合性(例如,赋值运算符,若有a=b=c,按
从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。关于“结合性”
的概念在其他一些高级语言中是没有的,是C语言的特点之一,希望能弄清楚。附录D列
出了所有运算符以及它们的优先级别和结合性。
评:关于“算术表达式”、“优先级”和“结合性”的胡扯
首先,来看一下引用部分小标题里的第一个词——“算术表达式”。
这个词很给人一种“亲切感”特别有迷惑力,然而它确是一个模糊的、似是而非而且毫无用处的概念。
据我所知,这个词是老谭自创的。C语言中并没有这样的概念。
C语言中只有算术类型(arithmetic types)和算术运算符(arithmetic operators)这样的概念,并没有“算术表达式”这种概念。
没有这样的概念,难道不可以自己创造概念吗?当然可以。但必须知道的是,创造概念是有前提的:

创造者要给出概念的定义;

概念要科学严谨;

这个概念有用,或者方便简洁地描述了一个道理,或者帮助别人认识了一类现象或规律。
这样才可以创造新概念。
不满足这三个前提,自创概念不是吃饱了撑的就是假行家故弄玄虚的蒙人行为。考察一下“算术表达式”这个概念。
作者给出了定义:“用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式。”
(很好,老谭自创概念不给定义的例子比比皆是,这次很有进步)
然而,这个概念并不科学,也不严谨。为什么这么说呢?简单地考察一下下面的表达式就会知道了:
1+(2+3)
在这里,如果把“+”说成是“连接”“操作数”还是勉强说得过去的,但是“()”的意义则绝对不是为了“连接”“操作数”。“()”的意义是为了表明其内部的子表达式作为一个整体——仿佛一个独立的操作数参与运算,这与“连接”是八竿子打不着的。再比如“(1)”这个表达式中,“()”连接了什么呢?其实它什么也没连接,它只表明它与其扩起来的部分是一个完整的整体而已。
所以说,这里的“()”是一种“界定”(to delimit)范围的符号,把它与表示运算的运算符并列在一起是很荒唐的。
作者接着补充道:“运算对象包括常量、变量、函数等”。
这就让人迷惑不解了,这里的“函数”究竟是指的什么呢?是“函数调用”还是“函数名”本身呢?如果是“函数调用”,就成了自己打自己耳光,因为函数调用一定是通过函数调用运算符“()”实现的(注意这里这个"()"不是括号),这和作者在前面说的“用算术运算符和括号将运算对象(也称操作数)连接起来的”自相矛盾。如果这里的所谓“函数”是“函数名”的话,总所周知,“函数名”这种数据类型怎么可能进行算术运算呢?
所以必须感谢作者的及时补充,他成功地使用“函数”这个词戳穿了他自己创造的伪概念——“算术表达式”,这个概念是经不起推敲的。
紧接着的“例如”也很成问题。权且承认他给出“a*b/c-1.5+‘a’ ”是合法的,但是这个表达式即不是“用算术运算符和括号将运算对象(也称操作数)连接起来的”(因为没有括号),“运算对象”也不“包括常量、变量、函数”(“函数”在哪?),作者举例到底想说明什么呢?无厘头么!
或许,有人很不以为然:“括号”和“函数”都是随手写的,不算大错。好,至少现在你已经承认这书很不严谨了。问题是我没提到这些错误之前,你发现这些错误了吗?就算你也和我一样发现了这些错误,那些把这本书作为教材的初学者怎么可能发现这些错误呢?你老人家胡写一通不要紧,你让那些初学者情何以堪呢?
又或许有人觉得这段文字只要像下面那样修改一下就可以了

“用算术运算符和将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式。运算对象包括常量、变量等。例如,下面是一个合法的C算术表达式:
  a*b/c-1.5+'a'  ”

 Sound good!这段文字似乎基本没什么问题了。然而我们还是要问,这个自创的概念有什么用吗?继续往下看。下面一段文字是

“C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-bc,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(bc)。”
我在读这一段第一句话的时候被骇了一大跳。这句型,简直太惊人了。前面压根没提过“优先级”,抽冷子就来了一句“C语言除了规定了运算符的优先级外,还……”,有这么说话的吗?如果连话都说不利索,但却非要写在教科书里,您老人家又成天把这个“1100万”挂在嘴上,您说这和一只著名的苍蝇成天炫耀自己污染过很多食物有区别吗?
也许有人觉得这只是语文问题,不是C语言方面的问题。好吧,我让步!问题是这段文字和前面作者自创的“算术表达式”没有任何关系,没有人能否认这一点吧?!
再看引用文字的最后一段。这段文字同样和“算术表达式”这个伪概念没有任何关系。唯一可能牵强地和“算术表达式”扯上一点关系的是“算术运算符的结合方向都是‘自左至右’”,然而这句话本身就是错误的,因为作者在书中的第52页明确地说明一元“+”、“-”都是算术运算符,然而我们都知道一元“+”、“-”的结合性是“自右至左”,更何况“算术运算符”和“算术表达式”分明就是两个不同的概念。
而且,优先级和结合性的规律是对所有运算符而言的,算术运算符并没有什么特殊的优先级和结合性规律;而且所谓的“算术表达式”在这方面也没有任何特殊性而言,创造“算术表达式”比画蛇添足还要无聊。不仅无聊,而且有害。这个概念甚至无法比喻成懒婆娘的裹脚布,最多只能称得上是懒婆娘丢弃的裹脚布而已。
问题出来了,谭大爷为什么要自创这个不伦不类而且漏洞百出的伪概念——“算术表达式”,他到底想说什么?我估计他自己也不清楚。
话说到这里,我想我已经证明了这段引文的“胡扯性”。但是,根据我的经验,这时候一定会有无耻之徒跳出来无赖地狡辩:“毕竟还介绍了优先级和结合性么”——没办法不让他们跳出来,“卑鄙是卑鄙者的通行证”。然而,在这里,即使是这种最无耻的狡辩也是不可能得逞的,因为这段引文对“优先级”和“结合性”的讲解也是错的!
关于优先级,作者是这样写的:
“在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-bc,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(bc)。”
这段文字的错误在于作者把运算符的优先级和表达式求值的执行次序混为了一谈。尤其是“先乘除后加减”更是对优先级的曲解。实际上,优先级和表达式求值的执行次序基本上是不相干的两回事情。
优先级的意义在于表明表达式的含义(结合性也是),而求值的执行次序则是编译器的自选动作。只要不违反表达式的含义,编译器可以按照自己的爱好安排求值次序,编译器也没有义务告诉你它是按照什么次序求值的。这种自选动作叫implementation-defined behavior。
举例来说,对于表达式“ 1+2-34 ” ,“”的优先级高于“-”,其意义在于表明减去的是(34)而不是减3。只要你清楚地表明了这一点,你的任务就完成了。至于计算次序,编译器至少有两种选择:
1) 1+2-3
4 => 1+2-12 => 3-12 => -9
2) 1+2-34 => 3-34 => 3-12 => -9
按照外交部的惯用句型来说就是,怎么选择求值次序是编译器自己的事,程序员无权干涉(甚至无权了解,好奇也不行,编译器没义务告诉你)。 程序员的责任在于把表达式的含义写清楚,写正确;编译器的义务仅仅在于给出正确的结果而已。至于算出结果的过程(次序),对不起,关您什么事啊?您管的也太宽了吧。
总之,“优先级”的意思就是优先级高的运算符先选“对象”——运算对象。谭大爷明显是不明白这点小道理,可笑地把优先级和求值次序“绑定”到了一起,一相情愿地拉郎配。
如果优先级高的运算符选完了“对象”,剩下的运算符优先级相同,怎么办呢?
答案也简单,按照结合性挑选“对象”,如果是从左到右的结合方向,就左边的先挑,依次向右进行;如果是从右到左,则顺序相反。例如:
1+2-34
由于“
”的优先级最高,所以先挑运算对象,表达式的含义为
1+2-(34)
剩下的两个运算符“+”和“-”的优先级相同,所以看结合性,这两个运算符的结合性是从左到右,因此左面的先挑,表达式的含义可以进一步明确为
(1+2) - (3
4)
最后,可以确定“-”的运算对象分别为 (1+2) 和 (34)。
这就是优先级和结合性的全部含义。如此而已,非常简单。再来看看老谭是怎么讲的。
“如果在一个运算对象两侧的运算符的优先级别相同”,看到这句我差点没吐出来。老谭居然给来了个“如果”“运算对象”“两侧”,这跟优先级、结合性有什么关系吗?如果运算符在运算对象的同一侧怎么办?比如 - - i (不是"–“,两个“-”之间有空格),这应该怎么办呢?再比如 a[3],那个3应该算是在运算符的哪一侧呢?
所以看老谭的书,如果你不能发现他的愚蠢,那就只剩下了两种可能:1.你自己愚蠢;2.你正在变得愚蠢。
再往下看:
“算术运算符的结合方向都是’自左至右”,即先左后右’”,这句话本身就是错误的,前面已经提到过。
“因此b先与减号相结合”,这句更可笑,纯粹是望文生义式的妄断臆测。老谭把左结合性理解成了“运算对象先与左面的运算符结合”,简直是荒唐透顶。结合性,即使从字面上来说,也不是简单地指操作数与左面或右面的运算符相“结合”。简单举个例子就说明老谭的荒谬,”[]"运算符的结合性是从左至右,那么表达式 a [ b[0] ] 中 ,无论是a或是b,怎么与左面的运算符结合?所以“运算对象先与左面的运算符结合”这样的昏话绝对不可能是荒谬,只可能是荒谬透顶。
“即右结合性(例如,赋值运算符,若有a=b=c,按从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。”,这里也是错的。根据结合性,a=b=c 的含义是 a=(b=c) ,也就是说是把b=c的值赋给a,而不是老谭所说的“然后变量b的值赋给a”。
“关于“结合性”的概念在其他一些高级语言中是没有的,是C语言的特点之一”,这是井底之蛙坐井观天式思考的结果,根本不用反驳。
“希望能弄清楚”,希望很好,问题是您老人家自己弄清楚没有啊?您总这么瞎忽悠,让学习者怎么弄清楚啊?
“附录D列出了所有运算符以及它们的优先级别和结合性。”,第一,附录D并没有列出“所有”的运算符,第二,列出的部分不少是错误的。
P54
4.不同类型数据的混合运算
……如果一个运算符的两侧的数据类型不同,则先自动进行类型转换,使两者具有同一类型,然后进行运算。
评:若有
int a[1];
那么对于表达式
( a + 0 )
显然“+”“两侧的数据类型不同”。
老谭能否给解释一下,如何“自动进行类型转换”?使两者具有了什么样的“同一类型”?
P54
(1)+、-、
、/运算的两个数中有一个数为float或double型,结果是double型,因为系统将所有的float型数据都先转换为double型,然后进行运算。
评:一句不长的话错了两次。
1.“+、-、
、/运算的两个数中有一个数为float或double型,结果是double型”
老谭既然在前面吹嘘这本书是“按照C语言的新标准C99进行介绍”
难道不知道C语言有复数类型吗?
如果“+、-、*、/运算的两个数中有一个数为float或double型”,另一个是复数类型,“结果是double型”吗?

就算你根本不懂得C99
虚张声势地出来蒙人
long double 类型可是几十年前就有了
如果“+、-、*、/运算的两个数中有一个数为float或double型”,另一个是long double,“结果是double型”吗?

2.“系统将所有的float型数据都先转换为double型,然后进行运算。”,简直是胡扯
P54
(2)如果int型与float或double型数据进行运算,先把int型和float型数据转换为double型,然后进行运算。结果是double型
评:第一次读完这个(2),我愣住了,简直不敢相信
再仔细看一遍,发现这个(2)真的是很(2)
因为什么呢
因为(1)已经说过“+、-、*、/运算的两个数中有一个数为float或double型”
现在(2)居然又开始大谈特谈“如果int型与float或double型数据进行运算”
车轱辘话来回说是老谭的一个特点
但刚说完就repeat一遍还是罕见的
中等程度的神经错乱都写不出来这种东西

而且,char 、long等与float或double型数据进行运算的情况,阙如

这段中的严重错误就不谈了
与错乱相比,错误简直不算什么了不起的事情
P54
(3)字符(char)型数据与整型数据进行运算,就是把字符的ASCII代码与整型数据进行运算。
评:(暂且不谈C语言的执行环境未必使用ASCII码的问题)
这里的“整型数据”是个莫名其妙的概念

在该书的43页写到
| 基本整型 (int)
| 短整型 (short int)
| 长整型 (long int)
整型类型 | *双长整型 (long long int)
| 字符型 (char)
| *布尔型 (bool) [注:这里的bool也是错误的]

所谓“字符(char)型数据与整型数据进行运算”
说的到底是哪种"整型类型"呢?
况且这根本就是一个不必要的条件限制
难道 char类型数据 与 double类型数据 做加法运算时那个char类型数据不是字符编码吗?
它不是字符的编码还能是什么别的东西吗?
P54
(3)字符(char)型数据与整型数据进行运算,就是把字符的ASCII代码与整型数据进行运算。如:12 + ‘A’,由于……
评:前面说的是“字符(char)型数据与整型数据进行运算”
立刻就拿出个两个 int 类型数据的加法来举例
很明显
老谭根本不知道 ‘A’ 的类型究竟是什么
P54
字符数据可以直接与整型数据进行运算。
评:不知所云。
什么叫“直接”进行运算?
是否还有“间接”进行运算?
P54
如果字符型数据与实型数据进行运算,则将字符的ASCII代码转换为double型数据,然后进行运算。
评:1. “实型数据”,应为(实)浮点型数据。因为在C语言中real types是整数类型与(实)浮点类型的合称。
2.“字符的ASCII” ,运行环境中的字符不一定是用ASCII码表示的。况且,即使运行环境也使用ASCII码,字符数据中存储的也不一定是ASCII码。比如,字符数据可能是一个负值
3.“转换为double型数据” ,完全无视了integer promotions。而且最终可能转换成三种而不是只有double这一种。
紧接着
以上的转换是编译系统自动完成的,用户不必过问。
不知所云。
P54~55
分析下面的表达式,假设已指定i为整型变量,值为3,f为float型变量,值为2.5,d为double型变量,值为7.5 。
10+‘a’+if-d/3
编译时,从左到右扫描,运算次序为:
①进行10+'a’的运算,……
②由于“
”比“+”优先级高,先进行if的运算。先将i和f都转成double型,……
③整数107与i
f的积相加。
④进行d/3的运算……
⑤将10+‘a’+i*f的结果与d/3的商2.5相减,……
评:最雷人的是: “编译时……运算次序为”
编译时就运算,那运行时计算机干些什么呢?

“②由于“”比“+”优先级高,先进行if的运算”
可是“进行10+'a’的运算”为什么先做了呢,
难道“*”比第1个“+”优先级低、比第二个“+”优先级高吗?

“先将i和f都转成double型”
错的。

小学语文问题:
“if的积”,
“将10+‘a’+i
f的结果与d/3的商2.5相减”

P55
例3.3 给定一个大写字母,要求用小写字母输出。
……,字符数据以ASCII码存储在内存的
……
char c1,c2;
c1=‘A’;
c2=c1+32;
printf(“%c\n”,c2);
printf(“%d\n”,c2);
……
评:1.题目中的“要求用小写字母输出”莫名其妙。语文问题
2.“字符数据以ASCII码存储在内存的”,语文问题,而且观点错误。
3.c2=c1+32; 那个32是再拙劣不过的写法
4.既然“要求用小写字母输出”,printf(“%d\n”,c2);属于画蛇添足。程序完成不应该完成的功能,也是一种错误。
P55
一个字符数据既可以以字符形式输出,也可以以整数形式输出
评:但没有讲其中的道理
更可笑的是图3.13
竟然表明用%c和%d可以用来输出一个字节的数据(01100001)
属于外行的似是而非
对学习者有极大的误导
P56
5.强制类型转换运算符
……
(double)a (将a转换成double类型)
评:C语言居然有这功能?
P56
……例如:
a=(int)x
如果已定义x为float型变量,a为整型变量,进行强制类型运算(int)x后得到一个int类型的临时值,它的值等于x的整数部分,把它赋给a,注意x的值和类型都未变化,仍为float型。该临时值在赋值之后就不再存在了。
评:可怜的老谭,看来根本就不理解表达式的意义。
居然必须通过臆造一个“临时值”才能理解类型转换表达式
处于还没透彻理解表达式意义的水平下,让他写C语言书实在难为他了
“注意x的值和类型都未变化”,简直是废话,x的类型怎么可能变化呢?任何情况下也没这个可能啊
“它的值等于x的整数部分”,是错误的,至少是不严谨的
“该临时值在赋值之后就不再存在了。”,对无中生有的“临时值”所做的无中生有的臆测。
P56
如%运算符要求其两侧均为整型量,若x为float型,则x%3不合法,必须用(int)x%3。……因此先进行(int)x的运算,得到一个整型的中间变量。
评:“中间变量”属于老谭的“发明”
但老谭显然忘了他自己在前面是怎么说的了,
在41页:“变量代表一个有名字的、具有特定属性的一个存储单元”、“变量必须先定义,后使用”……
于是自己了打自己一耳光

3.3C语句
P57
语句的作用是向计算机系统发出操作指令,要求执行相应的操作
评:表达式才是要求操作
P57
声明部分不是语句,只是对有关数据的说明
评:声明不是对数据的说明
P58
(2)函数调用语句。……
(3)表达式语句
评:尽管老谭辩解说这是为了“理解”和“使用”
但实际上这表明他自己的不理解和不会使用
不得不按照BASCI或FORTRAN的方式来理解C
从书中的代码来看,除了构成表达式语句,他确实不会使用函数调用表达式
P59
在C程序中,最常用的语句是:赋值语句和输入输出语句。其中最基本的是赋值语句。程序中的计算功能大部分是由赋值语句实现的,几乎每一个有实用价值的程序都包括赋值语句。有的程序中的大部分语句都是赋值语句。

评:我简直无语
更雷人的是
书的另一处居然写着:
“C语言本身不提供输入输出语句”(p12)
让我们究竟相信你哪个说法呢

在C程序中,最常用的语句是:赋值语句和输入输出语句。其中最基本的是赋值语句。程序中的计算功能大部分是由赋值语句实现的,几乎每一个有实用价值的程序都包括赋值语句。有的程序中的大部分语句都是赋值语句。
评:笑喷了
这是史上最强悍最露骨的无知无畏——什么他都敢说,而且都是错话和废话
P59
printf(“a=%f\tb=%f\t%f\n”,a,b,c);
评:

P60
赋值符号=就是赋值运算符,它的作用是将一个数据赋给一个变量。如a=3的作用是执行一次赋值操作(或称赋值运算)。把常量3赋给变量a。也可以将一个表达式的值赋给一个变量。
评:
P60
a+=3 等价于 a=a+3
评:不很严谨
不过指望老谭能理解二者的差异是不可能的事情
算了
P60
①a+=b (其中a为变量,b为表达式)
……
注意:如果b是包含若干项的表达式,则相当于它有括号。例如,……
评:例如
a+=3,2
就相当于
a+=(3,2) 吧?

结论:老谭根本不懂得表达式
不懂表达式根本连C的门都还没摸到
奇怪的是
居然有那么多人自称跟着老谭入了门
P61
凡是二元(二目)运算符,都可以与赋值符一起组合成复合赋值符。
评:好一个“凡是”啊
这口气听起来就像权威
不过
”应该是“二元(二目)运算符”吧
请问老谭
= ”是什么“复合赋值符”呢
P61
C语言采用这种复合运算符,一是为了简化程序,使程序精炼,二是为了提高编译效率,能产生质量较高的目标代码。
评:什么叫敢想敢说?
这就是
P61
左值的意思是它可以出现在赋值运算符的左侧,它的值是可以改变的
评:记得supermegaboy就这个问题给老谭上过一课
看来老谭还是没懂
有一点进步
已经不说左值只能是变量而不能是表达式了
P61
执行表达式“a=(b=5)”,就是执行b=5和a=b两个赋值表达式。
评:

凡是二元(二目)运算符,都可以与赋值符一起组合成复合赋值符。

评:是没睡醒还是在信口开河。
如此说来
“=”是二元运算符,它可以与赋值符一起组合成复合赋值符“==” 、

P62
赋值表达式也可以包括复合的赋值运算符。例如:
a+=a-=aa
也是一个赋值表达式。如果a的初值为12,此赋值表达式的求解步骤如下:
①先进行“a-=a
a”的运算,它相当于a=a-a*a,a的值为12-144=132。
④ 再进行“a+=-132”的运算,相当于a=a+(-132),a的值为-132-132=-264
评:这个例子表明老谭不知道写代码的最基本准则
这种错误在大学教材里存在了二十年
是一种国耻

也是程序员之耻
人们有理由问
这个国家的程序员们究竟懂不懂C语言
P62
printf(“%d”,a=b);
如果b的值为3,则输出a的值(也是表达式a=b的值)为3。
评:这句话对初学者的误导几乎是致命的。
P63
(4)字符型数据赋给整型变量时,将字符的ASCII代码赋给整型变量。如:
i=‘A’;;
评:1.字符型数据存储的未必是ASCII码
2.'A’根本不是字符型数据
3.“;;”
P63
(5)将一个占字节多的整型数据赋给一个占字节少的整型变量或字符变量(例如把占4个字节的int型数据赋给占2个字节的short变量或占一个字节的char变量)时,只将其低字节原封不动地送到被赋值的变量(即发生“截断”)。
评:这是错误的
估计这是老谭根据某个编译器下的运行结果自己总结出来的

P64
例如:
if((a=b)>0) max=a;
评:在这个地方之前根本就没介绍过“>”运算,更没介绍过if语句。这例子很差劲
P64
先进行赋值运算(将b的值赋给a),然后判断a是否大于0
评:对“if((a=b)>0) max=a; ”的解说
是错误的。
解说所描述的是 a=b,a>0 这个表达式而不是(a=b)>0这个表达式
P64
6.变量赋初值
……
也可以被定义变量的一部分赋初值。
评:语文问题
冷一看还以为老谭在说结构体呢

什么叫“变量的一部分”?

P65
一般变量初始化不是在编译阶段完成的(只有在静态存储变量和外部变量的初始化是在编译阶段完成的),而是在程序运行时执行本函数时赋予初值的,相当于执行一个赋值语句。
评:编译时变量存在吗?

“只有在静态存储变量和外部变量的初始化是在编译阶段完成的”,语文问题
3.4 数据的输入输出
p66
该scanf函数表示从终端输入的3个数据……。双撇号内用%lf格式声明,表示输入的是双精度型实数。
评:scanf函数的输入来自标准输入设备

“表示输入的是双精度型实数”是很严重的误导。这种似是而非的“省劲”说法,会给后面的学习带来很多困惑和麻烦。
标准输入设备提供的只是一些字符而已,根本就没有什么双精度实数。

P67
(1)所谓输入输出是以计算机主机为主体而言的。从计算机向输出设备(如显示器、打印机等)输出数据称为输出,从输入设备(如键盘、磁盘、光盘、扫描仪等)向计算机输入数据称为输入,如见图3.17所示。
评:总体来说是废话
给人的感觉是输入输出设备明显已经不是计算机的组成部分了,不知道冯.诺依曼是否知道他的体系已经被老谭给强力摧毁

P67
(2)C语言本身不提供输入输出语句,
评:我就不明白老谭为什么反复说这句话,从开头到这里至少说四五次了。是不是没话找话呢
P67
例如printf函数和scanf函数。读者在使用它们时,千万不要误认为它们是C语言提供的“输入输出语句”
评:除了老谭自己的书上,我从来没见过把printf函数和scanf函数调用叫做输入输出语句的
P67
C提供的标准库函数以库的形式在C的编译系统中提供,它们不是C语言文本中的组成部分。
评:前半句就是个病句,搞不清到底是谁“提供”
后半句不知所云。什么叫“C语言文本”
P67
不把输入输出作为C语句的目的是使C语言编译系统简单精炼,因为将语句翻译成二进制的指令是在编译阶段完成的,没有输入输出语句就可以避免在编译阶段处理与硬件的有关问题,可以使编译系统简化,而且通用性强,可移植性好,在各种型号的计算机和不同的编译环境下都能适用,便于在各种计算机上实现。

各种C编译系统提供的系统函数库是各软件公司编译的,它包括了C语言建议的全部标准函数。
评:武断
P67
各种C编译系统提供的系统函数库是各软件公司编译的,它包括了C语言建议的全部标准函数,还根据用户的需要补充一些常用的函数,已对它们进行了编译,成为目标文件(.obj文件)。它们在程序连接阶段与源程序经编译得到的目标文件(.obj文件)相连接,生成一个可执行的目标程序(.exe文件)。如果在源程序中有printf函数,在编译时并不把它翻译成目标指令,而是在连接阶段与系统函数库相连接后,在执行阶段中调用函数库中的printf函数。
评:1.头一次听说库是 .obj文件,不知道老谭是否在编译软件中发现了printf.obj啊?
2.“在编译时并不把它翻译成目标指令”,那还叫编译吗?不翻译难道保留源代码?
3.最雷人的是“在执行阶段中调用函数库中的printf函数”,老谭自己知不知道自己在说什么啊?对自己根本不了解的东西难道可以这样信口开河吗?执行阶段怎么调用库?难道发行可执行文件必须像VB那样搭配一个库?

P68
#include 指令都放在程序文件的开头
评:未必
P69
printf函数(格式输出函数)用来向终端(或系统隐含指定的输出设备)输出若干个任意类型的数据。
评:这个属于昏话。
首先“终端”这词是模糊不清的、不准确的
“系统隐含指定的输出设备”更是不知所云的昏话
至于“若干个任意类型的数据”更是胡扯

printf()函数的功能是在标准输出设备上输出若干字符
P69
“输出表列”是程序需要输出的一些数据,可以是常量、变量或表达式。
评:这个说法很滑稽
常量、变量都是表达式
老谭居然整出了个“常量、变量或表达式”
和“香蕉、苹果或水果”如出一辙
这东西作为小学作文也不及格吧

此外“表列”这个词也很奇怪
注意了一下
这个怪异的说法并非笔误而是在多处一致地这样写

P70
如果整数比较大,则把它的最后一个字节的信息以字符形式输出。如:
int a=377;
printf(“%c”,a);
评:这个说法不准确,实际上是通过类型转换
P70
(1)d格式符
……在输出时,按十进制整型数据的实际长度输出,正数的符号不输出。
评:1.既然谈到了数据的类型,就谈不到什么“十进制”
2.正数的符号可以输出
P73
(2)o格式符。以八进制整数形式输出。将内存单元中的各位(0或1)按八进制形式输出,因此输出的数值不带符号,即将符号位也一起作为八进制数的一部分输出。例如:
int a=-1;
printf(“%d\t%o\n”,a,a);
运行时输出:
-1 37777777777

评:用%o输出int类型数据是未定义行为
P73~74
(5)g格式符。用来输出浮点数,系统自动选f格式或e格式输出,选择其中长度较短的格式,不输出无意义的0。如:
double a=12345678954321
printf(“%f\t%e\t%g\n”,a,a,a);
的输出结果为:
12345678954321.000000 1.234568e+013 1.23457e+013
可以从以上看到用%f格式输出占20列,用%e格式输出占13列,故%g采用%e格式输出。
评:问题是“double a=12345678954321”根本无法通过编译
结果究竟是怎么得到的?

即使按“double a=12345678954321;”来看,这个赋初值也是很成问题的

况且用%f格式输出占的是21列
%g格式的输出也不是按照%e格式

P74
表3.6 printf函数中用到的格式符
……
d,i 以带符号的十进制形式输出整数(正数不输出符号)
o 以八进制无符号形式输出整数(不输出前导符0)
x,X 以十六进制无符号形式输出整数(不输出前导符0x)
评:不是这样的
P74
表3.6 printf函数中用到的格式符
……
g,G 选用%f或%e格式中输出宽度较短的一种格式,不输出无意义的0。用G时若以指数形式输出,则指数以大写表示。
评:这个也是错的。
P74
表3.7 printf函数中用到的格式附加字符
……
评:1.不全
2.其中的m,n是什么以及如何用根本就没说清楚,并且有其他错误。

P75
scanf(“a=%f,b=%f,c=%f”,&a,&b,&c);
评:这种写法要多蠢有多蠢
简直是自虐

P76
表3.8 scanf函数中所用到的格式字符
u 用来输入无符号的十进制数
o 用来输入无符号的八进制数
x,X 用来输入无符号的十六进制数
评:这几种格式都容许输入有符号整数
P76
例如,若a和b为整型变量,如果写成
scanf(“%f%f%f”,a,b,c);
是不对的。应将“a,b,c”改成“&a,&b,&c”。许多初学者常犯此错误。
评:那样是不对
老谭改的就对吗?

前面说“若a和b为整型变量”
后边居然莫名其妙地出来个c
而且整型使用%f
这不是粗制滥造是什么呢
P76
如果有
scanf(“a=%f,b=%f,c=%f”,&a,&b,&c);
在输入数据时,应在对应的位置上输入同样的字符。即输入
a=1,b=3,c=2
评:scanf(“a=%f,b=%f,c=%f”,&a,&b,&c);
如此愚蠢的代码怎么可以写在教科书里
而且煞有介事地进行解释呢

P77
如果scanf函数为
scanf(“a=%f b=%f c=%f”,&a,&b,&c);
由于在两个%f间有两个空格,因此在输入数据时,两个数据间应有两个或更多的空格字符。)
评:第一,两个数据间应有两个或更多的不一定是空格字符
第二,这种写法同样很蠢,两个%f之间加空格干什么?
P77
(3)在用“%c”格式声明输入字符时,空格字符和“转义字符”中的字符都作为有效字符输入
评:“转义字符”中的字符
这个太搞了

P78
putchar©的作用是输出字符变量c的值,显然它是一个字符
评:显然老谭不知道那个c不是char类型
P78
例3.8 先后输出BOY三个字符。
解题思路:定义3个字符变量,……
#include <stdio.h>
int main()
{
char a=‘B’,b=‘O’,c=‘Y’;
putchar(a);
putchar(b);
putchar©;
putchar(‘\n’);
return 0;
}
评:唯一值得表扬的是putchar(‘\n’);
输出三个字符哪里需要用的着费那么大劲啊
printf(“BOY\n”);
定义变量很傻

P79
因此将一个字符赋给字符变量和将字符的ASCII代码赋给字符变量作用是完全相同的(但应注意,整型数据应在0~127的范围内)
评:完全不懂得赋值是怎么回事
所谓“将一个字符赋给字符变量”“赋给字符变量”是完全不通的说法
实际是根本不存在的情况
0~127这个条件根本不需要
那个“整型数据”不清楚是从哪里窜出来的,前文中根本没有
P79
说明:putchar©中的c可以是字符常量、整型常量、字符变量或整型变量(其值在字符的ASCII代码范围内)
评:c就是一个int类型数据
“其值在字符的ASCII代码范围内”也不对
P79
一般是显示器的键盘
评:看来我真out了
还真没见过这种东东
P79
例3.9 从键盘输入BOY三个字符,然后把它们输出到屏幕。
评:问题的提法非常古怪无聊
如果输入DOG是不是还得另外编个程序
P79
用putchar函数既可以输出能在显示器屏幕上显示的字符,也可以输出屏幕控制字符。
评:老谭总是自创一些莫名其妙的概念,这表明他自己概念不清
一大群幼稚的初学者跟着莫名其妙地理解,各有各的糊涂理解。有人把这叫“通俗易懂”
P79
字符类型也属于整数类型。
评:严重表扬!老谭说了一句正确的话
相对以前各版,这是一个巨大的进步

P80
这些字符先暂存在键盘的缓冲器中,
评:前所未闻
P82
可以用printf函数和scanf函数输入或输出字符。
请比较这两个方法的特点,在特定情况下用哪一种方法为宜。
评:语文问题
第4章 选择结构程序设计
4.1 选择结构和条件判断
P85

输入一个数,要求输出其绝对值。
评:不明确的程序功能要求
P86
例4.1 ……
……//disc是判别式sqrt(b*b-4ac)
disc = b * b - 4 * a * c;
……
评:此外此例代码风格欠佳。

(1) 为提高精度以及避免在编译时出现“警告”,将所有变量定义为双精度浮点型。
评:如果是为了“避免”“警告”“将所有变量定义为双精度浮点型”表明老谭不会使用float类型而不得不废掉C语言的一种基本数据类型。
这是一种误导。选择数据类型不能根据(因为不懂得如何使用而引起的)“警告”
谭的做法是因噎废食,道无知以误人

4.1小节的另一个缺欠是,在没介绍关系运算和if语句的条件下用它们写代码。
4.1 用if语句实现选择结构
P87
从例4.1可以看到:在C语言中选择结构主要是用if语句实现的。
评:从一个例子就能看出“在C语言中”如何如何
老谭的因果关系一向非常山奔海立,让人可惊可愕

为了实现互换,必须借助于第3个变量。
评:“必须”两个字过于武断
不借助第3个变量也可以交换变量的值

例4.2 输入两个实数,按代数值由小到大的顺序输出这两个数。
例4.3 输入3个数a、b、c,要求按由小到大的顺序输出。
评:前一个是合格的题目
后一个不合格:"3个数"性质不明确;a、b、c更是多此一举

4.2 用if语句实现选择结构
评:这一小节最重要的缺失是没有介绍if语句的具体执行过程
实际上
if(表达式)语句
计算机首先计算“表达式”的值,然后根据这个值是否不为0选择是否执行语句
这种次序观念对于初学者来说极其重要

P89
4.2.2 if语句的一般形式
……
if(表达式)语句1
[else 语句2]
if语句中的“表达式”可以是关系表达式、逻辑表达式,甚至是数值表达式。其中最直观、最容易理解的是关系表达式。
评:这恍然大悟式的惊呼说明了老谭对C的理解程度
然而这里列举的并不全面(其实除了void 类型所有表达式都可以)
而且对比不当
试问什么是“数值表达式”?
关系表达式、逻辑表达式是不是数值表达式?
如果承认关系表达式、逻辑表达式也是数值表达式
那么何谈“甚至”呢?

而“最直观、最容易理解”其实恰恰说明老谭自己对其他表达式感到难以理解,至于他能否恰当地应用,至少我是怀疑的
P90
……
else cost=0
评:讽刺的是在这页上写着“每个内嵌语句的末尾都应当有分号,因为分号是语句中的必要成分。”

(1) 整个if语句可以写在多行上,也可以写在一行上,如:
if(x>0)y=1 ;else y=-1;
但是,为了程序的清晰,提倡写成锯齿形式。
评:这是无厘头的废话
除了预处理命令,整个源程序都可以写在一行上,难道也告诉读者?

C源程序的写法是统一的,if语句并不存在任何特殊性

每个内嵌语句的末尾都应当有分号,因为分号是语句中的必要成分。
评:没这个事

if(1){
}
else{
}
就一个分号都没有

联系到该书把语句分为 控制语句、函数调用语句、表达式语句、空语句、复合语句,以及“最基本的语句——赋值语句”这样的说法
不难发现该书作者对“语句”始终缺乏准确、完整的概念

P91
(6)在if语句中要对给定的条件进行检查,判定所给定的条件是否成立。判定的结果是一个逻辑值“是”或“否”。
评:本来以为89页的那句带着“甚至”的惊呼“ if语句中的“表达式”可以是关系表达式、逻辑表达式,甚至是数值表达式。”表明老谭的C语言开始入门了。现在看来,他还是在BASIC的“是”、“否”泥潭里面折腾着呢。
不难发现他的自相矛盾
也终于理解他前面为什么要说“甚至是数值表达式”了,这是一种初学者新发现式的惊奇。

事实上,以
if(表达式) 语句
为例

其中的“表达式”本来就是一个数值表达式,如果这个表达式的值不为0则执行“语句”,否则不执行“语句”
如此而已

根本就没有什么“条件”、“检查”,C也没有“是”、“否”这样的概念

又如:判断“a>b”条件是否满足,当a>b时,就称条件“a>b”为“真”,如果“a≤b”,则不满足“a>b”条件,就称此时条件“a>b”为假。————
评:这讲的不是C语言
在C语言中“>”是一种运算符
a>b的值可能为1,也可能为0
4.3 关系运算符合关系表达式
P91

其中的“>”是一个比较符
评:老谭总喜欢发明这些古怪且没有什么意义的“新术语”

例如,a>3是一个关系表达式,大于号是一个关系运算符,如果a的值为5,则满足给定的“a>3”条件,因此关系表达式的值为“真”(即“条件满足”);如果a的值为2,不满足“a>3”条件,则称关系表达式的值为“假”
评:似是而非的误导。
http://bbs.chinaunix.net/thread-2297714-1-2.html
这个帖子说明了这种误导的后果

实际上“真”、“假”这些概念根本不属于C语言
总是借助多余的概念来理解C所表明的是并不真正理解C

在C语言中,>是一种运算符
5>3的运算结果为1
2>3的运算结果为0
就这么简单。根本不需要那些画蛇添足的“真”、“假”

P92
用关系运算符将两个数值或数值表达式连接起来的式子,称关系表达式。
评:“称关系表达式”,这语文水平就不评说了
表述也极不严谨
试问“3&4”、“5|6”算不算数值表达式
用关系运算符连接起来——“ 3&4<5|6 ”,是关系表达式吗?
4.4 逻辑运算符和逻辑表达式
P92~93
4.4 逻辑运算符和逻辑表达式
有时要求判断的条件不是一个简单的条件,而是由几个给定简单条件组成的复合条件。如:“如果星期六不下雨,我去公园玩”。这就是由两个简单条件组成的复合条件,需要判定两个条件:(1)是否星期六;(2)是否下雨。只有这两个条件都满足,才去公园玩。又如“参加少年运动会的年龄限制为13~17岁”,这就需要检查两个条件:(1)年龄age≥13(2)年龄age≤17。这个组合条件条件是不能够用一个关系表达式来表示的,要用两个表达式的组合来表示,即:age≥13 AND age≤17。用一个逻辑运算符AND连接age≥13和age≤17。两个关系表达式组成一个复合条件,“AND”的含义是“与”,即“二者同时满足”。age≥13 AND age≤17表示age≥13和age≤17同时满足。这个复合的关系表达式“age≥13 AND age≤17”就是一个逻辑表达式。其他逻辑表达式可以有:
x>0 AND y>0 (同时满足x>0和y>0)
age<12 OR age>65 (年龄age小于12的儿童或大于65的老人)
上面第1个逻辑表达式的含义是:只有x>0和y>0都为真时,逻辑表达式x>0 AND y>0 才为真。上面第2个逻辑表达式的含义是:age<12 或 age>65至少有一个为真时,逻辑表达式age<12 OR age>65为真。OR是“或”的意思,即“有一即可”,在两个条件中有一个满足即可。AND和OR是逻辑运算符。
用逻辑运算符将关系表达式或其他逻辑量连接起来的式子就是逻辑表达式。
评:云山雾罩,不知所云
任何合格的C程序员都不可能认为这是在讲C语言
尤其是“AND和OR是逻辑运算符”这句,属于根本性的概念错误
在C语言中AND和OR是标识符不是运算符(在C语言中唯一可以做运算符的标识符是sizeof)
连“标识符”、“运算符”这样最最的基本概念都拎不清,居然写教材?C语言界没人啦

"用逻辑运算符将关系表达式或其他逻辑量连接起来的式子就是逻辑表达式"的错误性质见1180楼
P93
有3种逻辑运算符:与(AND),或(OR),非(NOT)。在BASIC和Pascal等语言中可以在程序中直接用AND,OR,NOT作为逻辑运算符。
评:逻辑运算有很多种,至少还有个XOR,怎么可能只有3种逻辑运算符呢?
在BASIC中还有Eqv,Imp
BASIC也不是直接用AND,OR,NOT作为逻辑运算符,而是用的And,Or,Not,只不过BASIC不区分大小写罢了
再则,BASIC的逻辑运算符的运算含义也绝对不同于C的逻辑运算符的运算意义,根本不能当作同一种东西一概而论

在C语言中不能在程序中直接用AND,OR,NOT作为逻辑运算符,而是用其他符号代替。见表4 .1。
评:这圈子绕的!从上海到苏州,却非要舍近求远地先到欧洲兜一圈再到苏州
讲了一火车废话错话,有意义的内容用几个字就能概括——C有三个逻辑运算符:&&,||、!。

表4.1 C逻辑运算符及其含义
运算符 含义 举例 说明
&& 逻辑与 a&&b 如果a和b都为真,则结果为真,否则为假
|| 逻辑或 a || b 如果a和b有一个以上为真,则结果为真,二者都为假时,结果为假
! 逻辑非 !a 如果a为假时,则!a为真,如果a为真,否则!a为假
评:1.C语言没有“真”“假”这种东西
2.对&& ||运算的解释是错误的,是一种似是而非的歪曲
3.只介绍了优先级,对结合性只字未提。

总之,学习者不可能从中真正理解这3个运算

&&和||运算涉及到sequence point,不懂得这个概念,就不能说是真正懂得了这两种运算

P94
5>3&&8<4-!0
表达式自左至右扫描处理求解。首先处理“5>3”(因为关系运算符优先于逻辑运算符&&)。在关系运算符>两侧的5和3作为数值参加关系运算,“5>3”的值为1(代表真),再进行“1&&8<4-!0”的运算,8的左侧为“&&”,右侧为“<”,根据优先规则,应先进行“<”的运算,即先进行“8<4-!0”的运算。现在4的左侧为“<”,右侧为“-”运算符,而“-”优先于“<”,因此应先进行“4-!0”的运算,由于“!”的级别最高,因此先进行“!0”的运算,得到结果1.然后进行“4-1”的运算,得到结果3,再进行“8<3”的运算,得0,最后进行“1&&0”的运算,得0.
评:自相矛盾之处在于
一会说“自左至右扫描处理求解”
一会又根据优先级确定运算的先后
"8的左侧为“&&”,右侧为“<”,根据优先规则,应先进行“<”的运算"更荒唐,大概老谭忘记了还有优先级更高的“!”

这两种说法都是站不住脚的,也都是错误的

C语言编译系统在表示逻辑运算结果时,以数值1代表“真”,以0代表“假”
评:是什么话
编译系统怎么表示运算结果?运算是在编译时进行的吗?如果不是,编译系统怎么可能表示运算结果呢

(1)若a=4,则!a的值为0.……
(2)若a=4,b=5,则a&&b的值为1.……
(3)a和b值分别为4和5,a||b的值为1.
……
评:这就和他的代码中,哪怕是常量也一定要赋值给一个变量再运算,不管有没有必要,是一致的

实际上
(1)无非说的是 !4的值为0
(2)无非说的是 4&&5的值为1
(3)无非说的是 4||5的值为1
搞不清楚他画蛇添足地弄出来a、b作甚

P95
在逻辑表达式的求解中,并不是所有的逻辑运算符都被执行,只是在必须执行下一个逻辑运算符才能求出表达式的解时,才执行该运算符。举例如下。
评:“执行”“运算符”
听着比日本话还别扭:运算符执行地有
作者是中国人吗

(1)a&&b&&c。只有a为真(非0)时,才需要判别b的值。只有当a和b都为真的情况下才需要判别c的值。如果a为假,就不必判别b和c(此时整个表达式已确定为假)。如果a为真,b为假,不判别c,见图4.7。
评:原来这就是所谓的“并不是所有的逻辑运算符都被执行”啊
这里的例子说明的恰恰是两个“&&”运算都执行了
任何一个没执行都不可能得到最后的结果
没被执行的是求b和c的值

既然关系表达式和逻辑表达式的值是0和1,而且在判断一个量是否为“真”时,以0代表“假”,以非0代表“真”。那么就可以理解为什么在if语句中表达式可以是任何数值表达式。
评:不存在这种因果关系。把“关系表达式和逻辑表达式的值是0和1”扯进来更是思路不清的表现
试想,关系表达式和逻辑表达式的值是0和1和if语句的表达式有什么必然的联系呢
所以这是一段似是而非的论述

此外“if语句中表达式可以是任何数值表达式”是错误的
这个表达式只能是标量(scalar)类型
P97
如果读者使用的C++的编译器(如Visual C++),则可以使用逻辑型变量。但要把文件名后缀改为.cpp,作为C++程序,
评:搞不清楚这是想做什么
C、C++分不清楚在谭书中是一贯的
著名的把赋值表达式当作左值的笑话就是这种情况

4.5 逻辑运算符和逻辑表达式
P97

if(a>b)
max=a;
else
max=b;
……
C提供条件运算符和条件表达式来处理这类问题。
评:牵强附会
C语言的条件运算符并非是为这类问题而存在的
这种说法是误导,会限制学习者正确地应用条件运算符

“?"是条件运算符。
评:有人不主张挑这种鸡毛蒜皮的小错误
但这种小错误如此之多又说明什么呢

P98
a>b?(max=a ) : (max=b);
a>b?printf(“%d”,a):printf(“%d”,b);
评:真应了那句“再好的语言也挡不住有人写出垃圾代码”?
P99
ch=(ch>=‘A’&&ch<=‘Z’)?(ch+32):ch;
评:风格、效率、可移植性都成问题
效率指的是和什么比的?能具体说一下不?
4.6 选择结构的嵌套
P100
为了避免二义性的混淆,最好使内嵌语句也包含else部分,这样if的数目和else的数目相同,从内层到外层一一对应,不致出错。
评:这招灵吗?我很怀疑
不管有没有必要都把else写上?
P100~101
-1 (x<0 )
y = 0 (x=0 )
1 (x>0 )
……
程序1:
#include <stdio.h>
int main()
{
int x,y;
scanf(“%d”,&x);
if(x<0)
y=-1;
else
if(x==0)y=0;
else y=1;
printf(“x=%d,y=%d\n”,x,y);
return 0;
}
……
程序2:
#include <stdio.h>
int main()
{
intx,y;
scanf(“%d”,&x);
if(x>=0)
if(x>0)y=1;
else y=0;
else y=-1;
printf(“x=%d,y=%d\n”,x,y);
return 0;
}
评:这也太雷人了吧
一个如此简单的问题
居然弄了个那么复杂的嵌套结构
舍简就繁

程序2居然有运行结果
P101~102
为了使逻辑关系清晰,避免出错,一般把内嵌的if语句放在外层的else子句中(如程序1那样),这样由于有外层的else相隔,内嵌的else不会被误认为和外层的if配对,而只能与内嵌的if配对,这样就不会搞混。
评:这是因噎废食而想到的削足适履性质的差劲办法
代码的结构应该服从思想,应该是思想的自然展开和表达
先把思想装进形式上的套子里写不出优质的代码
4.7用switch语句实现多分支选择结构
P103
4.7 用switch语句实现多分支选择结构
switch语句的一般形式如下:
switch(表达式)
{
case 常量1:语句1
case 常量2:语句2
……
case 常量n:语句n
default:语句n+1
}
……
switch语句下面的花括号内是一个复合语句。
评:switch语句的一般形式并非是
switch(表达式) 复合语句

switch后面括号内的“表达式”,其值的类型应为整数类型(包括字符型)
评:1 整数类型本来就包括字符类型
2 谭书43页数据类型总表中根本没有“整数类型”,不清楚谭书中的“整数类型”究竟是指的什么

case 后面跟一个常量(或常量表达式)
评:不对!只能是整数常量表达式

每个case标号出现的次序不影响执行结果
评:显然不对

P104
在case子句中虽然包含了一个以上执行语句,但可以不必用花括号括起来,会自动顺序执行本case标号后面所有的语句。当然加上花括号也可以。
评:到底想表达什么?

例4.7
评:毫无意义的例题
1.根本就不是完整的代码
2.用到了根本没介绍的知识(函数定义等)
3.风格拙劣,写出的代码中也有很多错误,例如 intx
问题本身就描述的不完整,讲解部分完全是空对空的纸上谈兵
4.8 选择结构程序综合举例
P105
4.8 选择结构程序综合举例
评:居然一口气把判断闰年的问题写了四遍,不可思议
完全看不出那样做有什么意义
没有分析各种写法的利弊得失
几个写法都不咋地
尤其是第四个,简直是为赋新词强说愁——仅仅是为了用一下C99的_Bool类型,其余的都和第一个写法完全一样。写完之后无法编译,又建议读者把.c改成.cpp,简直是笑话。C++和C根本就是两回事,披着羊皮的狼不还是狼吗

P106
#include <stdio.h>
int main()
{
int year,leap;
printf(“enter year:”);
scanf(“%d”,&year);
if(year%40)
{
if(year%100
0)
{
if(year%400==0)
leap=1;
else
leap=0;
}
else
leap=1;
}
else
leap=0;
if(leap)
printf("%d is “,year);
else
printf(”%d is not ",year);
printf(“a leap year.\n”);
return 0;
}

评:这段代码没有错误,但风格很差,而且啰嗦

1.leap=1; leap=0; 是很差劲的写法,可读性差且容易出错。应该使用符号常量

2.第7~20行,啰嗦累赘。若在此之前给leap赋值,代码和逻辑则都要简洁得多。

3.第21~25行,更是似是而非。若不希望写重复的代码,至少应该把"%d is “部分写在if语句外面,即:
printf(”%d is ",year);
if(leap==0)
printf("not ");
printf(“a leap year.\n”);

但这也不是最好的写法,实际上还有更好的写法。

下面是我改写的代码
#include <stdio.h>
#define LEAP 1
#define NOT_A_LEAP 0
int main( void )
{
int year,leap = NOT_A_LEAP ;
printf(“enter year:”);
scanf(“%d”,&year);
if( year % 4 == 0 )
if( year % 100 == 0 ){
if( year % 400 == 0 )
leap = LEAP ;
}
else {
leap = LEAP ;
}

printf( “%d is %sa leap year.\n” , year , ( leap == LEAP )?“”:"not ");

return 0;
}

P108~109
例 4.9 求ax^2+bx+c=0方程的解。
#include <stdio.h>
#include <math.h>
int main( )
{
double a,b,c,disc,x1,x2,realpart,imagpart;
scanf(“%lf,%lf,%lf”,&a,&b,&c);
printf(“The equation”);
if(fabs(a)<1e-6)
printf(“is not a quadratic\n”);
else
{
disc=bb-4ac;
if(fabs(disc)<1e-6)
printf(“has two equal roots:%8.4f\n”,-b/(2
a));
else
if(disc>1e-6)
{
x1=(-b+sqrt(disc))/(2a);
x2=(-b-sqrt(disc))/(2
a);
printf(“has distinct real roots:%8.4f and %8.4f\n”,x1,x2);
}
else
{
realpart=-b/(2b);
imagpart=sqrt(-disc)/(2
a);
printf(“has complex roots:\n”);
printf(“%8.4f+%8.4fi\n”,realpart,imagpart);
printf(“%8.4f-%8.4fi\n”,realpart,imagpart);
}
}
return 0;
}

评:书上说输入1,2,1
输出为 The equationhas two equal roots: -1.0000
这没什么问题

可是输入 1e-8,2e-8,1e-8 时
输出居然是 The equation is not a quadratic就说不过去了吧?
P109~110
例4.10 运输公司对用户计算费用。路程(skm)越远。每吨·千米运费越低。标准如下
s<250 没有折扣
250< s<500 2%折扣
500< s<1000 5%折扣
1000< s<2000 8%折扣
2000< s<3000 10%折扣
3000≤ s 15%折扣
……
(代码略)
评:此题目根本就不应该用swtitch语句解决
P111
float p,w,d,f;
(3)变量名尽量采用“见名知意”的原则,……,在本书的例题程序,由于是练习程序,并且考虑到多数读者的习惯和方便,尽量不采用较长的变量名,而用单词的首字母或缩写作为变量名。在读者今后编程时,可根据实际情况决定。
评:实际是在用垃圾风格熏陶读者,使读者耳濡目染地养成坏习惯
口口声声“考虑到多数读者的习惯和方便”是文过饰非,为自己的垃圾风格辩解
良好的风格习惯不是一下子就能养成的
相反,养成了坏习惯就很难改掉

(4)第6行“printf(“please enter price,weight,discount:”);”的作用是向用户提示应输入什么数据,以方便用户使用,避免出错,形成友好界面。建议读者在编程序(尤其是供别人使用的应用程序)也这样做,在scanf函数语句输入数据前,用printf函数语句输出必要的“提示信息”。
评:1.身教胜于言教,除了这个例题,老谭自己做到没有?赤裸裸的scanf到处都是,教别人去做,有点不给力吧?

2.代码中写的是
printf(“please enter price,weight,discount:”); //提示输入的数据
scanf(“%f,%f,%d”,&p,&w,&s) ; //输入单价、重量、距离

英语不好,硬是没看懂

3.“ scanf函数语句”,“printf函数语句”,估计是C99的新概念

第5章 循环结构程序设计
P116
循环体如果包含一个以上的语句,应该用花括号括起来,作为复合语句出现。如果不加花括号,则while语句的范围只到while后面第一个分号处。
评:while(EXP1)
if(EXP2)
S1 ;
else
S2 ;
怎么说?

sum=sum+i;
评:太像BASIC了
应该写成 sum += i ;
5.3 用do…while语句实现循环
P117
do
语句
while(表达式);
其中的“语句”就是循环体。它的执行过程可以用图5.4表示。
评:图5.4的流程图居然没有入口
太有“创意”了

                                     100                     

例5.2 用do…while语句求1+2+3+……100,即∑n
n=1
评:这个题目用do…while语句来解决属于头脑不清醒,思维呈发散性混乱状态

我也同意应该用for语句。
编程应该用恰当的语句描述算法,而不是扭曲思想去适应语句。编程不是数学上的一题多解,不是花样越多越好,这方面软件史上有过深刻的教训

P118
#include <stdio.h>int main()

对同一个问题可以用while语句处理,也可以用do…while语句处理。do…while语句结构可以转换成while结构。
评:严重的误导
结构换来换去只说明思路上模糊不清

在一般情况下,用while语句和用do…while语句处理同一问题时,若二者的循环体部分是一样的,那么结果也一样。
评:这是瞪着眼睛说胡话
完全不具备起码的逻辑思维能力
P118~119
例5.3 while和do……while循环比较
评:无厘头式的比较
结论毫无意义
5.4 用for语句实现循环
P120
而且for语句更为灵活,不仅可以用于循环次数已经确定的情况,还可以用于循环次数不确定而只给出循环结束条件的情况。它完全可以代替while语句。
评:令人啼笑皆非
既生瑜何生亮
老谭的意思是K&R发明C时既然设置了for语句再设置while语句实在是多余
P121
for(i=1;i<=100;i++)
sum=sum+i;

(2)"表达式1"可以省略……
i=1;
for(;i<=100;i++)sum=sum+i;
评:因为不懂
所以把恶劣的编程风格作为知识讲授

for语句的一般形式
for(表达式1;表达式2;表达式3)语句
可以改写为while循环的形式:
表达式1;
while 表达式2
{
语句
表达式3;
}
二者无条件等价。
评:1.while 语句居然没有()
2.老谭大概忘记C语言还有个continue语句吧

P122
(3)"表达式2"也可以省略……
for(i=1;;i++)sum=sum+i;
评:阉割for语句的“大师”
整个121,122,123页都是在做这种无聊的阉割
P123
可见for语句比while语句功能强,
评:胡扯
这表明老谭根本不懂得各种循环语句究竟应该怎么应用

while(1)rintf(“%d\n”,i);

for(i=0,j=100;i<=j;i++,j++)k=i+j;
表达式1和表达式3都是逗号表达式,各包含两个赋值表达式,

(9)表达式2一般是关系表达式(如i<=100)或逻辑表达式(如a<b&&x<y),但也可以是数值表达式或字符表达式,
评:典型的谭体句型
仿谭体造句:一般是土豆或洋葱,但也可以是植物或辣椒
P124
for(;(c=getchar())!=‘\n’ ; )
printf(“%c”,c);
for语句中只有表达式2,而无表达式1和表达式3.其作用是每读入一个字符后立即输出该字符,直到输入一个“换行”字符。
评:1.实现那个for语句的功能,应该
while( ( c = getchar() ) != ‘\n’ )

     putchar(c); 

2.功能描述错误
所描述的功能实际是
do
putchar( c=getchar() );
while( c != ‘\n’ );

C语言的for语句比其他语言(如FORTRAN,Pascal)中的for语句功能强得多。
评:无病呻吟
比较的标准是什么?
搞不清想要传递给读者什么信息
C的for语句比C++的for语句功能也强得多?
5.5 循环的嵌套

P124~125
5.5 循环的嵌套
评:毫无意义地罗列了6种循环嵌套的结构
还写错了一种
do
{…
do
{…}
while()
}while()

这个反映了老谭基本功
5.6 几种循环的比较
P125
3种循环都可以用来处理同一问题,一般情况下它们可以互相替代。
评:严重的误导

for循环可以在表达式3中包含使循环趋于结束的操作,甚至可以将循环体中的操作全部放到表达式3中。因此for语句的功能更强,凡用while循环能完成的,用for循环都能完成。
评:胡扯+误导
5.7 改变循环执行的状态
P126
例5.4 在全系1000学生中,征集慈善募捐,当总数达到10万元时就结束,统计此时捐款的人数,以及平均每人捐款的数目。
评:嗯!
够黑!够狠!
和咱们的XX有得一拼
从题目中根本看不出来若是总数达不到怎么办
看来是志在必得
收钱的事,完不成怎么可以?!

num=7
aver= 14669.71
评:好在学生中有几个大款
问题是
哪怕小学生也不会算出平均每人捐款的数目是14669.71吧?

P127
break语句的一般形式为
break
评:还真是从没见过这种语句

P128~129
例5.6 输出以下4*5的矩阵。
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20

……
编写程序:
#include <stdio.h>
int main( void )
{
int i,j,n=0;
for(i=1;i<=4;i++)
for(j=1;j<=5;j++,n++)
{ if(n%5==0)printf(“\n”);
printf(“%d\t”,i*j);
}
printf(“\n”);
return 0;
}
评:这个代码写得非常愚蠢,功能上也有缺陷。
在教科书中传授这种代码是一种犯罪行为
这种愚蠢的代码能把初学者变成脑残和弱智

在129页愚蠢代码的基础上又毫无意义地分别加上了continue和break
使得愚蠢毫无意义地复杂化,然后让读者分析
复杂的愚蠢代码是最难分析的
而且有害无益
5.8循环程序举例
P131
例5.7 用π/4≈1-1/3+1/5-1/7…公式求的近似值,直到发现某一项的绝对值小于10^6为止(该项不累加)。
评:任何一项都小于10^6

P132
#include <stdio.h>
#include <math.h>
int main()
{
int sign=1;
double pi=0.0,n=1.0,term=1.0;
while(fabs(term)>=1e-6)
{
pi=pi+term;
n=n+2;
sign=-sign;
term=sign/n;
}
pi=pi*4;
printf(“pi=%10.8f\n”,pi);
return 0;
}

评:那个fabs()函数调用毫无必要
n为double类型sign为int类型也莫名其妙
此外pi=pi+term这样的写法纯粹是BASIC语言的写法
这个代码至少应该改成这样
#include <stdio.h>
int main( void )
{
double pi=0.,n=1.,term=1., sign=1.;
while( term >= 1e-6 )
{
pi += sign * term;
n += 2. ;
sign = - sign ;
term = 1. / n ;
}
pi *= 4. ;
printf(“pi=%10.8f\n”,pi);
return 0;
}

在C库函数中,有两个求绝对值的函数
评:错
C99中不是这样
这再次表明老谭这本书“按C语言的新标准C99进行介绍”是骗人的

P133
经过对程序的补充和运行,可以知道在while(fabs(t)>=1e- 6 )时,执行50万次,当while(fabs(t)>=1e- 8 )时,执行循环体5000万次、两者相差100倍,在分别运行以上两种情况下得程序时,可以明显地感觉到后者运行的时间长很多。
评:一堆废话

对于
π/4≈1-1/3+1/5-1/7…
最后一项<1e-6要执行多少次,小学生心算都能算出来,可老谭居然要“经过对程序的补充和运行”才知道
fabs(t):哪冒出来的“t”?
运行时间的估计居然靠实际“感觉”,不知道老谭怎么感觉出相差100倍的

P134
例5.8 求Fibonacci数列的前40个数。
#include <stdio.h>
int main()
{
int f1=1,f2=1,f3;
int i;
printf(“%12d\n%12d\n”,f1,f2);
for(i=1;i<=38;i++)
{
f3=f1+ f2;
printf(“%12d\n”,f3);
f1=f2;
f2=f3;
}
return 0;
}

评:for语句中的 “38”非常蹩脚
属于莫名其妙的常数

P135
#include <stdio.h>
int main()
{
int f1=1,f2=1;
int i;

for(i=1;i<=20;i++)
{
printf(“%12d%12d”,f1,f2);
if(i%20)printf(“\n”);
f1=f1+f2;
f2=f2+f1;
}
return 0;
}
评:这个代码中的那个20很烂
if(i%2
0)printf(“\n”); 十分蹩脚,风格也极其差劲

例5.9 输入一个大于3的整数n,判定它是否为素数(prime,又称质数)
评:居然有这样的程序,只能判断大于3的整数是不是素数
这东西交给客户时怎么说呢?
我这个程序能判断3以上的素数
3以下的还得您自己亲自判断

#include <stdio.h>
int main()
{int n,i;
printf(“please enter a integer number,n=?”);
scanf(“%d”,&n);
for(i=2;i<=n-1;i++)
if(n%i==0)break;
if(i<n)printf(“%d is not a prime number.\n”,n);
else printf(“%d is a prime number.\n”,n);
return 0;
}

评:这风格!山奔海立的,自成一派
if(i<n)printf(“%d is not a prime number.\n”,n);
else printf(“%d is a prime number.\n”,n);
写的无比笨拙

P137
图 5.19
k<√n
评:应为k=√n

#include <stdio.h>
#include <math.h>
int main()
{int n,i,k;
printf(“please enter a integer number,n=?”);
scanf(“%d”,&n);
k=sqrt(n);
for(i=2;i<=k;i++)
if(n%i==0)break;
if(i<=k)printf(“%d is not a prime number.\n”,n);
else printf(“%d is a prime number.\n”,n);
return 0;
}

评:k=sqrt(n);试图求出n的平方根的整数部分
但这点是得不到保证的
另一个问题是
题目要求“输入一个大于3的整数n”
但无论输入的整数是否大于3
程序都会给出结果
输入一个负数的话还会亲眼目睹程序崩溃的壮烈景观

P137~138
例5.10 求100~200间的全部素数。
#include <stdio.h>
#include <math.h>
int main()
{int n,k,i,m=0;
for(n=101;n<=200;n=n+2)
{k=sqrt(n);
for(i=2;i<=k;i++)
if(n%i0)break;
if(i>=k+1)
{printf(“%d”,n);
m=m+1;
}
if(m%10
0)printf(“\n”);
}
printf(“\n”);
return 0;
}
评:1.逻辑不清,到底怎样算“100~200间”,“n=101;n<=200”无论怎么解释都是错的
2.k=sqrt(n);,见1363楼
3.if(i>=k+1),很不清醒的画蛇添足,实际应该是if(i>k)
4.if(m%100)printf(“\n”); 不仅笨拙,而且如果n的初值不是素数的话会输出若干丑陋的空行
5.风格很烂
if(m%10
0)printf(“\n”);
if(n%i==0)break;
{int n,k,i,m=0;
{k=sqrt(n);
都属于很烂的风格
n=n+2
m=m+1
这种写法简直就不是C语言,而是BASIC等语言的写法

P138
(1) 根据常识,偶数不是素数,所以不必对偶数进行判断,只对奇数进行检查。故循环变量n从101开始,每次增值2。
评:这里至少有三个问题
1.200也是偶数,为什么要n<=200,和这段解释自相矛盾。
2.这种代码根本没有通用性,如果是求另一个范围内的素数,代码需要改动的地方很多。
3.“根据常识,偶数不是素数”,这是常识性的错误。小学生都知道错在哪里

(2)从附录F可以看到:sqrt是求平方根的函数,它要求参数为双精度数。在执行时会自动将整数n转换为双精度数。求出的函数值也是双精度数,再把它赋值给整型变量k,系统会自动小数部分舍弃,只把整数部分赋给k。在进行编译时,系统给出警告,提醒用户有可能出现误差。只要用户确认没有问题,可以不理会它。
评:1.用整数类型做对应原型中类型为double的实参,不规矩(当然不是不可以)
2.“系统会自动小数部分舍弃”,语文问题
3.用户不可能确认没有问题
4.教人不理会警告,是一种教唆行为。优秀的程序员不会容许任何警告,哪怕是无害的情况。更何况这里的警告是一个让人放心不下的警告。
P138~139
例5.11 译密码……
将字母A变成字母E,a变成e,即变成其后的第4个字母,W变成A,X变成B,Y变成C,Z变成D,
……#include <stdio.h>
int main()
{char c;
c=getchar();
while(c!=‘\n’)
{if((c>=‘a’&&c<=‘z’)||(c>=‘A’&&c<=‘Z’))
{if( c>=‘W’&&c<=‘Z’||c>=‘w’&&c<=‘z’)c=c-22;
//如果是26个字母中最后4个字母之一就使c-22
else c=c+4;
}
printf(“%c”,c);
c=getchar();
}
printf(“\n”);
return 0;
}

评:风格太烂
P139~140
程序改进:
#include <stdio.h>
int main()
{char c;
while((c=getchar())!=‘\n’)
{if((c>=‘A’&&c<=‘Z’)||(c>=‘a’&&c<=‘z’))
{ c=c+4;
if( c>=‘Z’&&c<=‘Z’+4|| c>‘z’)
c=c-26;
}
printf(“%c”,c);
}
printf(“\n”);
return 0;
}
评:确实有点改进
可惜改错了

第6章 利用数组处理批量数据
6.1 怎样定义和引用一维数组
P142
由于计算机键盘只能输入有限的单个字符而无法表示上下标,C语言就规定用方括号中的数字来表示下标,
评:胡扯
P143
如果在被调用的函数(不包括主函数)中定义数组,其长度可以是变量或非常量表达式。如:
void fun(int n)
{
int a[2*n]; //合法,n的值从实参传来
……
}
评:胡扯

如“int a[n];”是不合法的。也就是说,C语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值。
评:可笑的是这段文字与1403楼引用的文字出现在同一页上
还没翻页就开始自己打自己耳光

int a[10];
它表示定义了一个整型数组,数组名为a,此数组有10个整型元素。
评:根据该书43页对数据类型的划分
整型数据至少包括6种以上的数据类型
所以,所谓的“数组有10个整型元素”中的整型元素属于指代不明

定义一维数组的一般形式为
类型符 数组名[常量表达式];
评:无论从C90还是C99标准来说都是错误的

从C90来说,“常量表达式”是不准确的,不是任何常量表达式都可以
从C99来说,并没有要求一定是“常量表达式”

常量表达式中可以包括常量和符号常量
评:标准的谭氏废话句型
这话放小学生作文里是要得红叉叉的

不能包括变量,如“int a[n];”是不合法的。也就是说,C语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值。例如下面这样定义数组是不行的:
int n;
scanf(“%d”,&n) ; //企图在程序中临时输入数组的大小
int a[n];
评:已经说了是“常量表达式”,“不能包括变量”是废话
此外,老谭号称他这本书依据C99写的,如“int a[n];”是不合法的 。这句是完全错误的。

这段代码在支持C99的编译器下没有问题。
自相矛盾的是,在同一页,老谭又写到

如果在被调用的函数(不包括主函数)中定义数组,其长度可以是变量或非常量表达式。如:
void fun(int n)
{
int a[2n]; //合法,n的值从实参传来
……
}
评:刚说完[]内只能是“常量表达式”,一转眼又说int a[2
n]; //合法
这种自相矛盾带给初学者的不止是错误还有混乱和无所适从
如果按C90这段论说是绝对错误的
如果按C99来说这段说法依然是错误的,因为main()与其他函数相比除了是程序开始执行的位置之外没有什么特殊的,“不包括主函数”的说法完全是不懂装懂的信口开河,没有任何根据

在调用func函数时,形参n从实参得到值。这种情况称为“可变长数组”,允许在每次调用func函数时,n有不同的值。
评:又在胡扯,
“可变长数组”根本不是像他说的那样

但是在执行函数时,n的值是不变的
评:n是变量,怎么就不变了呢

P144
6.1.2 怎样引用一维数组

6.1.2 怎样引用一维数组
在定义数组并对其中的各元素赋值后,就可以引用数组中的元素。
评:赋值后就可以引用
赋值时呢?
难道不是引用?

例如下面的赋值表达式包含了对数组元素的引用:
:a[0]=a[5]+a[7]-a[2*3]
每一个数组元素都代表一个数值。
评:”:” 印刷错误

每一个数组元素都代表一个数值:错!这个表达式中的数组元素并非都代表数值

例6.1
int i,a[10];
for(i=0;i<=9;i++)
a [ i ] = i ;
评:i<=9 是业余写法

P147
例6.3 有10个地区的面积,要求对它们按由小到大的顺序排列。
评:这算题吗?
条件本身就不全,怎么可能完成呢?
在题目条件不充分的条件下给出代码
是在鼓励程序员胡乱猜测程序功能并示范如何进行胡猜
而程序员擅自胡乱猜测程序功能是与软件工程的思想和基本原则背道而驰格格不入的
所以就不能理解老谭这书究竟是在教别人写程序
还是在诱导教唆学习者违背并践踏写程序的基本原则
6.2 怎样定义和引用二维数组
P149
二维数组常称为矩阵(matrix)。
评:概念混乱的惊人
一个是程序设计语言中的概念
另一个是数学中概念

二维数组定义的一般形式为
类型说明符 数组名[常量表达式][常量表达式];
评:两个“常量表达式”给人貌似相同的错觉
另外“常量表达式”这个描述也不准确,不是所有的常量表达式都可以

P150
二维数组元素的表示形式为
数组名[下标][下标]
评:两个“下标”没有区别,不妥

数组元素可以出现在表达式中,也可以被赋值
评:谭氏废话
相当于什么也没说
其实只要知道数组元素本身也是表达式
那些都是不言而喻的
P153
例 6.4
#include <stdio.h>
int main()
{
int a[2][3]={{1,2,3},{4,5,6}};
……
for(i=0;i<=1;i++)
{
for(j=0;j<=2;j++)
{
printf(“%5d”,a [ i ][j]);
……
}
}
……
return 0;
}
评:这是一种似是而非的外行写法
会给初学者带来很坏的影响

至少应该写成
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
printf(“%5d”,a[ i ][j]);
……
}
}

更好的写法是用sizeof运算求出数组的尺寸

printf(“max=%d\nrow=%d\ncolum=%d\n”,max,row,column);
评:密密麻麻
风格太差

6.3 字符数组
P154

前已介绍:字符型数据是以字符的ASCII代码存储在存储单元中的,一般占一个字节。由于ASCII代码也属于整数形式,因此在C99标准中,把字符类型归纳为整型类型中的一种。评:短短一段话有多处错误
1.“字符型数据是以字符的ASCII代码存储在存储单元中的”,未必
2.“一般占一个字节”,没什么一般特殊而言,char类型就是占一个字节
3.“由于ASCII代码也属于整数形式,因此在C99标准中,把字符类型归纳为整型类型中的一种。”,压根就没有这种因为所以。
此外C89中字符数据就属于整数类型
4.“整型”,这个概念和书里出现的其他“整型”概念不一致。

字符串是存放在字符型数组中的。
评:“ABC”
这样的字符串存放在哪个数组中?

P154~155
用来存放字符数据的数组是字符数组。
……
也可以用整型数组它存放字符数据,例如
int c[10]; //合法,但浪费存储空间
c[0]=‘a’;
评:1.自相矛盾。按照第一句的说法, c也是字符数组
2.第二句话是病句
3.int c[10];未必属于浪费存储空间
P156
例 6.6 输出一个已知的字符串。
例 6.7 输出一个菱形图。
评:谭书的很多例题都是这样没头没脑
如果不看输出结果
你根本不知道他这题目究竟想做什么

例 6.6 输出一个已知的字符串。
解题思路:先定义一个字符数组,并用“初始化列表”对其赋以初值。然后逐个输出此字符数组中的字符。
评:“解题思路”倒是更像一个具有具体要求的题目
而“输出一个已知的字符串”根本不成其为题目
矛盾的是“解题思路”和输出字符串根本扯不上边
如果输出一个字符串
puts()和printf()都很容易完成
根本用不着舍近求远地去定义字符数组
况且解题思路中没有丝毫字符串的影子
如果看下代码
就会发现代码和题目之间驴唇不对马嘴
编写程序:
#include <stdio.h>
int main()
{ char c[15]={‘I’,’ ‘,‘a’,‘m’,’ ‘,‘a’,’ ‘,‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’,’.'};
int i;
for(i=0;i<15;i++)
printf(“%c”,c[ i ]);
printf(“\n”);
return 0;
}

例 6.7 输出一个菱形图。
解题思路:先画出一个如图6.12所示的平面菱形图案。
评:题目本身没头没脑
没有人能解这样的题目

图6.12所画的也不是菱形图案

编写程序:
……
char diamond[][5]={{’ ‘,’ ‘,’‘},{’ ‘,’‘,’ ‘,’‘},{’‘,’ ‘,’ ‘,’ ‘,’‘},
{’ ‘,’
‘,’ ‘,’‘},{’ ‘,’ ‘,’'}}
……
评:初始化的风格很差。

P156~157
例6.6就是用一个一维的字符数组来存放字符串"I am a student."的
评:然而 例6.6 写的却是
char c[15]={‘I’,’ ‘,‘a’,‘m’,’ ‘,‘a’,’ ‘,‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’,’.'};

P157
为了测定字符串的实际长度,C语言规定了一个“字符串结束标志”。
评:C语言确实规定了一个“字符串结束标志”,但并不是“为了测定字符串的实际长度”。所谓“为了测定字符串的实际长度”,属于老谭自己的臆测

ASCII码为0的字符不是一个可以显示的字符,而是一个“空操作符”
评:“字符”怎么成了“操作符”?
概念严重不清

printf(“How do you do?\n”);
在执行此语句时系统怎么知道应该输出到哪里为止呢?实际上,在向内存中存储时,系统自动在最后一个字符’\n’的后面加了一个’\0’,……
评:又见“系统”
“系统”是老谭最得心应手的挡箭牌
说不清楚的事情统统都推给含混不清的“系统”
相信老谭自己也解释不清这两个系统究竟是什么,是不是同一个意思

char c[]=“I am happy”;
这里不像例6.6 那样用单个字符作为字符数组的初值,而是用一个字符串(注意字符串的两端是用双撇号而不是单撇号括起来的)作为初值。
评:1.单个字符不可能作为字符数组的初值
2.“用单个字符作为字符数组的初值”与同页上“例6.6就是用一个一维的字符数组来存放字符串"I am a student."的”自相矛盾
3.用字符串作为数组的初值同样说不通

p160
char str1[5],str2[5],str3[5];
scanf(“%s%s%s”,str1,str2,str3);
输入数据:
How are you?
由于有空格字符分隔,作为3个字符串输入。在输入完后,str1,str2,str3数组的状态如下:
H o w \0 \0
a r e \0 \0
y o u ? \0
数组中未被赋值的元素的值自动置’\0’。
评:荒唐透顶
这又是老谭自己的臆测

char str[13];
scanf(“%s”,str);
如果输入以下12个字符:
How are you?
由于系统把空格作为输入的字符串之间的分隔符,因此只将空格前得字符“How”送到str中。由于把“How”作为一个字符串处理,故在其后加’\0’。str数组的状态为
H o w \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
评:不能因为新手无知就这样糊弄他们吧

可以用下面的输出语句得到数组的起始地址。
printf(“%o”,c);
评:老谭在前面反复强调“C语言本身不提供输入输出语句”(例如第12页)
然后又反复地自己打自己耳光(例如第59页:最常用的语句是:赋值语句和输入输出语句)
这里居然又出现了“输出语句”
如此地颠三倒四
令人简直无语

其次,用%o格式输出c是错误的

2、即使是在C语言中,事实上也容许非C风格字符串(的存储实现),虽然并非C语言标准定义的——例如BSTR。
评:如果是这样,前提是必须给出“字符串”的一般性定义
但老谭的书中并没有给出“字符串”的一般定义
倒是说过“字符串(以’\0’结束的字符序列)”这样的话(161页)
所以可见谭书所说的字符串并非一般意义上的、非C标准定义的字符串

p161
1.puts函数——输出字符串的函数
其一般形式为
puts(字符数组)
评:把puts的参数说成“字符数组”是不对的
(考虑到这里还没有学习指针,把gets的参数说成是字符数组还马马虎虎)

由于可以用printf函数输出字符串,因此puts函数用得不多。
评:这个属于井蛙之见
也表明谭不懂得在何种场合如何恰当地使用puts()函数

用puts函数输出的字符串中可以包含转义字符。例如:
char str[]={“China\nBeijing”};
puts(str);
评:废话
字符串中的字符可以用转义字符的形式写出
跟puts()函数有什么关系

那个例子中的str是多余的
其实可以直接写
puts(“China\nBeijing”);
看来老谭真的以为puts()函数的参数只能是“字符数组”

p162
3.strcat函数——字符串连接函数
其一般形式为
strcat(字符数组1,字符数组2)
评:实际上这两个参数都必须是字符串
第二个参数不必是保存在字符数组中的字符串

7.strlwr函数——转换为小写的函数
其一般形式为
strlwr(字符串)
strlwr是STRing LoWeRcase(字符串小写)的缩写。函数的作用是将字符串中的大写字母换成小写字母。
评:不知道老谭哪儿搞来的这东西
这个函数根本就不是标准函数

从功能上来说
这个函数的参数也不可能是“字符串”,只能是存放在数组中的字符串

p163
5)可以用strncpy函数将字符串2中前面n个字符复制到字符数组1中去。例如:
strncpy(str1,str2,2);
作用是将str2中最前面两个字符复制到str1中,取代str1中原有的最前面2个字符。但复制的字符个数n不应多于str1中原有的字符(不包括’\0’)。
评:对照一下C标准的说法:
The strncpy function copies not more than n characters (characters that follow a null
character are not copied) from the array pointed to by s2 to the array pointed to by
s1. If copying takes place between objects that overlap, the behavior is undefined.
If the array pointed to by s2 is a string that is shorter than n characters, null characters
are appended to the copy in the array pointed to by s1, until n characters in all have been
written.
不难发现谭的说法漏洞百出

p164
7.strlwr函数——转换为小写的函数
其一般形式为
strlwr(字符串)
strlwr是STRing LoWeRcase(字符串小写)的缩写。函数的作用是将字符串中的大写字母换成小写字母。
评:不知道老谭哪儿搞来的这东西
这个函数根本就不是标准函数

从功能上来说
这个函数的参数也不可能是“字符串”,只能是存放在数组中的字符串

p165
库函数并非C语言本身的组成部分,而是C语言编译系统为方便用户使用而提供的公共函数。
评:看来C标准完全可以把库函数部分删除了
这会节约很多纸张

p165~166
例6.8 输入一行字符,统计其中有多少个单词,单词之间用空格分隔开。
#include <stdio.h>
int main()
{
char string[81];
int i,num=0,word=0;
char c;
gets(string);
for(i=0;(c=string[ i ])!=‘\0’;i++)
if(c==’ ')word=0;
else if(word==0)
{word=1;
num++;
}
printf(“There are %d words in this line.\n”,num);
return 0;
}
评:这是老谭书中最好的代码
模仿的是K&R的代码
不过学的不怎么样
毛病很多
首先,c变量是多余的
81这个常数也很怪异
第三,老谭在165页说过使用gets应当在程序开头用 #include <string.h>
但在这里却带头违背
第四 与该代码对应的“N-S流程图”(图6.18)不伦不类且有非常明显的几处错误

欣赏一下K&R的代码
不难发现老谭1538楼的代码如何点金成铁
#include <stdio.h>
#define IN 1
#define OUT 0
main()
{
int c,nl,nw,nc,state;
state = OUT ;
nl = nw = nc = 0 ;
while((c = getchar())!= EOF){
++nc;
if( c == ‘\n’)
++nl;
if( c ==’ ’ || c ==‘\n’ || c ==‘\t’)
state = OUT ;
else if(state == OUT){
state = IN ;
++nw;
}
}
printf(“%d %d %d\n”, nl , nw , nc );
}

K&R 用的标识符是state,老谭用的是word
K&R 用了两个漂亮的#define , 老谭用的是丑陋的0,1
风格方面
K&R :
else if(state == OUT){
state = IN ;
++nw;
}
老谭:
else if(word==0)
{word=1;
num++;
}

p167
C语言把赋值运算作为表达式
评:晕

例 6.9
图 6.19
评:这个图是错误的
而且和题目不相符

解题思路:……然后经过3次两两比较……
评:代码中并没有经过3次两两比较

p168
printf(“\nthe largest string is:\n%s\n”,string);
评:代码丑了点
太爱用printf了
其实可以写为
puts(“\nthe largest string is:”);
puts(string);

运行结果:
Holland
China
America

the largest string is:"
Holland
评:然而说明该例题的图6.19中却是
China
Japan
India
没想到结果居然冲出了亚洲走向了世界

第7章 用函数实现模块化程序设计
7.1 为什么要用函数
p170

7.1 为什么要用函数
评:讲老实话,我不清楚什么叫“模块化程序设计”
老谭也没讲清楚究竟什么叫“模块化程序设计”

看来以其昏昏使人昭昭是不行的

注意:函数就是功能。
评:这是个很滑稽的解释
把英汉词典上的两个义项硬说成是一个
如果这个逻辑成立
那么也完全可以说
(教科书的)谬误(error)就是罪孽(error)或犯罪(error)

图7.1是一个程序中函数调用的示意图。
评:只是一个三叉树而已
无法示意函数调用
函数调用可能比这复杂得多

p171
#include <stdio.h>
int main()
{ void print_star();
void print_message();
print_star();
print_message();
print_star();
}
void print_star()
{
printf(“******************\n”);
}

void print_message()
{printf(“How do you do!\n”);
}

运行结果:


How do you do!

评:把函数原型写在main()之内
是误人子弟的写法

此外函数原型写的要么可以说是错误的,要么就是毫无意义

运行结果是伪造的

在定义这两个函数时指定函数的类型为void,意为函数无类型。
评:函数无类型的说法极其荒谬
C语言中不存在无类型的函数

指定函数的类型为void
应为指定函数返回值的类型为void

一个源程序文件可以为多个C程序共用
评:令人啼笑皆非的说法
p172
C程序的执行是从main函数开始的,如果在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行。
评:这段文字有些莫名其妙,即使从汉语语法的角度来说也有些成问题。成问题的地方在于“在调用后流程返回到main函数”不清楚是前面“如果”的一个结果还是对前面“如果”的一个补充。
抛开这种语文方面的缺点不谈,这段文字给人的印象就是“C程序从main函数开始执行,在main函数结束”。然而事实真的如此吗?
C程序的运行离不开一定的环境(Environment),这种环境叫做执行环境(Execution environment)。运行环境有两种:独立环境(Freestanding environment)和宿主环境(Hosted environment)。
所谓独立环境是指程序并非借助操作系统来运行的,宿主环境则是指程序是在操作系统的控制下执行的。
在这两种环境下,程序开始运行(Program startup)的标志是程序的某个指定的函数开始被调用。
在独立环境中,首先被调用的函数的名字并不一定是main,而是由编译器自行确定的,这叫做由实现定义(Implementation-defined)。甚至这个首先被调用的函数的类型也同样是由实现定义的。
只有在宿主环境下,C程序的运行才是从main()开始的。
因此,“C程序的执行是从main函数开始的”这句话仅仅是在一定的条件下才成立,是片面的。
至于程序在哪个函数结束,C语言从来没有任何规定。程序可能在main()中结束,也可能在其他函数中结束。C语言标准库中有很多函数都与结束程序有关,这些函数的函数原型在stdlib.h中描述,例如
void abort ( void ) ;
void exit ( int ) ;

下面的代码就是一个并非在main()中结束程序的简单示例:
#include “stdlib.h”
void fun ( void ) ;
int main ( void ){
fun();
return 0;
}
void fun ( void ){
exit(1);
}

它是在fun()函数中结束的。

p172
无参函数可以带回也可以不带回函数值,但一般以不带回函数值得居多。
评:毫无意义的废话

所有的函数都有函数值,只是有的函数的返回值是void类型

②有参函数。在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用。
评:思维混乱
居然从有参函数扯到了函数返回值
有参和返回值没关系

例1.3的max函数定义为int型。
评:函数 为 int型
完全不理解数据类型的含义

7.2怎样定义函数
p172
7.2.1为什么要定义函数
C语言要求,在程序中用到的所有函数,必须“先定义,后使用”。例如想用max函数求两个数中的大者,必须事先按规范对它进行定义,指定它的名字、函数返回值类型、函数实现的功能以及参数的个数与类型,将这些信息通知编译系统。这样,在程序执行max时,编译系统就会按照定义时所指定的功能执行。如果事先不定义,编译系统怎么能知道max是什么、要实现什么功能呢!
评:什么叫“先”、什么叫“后”?
如果是指编辑时的时间顺序,毫无疑问这种说法是错误的。因为先写调用,最后完成再函数定义的情况比比皆是
如果是指源代码的空间次序,这种说法也是错误的。因为在多个源文件情况下,根本就谈不上先后
所以,所谓“先定义,后使用”是一句似是而非的废话

“想用max函数求两个数中的大者”,这也是模棱两可的话,什么叫“两个数中的大者”,“两个数”也是含糊不清的概念

“指定它的名字、函数返回值类型、函数实现的功能以及参数的个数与类型,将这些信息通知编译系统”:搞不清这是在说函数定义还是在说函数声明

“在程序执行max时,编译系统就会按照定义时所指定的功能执行”,“编译系统”居然也能“执行”程序了,不管你们信不信,反正我就这么写了

“如果事先不定义,编译系统怎么能知道max是什么、要实现什么功能呢!”:编译系统可以通过函数原型知道max是什么,编译系统并不需要max要实现什么功能

p173
对于C编译系统提供的库函数,是由于编译系统事先定义好的,库文件中包括了对各种函数的定义。
评:编译系统居然还能“定义”函数
库文件中居然包括函数定义

究竟什么叫“定义函数”?
在p172页谭是这样写的

定义函数应包括以下几个内容:
(1)指定函数的名字,……
……
(4)指定函数应当完成什么样的操作
评:编译系统如何指定函数的名字的呢?
……
又是如何指定函数完成的操作的呢?

哪个库文件中写着函数的定义?

1.定义无参函数
……定义无参函数的一般形式为
类型名 函数名()
{
函数体
}
评:1.这是早就过时的写法,应该避免使用
2.函数体包括{}部分

int max(int x,int y)
{int z;
z=x>y?x:y;
return(z);
}
评:不多定义一个多余的变量心里就没底

p174
3.定义空函数
……
类型名 函数名()
{}
评:1.过时的写法
2.分类不当,结构混乱,造成误导

该小节的结构是

7.2.2 定义函数的方法
1.定义无参函数
……
2.定义有参函数
……
3.定义空函数
……
评:给读者造成函数有无参函数、有参函数、空函数三类函数的错觉

7.3 调用函数
p174~175
7.3.1 函数调用的形式
……
按函数调用在程序中出现的形式和位置,可以有以下3种函数调用方式。
1.函数调用语句
……
2.函数表达式
……
3.函数参数
……
评:完全看不出有必要这样划分
三种形式中的函数调用没有任何本质区别
除非对语句、表达式或实参的概念缺乏准确的理解,否则不可能想到这样划分
因为在三种方式中函数调用都是表达式

p175
调用函数并不一定要求包括分号(如print_star();),只有作为函数调用语句才需要有分号
评:前面说“不一定要求包括分号”,可“如”的却是有分号,混乱不清自相矛盾,即使作为小学生作文也是不合格的
“只有作为函数调用语句才需要有分号”更是胡扯
for(i=0;i<strlen(s);i++)这个怎么算?

实际上函数调用和分号扯不上半点关系
老谭把他们放在一起说纯属概念混乱纠缠不清

实际参数可以是常量、变量或表达式。
评:就如同说
应该多吃青菜、白菜和蔬菜
可以看出老谭连什么是表达式都不清楚

p176
在调用函数过程中发生的实参与形参间的数据传递,常称为“虚实结合”
评:这回没用BASIC冒充C
改用FORTRAN冒充了
为此还特意捏造了一个“虚拟参数”的概念

p177
实际参数可以是常量、变量或表达式。
评:参见1763楼
(就如同说
应该多吃青菜、白菜和蔬菜
可以看出老谭连什么是表达式都不清楚)

如果说不懂指针就不完全懂得C的话
那么不懂表达式就完全不懂得C语言

很不幸
老谭属于后者

如果实参为int型而形参x为float型,或者相反,则按不同类型数值的赋值规则进行转换
评:这个是有前提的
按照老谭写函数声明的风格就不会

字符型与int可以互相通用
评:这个是胡扯
不同的字符类型都不能通用
遑论字符类型与int类型

调用结束,形参单元被释放。注意,实参单元仍保留并维持原值,没有改变。
实参和形参在内存中占有不同的存储单元
评:压根就不存在实参单元这种东西

p178
赋值语句把这个函数值赋给变量。
评:然而在该书的第12页、第67页
老谭煞有介事地铁口直断

C语言本身不提供输入输出语句。
评:自己扇自己耳光
一点感觉都没有吗
境界啊

max(int x,int y)
{
return ((x>y)?x:y);
}
评:然而在同一页上居然写着
注意:在定义函数时要指定函数的类型。

例7.3 将例7.2稍作改动,将在max函数中定义的变量z改为float型。函数返回值的类型与指定的函数类型不同,分析其处理方法。
评:例7.2 的要求是“输入两个整数,要求输出其中的大者。要求用函数找到大者。”
相应的max()函数定义是
int max(int x,int y)
{
int z;
z=x>y?x:y;
return(z) ;
}

怎么可以胡乱“将max函数中定义的变量z改为float型”呢?
难道可以不顾要求
仅仅为了要说明一下“函数返回值与指定的函数类型不同,按照赋值规则处理”(注“函数类型”也是个错误的术语)
就胡乱修改代码?

int max(float x,float y)
{
float z;
z=x>y?x:y;
return(z) ;
}
评:这改成什么了呢?函数的功能不伦不类简直无法言说

这是一种很奇怪的无病呻吟
不是因为痒了而去挠
而是为了挠一下故意装得有点痒
更何况return 的表达式与函数返回值的类型不一致本来就是一种很差劲的代码风格

p179
(4)对于不带回值的函数,应当用定义函数为“void”类型(或称“空类型”)。……此时在函数体中不得出现return语句。
评:应当用定义函数为“void”类型 : 这不是话

此时在函数体中不得出现return语句:胡扯

float z; //z为实型变量
评:“实型”是个含义不清的概念

7.4 对被调用函数的声明和函数原型
p180
如果不包含“stdio.h”文件中的信息,就无法使用输入输出库中的函数。同样,使用数学库中的函数。应该用#include <math.h>。
评:这给读者一种错觉
以为编译器提供的库有多个

如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明(declaration)。
评:把函数类型声明放在函数体内是一种拙劣的风格
会造成函数体内代码的污染
并且可能需要多次进行不必要的函数类型声明
“函数的位置”是含混不清的说法
应该是函数定义的位置

p181
如果没有对函数add的声明,当编译到程序第7行时,编译系统无法确定add是不是函数名,
评:编译器可以根据运算符()来确定add是一个函数
尽管应该写函数声明,但不是因为不写编译系统就无法确定add是不是函数名

如果不作检查,在运行时才发现实参与形参的类型或个数不一致,出现运行错误。大体上可猜出这是一个输出学号,性别和成绩的函数。
评:运行时可以产生运行错误
但不可能发现实参与形参的类型或个数不一致

使用函数原型作声明是C的一个重要特点。
评:这个又是在信口开河了
C语言最初并没函数原型这个概念
这是向C++学来的
正因为如此
这方面无论在理论还是实践方面目前都有些混乱
老谭自己不就是把函数声明写得乱七八糟吗
一会用函数原型
一会用过时的函数声明
甚至把许多过时的东西写在书里吗

用函数原型来声明函数,能减少编写程序时可能出现的错误。由于函数声明的位置与函数调用语句的位置比较近,因此在写程序时便于就近参照函数原型来书写函数调用,不易出错。
评:没头没脑
怎么就“由于”了呢?

函数原型是给谁看的?难道是给程序员吗

p182
对函数的“定义”和“声明”不是一回事。函数的定义是指对函数功能的确立,包括指定函数名,函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包括函数体。
评:显著的错误是检查“函数名是否正确”,函数声明不检查这个

对函数的“定义”和“声明”不是一回事:实际上函数定义也具有函数声明的作用。因此就说明函数名性质这个角度来说,函数的定义也是函数声明的一种
如果从狭义的角度来理解的话,即“定义”和“声明”不是一回事,那么后面的叙述——“函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统”就是错误的
这里混淆了函数声明与函数原型的概念

此外,“函数值类型”这个说法是可接受的,但后面又说“函数类型”则是错误的。“函数类型”和“函数值类型”根本就不是一回事

用了有意义的参数名有利于理解程序,如:
void print(int num,char XXX,float score); (注:XXX处的原文可能很黄,发不出去,只好以XXX代替)
大体上可猜出这是一个输出学号,性别和成绩的函数。
评:这种猜测不但是危险的、盲目的
也是违背软件工程基本原则的
放纵这种猜测
必然导致BUG丛生

而且函数原型的目的并不是让人照着写函数调用
其意义主要在于给编译器看

7.5 函数的嵌套调用
p183
7.5 函数的嵌套调用
……
(5)执行b函数,如果再无其他嵌套的函数,则完成b函数的全部操作;
(6)返回到a函数中调用b函数的位置;
(7)继续执行a函数中尚未执行的部分,直到a函数结束;
评:实际上应该是遇到return返回或直到函数结束返回

p184
程序分析:可以清楚地看到,在主函数中要调用max4函数,因此在主函数的开头要对max4函数作声明。……
评:的确“可以清楚地看到”
然而可以更清楚看到的是这种写法把各个函数污染得乱七八糟

程序改进:
(1)……
int max2(int a,int b)
{ return (a>b)?a:b; }
评:看不懂这究竟在是“改进”还是“改退”

(2)在max4函数中,3个调用max2的语句(如m=max2(a,b);)可以用以下一行代替:
m=max2(max2(max2(a,b),c),d);
甚至可以取消变量m,max4可写成

int max4(int a,int b,int c,int d)
{ int max2(int a,int b);
return max2(max2(max2(a,b),c),d);
}
评:max2(max2(max2(a,b),c),d) 显然不如 max2 ( max2(a,b) , max2(c,d) )
而且这一切有什么必要放在max4()里完成呢?
放在main()里难道不是“甚至可以取消”函数max4()吗?
函数max4()难道不是无病呻吟吗
不仅max4()是无病呻吟
max变量,m变量也是如此

通过此例,可以知道,不仅要写出正确的程序,还要学习怎样使程序更加精炼、专业和易读。
评:通过此例,只能看到如何使程序更繁复、业余和晦涩

精炼、专业和易读的代码是这样的
#include <stdio.h>
int max(int ,int );
int main( void )
{
int integer_1 , integer_2 , integer_3 , integer_4 ;
printf( “输入4个整数:” ); //假洋鬼子才写interger
scanf( “%d%d%d%d” ,
&integer_1,&integer_2,&integer_3,&integer_4);
printf( “max=%d\n” ,
max( max(integer_1,integer_2) , max(integer_3,integer_4) ) );
return 0;
}
int max( int i , int j)
{
return ( i > j )? i: j ;
}

7.6 函数的递归调用

C语言的特点之一就在于允许函数的递归调用。
评:这是在没话找话吧
很多语言都“允许函数的递归调用”
这怎么成了“C语言的特点”了呢

p185
int f(int x)
{
int y,z;
z=f(y);
return (2*z);
}
评:这个例子要多烂有多烂
简直是教唆读者如何写烂代码:

  1. x没用
  2. 使用垃圾值y
  3. 无限递归
  4. return 一句是无效代码

p186
int age(int n)
{
int c;
if(n==1)
c=10;
else
c=age(n-1)+2;
return©;
}

评:return©; 中的()很搞笑

那个c是多余的
可能有人觉得是为了单一出口
我个人觉得那很做作
并没有收获到实惠

p188
#include <stdio.h>
int main()
{ int fac(int n);
int n;
int y;
printf(“input an integer number:”);
scanf(“%d”,&n);
y=fac(n);
printf(“%d=%d\n”,n,y);
return 0;
}
int fac(int n)
{
int f;
if(n<0)
printf(“n<0,data error!”);
else if(n0||n1)
f=1;
else f=fac(n-1)*n;
return(f);
}

评:n<0的判断本来应该是main()的任务
却硬塞到了fac()内
得到的是一个毫无意义的怪胎
如果真的输入一个负数
会得到更怪的怪胎

input an integer number:-1
n<0,data error!-1!=65

除了可以让人们观瞻一下代码可以丑陋到何种程度
这个fac()没有任何价值
更可笑的是老谭居然宣称他用这个代码计算出了31!

……当n=31时,运行正常,输出为
input an integer number:31
31!=738197504
评:用脚后跟也应该能想到738197504不是31!的阶乘吧

p191
void hanoi(int n,char one,char two,char three) //定义hanoi函数
//将n个盘子从one座借助two座,移到three座
{
……
}
评:这是一种令人作呕的注释,污染代码
另:该页上的main()缺少return 0;语句

7.7 数组作为函数参数

p192
实参可以是常量、变量或表达式。
评:逻辑错乱
茄子、辣椒或蔬菜

凡是变量可以出现的地方,都可以用数组元素代替
评:int i;
这个 i 变量出现的地方呢?

数组名也可以作实参和形参
评:数组名根本不能作形参

p193
数组元素可以用作函数实参,不能用作形参
评:这个没什么问题
问题在于下面的“因为”

因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元
评:这就是在胡扯了
1.auto 变量都是临时的,不仅仅是形参
2.什么时候也不可能为数组元素单独分配存储单元,这跟“形参是在函数被调用时临时分配存储单元的”有个屁关系

在用数组元素作函数实参时,把实参的值传给形参,是“值传递”方式
评:就如同在说
今天是中秋,早上太阳是从东边出来的
实际上C语言只有值传递
和用什么作实参没有关系

例 7.9 输入10个数,要求输出其中值最大的元素和该数是第几个数,
评:题目本身就很蠢
“输入10个数”,但没明确什么样的数
“元素”是代码层面的术语,却和问题的描述混为一谈

#include <stdio.h>
int main()
{int max(int x,int y);
int a[10],m,n,i;
printf(“enter 10 integer numbers:”);
for(i=0;i<10;i++) //输入10个数给a[0]~a[10]
scanf(“%d”,&a[ i]);
printf(“\n”);
for(i=1,m=a[0],n=0;i<10;i++)
{
if(max(m,a[ i])>m)
{m=max(m,a[ i]);
n=i;
}
}
printf(“The largest number is %d\nit is the %dth number.\n”,m,n+1);
}

int max(int x,int y)
{
return(x>y?x:y);
}

评:对愚蠢的完美诠释
1
根本不需要数组
2
输入10个数给a[0]~a[10]
3
printf(“\n”);
4
if(max(m,a[ i])>m)
{m=max(m,a[ i]);
n=i;
}
调用 max()两次

这里其实可以简洁地写为
for( i = 1 , n = 0 ; i < 10 ; i++ )
if(max(a[n] , a[ i]) > a[n] )
n = i ;
当然这种做法还是比较啰嗦
5
缺少 return 0

p194
当然,本题可以不用max函数求两个数中的最大数,而在主函数中直接用if(m>a[ i])来判断和处理。本题的目的是介绍如何用数组元素作为函数实参

评:用愚蠢的数据结构和愚蠢的算法介绍数组元素作为函数实参
读者最多只能学会怎样愚蠢地用数组元素作实参

用数组元素作实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。

评:这个是误导
不管什么作实参,传的都是“值”,数组名作实参也不例外
此外形参不可能是数组名

#include <stdio.h>
int main( )
{ float average(float array[10]);
float score[10],aver;
int i;
printf(“input 10 scores:\n”);
for(i=0;i<10;i++)
scanf(“%f”,&score[i]);
printf(“\n”);
aver=average(score);
printf(“average score is%5.2f\n”,aver);
return 0;
}

float average(float array[10])
{int i;
float aver,sum=array[0];
for(i=1;i<10;i++)
sum=sum+array[i];
aver=sum/10;
return(aver);
}
评:float average(float array[10])
中的10非常愚蠢

p195
用数组名作函数参数,应该在主调函数和被调函数分别定义数组,例中array是形参数组名,score是实参数组名,分别在其所在函数中定义,不能只在一方定义。

评:没弄清什么叫定义数组

在定义average函数时,声明数组的大小为10,但在实际上,指定其大小是不起任何作用的,
评:不起作用为什么要写?
压根就不该写

形参数组可以不指定大小,在定义数组时在数组名后面跟一个空的方括号,如:
float average(float array[])

评:不指定大小是对的
但不指定数组的尺寸则是误导
写出的函数是半身不遂的函数

例7.11 有两个班级,分别有35名和30名学生,调用一个average函数,分别求这两个班的学生的平均成绩。
评:调用一个average函数,分别求这两个班的学生的平均成绩:病句

实际的代码居然是

float score1[5]=……
float score2[10]=……

巨无聊

拙劣的题目可以伤害正常的逻辑思维能力

p196
用数组名作函数实参,不是把数组元素的值传递给形参,而是把实参数组的首元素的地址传递给形参数组,这样两个数组就共占同一段内存单元。
评:“两个数组”,是一种捏造

用数组名作函数实参,不是把数组元素的值传递给形参,而是把实参数组的首元素的地址传递给形参数组,这样两个数组就共占同一段内存单元。
评:“两个数组”,是一种捏造

p197
可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。例如:
int array[3][10];

int array[][10];
评:写那个3是误导,因为根本没必要
此外,形参声明是根本不可能带那个“;”的,除非是古代的C语言

p198
由于形参数组与实参数组类型相同
评:这个错的没边了
它们的类型压根就不一样

在第2维大小相同的前提下,形参数组的第1维可以与实参数组不同。例如,实参数组定义为
int score[5][10];
而形参数组定义为
int score[][10];

int score[8][10];均可以。
评:这是把无聊当知识卖弄
写那个8毫无意义

这时形参数组和实参数组都是由相同类型和大小的一维数组组成的
评:形参根本就不可能是数组(即使形式上是,但那叫不完整类型)
更不可能由一维数组组成

int max_value(int array[][4])
……
评:这是在教人写半身不遂的函数
这种函数没有价值和适应性
因为它本身依赖于一个莫名其妙的Magic number
7.8 局部变量和全局变量
p199
定义变量可能有3种情况:
(1)在函数开头定义;
(2)在函数内的复合语句内定义;
(3)在函数的外部定义。
评:错
这表明老谭所号称的这本书是“按照C语言的新标准C99进行介绍”是蒙人的
老谭连C99门都没摸到

实际上只有两种可能:函数内和函数外

P200
(4)在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效。这种复合语句也称为“分程序”或“程序块”。
在函数内定义的变量是局部变量,在函数之外定义的变量是全局变量。
评:“分程序”或“程序块”是老谭捏造的新概念
第一不必要,因为有复合语句的概念,没必要再给它两个别名
第二不合理,因为源程序的下一层是函数定义,再下一层才是复合语句的层次,所以说“复合语句”是分程序非常荒谬
这种复合语句也称为“分程序”或“程序块”。:语文问题。既然是“这种”,就意味着还有“那种”,实际上根本没有。

P201
int p=1,q=5; //定义外部变量。
int c1,c2; //定义外部变量。
p,q,c1,c2都是全局变量,
评:概念不统一

设置全局变量的作用是增加了函数间数据联系的渠道。
评:这个说法违背结构化程序设计原则,是误导

如果在一个函数中改变了全局变量的值,就能影响到其他函数中全局变量的值。
评:“其他函数中全局变量”,这叫什么话?

由于函数的调用只能带回一个函数返回值,因此有时可以利用全局变量来对增加函数间的联系渠道,通过函数调用能得到一个以上的值。
评:这个外行,竟然用这种垃圾理论来蒙学生
完全不顾结构化程序设计最基本的原则

在C程序设计人员中有一个习惯(但非规定),将全局变量名的第个字母用大写表示。
评:有这事儿吗?
从没听说过
老谭捏造也不是第一次了

例7.14 有一个一维数组,内放10个学生成绩,写一个函数,当主函数调用此函数后,能求出平均分、最高分和最低分。
评:题目要求很变态

解题思路:调用一个函数可以得到一个函数返回值,现在希望通过函数调用能得到3个结果。可以利用全局变量来达到此目的。
评:解题思路更变态
P202
#include <stdio.h>
float Max=0,Min=0;
int main()
{ float average(float array[],int n);
float ave,score[10];
int i;
printf(“Please enter 10 scores:”);
for(i=0;i<10;i++)
scanf(“%f”,&score[i]);
ave=average(score,10);
printf(“max=%6.2f\nmin=%6.2f\naverage=%6.2f\n”,Max,Min,ave);
return 0;
}
float average(float array[],int n)
{ int i;
float aver,sum=array[0];
Max=Min=array[0];
for(i=1;i<n;i++)
{if(array[i]>Max)Max=array[i];
else if(array[i]<Min)Min=array[i];
sum=sum+array[i];
}
aver=sum/n;
return(aver);
}

评:代码则令人作呕
这是在教唆别人学坏
那两个外部变量要多恶心有多恶心

由于Max和Min是全局变量
评:C语言只有外部变量,没有全局变量这个概念

建议不在必要时不要使用全局变量,原因如下:
①全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
评:1
全局变量为外部变量之误
2
在程序的全部执行过程中都占用存储单元,并非不应该使用外部变量的原因

在某些语言里,全局变量的意思是只要定义了,其他模块就可见

在C语言中没有这样的东西

外部变量有两种 extern 或 static
前一种其他模块可以用,但在使用前应该声明(这个应该是和全局变量的一个最主要的区别)
后一种其他模块不可以用

使用外部变量,最主要的问题是破坏了良好的程序结构,使程序的各个部分联系更紧密了

如果全部执行过程都占有单元也算一个缺陷的话,那static 局部变量怎么说,也不应该?有些变量确实需要一直占用内存

P203
②它使函数的通用性降低了
评:应该是这样的函数没有通用性

例7.15
评:对全局变量(外部变量)b的作用范围的标示是错误的
7.9 变量的存储方式和生存期
P204
变量可以分为全局变量和局部变量。
评:全局变量是个既不正确也很糟糕的概念
在有的语言里
全局变量的意思是只要定义,就全局可见
但C语言中外部变量并没有这样的性质
首先,外部变量的作用域只是定义点到所在源文件(或编译单元结束)
其他源文件使用前必须声明而不是可以直接拿过来使用
而且有的外部变量(static)其他源文件根本不能使用
即使在所在文件,外部变量也可能需要声明才能使用(定义在使用点后面的情形)
“全局”这两个字含糊掩盖了以上所提到的外部变量的精确含义
对于初学者来说尤其如此

有的变量则是在调用其所在的函数时才临时分配内存单元,而在函数调用结束后该存储单元就马上释放了,变量不存在了。
评:这个说法是错误的
局部非static变量不是在调用其所在函数后存在的
应该是程序执行到其所在块声明点之后才存在的
释放也是如此

变量的存储有两种不同的方式:静态存储方式和动态存储方式
评:这个基本属于捏造
变量的存储方式没什么静态动态之分
变量的生命期有static storage duration 和 automatic storage duration

静态存储方式是指在程序运行期间由系统分配的存储空间的方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配空间的方式
评:“静态存储方式是指在程序运行期间由系统分配的存储空间的方式”
这个是错误的,static storage duration的变量在程序运行前就存在了
这里所谓的“由系统分配”是在打马虎眼,搞不清这是什么系统
如果是编译系统,那么后者也是由系统分配

先看一下内存中供用户使用的存储空间的情况。这个存储空间可以分为3部分:
(1)程序区。
(2)静态存储区。
(3)动态存储区。
评:这个说法没有任何根据,更荒谬的是

在动态存储区存放以下数据:
(1)函数形式参数。在调用函数时给形参分配空间。
(2)函数中定义的没有用关键字static声明的变量,即自动变量。
(3)函数调用时的现场保护和返回值等。
评:(1)、(2)都是错误的,因为还有register
(3)是一种没有根据的猜测或根据个别实现的情况作出的不完全归纳

P205
即长如果用户不指定
静态的(statis)
根据变量的存储类别,可以知道变量的作用域和生存期。
评:变量的存储类别与变量的作用域没有半毛钱关系

C的存储类别包括4种
评:实际上是5种

函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。
评:这个说法是错误的
“动态存储区”是老谭捏造的一块“内存”区域
而非static的局部变量,未必放在内存之中

函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量)……在函数调用结束时就自动释放这些存储空间。
评:块作用域的auto局部变量不是在函数结束时释放,而是在块结束时释放

关键字“auto”可以省略,不写auto则隐含指定为“自动存储类别”
评:错
应该是没声明存储类别的局部变量为自动存储类别

P206
static c=3;
评:老谭号称是按照C99写的
但这种写法在C99是禁止的
一句话,挂羊头卖狗肉

对应的运行结果也是货不对板
7
8
9

评:最后“-”莫名其妙
P207
什么情况下需要用局部静态变量呢?……例如可以用下面的方法求n!。
例7.17 输出1到5的阶乘值。
#include <stdio.h>
int main( void )
{int fac(int n);
int i;
for(i=1;i<=5;i++)
printf(“%d!=%d\n” ,i,fac(i) );
return 0;
}
int fac(int n)
{static int f=1;
f=f*n;
return(f);
}
评:误导
这不是正确使用static 局部变量的方法
因为这个fac()函数和那啥一样
只能一次性使用
甚至连一次性都算不上
如果题目要求求 1、3、5的阶乘
立刻就会发现fac()的残疾
如果函数中的变量只被引用而不改变值,则定义为静态局部变量(同时初始化)比较方便,以免每次调用时重新赋值。
评:既然不改变,为什么不用常量?外部变量等技术手段?
究竟何时使用局部静态变量以及如何使用,看来老谭根本不清楚
所以无论给出的例子还是讲解都是错误的

用静态存储要多占内存(长期占用不释放,而不是像动态存储那样一个单元可以先后为多个变量使用,节约内存)……而且降低了程序的可读性……
评:降低程序可读性是无稽之谈
动态存储节约内存也是无稽之谈

评:注意这里的静态存储与外部还是局部变量无关
局部变量也可以是静态生命期
可笑的是你把静态存储理解为非局部变量

P208
寄存器存储在CPU中的寄存器中
评:这根本就不是话

全局变量都是存放在静态存储区中的。因此它们的生存期是固定的,存在于程序的整个运行过程。
评:1.全局变量是外部变量之误
2.外部变量存放在哪里跟程序员无关,这是编译器作者的事情
3.外部变量的生存期和它存放在哪里没有因果关系
总之一堆错话与废话
实际上只要一句话就足够了:外部变量具有静态存储持续期(static storage duration),在程序运行过程中一直存在。

一般来说,外部变量是在函数外部定义的全局变量。
评:这个胡扯太过分了
如果“外部变量是在函数外部定义的全局变量”
那么老谭能否说说什么是在函数内部定义的全局变量

一般来说,外部变量是在函数外部定义的全局变量。它的作用域是从变量的定义处开始,到本程序文件的末尾。
评:变量的作用域并不是从变量的定义处开始
而是从变量的声明处开始

但有时设计人员希望能扩展外部变量的作用域。
评:不存在所谓作用域扩展
作用域是声明决定的

  1. 在一个文件内扩展外部变量的作用域
    评:这是一个伪命题
    在C里根本就不存在扩展变量作用域
    变量的作用域决定于声明的位置

如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。
评:1.外部变量的作用域取决于声明的位置
2.那个“如果”莫名其妙,外部变量的作用域无论你“如果”还是不“如果”都是从声明处到文件的结束

P208~212
7.9.3 全局变量的存储类别。
1.在一个文件内扩展外部变量的作用域
2.将外部变量的作用域扩展到其他文件
3.将外部变量的作用域限制在本文件中
评:大标题是“存储类别”
下面三个下标题却都是讲“作用域”
思维混乱竟至于此
P209
#include <stdio.h>
int main()
{int max();
extern int A,B,C; //把外部变量A,B,C的作用域扩展到从此处开始
printf(“Please enter three integer numbers:”);
scanf(“%d%d%d”,&A,&B,&C);
printf(“max is %d\n”,max());
return 0;
}

int A,B,C;

int max()
{int m;
m=A>B?A:B;
if(C>m)m=C;
return(m);
}
评:所谓“把外部变量A,B,C的作用域扩展到从此处开始”是误导,实际上是在main()中声明这三个变量,以便在该函数内使用
代码风格拙劣,思想垃圾,简直是教唆别人如何写垃圾代码
由于A,B,C是外部变量,所以在调用max函数时用不到参数传递。在max函数中可直接使用外部变量A,B,C的值。
评:这是在教读者如何用最荒唐的方法使用外部变量

用extern声明外部变量时,类型名可以写也可以不写。例如,“extern int A,B,C;”也可以写成“extern A,B,C;”。因为它不是定义变量,可以不指定类型,只须写出外部变量名即可。
评:这是在信口开河,胡说八道

P210
extern Num
评:错误性质同前

例7.19 给定b的值,输入a和m,求a*b和a^m的值。
评:这也能叫题目?

文件file1.c
int A;
int main()
{int power(int);
int b=3,c,d,m;
printf(“enter the number a and its power m:\n”);
scanf(“%d,%d”,&A,&m);
c=Ab;
printf("%d
%d=%d\n",A,b,c);
d=power(m);
printf(“%d**%d=%d\n”,A,m,d);
return 0;
}
文件file2.c
extern A;
int power(int n)
{int i,y=1;
for(i=1;i<=n;i++)
y*=A;
return(y);
}
评:extern A的错误不算
这个例题成功地应用外部变量把代码写得无比垃圾
没有足够多的愚蠢是无论如何也写不出如此垃圾的代码的
首先3是常量,却画蛇添足地赋值给b
A,b,c,d无比垃圾式的命名
毫无必要的变量c,d及赋值
毫无通用性的函数power()
怪异的return(y);
业余的循环for(i=1;i<=n;i++)

实际上,在编译时遇到extern 时,先在本文件中找外部变量的定义,如果找到,就在本文件扩展作用域;如果找不到,就在连接时从其他文件中找外部变量的定义。如果从其他文件中找到了,就将作用域扩展到本文件;如果再找不到就按出错处理。
评:“先后理解成在限定一个翻译单元内的代码顺序解析能说得通。当然先定义后使用的说法是错的,应该是先声明后使用。”
这个完全同意。只能在同一个翻译单元内谈先后
而且应该是先声明后使用而不是先定义后使用
不止如此,至少还是有遗漏的。
int(*foo(void))[3]
{
/return…/
}
这里的“类型”是都能写在函数名左边的“类型名”吗?
谢谢补充

P212
对一个数据的定义,需要指定两种属性:数据类型和存储类别,分别使用两个关键字。
评:错乱不堪
数据包括常量与变量
常量即不存在定义的问题
也无需指定存储类别
对于变量来说
存储类别也不一定通过关键字指定
"分别使用两个关键字"更是胡扯
至少还有两种情况:一个关键字不足以说明类型,仅用关键字不足以说明类型

可以用extern声明已定义的外部变量,例如:
extern b;
评:这个错误已经出现至少十几次了

自动变量,即动态局部变量(离开函数,值就消失)
寄存器变量(离开函数,值就消失)
评:这里有两个错误
1.不是值消失,而是对象消失
2.“离开函数”这个说法是错误的,应该是离开语句块

从作用域角度分,有局部变量和全局变量。它们采用的存储类别如下:
评:1.全局变量是个错误的概念
2.存储类别是个含糊不清的概念

寄存器变量(离开函数,值就消失)
评:“函数”、“离开”和“值”这三个概念都有错误
分别应该是“语句块”、“结束”和“数据对象”

静态外部变量
全局变量
外部变量
评:首先“全局变量”是无中生有
其次把全局变量分为静态外部变量和外部变量很错乱
第三和200页老谭给出的定义自相矛盾

从变量存在的时间(生存期)来区分,有动态存储和静态存储两种类型。
评:“动态存储”的说法值得商榷
根据2006楼 幻の上帝 的帖子
在C99中
There are three storage durations: static, automatic, and allocated.
如果“动态”是来自automatic的话,这个翻译明显不当
而若根据《C语言参考手册》
storage duration可分为static extend,local extend和dynamic extend
这里的dynamic 倒是可以译为“动态”,但其含义和老谭所说的截然不同
P213
(本函数内有效)
评:这个错误出现多次

静态外部变量(本文件内有效)
评:这个说法是错误的
在声明点之前无效

外部变量(用extern声明后,其他文件可引用)
评:明显的词不达意,该回去重修小学语文

图7.20是生存期的示意图
评:图7.20中,把f2()函数中的局部静态变量c的生存期从f2()被调用开始算起,是错误的

如果一个变量在某个文件或函数范围内是有效的,就称该范围为该变量的作用域,在此作用域内可以引用该变量,在专业书中称变量在此作用域内“可见”
评:这个是似是而非、不求甚解的蒙人的说法
作用域和可见性是两回事
在作用域内未必可见

表7.2表示各种类型变量的作用域和存在性的情况。
评:表7.2是非常荒谬的一张表格
比如
认为外部变量的作用域涵盖函数内与函数外
实际上函数内与函数外都可能不是外部变量的作用域
因为外部变量的作用域从声明处开始到源文件结束或到所在块的结束处
因为外部变量的作用域从定义处开始到源文件结束
定义前面的部分可能不属于其作用域
再比如
以函数内外来描述变量的存在性也很荒谬
7.10 关于变量的声明和定义
P214
在第2章介绍了如何定义一个变量。
评:第2章并没有介绍如何定义变量

从第2章已经知道,一个函数一般由两部分组成:声明部分和执行语句。
评:1
第2章并没有介绍函数由声明部分和执行语句两部分组成
2
函数也并不是由声明部分和执行语句两部分组成

函数的声明是函数的原型
评:这个恰恰说反了
函数的原型是函数的声明(的一种)

对被调用函数的声明是放在主调函数的声明部分中的
评:语法上没有错误
但实际上是作茧自缚
不仅可能不必要地多次声明
而且污染主调函数的代码

对“int a;” 而言,它既是声明,又是定义;而对“extern a;”而言,它是声明而不是定义。
评:1.
如果
int a;
的位置在{}之内,可以说“它既是声明,又是定义”
如果在{}之外,它可能不是定义
2.
extern a;
这种写法本身是错误的
在C89中只有int类型可以这样写
在C99中不容许省略int
而且,不可能仅仅根据具有extern这个storage-class specifiers这一点就断定它一定是声明而非定义

int main()
{
extern A; //是声明,不是定义,声明将已定义的外部变量A的作用域扩展到此
.
.
.
return 0;
}

int A; //是定义,定义A为整型外部变量
评:extern A; //是声明,不是定义,声明将已定义的外部变量A的作用域扩展到此:
这个确实是声明,不是定义。但并不是“将已定义的外部变量A的作用域扩展到此”,这个标识符A本身有自己的作用域,即声明点到}。这个声明同时说明了A的定义在别处。此外,这个声明应该写明A的类型

int A; //是定义,定义A为整型外部变量:
这个说法是武断的。这个可能是定义也可能不是
例如
file1.c
int main( void )
{
extern int A;
printf(“%d\n”,A);
return 0;
}
int A;//这个不是定义

file2.c
int A=3;//这个才是定义
.

在同一文件中,可以有多次对同一外部变量的声明,它的位置可以在函数之内(哪个函数要用就在哪个函数中声明),也可以在函数之外(在外部变量的定义点之前)。
评:话确实可以这样说
但其出发点则是愚蠢的
尤其是“哪个函数要用就在哪个函数中声明”
7.10 内部函数和外部函数
P215
在定义内部函数时,在函数名和函数类型的前面加static,即:
static 类型名 函数名(形参表);
评:这不是定义

通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用。
评:毫无道理
莫名其妙
这不是“通常”的做法,而是“反常”的做法
P216
例 7.20 有一个字符串,内有若干个字符,现输入一个字符,要求程序将字符串中该字符删去。用外部函数实现。
评:“内有若干个字符”这种废话绝对是谭氏体
字符串里除了有若干个字符还能有什么呢
P216~217
file1.c(文件1)
#include <stdio.h>
int main()
{
extern void enter_string(char str[]);
extern void delet_string(char str[],char ch);
extern void print_string(char str[]);
char c,str[80];
enter_string(str);
scanf(“%c”,&c);
delet_string(str,c);
print_string(str);
return 0;
}

file2.c(文件2)
void enter_string(char str[80])
{
gets(str);
}

file3.c(文件3)
void delet_string(char str[],char ch)
{int i,j;
for(i=j=0;str[ i]!=‘\0’;i++)
if(str[ i]!=ch)
str[j++]=str[ i];
str[j]=‘\0’;
}

file4.c(文件4)
void print_string(char str[])
{
printf(“%s\n”,str);
}。
评:这段源程序有下面这些毛病

extern void enter_string(char str[]);
extern void delet_string(char str[],char ch);
extern void print_string(char str[]);

写在main()中
污染了main()
而且这样很难保证函数类型声明的一致性

P217
一般都省写extern,例如例8.20程序中main函数的第一个函数声明可写成
void enter_string(char str[]);
评:“一般都省写extern”是误导,C语言鼓励程序员明确
此处是第7章,居然扯出个“例8.20”

第8章 善于利用指针
P220
将地址形象化地称为“指针”
评:这是胡扯
多数人指针学得稀里糊涂不是因为别的
就是因为他们把指针和地址混为一谈

地址指向该变量单元。
评:关于地址,老谭是这样说的“内存区的每一个字节有一个编号,这就是‘地址’”

然而,变量单元所占的可能不只一个字节
所以地址指向变量单元的说法是荒谬的
P220~221
由于通过地址能找到所需的变量单元,因此说,地址指向该变量单元。
评:车轱辘话来会说
此外通过地址并不能找到所需的变量单元
P221
一个变量的地址称为该变量的“指针”
评:这是自己扇自己耳光
前面老谭说“内存区的每一个字节有一个编号,这就是‘地址’”
变量可能占几个字节,哪来的地址?

这句话的错误还体现在
指针未必和变量相关

如果有一个变量专门用来存放另一个变量的地址(即指针),则它称为“指针变量”
评:扯淡
再次混淆指针与地址这两个概念
“专门用来存放另一个变量的地址”,井蛙之见
double (*p)( double )=sin;
p算不算是指针变量?
如果不算它算什么?
如果算,它存放了哪个变量的地址啦?

指针变量就是地址变量
评:C语言中压根就没有“地址变量”这种东西

指针变量的值是地址(即指针)
评:再次混淆指针与地址这两个概念
这是把读者往沟里带
P222
第8行输出pointer_1和pointer_2的值。其中的“”表示“指向”
评:“
”表示“指向”是胡扯
在这里这是一个运算符
P223
定义指针变量的一般形式为
类型名 指针变量名;
评:void (
(*p) (void () (char *, long , long ))) (char *, long , long);
这个不知道老谭如何解释

int **pointer_1,*pointer_2;
左端的int是在定义指针变量时必须指定的“基类型”。
评:“基类型”是臆造出的概念
P224
(int *),(float *),(char *)是3种不同的类型
评:(int *),(float *),(char *) 根本就不是类型

指针变量中只能存放地址(指针),不要将一个整数赋给一个指针变量。如:
*pointer_1=100;
…系统…判为非法。
评:莫名其妙
这根本不是“将一个整数赋给一个指针变量”
也根本不会“判为非法”

在引用指针变量时,可能有3种情况:
(1)给指针变量赋值。……
(2)引用指针变量指向的变量。
(3)引用指针变量的值。如:
printf(“%o”,p);
评:搞笑。岂止有3种情况
printf(“%o”,p); 是野路子写法, "%o"是转换输出unsigned类型数据的
P225
例8.2输入a和b两个整数,按先大后小的顺序输出a和b。

#include <stdio.h>

int main()
{int *p1,*p2,*p,a,b;
printf(“please enter two integer numbers:”);
scanf(“%d,%d”,&a,&b);
p1=&a;
p2=&b;
if(a<b)
{p=p1;p1=p2;p2=p;}
printf(“a=%d,b=%d\n”,a,b);
printf(“max=%d,min=%d\n”,*p1,*p2);
return 0;
}
评:交换两个指针完全是脱裤子放屁

#include <stdio.h>

int main( void )
{
int *p1,*p2,a,b;

printf(“please enter two integer numbers:”);
scanf(“%d%d”,&a,&b);

if(a<b)
{
p1 = &a;
p2 = &b;
}
else
{
p2 = &a;
p1 = &b;
}

printf(“a=%d,b=%d\n”,a,b);
printf(“max=%d,min=%d\n”,*p1,*p2);

return 0;
}

P226
例8.3 题目要求同8.2……
#include <stdio.h>

int main()
{void swap(int *p1,int * p2);
int a,b;
int *pointer_1,*pointer_2;
printf(“please enter a and b:”);
scanf(“%d,%d”,&a,&b);
pointer_1=&a;
pointer_2=&b;
if(a<b)swap(pointer_1,pointer_2);
printf(“max=%d,min=%d\n”,a,b);
return 0;
}
void swap(int *p1,int * p2)
{int temp;
temp=*p1;
*p1=*p2;
*p2=temp;
}
评:画蛇添足,脱裤子放屁

8.2.4 指针变量作为函数参数
评:标题就有问题
狭隘
P227
这个改变不是通过将形参值传回实参来实现的。
评:这是废话
任何时候都不可能“将形参值传回实参”
P228
为了使在函数中改变了的变量能被主调函数main所用,不能采取上述把要改变值的变量作为实参的办法,而应该用指针变量作为函数参数,在函数执行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量值的变化依然保留下来,这样就实现了“通过调用函数使变量的值发生变化,在主调函数(如main函数)可以使用这些改变了的值”的目的。
评:这文字也太垃圾了吧
这就是通俗易懂?
我是看不懂
P228~229
例8.4 对输入的两个整数按大小顺序输出。

int main()
{void swap(int *p1,int * p2);
int a,b;
int *pointer_1,*pointer_2;
printf(“please enter a and b:”);
scanf(“%d,%d”,&a,&b);
pointer_1=&a;
pointer_2=&b;
if(a<b)swap(pointer_1,pointer_2);
printf(“max=%d,min=%d\n”,a,b);
return 0;
}
void swap(int *p1,int * p2)
{int *p;
p=p1;
p1=p2;
p2=p;
}
评:老谭无中生有地批评这个代码企图通过“形参p1与p2将它们的值(是地址)传回实参pointer_1和pointer_2”,“但是,这是办不到的”,因而“在输入5,9之后程序的实际输出为max=5,min=9”
这是在放空炮
因为即使形参的值能够传回实参,这个程序“在输入5,9之后程序的实际输出”依然“为max=5,min=9 ”
P228~229
函数的调用可以(而且只可以)得到一个返回值(即函数值),而使用指针变量作参数,可以得到多个变化了的值。
评:关公战秦琼
驴唇不对马嘴
P230
int a,b,c,*p1,*p2,*p3
评:p1,p2,p3三个变量定义得毫无意义
P231
所谓数组元素的指针就是数组元素的地址。
评:再次混淆指针与地址的概念
把读者往沟里带

可以用一个指针变量指向一个数组元素
评:无的放矢的废话
引用数组元素可以用下标法,也可以使用指针法……使用指针法能使目标程序质量高(占内存少,运行速度快)
评:这是没有任何依据的以讹传讹

在C语言中,数组名(不包括形参数组名,形参数组并不占据实际的内存单元)代表数组中首元素(即序号为0的元素)的地址
评:大谬特谬
数组名和指针是两回事
此外,所谓的形参数组根本就不存在

注意:数组名不代表整个数组,只代表数组首元素的地址。
评:吐血
纯粹是狗屁不通

前已反复说明指针就是地址。
评:说指针就是地址说明不懂指针
在教科书里这样说应该被枪毙

P232
两个指针相减,如p1-p2(只有p1和p2都指向同一数组中的元素时才有意义)。
评:这个说法是错误的

p+1所代表的地址实际上是(p+1)*d,d是一个数组元素所占的字节
评:小学数学没学好

这里需要注意的是a代表数组首元素的地址,a+1也是地址,它的计算方法同p+1,即它的实际地址为(a+1)*d。
评:首先,a并不代表数组首元素的地址
其次,a+1也不是地址。在C语言中压根就没有地址这种数据类型
至于(a+1)*d已经荒谬的没边了

[]实际上是变址运算符
评:这是无中生有的捏造
把C给变质了

p2指向实型数组元素
评:C语言里压根就没有“实型”
P233
引用一个数组元素,可以用下面两种方法:
(1)下标法,如a[ i ]形式;
(2)指针法,如*(a+i)或*(p+i)。其中a是数组名,p是指向数组元素的指针变量,其初值p=a。
评:这种浮于表面而不及实质的、愚蠢而又自作聪明的不全面且无意义的归纳会使读者永远无法理解什么是指针
估计老谭自己也是稀里糊涂,要是看到p[ i ]恐怕会吓一跳吧

printf(“%\n”);
评:这是神马东东
估计又是一个未定义行为
P233~234
(1)
int a[10];
int i;
for(i=0;i<10;i++)
printf(“%d”,a[ i ]);

(2)
int a[10];
int i;
for(i=0;i<10;i++)
printf(“%d”,*(a +i));

(3)
int a[10];
int *p;
for(p=a;p<(a+10);p++)
printf(“%d”, *p );
第(3)种方法比第(1)和第(2)种方法快
评:我认为这种说法根据不足
而且也没什么意义
P235
指针变量p可以指向数组以后的存储单元。
评:严重的误导
后果很严重
最多可以指向数组之后的第一个数组元素类型的对象

P236
……定义数组时指定它包含10个元素……如果在程序中引用数组元素a[10]……但C编译程序并不认此为非法。系统把它按*(a+10)处理
评:引用a[10]是未定义行为
“系统把它按*(a+10)处理”的说法是武断的,毫无依据

p++;
由于++和
同优先级
评:++和*优先级并不相同

P237
将++和–运算符用于指针变量十分游戏,可以使指针变量自动向前或向后移动
评:“自动”二字滑稽
什么叫“自动”移动
是不是还有“被动”移动啊

p=a;
while(p<a+100)
printf(“%d”,*p++);
评:风格拙劣

p=a;
while(p<a+100)
{printf(“%d”,*p);p++;}
评:风格拙劣

P238
arr为形参数组名
评:形参根本就不可能是数组

常用这种方法通过调用一个函数来改变实参数组的值。
评:函数永远不可能改变实参的值
"数组的值"也是莫名其妙的说法
什么叫数组的值?
数组的值用什么表示
如果说数组名表示数组的值的话,那么数组的值根本不可能以任何方式被改变
如果说数组所占据内存的内容代表数组的值,那么数组绝对不可能是实参
P239
表 8.1 以变量名和数组名作为函数参数的比较
参数类型 变量名 数组名
要求形参的类型 变量名 数组名或指针变量
传递的信息 变量的值 实参数组首元素的地址
通过函数调用能否改变实参的值 不能改变实参变量的值 能改变实参数组的值
评:这是脱离本质看现象
实际上实参是一个表达式,不能分为变量名和数组名
形参也不存在数组名,形参只是一个变量
无论实参如何传递的都是一个值
通过函数调用绝对不可能改变实参的值

评:那段代码还不算最坏的
下面这段居然能出现在教科书上实在是令人拍案惊奇
void fun(arr[ ],int n)
{ printf(“%d\n”,*arr);
arr=arr+3;
printf(“%d\n”,*arr);
}

在用数组名作为函数实参时,既然实际上相应的形参是指针变量,为什么还允许使用形参数组的形式呢?这是因为在C语言中用下标法和指针法都可以访问一个数组
评:问题本身荒唐
回答逻辑错乱
这个原因应该问问K&R
老谭不要越位瞎说一通

形参数组与实参数组共占同一段内存。
评:荒谬
形参根本就不可能是数组

在函数调用进行虚实结合后
评:C语言根本就没有虚实结合这一说

将a[int(n-1)/2]与a[n-int((n-1)/2)-1]对换
评:这是什么污七八糟的烂东西
编译都不可能通过

例8.8 将数组a中的n个整数按相反顺序存放,……
再将a[ i ] 与a[j]对换,直到i=(n-1)/2为止
评:“直到i=(n-1)/2为止”在有些情况下多做了一次无用的交换

实参用数组名a,形参可以用数组名,也可以用指针变量名。
评:这个说法的荒唐之处在于
int i;

f(&i)

f(int a[])
{
……
}

所以所谓的形参数组,根本就是无中生有的说法
P239~240
例8.8 将数组a中的n个整数按相反顺序存放,……
#include <stdio.h>
int main( void )
{void inv(int x[],int n);
int i, a[10]={3,7,9,11,0,6,7,5,4,2};
printf(“The original array:\n”);
for(i=0;i<10;i++)
printf(“%d “,a[ i ]);
printf(”\n”);
inv(a,10);
printf(“The array has been vnverted:\n”);
for(i=0;i<10;i++)
printf(“%d “,a[ i ]);
printf(”\n”);
return 0;
}

void inv(int x[ ],int n) //形参x是数组名
{int temp,i,j,m=(n-1)/2;
for(i=0;i<=m;i++)
{j=n-1-i;
temp=x[ i];x[ i]=x[j];x[j]=temp;
}
return ;
}
评:可呕可吐之处颇多
inv()函数调淹没在脑满肠肥的琐碎且无聊的细节之中
main()中存在两段相同的代码
m变量明显多余
"形参x是数组名"的说法是胡扯
……

void inv(int x[ ],int n)
{int temp,i,j,m=(n-1)/2;
for(i=0;i<=m;i++)
{j=n-1-i;
temp=x[ i];x[ i]=x[j];x[j]=temp;
}
return ;
}
评:以n为3时走查一下
i j m
0 2 1
1 1 1

不难发现,这时temp=x[ i];x[ i]=x[j];x[j]=temp; 是在毫无必要地x[1]自己在和自己交换

这段代码至少应该写成
void inv(int x[ ],int n)
{
int temp,i,j;
for( i = 0 , j = n - 1 ; i < j ; i++ , j-- )
{
temp=x[i];
x[i]=x[j];
x[j]=temp;
}
return ;
}

P241
修改程序:
#include <stdio.h>
int main( void )
{void inv(int x[],int n);
int i, a[10]={3,7,9,11,0,6,7,5,4,2};
printf(“The original array:\n”);
for(i=0;i<10;i++)
printf(“%d “,a[ i ]);
printf(”\n”);
inv(a,10);
printf(“The array has been vnverted:\n”);
for(i=0;i<10;i++)
printf(“%d “,a[ i ]);
printf(”\n”);
return 0;
}

void inv(int *x,int n)
{int *p,temp,*i,*j,m=(n-1)/2;
i=x;j=x+n-1;p=x+m;
for(;i<=p;i++,j–)
{temp=*i;*i=*j;*j=temp;}
return ;
}
评:修改之后的代码更dirty更令人作呕

P241~242
如果有一个实参数组,要想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况。
(1)形参和实参都用数组名,
(2)实参用数组名,形参指针变量。
(3)实参和形参都用指针变量
int main()
{int a[10],*p=a;

f(p,10);
}
void f(int *x,int n)
{
}
(4)实参为指针变量,形参为数组名
评:一堆废话,说不到点子上,只会把读者绕晕
实际上根本不存在形参数组
数组名作为实参本来就表示指针
p=a 属于脱裤子放屁

P242~243
例 8.9
#include <stdio.h>
int main( void )
{void inv(int *x,int n);
int i, a[10],*p=arr;
printf(“The original array:\n”);
for(i=0;i<10;i++,p++)
scanf(“%d “,p);
printf(”\n”);
p=arr;
inv(p,10);
printf(“The array has been vnverted:\n”);
for(p=arr;p<arr+10;p++)
printf(“%d “,*p);
printf(”\n”);
return 0;
}
void inv(int *x,int n)
{int *p,m,temp,*i,*j;
m=(n-1)/2;
i=x;j=x+n-1;p=x+m;
for(;i<=p;i++,j–)
{temp=*i;*i=*j;*j=temp;}
return ;
}
评:一蟹不如一蟹
P243
#include <stdio.h>
int main( void )
{void inv(int *x,int n);
int i, arr;
printf(“The original array:\n”);
for(i=0;i<10;i++)
scanf(“%d “,arr+i);
printf(”\n”);
inv(arr,10);
printf(“The array has been vnverted:\n”);
for(i=0;i<10;i++)
printf("%d ",
(arr+i));
printf(“\n”);
return 0;
}

评:信口开河的弥天大谎
后面的解释更是胡说八道

f(x[],int n )
评:函数定义都写不全
老谭的基本功很成问题
P244
注意:如果指针变量作实参,必须先使指针变量有确定值,指向一个已定义的对象。
评:纯粹是废话
任何东西做实参都必须有确定的值,和实参是不是“指针变量”没有半毛钱狗屁关系

“已定义的对象”也属于似是而非、狗屁不通的说法

例8.10 用指针方法对10个整数由大到小顺序排序。
评:“指针方法”是子虚乌有的

解题思路:在主函数中定义数组a存放10个整数,定义int *型指针变量p指向a[0],定义函数sort使数组a中的元素由大到小的顺序排列。在主函数中调用sort函数,用指针变量p作实参。sort函数的形参用数组名。
评:这是脱裤子放屁,因为a本身就是指向a[0]的指针
难道求3.0的平方根不直接写sqrt(3.0)非要先
double d=3.0;
再 sqrt(d) 不成

#include <stdio.h>
int main( void )
{void sort(int x[],int n);
int i, *p,a[10];
p=a;
printf(“please enter 10 integer numbers:”);
for(i=0;i<10;i++)
scanf(“%d”,p++);
p=a;
sort(p,10);
for(p=a,i=0;i<10;i++)
{printf(“%d “,*p);
p++;
}
printf(”\n”);
return 0;
}

void sort(int x[],int n)
{int i,j,k,t;
for(i=0;i<n-1;i++)
{k=i;
for(j=i+1;j<n;j++)
if(x[j]>x[k])k=j;
if(k!=i)
{t=x[i];x[i]=x[k];x[k]=t;}
}
}

评:其蠢无比的代码

首先
for(i=0;i<10;i++)
scanf(“%d”,p++);
————谭浩强 ,《C程序设计》(第四版),清华大学出版社, 2010年6月,p244

本可以简单地写成
for(;p<a+10;p++)
scanf(“%d”,p);
其次
p=a;
sort(p,10);
————谭浩强 ,《C程序设计》(第四版),清华大学出版社, 2010年6月,p244
更蠢

本可以简单地
sort(a,10);

for(p=a,i=0;i<10;i++)
{ printf("%d ",*p);
p++;
}
————谭浩强 ,《C程序设计》(第四版),清华大学出版社, 2010年6月,p244
同样很蠢

表达的无非是
for(p=a;p<a+10;p++)
printf("%d ",*p);
而已

void sort(int x[],int n)
{int i,j,k,t;
for(i=0;i<n-1;i++)
{k=i;
for(j=i+1;j<n;j++)
if(x[j]>x[k])k=j;
if(k!=i)
{t=x[i];x[i]=x[k];x[k]=t;}
}
}

评:最傻的就是 if(k!=i) 的那个一本正经的缩进,滑稽
其他风格方面的残疾就不多说了
P245
int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
……
a代表二维数组首元素的地址
评:这种说法是片面的
a本身就是数组
但在有些情况下
a是作为指针(而不是地址)来使用

a代表的是首行(即序号为0的行)的首地址
评:首地址 这个概念本身就有问题,是含混不清的
P246
C语言又规定了数组名代表数组首元素的地址
评:混淆指针和地址的概念,误导
C语言并没有规定了数组名代表数组首元素的地址

&[0][1],&[0][2],&[0][3]
评:竟然有这种低级错误
难以想象这种错误是怎么犯的

有必要对a[ i]的性质作进一步说明。a[ i]从形式上看是a数组中序号为i的元素。如果a是一维数组名,则a[ i]代表a数组序号为i的存储单元。a[ i]是有物理地址的,是占存储单元的。但如果a是二维数组,则a[ i]是一维数组名,它只是一个地址,并不代表某一元素的值(如同一维数组名只是一个指针常量一样)。a,a+i,a[ i],(a+i),(a+i)+j,a[ i]+j都是地址。而*(a[ i]+j)和*(*(a+i)+j)是二维数组元素a[ i][j]的值,……
评:这段陈述错误太多了
几乎就没有正确的地方

a[ i]从形式上看是a数组中序号为i的元素。

它就是a数组中序号为i的元素

如果a是一维数组名,则a[ i]代表a数组序号为i的存储单元。a[ i]是有物理地址的,是占存储单元的。

a是多维数组名时,a[ i]同样代表a数组序号为i的存储单元
“物理地址”是莫名奇妙的概念

如果a是二维数组,则a[ i]是一维数组名,它只是一个地址,并不代表某一元素的值

“只是一个地址”是大错特错的说法
“不代表某一元素的值”与老谭前面讲的“首元素”是自相矛盾的
a[ i] 同样可以代表某一元素的值,当然这要看怎么理解“元素”这个概念

a,a+i,a[ i],(a+i),(a+i)+j,a[ i]+j都是地址。

片面
而*(a[ i]+j)和*(*(a+i)+j)是二维数组元素a[ i][j]的值

未必

P247
表8.2 二维数组a的有关指针
评:基本上全是错误的
错误在于片面地解释数组名
并把指针当作地址
全然忽视了数据类型

班长相当于列指针,排长相当于行指针
评:C语言中压根就没有行指针、列指针这样的概念
行指针、列指针是没有定义的不严谨的概念
而不严谨对于程序设计来说
就是灾难
P248
a和a[0]的值虽然相同(等于2000),但是由于指针的类型不同(相当于排长和班长面向的对象一样,a指向一维数组a[0],而[0]指向列元素a[0][0])。
评:“a和a[0]的值虽然相同(等于2000),但是由于指针的类型不同。”
这根本就不是话,小学语文老师要打屁股的

“相当于排长和班长面向的对象一样”
更是莫名其妙前后自相矛盾

“[0]指向列元素a[0][0]”
喝高了吧
什么是"列元素"
[0]是神马东西

再次强调:二维数组名(如a)是指向行的。因此a+1中的“1”代表一行中全部元素所占的字节数
评:指向数据对象的指针+1有统一、确定的定义
因此这里根本没有“因此”可言

一维数组名(如a[0],a[1])是指向列元素的。a[0]+1中的1代表一个a元素所占的字节数。
评:“列元素”是个扯淡的概念
什么叫“列元素”?
一列元素吗?
对不起,压根没有指向这种东西的指针
如果“列元素”指的只是数组中某一个具体的元素
有什么必要再发明一个子虚乌有、多余且压根没有准确定义的“列元素”的概念呢

例如,a和a+1是指向行的指针,在它们前面加一个就是a和*(a+1),它们就成为列的指针,分别指向a数组0行0列的元素和1行0列的元素。
评:这是胡扯
根本就不存在指向列的指针
因而a和(a+1)不可能是什么列的指针
甚至a和(a+1)是否是指针还是未定之数

反之,在指向列的指针前面加&,就成为指向行的指针。例如a[0]是指向0行0列元素的指针,在它前面加一个&,得&a[0],由于a[0]与*(a+0)等价,因此&a[0]与&*a等价,也就是与a等价,它指向二维数组的0行。
评:这个更荒诞
试想
int a[3][4];
int *p = a[0];
p算不算指向“列的指针”呢?
如果算的话
你就是把刘谦招来
他也无法把&p变成指向行的指针
再比如
如果把“a[0] ” 说成“是指向0行0列元素的指针”
那么a[0]+1显然是指向0行1列元素的指针
你若敢在代码中斗胆写出 &(a[0]+1)
连编译都根本通不过

不要把&a[ i]简单地理解为a[ i]元素的物理地址,因为并不存在a[ i]这样一个实际的数据存储单元
评:这里,“物理地址”是故弄玄虚,是不懂装懂地唬人
“并不存在a[ i]这样一个实际的数据存储单元”则是自相矛盾
因为前面有“a数组包含3行,即3个元素:a[0],a[1],a[2]。”(p245)这样的叙述

不要把&a[ i]简单地理解为a[ i]元素的物理地址,因为并不存在a[ i]这样一个实际的数据存储单元
。它只是一种地址的计算方法,能得到第i行的首地址
评:在C语言中并不存在地址计算
a[ i] 得到的也并非第i行的首地址
而且首地址这个概念本身就是错误的

&a[ i]或a+i指向行,而a[ i]或*(a+i)指向列
评:扯淡
什么叫“指向列”
这是根本不可能的

而对二维数组,a+i不是指向具体存储单元而指向行。
评:内存中压根就没有“行”这种东西
指针只可能指向存储单元(除非void *)

例8.11
int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
printf(“%d,%d\n”,a,a);
评:%d 转换格式用于转换int类型数据而不是转换指针
P249
例8.12 有一个3
4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值。
#include <stdio.h>
int main()
{int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
int *p;
for(p=a[0];p<a[0]+12;p++)
{if((p-a[0])%40)printf(“\n”);
printf(“%4d”,*p);
}
printf(“\n”);
return 0;
}
评:12:Magic Number
if((p-a[0])%4
0)printf(“\n”);:喧宾夺主。而且没有完成功能“输出4个数据后换行”(p250),不得不在后面补了一个printf(“\n”); 。并且它在输出数据之前多输出了一个换行
printf(“%4d”,*p);:潜在的BUG,之所以没暴露出来是因为数组元素的值太小而已
P251~252
#include <stdio.h>
int main()
{ int a[4]={1,3,5,7};
int (*p)[4];
p=&a; //p指向一维数组
printf(“%d\n”,(*p)[3]);
return 0;
}
注意第5行不应写成“p=a;”,因为这样写表示p的值是&a[0],指向a[0]。
评:第5行确实不应写成“p=a;”
但是这样写并不表示p指向a[0]
事实上尽管写p=a;有些瑕疵,但这样p依然是指向数组的

p=&a; //p指向一维数组

这本身没有什么错误
但是和老谭在p231页所讲的
数组名不代表整个数组,只代表数组首元素的地址。

完全是自相矛盾的

P252
由于p是指向一维数组的指针变量,因此p加1,就指向下一行
评:正确的说法是
由于p是指向一维数组的指针,因此p加1,就指向下一个一维数组

它已经转化为指向列元素的指针了。
评:估计只有SB才能领会列元素这种SB概念

一维数组名可以作为函数参数,多维数组名也可以作为函数参数。用指针变量作形参,以接受实参数组名传递来的地址。可以有两种方法:①用指向变量的指针变量;②用指向一维数组的指针变量。
评:逻辑错乱
首先:“一维数组名可以作为函数参数,多维数组名也可以作为函数参数”这个说法是错误的,数组名只能做实参。形参不可能是数组名,即使用[]修饰,那也是对指针的一种变相说明
其次:“用指针变量作形参,以接受实参数组名传递来的地址”以接受实参数组名传递来的指针
第三:形参是何种类型只能依据实参的类型,不可能有两种方法
第四:前面说“多维数组名也可以作为函数参数”,后面却说“用指向一维数组的指针变量”,显而易见是错的

P252~253
例8.14 有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩。
#include <stdio.h>
int main( )
{void average(float *p,int n);
void search(float (*p)[4],int n);
float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}};
average( * score,12);
search(score,2);
return 0;
}

void average(float *p,int n)
{float *p_end;
float sum=0,aver;
p_end=p+n-1;
for(;p<=p_end;p++)
sum=sum+(*p);
aver=sum/n;
printf(“average=%5.2f\n”,aver);
}

void search(float (p)[4],int n)
{int i;
printf(“The score of No.%d are:\n”,n);
for(i=0;i<4;i++)
printf("%5.2f ",
(*(p+n)+i));
printf(“\n”);
}
评:先,问题和代码根本对不上号
问题是“第n个学生的成绩”
但代码没有反应出这一点
毛病出在“问题”上
另外“计算总平均分数以及第n个学生的成绩”在语文上是不通的
第n个学生的成绩根本不是计算出来的
“有一个班,3个学生,各学4门课”根本就不能算是问题的条件
相当于什么都没说
这种似是而非的问题对学习程序设计危害极大
正确地提出问题比解决问题重要得多

void average()这个函数的功能根本就不是“计算总平均分数”,而是计算之后输出。
把多个功能揉在一个函数里,不符合编程之道

average( * score,12);
这个调用写得也不好
那个12显得莫名其妙
不如写average( * score, sizeof score / sizeof **score );
甚至不如写
average( * score,3*4);

用函数search找出并输出第i个学生的成绩
评:在解题思路中
题目本身不明确的要求被篡改为
这种胡乱篡改需求(尽管需求本身也不明确且有错误)的做法是最要不得的
这给学习者带来的坏影响比把C讲错了还严重
况且,这里的i也是莫名其妙不知所云的东东

谭的代码一向存在在main()中写不该写代码的毛病
但这次走向了另一个极端
把该在main()中写的内容写到了其他函数中

void search(float (*p)[4],int n)
中的 printf(“The score of No.%d are:\n”,n);
以及
void average(float *p,int n)
中的 printf(“average=%5.2f\n”,aver);
都是这样

此外seach()的功能也根本不是在“查找”什么,它只是输出了一个一维数组的各个元素的值而已

P254
注意:实参与形参如果是指针类型,应当注意它们的类型必须一致。
评:这是废话
实参与形参本来就应该类型一致
怎么能扯出个“如果”来呢

不应把int 型的指针(即元素的地址)传给int ()[4]型(指向一维数组)的指针变量,反之亦然。正如不应把“班长”传给“排长”一样,应当是“门当户对”
评:这是什么不伦不类的比喻啊
令人无语

search(score,2); //用score(即score[0]的起始地址作为实参)
评:“score[0]的起始地址作为实参”这种说法相当于把score贬低成了 void *
约等于什么都没说
因为score ,score[0],score[0][0]的起始地址都是一个

search(score,2); //用score(即core[0]的地址作为实参
评:确实不对
但老谭说的理由也不对

虽然score和*score都是地址,但后者的类型与形参p的类型不匹配。
评:胡扯
地址哪来的类型可言
C语言中压根就没有地址这种类型

例 8.15 在上题的基础上,查找有一门以上课程不及格的学生,输出他们全部课程的成绩。
评:上个题本身就有很多毛病(参见2538楼)
居然还要在“在上题的基础上”

这里"一门以上"就是混沌不清的说法

#include <stdio.h>
int main()
{void search(float (*p)[4],int n);
float score[3][4]={{65,57,70,60},{58,87,90,81},{90,99,100,98}};

search(score,3);
return 0;
}

void search(float (p)[4],int n)
{int i,j,flag;
for(j=0;j<n;j++)
{flag=0;
for(i=0;i<4;i++)
if(
((p+j)+i)<60)flag=1;
if(flag==1)
{printf(“No.%d fails,his scores are:\n”,j+1);
for(i=0;i<4;i++)
printf("%5.2f ",
(*(p+j)+i));
printf(“\n”);
}
}
}

评:主要毛病
1.flag用得很垃圾
2.main()里什么也没有,把所有的事情都推给了search()函数
与其这样还不如把所有的代码都在main()中写
3.search()函数名不符实,功能混乱不清
这种写法
就像用把所有的东西都塞到柜子里去的办法收拾房间一样
表面看main()里很清爽
但骨子里依然是混乱不堪
P255
通过指针变量存取数组元素速度快,程序简明。用指针变量作形参,所处理的数组大小可以改变。因此数组与指针常常是紧密联系的,使用熟练的话可以使程序质量提高,编写程序灵活方便。
评:“通过指针变量存取数组元素速度快,程序简明。”:说速度快没有根据,未必程序简明。例8.15是直接的例子,速度不快而且不简明
“用指针变量作形参,所处理的数组大小可以改变。”:这句更荒唐,C语言里没有什么东西在存在滞后大小可以改变
“因此数组与指针常常是紧密联系的”:平白无故地哪儿冒出个“因此”呢?从上文看不出任何“因”。逻辑混乱

在C程序中,字符数组是存放在字符数组中的
评:这个是无稽之谈
试问"ABC" 存在在哪个数组中?数组的名字是什么?

想引用一个字符串,可以用以下两种方法。
(1)用字符数组存放一个字符串,[quote]可以通过数组名和下标引用字符串中的一个字符,也可以通过数组名和格式声明"%s"输出该字符串。
评:文不对题,莫名其妙
用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社, 2010年6月,p256
狭隘
字符指针变量指向字符串的说法也不够准确

P256
C语言对字符串常量是按字符数组处理的,在内存中开辟了一个字符数组用来存放该字符串常量,但是这个字符数组是没有名字的,因此不能通过数组名来引用,只能通过指针变量来引用。
评:“只能”是错误的

char string[]=“I love China”;
printf(“%s\n”,string);
评:这种写法很笨拙且低效
实际上可以简单地
puts(string);

P257
printf(“%s\n”,string);
%s是输出字符串时所用的格式符,在输出项中给出字符指针变量名string,则系统会输出string所指向的字符串第1个字符,然后自动使string加1,使之指向下一个字符……如此直到遇到字符串结束标志’\0’为止。
评:非常通俗易懂
然而却是错的
错误的东西就是再通俗易懂又有什么意义呢
“自动使string加1,使之指向下一个字符”极其荒谬
此外,前面说%s是格式声明,这里又说是格式符
自相矛盾
“系统”二字一向是老谭打马虎眼时的常用词

通过字符数组名或字符指针变量可以输出一个字符串。
评:不通过这两者也可以
谭的归纳没有说到点子上
因而是不全面的和肤浅的

P257~258
int a[10];
……
printf(“%d\n”,a);
是不行的,它输出的是数组首元素的地址。
评:确实不行
但printf(“%d\n”,a);本身也是不对的,并非是“它输出的是数组首元素的地址”
P258
例8.18 将字符串a复制为字符串b,然后输出字符串b。
评:莫名其妙
题就是错的
汉语“复制为”的含义之一是“复制成”
显然这是错的

这个问题的正确提法是把a指向的字符串复制到b指向的位置

字符串a、字符串b这种说法的错误在于“a”和“b”都 不是字符串
在C语言中,除非string literal可以直接称呼
比如 字符串"ABC"
用指针称呼只能说某指针指向的字符串
或从某个位置开始的字符串
用数组称呼只能说某数组中存储的字符串

#include <stdio.h>
int main( void )
{char a[]=“I am a student.”,b[20];
int i;
for(i=0;*(a+i)!=‘\0’;i++)
(b+i)=(a+i);
*(b+i)=‘\0’;
printf(“string a is:%s\n”,a);
printf(“string b is:”);
for(i=0;b[i]!=‘\0’;i++)
printf(“%c”,b[i]);
printf(“\n”);
return 0;
}

评:拷贝字符串,从没见过这么笨的法子
for(i=0;*(a+i)!=‘\0’;i++)
(b+i)=(a+i);
*(b+i)=‘\0’;
原因:选择错误的语句
这里应该用while语句

这个题目应该这样写
#include <stdio.h>
void str_copy( char * , char const * );

int main( void )
{
char tgt[80];

str_copy( tgt , “I am a student.” );//假设不会越界

puts(tgt);

return 0;
}

void str_copy( char * to, char const *from )
{
while( (*to++=*from++)!=‘\0’ )
;
}

程序中a和b都定义为字符数组,今通过地址访问其数组元素。
评:错。是通过指针
P258~259
例8.19 用指针变量来处理8.18问题。
解题思路:定义两个指针变量p1和p2,分别指向字符数组a和b。改变指针变量p1和p2的值,使它们循序指向数组中的各元素,进行对应元素的复制。
评:老谭根本就没搞清楚“指向”两个字的含义
前面说指向数组
后面又说指向元素
自相矛盾
P259
如果想把一个字符串从一个函数“传递”到另一个函数,可以用地址传递的办法,即用字符数组名作参数,也可以用字符指针变量作参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以引用改变后的字符串。
评:“用字符数组名作参数”和“用字符指针变量作参数”没有任何本质区别
其本质都是传递一个指针(而不是“地址传递”)

至于“在被调用的函数中可以改变字符串的内容”,这是显而易见的事情,因为传递的是指针
而“在主调函数中可以引用改变后的字符串”则是废话一句

8.4.2 字符指针作函数参数
评:字符指针作为函数参数没有什么特殊性
之所以把这个作为小节标题
是因为前面根本没有讲清楚数组名的本质
也没有讲清楚“数组形式”的形参的本质

函数的形参和实参可以分别用字符数组名或字符指针变量。
评:即是废话又是错话
首先
实参和形参是两回事
形参:
在“形式上”可以是数组名,但这不是数组类型,而是不完整类型,本质上是一个指针,把形参写成数组名有一些缺点,而把形参理解为数组名只有坏处没有好处

实参:实参不仅仅限于可以用“字符数组名或字符指针变量”
P259~260
#include <stdio.h>
int main( void )
{void copy_string(char from[],char to[]);
char a[]=“I am a teacher.”;
char b[]=“You are a student.”;
printf(“string a=%s\nstring b=%s\n”,a,b);
printf(“copy string a to string b:\n”);
copy_string(a,b);
printf(“string a=%s\nstring b=%s\n”,a,b);
return 0;
}
void copy_string(char from[],char to[])
{int i=0;
while(from[i]!=‘\0’)
{to[i]=from[i];i++;}
to[i]=‘\0’;
}

评:总算是用函数了
不过那个i明显是多余了
拷贝的实现也过于笨拙

但是260页的运行结果明显是伪造的(中间多一个空行)
根据代码无法得到这样的结果

P260~261
(2)用字符型指针变量作实参
#include <stdio.h>
int main( void )
{void copy_string(char from[],char to[]);
char a[]=“I am a teacher.”;
char b[]=“You are a student.”;
char *from=a,*to=b;
printf(“string a=%s\nstring b=%s\n”,a,b);
printf(“copy string a to string b:\n”);
copy_string(a,b);
printf(“string a=%s\nstring b=%s\n”,a,b);
return 0;
}
void copy_string(char from[],char to[])
{int i=0;
while(from[i]!=‘\0’)
{to[i]=from[i];i++;}
to[i]=‘\0’;
}

评:一蟹不如一蟹
char *from=a,*to=b; 无比荒唐
把a,b的值存放在变量里毫无必要
P261
#include <stdio.h>
int main( void )
{void copy_string(char *from,char *to);
char *a=“I am a teacher.”;
char b[]=“You are a student.”;
char *p=b;
printf(“string a=%s\nstring b=%s\n”,a,b);
printf(“copy string a to string b:\n”);
copy_string(a,b);
printf(“string a=%s\nstring b=%s\n”,a,b);
return 0;
}
void copy_string(char *from,char *to)
{
for(;*from!=‘\0’;from++,to++)
{*to=*from;}
*to=‘\0’;
}

评:一样的东西反复写
有什么意义!
而且风格怪异
{*to=*from;}
{to[i]=from[i];i++;}
{to[i]=from[i];i++;}

都属于垃圾写法
P262
程序改进:
void copy_string(char *from,char *to)
{while((*to=*from)!=‘\0’)
{to++;from++;}
}
评:改进有限
{to++;from++;}依然怪异

{while((*to++=*from++)!=‘\0’);}
评:这个马马虎虎
但是把“;”写在行尾是一种糟糕的风格

{while(*from!=‘\0’)
*to++=*from++;
*to=‘\0’;
}
评:没必要展示这些垃圾写法

{while(*from)
*to++=*from++;
*to=‘\0’;
}
评:无语
P263
(6)函数体中也可以改为只用一个for语句:
for(😭*to++=*from++)!=0 ; ) ; );
评:连for语句都能写错?

void copy_string(char from[],char to[])
{ char *p1,*p2;
p1=from;p2=to;
while((*p2++=*p1++)!=‘\0’);
}
以上各种用法,变化多端,使用十分灵活,程序精炼,比较专业,初学者看起来不太习惯
评:应该说是非常业余,专业人士看不惯
尤其是那个p1,p2
画蛇添足

表8.3 调用函数时实参与形参的对应关系
实参 形参 实参 形参
字符数组名 字符数组名 字符指针变量 字符指针变量
字符数组名 字符指针变量 字符指针变量 字符数组名
评:这个表毫无意义
只能给学习者以误导
实际上
形参只可能是“字符指针变量”
而实参则绝不限于“字符数组名”和“字符指针变量”

8.4.3 使用字符指针和字符数组的比较
评:所讲的并非是字符指针的特殊性而是指针的共性
所讲的字符数组的性质也是所有数组的共性(除了赋初值,字符数组有一点特殊)
把指针和数组的共性作为特殊的字符指针和字符数组的性质来讲解
只能说是在误导

P264
编译时为字符数组分配若干存储单元,以存放各元素的值,而对字符指针变量,只分配一个存储单元
评:存储单元这种概念
在老谭那里不知道有多少种含义
在这里
同一句话中出现了两次
但含义完全不同
这是小学生都能看出的逻辑错误

P265
指针变量的值是可以改变的,而数组名代表一个固定的值(数组首元素的地址),不能改变。
评:片面

指针变量的值未必是可以改变的
数组名也未必代表一个固定的值

字符数组中各元素的值是可以改变的(可以对它们再赋值)
评:荒谬
const char a[]=“HOUSE”;
改改这个试试?

(7)引用数组元素。对字符数组可以用下标法(用数组名和下标)引用一个数组元素(如a[5]),也可以用地址法(如*(a+5))引用数组元素。
评:毫无意义的废话
这个前面都讲过了
有什么必要再借字符数组和字符指针重讲一次呢

所谓“下标法”、“地址法”是可笑的说法
表明根本还不懂得什么是指针

如果定义了字符指针变量p,并使它指向数组a的首元素,则可以用指针变量带下标的形式引用数组元素(如p[5]),同样,可以用地址法(如*(p+5))引用数组元素a[5]。
评:更是废话
而且前提条件“如果定义了字符指针变量p,并使它指向数组a的首元素”是完全多余的

但是,如果指针变量没有指向数组,则无法用p[5]或*(p+5)这样的形式引用数组中的元素。
评:简直让人喷饭
还有比这更费的话吗
不过也难说
恐怕没有最费
只有更费

若字符指针变量p指向字符串常量,就可以用指针变量带下标的形式引用所指的字符串中的字符。
评:误导
只要是指针就可以进行[]运算
根本不需要“若字符指针变量p指向字符串常量”这样的前提条件

( 8 )用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。
评:这个一点都不值得惊奇
实参与形参类型一致而已
P266
例如:
char *format;
format=“a=%d,b=%f\n”;
printf(format,a,b);
……这种printf函数称为可变格式输出函数。
评:少见多怪不要紧
但不要随意捏造什么“可变格式输出函数”
这种捏造对于学习者来说是故部迷阵,没有任何意义
printf()函数只有一种,根本不存在什么“可变格式输出函数”

也可以用字符数组实现。
评:离题万里的废话
8.5 指向函数的指针
P267
c=max(a,b);
printf(“a=%d\nb=%d\nmax=%d\n”,a,b,c);

评:不把值赋给变量就不放心
实际上c这个变量毫无必要

int max(int x,int y)
{int z;
if(x>y) z=x;
else z=y;
return z;
}

评:不就是
int max(int x,int y)
{
return (x>y)?x:y;
}

吗?

P268
和数组名代表数组首元素地址类似,函数名代表该函数的入口地址。
评:片面。
正如数组名并不总是代表指向起始元素的指针一样,函数名在有些情况并不代表函数的入口地址

定义指向函数的指针变量的一般形式为:
类型名 (*指针变量名)(函数参数列表);
评:那不叫“函数参数列表”
是参数类型列表

P269
用函数指针变量调用函数时,只须将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括号中根据需要写上实参。例如:
c=(*p)(a,b);
评:这只是使用指向函数指针调用函数的方法之一
而且是一种比较笨拙的方式

int max(int,int);//函数声明
int min(int x,int y);//函数声明

评:风格不统一
P268~269
#include <stdio.h>
int main()
{int max(int,int);
int min(int x,int y);
int (*p)(int,int);
int a,b,c,n;
printf(“please enter a and b:”);
scanf(“%d,%d”,&a,&b);
printf(“please choose 1 or 2:”);
scanf(“%d”,&n);
if(n1)p=max;
else if(n
2)p=min;
c=(*p)(a,b);
printf(“a=%d,b=%d\n”,a,b);
if(n==1)printf(“max=%d\n”,c);
else printf(“min=%d\n”,c);
return 0;
}

int max(int x,int y)
{int z;
if(x>y) z=x;
else z=y;
return(z);
}

int min(int x,int y)
{int z;
if(x<y) z=x;
else z=y;
return(z);
}

评:首尾不一
假设用户一定会正确输入的话
那么else if(n2)p=min;中的 if(n2) 就是多余的
假设用户输入可能会有错误
那么这个代码的安全性方面的问题就太严重了

P269
这个例子是比较简单的,只是示意性的,但它很有实用价值。
评:简单、示意性是对的
说有实用价值是不顾事实

在许多应用程序中常用菜单提示输入一个数字,然后根据输入的不同值调用不同的函数,实现不同的功能,就可以用此方法。
评:这是一相情愿的想当然
菜单调用的函数的类型多数不同
这种方法根本用不上

当然,也可以不用指针变量,而用if语句或switch语句进行判断,调用不同的函数。但是显然用指针变量更简洁和专业。
评:这就是在胡扯了
这个例题说是简单的示意性的代码还差强人意
但就这个题目而言
它显然不是很恰当地使用指向函数的指针
至于“更简洁和专业”更是无中生有的说法
这个代码一点也不简洁,一点也不专业
这个题目完全可以像下面这样写
#include <stdio.h>

int max(int,int);
int min(int,int);

int main( void )
{
int a,b,n;

printf(“输入两个整数:”);
scanf(“%d%d”,&a,&b);
printf(“选择 1 或 2:”);
scanf(“%d”,&n);

if(n==1)
printf(“max=%d\n”,max(a,b));

if(n==2)
printf(“max=%d\n”,max(a,b));

return 0;
}

int max(int x,int y)
{
return (x>y)?x:y;
}

int min(int x,int y)
{
return (x<y)?x:y;
}

相比之下,少了4个变量,代码更少。而且根本不存在谭代码中存在的严重的安全性隐患问题
这才叫简洁和专业
P270
指向函数的指针变量的一个重要用途是把函数的地址作为参数传递到其他函数。
评:隔靴搔痒式的议论
另外这里的“指向函数的指针变量”中的“变量”这个限定也是错误的
P271~272
#include <stdio.h>
int main()
{void fun(int x,int y,int (*p)(int,int));
int max(int ,int);
int min(int ,int);
int add(int ,int);
int a=34,b=-21,n;
printf(“please choose 1,2 or 3:”);
scanf(“%d”,&n);
if(n1) fun(a,b,max);
else if(n
2) fun(a,b,min);
else if(n==3) fun(a,b,add);
return 0;
}

int fun(int x,int y,int (*p)(int,int))
{int result;
result=(*p)(x,y);
printf(“%d\n”,result);
}

int max(int x,int y)
{int z;
if(x>y) z=x;
else z=y;
printf(“max=”);
return(z);
}

int min(int x,int y)
{int z;
if(x<y) z=x;
else z=y;
printf(“min=”);
return(z);
}

int add(int x,int y)
{int z;
z=x+y;
printf(“sum=”);
return(z);
}
评:这个代码根本不可能通过编译
可蹊跷的是
在272页~273页
老谭居然一本正经地给出了运行结果
please choose 1,2 or 3:1
max=34

please choose 1,2 or 3:2
min=-21

please choose 1,2 or 3:3
sum=13

这算不算伪造啊?
算不算欺骗读者啊
8.6 返回指针值的函数
P274
#include <stdio.h>
int main()
{float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};
float *search(float (*pointer)[4],int n);
float p;
int i,k;
printf(“enter the number of student:”);
scanf(“%d”,&k);
printf(“The scores of No.%d are:\n”,k);
p=search(score,k);
for(i=0;i<4;i++)
printf(“%5.2f\t”,
(p+i));
printf(“\n”);
return 0;
}

float *search(float (*pointer)[4],int n)
{float pt;
pt=
(pointer+n);
return(pt);
}

评:传说中的为赋新词强说愁
search()函数矫揉造作且毫无意义

下面的写法要好的多
#include <stdio.h>
int main()
{
float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};
int i,k;
printf(“enter the number of student:”);
scanf(“%d”,&k);
printf(“The scores of No.%d are:\n”,k);
for(i=0;i<4;i++)
printf(“%5.2f\t”,score[k][i]);
putchar(‘\n’);
return 0;
}

P275
对pointer+1加了"“号后,指针从行控制转化为列控制了
评:“对pointer+1加了”
"号”,应该是对pointer+1进行了*运算

行控制,列控制:莫名其妙,不知所云
P276
#include <stdio.h>
int main()
{float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};
float *search(float (pointer)[4]);
float p;
int i,j;
for(i=0;i<3;i++)
{p=search(score+i);
if(p==
(score+i))
{printf(“No.%d score:”,i);
for(j=0;j<4;j++)
printf("%5.2f ",
(p+j));
printf(“\n”);
}
}
return 0;
}

float *search(float (*pointer)[4])
{int i=0;
float pt;
pt=NULL;
for(;i<4;i++)
if(
(*pointer+i)<60)pt=*pointer;
return(pt);
}

评:这段代码毛病更多

float *search(float (*pointer)[4])
{int i=0;
float pt;
pt=NULL;
for(;i<4;i++)
if(
(*pointer+i)<60)pt=*pointer;
return(pt);
}

简直是傻到家了的写法
应该在循环语句内直接写return语句
而且pt是完全多余的
search()函数的返回值类型也设计得极不合理,根本没必要返回指针

main()中不应该写输出的代码,应该用函数完成
p=search(score+i); 很傻,search()这个函数应该回答的问题是score+i所指向的数据中是否有小于60的,根本没有必要返回指针,因为这个指针在main()中是清清楚楚的,根本不用求
P277
什么情况下要用到指针数组呢?指针数组比较适合用来指向若干个字符串,使字符串处理更加方便灵活。
评:前面介绍指针数组是以 int *p[4]; 为例
这里又说“指针数组比较适合用来指向若干个字符串”
显然自相矛盾

指针数组并非“适合用来指向若干个字符串”(这里老谭显然混淆了string与string literal)
况且指针数组指向若干字符串这话也根本说不通
数组不是指针,如果把数组作为指针的话它指向的只是其起始元素,指针不可能指向多个对象

可以分别定义一些字符串
评:变量和函数可以定义
字符串怎么定义
P278~279
#include <stdio.h>
#include <string.h>
int main()
{void sort(char *name[],int n);
void print(char *name[],int n);
char *name[]={“Follow me”,“BASIC”,“Great Wall”,“FORTRAN”,“Computer design”};

int n=5;
sort(name,n);
print(name,n);
return 0;
}

void sort(char *name[],int n)
{char *temp;
int i,j,k;
for(i=0;i<n-1;i++)
{k=i;
for(j=i+1;j<n;j++)
if(strcmp(name[k],name[j])>0)k=j;
if(k!=i)
{temp=name[i];name[i]=name[k];name[k]=temp;}
}
}
void print(char *name[],int n)
{int i;
for(i=0;i<n;i++)
printf(“%s\n”,name[i]);
}

评:公平地说
这是谭书中写得比较好的代码
但是很可惜
main()中的
int n=5;
sort(name,n);
print(name,n);

太荒唐了
倒不如直接写
sort(name,5);
print(name,5);
无论如何这个5不可能来自一个不相干的变量n的值
规矩的做法是根据name计算得到

此外sort()函数中的交换应用一个函数实现
print()函数中的
printf(“%s\n”,name[ i ]);
不如
puts(name[ i ]);

程序改进:
print函数也可以写为以下形式:
void print(char *name[],int n)
{int i=0;
char p;
p=name[0];
while(i<n)
{p=
(name+i++);
printf(“%s\n”,p);
}
}
评:不改倒还好
这一改就露马脚了
首先p多余
p=name[0];更是说明作者思路不清,因为这是一句废话
这不是“改进”是“改退”
P280
char **p;
……
printf(“%d\n”,*p);
评:用%d输出指针是错误的
P 282
如果在一个指针变量中存放一个目标变量的地址,这就是“单级间址

指向指针的指针用的是“二级间址”方法。

评:怎么总发明这些没用的新概念
“间址”这个说法非常可笑
P 282
但实际上在程序中很少有超过二级间址的。级数愈多,愈难理解,容易产生混乱,出错机会也多。

评:出错不是因为级数多
而是还不懂
P 283
在某些情况下,main函数可以有参数

评:这叫什么话
什么叫“在某些情况下”
在哪些情况下啊
P 283
int main(int argc,char argv[])
……argv……,它是一个
char指针数组,数组中的每一个元素(其值为指针)指向命令行中的一个字符串。

评:数组中有一个元素并不指向命令行中的字符串
P 283
命令行可以写成以下形式:
file1 China Beijing
file1为可执行文件名,……实际上,文件名应包括盘符、路径,今为简化起见,用file1来代表。

评:那东西能简化吗?
和实际情况根本不符
P 284
在Visual C++环境下对 程序编译和连接后,……

评:程序不可移植?

P 284
如果用UNIX系统的命令行输入:
$ ./echo Computer and C Language

评:前面用VC++
这里居然冒出个UNIX系统

难得的是老谭居然知道了前几版的echo的荒谬
P 285
第7章介绍过全局变量和局部变量,全局变量是分配在内存中的静态存储区的,非静态的局部变量(包括形参)是分配在内存中的动态存储区的,这个存储区是一个称为栈(stack)的区域

评:C语言并没有全局变量这个概念
只有外部变量
如果把外部变量叫全局变量
那么static 的外部变量的“全局”在哪里体现?

此外静态存储区、动态存储区、栈之类也都是没有依据的说法

P 285
C语言还允许建立内存动态分配区域

评:有时陈述越荒唐,越难以反驳
C语言根本就没有“内存动态分配区域”这样的概念,更谈不上“允许建立”

P 285
这些数据是临时存放在一个特别的自由存储区,称为堆(heap)区

评:对错暂且不谈
这些与C没有任何关系”
http://bbs.chinaunix.net/thread-3591547-1-1.html
这个帖子里对前面几个相关问题有详细的讨论
这里不再赘述
P 285
void *malloc(unsigned int size);
……形参size的类型定为无符号整型(不容许为负数)

评:形参size的类型不一定是无符号整型
此外没有说明size为0的情况
P 285
第7章介绍过全局变量和局部变量,全局变量是分配在内存中的静态存储区的,非静态的局部变量(包括形参)是分配在内存中的动态存储区的,这个存储区是一个称为栈(stack)的区域
评:C语言并没有全局变量这个概念
只有外部变量
如果把外部变量叫全局变量
那么static 的外部变量的“全局”在哪里体现?

此外静态存储区、动态存储区、栈之类也都是没有依据的说法
P 285
C语言还允许建立内存动态分配区域
评:有时陈述越荒唐,越难以反驳
C语言根本就没有“内存动态分配区域”这样的概念,更谈不上“允许建立”
P 285
这些数据是临时存放在一个特别的自由存储区,称为堆(heap)区
评:对错暂且不谈
这些与C没有任何关系

P 285
void *malloc(unsigned int size);
……形参size的类型定为无符号整型(不容许为负数)
评:形参size的类型不一定是无符号整型
此外没有说明size为0的情况

P 286
void *calloc(unsigned n,unsigned size);

评:这种函数原型是几十年前的
早就过时了
老谭居然用这种过时的东西来冒充C99
此外书中对该函数的功能描述也极不完整
P 286
void free(void *p);
……。p应是最近一次调用calloc或malloc函数时得到的函数返回值。

评:简单的一句话
竟然有两个错误
而且都是原则性的错误
P 286
void *realloc(void *p, unsigned int size);
用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL。如
realloc(p,50);//将p所指向的已分配的动态空间改为50字节

评:能够接二连三地在两页之内连续不断地信口开河
令人叹为观止

P 286
说明:以前的C版本提供的malloc和calloc函数得到的是指向字符型数据的指针,其原型为
char *malloc(unsigned int size);

评:必须补充说明的是
老谭的书提供的函数原型也是旧版本,最多是一种伪装成新版本的显得有点不伦不类的“半新半旧”版本
P 286
说明:以前的C版本提供的malloc和calloc函数得到的是指向字符型数据的指针,其原型为
char *malloc(unsigned int size);

评:必须补充说明的是
老谭的书提供的函数原型也是旧版本,最多是一种伪装成新版本的显得有点不伦不类的“半新半旧”版本

P 286
void *calloc(unsigned n,unsigned size);
评:这种函数原型是几十年前的
早就过时了
老谭居然用这种过时的东西来冒充C99
此外书中对该函数的功能描述也极不完整
P 286
void free(void *p);
……。p应是最近一次调用calloc或malloc函数时得到的函数返回值。
评:简单的一句话
竟然有两个错误
而且都是原则性的错误
P 286
void *realloc(void *p, unsigned int size);
用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL。如
realloc(p,50);//将p所指向的已分配的动态空间改为50字节
评:能够接二连三地在两页之内连续不断地信口开河
令人叹为观止
P 286
说明:以前的C版本提供的malloc和calloc函数得到的是指向字符型数据的指针,其原型为
char *malloc(unsigned int size);
评:必须补充说明的是
老谭的书提供的函数原型也是旧版本,最多是一种伪装成新版本的显得有点不伦不类的“半新半旧”版本

P 287
pt=(int *)malloc(100);
……
要说明的是类型转换只是产生了一个临时的中间值赋给了pt,但没有改变malloc函数本身的类型。

评:废话
malloc函数返回值的类型怎么可能改变呢?
“一个临时的中间值”属于捏造
产生于根本不理解类型转换运算
P 287
C99标准把以上malloc,calloc,realloc函数的基类型定为void类型,这种指针称为无类型指针(typeless pointer),即不指向哪一种具体的类型数据,只表示用来指向一个抽象的类型的数据,即提供一个纯地址,而不能指向任何具体的对象。

评:
C99标准把以上malloc,calloc,realloc函数的基类型定为void类型
这几个函数的类型和C99没有关系,它们并非C99的新内容
这是在为简介与封底所吹嘘的“按C99标准介绍”提供谎言背书
事实上这本书的很多内容异常陈旧,连C89都没达到

这种指针称为无类型指针(typeless pointer)

这是公然的捏造,欺骗对C一无所知的初学者
C语言中任何表达式都有类型
根本就不存在无类型(typeless)这种概念

只表示用来指向一个抽象的类型的数据

这是用捏造的,子虚乌有的“抽象的类型”这个概念来胡扯

整个8.8.1 8.8.2 两小节,几乎处处是错,而且绝大多数都是硬伤
P 287
C99允许使用基类型为void的指针类型

评:在C89中就已经是这样了
C99在这一点上没有改变
老谭非要在此强调C99这三个字
究竟是何居心
P 287
例8.30建立动态数组,输入5个学生的成绩,另外用一个函数检查其中有无低于60分的,输出不合格的成绩。
#include <stdio.h>
#include <stdlib.h>
int main()
{void check(int *);
int *pl,i;
pl=(int )malloc(5sizeof(int));
for(i=0;i<5;i++)
scanf(“%d”,p1+i);
check(pl);
return 0;
}
void check(int *p)
{int i;
printf(“They are fail:”);
for(i=0;i<5;i++)
if(p[ i]<60)printf(“%d”,p[ i]);
printf(“\n”);
}

评:嗯,初学者使用malloc()函数最常见的两个毛病都占全了
P 287
pt=(int *)malloc(100);
……
要说明的是类型转换只是产生了一个临时的中间值赋给了pt,但没有改变malloc函数本身的类型。
评:废话
malloc函数返回值的类型怎么可能改变呢?
“一个临时的中间值”属于捏造
产生于根本不理解类型转换运算
P 287
C99标准把以上malloc,calloc,realloc函数的基类型定为void类型,这种指针称为无类型指针(typeless pointer),即不指向哪一种具体的类型数据,只表示用来指向一个抽象的类型的数据,即提供一个纯地址,而不能指向任何具体的对象。
评:这几个函数的类型和C99没有关系,它们并非C99的新内容
这是在为简介与封底所吹嘘的“按C99标准介绍”提供谎言背书
事实上这本书的很多内容异常陈旧,连C89都没达到

这种指针称为无类型指针(typeless pointer)

这是公然的捏造,欺骗对C一无所知的初学者
C语言中任何表达式都有类型
根本就不存在无类型(typeless)这种概念

只表示用来指向一个抽象的类型的数据

这是用捏造的,子虚乌有的“抽象的类型”这个概念来胡扯

整个8.8.1 8.8.2 两小节,几乎处处是错,而且绝大多数都是硬伤
P 287
C99允许使用基类型为void的指针类型
评:在C89中就已经是这样了
C99在这一点上没有改变
老谭非要在此强调C99这三个字
究竟是何居心
P 287~288
例8.30建立动态数组,输入5个学生的成绩,另外用一个函数检查其中有无低于60分的,输出不合格的成绩。
#include <stdio.h>
#include <stdlib.h>
int main()
{void check(int *);
int *pl,i;
pl=(int )malloc(5sizeof(int));
for(i=0;i<5;i++)
scanf(“%d”,p1+i);
check(pl);
return 0;
}
void check(int *p)
{int i;
printf(“They are fail:”);
for(i=0;i<5;i++)
if(p[ i]<60)printf(“%d”,p[ i]);
printf(“\n”);
}
评:嗯,初学者使用malloc()函数最常见的两个毛病都占全了

P 289
指针就是地址,凡是出现“指针”的地方,都可以用“地址”代替,例如,变量的指针就是变量的地址,指针变量就是地址变量。

评:大错特错
根据我的观察,绝大多数学不懂指针的人恰恰就是因为把指针当成了地址
另一些把指针当成地址的人,则往往是用一种扭曲的逻辑来理解指针
P 289
指针就是地址本身

评:不是去辨析两个概念之间的区别
而是武断地混淆它们
首先,这两个概念属于不同的范畴
指针是C语言层面的概念
而地址则是C语言之外的概念
地址用于描述内存(可能是实的也可能是虚的)
C语言用指针的值来表示地址
但不能说指针就是地址
就像代码中可能用1表示问题中的一只猴子
不能说C代码中的1就是1只猴子一样
其次,指针表达式的值表示地址
但指针的含义通常不仅仅限于值这一方面
不能因为人有两只手
就说两只手就是人
第三
指针是C语言的一种数据类型
但C语言并没有地址这种数据类型

P 289
例如2008是某一变量的地址,2008就是变量的指针。

评:2008在C语言中只是一个int类型的字面量
绝对不可能是指针

P 289
有人认为指针是类型名,指针的值是地址

评:不是什么“有人认为”
而是C标准就是如此规定
老谭既然号称这本书是“按照C99”写的
难道连C99都压根没有看过吗?

至于“指针的值是地址”,其含义是“指针类型表达式的值表示地址”
这有什么不对吗

P 289
类型是没有值的,只有变量才有值

评:“类型是没有值的”,是的,但所有表达式都有类型,也都有值
至于
“只有变量才有值”
这话说的太过分了吧
和“根据常识,偶数不是素数”有得一拼
希望谭先生自己改正

P 289
正确的说法是指针变量的值是一个地址

评:“这句话确实不能算错,但是和指针的值是地址的含义没有本质区别
对比起来
“指针变量的值是一个地址”是一种相当片面的说法
因为严格地说法应该是,指针类型表达式的值是一个地址,或曰指针的值是地址

P 289
不要杜撰出“地址的值”这样莫须有的名称。

评:老谭这是针对谁啊?
没看见有人杜撰“地址的值”这样的词啊
所以老谭的这个指责本身倒是显得非常“莫须有”
而且
谭先生自己恰恰一向是以杜撰莫须有名词为能事的
试举几例:“基类型”“中间变量”“行控制”“列控制”“列元素”“动态自由分配区”“动态空间”“无类型指针”……
而这些,都是子虚乌有的概念

P 289
什么叫“指向”,地址就意味着指向,因为通过地址能找到具有该地址的对象。

评:谭先生又错了
通过地址并不一定能找到具有该地址的对象
int i=6;
void *p=&i;
按照你所谓“指针变量的值是一个地址”的说法
你不否认p的值是一个地址吧
然而仅仅根据这个地址能找到i这个对象吗?
所以“地址就意味着指向”显然是错误的

P 289
对于指针变量来说,把谁的地址存放在指针变量中,就说指针变量指向谁

评:懒得多废话了
参见LS
难道能说p指向i吗?
显然不能
p谁也不指向
P 289
int a,*p;
float b;
p=&a; //a是int型,合法
p=&b; //b是float型,类型不匹配

评:居然用了“int型”而不是“整型”,有进步,表扬
但最后一句也并非不合法

P 289
既然许多数据对象(如变量、数组、字符串和函数等)都在内存中被分配存储空间,就有了地址,也就有了指针。

评:这段话表明谭先生对数据对象这个词的确切含义不甚了了
其次“也就有了指针”的说法是错误的
P 289
可以定义一些指针变量,存放这些数据对象的地址,即指向这些对象

评:同上,谭先生还不懂得“对象”或“数据对象”这个词在C语言中的基本含义

P 289
指针就是地址,凡是出现“指针”的地方,都可以用“地址”代替,例如,变量的指针就是变量的地址,指针变量就是地址变量。
评:大错特错
根据我的观察,绝大多数学不懂指针的人恰恰就是因为把指针当成了地址
另一些把指针当成地址的人,则往往是用一种扭曲的逻辑来理解指针
P 289
指针就是地址本身
评:不是去辨析两个概念之间的区别
而是武断地混淆它们

首先,这两个概念属于不同的范畴
指针是C语言层面的概念
而地址则是C语言之外的概念
地址用于描述内存(可能是实的也可能是虚的)
C语言用指针的值来表示地址
但不能说指针就是地址
就像代码中可能用1表示问题中的一只猴子
不能说C代码中的1就是1只猴子一样
其次,指针表达式的值表示地址
但指针的含义通常不仅仅限于值这一方面
不能因为人有两只手
就说两只手就是人
第三
指针是C语言的一种数据类型
但C语言并没有地址这种数据类型
(暂时想到这么多,后面想到我再补充)
P 289
例如2008是某一变量的地址,2008就是变量的指针。
评:2008在C语言中只是一个int类型的字面量
绝对不可能是指针
P 289
有人认为指针是类型名,指针的值是地址
评:不是什么“有人认为”
而是C标准就是如此规定
老谭既然号称这本书是“按照C99”写的
难道连C99都压根没有看过吗?

至于“指针的值是地址”,其含义是“指针类型表达式的值表示地址”
这有什么不对吗
P 289
类型是没有值的,只有变量才有值
评:“类型是没有值的”,是的,但所有表达式都有类型,也都有值
至于
“只有变量才有值”
这话说的太过分了吧
和“根据常识,偶数不是素数”有得一拼
希望谭先生自己改正

P 289
正确的说法是指针变量的值是一个地址
评:这句话确实不能算错,但是和指针的值是地址的含义没有本质区别
对比起来
“指针变量的值是一个地址”是一种相当片面的说法
因为严格地说法应该是,指针类型表达式的值是一个地址,或曰指针的值是地址

P 289
不要杜撰出“地址的值”这样莫须有的名称。
评:老谭这是针对谁啊?
没看见有人杜撰“地址的值”这样的词啊
所以老谭的这个指责本身倒是显得非常“莫须有”
而且
谭先生自己恰恰一向是以杜撰莫须有名词为能事的
试举几例:“基类型”“中间变量”“行控制”“列控制”“列元素”“动态自由分配区”“动态空间”“无类型指针”……
而这些,都是子虚乌有的概念
P 289
什么叫“指向”,地址就意味着指向,因为通过地址能找到具有该地址的对象。
评:谭先生又错了
通过地址并不一定能找到具有该地址的对象
int i=6;
void *p=&i;
按照你所谓“指针变量的值是一个地址”的说法
你不否认p的值是一个地址吧
然而仅仅根据这个地址能找到i这个对象吗?
所以“地址就意味着指向”显然是错误的

P 289
对于指针变量来说,把谁的地址存放在指针变量中,就说指针变量指向谁
评:懒得多废话了
参见LS
难道能说p指向i吗?
显然不能
p谁也不指向

P 289
只有与指针变量的基类型相同的数据的地址才能存放在相应的指针变量中。
评:写的书如果如同筛子一般处处都是漏洞的话
我觉得还是少使用“只有”这类只有懂得C语言的人才有资格使用的词汇
谭先生如果把自己书上的“只有”二字全部删除的话
错误至少会减少三分之一

根据我的经验
谭先生一说“只有”
十有八九是错的

P 289
int a,*p;
float b;
p=&a; //a是int型,合法
p=&b; //b是float型,类型不匹配
评:居然用了“int型”而不是“整型”,有进步,表扬
但最后一句也并非不合法

P 289
既然许多数据对象(如变量、数组、字符串和函数等)都在内存中被分配存储空间,就有了地址,也就有了指针。
评:这段话表明谭先生对数据对象这个词的确切含义不甚了了
其次“也就有了指针”的说法是错误的

P 289
可以定义一些指针变量,存放这些数据对象的地址,即指向这些对象
评:同上,谭先生还不懂得“对象”或“数据对象”这个词在C语言中的基本含义

P 289
void *是一种特殊的指针,不指向任何类型的数据。
评:很对
但请谭先生注意在287页有一个自相矛盾的陈述
“只表示用来指向一个抽象的类型的数据”

所谓“抽象的类型的数据”是子虚乌有的说法

P 289
如果需要用此地址指向某类型的数据,应对地址进行类型转换
评:“此地址指向某类型的数据”是绝对不可能的
是进行类型转换后得到的指针指向数据

P 289
表8.4 指针变量的类型及含义
int i; 定义整型变量i
评:“整型”是国内C语言书最混乱不堪的概念
就谭这本书而言
有时它表示一个集合概念
在这里又表示int类型
国内对这种混乱熟视无睹
表明我们的教育在总体上来说缺乏起码的逻辑常识
P 289
int (*p)[4];p为指向包含4个元素的一维数组的指针变量
评:“包含4个元素”应为“包含4个int类型元素”
P 289
int f();f为返回整型函数值的函数
评:这个……
是否应该算是一种不完全类型?
请教 幻の上帝 或 OwnWaterloo

P 290
指针变量加(减)一个整数。
例如:p++,p–,p+i,p-i,p+=i,p-=i等均是指针变量加(减)一个整数。
将该指针变量的原值(是一个地址)和它指向的变量所占用的存储单元的字节数相加(减)。
评:这些运算是有前提条件的
离开前提谈不上运算
P 290
指针变量赋值。
将一个变量地址赋给一个指针变量。例如
p=&a; (将变量a的地址赋给p)
评:没人知道这里的a和p是什么
所以这里写的东西毫无意义

P 290
p=array; (将数组array的首地址赋给p)
评:同上
P 290
p=&array[ i]; (将数组array第i个元素的地址赋给p)
评:这种写法很笨

p=max;
p1=p2;

脱离声明写这些毫无意义
P 290
两个指针变量可以相减。
如果两个指针变量都指向同一数组元素,则两个指针变量值之差是两个指针之间的元素个数
评:这个说法偷工减料,不完整
此外指针相减不限于指针变量

P 290
(5)指针运算
评:内容实际上是“指针变量”运算,而且严重不全。叙述不完整,不严密
P 290
指针变量可以有空值,即该指针变量不指向任何变量
评:指针变量不一定指向变量

P 290
在stdio.h头文件中对NULL进行了定义:
#define NULL 0
评:实际上正规一点的说法应该是在stddef.h中
当然stdio.h中也有NULL的定义

但说NULL 被定义为0
不知道老谭用的到底是什么编译器
该书的编译器是VC++6.0
在VC++6.0的stdio.h中
NULL并非是被定义成了0
而是
#define NULL ((void *)0)

P 291
任何指针变量或地址都可以与NULL作相等或不相等的比较
评:既然前面说“指针就是地址”
那么“指针变量或地址”是什么意思?
小学语文问题
P 291
使用指针的优点:①提高程序效率
评:未必
P 291
②在调用函数时当指针指向的变量值改变时,这些值能够为主调函数使用,即可以从函数调用得到多个改变的值;
评:这是话吗?
P 291
同时应该看到,指针使用实在太灵活
评:没看到
这是在用故弄玄虚的方法来吓唬读者

P 291
使用指针的优点:①提高程序效率
评:未必

第9章 用户自己建立数据类型
P 293
第9章 用户自己建立数据类型
评:这个标题不恰当
这一章讲的是结构体
但其实数组、指针也是用户自己建立的数据类型
P 293
struct Student
{int num;
char name[20];
char s ex;
int age;
float score;
char addr[30];
};
在定义了结构体变量后,系统会为之分配内存单元。根据结构体类型中包含的成员情况,在Visual C++中占63个字节(4+20+1+4+4+30=63)
评:露怯了
这个表明谭先生对结构体类型所知极其有限
简单地把结构体各成员的长度加起来并不是结构体的尺寸

其实回避掉这个尺寸问题倒不失为一良策
既然无知就要懂得藏拙
P 294
只不过int等类型是系统已声明的
评:系统什么时候,在哪声明了?
P 295
计算机对内存的管理是以“字”为单位的(许多计算机系统以4个字节为一个“字”)。如果在一个“字”中只存放了一个字符,虽然只占一个字节,但该“字”中的其他3个字节不会接着存放下一个数据,而会从下一个“字”开始存放其他数据。因此在用sizeof运算符测量student1的长度时,得到的不是理论值63,而是64,必然是4的倍数。
评:“计算机对内存的管理是以“字”为单位的(许多计算机系统以4个字节为一个“字”)”
掩盖无知的秘诀之一的大而化之
笼统地说“计算机对内存的管理”
但是谁都知道计算机包括硬件软件
硬件包括CPU MU IO设备等
软件包括OS 编译器
老谭所谓的计算机是指的那个呢
对不起,我就是不说
求甚解的话让你自己去瞎猜
不求甚解的话正好蒙过去了
“许多计算机系统以4个字节为一个“字””同样是企图蒙混过关
既然“许多”,那么想必还有不是以4个字节为一个字的
试问这种情况又怎么说呢?

至于“得到的不是理论值63,而是64”
这东西本来就没有理论值
而且我特意用VC++编译器试了一下
得到的却是68

这个68足以说明前面老谭所说的都是在胡扯
P 296
只能对变量赋值,存取或运算,而不能对一个类型赋值、存取或运算。
评:1.
赋值本身就是运算
2.
谁说对类型不能运算
sizeof(int) 难道不是运算?
P 298
在程序中可以对变量的成员赋值,例如:
student.num=10010;
“.”是成员运算符,它在所有的运算符中优先级最高,因此可以把student.num作为一个整体来看待,相当于一个变量。
评:1.如果变量的成员是用const限定的,就不可以对它赋值
2.确实“可以把student.num作为一个整体来看待,相当于一个变量”,但这和“它在所有的运算符中优先级最高”没有因果关系,因此“因此”二字是牵强附会。

P 298
struct Date
{int month;
int day;
int year;
};
struct Student
{int num;
char name[20];
char se x;
int age;
struct Date birthday;
char addr[30];
};
struct Student student1;

如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低级的一级成员。只能对最低级的成员进行赋值或存取以及运算。……
不能用student1.birthday来访问student1变量中的成员birthday,因为birthday本身是一个结构体成员。
评:这简直是瞪着眼睛胡说八道

P 298
student1.age++;
由于“.”运算符的优先级最高,因此student1.age++是对(student1.age)进行自加运算,而不是先对age进行自加运算。
评:错。“.”和“++”优先级一样高
P 299
说明:结构体变量的地址主要用作函数参数,传递结构体变量的地址。
评:这个绝对是无稽之谈
如果说结构体变量简化了函数间的参数可能还有点贴边
但是说指向结构体的指针的主要作用是函数参数
基本上就是胡扯了
P 299
例9.2 输入两个学生的学号、姓名和成绩,输出成绩较高的学生的学号、姓名和成绩。
#include <stdio.h>
int main()
{struct Student
{ int num;
char name[20];
float score;
}student1,student2;
scanf(“%d%s%f”,&student1.num,student1.name,&student1.score);
scanf(“%d%s%f”,&student2.num,student2.name,&student2.score);
printf(“The higher score is:\n”);
if(student1.score>student2.score)
printf(“%d %s %6.2f\n”,student1.num,student1.name,student1.score);
else if(student1.score<student2.score)
printf(“%d %s %6.2f\n”,student2.num,student2.name,student2.score);
else
{printf(“%d %s %6.2f\n”,student1.num,student1.name,student1.score);
printf(“%d %s %6.2f\n”,student2.num,student2.name,student2.score);
}
return 0;
}

评:从没见过这么傻的代码
居然如此笨拙地使用结构体
跟买辆汽车用驴拉没什么两样

P 300
输出student1的全部信息是轻而易举的……如果用普通变量则难以方便地实现这一目的
评:恰恰相反
这种不恰当地使用结构体的方式效果反而不如用普通变量
P 300
例9.3 有3个候选人,每个选民只能投票选一人,要求编一个统计选票的程序,先后输入被选人的名字,最后输出各人得票结果。
#include <string.h>
#include <stdio.h>

struct Person {char name[20];
int count;
}leader[3]={“Li”,0,“Zhang”,0,“Sun”,0};
int main()
{int i,j;
char leader_name[20];
for(i=1;i<=10;i++)
{scanf(“%s”,leader_name);
for(j=0;j<3;j++)
if(strcmp(leader_name,leader[j].name)==0)leader[j].count++;
}
printf(“\nResult:\n”);
for(i=0;i<3;i++)
printf(“%5s:%d\n”,leader[i].name,leader[i].count);
return 0;
}
评:把类型声明写在了前面,很好。但同时又定义了一个外部变量,不伦不类,毫无意义且肆无忌惮地破坏代码结构
为只有一个main()的代码定义外部变量无论怎么说都属于头脑不清醒。严重的误导

代码中出现了一个莫名其妙的10,非但是一个Magic Number,而且毫无依据

就这个写法(只有一个main())而言,不使用结构体,代码更简洁

还有一点,这个代码的命名很荒唐。
我发现有个奇怪的现象,主张用英文单词做标识符的人从来不指责这种荒唐的英文
P 301
Li
Li
Sun
Zhang
Zhabg
Sun
Li
Sun
Zhang
Li

Result:
Li:4
Zhang:2
Sun:3
评:结果很荒唐
4+2+3 共9票
但书中对此没有做任何交代和说明
P 301~302
例9.4 有n个学生的信息(包括学号、姓名、成绩),要求按照成绩的高低顺序输出各学生的信息
#include <stdio.h>
struct Student { int num;
char name[20];
float score;
};
int main()
{struct Student stu[5]={{10101,“Zhang”,78},{10103,“Wang”,98.5},{10106,“Li”,86},
{10108,“Ling”,73.5},{10110,“Sun”,100}};
struct Student temp;
const int n=5;
int i,j,k;
printf(“The order is:\n”);
for(i=0;i<n-1;i++)
{k=i;
for(j=i+1;j<n;j++)
if(stu[j].score>stu[k].score)
k=j;
temp=stu[k];stu[k]=stu[i];stu[i]=temp;
}
for(i=0;i<n;i++)
printf(“%6d%8s%6.2f\n”,stu[i].num,stu[i].name,stu[i].score);
printf(“\n”);
return 0;
}

评:这段代码最可笑的就
const int n=5;
企图追认数组尺寸

其次
printf(“The order is:\n”);
这句话的位置也不正确

其他的毛病这里就不说了
P 302
程序分析:
(1)程序中第11行定义了常变量n,在程序运行期间它的值不能改变。如果学生数改为30,只须把第11行改为下行即可,其余各行不必修改。
const int n=30;
也可以不用常变量,而用符号常量,可以取消第11行,同时在第二行前加一行:
#define N 5
读者可比较这两种方法,觉得哪一种方法方便好用?
评:其实两种方法都行不通
都是一厢情愿

常变量这个翻译非常蹩脚

而且n的值也并非“在程序运行期间它的值不能改变”
P 303
一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就是指向该结构体变量
评:“一个结构体变量的起始地址就是这个结构体变量的指针”
struct x{ char c ;} s;
&s.c 和 & s 的值相同,但前者并不是这个结构体变量的指针

“如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就是指向该结构体变量”
void *q = &s;
q 并不指向结构体变量
P 303
指向结构体对象的指针既可以指向结构体变量,也可以指向结构体数组中的元素。
评:这是毫无意义的废话
P 305
#include <stdio.h>
struct Student
{int num;
char name[20];
char six;
int age;
};
struct Student stu[3]={{10101,“Li Lin”,‘M’,18},{10102,“Zhang Fang”,‘M’,19},
{10104,“Wang Min”,‘F’,20}};
int main()
{struct Student *p;
printf(“No. Name six age\n”);
for(p=stu;p<stu+3;p++)
printf(“%5d %-20s%2c%4d\n”,p->num,p->name,p->six,p->age);

return 0;
}

评:只有一个main()函数,但却使用了外部变量,无厘头
只有古代的C语言才必须这样
P 305
本例中一个元素所占的字节数理论上为4+20+1+4=29字节
评:什么“理论”
根本就不存在这种理论,哪里谈得上“理论上”?
P 306
不要认为反正p是存放地址的,可以将任何地址赋给它。
评:造成这种错误认识的根源恰恰是老谭自己一向把地址与指针混为一谈
在303页,老谭是这样写的

一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就是指向该结构体变量
P 306
如果要将某一成员的地址赋给p,可以用强制类型转换,先将成员的地址转换成p的类型。例如:
p=(struct Student *)stu[0].name;
此时,p的值是stu[0]元素的name成员的起始地址。可以用“printf(“%s”,p);”输出stu[0]中成员name的值。但是,p仍保持原来的类型。如果执行“printf(“%s”,p+1);”,则会输出stu[1]中name的值。执行p++时,p的值增加了结构体struct Student的长度。
评:这是 胡闹+教唆
一相情愿的臆测
P 306
在调用inpu函数时,将主函数中的stu数组的首元素的地址传给形参数组stu,使形参数组stu与主函数中的stu数组具有相同的地址
评:概念错误
形参stu的地址是&stu , 它不可能与main()中的stu或&stu相同
P 309
实参是指针变量p,形参是结构体数组
评:形参不可能是数组,哪怕形式上是用[]声明的
实参也不是指针变量p,严格的说法是p的值
P 309
用数组存放数据时,必须事先定义固定的数组长度(即元素个数)
评:从上下文来看,这本号称“按照C99标准进行介绍”的书的作者明显忘记了C99还有VLA
P 310
例9.8 建立一个如图9.9所示的简单链表,它由三个学生数据的结点组成,要求输出各结点中的数据。
评:很难说图9.9所示的是否是链表,因为没头没尾
而且和后面给出的代码不一致
P 310~311
#include <stdio.h>
struct Student
{
int num;
float score;
struct Student *next;
};
int main()
{struct Student a,b,c,*head,*p;
a.num = 10101;a.score = 89.5;
b.num = 10103;b.score = 90;
c.num = 10107;c.score = 85;
head = &a;
a.next = &b;
b.next = &c;
c.next = NULL;
p = head;
do
{printf(“%ld%5.1f\n”,p->num,p->score);
p=p->next;
}while (p != NULL );
return 0;
}

评:不应该使用do-while语句,而应该使用while语句
P 311
请读者分析:……②没有头指针head行不行。③p起什么作用,没有它行不行?
评:行!至少可以删去一个,如果用函数实现输出,两个都可以去掉
所以定义这两个变量多此一举
P 311
本例是比较简单的,所有结点都是在程序中定义的,不是临时开辟的,也不能用完后释放,这种链表称为“静态链表”
评:所谓“静态链表”是一种捏造,说是“伪链表”倒是比较符合实际
实际上定义a,b,c还不如定义一个数组
P 311
例9.9 写一函数建立一个有3名学生数据的单向动态链表。
评:这个题目的要求很滑稽
已经确定是3名学生了
建立链表是无聊之极的行为
这和链表的本质是相矛盾的
P 311
解题思路:……定义3个指针变量:head,p1和p2,它们都是用来指向struct Student类型数据的。先用malloc函数开辟第一个结点,并使p1和p2指向它。然后从键盘读入一个学生的数据给p1所指的第1个结点。在此约定学号不会为零,如果输入的学号为0,则表示建立链表的过程完成,该结点不应连接到链表中。先使head的值为NULL(即等于0),这是链表为“空”时的情况(即head不指向任何结点,即链表中无结点),当建立第1个结点就使head指向该结点。
评:总的印象是,这段“思路”颠三倒四,混乱不堪
首先,“定义3个指针变量”,没头没脑。其实也没有必要
“先用malloc函数开辟第一个结点,并使p1和p2指向它”,还没输入数据就开辟结点,无事生非
“然后从键盘读入一个学生的数据给p1所指的第1个结点”,结点是否开辟出来他是不管的
“在此约定学号不会为零,如果输入的学号为0”,小学语文不过关
“则表示建立链表的过程完成,该结点不应连接到链表中”,当然不应该,应该去造成内存泄露
“先使head的值为NULL(即等于0),”,又是没头没脑
“当建立第1个结点就使head指向该结点”,这话应该在前面说吧
P 312
由于p1->num的值为0,不再执行循环,此新结点不应被连接到链表中。此时将NULL赋给p2->next,见图9.14(b)。建立链表过程至此结束,p1最后所指的结点未链入链表中
评:嗯。成功地造成了内存泄露
老谭在这里演示了他制造内存泄露的独家秘笈
学这本书的人有望成为破坏之神或者是混乱之神
P 313
图9.14(b)
评:如果有人不清楚令程序员心惊胆战谈虎色变在写代码时如履薄冰竭力避免的内存泄露是什么
看一下这个就清楚了

P 313~314
#include <stdio.h>
#include <stdlib.h>
#define LEN sizeof(struct Student)
struct Student
{long num;
float score;
struct Student *next;
};
int n;
struct Student *creat(void)
{struct Student *head;
struct Student *p1,*p2;
n=0;
p1=p2=(struct Student )malloc(LEN);
scanf(“%ld,%f”,&p1->num,&p1->score);
head=NULL;
while(p1->num!=0)
{n=n+1;
if(n==1)head=p1;
else p2->next=p1;
p2=p1;
p1=(struct Student
)malloc(LEN);
scanf(“%ld,%f”,&p1->num,&p1->score);
}
p2->next=NULL;
return(head);
}

int main()
{ struct Student *pt;
pt=creat();
printf(“\nnum:%ld\nscore:%5.1f\n”,pt->num,pt->score);//输出第1个结点的成员值
return 0;
}

评:这段代码的错误和毛病太多了
数不过来
但最严重的一个恐怕是产生了内存泄露(memory leak)

另外,如果一开始就输入0值,程序会发生可怕的崩溃
这是因为根本没有考虑
printf(“\nnum:%ld\nscore:%5.1f\n”,pt->num,pt->score);
这条语句的前提条件是pt不为0
建立链表,居然要借助一个外部变量的做法前无古人
这样对外部变量有依赖性的链表基本上是没有意义的
更为怪异的是
int n;
在定义时不赋初值
却在函数调用内部赋值为0
P 315
例9.10 编写一个输出链表的函数print。
#include <stdio.h>
#include <stdlib.h>
#define LEN sizeof(struct Student)
struct Student
{long num;
float score;
struct Student *next;
};
int n;
void print(struct Student *head)
{struct Student *p;
printf(“\nNow,These %d records are:\n”,n);
p=head;
if(head!=NULL)
do
{printf(“%ld %5.1f\n”,p->num,p->score);
p=p->next;
}while(p!=NULL); //当p不是空地址
}

评:这段代码很傻
首先
搞不清楚为什么#include <stdlib.h>
其次,那个n充分暴露了代码的弱点
第三,此head非彼head,定义p并把head的值赋给p,多此一举
第四,
if(head!=NULL)
do
{printf(“%ld %5.1f\n”,p->num,p->score);
p=p->next;
}while(p!=NULL);
这明明就是一个while语句,能把一条简单的while语句写得如此复杂且别扭,却也难得
第五,即使写if(head!=NULL)它也应该在
printf(“\nNow,These %d records are:\n”);
p=head;
之前,而不是之后
第六,“空地址”是老谭发明的新概念,C语言里没有这东东
P 316
把例9.7和9.8合起来加上一个主函数,组成一个程序,即:
#include <stdio.h>
#include < malloc.h >
#define LEN sizeof(struct Student)
struct Student
{long num;
float score;
struct Student *next;
};
int n;
struct Student *creat()
{struct Student *head;
struct Student *p1,*p2;
n=0;
p1=p2=(struct Student )malloc(LEN);
scanf(“%ld,%f”,&p1->num,&p1->score);
head=NULL;
while(p1->num!=0)
{n=n+1;
if(n==1)head=p1;
else p2->next=p1;
p2=p1;
p1=(struct Student
)malloc(LEN);
scanf(“%ld,%f”,&p1->num,&p1->score);
}
p2->next=NULL;
return(head);
}

void print(struct Student head)
{struct Student *p;
printf(“\nNow,These %d records are:\n”,n);
p=head;
if(head!=NULL)
do
{printf(“%ld %5.1f\n”,p->num,p->score);
p=p->next;
}while(p!=NULL);
}

void main()
{ struct Student *head;
head=creat();
print(head);
}

评:

居然弄出个void main()
老谭不是说C99不带这样的吗

此外
void print(struct Student head)
{struct Student *p;
printf(“\nNow,These %d records are:\n”,n);
p=head;
是一个显而易见的低级错误

#include <malloc.h>

是一种古老得已经腐朽的写法
P 317
实型变量
评:把float称为实型既没有任何依据也没有任何意义
是错误的

P 317
也就是使用覆盖技术,后一个数据覆盖了前面的数据
评:这是故弄玄虚
向内存或寄存器写入后以前的数据就消失,这是存储器的基本性质
根本不是什么“覆盖技术”
P 317
图 9.17 1000地址
评:不知所云
P 318
union Data
{ int I;
char ch;
float f;
}
评:根据上下文
应为 int i;
P 318
结构体变量所占内存长度是各成员占的内存长度之和。每个成员分别占有其自己的内存单元。而共用体变量所占内存长度等于最长成员的长度。
评:大错特错
P 318
只有先定义了共用体变量才能引用它,。
评:如果不算废话的话只能算是错话
P 318
但应注意,不能引用共用体变量,而只能引用共用体中的成员nion Data
评:胡扯。完全是无稽之谈
P 318
例如,前面定义了a,b,c为共用体变量,下面的引用方式是正确的
a.i
a.ch
a.f
不能只引用共用体变量,例如下面的引用是错误的:
printf(“%d”,a);
因为a的存储区可以按不同的类型存放数据,有不同的长度,仅用共用体变量名a,系统无法知道究竟应该输出哪一个成员的值。
评:printf(“%d”,a); 确实是错误的
但后面的解释是错误的
printf(“%d”,a);错误的原因是对于共用体没有相应的转换格式
P 319
union Data
{
int i;
char ch;
float f;
}a;
a.i=97;
……可以用以下的输出语句:
printf(“%d”,a.i);
printf(“%c”,a.ch);
printf(“%f”,a.f);
评:误导
后两句都是有问题的
无法确定输出什么样的结果
P 319
a.ch=‘a’;
a.f=1.5;
a.i=40;
……用“printf(“%c”,a.ch);”,输出的不是字符’a’,而是字符’('。因为……
评:这个问题涉及到计算机字符编码和大小端等问题
是不确定的
P 320
不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
评:错
很明显,老谭根本不懂得在C语言中“值”(value)这个术语的基本含义
P 320
C99允许同类型的共用体变量互相赋值
评:在谭的这本书中,一再出现把C99之前就已经存在的规定硬说成是C99的规定
竭力把这本书伪装成C99(事实上该书使用VC++这种编译器注定不可能用来学习C99)
我个人认为这不是学识问题
P 320
C99允许共用体变量作为函数参数
评:再次用C89冒充C99
P 320
例9.11 ……要求用同一表格来处理
评:对不起,C语言里没有“表格”这种东东
P 320
#include <stdio.h>
struct
{int num;
char name[10];
char six;
char job;
union
{int clas;
char position[10];
}category;
}person[2];

int main()
{
int i;
for(i=0;i<2;i++)
{printf(“please enter the data of person:\n”);
scanf(“%d %s %c %c”,&person[i].num,&person[i].name,
&person[i].six,&person[i].job);
if(person[i].job==‘s’)
scanf(“%d”,&person[i].category.clas);
else if(person[i].job==‘t’)
scanf(“%s”,&person[i].category.position);
else
printf(“Input error”);
}
printf(“\n”);
printf(“No. name six job class/position\n”);
for(i=0;i<2;i++)
{
if(person[i].job==‘s’)
printf(“%-6d%-10s%-4c%-4c%-10d\n”,person[i].num,person[i].name,
person[i].six,person[i].job,person[i].category.clas);
else
printf(“%-6d%-10s%-4c%-4c%-10s\n”,person[i].num,person[i].name,
person[i].six,person[i].job,person[i].category.position);
}
return 0;
}

评:毛病很多
首先,只有一个main()却随手定义了一个外部数组,无厘头行为
scanf(“%d %s %c %c”,&person[i].num,&person[i].name,
&person[i].six,&person[i].job);

这句是错误的
else
printf(“Input error”);谭g dfg《C程序设计》(第四版)错误不完全汇集
谭浩强《C程序设计》(第四版)错误不完全汇集 1
前言 25
p12 25dfgdf
p16 26
第一章 程序设计和C语言 27
1.2 什么是计算机语言 27
p1 27
p2 27
p2 27
p3 27
p3 27
1.3 C语言的发展及其特点 29
p4 29
p4 30
p4 31
p5 32
p5 33
p5 34
p5 34
p5 35
p5 36
1.4 最简单的C语言程序 37
p6 37
p6 37
p6 37
p6 37
p7 38
p7 38
p8 39
p9 39
p9 39
p9 39
p10 39
p10 40
p10 40
p11 40
p11 40
p12 40
p10~12 41
1.5 运行C程序的步骤与方法 42
p12 42
p13 42
p14 42
1.6 程序设计的任务 43
p14 43
习题 45
p15 45
第二章 算法——程序的灵魂 46
p16 46
p16 46
p16 47
2.1 什么是算法 47
p16~17 47
2.2 简单的算法举例 48
p17 48
p18 48
p19~20 48
p20 48
p21 48
p21 49
2.3 算法的特性 49
p21 49
2.4 怎样表示一个算法 49
p22 49
p26 50
p26 50
p28 50
p29 51
p31 51
p32 51
p33 51
2.5 结构化程序设计方法 52
p34 52
第三章最简单的C程序设计——顺序程序设计 53
P37 53
P37 54
3.1 顺序程序设计举例 55
P37 55
P37~38 55
P38 56
P38 56
P38 56
P38 57
P38 57
P37~39 57
P39 58
P39 58
3.2数据的表现形式及其运算 58
P39 58
P39 59
P39 59
P37 59
P39~41 60
p40 61
p40 61
p41 61
p41 62
p41 62
p41 62
p41 62
P42 63
P42 63
P42 63
P42 63
P42 63
P42 65
p43 65
p44 65
p44 66
p44 66
p45 66
p45 66
p45 66
p46 66
p46 67
p47 67
p47 68
p47 68
p47 68
p47 68
p48 69
p48 69
p48 69
p48 69
p49 76
p49 76
p49 76
p49 76
p49 77
p49~51 77
P50 78
P50 78
P50 78
P50 79
P51 79
P51 79
3.2.6 怎样确定常量的类型 79
P51 79
P51 79
P51 80
P51 80
P51~52 80
3.2.7 运算符和表达式 80
P52 80
P53 81
P53 81
P53 81
P53 81
P53 81
P53 82
P53 82
P53 82
P54 82
P54 82
P54 83
P54 83
P54 83
P54 83
P54 84
P54 87
P54 87
P54 88
P54 88
P54 88
P54 89
P54 89
P54~55 89
P55 91
P55 91
P56 91
P56 91
P56 92
3.3C语句 92
P57 92
P57 92
P58 92
P59 92
P59 93
P60 93
P60 93
P60 93
P61 94
P61 94
P61 94
P61 94
P62 96
P62 96
P63 96
P63 96
P64 97
P64 97
P64 97
P65 98
3.4 数据的输入输出 98
p66 98
P67 99
P67 99
P67 99
P67 99
P67 99
P67 99
P68 101
P69 101
P69 101
P70 102
P70 102
P73 102
P73~74 102
P74 103
P74 103
P74 103
P75 103
P76 103
P76 104
P76 104
P77 104
P77 104
P78 105
P78 105
P79 105
P79 105
P79 106
P79 106
P79 106
P79 106
P80 106
P82 106
第4章 选择结构程序设计 107
4.1 选择结构和条件判断 107
P85 107
P86 107
4.1 用if语句实现选择结构 107
P87 107
P89 108
P90 108
P91 109
4.3 关系运算符合关系表达式 110
P91 110
P92 110
4.4 逻辑运算符和逻辑表达式 110
P92~93 110
P93 111
P94 112
P95 112
P97 113
4.5 逻辑运算符和逻辑表达式 113
P97 113
P98 114
P99 114
4.6 选择结构的嵌套 114
P100 114
P100~101 114
P101~102 115
4.7用switch语句实现多分支选择结构 116
P103 116
P104 116
4.8 选择结构程序综合举例 117
P105 117
P106 117
P108~109 118
P109~110 119
P111 120
第5章 循环结构程序设计 120
P116 120
5.3 用do…while语句实现循环 121
P117 121
P118 121
P118~119 122
5.4 用for语句实现循环 122
P120 122
P121 122
P122 123
P123 123
P124 123
5.5 循环的嵌套 124
P124~125 124
5.6 几种循环的比较 124
P125 124
5.7 改变循环执行的状态 125
P126 125
P127 125
P128~129 125
5.8循环程序举例 126
P131 126
P132 126
P133 127
P134 128
P135 128
P137 129
P137~138 130
P138 131
P138~139 131
P139~140 132
第6章 利用数组处理批量数据 132
6.1 怎样定义和引用一维数组 132
P142 132
P143 132
P144 134
6.1.2 怎样引用一维数组 134
P147 134
6.2 怎样定义和引用二维数组 135
P149 135
P150 135
P153 135
6.3 字符数组 136
P154 136
P154~155 137
P156 137
P156~157 139
P157 140
p160 141
p161 142
p162 143
p163 143
p164 144
p165 145
p165~166 146
p167 148
p168 149
第7章 用函数实现模块化程序设计 150
7.1 为什么要用函数 150
p170 150
p171 151
p172 152
p172 153
7.2怎样定义函数 153
p172 153
p173 155
p174 156
7.2.2 定义函数的方法 156
7.3 调用函数 157
p174~175 157
7.3.1 函数调用的形式 157
p175 158
p176 159
p177 160
p178 161
p179 163
7.4 对被调用函数的声明和函数原型 164
p180 164
p181 165
p182 166
7.5 函数的嵌套调用 167
p183 167
p184 168
7.6 函数的递归调用 169
p185 170
p186 171
p188 172
p191 174
7.7 数组作为函数参数 174
p192 175
p193 176
p194 178
p195 180
p196 181
p197 182
p198 183
7.8 局部变量和全局变量 183
p199 183
P200 184
P201 184
P202 185
P203 186
7.9 变量的存储方式和生存期 186
P204 186
P205 187
P206 188
P207 188
P208 189
P208~212 190
P209 190
P210 191
P212 192
P213 193
7.10 关于变量的声明和定义 194
P214 194
7.10 内部函数和外部函数 196
P215 196
P216 196
P216~217 196
P217 197
第8章 善于利用指针 197
P220 197
P220~221 198
P221 198
P222 199
P223 199
P224 199
P225 199
P226 201
P227 201
P228 201
P228~229 202
P228~229 202
P230 202
P231 203
P232 203
P233 204
P233~234 204
P235 205
P236 205
P237 205
P238 205
P239 206
P239~240 207
P241 209
P241~242 209
P242~243 210
P243 210
P244 211
P245 213
P246 213
P247 214
P248 215
P249 216
P251~252 217
P252 217
P252~253 218
P254 220
P255 221
P256 222
P257 222
P257~258 222
P258 223
P258~259 224
P259 224
P259~260 225
P260~261 226
P261 226
P262 227
P263 228
P264 228
P265 229
P266 230
8.5 指向函数的指针 231
P267 231
P268 231
P269 232
P268~269 232
P269 233
P270 234
P271~272 234
8.6 返回指针值的函数 236
P274 236
P275 237
P276 237
P277 238
P278~279 238
P280 240
P 282 240
P 282 240
P 283 241
P 283 241
P 283 241
P 284 241
P 284 242
P 285 242
P 285 242
P 285 242
P 285 243
P 285 243
P 285 243
P 285 243
P 285 244
P 286 244
P 286 244
P 286 244
P 286 245
P 286 245
P 286 245
P 286 245
P 286 246
P 286 246
P 287 246
P 287 246
P 287 247
P 287 247
P 287 248
P 287 248
P 287 249
P 287~288 249
P 289 250
P 289 250
P 289 250
P 289 251
P 289 251
P 289 251
P 289 252
P 289 252
P 289 252
P 289 253
P 289 253
P 289 253
P 289 253
P 289 254
P 289 254
P 289 254
P 289 255
P 289 255
P 289 255
P 289 255
P 289 256
P 289 256
P 289 256
P 289 256
P 289 257
P 289 257
P 289 257
P 289 257
P 289 257
P 289 258
P 290 258
P 290 258
P 290 258
P 290 258
P 290 259
P 290 259
P 290 259
P 290 259
P 291 259
P 291 260
P 291 260
P 291 260
P 291 260
第9章 用户自己建立数据类型 260
P 293 260
P 293 260
P 294 261
P 295 261
P 296 262
P 298 262
P 298 262
P 298 263
P 299 263
P 299 263
P 300 264
P 300 264
P 301 265
P 301~302 265
P 302 266
P 303 266
P 303 267
P 305 267
P 305 267
P 306 267
P 306 268
P 306 268
P 309 268
P 309 268
P 310 268
P 310~311 269
P 311 269
P 311 269
P 311 270
P 311 270
P 312 270
P 313 270
P 313~314 271
P 315 272
P 316 273
P 317 274
P 317 274
P 317 274
P 318 274
P 318 275
P 318 275
P 318 275
P 318 275
P 319 275
P 319 276
P 320 276
P 320 276
P 320 276
P 320 276
P 320 277
P 322 278
P 322 278
9.6 使用枚举类型 278
P 323 278
P 323 279
P 323 279
P 324 279
P 324~326 279
P 326 280
P 326 280
P 327 280
P 327 281
P 327 281
P 327 281
P 328 281
P 329 281
P 329 281
P 329 282
P 329 282
P 330 282
第10章 对文件的输入输出 282
P 331 282
P 331 282
P 331 282
P 331 283
P 331 283
P 331 283
P 331 283
P 332 283
P 332 283
P 332 283
P 332 284
P 332 284
P 332 284
P 332 284
P 333 284
P 333 284
P 334 285
P 334 285
P 334 285
P 335 285
P 335 285
P 335 285
P 336 286
P 336 286
P 336 286
P 336 286
P 336 286
P 336 286
P 336 287
P 337 287
P 337 287
P 337 287
P 337 287
P 338 287
P 338 288
P 338~339 288
P 340 289
P 340 290
P 341 290
P 341 290
P 341 290
P 341 290
P 341 291
P 341 291
P 341 291
P 341 291
P 341 291
P 342 291
P 342 292
P 342 292
P 343 292
P 343 292
P 344 292
P 345~346 292
P 346 293
P 346~347 293
P 347 294
P 347 294
P 347~348 294
P 348 295
P 348 295
P 348 295
P 348 296
P 348 296
P 348 296
P 349 296
P 349 297
P 350 297
P 350 297
P 350 297
P 350 297
P 350~351 297
P 351 298
P 351 298
P 351 298
P 352 298
P 353 298
P 354 299
P 354 299
P 354 299
P 354 299
P 354 299
第11章常见错误分析 299
P 355 299
P 355 300
P 355 300
P 356 300
P 356 300
P 356 300
P 357 300
P 357 301
P 357 301
P 357~358 301
P 358 301
P 358 302
P 360 302
P 361 302
P 363 302
P 363 302
P 363 302
P 363~364 303
P 365 303
P 366 303
P 366 304
P 367 304
P 367 304
P 368 304
P 368 305
P 369 305
P 370 306
P 372 306
P 373 306
P 377 306
P 377 306
P 378 306
P 378~379 306
P 379 307
P 379 307
P 380 307
P 380 307
P 380 307
P 380 308
P 380 308
P 380 308
P 380 308
P 380 308
P 381 308
P 380 308
P 380 309
P 381 309
P 381 309
P 381 309
P 381 309
P 381 310
P 381 310
P 382 310
P 382 310
P 383 310
P 383 310
P 384 311
P 384 311
P 384 311
P 384 311
P 385 311
P 386 311
P 386 312
P 386 312
P 386 312
P 386 312
P 386 312
P 387 312
P 387 312
P 387 313
P 387 313
P 387 313
P 388 313
P 389 314
P 389 314
P 389 314
P 389 314
P 389 314
P 389 315
P 389 315
P 389 315
P 389 315
参考文献 315
P 389 315
《C程序设计(第四版)学习辅导》部分 316
P14 316
P14~16 318
P17 318
P17 318
P18 319
P18 319
P18 319
P19 319
P19 319
P20~23 319
P25~26 320
P29 322
P30~31 322
P35 323
P37~38 324
P40 324
P42~43 325
p43~44 327
P47~48 329
P51~52 330
P52~53 331
P54 333
P55~56 336
P56 338
P57 340
p57~58 341
p61 343
p61 343
p61~62 343
P63 347
P65~66 348
P67 350
P68 350
P69 352
p71 353
P71~72 354
p72 355
p72~73 356
P74 357
P74~75 357
P75~76 359
P76 360
P76~77 360
P77 365
P77~78 365
P78~79 367
P79 368
P80 370
P81 371
P82 373
P82~84 374
P83 375
P84 376
P85 376
P87 377
P88~90 377
P91~92 380
P92 383
P93 384
P94 386
P94~96 386
P96 395
P96~97 395
P96~97 396
P99 398
P99~100 398
P106~107 399
P107 401
P108 402
P109~110 402
P112 404
P112 405
P117 405
P117 405
P117~120 406
P120~121 422
P121~122 422
P122 431
P122 431
P122~123 431
p123 432
p124 433
p124 433
p124~125 434
P125 435
P125 435
P125 435
126 436
126 436
126 436
126 436
126 437
127 437
128 第九章 437
128 438
128 439
129 439
129 439
129~130 440
130~131 441
P131 442
P 131~132 442
P 133~134 443
P 134~136 443
第11章 预处理命令 446
P177 446
P177 446
P177 446
P177 447
P178 447
P178 447
P178 447
P178 447
P179 447
P179 448
P179 448
P179 448
P179 448
P180 448
P181 448
P181 448
P181 449
P181 449
P181 449
P182 449
P182 449
P182 449
P182 450
P182 450
P183 450
P183 450
P183 450
P183 450
P183 451
P183 451
P183 451
P184 451
P184 451
P184 451
P184~185 452
P185 452
P185 452
P186 452
P186 454
P186 454
P187 454
P188 454
P188 455
P189 455
P192 455
P192 455
P192 455
P192 456
P192 456
第12章 位运算 456
P193 456
P193 456
P193 456
P193 457
P193 457
P193 457
P193 457
P193 457
P193 457
P194 457
P194 458
P194 458
P195 458
P195 458
P197 459
P197 459
P197 459
P198 459
P198 459
P198 460
P199 460
P199 460
P200 460
P201 461
P201 461
P202 461
P202 461
P202 462
P202 462
P203 462
P203 462
P203 462
P203 462
P203 463
P203 463
P203 463
P203 463
P203 463
《C程序设计(第三版)》部分 464
目录 464
(前言)pIX 464
(前言)pIX 464
(前言)pX 464
(前言) p VII 465
p VIII 465
p 2 465
p 2 466
p 2 466
p 3 466
p 3 466
p4~p5 467
P6 468
P6 468
P37 468
P38 468
P40 469
P40 469
P49 469
P50 469
P54 470
P56 470
P56 470
P56 471
P58 471
P63 471
P64 471
P64 473
P69~71 473
P72 474
P73 474
P73 474
P85 474
P91 475
P94 475
P95,96 475
p105 476
P113 476
P113 476
P120 476
P156 477
P156 477
P162~163 477
P163~164 477
P171 478
P179 478
P193 478
P194 479
P204 479
P219 479
P229 479
P245 479
P255 480
P257 480
P258 481
P265~266 481
P281~282 481
P310 482
P310 482
P310 482
P365 482
P377 483
题解 484
P38 484
P38 484
P40 485
P50~51 485
P50~52 486
P58~59 486
P70 487
P71~72 488
P73 489
P74 491
P76 491
P79~80 492
P91~92 494
P94 494
P134~135 495
P135~138 496
P146 496

前言
p12
① 数据类型介绍中,增加了C99扩充的双长整型(long long int)、复数浮点型(float complex,double complex ,long long complex)、布尔型(bool)等,使读者有所了解。
② C99要求,main函数的类型一律指定为int型,并在函数的末尾加一个返回语句“return 0;”。

评:long long complex,bool根本是子虚乌有的。数据类型都整不明白,还谈什么语言、算法呢?
C99并没有要求main函数的类型一律指定为int型
main函数的类型一律指定为int型其实是C89要求的

p16
“位运算”是C语言区别于其他高级语言的一个重要特点。

不顾常识,胡说八道

第一章 程序设计和C语言
1.2 什么是计算机语言
p1
计算机发展的初期,一般计算机的指令长度为16,

不晓得老谭把什么时代算做“计算机发展的初期”

p2
符号语言……LD代表“传送”等————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p2
LD一般表示赋值
p2
高级语言……用到的语句和指令是用英文单词表示的————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p2
高级语言这个层次没有指令这个概念

p3
C(系统描述语言)————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p3

这是把张飞当张良了

p3
C++(支持面向对象程序设计的大型语言)————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p3

C++是一种支持多重编程范式的通用程序设计语言

1.3 C语言的发展及其特点
p4
……增加了一些功能,尤其是C++中的一些功能,命名为ISO/IEC 9899:1999。

又在信口开河

p4
1983年,美国国家标准协会(ANSI)成立了一个委员会,根据C语言问世以来各种版本对C语言的发展和扩充,制定了第一个C语言标准草案(‘83 ANSI C)

曾经的子虚乌有的“87 ANSI C”不见了,很好。但这个‘83 ANSI C是哪来的呢?

p4
1990年,国际标准化组织ISO(International Standard Orgnization)接受C89……
评:ISO啥时候改名了?
我记得这个组织的正式名称是International Organization for Standardization
难怪书这么“水”
ISO都被山寨了

p5
C语言的运算符包含的范围很广泛,共有34种运算符(见附录C)。

评:这个恐怕要麻烦老谭自己去亲自数数了。34种究竟是怎么数出来的呢?再说附录C也不是关于运算符的啊

p5
C语言把括号、赋值和强制类型转换等都作为运算符处理,从而使C语言的运算类型极其丰富,表达式类型多样化。

评:怎么就“从而使”了呢?哪来的这种因果关系?
“运算类型极其丰富”应该是“运算种类极其丰富”
“表达式类型多样化”,不知所云

p5
C语言是完全模块化和结构化的语言。

评:什么叫“完全模块化”?什么叫完全结构化?这两个概念的定义是什么?
p5
例如,整型量与字符型数据以及逻辑型数据可以通用。

评:这是对初学者很严重的误导。另外“量”与“数据”并列,从小学生作文的角度也是说不过去的

p5
C语言……是成功的系统描述语言……

评:请问是描述什么“系统”?如何“描述”?知不知道什么叫“系统描述语言”

p5
而且C编译系统在新的系统上运行时,可以直接编译“标准链接库”中的大部分功能,不需要修改源代码,因为标准链接库是用可移植的C语言写的。

评:C编译系统在新的系统上运行:估计老谭在linux下成功地运行了他的VC6
可移植的C语言:说实话,没见过这种C语言
可以直接编译“标准链接库”中的大部分功能:看不懂这是说什么哪

1.4 最简单的C语言程序
p6
int main()

这是一种过时的写法,不符合C99的精神。C99只是为了兼容现有代码才允许这种写法
p6
而返回程序窗口

实际上恰恰是关闭程序窗口,返回IDE界面

p6
C99建议把main函数指定为int型(整型)

其实是C89标准的规定,老谭大概是为以前不规范的void main找台阶
p6
在main函数中,在执行的最后设置一个“return 0;”语句。当主函数正常结束时,得到的函数值为0,当执行main函数过程中出现异常或错误时,函数值为一个非0的整数。

评:写了“return 0;”居然还可能得到“一个非0的整数”?太神奇了吧

p7
文件后缀.h的意思是头文件(head file),因为这些文件都是放在程序各文件模块开头的。

评:head未必是以文件形式存在。
文件放在“文件模块开头”是莫名其妙的说法
p7
则表示从到本行结束是“注释”。

语文问题

p8
printf(“sum is %d\n”, sum);
……在执行printf函数时,将sum变量的值(以十进制整数表示)取代双撇号中的%d。

执行printf函数:怎么读怎么别扭,应该是调用printf函数
将sum变量的值(以十进制整数表示)取代双撇号中的%d:变量的值是取代不了%d的
p9
第2行输出“大数为8”。

输出的是“max=8”
p9
程序第4行是……

那是第5行

p9
执行scanf函数,从键盘读入两个整数,送到变量a和b的地址处,然后把这两个整数分别赋给变量a和变量b。

哪来的什么“然后”?

p10
%d由变量c的值取代之。

不是由“变量c的值”取代之

p10
例如可以把例1.2程序中的“int a,b,sum”放到main函数前面,这就是全局声明,在函数外面声明的变量称为全局变量

这是教唆,多数情况下是很糟糕的写法。
此外C没有全局变量这种说法,只有外部变量

p10
源积程序……

p11
①函数首部……包括函数名、函数类型、函数属性、函数参数(形式参数)名、参数类型。

“函数类型”:错误使用术语
“函数属性”:莫名其妙的说法

p11
函数体一般包括以下两部分。
 声明部分
 执行部分

作为一本号称“按照C99标准”的教科书来说,这种说法显然是错误的
p12
在每个数据声明和语句的最后必须有一个分号。分号是C语言的必要组成部分。

武断+无知
此外“数据声明”也很荒唐,C语言没有这样的概念

p10~12
“1.4.2 C语言程序的结构”这一小节下面的小标题是

1.4.2 C语言程序的结构
(1) 一个程序由一个或多个源程序文件组成。
(2) 函数是C语言的主要组成部分。
(3) 一个函数包括两个部分
(4) 程序总是从main函数开始执行的
(5) 程序中对计算机的操作是由函数中的C语句完成的。
(6) 在每个数据声明和语句的最后必须有一个分号。
(7) C语言本身不提供输入输出语句。
( 8 ) 程序应当包括注释。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p10~12
仔细回味一下,很有喜感。
即使当作小学生作文,恐怕也不合格吧
讲C语言的程序结构,怎么冒出来“C语言本身不提供输入输出语句”了呢?
程序结构和“程序总是从main函数开始执行的”又有什么关系呢
说老谭离题万里、云山雾罩不冤枉他吧

1.5 运行C程序的步骤与方法
p12
1.5 运行C程序的步骤与方法
这一小节讲的实际上是用C语言开发程序的过程与步骤(编辑、编译、链接及运行)
从内容上看,标题文不对题

这一小节的重大疏失是
只讲了编译和运行时发现错误应该返回修改源程序
但对链接时可能也会发生错误却只字未提
事实上链接错误也是编程最常见的错误之一
对这样常见的一大类错误怎么可以忽视呢

语言罗嗦和概念不清的毛病在这一小节同样存在
譬如

(1)上机输入和编辑源程序。通过键盘向计算机输入程序,……————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p12
什么叫“通过键盘向计算机输入程序”?难道这不属于“编辑”的范畴吗?
试问老谭,“输入“和”编辑”之间的区分到底应该如何界定?

p13
经过编译得到目标程序文件f.obj,再将所有目标模块输入计算机————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p13

“将所有目标模块输入计算机”:老谭能否演示一下怎么输入?
p14
集成环境(IDE)————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p13

直接无视D字的含义

1.6 程序设计的任务
p14
1.6 程序设计的任务————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p14
这是第一章最糟糕的一个小节
首先是文不对题
程序设计的任务只能是解决问题,不可能是别的什么
但这一小节的内容其实是对完整的程序设计过程和步骤的描述
如果仅仅如此
问题并不严重
可是书中对完整的程序设计过程和步骤的描述居然是

从确定问题到最后完成任务,一般经历以下几个工作阶段:
(1)问题分析。……
(2)设计算法。……
(3)编写程序。……
(4)对源程序进行编辑、编译和连接。……
(5)运行程序,分析结果。……
(6)编写文档。……
这种“谭式”工作流程几乎是置几十年来人类程序设计的经验和思考于不顾,全然自创的
不过说实话,我倒更希望老谭的这一小节能找本象样的书抄一抄,因为那样至少不会荒谬的如此离谱

首先,轻飘飘的一句“从确定问题到最后完成任务”
把确定问题当成了轻而易举的一件事
事实上除了判断一个正整数是否是素数这样的问题
“确定问题”的难度常常并不亚于解决问题

其次,老谭的“问题分析”居然是

对于接手的任务要进行认真的分析,研究所给定的条件,分析最后应该到达的目标,找出解决问题的规律,选择解题的方法。在此过程中可以忽律一些次要的因素,使问题抽象化,例如用数学式子表示问题的内在特性。这就是建立模型。
我的印象,这不过是中学数学解题方法与数据建模方法的一种杂交而已,不伦不类四不象。
“对于接手的任务要进行认真的分析,研究所给定的条件”:空对空的废话
“分析最后应该到达的目标”,目标都不明就编程?
“找出解决问题的规律,选择解题的方法”,耳熟,很有亲切感。后来想起来中学数学老师总这么说
“在此过程中可以忽律一些次要的因素”:从胆量方面来说不得不赞一个。复杂的东西咱都推还给客户好了
“使问题抽象化”:不知所云
“用数学式子表示问题的内在特性”,软件设计解决的问题有几个有数学式子?
“这就是建立模型”:不打自招

第三,“设计算法”中只字未提数据结构。老谭是强调“算法—程序的灵魂”的,可惜他一点不懂什么叫数据结构
在老谭的影响和带动下,经常看到一群整天把算法挂在嘴边但却连最基本的数据类型都搞不懂的家伙,根本就不能指望他们能懂得数据结构

第四,(3)的“编写程序”和(4)的“对源程序进行编辑”到底有什么分别

第五,(6)编写文档。程序开发完了,终于想起来“文档”了。任何一个软件工程专业的二年级本科生都能指出为什么这很荒谬。更荒谬的是,在老谭那里,“文档”居然不过是“程序说明书”、“帮助(help)”或“readme”。

习题
p15

这一章的习题也很糟糕

习题
6.编写一个C程序,输入a,b,c三个值,输出其中最大者。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p15

这道题不具备程序设计习题的最基本要求,是错误的题目。没有人可以做出这种题目。这反映了作者对软件开发基本上一窍不通

第二章 算法——程序的灵魂
p16
第二章 算法——程序的灵魂
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16

这一章总的来说就如同阑尾,而且是发炎的阑尾。
p16

首先映入眼帘让人感到眼前一亮的是
著名计算机科学家沃思(Nikiklause Wirth)……
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16

又来了。有人做过实验,google Nikiklause Wirth搜来的全是中文网页。不要问我为什么,只有智商约等于0的人才会问这样的问题。
由此可见,垃圾也是会疯狂地繁殖的。有人总喜欢沾沾自喜地拿“1100万”说事儿, 我倒想问一句,“1100万”个错误是个什么概念,“1100万”堆垃圾又会造成多大的污染。

p16
对于 Wirth 提出的
算法+数据结构=程序
老谭评论道:

直到今天,这个公式对于过程化程序来说依然是适用的。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16
没有任何理由和依据,而敢于指责大师的公式具有局限性,不能不承认谭大师很有“勇气”。
接着,老谭提出

实际上,一个过程化的程序除了以上两个主要因素之外,还应当采用结构化程序设计方法进行程序设计,并且用某一种计算机语言表示。因此,算法、数据结构、程序设计方法和语言工具4个方面是一个程序设计人员所应具备的知识,在设计一个程序时要综合运用这几方面的知识。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16
感慨:大师总是一针见骨地道出事物的本质;“伪大师”总是点金成铁狗尾续貂并且把水搅混。不妨再续狗尾:小学数学也是“程序设计人员所应具备的知识”。
2.1 什么是算法
p16~17
2.1 什么是算法————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p16

本小节前半部分介绍什么是算法,后半部分讨论了数值算法与非数值算法。主要毛病在后半部分。
后半部分实际上毫无必要;数值运算算法和非数值运算算法属于作者闭门造车自创的不规范用语(估计是不怎么读文献所导致);
对各种数值运算都有比较成熟的算法可供使用————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p17
乃极其武断的信口开河,表明作者对数值计算领域惊人的无知。

2.2 简单的算法举例
p17
2.2 简单的算法举例
这一小节读起来犹如嚼棉花的感觉,只好快些扫过。
p18
例 2.1
…由于计算机是高速运算的自动机器,实现循环是轻而易举的……
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p18

脑残是怎样炼成的,这种似是而非的“由于”句型功不可没。“循环”居然是由于“高速”,咄咄怪事。

p19~20
例 2.2
…从图2.1可以看出:“其他”这一部分,包括不能被4整除的年份,以及能被4整除,又能被100整除,但不能被400整除的那些年份(如1900年),它们都是非闰年。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p19

敢问大师有没有亲自看过图2.1中“其他”这部分写的究竟是什么?要是看过的话,我们可就知道什么叫睁着眼睛胡说了
p20
例 2.4
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p20
其中描述的算法必然导致拖泥带水的代码。
p21
例 2.5……
所谓素数(prime),是指除了1和该数本身之外,不能被其他任何整数整除的数
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p21

厉害!把素数的定义都给改了,数学家们该感到汗颜了吧。
另外作者连“整数”和“正整数”的区别都搞不清楚
p21
判断一个数n(n≥3)是否为素数……
实际上,n不必被2n-1的整数除,只须被2n/2间的整数除即可,甚至只须被2~n之间的整数除既可。例如,判断13是否为素数,只须将13被2,3除即可,如都除不尽,n必为素数。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p21

2~n/2 , 2~n 这两个区间到底哪个大?怎么后者竟然成了“甚至” 了呢
2.3 算法的特性
p21
2.3 算法的特性
……
(2)确定性。算法中的每一个步骤都应当是确定的,而不不应当是含糊的、模棱两可的。…… 也就是说,算法的含义应当是唯一的,而不应当产生“歧义性”。所谓“歧义性”,是指可以被理解为两种(或多种)的可能含义。 ————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p21
用老谭自己的矛攻一下他自己的盾
前一章的一道习题

6.编写一个C程序,输入a,b,c三个值,输出其中最大者。
这个题目本身是否“含糊”,是否具有“歧义性”?
问题本身就“模棱两可”,又何谈算法的“确定性”呢?

2.4 怎样表示一个算法
p22
2.4 怎样表示一个算法
流程图是用一些图框来表示各种操作。用图形表示算法,直观形象,易于理解。美国国家标准化委员会 ANSI(American National Standard Institute)规定了一些常用的流程图符号(见图2.3),已为世界各国程序工作者普遍采用。 ————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p22
首先ANSI是 American National Standards Institute.
其次,老谭把ANSI搬出来是无知的卖弄。理由是:
这个标准有ISO版本,ANSI采纳了这个ISO标准。而且对应于ISO标准,我国有自己的GB。这些标准的内容是一致的。老谭不谈GB,不谈ISO,却舍近求远地扯什么ANSI标准,又有什么特殊的理由呢?只能说他自己并不熟悉这些标准及其相互的关系。
而且书中的流程图根本不符合无论是 ISO标准还是GB还是ANSI标准,流线乱窜,甚至全书中压根就没有标准用来表示循环的基本图形。
既然连这些标准读都没读过,把ANSI搬出来不是唬人又能是什么呢?

p26
2.4.3 三种基本结构和改进的流程图
1.传统流程图的弊端————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p26
成功地把滥用goto的危害移花接木地指误为传统流程图的弊端。

……人们规定出几种基本结构,然后由这些基本结构按一定规律组成一个算法结构(如同用一些基本预制件来搭成房屋一样),如果能做到这一点,算法的质量就能得到保证和提高。
说的象真的似的。C语言再好也没耽误老谭写出那么多垃圾代码呀。由“基本结构”“按一定规律”就一定能保证算法的质量?!

p26
2.三种基本结构
1966年,Bohra和Jacopini提出了以下3种基本结构,用这3种基本结构作为表示一个良好算法的基本单元。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p26
首先把Böhm的名字错误地写成了Bohra。
其次更严重的是Böhm和Jacopini根本就没有提出什么3种基本结构,更没有提出“用这3种基本结构作为表示一个良好算法的基本单元”。3种基本结构在之前的高级语言中普遍存在。
Böhm和Jacopini仅仅不很严格地证实了向前的goto语句可以用其他语句实现,他们的论文发表的时候,Dijkstra反对goto语句的论文(1968)还没发表呢

p28
2.4.4用N-S流程图表示算法————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p28
N-S图废除了流线,哪里还是什么“流程图”?
事实上通用的说法是Nassi–Shneiderman diagram (NSD)
老谭的一个毛病就是喜欢发明一些狗屁不通的新概念
比如二级指针,行指针,数值运算算法
这些都是他自己根本没学懂的表现

p29
例2.15 ……
图2.31————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p29

图2.31的算法其蠢无比
明明一次循环就可以解决非要用两次循环
明明一个变量就可以解决非要用一个数组
而且那个50基本没有用处
因为可以轻易写出人数为一正整数时的算法

三鹿往奶粉里掺加大量三聚氰胺使婴儿身体受到摧残而被判刑
老谭往教科书里掺加的无知、错误和愚蠢使初学者大脑变残为什么不被判刑?
我看刑法应该参照食品卫生法考虑一下这个问题
在教科书中传播愚蠢败坏民族的智力难道不是犯罪?

p31
2.4.5 用伪代码表示算法————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p31
两道例题都是用for循环可以很漂亮完成
却莫名其妙的选择了用罗嗦的while循环描述
误导初学者

p32
2.4.6 用计算机语言表示算法
……用计算机语言表示的算法是计算机能够执行的算法。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p32
胡扯,计算机能够执行只有机器语言

p33
例2.19……
double deno=2.0,sum,term;
while(deno<=100)
{
……
deno=deno+1;
}————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p33
明显的错误,缺乏最基本的编程素质

2.5 结构化程序设计方法
p34
2.5 结构化程序设计方法
……关键是算法,有了正确的算法,用任何语言进行编码都不是什么困难的事情。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p34
这种对算法的片面强调是对初学者绝对的误导。误导的结果是,许多初学者往往只会用最拙劣数据结构绞尽脑汁地苦思复杂无比的“算法”。
此外,有经验的人都知道,即使懂得了算法,学习用另一种语言编程往往也不是那么容易的事,比如C++。

第三章最简单的C程序设计——顺序程序设计
P37

对第3章的点评

第3章 最简单的C程序设计——顺序程序设计
开场白讲了两件事:作者对如何学习编程的看法,介绍该书的做法。与该章的主题没什么关系。前者在第二章已经说过,后者应该是前言或第一章的内容,写在这里有没话找话自吹自擂之嫌。
作者对如何学习编程的看法基本是片面、偏颇及错误的,是对Wirth“算法+数据结构=程序”的曲解和错误发挥。这对学习者是一种根本上的误导。

3.1 顺序程序设计举例
两个例子从问题的提法到最后的代码都糟糕透顶、漏洞百出。这两个例题给学习者带来的负面影响有很多。从这两个例题中学习者不可能领悟到究竟什么是顺序程序设计,更不可能领悟到究竟什么是程序设计。他们很可能误以为编程就是把代数式写成表达式 + 进行函数调用。
更严重的是,谭书居然告诉读者“警告”可以作为正常来接受,这是教唆式的误导。
关于例3.1
例3.1 有人用温度计测量出用华氏法表示的温度(如69°F),今要求把它转换为以摄氏法表示的温度(如20°C)。

这个题目看似描述的平易近人通俗易懂。但我想小学语文老师会评价说,温度就是温度,和“有人”或没人、用或不“用温度计测量”有什么关系?“有人用温度计测量出”完全是废话。而小学数学老师会说,谭同学,69°F根本不是20°C。
这就是老谭。有人说他的书通俗易懂,其实多半只是似是而非且毫无内容的废话甚至病句而已。如果阅读不仔细很容易被他的似是而非唬过去,但只要稍微一推敲,立刻就能把他戳出几个洞。赞赏他的书的人,通常小学数学和小学语文都不咋样
此外,在题解中有这样一些毛病:
把算法说成是找公式
流程图写输入,但代码中是赋值运算
代码使用不合适的数据类型
容易出错的代码风格
……

3.2 数据的表现形式及其运算
这部分总的来说就是概念不清,逻辑混乱。
3.2.1 常量和变量
1.常量
“常用的常量有以下几类:(1)整型常量……(2)实型常量……(3)字符常量……(4)字符串常量……(5)符号常量”
2.变量
3.常变量
4.标识符
从这个标题结构很容易发现其荒谬,总体来说就是不伦不类:
把标识符和常量、变量并列;把“常变量”和“变量”并列;
对常量的分类同样是不伦不类
先介绍“符号常量”后介绍“标识符”是本末倒置

此外还有如下错误
3.2.1 之 1.常量 部分
“整型常量。如1000,12345,0,-345等都是整型常量。”,这里把“-345”这个常量表达式说成了常量
刚说完“十进制小数形式,由数字和小数点组成”,立刻给出了“-56.79”这个例子,明显自相矛盾。试问,“-”是数字还是小数点?
对字符常量和符号常量的讲解均存在错误。

3.2.1 之 2.变量 部分
对编程最重要的概念之一——变量的介绍存在惊人的致命错误(“变量名实际上是以一个名字代表的一个存储地址”)
“C99允许在函数中的复合语句(用一句花括号括起来)中定义变量。”属于用C89的知识冒充C99

3.2.1 之 3.常变量 部分
把const 变量想当然地说成是C99的,实际上C89就有const 这个关键字

3.2.1 之 4.标识符 部分
介绍的是过时(对于C99来说)的说法
P37
第3章 最简单的C程序设计——顺序程序设计
……
为了能编写出C语言程序,必须具备以下的知识和能力:
(1) 要有正确的解题思路,即学会设计算法,否则无从下手。
(2) 掌握C语言的语法,知道怎样使用C语言所提供的功能编写出一个完整的、正确的程序。也就是在设计好算法之后,能用C语言正确表示此算法。
(3) 在写算法和编写程序时,要采用结构化程序设计方法,编写出结构化的程序。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p37
这段总的、指导性的叙述,由于并不全面,具有本质性的缺失,因而是对初学者不折不扣的误导。
事实上仅有这些是不够的,当初学者仅仅注意这几点的时候是绝对不可能学好程序设计的。
众所周知,程序由数据、运算、控制和传输这四个基本成分。谭的叙述中完全看不到“数据”这个最基本成分的影子,这就造成了这样一种现象,许多学习谭书的人往往津津乐道地把“算法才是王道”挂在嘴边,但却对数据缺乏起码的必要了解。谭书本身最致命的一个根本缺陷就是对数据缺乏重视,不甚了了甚至存在大量的根本性的错误。
此外不难发现,谭的这段叙述和Wirth所说的“算法+数据结构=程序”在精神上是背道而驰的。

3.1 顺序程序设计举例
P37
3.1 顺序程序设计举例
例3.1 有人用温度计测量出用华氏法表示的温度(如69°F),今要求把它转换为以摄氏法表示的温度(如20°C)。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p37
这个题目看似描述的平易近人通俗易懂。但我想小学语文老师会评价说,温度就是温度,和“有人”或没人、用或不“用温度计测量”有什么关系?“有人用温度计测量出”完全是废话。而小学数学老师会说,谭同学,69°F根本不是20°C。
这就是老谭。有人说他的书通俗易懂,其实多半只是似是而非且毫无内容的废话甚至病句而已。如果阅读不仔细很容易被他的似是而非唬过去,但只要稍微一推敲,立刻就能把他戳出几个洞。赞赏他的书的人,通常小学数学和小学语文都不咋样。
P37~38
例3.1的讲解存在这样几个问题
1.

解题思路:这个问题的算法很简单,关键在于找到二者间的转换公式……
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p37~38
找个简单的公式也是算法?
2.

图3.1(N-S图)中
输入f的值
但代码中却是

f = 64.0;
这是“输入”吗
3.
代码中出现了从来没介绍过的关键字float
4.
代码中计算摄氏温度的语句是

c=(5.0/9)*(f-32);
这个风格怪异不说
初学者不可能明白为什么原来公式中的5写成了5.,而原公式中的9和32却没变
5.
老谭最后说

读者应能看懂这个简单的程序。
我看读者根本看不懂
P38

例 3.2 计算存款利息。有1000元,想存一年。有3种方法可选:(1)活期,年利率为r1;(2)一年定期,年利率为r2;(3)村两次半年定期,年利率为r3。请分别计算出一年后按三种方法所得到的本息和。

这也叫例题?谁知道你r1,r2,r3是神马东西啊
谁能做出来?我出1000元悬赏!
在公司里搞出这样的需求分析老板不抽他才怪

1000明明是问题中的常量
却非要设个变量(float p0=1000,)
难道不是脱裤子放屁吗?
既丑陋无比又容易弄出差错
这难道不是教小朋友们学坏吗
P38

例 3.2————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p38
再次出现了“N-S流程图”这个词
在书中一会儿“N-S流程图”,一会儿“N-S图”
明显的缺乏概念的统一
是形式逻辑中所说的“概念不清”的一种
N-S图中写的是“输入p0,r1,r2,r3的值”
代码中却是赋初值“float p0=1000,r1=0.0036,r2=0.0225,r3=0.0198,p1,p2,p3;”
P38

更可笑的是

运行结果:
p1=1003.599976
p2=1022.500000
p3=1019.898010
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p38

竟然给出了小数点后面的第6位
银行会按这个给你付钱吗?
不懂银行的业务写出这种代码只能是一个笑柄
更惊人的是
1000元按照年利率0.0036计
小学生用心算都能脱口而出本息是1003.6元
而你这个程序给出的结果竟然是1003.599976
这不是糟蹋C语言是什么
所以读老谭的书会变傻
这是确凿无疑的
P38

程序分析:第4行,在定义实型变量p0,p1,p2,p3,r1,r2,r3的同时,对变量p0,r1,r2,r3赋予初值。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p38
啥叫“实型变量”啊?
再说,在N-S图里不是“输入p0,r1,r2,r3的值”吗?
怎么到这里又成了“赋予初值”了呢?
P38

第8行,在输出p1,p2,p3的值之后,用\n使输出换行。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p38
这段文字出现在第38页
到这里为止,代码中的\n至少出现过20次左右
老谭已经讲过十几次\n是换行了
车轱辘话来回说是老谭的主要作文技巧
P37~39
#include <stdio.h>
int main()
{
float f,c;
f=64.0;
c=(5.0/9)*(f-32);
printf(“f=%f\nc=%f\n”,f,c);
return 0;
}
在使用Visual C++6.0编译系统时,……,会显示出“警告”信息:“在初始化时把一个双精度常量赋给一个float型变量”。

评:这条警告信息是捏造的,在这段代码中根本不存在“初始化”
P39

注意:……这是因为有的C编译系统(如Visual C++)把所有的实数都作为双精度处理。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39
毫无根据的臆断,信口开河
P39
对这类“警告”,用户知道是怎么回事就可以了。承认此现实,让程序继续进行连接和运行,不影响运行结果。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39

为自己的垃圾代码辩解
教唆初学者放过警告
严重地误导初学者
3.2数据的表现形式及其运算

P39
3.2 数据的表现形式及其运算
……
3.2.1 常量和变量
……
(1)整型常量。如1000,12345,0,-345等都是整型常量。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39

首先“整型”这个词含义是不明确的,在谭书中一直有n种并存的含义
其次,-345不是常量,而是常量表达式。如果可以把-345说成常量,那么3-4也可以说成是常量。岂不荒谬

P39
(2)实型常量。有两种表示形式:————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39
老谭既然号称“按照C语言的新标准C99进行介绍”,怎么会写出实型常量有两种表示形式这样的胡话?
这只能说明老谭对C99缺乏最基本的了解
还是老作风:信口开河
P39

(3)字符常量。有两种形式的字符常量:
①普通字符,用单撇号括起来的一个字符,如:‘a’,‘Z’,‘3’,‘?’,‘#’。不能写成’ab’或’12’……字符常量只能是一个字符,……。————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p39
这一页上的第6个硬伤。

P37

评:开场白讲了两件事:作者对如何学习编程的看法,介绍该书的做法。与该章的主题没什么关系。前者在第二章已经说过,后者应该是前言或第一章的内容,写在这里有没话找话自吹自擂之嫌。
作者对如何学习编程的看法基本是片面、偏颇及错误的,是对Wirth“算法+数据结构=程序”的曲解和错误发挥。这对学习者是一种根本上的误导。

评:两个例子从问题的提法到最后的代码都糟糕透顶、漏洞百出。这两个例题给学习者带来的负面影响有很多。从这两个例题中学习者不可能领悟到究竟什么是顺序程序设计,更不可能领悟到究竟什么是程序设计。他们很可能误以为编程就是把代数式写成表达式 + 进行函数调用。
更严重的是,谭书居然告诉读者“警告”可以作为正常来接受,这是教唆式的误导。

例3.1 有人用温度计测量出用华氏法表示的温度(如69°F),今要求把它转换为以摄氏法表示的温度(如20°C)。
评:这个题目看似描述的平易近人通俗易懂。但我想小学语文老师会评价说,温度就是温度,和“有人”或没人、用或不“用温度计测量”有什么关系?“有人用温度计测量出”完全是废话。而小学数学老师会说,谭同学,69°F根本不是20°C。
这就是老谭。有人说他的书通俗易懂,其实多半只是似是而非且毫无内容的废话甚至病句而已。如果阅读不仔细很容易被他的似是而非唬过去,但只要稍微一推敲,立刻就能把他戳出几个洞。赞赏他的书的人,通常小学数学和小学语文都不咋样
此外,在题解中有这样一些毛病:
把算法说成是找公式
流程图写输入,但代码中是赋值运算
代码使用不合适的数据类型
容易出错的代码风格
……

3.2 数据的表现形式及其运算
评:这部分总的来说就是概念不清,逻辑混乱

3.2.1 常量和变量
1.常量
“常用的常量有以下几类:(1)整型常量……(2)实型常量……(3)字符常量……(4)字符串常量……(5)符号常量”
2.变量
3.常变量
4.标识符
评:从这个标题结构很容易发现其荒谬,总体来说就是不伦不类:
把标识符和常量、变量并列;把“常变量”和“变量”并列;
对常量的分类同样是不伦不类
先介绍“符号常量”后介绍“标识符”是本末倒置

此外还有如下错误
3.2.1 之 1.常量 部分
“整型常量。如1000,12345,0,-345等都是整型常量。”,这里把“-345”这个常量表达式说成了常量
刚说完“十进制小数形式,由数字和小数点组成”,立刻给出了“-56.79”这个例子,明显自相矛盾。试问,“-”是数字还是小数点?
对字符常量和符号常量的讲解均存在错误。

3.2.1 之 2.变量 部分
对编程最重要的概念之一——变量的介绍存在惊人的致命错误(“变量名实际上是以一个名字代表的一个存储地址”)
“C99允许在函数中的复合语句(用一句花括号括起来)中定义变量。”属于用C89的知识冒充C99

3.2.1 之 3.常变量 部分
把const 变量想当然地说成是C99的,实际上C89就有const 这个关键字

3.2.1 之 4.标识符 部分
介绍的是过时(对于C99来说)的说法
P39~41
评:这一小节讲的是“常量”
除了前面提到的错误、概念或知识缺失、含糊不清、误导等
回过头来看还会因为分类错乱而感觉很滑稽
(1)整型常量
(2)实型常量
(3)字符常量
(4)字符串常量
(5)符号常量
这完全是一脚天上一脚地下,关公战秦琼式的写法
p40
③ 转义字符……'\t’代表将输出的位置跳到下一个tab位置(制表位置),一个tab位置为8列
评:tab位置是实现定义的
表3.1 转义字符及其作用


转义字符 │ 字符值 | 输出结果


\'           |     一个单撇号(')   |      具有此八进制码的字符   

\"          |     一个双撇号(“)   |     输出此字符   

……

评:对“’ ”输出结果的描述不知所云

p40
字符串常量是双撇号中的全部字符(但不包括双撇号本身)
评:这个表述很似是而非
没有讲清String literal的本质
如果是讲述String literal中的字符,那么忽视了还有一个’\0’

单撇号内只能有包含一个字符, 双撇号内可以包含一个字符串。

前半句是错的,后半句看不懂究竟想说什么。
p41
(5)符号常量。……
#define PI 3.1416
经过以上指定后,本文件中此行开始的所有的PI都代表3.1416。在对程序进行编译前,预处理器先对PI进行处理,把所有的PI全部置换为3.1416。
评:成问题的地方在“所有的”那三个字,这给人以严重的误导。至少我见过读了这句话之后试图替换“……PI ……” 或v_PI中的PI的学习者。
p41
2.变量
评:这一小节错得很不靠谱
已经突破了信口开河的境界达到胡说八道的高度了
例如
C99允许在函数中的复合语句(用一句花括号括起来)中定义变量。
C99允许使用常变量,如:
const int 3=3;

我注意到本书到此为止关于C99的论述至少有九成都是错误的
而且这些错误并没涉及到什么复杂的概念,只涉及一些简单的基本常识
在这些常识性的地方居然大错特错
那么就有理由怀疑老谭究竟看过C99没有(哪怕是粗略地看过)
根本没看过C99就在书里自称“按照C语言的新标准C99进行介绍”
这不是学识问题
是品质问题
p41
请注意变量名和变量值这两个不同的概念,……
评:实际上这两个概念有时是同一的,是区分不开的。
而且老谭根本也没有讲清楚这两个概念的区别究竟何在
p41
变量名实际上是以一个名字代表的一个存储地址。
评:这是简直是胡扯!
假如有
int i;
如果 i 代表地址,那么 &i 又是什么呢?

p41
C99允许使用常变量

评:且不谈“常变量”这个翻译有多么拙劣
const关键字早在C89中就有了
关C99什么事
P42
常量是没有名字的不变量。
评:常量就一定没有名字吗?显然不是
P42
定义符号常量是用#define指令,它是预编译指令,它只是用符号常量代表一个字符串,……
评:“字符串”这个词在C语言中是有特定含义的,这里使用这个词明显不当
P42
C语言规定标识符只能由字母、数字和下划线3种字符组成,
评:不是号称“按照C99介绍”的吗?
C99是这样规定的吗?!
麻烦老谭自己亲自去看看
不要把几十年前的规定拿来冒充C99
P42
3.2.2 数据类型
评:数据类型是C语言最基础最根本性的东西。对数据类型的理解程度基本上决定了一个人C语言的水平。同样一本书对数据类型的介绍是否准确全面也在很大程度上决定了书的优劣。下面来看看谭书对数据类型的介绍。
P42
所谓类型,就是数据分配存储单元的安排,包括存储单元的长度(占多少字节)以及数据的存储形式。不同的类型分配不同的长度和存储形式。
评:除了最后一句是病句以外,这段话最大的毛病在于并不全面,仅仅把数据类型理解为数据在存储单元中的表示形式大约只理解了数据类型全部含义的45%。纵观全书,这个毛病始终存在。这一点我认为是谭书一个致命的硬伤。由于这个致命的硬伤,所以这本书的错误并非象有些天真的人们以为的那样可以通过修修补补改好——因为根本性的基调就是不准确和错误的。

该书把数据类型分为“基本类型”、“枚举类型”、“空类型”和“派生类型”。(表 3.4)
这也是要命的,反映了作者对数据类型的一知半解并且根本不懂得数据类型的重要性。
首先“枚举类型”与“基本类型”、“派生类型”并列是不伦不类的半调子分类。回顾本书的前几版,这个“枚举类型”的位置始终很尴尬,有时根本没有,有时与“整型”、“字符型”、“实型”并列。作者一直把“枚举类型”当作临时工一样今天放这,明天放那,后天还可能给解雇了。这些说明作者这几十年一直没能把数据类型搞清。

表 3.4中的另外几个错误是
缺乏不完全数据类型,只写了一个void类型
*布尔型(bool) ,这个写错了,再次表明老谭根本没读过C99,但他却宣称“按照C99进行介绍”,品质问题。
浮点类型中缺少long double。
复数浮点型(float_complex,double_complex,long long_complex):这个非但不全,而且全错。

仅仅这一张表上就有如此多的严重错误,这本书绝对是垃圾到家了

P42
3.2.2 数据类型
评:“为什么在用计算机运算时,要指定数据的类型呢?”这个设问的可笑之处在于后面根本没有回答,而是驴唇不对马嘴地瞎扯了一通。

“所谓类型,就是数据分配存储单元的安排,包括存储单元的长度(占多少字节)以及数据的存储形式。不同的类型分配不同的长度和存储形式。”
表明作者根本不懂得数据类型的含义。

把数据类型分为“基本类型”、“枚举类型”、“空类型”和“派生类型”(表 3.4)。很荒唐
更荒唐的是一口气写错了4个描述类型的关键字

3.2.3 整型数据
评:概念不清,逻辑混乱。(比如,整型变量的符号属性)纠结于具体编译器下各数据类型的空间(范围)和所谓的“补码”,并没有真正讲清楚数据类型本质性的东西。
一些原则性的错误:
在将一个变量定义为无符号整型后,不应向它赋予一个负值,否则就会得到错误的结果。
变量值在存储单元中都是以补码形式存储的

3.2.4 字符型数据
评:“因此C99把字符型数据作为整数类型的一种”,信口开河。
p43
例如,Visual C++ 6.0为char型(字符型)数据分配1个字节,
评:居然是“例如”!
敢问老谭你能否找到一个“例外”?
p44
3.2.3 整型数据
1.整型数据的分类
本节介绍最基本的整型类型。
(1)基本整型(int型)
编译系统分配给int型数据2个字节或4个字节(由具体的C编译系统自行决定)。如Turbo C2.0为每一个整型数据分配2个字节(16个二进位),而Visual C++为每一个整型数据分配4个字节(32位)。在存储单元中的存储方式是:用整数的补码(complement)形式存放。
评:这段中“整型”的含义是前后不一的,这是最基本的逻辑错误:概念不清,其后果必然是逻辑错乱
“编译系统分配给int型数据2个字节或4个字节”:字面意思是int类型只可能有两种可能,这也是错误的。
“在存储单元中的存储方式是:用整数的补码(complement)形式存放”:这是一相情愿的想当然
p44
在存放整数的存储单元中,最左面一位是用用来表示符号的,如果该位为0,表示数值为正;如果该位为1,表示为负。
评:1. unsigned 算不算整数? 哪位表示符号?
2. 什么叫“最左面一位”,拜托,那叫“高位”
3. 0000 0000 “表示数值为正”吗?乱
p44
1.整数类型的分类
(1)基本整型(int型)
(2)短整型(short int)
(3)长整型(long int)
(4)双长整型(long long int)
评:最多是对Turbo C2.0和Visual C++6.0的情况进行反复举例而已,并且错误地把补码、一个字节8位这一特殊情况作为了唯一情况
p45
编译系统分配给long数据4个字节

评:又是想当然
p45
2.整型变量的符号属性
评:这是个不通的标题。暂且不说“符号属性”这个概念是否恰当,即使真有“符号属性”这个概念,那么它也是数据类型的性质,而不是“变量”的性质
p45
以上介绍的几种数据类型,变量值在存储单元中都是以补码形式存储的
评:胡扯!
p46
因此无符号整型变量中可以存放的正数的范围比一般整型变量中正数的范围扩大一倍。
评:抛开语文等方面的问题不谈
就以两个字节的补码表示形式的int以及unsigned来说
int:1~32767
unsigned:1~65535
这难道是扩大一倍吗?
老谭显然掰不开正数和非负数这两个概念的区别
p46
(2)对无符号整型数据用%u输出。%u表示用无符号十进制数的格式输出。如:
unsigned short price = 50 ;
printf(“%u\n”,price);
评:不否认能输出正确的结果
但是“对无符号整型数据用%u输出”显然是荒谬的
这里的“整型”是个集合概念还是个体概念?
如果是集合概念,陈述显然是错误的
如果是个体概念(unsigned int)怎么可以用short举例呢
老谭显然根本不清楚printf(“%u\n”,price)函数调用中的price表达式的真实类型
输出正确结果其实是张冠李戴之后瞎蒙碰出来的结果

“无符号十进制数”也属于莫名其妙的说法
老谭好像是在自言自语地说着自己才仿佛懂得的话
p47
在将一个变量定义为无符号整型后,不应向它赋予一个负值,否则就会得到错误的结果。如:
unsigned short price = - 1 ;//不能把一个负整数存储在无符号变量中
printf(“%d\n”,price);
得到的结果为65535.显然于原意不符。
思考:这是为什么?
原因是:系统对-1先转换成补码形式。就是全部二进制位都是1(见图3.8),然后把它存入变量price中。由于price是无符号短整型变量,其左面第一位不代表符号,按%d格式输出,就是65536.

price
1111111111111111
图 3.8
评:这段短短的文字至少有四个错误
找出3个C语言算入门了

p47
3.2.4 字符型数据
由于字符是按其代码(整数)形式存储的,因此C99把字符型数据作为整数类型的一种。但是,字符型数据在使用上有自己的特点,因此把它单独列为一节介绍。
评:1.代码、编码,两个概念。老谭这个“代码”大概是翻译软件翻译的吧
2.字符类型作为整数类型的一种,C89就已然如此,和C99有什么关系。老谭这是在不懂装懂地蒙人
3.每种数据类型都有自己的特点。在这点上,字符型数据没什么特殊的
p47
字符与字符代码并不是任意写一个字符,程序都能识别的。
评:这是人话吗
主语是什么
谓语是什么
宾语又是什么
程序识别什么
p47
例如圆周率π在程序中是不能识别的,只能使用系统的字符集中的字符集,目前大多数系统采用ASCII字符集。
评:谁说π就是圆周率的?
什么叫“识别”
什么“系统”
“字符集”有几套?
“大多数系统采用ASCII字符集”吗?
前面有网友说老谭无论对C89还是C99都还没入门
确实一针见血
p47
在将一个变量定义为无符号整型后,不应向它赋予一个负值,否则就会得到错误的结果。如:
unsigned short price = - 1 ;//不能把一个负整数存储在无符号变量中
printf(“%d\n”,price);
得到的结果为65535.显然于原意不符。
思考:这是为什么?
原因是:系统对-1先转换成补码形式。就是全部二进制位都是1(见图3.8),然后把它存入变量price中。由于price是无符号短整型变量,其左面第一位不代表符号,按%d格式输出,就是65536.

price
1111111111111111
图 3.8
评:向无符号整数类型变量赋值,C语言是有定义的。不是什么错误的结果。不能因为自己不理解C语言的真正语义就说那是错误的
printf(“%d\n”,price);得到的结果为65535 。不是什么与“原意不符”,而是作者根本不懂得那段代码的真正含义(尤其是其中存在的类型转换)
C语言没说一定采用补码
那个price其实并不是unsigned short,而是一个int类型
……
此外很多不严谨的地方就不多说了

p48
字符是以整数形式(字符的ASCII代码)存放在内存单元中的。
评:C语言从来没说过只处理ASCII码
p48
所以在C中,指定用1个字节(8位)存储一个字符(所有系统都不例外)
评:据我所知,有的系统的1字节是9位
老谭你怎么解释这个“所有系统都不例外”
p48
2. 字符变量
字符变量是用类型符char定义字符变量。
评:这是人话么?

p48
字符变量是用类型符char定义字符变量

这是话么?
这就是简介说的“文字流畅”?
当然
与以前的“字符型变量用来存放字符常量”还是天壤之别
以前的是错话
现在的是废话

(1)+、-、、/运算的两个数中有一个数为float或double型,结果是double型,因为系统将所有的float型数据都先转换为double型,然后进行运算。
评:一句不长的话错了两次。
1.“+、-、
、/运算的两个数中有一个数为float或double型,结果是double型”
老谭既然在前面吹嘘这本书是“按照C语言的新标准C99进行介绍”
难道不知道C语言有复数类型吗?
如果“+、-、*、/运算的两个数中有一个数为float或double型”,另一个是复数类型,“结果是double型”吗?

就算你根本不懂得C99
虚张声势地出来蒙人
long double 类型可是几十年前就有了
如果“+、-、*、/运算的两个数中有一个数为float或double型”,另一个是long double,“结果是double型”吗?

2.“系统将所有的float型数据都先转换为double型,然后进行运算。”,简直是胡扯

(2)如果int型与float或double型数据进行运算,先把int型和float型数据转换为double型,然后进行运算。结果是double型
评:第一次读完这个(2),我愣住了,简直不敢相信
再仔细看一遍,发现这个(2)真的是很(2)
因为什么呢
因为(1)已经说过“+、-、*、/运算的两个数中有一个数为float或double型”
现在(2)居然又开始大谈特谈“如果int型与float或double型数据进行运算”
车轱辘话来回说是老谭的一个特点
但刚说完就repeat一遍还是罕见的
中等程度的神经错乱都写不出来这种东西

而且,char 、long等与float或double型数据进行运算的情况,阙如

这段中的严重错误就不谈了
与错乱相比,错误简直不算什么了不起的事情

(3)字符(char)型数据与整型数据进行运算,就是把字符的ASCII代码与整型数据进行运算。如:12 + ‘A’,由于……
评:前面说的是“字符(char)型数据与整型数据进行运算”
立刻就拿出个两个 int 类型数据的加法来举例
很明显
老谭根本不知道 ‘A’ 的类型究竟是什么

评:暂且不谈C语言的执行环境未必使用ASCII码的问题)
这里的“整型数据”是个莫名其妙的概念

在该书的43页写到
| 基本整型 (int)
| 短整型 (short int)
| 长整型 (long int)
整型类型 | *双长整型 (long long int)
| 字符型 (char)
| *布尔型 (bool) [注:这里的bool也是错误的]

所谓“字符(char)型数据与整型数据进行运算”
说的到底是哪种"整型类型"呢?
况且这根本就是一个不必要的条件限制
难道 char类型数据 与 double类型数据 做加法运算时那个char类型数据不是字符编码吗?
它不是字符的编码还能是什么别的东西吗?

字符数据可以直接与整型数据进行运算。
评:不知所云。
什么叫“直接”进行运算?
是否还有“间接”进行运算?

⑤将10+‘a’+i*f的结果与d/3的商2.5相减,……
评:1. “实型数据”,应为(实)浮点型数据。因为在C语言中real types是整数类型与(实)浮点类型的合称。
2.“字符的ASCII” ,运行环境中的字符不一定是用ASCII码表示的。况且,即使运行环境也使用ASCII码,字符数据中存储的也不一定是ASCII码。比如,字符数据可能是一个负值
3.“转换为double型数据” ,完全无视了integer promotions。而且最终可能转换成三种而不是只有double这一种。
紧接着

以上的转换是编译系统自动完成的,用户不必过问。
评:不知所云。

分析下面的表达式,假设已指定i为整型变量,值为3,f为float型变量,值为2.5,d为double型变量,值为7.5 。
10+‘a’+if-d/3
编译时,从左到右扫描,运算次序为:
①进行10+'a’的运算,……
②由于“
”比“+”优先级高,先进行if的运算。先将i和f都转成double型,……
③整数107与i
f的积相加。
④进行d/3的运算……
⑤将10+‘a’+i*f的结果与d/3的商2.5相减,……
评:最雷人的是: “编译时……运算次序为”
编译时就运算,那运行时计算机干些什么呢?

“②由于“”比“+”优先级高,先进行if的运算”
可是“进行10+'a’的运算”为什么先做了呢,
难道“*”比第1个“+”优先级低、比第二个“+”优先级高吗?

“先将i和f都转成double型”
错的。

小学语文问题:
“if的积”,
“将10+‘a’+i
f的结果与d/3的商2.5相减”

4.不同类型数据的混合运算
……如果一个运算符的两侧的数据类型不同,则先自动进行类型转换,使两者具有同一类型,然后进行运算。
评:若有
int a[1];
那么对于表达式
*( a + 0 )
显然“+”“两侧的数据类型不同”。
老谭能否给解释一下,如何“自动进行类型转换”?使两者具有了什么样的“同一类型”?

3.算术表达式和运算符的优先级和结合性
用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,
称为C算术表达式。运算对象包括常量、变量、函数等。例如,下面是一个合法的C算术表
达式:
ab/c-1.5+‘a’
C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先
按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-b
c,b的左侧为减号,右
侧为乘号,而乘号的优先级高于减号,因此,相当于a-(b*c)。
如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方
向”处理。C语言规定了各种运算符的结合方向(结合性),算术运算符的结合方向都是“自
左至右”,即先左后右,因此b先与减号相结合,执行a-b的运算,然后再执行加c的运算。
“自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符结合。以后可以看
到有些运算符的结合方向为“自右至左”,即右结合性(例如,赋值运算符,若有a=b=c,按
从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。关于“结合性”
的概念在其他一些高级语言中是没有的,是C语言的特点之一,希望能弄清楚。附录D列
出了所有运算符以及它们的优先级别和结合性。
评:首先,来看一下引用部分小标题里的第一个词——“算术表达式”。
这个词很给人一种“亲切感”特别有迷惑力,然而它确是一个模糊的、似是而非而且毫无用处的概念。
据我所知,这个词是老谭自创的。C语言中并没有这样的概念。
C语言中只有算术类型(arithmetic types)和算术运算符(arithmetic operators)这样的概念,并没有“算术表达式”这种概念。
没有这样的概念,难道不可以自己创造概念吗?当然可以。但必须知道的是,创造概念是有前提的:

创造者要给出概念的定义;

概念要科学严谨;

这个概念有用,或者方便简洁地描述了一个道理,或者帮助别人认识了一类现象或规律。

这样才可以创造新概念。
不满足这三个前提,自创概念不是吃饱了撑的就是假行家故弄玄虚的蒙人行为。考察一下“算术表达式”这个概念。
作者给出了定义:“用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式。”
(很好,老谭自创概念不给定义的例子比比皆是,这次很有进步)
然而,这个概念并不科学,也不严谨。为什么这么说呢?简单地考察一下下面的表达式就会知道了:
1+(2+3)
在这里,如果把“+”说成是“连接”“操作数”还是勉强说得过去的,但是“()”的意义则绝对不是为了“连接”“操作数”。“()”的意义是为了表明其内部的子表达式作为一个整体——仿佛一个独立的操作数参与运算,这与“连接”是八竿子打不着的。再比如“(1)”这个表达式中,“()”连接了什么呢?其实它什么也没连接,它只表明它与其扩起来的部分是一个完整的整体而已。
所以说,这里的“()”是一种“界定”(to delimit)范围的符号,把它与表示运算的运算符并列在一起是很荒唐的。
作者接着补充道:“运算对象包括常量、变量、函数等”。
这就让人迷惑不解了,这里的“函数”究竟是指的什么呢?是“函数调用”还是“函数名”本身呢?如果是“函数调用”,就成了自己打自己耳光,因为函数调用一定是通过函数调用运算符“()”实现的(注意这里这个"()"不是括号),这和作者在前面说的“用算术运算符和括号将运算对象(也称操作数)连接起来的”自相矛盾。如果这里的所谓“函数”是“函数名”的话,总所周知,“函数名”这种数据类型怎么可能进行算术运算呢?
所以必须感谢作者的及时补充,他成功地使用“函数”这个词戳穿了他自己创造的伪概念——“算术表达式”,这个概念是经不起推敲的。
紧接着的“例如”也很成问题。权且承认他给出“a*b/c-1.5+‘a’ ”是合法的,但是这个表达式即不是“用算术运算符和括号将运算对象(也称操作数)连接起来的”(因为没有括号),“运算对象”也不“包括常量、变量、函数”(“函数”在哪?),作者举例到底想说明什么呢?无厘头么!
或许,有人很不以为然:“括号”和“函数”都是随手写的,不算大错。好,至少现在你已经承认这书很不严谨了。问题是我没提到这些错误之前,你发现这些错误了吗?就算你也和我一样发现了这些错误,那些把这本书作为教材的初学者怎么可能发现这些错误呢?你老人家胡写一通不要紧,你让那些初学者情何以堪呢?
又或许有人觉得这段文字只要像下面那样修改一下就可以了

“用算术运算符和将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式。运算对象包括常量、变量等。例如,下面是一个合法的C算术表达式:
  a*b/c-1.5+'a'  ”

 Sound good!这段文字似乎基本没什么问题了。然而我们还是要问,这个自创的概念有什么用吗?继续往下看。下面一段文字是

“C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-bc,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(bc)。”
我在读这一段第一句话的时候被骇了一大跳。这句型,简直太惊人了。前面压根没提过“优先级”,抽冷子就来了一句“C语言除了规定了运算符的优先级外,还……”,有这么说话的吗?如果连话都说不利索,但却非要写在教科书里,您老人家又成天把这个“1100万”挂在嘴上,您说这和一只著名的苍蝇成天炫耀自己污染过很多食物有区别吗?
也许有人觉得这只是语文问题,不是C语言方面的问题。好吧,我让步!问题是这段文字和前面作者自创的“算术表达式”没有任何关系,没有人能否认这一点吧?!
再看引用文字的最后一段。这段文字同样和“算术表达式”这个伪概念没有任何关系。唯一可能牵强地和“算术表达式”扯上一点关系的是“算术运算符的结合方向都是‘自左至右’”,然而这句话本身就是错误的,因为作者在书中的第52页明确地说明一元“+”、“-”都是算术运算符,然而我们都知道一元“+”、“-”的结合性是“自右至左”,更何况“算术运算符”和“算术表达式”分明就是两个不同的概念。
而且,优先级和结合性的规律是对所有运算符而言的,算术运算符并没有什么特殊的优先级和结合性规律;而且所谓的“算术表达式”在这方面也没有任何特殊性而言,创造“算术表达式”比画蛇添足还要无聊。不仅无聊,而且有害。这个概念甚至无法比喻成懒婆娘的裹脚布,最多只能称得上是懒婆娘丢弃的裹脚布而已。
问题出来了,谭大爷为什么要自创这个不伦不类而且漏洞百出的伪概念——“算术表达式”,他到底想说什么?我估计他自己也不清楚。
话说到这里,我想我已经证明了这段引文的“胡扯性”。但是,根据我的经验,这时候一定会有无耻之徒跳出来无赖地狡辩:“毕竟还介绍了优先级和结合性么”——没办法不让他们跳出来,“卑鄙是卑鄙者的通行证”。然而,在这里,即使是这种最无耻的狡辩也是不可能得逞的,因为这段引文对“优先级”和“结合性”的讲解也是错的!
关于优先级,作者是这样写的:
“在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-bc,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(bc)。”
这段文字的错误在于作者把运算符的优先级和表达式求值的执行次序混为了一谈。尤其是“先乘除后加减”更是对优先级的曲解。实际上,优先级和表达式求值的执行次序基本上是不相干的两回事情。
优先级的意义在于表明表达式的含义(结合性也是),而求值的执行次序则是编译器的自选动作。只要不违反表达式的含义,编译器可以按照自己的爱好安排求值次序,编译器也没有义务告诉你它是按照什么次序求值的。这种自选动作叫implementation-defined behavior。
举例来说,对于表达式“ 1+2-34 ” ,“”的优先级高于“-”,其意义在于表明减去的是(34)而不是减3。只要你清楚地表明了这一点,你的任务就完成了。至于计算次序,编译器至少有两种选择:
1) 1+2-3
4 => 1+2-12 => 3-12 => -9
2) 1+2-34 => 3-34 => 3-12 => -9
按照外交部的惯用句型来说就是,怎么选择求值次序是编译器自己的事,程序员无权干涉(甚至无权了解,好奇也不行,编译器没义务告诉你)。 程序员的责任在于把表达式的含义写清楚,写正确;编译器的义务仅仅在于给出正确的结果而已。至于算出结果的过程(次序),对不起,关您什么事啊?您管的也太宽了吧。
总之,“优先级”的意思就是优先级高的运算符先选“对象”——运算对象。谭大爷明显是不明白这点小道理,可笑地把优先级和求值次序“绑定”到了一起,一相情愿地拉郎配。
如果优先级高的运算符选完了“对象”,剩下的运算符优先级相同,怎么办呢?
答案也简单,按照结合性挑选“对象”,如果是从左到右的结合方向,就左边的先挑,依次向右进行;如果是从右到左,则顺序相反。例如:
1+2-34
由于“
”的优先级最高,所以先挑运算对象,表达式的含义为
1+2-(34)
剩下的两个运算符“+”和“-”的优先级相同,所以看结合性,这两个运算符的结合性是从左到右,因此左面的先挑,表达式的含义可以进一步明确为
(1+2) - (3
4)
最后,可以确定“-”的运算对象分别为 (1+2) 和 (3*4)。
这就是优先级和结合性的全部含义。如此而已,非常简单。再来看看老谭是怎么讲的。
“如果在一个运算对象两侧的运算符的优先级别相同”,看到这句我差点没吐出来。老谭居然给来了个“如果”“运算对象”“两侧”,这跟优先级、结合性有什么关系吗?如果运算符在运算对象的同一侧怎么办?比如 - - i (不是"–“,两个“-”之间有空格),这应该怎么办呢?再比如 a[3],那个3应该算是在运算符的哪一侧呢?
所以看老谭的书,如果你不能发现他的愚蠢,那就只剩下了两种可能:1.你自己愚蠢;2.你正在变得愚蠢。
再往下看:
“算术运算符的结合方向都是’自左至右”,即先左后右’”,这句话本身就是错误的,前面已经提到过。
“因此b先与减号相结合”,这句更可笑,纯粹是望文生义式的妄断臆测。老谭把左结合性理解成了“运算对象先与左面的运算符结合”,简直是荒唐透顶。结合性,即使从字面上来说,也不是简单地指操作数与左面或右面的运算符相“结合”。简单举个例子就说明老谭的荒谬,”[]"运算符的结合性是从左至右,那么表达式 a [ b[0] ] 中 ,无论是a或是b,怎么与左面的运算符结合?所以“运算对象先与左面的运算符结合”这样的昏话绝对不可能是荒谬,只可能是荒谬透顶。
“即右结合性(例如,赋值运算符,若有a=b=c,按从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。”,这里也是错的。根据结合性,a=b=c 的含义是 a=(b=c) ,也就是说是把b=c的值赋给a,而不是老谭所说的“然后变量b的值赋给a”。
“关于“结合性”的概念在其他一些高级语言中是没有的,是C语言的特点之一”,这是井底之蛙坐井观天式思考的结果,根本不用反驳。
“希望能弄清楚”,希望很好,问题是您老人家自己弄清楚没有啊?您总这么瞎忽悠,让学习者怎么弄清楚啊?
“附录D列出了所有运算符以及它们的优先级别和结合性。”,第一,附录D并没有列出“所有”的运算符,第二,列出的部分不少是错误的。

如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方向”处理。
评:这句话蠢就蠢在那句“如果”
它好像是在暗示如果在一个运算对象一侧的运算符的优先级别相同,则不按规定的“结合方向”处理
实际上这个问题和运算符在运算对象的两侧还是一侧根本没有关系
只要是优先级相同,那么总是按结合性处理的

也就是说
那句蠢话相当于说:如果明天下雨,那么明天就是星期一
但就连弱智都懂得,明天是不是星期一和明天是否下雨是没什么关系的

算术运算符的结合方向都是“自左至右”
评:这简直是自己往自己脸上扇了一记大耳光
因为在该书的52页
表3.5 最常用的算术运算符

老谭明确无误地把一元 + 和一元 - 运算符都列为了“最常用的算术运算符”
难道一元+和一元-的结合性也是“自左至右”的吗?
(顺便说一句,不知道在老谭眼里,哪些算术运算符不是“最常用的”算术运算符)
p49
可以用以下方法测定本系统的处理方式:
char c=255;
printf(“%d\n”,c);

在Visual C++系统上进行编译时,系统给出“警告”(warning):“把一个整常数赋给char变量”。表示255已超过char变量的数值允许值,在运行时输出-1.说明Visual C++是把char默认为signed char类型的。如果把第1行改为“unsigned char c=255;”,则不出现“警告”,且输出255
评:如果一个初学者这样测,是应该夸奖一下的,尽管这是错的
但作为“大师”,居然如此,呵呵,……
p49
用后面的7位存放0到127,即127个字符的代码。
评:老谭究竟会不会数数?
p49
把可用的字符由127个扩展为255个,即扩大了一倍。
评:老谭真的是不识数
p49
……把本来不用的第一位用起来。把char变量改为unsigned char,即第一位并不固定设为0,而是把8位都用来存放字符代码。这样,可以存放(2^8-1)即255个字符代码。
评:还是不会数数的问题
p49
……为什么在C中把实数称为浮点数呢?在C语言中,实数是以指数形式存放在存储单元中的。一个实数表示为指数可以有不止一种形式,
评:第一句的设问简直太莫名其妙了,什么时候在C中把实数称为浮点数了呢?
整数难道不是实数?是以指数形式存放在存储单元中的吗?
至于“一个实数表示为指数”这简直就不是话。哪怕作为小学生的作文这样写也不合格呀
p49~51
“3.2.5 浮点型数据”小节的主要错误
评:1.
浮点型数据是用来表示具有小数点的实数的 。

C99中浮点类型包括实数浮点类型和复数浮点类型
2.
为什么在C中把实数称为浮点数呢?

没有的事情
在C中实数类型是指实数浮点类型和整数类型
3.
实数是以指数形式存放在存储单元中的

错。理由同上
4.
由于小数点的位置可以浮动,所以实数的指数形式称为浮点数

臆测。
5.
小数点后第一位数字不为0的表示形式称为规范化的指数形式

规范化的指数形式是作者随心臆造的一个似是而非且含混不清的概念。
浮点数中有“normalized floating-point number”这样的概念,但和作者所谓的“规范化的指数形式”无关。
6.
在程序以指数形式输出一个实数时,必然以规范化的指数形式输出,如0.314159e001。

无中生有,信口开河
7.
编译系统为每一个float变量分配4个字节,
用8个字节存储一个double型数据

C语言并没有规定浮点数据的存储空间的大小
8.
Turbo C对long double 型分配16个字节。

无中生有,信口开河
9.
表3.4 实型数据的有关情况
……
2.310^-308 ……1.710^308

“实型数据”:概念错误
这个表实际是IEEE浮点数的特征
精度差的惊人:
2.310^-308应为 2.22510^-308
1.710^308 应为 1.7976910^308
10.
例如float型变量能存储的最小正数为1.2*10-38,不能存放绝对值小于此值的数,例如10-40

不准确且自相矛盾。浮点数0.F的绝对值就小于1.2*10^-38。
11.
在C语言中进行浮点数的算术运算时,将float型数据都转换为double类型,然后进行计算。

错!

P50
编译系统为每一个float型变量分配4个字节。
评:毫无根据的说法
P50
浮点数类型包括float(单精度浮点型)、double(双精度浮点型)、long double(长双精度浮点型)。
评:这个说法证明老谭对C99还处于根本没入门的地步。
然而他居然在内容简介中号称“按照C语言的新标准C99进行介绍。”
P50
Turbo C对long double 型分配16个字节。
评:信口开河
P50
3.2.5 浮点型数据
……编译系统为每一个float型变量分配4个字节,……
……为了扩大能表示的数值的范围,用8个字节存储一个double型数据,……
评:C语言并没有规定浮点数据的存储空间的大小
P51
float型变量能存储的范围见图3.12。
……
即数值可以在3个范围内:(1)-3.4×1038到-1.2×10-38;(2)0;(3)1.2×10-38到3.4×1038;
评:图3.12画的很蠢
它和这段文字给了读者一个误导,就是0是孤立的而另外两段区间是连续的
P51
3.2.6 怎样确定常量的类型
在程序中出现的常量是要存放在内存单元中的
评:至少是不严格的。
P51
从常量的表示形式可以判断其类型
评:123456是什么类型单从其表示形式上是判断不出的

此外,这本书从头到尾没有介绍各种常量的写法
这也算是个“独创”了
如果常量都不会写
读者怎么学习写代码?
P51
不带小数点的数值是整型常量
评:"123E4"带不带小数点?是整型常量吗
P51
整型常量。不带小数点的数值是整型常量,但应注意其有效范围。如在Turbo C中,系统为整型数据分配2个字节,其表值范围为-32768~32767,如果在程序中出现数值常量23456,系统把它作为int型处理,用2个字节存放。如果出现49875,由于超过32768,,2个字节放不下,系统会把它作为长整型(long int)处理,分配4个字节。在Visual C++中,凡在-2147483648~2147483647之间的不带小数点的数都作为int型,分配4个字节,在此范围外的整数,而又在long long型数的范围内的整数,作为long long型处理。
评:一系列的逻辑错乱
首先,“整型常量”中的“整型”显然是一个集合名词,而“整型数据”中的“整型”是另一个概念,似乎只能理解为int类型。这违反了形式逻辑中的同一律
其次,int类型的表示范围和它为2个字节没有必然的因果关系
“由于超过32768”更是胡说八道
“2个字节放不下,系统会把它作为长整型(long int)处理,分配4个字节”表明作者根本不清楚整数常量的类型划定原则
“在Visual C++中,凡在-2147483648~2147483647之间的不带小数点的数都作为int型”,事实上根本不存在负常量
“在此范围外的整数,而又在long long型数的范围内的整数,作为long long型处理”,这是胡扯

P51
C编译系统把浮点型常量都按双精度处理,分配8个字节。
注意:C程序中的实型常量都是双精度浮点型常量。
评:从这里可以看出,老谭根本不懂得“浮点型”、“双精度”、“实型”这三个概念的区别
概念混乱不清是老谭这本书的一个重要特色

至于“分配8个字节”云云更是属于不懂得C语言的基本原理
P51~52
float a=3.14159;
……可以容忍这样的“警告”,使程序接着进行连接和运行。
评:容忍“警告”的程序员是不可容忍的
3.2.7 运算符和表达式
P52
1.基本的算术运算符
P53
……两个实数相除的结果是双精度实数。
评:没这回事。
3.F/5.F 的值就不是double
3.L/5. 也不可能是double

更荒谬的是
这句话明显地把整数从实数中给开除了
老谭征求过数学家的意见没
人家答应不
P53
两个整数相除的结果是整数,如5/3的结果值为1,舍去小数部分。但是如果除数或被除数中有一个为负值,则舍入的方向是不固定的。
评:老谭要是在前面不吹嘘他这本书是按照C99标准介绍的
我还真不好批他
P53
%运算符要求参加运算的运算对象(即操作数)为整数,结果也是整数。如8%3,结果为2。
评:除数或被除数中有负值的情况这回压根给忘了
P53
2. 自增、自减运算符
评:最主要的是两个问题
1.对运算的介绍不全面(“作用是使变量的值加1或减1”)
2.四个运算符当成了两个来介绍
P53
自增运算符(++)和自减运算符(–)只能用于变量,而不能用于常量或表达式
评:变量本身就是表达式
怎么可以说不能用于表达式呢

再给老谭看一个
int *p = (int *)malloc( sizeof (int) );
*p=6;
++*p;

"*p"难道不是表达式?
P53
使用++和–运算符时,常常会出现一些人们想不到的副作用,如i+++j,是理解为(i++)+j呢?还是i+(++j)呢?
评:1.没出现过什么想不到的副作用啊。如果想不到,是没有正确理解这些运算。而没能正确理解,那首先是因为教科书本身就没讲正确,没讲清楚
2.后面的例子并不能支持“出现一些人们想不到的副作用”
3.“副作用”这个术语在C语言中是有特定含义的,用在这里显然是概念糊涂错乱
P53
熟练的程序开发人员喜欢在使用++和–运算符时,采取一些技巧,以体现程序的专业性,建议初学者慎用。
评:听起来专业人员特别喜欢炫耀技巧。没这回事情。专业人员只是恰当地使用运算符罢了
要初学者慎用,是在掩饰作者自己的一知半解,逃避向初学者讲明白这些运算的责任。
这是一种不懂装懂式的装腔作势
P53
自增(减)运算符常用于循环语句中,使循环变量自动加1;也用于指针变量,使指针指向下一个地址。
评:没有的事情。大概是老谭自己只晓得这两种用法
“循环变量自动加1”的说法很搞笑,什么叫“自动加1”?怎么“自动”了?老谭给说说什么样不是“自动加1”
指针指向地址的说法也是错误的
P54
3.算术表达式和运算符的优先级与结合性
……在表达式求值时,先按运算符的优先级别顺序进行,例如先乘除后加减。
评:这完全是对优先级的歪曲
C语言里根本就没有先乘除后加减这回事
运算次序是一种Unspecified behavior
P54
“自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符相结合。
评:这就不是一般的胡扯了
估计是喝高了才敢这么扯
P54
若有a=b=c,按从右到左顺序,先把变量c的值赋给变量b,然后把变量b的值赋给a
评:这是对表达式“a=b=c”的错误解释
完全不懂得C语言最基本的运算规则
这会使学习者误入歧途,永远学不懂C语言
P54
关于“结合性”的概念是在其他一些高级语言中是没有的,是C语言的特点之一
评:把优先级和结合性都讲错了不说
又信口雌黄地胡说其他高级语言没有“结合性”的概念
居然把“结合性”说成了C语言的特点,简直是胡扯

不过这段胡扯倒是和把“a=b=c的意思是a=(b=c)”说成“是C++的观念”有异曲同工之妙
P54
如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方向”处理。
评:这句话蠢就蠢在那句“如果”
它好像是在暗示如果在一个运算对象一侧的运算符的优先级别相同,则不按规定的“结合方向”处理
实际上这个问题和运算符在运算对象的两侧还是一侧根本没有关系
只要是优先级相同,那么总是按结合性处理的

也就是说
那句蠢话相当于说:如果明天下雨,那么明天就是星期一
但就连弱智都懂得,明天是不是星期一和明天是否下雨是没什么关系的
P54
算术运算符的结合方向都是“自左至右”
评:这简直是自己往自己脸上扇了一记大耳光
因为在该书的52页
表3.5 最常用的算术运算符

老谭明确无误地把一元 + 和一元 - 运算符都列为了“最常用的算术运算符”
难道一元+和一元-的结合性也是“自左至右”的吗?
(顺便说一句,不知道在老谭眼里,哪些算术运算符不是“最常用的”算术运算符)
P54
3.算术表达式和运算符的优先级和结合性
用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,
称为C算术表达式。运算对象包括常量、变量、函数等。例如,下面是一个合法的C算术表
达式:
ab/c-1.5+‘a’
C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先
按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-b
c,b的左侧为减号,右
侧为乘号,而乘号的优先级高于减号,因此,相当于a-(b*c)。
如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方
向”处理。C语言规定了各种运算符的结合方向(结合性),算术运算符的结合方向都是“自
左至右”,即先左后右,因此b先与减号相结合,执行a-b的运算,然后再执行加c的运算。
“自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符结合。以后可以看
到有些运算符的结合方向为“自右至左”,即右结合性(例如,赋值运算符,若有a=b=c,按
从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。关于“结合性”
的概念在其他一些高级语言中是没有的,是C语言的特点之一,希望能弄清楚。附录D列
出了所有运算符以及它们的优先级别和结合性。
评:关于“算术表达式”、“优先级”和“结合性”的胡扯
首先,来看一下引用部分小标题里的第一个词——“算术表达式”。
这个词很给人一种“亲切感”特别有迷惑力,然而它确是一个模糊的、似是而非而且毫无用处的概念。
据我所知,这个词是老谭自创的。C语言中并没有这样的概念。
C语言中只有算术类型(arithmetic types)和算术运算符(arithmetic operators)这样的概念,并没有“算术表达式”这种概念。
没有这样的概念,难道不可以自己创造概念吗?当然可以。但必须知道的是,创造概念是有前提的:

创造者要给出概念的定义;

概念要科学严谨;

这个概念有用,或者方便简洁地描述了一个道理,或者帮助别人认识了一类现象或规律。
这样才可以创造新概念。
不满足这三个前提,自创概念不是吃饱了撑的就是假行家故弄玄虚的蒙人行为。考察一下“算术表达式”这个概念。
作者给出了定义:“用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式。”
(很好,老谭自创概念不给定义的例子比比皆是,这次很有进步)
然而,这个概念并不科学,也不严谨。为什么这么说呢?简单地考察一下下面的表达式就会知道了:
1+(2+3)
在这里,如果把“+”说成是“连接”“操作数”还是勉强说得过去的,但是“()”的意义则绝对不是为了“连接”“操作数”。“()”的意义是为了表明其内部的子表达式作为一个整体——仿佛一个独立的操作数参与运算,这与“连接”是八竿子打不着的。再比如“(1)”这个表达式中,“()”连接了什么呢?其实它什么也没连接,它只表明它与其扩起来的部分是一个完整的整体而已。
所以说,这里的“()”是一种“界定”(to delimit)范围的符号,把它与表示运算的运算符并列在一起是很荒唐的。
作者接着补充道:“运算对象包括常量、变量、函数等”。
这就让人迷惑不解了,这里的“函数”究竟是指的什么呢?是“函数调用”还是“函数名”本身呢?如果是“函数调用”,就成了自己打自己耳光,因为函数调用一定是通过函数调用运算符“()”实现的(注意这里这个"()"不是括号),这和作者在前面说的“用算术运算符和括号将运算对象(也称操作数)连接起来的”自相矛盾。如果这里的所谓“函数”是“函数名”的话,总所周知,“函数名”这种数据类型怎么可能进行算术运算呢?
所以必须感谢作者的及时补充,他成功地使用“函数”这个词戳穿了他自己创造的伪概念——“算术表达式”,这个概念是经不起推敲的。
紧接着的“例如”也很成问题。权且承认他给出“a*b/c-1.5+‘a’ ”是合法的,但是这个表达式即不是“用算术运算符和括号将运算对象(也称操作数)连接起来的”(因为没有括号),“运算对象”也不“包括常量、变量、函数”(“函数”在哪?),作者举例到底想说明什么呢?无厘头么!
或许,有人很不以为然:“括号”和“函数”都是随手写的,不算大错。好,至少现在你已经承认这书很不严谨了。问题是我没提到这些错误之前,你发现这些错误了吗?就算你也和我一样发现了这些错误,那些把这本书作为教材的初学者怎么可能发现这些错误呢?你老人家胡写一通不要紧,你让那些初学者情何以堪呢?
又或许有人觉得这段文字只要像下面那样修改一下就可以了

“用算术运算符和将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式。运算对象包括常量、变量等。例如,下面是一个合法的C算术表达式:
  a*b/c-1.5+'a'  ”

 Sound good!这段文字似乎基本没什么问题了。然而我们还是要问,这个自创的概念有什么用吗?继续往下看。下面一段文字是

“C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-bc,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(bc)。”
我在读这一段第一句话的时候被骇了一大跳。这句型,简直太惊人了。前面压根没提过“优先级”,抽冷子就来了一句“C语言除了规定了运算符的优先级外,还……”,有这么说话的吗?如果连话都说不利索,但却非要写在教科书里,您老人家又成天把这个“1100万”挂在嘴上,您说这和一只著名的苍蝇成天炫耀自己污染过很多食物有区别吗?
也许有人觉得这只是语文问题,不是C语言方面的问题。好吧,我让步!问题是这段文字和前面作者自创的“算术表达式”没有任何关系,没有人能否认这一点吧?!
再看引用文字的最后一段。这段文字同样和“算术表达式”这个伪概念没有任何关系。唯一可能牵强地和“算术表达式”扯上一点关系的是“算术运算符的结合方向都是‘自左至右’”,然而这句话本身就是错误的,因为作者在书中的第52页明确地说明一元“+”、“-”都是算术运算符,然而我们都知道一元“+”、“-”的结合性是“自右至左”,更何况“算术运算符”和“算术表达式”分明就是两个不同的概念。
而且,优先级和结合性的规律是对所有运算符而言的,算术运算符并没有什么特殊的优先级和结合性规律;而且所谓的“算术表达式”在这方面也没有任何特殊性而言,创造“算术表达式”比画蛇添足还要无聊。不仅无聊,而且有害。这个概念甚至无法比喻成懒婆娘的裹脚布,最多只能称得上是懒婆娘丢弃的裹脚布而已。
问题出来了,谭大爷为什么要自创这个不伦不类而且漏洞百出的伪概念——“算术表达式”,他到底想说什么?我估计他自己也不清楚。
话说到这里,我想我已经证明了这段引文的“胡扯性”。但是,根据我的经验,这时候一定会有无耻之徒跳出来无赖地狡辩:“毕竟还介绍了优先级和结合性么”——没办法不让他们跳出来,“卑鄙是卑鄙者的通行证”。然而,在这里,即使是这种最无耻的狡辩也是不可能得逞的,因为这段引文对“优先级”和“结合性”的讲解也是错的!
关于优先级,作者是这样写的:
“在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-bc,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(bc)。”
这段文字的错误在于作者把运算符的优先级和表达式求值的执行次序混为了一谈。尤其是“先乘除后加减”更是对优先级的曲解。实际上,优先级和表达式求值的执行次序基本上是不相干的两回事情。
优先级的意义在于表明表达式的含义(结合性也是),而求值的执行次序则是编译器的自选动作。只要不违反表达式的含义,编译器可以按照自己的爱好安排求值次序,编译器也没有义务告诉你它是按照什么次序求值的。这种自选动作叫implementation-defined behavior。
举例来说,对于表达式“ 1+2-34 ” ,“”的优先级高于“-”,其意义在于表明减去的是(34)而不是减3。只要你清楚地表明了这一点,你的任务就完成了。至于计算次序,编译器至少有两种选择:
1) 1+2-3
4 => 1+2-12 => 3-12 => -9
2) 1+2-34 => 3-34 => 3-12 => -9
按照外交部的惯用句型来说就是,怎么选择求值次序是编译器自己的事,程序员无权干涉(甚至无权了解,好奇也不行,编译器没义务告诉你)。 程序员的责任在于把表达式的含义写清楚,写正确;编译器的义务仅仅在于给出正确的结果而已。至于算出结果的过程(次序),对不起,关您什么事啊?您管的也太宽了吧。
总之,“优先级”的意思就是优先级高的运算符先选“对象”——运算对象。谭大爷明显是不明白这点小道理,可笑地把优先级和求值次序“绑定”到了一起,一相情愿地拉郎配。
如果优先级高的运算符选完了“对象”,剩下的运算符优先级相同,怎么办呢?
答案也简单,按照结合性挑选“对象”,如果是从左到右的结合方向,就左边的先挑,依次向右进行;如果是从右到左,则顺序相反。例如:
1+2-34
由于“
”的优先级最高,所以先挑运算对象,表达式的含义为
1+2-(34)
剩下的两个运算符“+”和“-”的优先级相同,所以看结合性,这两个运算符的结合性是从左到右,因此左面的先挑,表达式的含义可以进一步明确为
(1+2) - (3
4)
最后,可以确定“-”的运算对象分别为 (1+2) 和 (34)。
这就是优先级和结合性的全部含义。如此而已,非常简单。再来看看老谭是怎么讲的。
“如果在一个运算对象两侧的运算符的优先级别相同”,看到这句我差点没吐出来。老谭居然给来了个“如果”“运算对象”“两侧”,这跟优先级、结合性有什么关系吗?如果运算符在运算对象的同一侧怎么办?比如 - - i (不是"–“,两个“-”之间有空格),这应该怎么办呢?再比如 a[3],那个3应该算是在运算符的哪一侧呢?
所以看老谭的书,如果你不能发现他的愚蠢,那就只剩下了两种可能:1.你自己愚蠢;2.你正在变得愚蠢。
再往下看:
“算术运算符的结合方向都是’自左至右”,即先左后右’”,这句话本身就是错误的,前面已经提到过。
“因此b先与减号相结合”,这句更可笑,纯粹是望文生义式的妄断臆测。老谭把左结合性理解成了“运算对象先与左面的运算符结合”,简直是荒唐透顶。结合性,即使从字面上来说,也不是简单地指操作数与左面或右面的运算符相“结合”。简单举个例子就说明老谭的荒谬,”[]"运算符的结合性是从左至右,那么表达式 a [ b[0] ] 中 ,无论是a或是b,怎么与左面的运算符结合?所以“运算对象先与左面的运算符结合”这样的昏话绝对不可能是荒谬,只可能是荒谬透顶。
“即右结合性(例如,赋值运算符,若有a=b=c,按从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。”,这里也是错的。根据结合性,a=b=c 的含义是 a=(b=c) ,也就是说是把b=c的值赋给a,而不是老谭所说的“然后变量b的值赋给a”。
“关于“结合性”的概念在其他一些高级语言中是没有的,是C语言的特点之一”,这是井底之蛙坐井观天式思考的结果,根本不用反驳。
“希望能弄清楚”,希望很好,问题是您老人家自己弄清楚没有啊?您总这么瞎忽悠,让学习者怎么弄清楚啊?
“附录D列出了所有运算符以及它们的优先级别和结合性。”,第一,附录D并没有列出“所有”的运算符,第二,列出的部分不少是错误的。
P54
4.不同类型数据的混合运算
……如果一个运算符的两侧的数据类型不同,则先自动进行类型转换,使两者具有同一类型,然后进行运算。
评:若有
int a[1];
那么对于表达式
( a + 0 )
显然“+”“两侧的数据类型不同”。
老谭能否给解释一下,如何“自动进行类型转换”?使两者具有了什么样的“同一类型”?
P54
(1)+、-、
、/运算的两个数中有一个数为float或double型,结果是double型,因为系统将所有的float型数据都先转换为double型,然后进行运算。
评:一句不长的话错了两次。
1.“+、-、
、/运算的两个数中有一个数为float或double型,结果是double型”
老谭既然在前面吹嘘这本书是“按照C语言的新标准C99进行介绍”
难道不知道C语言有复数类型吗?
如果“+、-、*、/运算的两个数中有一个数为float或double型”,另一个是复数类型,“结果是double型”吗?

就算你根本不懂得C99
虚张声势地出来蒙人
long double 类型可是几十年前就有了
如果“+、-、*、/运算的两个数中有一个数为float或double型”,另一个是long double,“结果是double型”吗?

2.“系统将所有的float型数据都先转换为double型,然后进行运算。”,简直是胡扯
P54
(2)如果int型与float或double型数据进行运算,先把int型和float型数据转换为double型,然后进行运算。结果是double型
评:第一次读完这个(2),我愣住了,简直不敢相信
再仔细看一遍,发现这个(2)真的是很(2)
因为什么呢
因为(1)已经说过“+、-、*、/运算的两个数中有一个数为float或double型”
现在(2)居然又开始大谈特谈“如果int型与float或double型数据进行运算”
车轱辘话来回说是老谭的一个特点
但刚说完就repeat一遍还是罕见的
中等程度的神经错乱都写不出来这种东西

而且,char 、long等与float或double型数据进行运算的情况,阙如

这段中的严重错误就不谈了
与错乱相比,错误简直不算什么了不起的事情
P54
(3)字符(char)型数据与整型数据进行运算,就是把字符的ASCII代码与整型数据进行运算。
评:(暂且不谈C语言的执行环境未必使用ASCII码的问题)
这里的“整型数据”是个莫名其妙的概念

在该书的43页写到
| 基本整型 (int)
| 短整型 (short int)
| 长整型 (long int)
整型类型 | *双长整型 (long long int)
| 字符型 (char)
| *布尔型 (bool) [注:这里的bool也是错误的]

所谓“字符(char)型数据与整型数据进行运算”
说的到底是哪种"整型类型"呢?
况且这根本就是一个不必要的条件限制
难道 char类型数据 与 double类型数据 做加法运算时那个char类型数据不是字符编码吗?
它不是字符的编码还能是什么别的东西吗?
P54
(3)字符(char)型数据与整型数据进行运算,就是把字符的ASCII代码与整型数据进行运算。如:12 + ‘A’,由于……
评:前面说的是“字符(char)型数据与整型数据进行运算”
立刻就拿出个两个 int 类型数据的加法来举例
很明显
老谭根本不知道 ‘A’ 的类型究竟是什么
P54
字符数据可以直接与整型数据进行运算。
评:不知所云。
什么叫“直接”进行运算?
是否还有“间接”进行运算?
P54
如果字符型数据与实型数据进行运算,则将字符的ASCII代码转换为double型数据,然后进行运算。
评:1. “实型数据”,应为(实)浮点型数据。因为在C语言中real types是整数类型与(实)浮点类型的合称。
2.“字符的ASCII” ,运行环境中的字符不一定是用ASCII码表示的。况且,即使运行环境也使用ASCII码,字符数据中存储的也不一定是ASCII码。比如,字符数据可能是一个负值
3.“转换为double型数据” ,完全无视了integer promotions。而且最终可能转换成三种而不是只有double这一种。
紧接着
以上的转换是编译系统自动完成的,用户不必过问。
不知所云。
P54~55
分析下面的表达式,假设已指定i为整型变量,值为3,f为float型变量,值为2.5,d为double型变量,值为7.5 。
10+‘a’+if-d/3
编译时,从左到右扫描,运算次序为:
①进行10+'a’的运算,……
②由于“
”比“+”优先级高,先进行if的运算。先将i和f都转成double型,……
③整数107与i
f的积相加。
④进行d/3的运算……
⑤将10+‘a’+i*f的结果与d/3的商2.5相减,……
评:最雷人的是: “编译时……运算次序为”
编译时就运算,那运行时计算机干些什么呢?

“②由于“”比“+”优先级高,先进行if的运算”
可是“进行10+'a’的运算”为什么先做了呢,
难道“*”比第1个“+”优先级低、比第二个“+”优先级高吗?

“先将i和f都转成double型”
错的。

小学语文问题:
“if的积”,
“将10+‘a’+i
f的结果与d/3的商2.5相减”

P55
例3.3 给定一个大写字母,要求用小写字母输出。
……,字符数据以ASCII码存储在内存的
……
char c1,c2;
c1=‘A’;
c2=c1+32;
printf(“%c\n”,c2);
printf(“%d\n”,c2);
……
评:1.题目中的“要求用小写字母输出”莫名其妙。语文问题
2.“字符数据以ASCII码存储在内存的”,语文问题,而且观点错误。
3.c2=c1+32; 那个32是再拙劣不过的写法
4.既然“要求用小写字母输出”,printf(“%d\n”,c2);属于画蛇添足。程序完成不应该完成的功能,也是一种错误。
P55
一个字符数据既可以以字符形式输出,也可以以整数形式输出
评:但没有讲其中的道理
更可笑的是图3.13
竟然表明用%c和%d可以用来输出一个字节的数据(01100001)
属于外行的似是而非
对学习者有极大的误导
P56
5.强制类型转换运算符
……
(double)a (将a转换成double类型)
评:C语言居然有这功能?
P56
……例如:
a=(int)x
如果已定义x为float型变量,a为整型变量,进行强制类型运算(int)x后得到一个int类型的临时值,它的值等于x的整数部分,把它赋给a,注意x的值和类型都未变化,仍为float型。该临时值在赋值之后就不再存在了。
评:可怜的老谭,看来根本就不理解表达式的意义。
居然必须通过臆造一个“临时值”才能理解类型转换表达式
处于还没透彻理解表达式意义的水平下,让他写C语言书实在难为他了
“注意x的值和类型都未变化”,简直是废话,x的类型怎么可能变化呢?任何情况下也没这个可能啊
“它的值等于x的整数部分”,是错误的,至少是不严谨的
“该临时值在赋值之后就不再存在了。”,对无中生有的“临时值”所做的无中生有的臆测。
P56
如%运算符要求其两侧均为整型量,若x为float型,则x%3不合法,必须用(int)x%3。……因此先进行(int)x的运算,得到一个整型的中间变量。
评:“中间变量”属于老谭的“发明”
但老谭显然忘了他自己在前面是怎么说的了,
在41页:“变量代表一个有名字的、具有特定属性的一个存储单元”、“变量必须先定义,后使用”……
于是自己了打自己一耳光

3.3C语句
P57
语句的作用是向计算机系统发出操作指令,要求执行相应的操作
评:表达式才是要求操作
P57
声明部分不是语句,只是对有关数据的说明
评:声明不是对数据的说明
P58
(2)函数调用语句。……
(3)表达式语句
评:尽管老谭辩解说这是为了“理解”和“使用”
但实际上这表明他自己的不理解和不会使用
不得不按照BASCI或FORTRAN的方式来理解C
从书中的代码来看,除了构成表达式语句,他确实不会使用函数调用表达式
P59
在C程序中,最常用的语句是:赋值语句和输入输出语句。其中最基本的是赋值语句。程序中的计算功能大部分是由赋值语句实现的,几乎每一个有实用价值的程序都包括赋值语句。有的程序中的大部分语句都是赋值语句。

评:我简直无语
更雷人的是
书的另一处居然写着:
“C语言本身不提供输入输出语句”(p12)
让我们究竟相信你哪个说法呢

在C程序中,最常用的语句是:赋值语句和输入输出语句。其中最基本的是赋值语句。程序中的计算功能大部分是由赋值语句实现的,几乎每一个有实用价值的程序都包括赋值语句。有的程序中的大部分语句都是赋值语句。
评:笑喷了
这是史上最强悍最露骨的无知无畏——什么他都敢说,而且都是错话和废话
P59
printf(“a=%f\tb=%f\t%f\n”,a,b,c);
评:

P60
赋值符号=就是赋值运算符,它的作用是将一个数据赋给一个变量。如a=3的作用是执行一次赋值操作(或称赋值运算)。把常量3赋给变量a。也可以将一个表达式的值赋给一个变量。
评:
P60
a+=3 等价于 a=a+3
评:不很严谨
不过指望老谭能理解二者的差异是不可能的事情
算了
P60
①a+=b (其中a为变量,b为表达式)
……
注意:如果b是包含若干项的表达式,则相当于它有括号。例如,……
评:例如
a+=3,2
就相当于
a+=(3,2) 吧?

结论:老谭根本不懂得表达式
不懂表达式根本连C的门都还没摸到
奇怪的是
居然有那么多人自称跟着老谭入了门
P61
凡是二元(二目)运算符,都可以与赋值符一起组合成复合赋值符。
评:好一个“凡是”啊
这口气听起来就像权威
不过
”应该是“二元(二目)运算符”吧
请问老谭
= ”是什么“复合赋值符”呢
P61
C语言采用这种复合运算符,一是为了简化程序,使程序精炼,二是为了提高编译效率,能产生质量较高的目标代码。
评:什么叫敢想敢说?
这就是
P61
左值的意思是它可以出现在赋值运算符的左侧,它的值是可以改变的
评:记得supermegaboy就这个问题给老谭上过一课
看来老谭还是没懂
有一点进步
已经不说左值只能是变量而不能是表达式了
P61
执行表达式“a=(b=5)”,就是执行b=5和a=b两个赋值表达式。
评:

凡是二元(二目)运算符,都可以与赋值符一起组合成复合赋值符。

评:是没睡醒还是在信口开河。
如此说来
“=”是二元运算符,它可以与赋值符一起组合成复合赋值符“==” 、

P62
赋值表达式也可以包括复合的赋值运算符。例如:
a+=a-=aa
也是一个赋值表达式。如果a的初值为12,此赋值表达式的求解步骤如下:
①先进行“a-=a
a”的运算,它相当于a=a-a*a,a的值为12-144=132。
④ 再进行“a+=-132”的运算,相当于a=a+(-132),a的值为-132-132=-264
评:这个例子表明老谭不知道写代码的最基本准则
这种错误在大学教材里存在了二十年
是一种国耻

也是程序员之耻
人们有理由问
这个国家的程序员们究竟懂不懂C语言
P62
printf(“%d”,a=b);
如果b的值为3,则输出a的值(也是表达式a=b的值)为3。
评:这句话对初学者的误导几乎是致命的。
P63
(4)字符型数据赋给整型变量时,将字符的ASCII代码赋给整型变量。如:
i=‘A’;;
评:1.字符型数据存储的未必是ASCII码
2.'A’根本不是字符型数据
3.“;;”
P63
(5)将一个占字节多的整型数据赋给一个占字节少的整型变量或字符变量(例如把占4个字节的int型数据赋给占2个字节的short变量或占一个字节的char变量)时,只将其低字节原封不动地送到被赋值的变量(即发生“截断”)。
评:这是错误的
估计这是老谭根据某个编译器下的运行结果自己总结出来的

P64
例如:
if((a=b)>0) max=a;
评:在这个地方之前根本就没介绍过“>”运算,更没介绍过if语句。这例子很差劲
P64
先进行赋值运算(将b的值赋给a),然后判断a是否大于0
评:对“if((a=b)>0) max=a; ”的解说
是错误的。
解说所描述的是 a=b,a>0 这个表达式而不是(a=b)>0这个表达式
P64
6.变量赋初值
……
也可以被定义变量的一部分赋初值。
评:语文问题
冷一看还以为老谭在说结构体呢

什么叫“变量的一部分”?

P65
一般变量初始化不是在编译阶段完成的(只有在静态存储变量和外部变量的初始化是在编译阶段完成的),而是在程序运行时执行本函数时赋予初值的,相当于执行一个赋值语句。
评:编译时变量存在吗?

“只有在静态存储变量和外部变量的初始化是在编译阶段完成的”,语文问题
3.4 数据的输入输出
p66
该scanf函数表示从终端输入的3个数据……。双撇号内用%lf格式声明,表示输入的是双精度型实数。
评:scanf函数的输入来自标准输入设备

“表示输入的是双精度型实数”是很严重的误导。这种似是而非的“省劲”说法,会给后面的学习带来很多困惑和麻烦。
标准输入设备提供的只是一些字符而已,根本就没有什么双精度实数。

P67
(1)所谓输入输出是以计算机主机为主体而言的。从计算机向输出设备(如显示器、打印机等)输出数据称为输出,从输入设备(如键盘、磁盘、光盘、扫描仪等)向计算机输入数据称为输入,如见图3.17所示。
评:总体来说是废话
给人的感觉是输入输出设备明显已经不是计算机的组成部分了,不知道冯.诺依曼是否知道他的体系已经被老谭给强力摧毁

P67
(2)C语言本身不提供输入输出语句,
评:我就不明白老谭为什么反复说这句话,从开头到这里至少说四五次了。是不是没话找话呢
P67
例如printf函数和scanf函数。读者在使用它们时,千万不要误认为它们是C语言提供的“输入输出语句”
评:除了老谭自己的书上,我从来没见过把printf函数和scanf函数调用叫做输入输出语句的
P67
C提供的标准库函数以库的形式在C的编译系统中提供,它们不是C语言文本中的组成部分。
评:前半句就是个病句,搞不清到底是谁“提供”
后半句不知所云。什么叫“C语言文本”
P67
不把输入输出作为C语句的目的是使C语言编译系统简单精炼,因为将语句翻译成二进制的指令是在编译阶段完成的,没有输入输出语句就可以避免在编译阶段处理与硬件的有关问题,可以使编译系统简化,而且通用性强,可移植性好,在各种型号的计算机和不同的编译环境下都能适用,便于在各种计算机上实现。

各种C编译系统提供的系统函数库是各软件公司编译的,它包括了C语言建议的全部标准函数。
评:武断
P67
各种C编译系统提供的系统函数库是各软件公司编译的,它包括了C语言建议的全部标准函数,还根据用户的需要补充一些常用的函数,已对它们进行了编译,成为目标文件(.obj文件)。它们在程序连接阶段与源程序经编译得到的目标文件(.obj文件)相连接,生成一个可执行的目标程序(.exe文件)。如果在源程序中有printf函数,在编译时并不把它翻译成目标指令,而是在连接阶段与系统函数库相连接后,在执行阶段中调用函数库中的printf函数。
评:1.头一次听说库是 .obj文件,不知道老谭是否在编译软件中发现了printf.obj啊?
2.“在编译时并不把它翻译成目标指令”,那还叫编译吗?不翻译难道保留源代码?
3.最雷人的是“在执行阶段中调用函数库中的printf函数”,老谭自己知不知道自己在说什么啊?对自己根本不了解的东西难道可以这样信口开河吗?执行阶段怎么调用库?难道发行可执行文件必须像VB那样搭配一个库?

P68
#include 指令都放在程序文件的开头
评:未必
P69
printf函数(格式输出函数)用来向终端(或系统隐含指定的输出设备)输出若干个任意类型的数据。
评:这个属于昏话。
首先“终端”这词是模糊不清的、不准确的
“系统隐含指定的输出设备”更是不知所云的昏话
至于“若干个任意类型的数据”更是胡扯

printf()函数的功能是在标准输出设备上输出若干字符
P69
“输出表列”是程序需要输出的一些数据,可以是常量、变量或表达式。
评:这个说法很滑稽
常量、变量都是表达式
老谭居然整出了个“常量、变量或表达式”
和“香蕉、苹果或水果”如出一辙
这东西作为小学作文也不及格吧

此外“表列”这个词也很奇怪
注意了一下
这个怪异的说法并非笔误而是在多处一致地这样写

P70
如果整数比较大,则把它的最后一个字节的信息以字符形式输出。如:
int a=377;
printf(“%c”,a);
评:这个说法不准确,实际上是通过类型转换
P70
(1)d格式符
……在输出时,按十进制整型数据的实际长度输出,正数的符号不输出。
评:1.既然谈到了数据的类型,就谈不到什么“十进制”
2.正数的符号可以输出
P73
(2)o格式符。以八进制整数形式输出。将内存单元中的各位(0或1)按八进制形式输出,因此输出的数值不带符号,即将符号位也一起作为八进制数的一部分输出。例如:
int a=-1;
printf(“%d\t%o\n”,a,a);
运行时输出:
-1 37777777777

评:用%o输出int类型数据是未定义行为
P73~74
(5)g格式符。用来输出浮点数,系统自动选f格式或e格式输出,选择其中长度较短的格式,不输出无意义的0。如:
double a=12345678954321
printf(“%f\t%e\t%g\n”,a,a,a);
的输出结果为:
12345678954321.000000 1.234568e+013 1.23457e+013
可以从以上看到用%f格式输出占20列,用%e格式输出占13列,故%g采用%e格式输出。
评:问题是“double a=12345678954321”根本无法通过编译
结果究竟是怎么得到的?

即使按“double a=12345678954321;”来看,这个赋初值也是很成问题的

况且用%f格式输出占的是21列
%g格式的输出也不是按照%e格式

P74
表3.6 printf函数中用到的格式符
……
d,i 以带符号的十进制形式输出整数(正数不输出符号)
o 以八进制无符号形式输出整数(不输出前导符0)
x,X 以十六进制无符号形式输出整数(不输出前导符0x)
评:不是这样的
P74
表3.6 printf函数中用到的格式符
……
g,G 选用%f或%e格式中输出宽度较短的一种格式,不输出无意义的0。用G时若以指数形式输出,则指数以大写表示。
评:这个也是错的。
P74
表3.7 printf函数中用到的格式附加字符
……
评:1.不全
2.其中的m,n是什么以及如何用根本就没说清楚,并且有其他错误。

P75
scanf(“a=%f,b=%f,c=%f”,&a,&b,&c);
评:这种写法要多蠢有多蠢
简直是自虐

P76
表3.8 scanf函数中所用到的格式字符
u 用来输入无符号的十进制数
o 用来输入无符号的八进制数
x,X 用来输入无符号的十六进制数
评:这几种格式都容许输入有符号整数
P76
例如,若a和b为整型变量,如果写成
scanf(“%f%f%f”,a,b,c);
是不对的。应将“a,b,c”改成“&a,&b,&c”。许多初学者常犯此错误。
评:那样是不对
老谭改的就对吗?

前面说“若a和b为整型变量”
后边居然莫名其妙地出来个c
而且整型使用%f
这不是粗制滥造是什么呢
P76
如果有
scanf(“a=%f,b=%f,c=%f”,&a,&b,&c);
在输入数据时,应在对应的位置上输入同样的字符。即输入
a=1,b=3,c=2
评:scanf(“a=%f,b=%f,c=%f”,&a,&b,&c);
如此愚蠢的代码怎么可以写在教科书里
而且煞有介事地进行解释呢

P77
如果scanf函数为
scanf(“a=%f b=%f c=%f”,&a,&b,&c);
由于在两个%f间有两个空格,因此在输入数据时,两个数据间应有两个或更多的空格字符。)
评:第一,两个数据间应有两个或更多的不一定是空格字符
第二,这种写法同样很蠢,两个%f之间加空格干什么?
P77
(3)在用“%c”格式声明输入字符时,空格字符和“转义字符”中的字符都作为有效字符输入
评:“转义字符”中的字符
这个太搞了

P78
putchar©的作用是输出字符变量c的值,显然它是一个字符
评:显然老谭不知道那个c不是char类型
P78
例3.8 先后输出BOY三个字符。
解题思路:定义3个字符变量,……
#include <stdio.h>
int main()
{
char a=‘B’,b=‘O’,c=‘Y’;
putchar(a);
putchar(b);
putchar©;
putchar(‘\n’);
return 0;
}
评:唯一值得表扬的是putchar(‘\n’);
输出三个字符哪里需要用的着费那么大劲啊
printf(“BOY\n”);
定义变量很傻

P79
因此将一个字符赋给字符变量和将字符的ASCII代码赋给字符变量作用是完全相同的(但应注意,整型数据应在0~127的范围内)
评:完全不懂得赋值是怎么回事
所谓“将一个字符赋给字符变量”“赋给字符变量”是完全不通的说法
实际是根本不存在的情况
0~127这个条件根本不需要
那个“整型数据”不清楚是从哪里窜出来的,前文中根本没有
P79
说明:putchar©中的c可以是字符常量、整型常量、字符变量或整型变量(其值在字符的ASCII代码范围内)
评:c就是一个int类型数据
“其值在字符的ASCII代码范围内”也不对
P79
一般是显示器的键盘
评:看来我真out了
还真没见过这种东东
P79
例3.9 从键盘输入BOY三个字符,然后把它们输出到屏幕。
评:问题的提法非常古怪无聊
如果输入DOG是不是还得另外编个程序
P79
用putchar函数既可以输出能在显示器屏幕上显示的字符,也可以输出屏幕控制字符。
评:老谭总是自创一些莫名其妙的概念,这表明他自己概念不清
一大群幼稚的初学者跟着莫名其妙地理解,各有各的糊涂理解。有人把这叫“通俗易懂”
P79
字符类型也属于整数类型。
评:严重表扬!老谭说了一句正确的话
相对以前各版,这是一个巨大的进步

P80
这些字符先暂存在键盘的缓冲器中,
评:前所未闻
P82
可以用printf函数和scanf函数输入或输出字符。
请比较这两个方法的特点,在特定情况下用哪一种方法为宜。
评:语文问题
第4章 选择结构程序设计
4.1 选择结构和条件判断
P85

输入一个数,要求输出其绝对值。
评:不明确的程序功能要求
P86
例4.1 ……
……//disc是判别式sqrt(b*b-4ac)
disc = b * b - 4 * a * c;
……
评:此外此例代码风格欠佳。

(1) 为提高精度以及避免在编译时出现“警告”,将所有变量定义为双精度浮点型。
评:如果是为了“避免”“警告”“将所有变量定义为双精度浮点型”表明老谭不会使用float类型而不得不废掉C语言的一种基本数据类型。
这是一种误导。选择数据类型不能根据(因为不懂得如何使用而引起的)“警告”
谭的做法是因噎废食,道无知以误人

4.1小节的另一个缺欠是,在没介绍关系运算和if语句的条件下用它们写代码。
4.1 用if语句实现选择结构
P87
从例4.1可以看到:在C语言中选择结构主要是用if语句实现的。
评:从一个例子就能看出“在C语言中”如何如何
老谭的因果关系一向非常山奔海立,让人可惊可愕

为了实现互换,必须借助于第3个变量。
评:“必须”两个字过于武断
不借助第3个变量也可以交换变量的值

例4.2 输入两个实数,按代数值由小到大的顺序输出这两个数。
例4.3 输入3个数a、b、c,要求按由小到大的顺序输出。
评:前一个是合格的题目
后一个不合格:"3个数"性质不明确;a、b、c更是多此一举

4.2 用if语句实现选择结构
评:这一小节最重要的缺失是没有介绍if语句的具体执行过程
实际上
if(表达式)语句
计算机首先计算“表达式”的值,然后根据这个值是否不为0选择是否执行语句
这种次序观念对于初学者来说极其重要

P89
4.2.2 if语句的一般形式
……
if(表达式)语句1
[else 语句2]
if语句中的“表达式”可以是关系表达式、逻辑表达式,甚至是数值表达式。其中最直观、最容易理解的是关系表达式。
评:这恍然大悟式的惊呼说明了老谭对C的理解程度
然而这里列举的并不全面(其实除了void 类型所有表达式都可以)
而且对比不当
试问什么是“数值表达式”?
关系表达式、逻辑表达式是不是数值表达式?
如果承认关系表达式、逻辑表达式也是数值表达式
那么何谈“甚至”呢?

而“最直观、最容易理解”其实恰恰说明老谭自己对其他表达式感到难以理解,至于他能否恰当地应用,至少我是怀疑的
P90
……
else cost=0
评:讽刺的是在这页上写着“每个内嵌语句的末尾都应当有分号,因为分号是语句中的必要成分。”

(1) 整个if语句可以写在多行上,也可以写在一行上,如:
if(x>0)y=1 ;else y=-1;
但是,为了程序的清晰,提倡写成锯齿形式。
评:这是无厘头的废话
除了预处理命令,整个源程序都可以写在一行上,难道也告诉读者?

C源程序的写法是统一的,if语句并不存在任何特殊性

每个内嵌语句的末尾都应当有分号,因为分号是语句中的必要成分。
评:没这个事

if(1){
}
else{
}
就一个分号都没有

联系到该书把语句分为 控制语句、函数调用语句、表达式语句、空语句、复合语句,以及“最基本的语句——赋值语句”这样的说法
不难发现该书作者对“语句”始终缺乏准确、完整的概念

P91
(6)在if语句中要对给定的条件进行检查,判定所给定的条件是否成立。判定的结果是一个逻辑值“是”或“否”。
评:本来以为89页的那句带着“甚至”的惊呼“ if语句中的“表达式”可以是关系表达式、逻辑表达式,甚至是数值表达式。”表明老谭的C语言开始入门了。现在看来,他还是在BASIC的“是”、“否”泥潭里面折腾着呢。
不难发现他的自相矛盾
也终于理解他前面为什么要说“甚至是数值表达式”了,这是一种初学者新发现式的惊奇。

事实上,以
if(表达式) 语句
为例

其中的“表达式”本来就是一个数值表达式,如果这个表达式的值不为0则执行“语句”,否则不执行“语句”
如此而已

根本就没有什么“条件”、“检查”,C也没有“是”、“否”这样的概念

又如:判断“a>b”条件是否满足,当a>b时,就称条件“a>b”为“真”,如果“a≤b”,则不满足“a>b”条件,就称此时条件“a>b”为假。————
评:这讲的不是C语言
在C语言中“>”是一种运算符
a>b的值可能为1,也可能为0
4.3 关系运算符合关系表达式
P91

其中的“>”是一个比较符
评:老谭总喜欢发明这些古怪且没有什么意义的“新术语”

例如,a>3是一个关系表达式,大于号是一个关系运算符,如果a的值为5,则满足给定的“a>3”条件,因此关系表达式的值为“真”(即“条件满足”);如果a的值为2,不满足“a>3”条件,则称关系表达式的值为“假”
评:似是而非的误导。
http://bbs.chinaunix.net/thread-2297714-1-2.html
这个帖子说明了这种误导的后果

实际上“真”、“假”这些概念根本不属于C语言
总是借助多余的概念来理解C所表明的是并不真正理解C

在C语言中,>是一种运算符
5>3的运算结果为1
2>3的运算结果为0
就这么简单。根本不需要那些画蛇添足的“真”、“假”

P92
用关系运算符将两个数值或数值表达式连接起来的式子,称关系表达式。
评:“称关系表达式”,这语文水平就不评说了
表述也极不严谨
试问“3&4”、“5|6”算不算数值表达式
用关系运算符连接起来——“ 3&4<5|6 ”,是关系表达式吗?
4.4 逻辑运算符和逻辑表达式
P92~93
4.4 逻辑运算符和逻辑表达式
有时要求判断的条件不是一个简单的条件,而是由几个给定简单条件组成的复合条件。如:“如果星期六不下雨,我去公园玩”。这就是由两个简单条件组成的复合条件,需要判定两个条件:(1)是否星期六;(2)是否下雨。只有这两个条件都满足,才去公园玩。又如“参加少年运动会的年龄限制为13~17岁”,这就需要检查两个条件:(1)年龄age≥13(2)年龄age≤17。这个组合条件条件是不能够用一个关系表达式来表示的,要用两个表达式的组合来表示,即:age≥13 AND age≤17。用一个逻辑运算符AND连接age≥13和age≤17。两个关系表达式组成一个复合条件,“AND”的含义是“与”,即“二者同时满足”。age≥13 AND age≤17表示age≥13和age≤17同时满足。这个复合的关系表达式“age≥13 AND age≤17”就是一个逻辑表达式。其他逻辑表达式可以有:
x>0 AND y>0 (同时满足x>0和y>0)
age<12 OR age>65 (年龄age小于12的儿童或大于65的老人)
上面第1个逻辑表达式的含义是:只有x>0和y>0都为真时,逻辑表达式x>0 AND y>0 才为真。上面第2个逻辑表达式的含义是:age<12 或 age>65至少有一个为真时,逻辑表达式age<12 OR age>65为真。OR是“或”的意思,即“有一即可”,在两个条件中有一个满足即可。AND和OR是逻辑运算符。
用逻辑运算符将关系表达式或其他逻辑量连接起来的式子就是逻辑表达式。
评:云山雾罩,不知所云
任何合格的C程序员都不可能认为这是在讲C语言
尤其是“AND和OR是逻辑运算符”这句,属于根本性的概念错误
在C语言中AND和OR是标识符不是运算符(在C语言中唯一可以做运算符的标识符是sizeof)
连“标识符”、“运算符”这样最最的基本概念都拎不清,居然写教材?C语言界没人啦

"用逻辑运算符将关系表达式或其他逻辑量连接起来的式子就是逻辑表达式"的错误性质见1180楼
P93
有3种逻辑运算符:与(AND),或(OR),非(NOT)。在BASIC和Pascal等语言中可以在程序中直接用AND,OR,NOT作为逻辑运算符。
评:逻辑运算有很多种,至少还有个XOR,怎么可能只有3种逻辑运算符呢?
在BASIC中还有Eqv,Imp
BASIC也不是直接用AND,OR,NOT作为逻辑运算符,而是用的And,Or,Not,只不过BASIC不区分大小写罢了
再则,BASIC的逻辑运算符的运算含义也绝对不同于C的逻辑运算符的运算意义,根本不能当作同一种东西一概而论

在C语言中不能在程序中直接用AND,OR,NOT作为逻辑运算符,而是用其他符号代替。见表4 .1。
评:这圈子绕的!从上海到苏州,却非要舍近求远地先到欧洲兜一圈再到苏州
讲了一火车废话错话,有意义的内容用几个字就能概括——C有三个逻辑运算符:&&,||、!。

表4.1 C逻辑运算符及其含义
运算符 含义 举例 说明
&& 逻辑与 a&&b 如果a和b都为真,则结果为真,否则为假
|| 逻辑或 a || b 如果a和b有一个以上为真,则结果为真,二者都为假时,结果为假
! 逻辑非 !a 如果a为假时,则!a为真,如果a为真,否则!a为假
评:1.C语言没有“真”“假”这种东西
2.对&& ||运算的解释是错误的,是一种似是而非的歪曲
3.只介绍了优先级,对结合性只字未提。

总之,学习者不可能从中真正理解这3个运算

&&和||运算涉及到sequence point,不懂得这个概念,就不能说是真正懂得了这两种运算

P94
5>3&&8<4-!0
表达式自左至右扫描处理求解。首先处理“5>3”(因为关系运算符优先于逻辑运算符&&)。在关系运算符>两侧的5和3作为数值参加关系运算,“5>3”的值为1(代表真),再进行“1&&8<4-!0”的运算,8的左侧为“&&”,右侧为“<”,根据优先规则,应先进行“<”的运算,即先进行“8<4-!0”的运算。现在4的左侧为“<”,右侧为“-”运算符,而“-”优先于“<”,因此应先进行“4-!0”的运算,由于“!”的级别最高,因此先进行“!0”的运算,得到结果1.然后进行“4-1”的运算,得到结果3,再进行“8<3”的运算,得0,最后进行“1&&0”的运算,得0.
评:自相矛盾之处在于
一会说“自左至右扫描处理求解”
一会又根据优先级确定运算的先后
"8的左侧为“&&”,右侧为“<”,根据优先规则,应先进行“<”的运算"更荒唐,大概老谭忘记了还有优先级更高的“!”

这两种说法都是站不住脚的,也都是错误的

C语言编译系统在表示逻辑运算结果时,以数值1代表“真”,以0代表“假”
评:是什么话
编译系统怎么表示运算结果?运算是在编译时进行的吗?如果不是,编译系统怎么可能表示运算结果呢

(1)若a=4,则!a的值为0.……
(2)若a=4,b=5,则a&&b的值为1.……
(3)a和b值分别为4和5,a||b的值为1.
……
评:这就和他的代码中,哪怕是常量也一定要赋值给一个变量再运算,不管有没有必要,是一致的

实际上
(1)无非说的是 !4的值为0
(2)无非说的是 4&&5的值为1
(3)无非说的是 4||5的值为1
搞不清楚他画蛇添足地弄出来a、b作甚

P95
在逻辑表达式的求解中,并不是所有的逻辑运算符都被执行,只是在必须执行下一个逻辑运算符才能求出表达式的解时,才执行该运算符。举例如下。
评:“执行”“运算符”
听着比日本话还别扭:运算符执行地有
作者是中国人吗

(1)a&&b&&c。只有a为真(非0)时,才需要判别b的值。只有当a和b都为真的情况下才需要判别c的值。如果a为假,就不必判别b和c(此时整个表达式已确定为假)。如果a为真,b为假,不判别c,见图4.7。
评:原来这就是所谓的“并不是所有的逻辑运算符都被执行”啊
这里的例子说明的恰恰是两个“&&”运算都执行了
任何一个没执行都不可能得到最后的结果
没被执行的是求b和c的值

既然关系表达式和逻辑表达式的值是0和1,而且在判断一个量是否为“真”时,以0代表“假”,以非0代表“真”。那么就可以理解为什么在if语句中表达式可以是任何数值表达式。
评:不存在这种因果关系。把“关系表达式和逻辑表达式的值是0和1”扯进来更是思路不清的表现
试想,关系表达式和逻辑表达式的值是0和1和if语句的表达式有什么必然的联系呢
所以这是一段似是而非的论述

此外“if语句中表达式可以是任何数值表达式”是错误的
这个表达式只能是标量(scalar)类型
P97
如果读者使用的C++的编译器(如Visual C++),则可以使用逻辑型变量。但要把文件名后缀改为.cpp,作为C++程序,
评:搞不清楚这是想做什么
C、C++分不清楚在谭书中是一贯的
著名的把赋值表达式当作左值的笑话就是这种情况

4.5 逻辑运算符和逻辑表达式
P97

if(a>b)
max=a;
else
max=b;
……
C提供条件运算符和条件表达式来处理这类问题。
评:牵强附会
C语言的条件运算符并非是为这类问题而存在的
这种说法是误导,会限制学习者正确地应用条件运算符

“?"是条件运算符。
评:有人不主张挑这种鸡毛蒜皮的小错误
但这种小错误如此之多又说明什么呢

P98
a>b?(max=a ) : (max=b);
a>b?printf(“%d”,a):printf(“%d”,b);
评:真应了那句“再好的语言也挡不住有人写出垃圾代码”?
P99
ch=(ch>=‘A’&&ch<=‘Z’)?(ch+32):ch;
评:风格、效率、可移植性都成问题
效率指的是和什么比的?能具体说一下不?
4.6 选择结构的嵌套
P100
为了避免二义性的混淆,最好使内嵌语句也包含else部分,这样if的数目和else的数目相同,从内层到外层一一对应,不致出错。
评:这招灵吗?我很怀疑
不管有没有必要都把else写上?
P100~101
-1 (x<0 )
y = 0 (x=0 )
1 (x>0 )
……
程序1:
#include <stdio.h>
int main()
{
int x,y;
scanf(“%d”,&x);
if(x<0)
y=-1;
else
if(x==0)y=0;
else y=1;
printf(“x=%d,y=%d\n”,x,y);
return 0;
}
……
程序2:
#include <stdio.h>
int main()
{
intx,y;
scanf(“%d”,&x);
if(x>=0)
if(x>0)y=1;
else y=0;
else y=-1;
printf(“x=%d,y=%d\n”,x,y);
return 0;
}
评:这也太雷人了吧
一个如此简单的问题
居然弄了个那么复杂的嵌套结构
舍简就繁

程序2居然有运行结果
P101~102
为了使逻辑关系清晰,避免出错,一般把内嵌的if语句放在外层的else子句中(如程序1那样),这样由于有外层的else相隔,内嵌的else不会被误认为和外层的if配对,而只能与内嵌的if配对,这样就不会搞混。
评:这是因噎废食而想到的削足适履性质的差劲办法
代码的结构应该服从思想,应该是思想的自然展开和表达
先把思想装进形式上的套子里写不出优质的代码
4.7用switch语句实现多分支选择结构
P103
4.7 用switch语句实现多分支选择结构
switch语句的一般形式如下:
switch(表达式)
{
case 常量1:语句1
case 常量2:语句2
……
case 常量n:语句n
default:语句n+1
}
……
switch语句下面的花括号内是一个复合语句。
评:switch语句的一般形式并非是
switch(表达式) 复合语句

switch后面括号内的“表达式”,其值的类型应为整数类型(包括字符型)
评:1 整数类型本来就包括字符类型
2 谭书43页数据类型总表中根本没有“整数类型”,不清楚谭书中的“整数类型”究竟是指的什么

case 后面跟一个常量(或常量表达式)
评:不对!只能是整数常量表达式

每个case标号出现的次序不影响执行结果
评:显然不对

P104
在case子句中虽然包含了一个以上执行语句,但可以不必用花括号括起来,会自动顺序执行本case标号后面所有的语句。当然加上花括号也可以。
评:到底想表达什么?

例4.7
评:毫无意义的例题
1.根本就不是完整的代码
2.用到了根本没介绍的知识(函数定义等)
3.风格拙劣,写出的代码中也有很多错误,例如 intx
问题本身就描述的不完整,讲解部分完全是空对空的纸上谈兵
4.8 选择结构程序综合举例
P105
4.8 选择结构程序综合举例
评:居然一口气把判断闰年的问题写了四遍,不可思议
完全看不出那样做有什么意义
没有分析各种写法的利弊得失
几个写法都不咋地
尤其是第四个,简直是为赋新词强说愁——仅仅是为了用一下C99的_Bool类型,其余的都和第一个写法完全一样。写完之后无法编译,又建议读者把.c改成.cpp,简直是笑话。C++和C根本就是两回事,披着羊皮的狼不还是狼吗

P106
#include <stdio.h>
int main()
{
int year,leap;
printf(“enter year:”);
scanf(“%d”,&year);
if(year%40)
{
if(year%100
0)
{
if(year%400==0)
leap=1;
else
leap=0;
}
else
leap=1;
}
else
leap=0;
if(leap)
printf("%d is “,year);
else
printf(”%d is not ",year);
printf(“a leap year.\n”);
return 0;
}

评:这段代码没有错误,但风格很差,而且啰嗦

1.leap=1; leap=0; 是很差劲的写法,可读性差且容易出错。应该使用符号常量

2.第7~20行,啰嗦累赘。若在此之前给leap赋值,代码和逻辑则都要简洁得多。

3.第21~25行,更是似是而非。若不希望写重复的代码,至少应该把"%d is “部分写在if语句外面,即:
printf(”%d is ",year);
if(leap==0)
printf("not ");
printf(“a leap year.\n”);

但这也不是最好的写法,实际上还有更好的写法。

下面是我改写的代码
#include <stdio.h>
#define LEAP 1
#define NOT_A_LEAP 0
int main( void )
{
int year,leap = NOT_A_LEAP ;
printf(“enter year:”);
scanf(“%d”,&year);
if( year % 4 == 0 )
if( year % 100 == 0 ){
if( year % 400 == 0 )
leap = LEAP ;
}
else {
leap = LEAP ;
}

printf( “%d is %sa leap year.\n” , year , ( leap == LEAP )?“”:"not ");

return 0;
}

P108~109
例 4.9 求ax^2+bx+c=0方程的解。
#include <stdio.h>
#include <math.h>
int main( )
{
double a,b,c,disc,x1,x2,realpart,imagpart;
scanf(“%lf,%lf,%lf”,&a,&b,&c);
printf(“The equation”);
if(fabs(a)<1e-6)
printf(“is not a quadratic\n”);
else
{
disc=bb-4ac;
if(fabs(disc)<1e-6)
printf(“has two equal roots:%8.4f\n”,-b/(2
a));
else
if(disc>1e-6)
{
x1=(-b+sqrt(disc))/(2a);
x2=(-b-sqrt(disc))/(2
a);
printf(“has distinct real roots:%8.4f and %8.4f\n”,x1,x2);
}
else
{
realpart=-b/(2b);
imagpart=sqrt(-disc)/(2
a);
printf(“has complex roots:\n”);
printf(“%8.4f+%8.4fi\n”,realpart,imagpart);
printf(“%8.4f-%8.4fi\n”,realpart,imagpart);
}
}
return 0;
}

评:书上说输入1,2,1
输出为 The equationhas two equal roots: -1.0000
这没什么问题

可是输入 1e-8,2e-8,1e-8 时
输出居然是 The equation is not a quadratic就说不过去了吧?
P109~110
例4.10 运输公司对用户计算费用。路程(skm)越远。每吨·千米运费越低。标准如下
s<250 没有折扣
250< s<500 2%折扣
500< s<1000 5%折扣
1000< s<2000 8%折扣
2000< s<3000 10%折扣
3000≤ s 15%折扣
……
(代码略)
评:此题目根本就不应该用swtitch语句解决
P111
float p,w,d,f;
(3)变量名尽量采用“见名知意”的原则,……,在本书的例题程序,由于是练习程序,并且考虑到多数读者的习惯和方便,尽量不采用较长的变量名,而用单词的首字母或缩写作为变量名。在读者今后编程时,可根据实际情况决定。
评:实际是在用垃圾风格熏陶读者,使读者耳濡目染地养成坏习惯
口口声声“考虑到多数读者的习惯和方便”是文过饰非,为自己的垃圾风格辩解
良好的风格习惯不是一下子就能养成的
相反,养成了坏习惯就很难改掉

(4)第6行“printf(“please enter price,weight,discount:”);”的作用是向用户提示应输入什么数据,以方便用户使用,避免出错,形成友好界面。建议读者在编程序(尤其是供别人使用的应用程序)也这样做,在scanf函数语句输入数据前,用printf函数语句输出必要的“提示信息”。
评:1.身教胜于言教,除了这个例题,老谭自己做到没有?赤裸裸的scanf到处都是,教别人去做,有点不给力吧?

2.代码中写的是
printf(“please enter price,weight,discount:”); //提示输入的数据
scanf(“%f,%f,%d”,&p,&w,&s) ; //输入单价、重量、距离

英语不好,硬是没看懂

3.“ scanf函数语句”,“printf函数语句”,估计是C99的新概念

第5章 循环结构程序设计
P116
循环体如果包含一个以上的语句,应该用花括号括起来,作为复合语句出现。如果不加花括号,则while语句的范围只到while后面第一个分号处。
评:while(EXP1)
if(EXP2)
S1 ;
else
S2 ;
怎么说?

sum=sum+i;
评:太像BASIC了
应该写成 sum += i ;
5.3 用do…while语句实现循环
P117
do
语句
while(表达式);
其中的“语句”就是循环体。它的执行过程可以用图5.4表示。
评:图5.4的流程图居然没有入口
太有“创意”了

                                     100                     

例5.2 用do…while语句求1+2+3+……100,即∑n
n=1
评:这个题目用do…while语句来解决属于头脑不清醒,思维呈发散性混乱状态

我也同意应该用for语句。
编程应该用恰当的语句描述算法,而不是扭曲思想去适应语句。编程不是数学上的一题多解,不是花样越多越好,这方面软件史上有过深刻的教训

P118
#include <stdio.h>int main()

对同一个问题可以用while语句处理,也可以用do…while语句处理。do…while语句结构可以转换成while结构。
评:严重的误导
结构换来换去只说明思路上模糊不清

在一般情况下,用while语句和用do…while语句处理同一问题时,若二者的循环体部分是一样的,那么结果也一样。
评:这是瞪着眼睛说胡话
完全不具备起码的逻辑思维能力
P118~119
例5.3 while和do……while循环比较
评:无厘头式的比较
结论毫无意义
5.4 用for语句实现循环
P120
而且for语句更为灵活,不仅可以用于循环次数已经确定的情况,还可以用于循环次数不确定而只给出循环结束条件的情况。它完全可以代替while语句。
评:令人啼笑皆非
既生瑜何生亮
老谭的意思是K&R发明C时既然设置了for语句再设置while语句实在是多余
P121
for(i=1;i<=100;i++)
sum=sum+i;

(2)"表达式1"可以省略……
i=1;
for(;i<=100;i++)sum=sum+i;
评:因为不懂
所以把恶劣的编程风格作为知识讲授

for语句的一般形式
for(表达式1;表达式2;表达式3)语句
可以改写为while循环的形式:
表达式1;
while 表达式2
{
语句
表达式3;
}
二者无条件等价。
评:1.while 语句居然没有()
2.老谭大概忘记C语言还有个continue语句吧

P122
(3)"表达式2"也可以省略……
for(i=1;;i++)sum=sum+i;
评:阉割for语句的“大师”
整个121,122,123页都是在做这种无聊的阉割
P123
可见for语句比while语句功能强,
评:胡扯
这表明老谭根本不懂得各种循环语句究竟应该怎么应用

while(1)rintf(“%d\n”,i);

for(i=0,j=100;i<=j;i++,j++)k=i+j;
表达式1和表达式3都是逗号表达式,各包含两个赋值表达式,

(9)表达式2一般是关系表达式(如i<=100)或逻辑表达式(如a<b&&x<y),但也可以是数值表达式或字符表达式,
评:典型的谭体句型
仿谭体造句:一般是土豆或洋葱,但也可以是植物或辣椒
P124
for(;(c=getchar())!=‘\n’ ; )
printf(“%c”,c);
for语句中只有表达式2,而无表达式1和表达式3.其作用是每读入一个字符后立即输出该字符,直到输入一个“换行”字符。
评:1.实现那个for语句的功能,应该
while( ( c = getchar() ) != ‘\n’ )

     putchar(c); 

2.功能描述错误
所描述的功能实际是
do
putchar( c=getchar() );
while( c != ‘\n’ );

C语言的for语句比其他语言(如FORTRAN,Pascal)中的for语句功能强得多。
评:无病呻吟
比较的标准是什么?
搞不清想要传递给读者什么信息
C的for语句比C++的for语句功能也强得多?
5.5 循环的嵌套

P124~125
5.5 循环的嵌套
评:毫无意义地罗列了6种循环嵌套的结构
还写错了一种
do
{…
do
{…}
while()
}while()

这个反映了老谭基本功
5.6 几种循环的比较
P125
3种循环都可以用来处理同一问题,一般情况下它们可以互相替代。
评:严重的误导

for循环可以在表达式3中包含使循环趋于结束的操作,甚至可以将循环体中的操作全部放到表达式3中。因此for语句的功能更强,凡用while循环能完成的,用for循环都能完成。
评:胡扯+误导
5.7 改变循环执行的状态
P126
例5.4 在全系1000学生中,征集慈善募捐,当总数达到10万元时就结束,统计此时捐款的人数,以及平均每人捐款的数目。
评:嗯!
够黑!够狠!
和咱们的XX有得一拼
从题目中根本看不出来若是总数达不到怎么办
看来是志在必得
收钱的事,完不成怎么可以?!

num=7
aver= 14669.71
评:好在学生中有几个大款
问题是
哪怕小学生也不会算出平均每人捐款的数目是14669.71吧?

P127
break语句的一般形式为
break
评:还真是从没见过这种语句

P128~129
例5.6 输出以下4*5的矩阵。
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20

……
编写程序:
#include <stdio.h>
int main( void )
{
int i,j,n=0;
for(i=1;i<=4;i++)
for(j=1;j<=5;j++,n++)
{ if(n%5==0)printf(“\n”);
printf(“%d\t”,i*j);
}
printf(“\n”);
return 0;
}
评:这个代码写得非常愚蠢,功能上也有缺陷。
在教科书中传授这种代码是一种犯罪行为
这种愚蠢的代码能把初学者变成脑残和弱智

在129页愚蠢代码的基础上又毫无意义地分别加上了continue和break
使得愚蠢毫无意义地复杂化,然后让读者分析
复杂的愚蠢代码是最难分析的
而且有害无益
5.8循环程序举例
P131
例5.7 用π/4≈1-1/3+1/5-1/7…公式求的近似值,直到发现某一项的绝对值小于10^6为止(该项不累加)。
评:任何一项都小于10^6

P132
#include <stdio.h>
#include <math.h>
int main()
{
int sign=1;
double pi=0.0,n=1.0,term=1.0;
while(fabs(term)>=1e-6)
{
pi=pi+term;
n=n+2;
sign=-sign;
term=sign/n;
}
pi=pi*4;
printf(“pi=%10.8f\n”,pi);
return 0;
}

评:那个fabs()函数调用毫无必要
n为double类型sign为int类型也莫名其妙
此外pi=pi+term这样的写法纯粹是BASIC语言的写法
这个代码至少应该改成这样
#include <stdio.h>
int main( void )
{
double pi=0.,n=1.,term=1., sign=1.;
while( term >= 1e-6 )
{
pi += sign * term;
n += 2. ;
sign = - sign ;
term = 1. / n ;
}
pi *= 4. ;
printf(“pi=%10.8f\n”,pi);
return 0;
}

在C库函数中,有两个求绝对值的函数
评:错
C99中不是这样
这再次表明老谭这本书“按C语言的新标准C99进行介绍”是骗人的

P133
经过对程序的补充和运行,可以知道在while(fabs(t)>=1e- 6 )时,执行50万次,当while(fabs(t)>=1e- 8 )时,执行循环体5000万次、两者相差100倍,在分别运行以上两种情况下得程序时,可以明显地感觉到后者运行的时间长很多。
评:一堆废话

对于
π/4≈1-1/3+1/5-1/7…
最后一项<1e-6要执行多少次,小学生心算都能算出来,可老谭居然要“经过对程序的补充和运行”才知道
fabs(t):哪冒出来的“t”?
运行时间的估计居然靠实际“感觉”,不知道老谭怎么感觉出相差100倍的

P134
例5.8 求Fibonacci数列的前40个数。
#include <stdio.h>
int main()
{
int f1=1,f2=1,f3;
int i;
printf(“%12d\n%12d\n”,f1,f2);
for(i=1;i<=38;i++)
{
f3=f1+ f2;
printf(“%12d\n”,f3);
f1=f2;
f2=f3;
}
return 0;
}

评:for语句中的 “38”非常蹩脚
属于莫名其妙的常数

P135
#include <stdio.h>
int main()
{
int f1=1,f2=1;
int i;

for(i=1;i<=20;i++)
{
printf(“%12d%12d”,f1,f2);
if(i%20)printf(“\n”);
f1=f1+f2;
f2=f2+f1;
}
return 0;
}
评:这个代码中的那个20很烂
if(i%2
0)printf(“\n”); 十分蹩脚,风格也极其差劲

例5.9 输入一个大于3的整数n,判定它是否为素数(prime,又称质数)
评:居然有这样的程序,只能判断大于3的整数是不是素数
这东西交给客户时怎么说呢?
我这个程序能判断3以上的素数
3以下的还得您自己亲自判断

#include <stdio.h>
int main()
{int n,i;
printf(“please enter a integer number,n=?”);
scanf(“%d”,&n);
for(i=2;i<=n-1;i++)
if(n%i==0)break;
if(i<n)printf(“%d is not a prime number.\n”,n);
else printf(“%d is a prime number.\n”,n);
return 0;
}

评:这风格!山奔海立的,自成一派
if(i<n)printf(“%d is not a prime number.\n”,n);
else printf(“%d is a prime number.\n”,n);
写的无比笨拙

P137
图 5.19
k<√n
评:应为k=√n

#include <stdio.h>
#include <math.h>
int main()
{int n,i,k;
printf(“please enter a integer number,n=?”);
scanf(“%d”,&n);
k=sqrt(n);
for(i=2;i<=k;i++)
if(n%i==0)break;
if(i<=k)printf(“%d is not a prime number.\n”,n);
else printf(“%d is a prime number.\n”,n);
return 0;
}

评:k=sqrt(n);试图求出n的平方根的整数部分
但这点是得不到保证的
另一个问题是
题目要求“输入一个大于3的整数n”
但无论输入的整数是否大于3
程序都会给出结果
输入一个负数的话还会亲眼目睹程序崩溃的壮烈景观

P137~138
例5.10 求100~200间的全部素数。
#include <stdio.h>
#include <math.h>
int main()
{int n,k,i,m=0;
for(n=101;n<=200;n=n+2)
{k=sqrt(n);
for(i=2;i<=k;i++)
if(n%i0)break;
if(i>=k+1)
{printf(“%d”,n);
m=m+1;
}
if(m%10
0)printf(“\n”);
}
printf(“\n”);
return 0;
}
评:1.逻辑不清,到底怎样算“100~200间”,“n=101;n<=200”无论怎么解释都是错的
2.k=sqrt(n);,见1363楼
3.if(i>=k+1),很不清醒的画蛇添足,实际应该是if(i>k)
4.if(m%100)printf(“\n”); 不仅笨拙,而且如果n的初值不是素数的话会输出若干丑陋的空行
5.风格很烂
if(m%10
0)printf(“\n”);
if(n%i==0)break;
{int n,k,i,m=0;
{k=sqrt(n);
都属于很烂的风格
n=n+2
m=m+1
这种写法简直就不是C语言,而是BASIC等语言的写法

P138
(1) 根据常识,偶数不是素数,所以不必对偶数进行判断,只对奇数进行检查。故循环变量n从101开始,每次增值2。
评:这里至少有三个问题
1.200也是偶数,为什么要n<=200,和这段解释自相矛盾。
2.这种代码根本没有通用性,如果是求另一个范围内的素数,代码需要改动的地方很多。
3.“根据常识,偶数不是素数”,这是常识性的错误。小学生都知道错在哪里

(2)从附录F可以看到:sqrt是求平方根的函数,它要求参数为双精度数。在执行时会自动将整数n转换为双精度数。求出的函数值也是双精度数,再把它赋值给整型变量k,系统会自动小数部分舍弃,只把整数部分赋给k。在进行编译时,系统给出警告,提醒用户有可能出现误差。只要用户确认没有问题,可以不理会它。
评:1.用整数类型做对应原型中类型为double的实参,不规矩(当然不是不可以)
2.“系统会自动小数部分舍弃”,语文问题
3.用户不可能确认没有问题
4.教人不理会警告,是一种教唆行为。优秀的程序员不会容许任何警告,哪怕是无害的情况。更何况这里的警告是一个让人放心不下的警告。
P138~139
例5.11 译密码……
将字母A变成字母E,a变成e,即变成其后的第4个字母,W变成A,X变成B,Y变成C,Z变成D,
……#include <stdio.h>
int main()
{char c;
c=getchar();
while(c!=‘\n’)
{if((c>=‘a’&&c<=‘z’)||(c>=‘A’&&c<=‘Z’))
{if( c>=‘W’&&c<=‘Z’||c>=‘w’&&c<=‘z’)c=c-22;
//如果是26个字母中最后4个字母之一就使c-22
else c=c+4;
}
printf(“%c”,c);
c=getchar();
}
printf(“\n”);
return 0;
}

评:风格太烂
P139~140
程序改进:
#include <stdio.h>
int main()
{char c;
while((c=getchar())!=‘\n’)
{if((c>=‘A’&&c<=‘Z’)||(c>=‘a’&&c<=‘z’))
{ c=c+4;
if( c>=‘Z’&&c<=‘Z’+4|| c>‘z’)
c=c-26;
}
printf(“%c”,c);
}
printf(“\n”);
return 0;
}
评:确实有点改进
可惜改错了

第6章 利用数组处理批量数据
6.1 怎样定义和引用一维数组
P142
由于计算机键盘只能输入有限的单个字符而无法表示上下标,C语言就规定用方括号中的数字来表示下标,
评:胡扯
P143
如果在被调用的函数(不包括主函数)中定义数组,其长度可以是变量或非常量表达式。如:
void fun(int n)
{
int a[2*n]; //合法,n的值从实参传来
……
}
评:胡扯

如“int a[n];”是不合法的。也就是说,C语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值。
评:可笑的是这段文字与1403楼引用的文字出现在同一页上
还没翻页就开始自己打自己耳光

int a[10];
它表示定义了一个整型数组,数组名为a,此数组有10个整型元素。
评:根据该书43页对数据类型的划分
整型数据至少包括6种以上的数据类型
所以,所谓的“数组有10个整型元素”中的整型元素属于指代不明

定义一维数组的一般形式为
类型符 数组名[常量表达式];
评:无论从C90还是C99标准来说都是错误的

从C90来说,“常量表达式”是不准确的,不是任何常量表达式都可以
从C99来说,并没有要求一定是“常量表达式”

常量表达式中可以包括常量和符号常量
评:标准的谭氏废话句型
这话放小学生作文里是要得红叉叉的

不能包括变量,如“int a[n];”是不合法的。也就是说,C语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值。例如下面这样定义数组是不行的:
int n;
scanf(“%d”,&n) ; //企图在程序中临时输入数组的大小
int a[n];
评:已经说了是“常量表达式”,“不能包括变量”是废话
此外,老谭号称他这本书依据C99写的,如“int a[n];”是不合法的 。这句是完全错误的。

这段代码在支持C99的编译器下没有问题。
自相矛盾的是,在同一页,老谭又写到

如果在被调用的函数(不包括主函数)中定义数组,其长度可以是变量或非常量表达式。如:
void fun(int n)
{
int a[2n]; //合法,n的值从实参传来
……
}
评:刚说完[]内只能是“常量表达式”,一转眼又说int a[2
n]; //合法
这种自相矛盾带给初学者的不止是错误还有混乱和无所适从
如果按C90这段论说是绝对错误的
如果按C99来说这段说法依然是错误的,因为main()与其他函数相比除了是程序开始执行的位置之外没有什么特殊的,“不包括主函数”的说法完全是不懂装懂的信口开河,没有任何根据

在调用func函数时,形参n从实参得到值。这种情况称为“可变长数组”,允许在每次调用func函数时,n有不同的值。
评:又在胡扯,
“可变长数组”根本不是像他说的那样

但是在执行函数时,n的值是不变的
评:n是变量,怎么就不变了呢

P144
6.1.2 怎样引用一维数组

6.1.2 怎样引用一维数组
在定义数组并对其中的各元素赋值后,就可以引用数组中的元素。
评:赋值后就可以引用
赋值时呢?
难道不是引用?

例如下面的赋值表达式包含了对数组元素的引用:
:a[0]=a[5]+a[7]-a[2*3]
每一个数组元素都代表一个数值。
评:”:” 印刷错误

每一个数组元素都代表一个数值:错!这个表达式中的数组元素并非都代表数值

例6.1
int i,a[10];
for(i=0;i<=9;i++)
a [ i ] = i ;
评:i<=9 是业余写法

P147
例6.3 有10个地区的面积,要求对它们按由小到大的顺序排列。
评:这算题吗?
条件本身就不全,怎么可能完成呢?
在题目条件不充分的条件下给出代码
是在鼓励程序员胡乱猜测程序功能并示范如何进行胡猜
而程序员擅自胡乱猜测程序功能是与软件工程的思想和基本原则背道而驰格格不入的
所以就不能理解老谭这书究竟是在教别人写程序
还是在诱导教唆学习者违背并践踏写程序的基本原则
6.2 怎样定义和引用二维数组
P149
二维数组常称为矩阵(matrix)。
评:概念混乱的惊人
一个是程序设计语言中的概念
另一个是数学中概念

二维数组定义的一般形式为
类型说明符 数组名[常量表达式][常量表达式];
评:两个“常量表达式”给人貌似相同的错觉
另外“常量表达式”这个描述也不准确,不是所有的常量表达式都可以

P150
二维数组元素的表示形式为
数组名[下标][下标]
评:两个“下标”没有区别,不妥

数组元素可以出现在表达式中,也可以被赋值
评:谭氏废话
相当于什么也没说
其实只要知道数组元素本身也是表达式
那些都是不言而喻的
P153
例 6.4
#include <stdio.h>
int main()
{
int a[2][3]={{1,2,3},{4,5,6}};
……
for(i=0;i<=1;i++)
{
for(j=0;j<=2;j++)
{
printf(“%5d”,a [ i ][j]);
……
}
}
……
return 0;
}
评:这是一种似是而非的外行写法
会给初学者带来很坏的影响

至少应该写成
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
printf(“%5d”,a[ i ][j]);
……
}
}

更好的写法是用sizeof运算求出数组的尺寸

printf(“max=%d\nrow=%d\ncolum=%d\n”,max,row,column);
评:密密麻麻
风格太差

6.3 字符数组
P154

前已介绍:字符型数据是以字符的ASCII代码存储在存储单元中的,一般占一个字节。由于ASCII代码也属于整数形式,因此在C99标准中,把字符类型归纳为整型类型中的一种。评:短短一段话有多处错误
1.“字符型数据是以字符的ASCII代码存储在存储单元中的”,未必
2.“一般占一个字节”,没什么一般特殊而言,char类型就是占一个字节
3.“由于ASCII代码也属于整数形式,因此在C99标准中,把字符类型归纳为整型类型中的一种。”,压根就没有这种因为所以。
此外C89中字符数据就属于整数类型
4.“整型”,这个概念和书里出现的其他“整型”概念不一致。

字符串是存放在字符型数组中的。
评:“ABC”
这样的字符串存放在哪个数组中?

P154~155
用来存放字符数据的数组是字符数组。
……
也可以用整型数组它存放字符数据,例如
int c[10]; //合法,但浪费存储空间
c[0]=‘a’;
评:1.自相矛盾。按照第一句的说法, c也是字符数组
2.第二句话是病句
3.int c[10];未必属于浪费存储空间
P156
例 6.6 输出一个已知的字符串。
例 6.7 输出一个菱形图。
评:谭书的很多例题都是这样没头没脑
如果不看输出结果
你根本不知道他这题目究竟想做什么

例 6.6 输出一个已知的字符串。
解题思路:先定义一个字符数组,并用“初始化列表”对其赋以初值。然后逐个输出此字符数组中的字符。
评:“解题思路”倒是更像一个具有具体要求的题目
而“输出一个已知的字符串”根本不成其为题目
矛盾的是“解题思路”和输出字符串根本扯不上边
如果输出一个字符串
puts()和printf()都很容易完成
根本用不着舍近求远地去定义字符数组
况且解题思路中没有丝毫字符串的影子
如果看下代码
就会发现代码和题目之间驴唇不对马嘴
编写程序:
#include <stdio.h>
int main()
{ char c[15]={‘I’,’ ‘,‘a’,‘m’,’ ‘,‘a’,’ ‘,‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’,’.'};
int i;
for(i=0;i<15;i++)
printf(“%c”,c[ i ]);
printf(“\n”);
return 0;
}

例 6.7 输出一个菱形图。
解题思路:先画出一个如图6.12所示的平面菱形图案。
评:题目本身没头没脑
没有人能解这样的题目

图6.12所画的也不是菱形图案

编写程序:
……
char diamond[][5]={{’ ‘,’ ‘,’‘},{’ ‘,’‘,’ ‘,’‘},{’‘,’ ‘,’ ‘,’ ‘,’‘},
{’ ‘,’
‘,’ ‘,’‘},{’ ‘,’ ‘,’'}}
……
评:初始化的风格很差。

P156~157
例6.6就是用一个一维的字符数组来存放字符串"I am a student."的
评:然而 例6.6 写的却是
char c[15]={‘I’,’ ‘,‘a’,‘m’,’ ‘,‘a’,’ ‘,‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’,’.'};

P157
为了测定字符串的实际长度,C语言规定了一个“字符串结束标志”。
评:C语言确实规定了一个“字符串结束标志”,但并不是“为了测定字符串的实际长度”。所谓“为了测定字符串的实际长度”,属于老谭自己的臆测

ASCII码为0的字符不是一个可以显示的字符,而是一个“空操作符”
评:“字符”怎么成了“操作符”?
概念严重不清

printf(“How do you do?\n”);
在执行此语句时系统怎么知道应该输出到哪里为止呢?实际上,在向内存中存储时,系统自动在最后一个字符’\n’的后面加了一个’\0’,……
评:又见“系统”
“系统”是老谭最得心应手的挡箭牌
说不清楚的事情统统都推给含混不清的“系统”
相信老谭自己也解释不清这两个系统究竟是什么,是不是同一个意思

char c[]=“I am happy”;
这里不像例6.6 那样用单个字符作为字符数组的初值,而是用一个字符串(注意字符串的两端是用双撇号而不是单撇号括起来的)作为初值。
评:1.单个字符不可能作为字符数组的初值
2.“用单个字符作为字符数组的初值”与同页上“例6.6就是用一个一维的字符数组来存放字符串"I am a student."的”自相矛盾
3.用字符串作为数组的初值同样说不通

p160
char str1[5],str2[5],str3[5];
scanf(“%s%s%s”,str1,str2,str3);
输入数据:
How are you?
由于有空格字符分隔,作为3个字符串输入。在输入完后,str1,str2,str3数组的状态如下:
H o w \0 \0
a r e \0 \0
y o u ? \0
数组中未被赋值的元素的值自动置’\0’。
评:荒唐透顶
这又是老谭自己的臆测

char str[13];
scanf(“%s”,str);
如果输入以下12个字符:
How are you?
由于系统把空格作为输入的字符串之间的分隔符,因此只将空格前得字符“How”送到str中。由于把“How”作为一个字符串处理,故在其后加’\0’。str数组的状态为
H o w \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
评:不能因为新手无知就这样糊弄他们吧

可以用下面的输出语句得到数组的起始地址。
printf(“%o”,c);
评:老谭在前面反复强调“C语言本身不提供输入输出语句”(例如第12页)
然后又反复地自己打自己耳光(例如第59页:最常用的语句是:赋值语句和输入输出语句)
这里居然又出现了“输出语句”
如此地颠三倒四
令人简直无语

其次,用%o格式输出c是错误的

2、即使是在C语言中,事实上也容许非C风格字符串(的存储实现),虽然并非C语言标准定义的——例如BSTR。
评:如果是这样,前提是必须给出“字符串”的一般性定义
但老谭的书中并没有给出“字符串”的一般定义
倒是说过“字符串(以’\0’结束的字符序列)”这样的话(161页)
所以可见谭书所说的字符串并非一般意义上的、非C标准定义的字符串

p161
1.puts函数——输出字符串的函数
其一般形式为
puts(字符数组)
评:把puts的参数说成“字符数组”是不对的
(考虑到这里还没有学习指针,把gets的参数说成是字符数组还马马虎虎)

由于可以用printf函数输出字符串,因此puts函数用得不多。
评:这个属于井蛙之见
也表明谭不懂得在何种场合如何恰当地使用puts()函数

用puts函数输出的字符串中可以包含转义字符。例如:
char str[]={“China\nBeijing”};
puts(str);
评:废话
字符串中的字符可以用转义字符的形式写出
跟puts()函数有什么关系

那个例子中的str是多余的
其实可以直接写
puts(“China\nBeijing”);
看来老谭真的以为puts()函数的参数只能是“字符数组”

p162
3.strcat函数——字符串连接函数
其一般形式为
strcat(字符数组1,字符数组2)
评:实际上这两个参数都必须是字符串
第二个参数不必是保存在字符数组中的字符串

7.strlwr函数——转换为小写的函数
其一般形式为
strlwr(字符串)
strlwr是STRing LoWeRcase(字符串小写)的缩写。函数的作用是将字符串中的大写字母换成小写字母。
评:不知道老谭哪儿搞来的这东西
这个函数根本就不是标准函数

从功能上来说
这个函数的参数也不可能是“字符串”,只能是存放在数组中的字符串

p163
5)可以用strncpy函数将字符串2中前面n个字符复制到字符数组1中去。例如:
strncpy(str1,str2,2);
作用是将str2中最前面两个字符复制到str1中,取代str1中原有的最前面2个字符。但复制的字符个数n不应多于str1中原有的字符(不包括’\0’)。
评:对照一下C标准的说法:
The strncpy function copies not more than n characters (characters that follow a null
character are not copied) from the array pointed to by s2 to the array pointed to by
s1. If copying takes place between objects that overlap, the behavior is undefined.
If the array pointed to by s2 is a string that is shorter than n characters, null characters
are appended to the copy in the array pointed to by s1, until n characters in all have been
written.
不难发现谭的说法漏洞百出

p164
7.strlwr函数——转换为小写的函数
其一般形式为
strlwr(字符串)
strlwr是STRing LoWeRcase(字符串小写)的缩写。函数的作用是将字符串中的大写字母换成小写字母。
评:不知道老谭哪儿搞来的这东西
这个函数根本就不是标准函数

从功能上来说
这个函数的参数也不可能是“字符串”,只能是存放在数组中的字符串

p165
库函数并非C语言本身的组成部分,而是C语言编译系统为方便用户使用而提供的公共函数。
评:看来C标准完全可以把库函数部分删除了
这会节约很多纸张

p165~166
例6.8 输入一行字符,统计其中有多少个单词,单词之间用空格分隔开。
#include <stdio.h>
int main()
{
char string[81];
int i,num=0,word=0;
char c;
gets(string);
for(i=0;(c=string[ i ])!=‘\0’;i++)
if(c==’ ')word=0;
else if(word==0)
{word=1;
num++;
}
printf(“There are %d words in this line.\n”,num);
return 0;
}
评:这是老谭书中最好的代码
模仿的是K&R的代码
不过学的不怎么样
毛病很多
首先,c变量是多余的
81这个常数也很怪异
第三,老谭在165页说过使用gets应当在程序开头用 #include <string.h>
但在这里却带头违背
第四 与该代码对应的“N-S流程图”(图6.18)不伦不类且有非常明显的几处错误

欣赏一下K&R的代码
不难发现老谭1538楼的代码如何点金成铁
#include <stdio.h>
#define IN 1
#define OUT 0
main()
{
int c,nl,nw,nc,state;
state = OUT ;
nl = nw = nc = 0 ;
while((c = getchar())!= EOF){
++nc;
if( c == ‘\n’)
++nl;
if( c ==’ ’ || c ==‘\n’ || c ==‘\t’)
state = OUT ;
else if(state == OUT){
state = IN ;
++nw;
}
}
printf(“%d %d %d\n”, nl , nw , nc );
}

K&R 用的标识符是state,老谭用的是word
K&R 用了两个漂亮的#define , 老谭用的是丑陋的0,1
风格方面
K&R :
else if(state == OUT){
state = IN ;
++nw;
}
老谭:
else if(word==0)
{word=1;
num++;
}

p167
C语言把赋值运算作为表达式
评:晕

例 6.9
图 6.19
评:这个图是错误的
而且和题目不相符

解题思路:……然后经过3次两两比较……
评:代码中并没有经过3次两两比较

p168
printf(“\nthe largest string is:\n%s\n”,string);
评:代码丑了点
太爱用printf了
其实可以写为
puts(“\nthe largest string is:”);
puts(string);

运行结果:
Holland
China
America

the largest string is:"
Holland
评:然而说明该例题的图6.19中却是
China
Japan
India
没想到结果居然冲出了亚洲走向了世界

第7章 用函数实现模块化程序设计
7.1 为什么要用函数
p170

7.1 为什么要用函数
评:讲老实话,我不清楚什么叫“模块化程序设计”
老谭也没讲清楚究竟什么叫“模块化程序设计”

看来以其昏昏使人昭昭是不行的

注意:函数就是功能。
评:这是个很滑稽的解释
把英汉词典上的两个义项硬说成是一个
如果这个逻辑成立
那么也完全可以说
(教科书的)谬误(error)就是罪孽(error)或犯罪(error)

图7.1是一个程序中函数调用的示意图。
评:只是一个三叉树而已
无法示意函数调用
函数调用可能比这复杂得多

p171
#include <stdio.h>
int main()
{ void print_star();
void print_message();
print_star();
print_message();
print_star();
}
void print_star()
{
printf(“******************\n”);
}

void print_message()
{printf(“How do you do!\n”);
}

运行结果:


How do you do!

评:把函数原型写在main()之内
是误人子弟的写法

此外函数原型写的要么可以说是错误的,要么就是毫无意义

运行结果是伪造的

在定义这两个函数时指定函数的类型为void,意为函数无类型。
评:函数无类型的说法极其荒谬
C语言中不存在无类型的函数

指定函数的类型为void
应为指定函数返回值的类型为void

一个源程序文件可以为多个C程序共用
评:令人啼笑皆非的说法
p172
C程序的执行是从main函数开始的,如果在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行。
评:这段文字有些莫名其妙,即使从汉语语法的角度来说也有些成问题。成问题的地方在于“在调用后流程返回到main函数”不清楚是前面“如果”的一个结果还是对前面“如果”的一个补充。
抛开这种语文方面的缺点不谈,这段文字给人的印象就是“C程序从main函数开始执行,在main函数结束”。然而事实真的如此吗?
C程序的运行离不开一定的环境(Environment),这种环境叫做执行环境(Execution environment)。运行环境有两种:独立环境(Freestanding environment)和宿主环境(Hosted environment)。
所谓独立环境是指程序并非借助操作系统来运行的,宿主环境则是指程序是在操作系统的控制下执行的。
在这两种环境下,程序开始运行(Program startup)的标志是程序的某个指定的函数开始被调用。
在独立环境中,首先被调用的函数的名字并不一定是main,而是由编译器自行确定的,这叫做由实现定义(Implementation-defined)。甚至这个首先被调用的函数的类型也同样是由实现定义的。
只有在宿主环境下,C程序的运行才是从main()开始的。
因此,“C程序的执行是从main函数开始的”这句话仅仅是在一定的条件下才成立,是片面的。
至于程序在哪个函数结束,C语言从来没有任何规定。程序可能在main()中结束,也可能在其他函数中结束。C语言标准库中有很多函数都与结束程序有关,这些函数的函数原型在stdlib.h中描述,例如
void abort ( void ) ;
void exit ( int ) ;

下面的代码就是一个并非在main()中结束程序的简单示例:
#include “stdlib.h”
void fun ( void ) ;
int main ( void ){
fun();
return 0;
}
void fun ( void ){
exit(1);
}

它是在fun()函数中结束的。

p172
无参函数可以带回也可以不带回函数值,但一般以不带回函数值得居多。
评:毫无意义的废话

所有的函数都有函数值,只是有的函数的返回值是void类型

②有参函数。在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用。
评:思维混乱
居然从有参函数扯到了函数返回值
有参和返回值没关系

例1.3的max函数定义为int型。
评:函数 为 int型
完全不理解数据类型的含义

7.2怎样定义函数
p172
7.2.1为什么要定义函数
C语言要求,在程序中用到的所有函数,必须“先定义,后使用”。例如想用max函数求两个数中的大者,必须事先按规范对它进行定义,指定它的名字、函数返回值类型、函数实现的功能以及参数的个数与类型,将这些信息通知编译系统。这样,在程序执行max时,编译系统就会按照定义时所指定的功能执行。如果事先不定义,编译系统怎么能知道max是什么、要实现什么功能呢!
评:什么叫“先”、什么叫“后”?
如果是指编辑时的时间顺序,毫无疑问这种说法是错误的。因为先写调用,最后完成再函数定义的情况比比皆是
如果是指源代码的空间次序,这种说法也是错误的。因为在多个源文件情况下,根本就谈不上先后
所以,所谓“先定义,后使用”是一句似是而非的废话

“想用max函数求两个数中的大者”,这也是模棱两可的话,什么叫“两个数中的大者”,“两个数”也是含糊不清的概念

“指定它的名字、函数返回值类型、函数实现的功能以及参数的个数与类型,将这些信息通知编译系统”:搞不清这是在说函数定义还是在说函数声明

“在程序执行max时,编译系统就会按照定义时所指定的功能执行”,“编译系统”居然也能“执行”程序了,不管你们信不信,反正我就这么写了

“如果事先不定义,编译系统怎么能知道max是什么、要实现什么功能呢!”:编译系统可以通过函数原型知道max是什么,编译系统并不需要max要实现什么功能

p173
对于C编译系统提供的库函数,是由于编译系统事先定义好的,库文件中包括了对各种函数的定义。
评:编译系统居然还能“定义”函数
库文件中居然包括函数定义

究竟什么叫“定义函数”?
在p172页谭是这样写的

定义函数应包括以下几个内容:
(1)指定函数的名字,……
……
(4)指定函数应当完成什么样的操作
评:编译系统如何指定函数的名字的呢?
……
又是如何指定函数完成的操作的呢?

哪个库文件中写着函数的定义?

1.定义无参函数
……定义无参函数的一般形式为
类型名 函数名()
{
函数体
}
评:1.这是早就过时的写法,应该避免使用
2.函数体包括{}部分

int max(int x,int y)
{int z;
z=x>y?x:y;
return(z);
}
评:不多定义一个多余的变量心里就没底

p174
3.定义空函数
……
类型名 函数名()
{}
评:1.过时的写法
2.分类不当,结构混乱,造成误导

该小节的结构是

7.2.2 定义函数的方法
1.定义无参函数
……
2.定义有参函数
……
3.定义空函数
……
评:给读者造成函数有无参函数、有参函数、空函数三类函数的错觉

7.3 调用函数
p174~175
7.3.1 函数调用的形式
……
按函数调用在程序中出现的形式和位置,可以有以下3种函数调用方式。
1.函数调用语句
……
2.函数表达式
……
3.函数参数
……
评:完全看不出有必要这样划分
三种形式中的函数调用没有任何本质区别
除非对语句、表达式或实参的概念缺乏准确的理解,否则不可能想到这样划分
因为在三种方式中函数调用都是表达式

p175
调用函数并不一定要求包括分号(如print_star();),只有作为函数调用语句才需要有分号
评:前面说“不一定要求包括分号”,可“如”的却是有分号,混乱不清自相矛盾,即使作为小学生作文也是不合格的
“只有作为函数调用语句才需要有分号”更是胡扯
for(i=0;i<strlen(s);i++)这个怎么算?

实际上函数调用和分号扯不上半点关系
老谭把他们放在一起说纯属概念混乱纠缠不清

实际参数可以是常量、变量或表达式。
评:就如同说
应该多吃青菜、白菜和蔬菜
可以看出老谭连什么是表达式都不清楚

p176
在调用函数过程中发生的实参与形参间的数据传递,常称为“虚实结合”
评:这回没用BASIC冒充C
改用FORTRAN冒充了
为此还特意捏造了一个“虚拟参数”的概念

p177
实际参数可以是常量、变量或表达式。
评:参见1763楼
(就如同说
应该多吃青菜、白菜和蔬菜
可以看出老谭连什么是表达式都不清楚)

如果说不懂指针就不完全懂得C的话
那么不懂表达式就完全不懂得C语言

很不幸
老谭属于后者

如果实参为int型而形参x为float型,或者相反,则按不同类型数值的赋值规则进行转换
评:这个是有前提的
按照老谭写函数声明的风格就不会

字符型与int可以互相通用
评:这个是胡扯
不同的字符类型都不能通用
遑论字符类型与int类型

调用结束,形参单元被释放。注意,实参单元仍保留并维持原值,没有改变。
实参和形参在内存中占有不同的存储单元
评:压根就不存在实参单元这种东西

p178
赋值语句把这个函数值赋给变量。
评:然而在该书的第12页、第67页
老谭煞有介事地铁口直断

C语言本身不提供输入输出语句。
评:自己扇自己耳光
一点感觉都没有吗
境界啊

max(int x,int y)
{
return ((x>y)?x:y);
}
评:然而在同一页上居然写着
注意:在定义函数时要指定函数的类型。

例7.3 将例7.2稍作改动,将在max函数中定义的变量z改为float型。函数返回值的类型与指定的函数类型不同,分析其处理方法。
评:例7.2 的要求是“输入两个整数,要求输出其中的大者。要求用函数找到大者。”
相应的max()函数定义是
int max(int x,int y)
{
int z;
z=x>y?x:y;
return(z) ;
}

怎么可以胡乱“将max函数中定义的变量z改为float型”呢?
难道可以不顾要求
仅仅为了要说明一下“函数返回值与指定的函数类型不同,按照赋值规则处理”(注“函数类型”也是个错误的术语)
就胡乱修改代码?

int max(float x,float y)
{
float z;
z=x>y?x:y;
return(z) ;
}
评:这改成什么了呢?函数的功能不伦不类简直无法言说

这是一种很奇怪的无病呻吟
不是因为痒了而去挠
而是为了挠一下故意装得有点痒
更何况return 的表达式与函数返回值的类型不一致本来就是一种很差劲的代码风格

p179
(4)对于不带回值的函数,应当用定义函数为“void”类型(或称“空类型”)。……此时在函数体中不得出现return语句。
评:应当用定义函数为“void”类型 : 这不是话

此时在函数体中不得出现return语句:胡扯

float z; //z为实型变量
评:“实型”是个含义不清的概念

7.4 对被调用函数的声明和函数原型
p180
如果不包含“stdio.h”文件中的信息,就无法使用输入输出库中的函数。同样,使用数学库中的函数。应该用#include <math.h>。
评:这给读者一种错觉
以为编译器提供的库有多个

如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明(declaration)。
评:把函数类型声明放在函数体内是一种拙劣的风格
会造成函数体内代码的污染
并且可能需要多次进行不必要的函数类型声明
“函数的位置”是含混不清的说法
应该是函数定义的位置

p181
如果没有对函数add的声明,当编译到程序第7行时,编译系统无法确定add是不是函数名,
评:编译器可以根据运算符()来确定add是一个函数
尽管应该写函数声明,但不是因为不写编译系统就无法确定add是不是函数名

如果不作检查,在运行时才发现实参与形参的类型或个数不一致,出现运行错误。大体上可猜出这是一个输出学号,性别和成绩的函数。
评:运行时可以产生运行错误
但不可能发现实参与形参的类型或个数不一致

使用函数原型作声明是C的一个重要特点。
评:这个又是在信口开河了
C语言最初并没函数原型这个概念
这是向C++学来的
正因为如此
这方面无论在理论还是实践方面目前都有些混乱
老谭自己不就是把函数声明写得乱七八糟吗
一会用函数原型
一会用过时的函数声明
甚至把许多过时的东西写在书里吗

用函数原型来声明函数,能减少编写程序时可能出现的错误。由于函数声明的位置与函数调用语句的位置比较近,因此在写程序时便于就近参照函数原型来书写函数调用,不易出错。
评:没头没脑
怎么就“由于”了呢?

函数原型是给谁看的?难道是给程序员吗

p182
对函数的“定义”和“声明”不是一回事。函数的定义是指对函数功能的确立,包括指定函数名,函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包括函数体。
评:显著的错误是检查“函数名是否正确”,函数声明不检查这个

对函数的“定义”和“声明”不是一回事:实际上函数定义也具有函数声明的作用。因此就说明函数名性质这个角度来说,函数的定义也是函数声明的一种
如果从狭义的角度来理解的话,即“定义”和“声明”不是一回事,那么后面的叙述——“函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统”就是错误的
这里混淆了函数声明与函数原型的概念

此外,“函数值类型”这个说法是可接受的,但后面又说“函数类型”则是错误的。“函数类型”和“函数值类型”根本就不是一回事

用了有意义的参数名有利于理解程序,如:
void print(int num,char XXX,float score); (注:XXX处的原文可能很黄,发不出去,只好以XXX代替)
大体上可猜出这是一个输出学号,性别和成绩的函数。
评:这种猜测不但是危险的、盲目的
也是违背软件工程基本原则的
放纵这种猜测
必然导致BUG丛生

而且函数原型的目的并不是让人照着写函数调用
其意义主要在于给编译器看

7.5 函数的嵌套调用
p183
7.5 函数的嵌套调用
……
(5)执行b函数,如果再无其他嵌套的函数,则完成b函数的全部操作;
(6)返回到a函数中调用b函数的位置;
(7)继续执行a函数中尚未执行的部分,直到a函数结束;
评:实际上应该是遇到return返回或直到函数结束返回

p184
程序分析:可以清楚地看到,在主函数中要调用max4函数,因此在主函数的开头要对max4函数作声明。……
评:的确“可以清楚地看到”
然而可以更清楚看到的是这种写法把各个函数污染得乱七八糟

程序改进:
(1)……
int max2(int a,int b)
{ return (a>b)?a:b; }
评:看不懂这究竟在是“改进”还是“改退”

(2)在max4函数中,3个调用max2的语句(如m=max2(a,b);)可以用以下一行代替:
m=max2(max2(max2(a,b),c),d);
甚至可以取消变量m,max4可写成

int max4(int a,int b,int c,int d)
{ int max2(int a,int b);
return max2(max2(max2(a,b),c),d);
}
评:max2(max2(max2(a,b),c),d) 显然不如 max2 ( max2(a,b) , max2(c,d) )
而且这一切有什么必要放在max4()里完成呢?
放在main()里难道不是“甚至可以取消”函数max4()吗?
函数max4()难道不是无病呻吟吗
不仅max4()是无病呻吟
max变量,m变量也是如此

通过此例,可以知道,不仅要写出正确的程序,还要学习怎样使程序更加精炼、专业和易读。
评:通过此例,只能看到如何使程序更繁复、业余和晦涩

精炼、专业和易读的代码是这样的
#include <stdio.h>
int max(int ,int );
int main( void )
{
int integer_1 , integer_2 , integer_3 , integer_4 ;
printf( “输入4个整数:” ); //假洋鬼子才写interger
scanf( “%d%d%d%d” ,
&integer_1,&integer_2,&integer_3,&integer_4);
printf( “max=%d\n” ,
max( max(integer_1,integer_2) , max(integer_3,integer_4) ) );
return 0;
}
int max( int i , int j)
{
return ( i > j )? i: j ;
}

7.6 函数的递归调用

C语言的特点之一就在于允许函数的递归调用。
评:这是在没话找话吧
很多语言都“允许函数的递归调用”
这怎么成了“C语言的特点”了呢

p185
int f(int x)
{
int y,z;
z=f(y);
return (2*z);
}
评:这个例子要多烂有多烂
简直是教唆读者如何写烂代码:

  1. x没用
  2. 使用垃圾值y
  3. 无限递归
  4. return 一句是无效代码

p186
int age(int n)
{
int c;
if(n==1)
c=10;
else
c=age(n-1)+2;
return©;
}

评:return©; 中的()很搞笑

那个c是多余的
可能有人觉得是为了单一出口
我个人觉得那很做作
并没有收获到实惠

p188
#include <stdio.h>
int main()
{ int fac(int n);
int n;
int y;
printf(“input an integer number:”);
scanf(“%d”,&n);
y=fac(n);
printf(“%d=%d\n”,n,y);
return 0;
}
int fac(int n)
{
int f;
if(n<0)
printf(“n<0,data error!”);
else if(n0||n1)
f=1;
else f=fac(n-1)*n;
return(f);
}

评:n<0的判断本来应该是main()的任务
却硬塞到了fac()内
得到的是一个毫无意义的怪胎
如果真的输入一个负数
会得到更怪的怪胎

input an integer number:-1
n<0,data error!-1!=65

除了可以让人们观瞻一下代码可以丑陋到何种程度
这个fac()没有任何价值
更可笑的是老谭居然宣称他用这个代码计算出了31!

……当n=31时,运行正常,输出为
input an integer number:31
31!=738197504
评:用脚后跟也应该能想到738197504不是31!的阶乘吧

p191
void hanoi(int n,char one,char two,char three) //定义hanoi函数
//将n个盘子从one座借助two座,移到three座
{
……
}
评:这是一种令人作呕的注释,污染代码
另:该页上的main()缺少return 0;语句

7.7 数组作为函数参数

p192
实参可以是常量、变量或表达式。
评:逻辑错乱
茄子、辣椒或蔬菜

凡是变量可以出现的地方,都可以用数组元素代替
评:int i;
这个 i 变量出现的地方呢?

数组名也可以作实参和形参
评:数组名根本不能作形参

p193
数组元素可以用作函数实参,不能用作形参
评:这个没什么问题
问题在于下面的“因为”

因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元
评:这就是在胡扯了
1.auto 变量都是临时的,不仅仅是形参
2.什么时候也不可能为数组元素单独分配存储单元,这跟“形参是在函数被调用时临时分配存储单元的”有个屁关系

在用数组元素作函数实参时,把实参的值传给形参,是“值传递”方式
评:就如同在说
今天是中秋,早上太阳是从东边出来的
实际上C语言只有值传递
和用什么作实参没有关系

例 7.9 输入10个数,要求输出其中值最大的元素和该数是第几个数,
评:题目本身就很蠢
“输入10个数”,但没明确什么样的数
“元素”是代码层面的术语,却和问题的描述混为一谈

#include <stdio.h>
int main()
{int max(int x,int y);
int a[10],m,n,i;
printf(“enter 10 integer numbers:”);
for(i=0;i<10;i++) //输入10个数给a[0]~a[10]
scanf(“%d”,&a[ i]);
printf(“\n”);
for(i=1,m=a[0],n=0;i<10;i++)
{
if(max(m,a[ i])>m)
{m=max(m,a[ i]);
n=i;
}
}
printf(“The largest number is %d\nit is the %dth number.\n”,m,n+1);
}

int max(int x,int y)
{
return(x>y?x:y);
}

评:对愚蠢的完美诠释
1
根本不需要数组
2
输入10个数给a[0]~a[10]
3
printf(“\n”);
4
if(max(m,a[ i])>m)
{m=max(m,a[ i]);
n=i;
}
调用 max()两次

这里其实可以简洁地写为
for( i = 1 , n = 0 ; i < 10 ; i++ )
if(max(a[n] , a[ i]) > a[n] )
n = i ;
当然这种做法还是比较啰嗦
5
缺少 return 0

p194
当然,本题可以不用max函数求两个数中的最大数,而在主函数中直接用if(m>a[ i])来判断和处理。本题的目的是介绍如何用数组元素作为函数实参

评:用愚蠢的数据结构和愚蠢的算法介绍数组元素作为函数实参
读者最多只能学会怎样愚蠢地用数组元素作实参

用数组元素作实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。

评:这个是误导
不管什么作实参,传的都是“值”,数组名作实参也不例外
此外形参不可能是数组名

#include <stdio.h>
int main( )
{ float average(float array[10]);
float score[10],aver;
int i;
printf(“input 10 scores:\n”);
for(i=0;i<10;i++)
scanf(“%f”,&score[i]);
printf(“\n”);
aver=average(score);
printf(“average score is%5.2f\n”,aver);
return 0;
}

float average(float array[10])
{int i;
float aver,sum=array[0];
for(i=1;i<10;i++)
sum=sum+array[i];
aver=sum/10;
return(aver);
}
评:float average(float array[10])
中的10非常愚蠢

p195
用数组名作函数参数,应该在主调函数和被调函数分别定义数组,例中array是形参数组名,score是实参数组名,分别在其所在函数中定义,不能只在一方定义。

评:没弄清什么叫定义数组

在定义average函数时,声明数组的大小为10,但在实际上,指定其大小是不起任何作用的,
评:不起作用为什么要写?
压根就不该写

形参数组可以不指定大小,在定义数组时在数组名后面跟一个空的方括号,如:
float average(float array[])

评:不指定大小是对的
但不指定数组的尺寸则是误导
写出的函数是半身不遂的函数

例7.11 有两个班级,分别有35名和30名学生,调用一个average函数,分别求这两个班的学生的平均成绩。
评:调用一个average函数,分别求这两个班的学生的平均成绩:病句

实际的代码居然是

float score1[5]=……
float score2[10]=……

巨无聊

拙劣的题目可以伤害正常的逻辑思维能力

p196
用数组名作函数实参,不是把数组元素的值传递给形参,而是把实参数组的首元素的地址传递给形参数组,这样两个数组就共占同一段内存单元。
评:“两个数组”,是一种捏造

用数组名作函数实参,不是把数组元素的值传递给形参,而是把实参数组的首元素的地址传递给形参数组,这样两个数组就共占同一段内存单元。
评:“两个数组”,是一种捏造

p197
可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。例如:
int array[3][10];

int array[][10];
评:写那个3是误导,因为根本没必要
此外,形参声明是根本不可能带那个“;”的,除非是古代的C语言

p198
由于形参数组与实参数组类型相同
评:这个错的没边了
它们的类型压根就不一样

在第2维大小相同的前提下,形参数组的第1维可以与实参数组不同。例如,实参数组定义为
int score[5][10];
而形参数组定义为
int score[][10];

int score[8][10];均可以。
评:这是把无聊当知识卖弄
写那个8毫无意义

这时形参数组和实参数组都是由相同类型和大小的一维数组组成的
评:形参根本就不可能是数组(即使形式上是,但那叫不完整类型)
更不可能由一维数组组成

int max_value(int array[][4])
……
评:这是在教人写半身不遂的函数
这种函数没有价值和适应性
因为它本身依赖于一个莫名其妙的Magic number
7.8 局部变量和全局变量
p199
定义变量可能有3种情况:
(1)在函数开头定义;
(2)在函数内的复合语句内定义;
(3)在函数的外部定义。
评:错
这表明老谭所号称的这本书是“按照C语言的新标准C99进行介绍”是蒙人的
老谭连C99门都没摸到

实际上只有两种可能:函数内和函数外

P200
(4)在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效。这种复合语句也称为“分程序”或“程序块”。
在函数内定义的变量是局部变量,在函数之外定义的变量是全局变量。
评:“分程序”或“程序块”是老谭捏造g的新概念
第一不必要,因为有复合语句的概念,没必要再给它两个别名
第二不合理,因为源程序的下一层是函数定义,再下一层才是复合语句的层次,所以说“复合语句”是分程序非常荒谬
这种复合语句也称为“分程序”或“程序块”。:语文问题。既然是“这种”,就意味着还有“那种”,实际上根本没有。

P201
int p=1,q=5; //定义外部变量。
int c1,c2; //定义外部变量。
p,q,c1,c2都是全局变量,
评:概念不统一

设置全局变量的作用是增加了函数间数据联系的渠道。
评:这个说法违背结构化程序设计原则,是误导

如果在一个函数中改变了全局变量的值,就能影响到其他函数中全局变量的值。
评:“其他函数中全局变量”,这叫什么话?

由于函数的调用只能带回一个函数返回值,因此有时可以利用全局变量来对增加函数间的联系渠道,通过函数调用能得到一个以上的值。
评:这个外行,竟然用这种垃圾理论来蒙学生
完全不顾结构化程序设计最基本的原则

在C程序设计人员中有一个习惯(但非规定),将全局变量名的第个字母用大写表示。
评:有这事儿吗?
从没听说过
老谭捏造也不是第一次了

例7.14 有一个一维数组,内放10个学生成绩,写一个函数,当主函数调用此函数后,能求出平均分、最高分和最低分。
评:题目要求很变态

解题思路:调用一个函数可以得到一个函数返回值,现在希望通过函数调用能得到3个结果。可以利用全局变量来达到此目的。
评:解题思路更变态
P202
#include <stdio.h>
float Max=0,Min=0;
int main()
{ float average(float array[],int n);
float ave,score[10];
int i;
printf(“Please enter 10 scores:”);
for(i=0;i<10;i++)
scanf(“%f”,&score[i]);
ave=average(score,10);
printf(“max=%6.2f\nmin=%6.2f\naverage=%6.2f\n”,Max,Min,ave);
return 0;
}
float average(float array[],int n)
{ int i;
float aver,sum=array[0];
Max=Min=array[0];
for(i=1;i<n;i++)
{if(array[i]>Max)Max=array[i];
else if(array[i]<Min)Min=array[i];
sum=sum+array[i];
}
aver=sum/n;
return(aver);
}

评:代码则令人作呕
这是在教唆别人学坏
那两个外部变量要多恶心有多恶心

由于Max和Min是全局变量
评:C语言只有外部变量,没有全局变量这个概念

建议不在必要时不要使用全局变量,原因如下:
①全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
评:1
全局变量为外部变量之误
2
在程序的全部执行过程中都占用存储单元,并非不应该使用外部变量的原因

在某些语言里,全局变量的意思是只要定义了,其他模块就可见

在C语言中没有这样的东西

外部变量有两种 extern 或 static
前一种其他模块可以用,但在使用前应该声明(这个应该是和全局变量的一个最主要的区别)
后一种其他模块不可以用

使用外部变量,最主要的问题是破坏了良好的程序结构,使程序的各个部分联系更紧密了

如果全部执行过程都占有单元也算一个缺陷的话,那static 局部变量怎么说,也不应该?有些变量确实需要一直占用内存

P203
②它使函数的通用性降低了
评:应该是这样的函数没有通用性

例7.15
评:对全局变量(外部变量)b的作用范围的标示是错误的
7.9 变量的存储方式和生存期
P204
变量可以分为全局变量和局部变量。
评:全局变量是个既不正确也很糟糕的概念
在有的语言里
全局变量的意思是只要定义,就全局可见
但C语言中外部变量并没有这样的性质
首先,外部变量的作用域只是定义点到所在源文件(或编译单元结束)
其他源文件使用前必须声明而不是可以直接拿过来使用
而且有的外部变量(static)其他源文件根本不能使用
即使在所在文件,外部变量也可能需要声明才能使用(定义在使用点后面的情形)
“全局”这两个字含糊掩盖了以上所提到的外部变量的精确含义
对于初学者来说尤其如此

有的变量则是在调用其所在的函数时才临时分配内存单元,而在函数调用结束后该存储单元就马上释放了,变量不存在了。
评:这个说法是错误的
局部非static变量不是在调用其所在函数后存在的
应该是程序执行到其所在块声明点之后才存在的
释放也是如此

变量的存储有两种不同的方式:静态存储方式和动态存储方式
评:这个基本属于捏造
变量的存储方式没什么静态动态之分
变量的生命期有static storage duration 和 automatic storage duration

静态存储方式是指在程序运行期间由系统分配的存储空间的方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配空间的方式
评:“静态存储方式是指在程序运行期间由系统分配的存储空间的方式”
这个是错误的,static storage duration的变量在程序运行前就存在了
这里所谓的“由系统分配”是在打马虎眼,搞不清这是什么系统
如果是编译系统,那么后者也是由系统分配

先看一下内存中供用户使用的存储空间的情况。这个存储空间可以分为3部分:
(1)程序区。
(2)静态存储区。
(3)动态存储区。
评:这个说法没有任何根据,更荒谬的是

在动态存储区存放以下数据:
(1)函数形式参数。在调用函数时给形参分配空间。
(2)函数中定义的没有用关键字static声明的变量,即自动变量。
(3)函数调用时的现场保护和返回值等。
评:(1)、(2)都是错误的,因为还有register
(3)是一种没有根据的猜测或根据个别实现的情况作出的不完全归纳

P205
即长如果用户不指定
静态的(statis)
根据变量的存储类别,可以知道变量的作用域和生存期。
评:变量的存储类别与变量的作用域没有半毛钱关系

C的存储类别包括4种
评:实际上是5种

函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。
评:这个说法是错误的
“动态存储区”是老谭捏造的一块“内存”区域
而非static的局部变量,未必放在内存之中

函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量)……在函数调用结束时就自动释放这些存储空间。
评:块作用域的auto局部变量不是在函数结束时释放,而是在块结束时释放

关键字“auto”可以省略,不写auto则隐含指定为“自动存储类别”
评:错
应该是没声明存储类别的局部变量为自动存储类别

P206
static c=3;
评:老谭号称是按照C99写的
但这种写法在C99是禁止的
一句话,挂羊头卖狗肉

对应的运行结果也是货不对板
7
8
9

评:最后“-”莫名其妙
P207
什么情况下需要用局部静态变量呢?……例如可以用下面的方法求n!。
例7.17 输出1到5的阶乘值。
#include <stdio.h>
int main( void )
{int fac(int n);
int i;
for(i=1;i<=5;i++)
printf(“%d!=%d\n” ,i,fac(i) );
return 0;
}
int fac(int n)
{static int f=1;
f=f*n;
return(f);
}
评:误导
这不是正确使用static 局部变量的方法
因为这个fac()函数和那啥一样
只能一次性使用
甚至连一次性都算不上
如果题目要求求 1、3、5的阶乘
立刻就会发现fac()的残疾
如果函数中的变量只被引用而不改变值,则定义为静态局部变量(同时初始化)比较方便,以免每次调用时重新赋值。
评:既然不改变,为什么不用常量?外部变量等技术手段?
究竟何时使用局部静态变量以及如何使用,看来老谭根本不清楚
所以无论给出的例子还是讲解都是错误的

用静态存储要多占内存(长期占用不释放,而不是像动态存储那样一个单元可以先后为多个变量使用,节约内存)……而且降低了程序的可读性……
评:降低程序可读性是无稽之谈
动态存储节约内存也是无稽之谈

评:注意这里的静态存储与外部还是局部变量无关
局部变量也可以是静态生命期
可笑的是你把静态存储理解为非局部变量

P208
寄存器存储在CPU中的寄存器中
评:这根本就不是话

全局变量都是存放在静态存储区中的。因此它们的生存期是固定的,存在于程序的整个运行过程。
评:1.全局变量是外部变量之误
2.外部变量存放在哪里跟程序员无关,这是编译器作者的事情
3.外部变量的生存期和它存放在哪里没有因果关系
总之一堆错话与废话
实际上只要一句话就足够了:外部变量具有静态存储持续期(static storage duration),在程序运行过程中一直存在。

一般来说,外部变量是在函数外部定义的全局变量。
评:这个胡扯太过分了
如果“外部变量是在函数外部定义的全局变量”
那么老谭能否说说什么是在函数内部定义的全局变量

一般来说,外部变量是在函数外部定义的全局变量。它的作用域是从变量的定义处开始,到本程序文件的末尾。
评:变量的作用域并不是从变量的定义处开始
而是从变量的声明处开始

但有时设计人员希望能扩展外部变量的作用域。
评:不存在所谓作用域扩展
作用域是声明决定的

  1. 在一个文件内扩展外部变量的作用域
    评:这是一个伪命题
    在C里根本就不存在扩展变量作用域
    变量的作用域决定于声明的位置

如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。
评:1.外部变量的作用域取决于声明的位置
2.那个“如果”莫名其妙,外部变量的作用域无论你“如果”还是不“如果”都是从声明处到文件的结束

P208~212
7.9.3 全局变量的存储类别。
1.在一个文件内扩展外部变量的作用域
2.将外部变量的作用域扩展到其他文件
3.将外部变量的作用域限制在本文件中
评:大标题是“存储类别”
下面三个下标题却都是讲“作用域”
思维混乱竟至于此
P209
#include <stdio.h>
int main()
{int max();
extern int A,B,C; //把外部变量A,B,C的作用域扩展到从此处开始
printf(“Please enter three integer numbers:”);
scanf(“%d%d%d”,&A,&B,&C);
printf(“max is %d\n”,max());
return 0;
}

int A,B,C;

int max()
{int m;
m=A>B?A:B;
if(C>m)m=C;
return(m);
}
评:所谓“把外部变量A,B,C的作用域扩展到从此处开始”是误导,实际上是在main()中声明这三个变量,以便在该函数内使用
代码风格拙劣,思想垃圾,简直是教唆别人如何写垃圾代码
由于A,B,C是外部变量,所以在调用max函数时用不到参数传递。在max函数中可直接使用外部变量A,B,C的值。
评:这是在教读者如何用最荒唐的方法使用外部变量

用extern声明外部变量时,类型名可以写也可以不写。例如,“extern int A,B,C;”也可以写成“extern A,B,C;”。因为它不是定义变量,可以不指定类型,只须写出外部变量名即可。
评:这是在信口开河,胡说八道

P210
extern Num
评:错误性质同前

例7.19 给定b的值,输入a和m,求a*b和a^m的值。
评:这也能叫题目?

文件file1.c
int A;
int main()
{int power(int);
int b=3,c,d,m;
printf(“enter the number a and its power m:\n”);
scanf(“%d,%d”,&A,&m);
c=Ab;
printf("%d
%d=%d\n",A,b,c);
d=power(m);
printf(“%d**%d=%d\n”,A,m,d);
return 0;
}
文件file2.c
extern A;
int power(int n)
{int i,y=1;
for(i=1;i<=n;i++)
y*=A;
return(y);
}
评:extern A的错误不算
这个例题成功地应用外部变量把代码写得无比垃圾
没有足够多的愚蠢是无论如何也写不出如此垃圾的代码的
首先3是常量,却画蛇添足地赋值给b
A,b,c,d无比垃圾式的命名
毫无必要的变量c,d及赋值
毫无通用性的函数power()
怪异的return(y);
业余的循环for(i=1;i<=n;i++)

实际上,在编译时遇到extern 时,先在本文件中找外部变量的定义,如果找到,就在本文件扩展作用域;如果找不到,就在连接时从其他文件中找外部变量的定义。如果从其他文件中找到了,就将作用域扩展到本文件;如果再找不到就按出错处理。
评:“先后理解成在限定一个翻译单元内的代码顺序解析能说得通。当然先定义后使用的说法是错的,应该是先声明后使用。”
这个完全同意。只能在同一个翻译单元内谈先后
而且应该是先声明后使用而不是先定义后使用
不止如此,至少还是有遗漏的。
int(*foo(void))[3]
{
/return…/
}
这里的“类型”是都能写在函数名左边的“类型名”吗?
谢谢补充

P212
对一个数据的定义,需要指定两种属性:数据类型和存储类别,分别使用两个关键字。
评:错乱不堪
数据包括常量与变量
常量即不存在定义的问题
也无需指定存储类别
对于变量来说
存储类别也不一定通过关键字指定
"分别使用两个关键字"更是胡扯
至少还有两种情况:一个关键字不足以说明类型,仅用关键字不足以说明类型

可以用extern声明已定义的外部变量,例如:
extern b;
评:这个错误已经出现至少十几次了

自动变量,即动态局部变量(离开函数,值就消失)
寄存器变量(离开函数,值就消失)
评:这里有两个错误
1.不是值消失,而是对象消失
2.“离开函数”这个说法是错误的,应该是离开语句块

从作用域角度分,有局部变量和全局变量。它们采用的存储类别如下:
评:1.全局变量是个错误的概念
2.存储类别是个含糊不清的概念

寄存器变量(离开函数,值就消失)
评:“函数”、“离开”和“值”这三个概念都有错误
分别应该是“语句块”、“结束”和“数据对象”

静态外部变量
全局变量
外部变量
评:首先“全局变量”是无中生有
其次把全局变量分为静态外部变量和外部变量很错乱
第三和200页老谭给出的定义自相矛盾

从变量存在的时间(生存期)来区分,有动态存储和静态存储两种类型。
评:“动态存储”的说法值得商榷
根据2006楼 幻の上帝 的帖子
在C99中
There are three storage durations: static, automatic, and allocated.
如果“动态”是来自automatic的话,这个翻译明显不当
而若根据《C语言参考手册》
storage duration可分为static extend,local extend和dynamic extend
这里的dynamic 倒是可以译为“动态”,但其含义和老谭所说的截然不同
P213
(本函数内有效)
评:这个错误出现多次

静态外部变量(本文件内有效)
评:这个说法是错误的
在声明点之前无效

外部变量(用extern声明后,其他文件可引用)
评:明显的词不达意,该回去重修小学语文

图7.20是生存期的示意图
评:图7.20中,把f2()函数中的局部静态变量c的生存期从f2()被调用开始算起,是错误的

如果一个变量在某个文件或函数范围内是有效的,就称该范围为该变量的作用域,在此作用域内可以引用该变量,在专业书中称变量在此作用域内“可见”
评:这个是似是而非、不求甚解的蒙人的说法
作用域和可见性是两回事
在作用域内未必可见

表7.2表示各种类型变量的作用域和存在性的情况。
评:表7.2是非常荒谬的一张表格
比如
认为外部变量的作用域涵盖函数内与函数外
实际上函数内与函数外都可能不是外部变量的作用域
因为外部变量的作用域从声明处开始到源文件结束或到所在块的结束处
因为外部变量的作用域从定义处开始到源文件结束
定义前面的部分可能不属于其作用域
再比如
以函数内外来描述变量的存在性也很荒谬
7.10 关于变量的声明和定义
P214
在第2章介绍了如何定义一个变量。
评:第2章并没有介绍如何定义变量

从第2章已经知道,一个函数一般由两部分组成:声明部分和执行语句。
评:1
第2章并没有介绍函数由声明部分和执行语句两部分组成
2
函数也并不是由声明部分和执行语句两部分组成

函数的声明是函数的原型
评:这个恰恰说反了
函数的原型是函数的声明(的一种)

对被调用函数的声明是放在主调函数的声明部分中的
评:语法上没有错误
但实际上是作茧自缚
不仅可能不必要地多次声明
而且污染主调函数的代码

对“int a;” 而言,它既是声明,又是定义;而对“extern a;”而言,它是声明而不是定义。
评:1.
如果
int a;
的位置在{}之内,可以说“它既是声明,又是定义”
如果在{}之外,它可能不是定义
2.
extern a;
这种写法本身是错误的
在C89中只有int类型可以这样写
在C99中不容许省略int
而且,不可能仅仅根据具有extern这个storage-class specifiers这一点就断定它一定是声明而非定义

int main()
{
extern A; //是声明,不是定义,声明将已定义的外部变量A的作用域扩展到此
.
.
.
return 0;
}

int A; //是定义,定义A为整型外部变量
评:extern A; //是声明,不是定义,声明将已定义的外部变量A的作用域扩展到此:
这个确实是声明,不是定义。但并不是“将已定义的外部变量A的作用域扩展到此”,这个标识符A本身有自己的作用域,即声明点到}。这个声明同时说明了A的定义在别处。此外,这个声明应该写明A的类型

int A; //是定义,定义A为整型外部变量:
这个说法是武断的。这个可能是定义也可能不是
例如
file1.c
int main( void )
{
extern int A;
printf(“%d\n”,A);
return 0;
}
int A;//这个不是定义

file2.c
int A=3;//这个才是定义
.

在同一文件中,可以有多次对同一外部变量的声明,它的位置可以在函数之内(哪个函数要用就在哪个函数中声明),也可以在函数之外(在外部变量的定义点之前)。
评:话确实可以这样说
但其出发点则是愚蠢的
尤其是“哪个函数要用就在哪个函数中声明”
7.10 内部函数和外部函数
P215
在定义内部函数时,在函数名和函数类型的前面加static,即:
static 类型名 函数名(形参表);
评:这不是定义

通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用。
评:毫无道理
莫名其妙
这不是“通常”的做法,而是“反常”的做法
P216
例 7.20 有一个字符串,内有若干个字符,现输入一个字符,要求程序将字符串中该字符删去。用外部函数实现。
评:“内有若干个字符”这种废话绝对是谭氏体
字符串里除了有若干个字符还能有什么呢
P216~217
file1.c(文件1)
#include <stdio.h>
int main()
{
extern void enter_string(char str[]);
extern void delet_string(char str[],char ch);
extern void print_string(char str[]);
char c,str[80];
enter_string(str);
scanf(“%c”,&c);
delet_string(str,c);
print_string(str);
return 0;
}

file2.c(文件2)
void enter_string(char str[80])
{
gets(str);
}

file3.c(文件3)
void delet_string(char str[],char ch)
{int i,j;
for(i=j=0;str[ i]!=‘\0’;i++)
if(str[ i]!=ch)
str[j++]=str[ i];
str[j]=‘\0’;
}

file4.c(文件4)
void print_string(char str[])
{
printf(“%s\n”,str);
}。
评:这段源程序有下面这些毛病

extern void enter_string(char str[]);
extern void delet_string(char str[],char ch);
extern void print_string(char str[]);

写在main()中
污染了main()
而且这样很难保证函数类型声明的一致性

P217
一般都省写extern,例如例8.20程序中main函数的第一个函数声明可写成
void enter_string(char str[]);
评:“一般都省写extern”是误导,C语言鼓励程序员明确
此处是第7章,居然扯出个“例8.20”

第8章 善于利用指针
P220
将地址形象化地称为“指针”
评:这是胡扯
多数人指针学得稀里糊涂不是因为别的
就是因为他们把指针和地址混为一谈

地址指向该变量单元。
评:关于地址,老谭是这样说的“内存区的每一个字节有一个编号,这就是‘地址’”

然而,变量单元所占的可能不只一个字节
所以地址指向变量单元的说法是荒谬的
P220~221
由于通过地址能找到所需的变量单元,因此说,地址指向该变量单元。
评:车轱辘话来会说
此外通过地址并不能找到所需的变量单元
P221
一个变量的地址称为该变量的“指针”
评:这是自己扇自己耳光
前面老谭说“内存区的每一个字节有一个编号,这就是‘地址’”
变量可能占几个字节,哪来的地址?

这句话的错误还体现在
指针未必和变量相关

如果有一个变量专门用来存放另一个变量的地址(即指针),则它称为“指针变量”
评:扯淡
再次混淆指针与地址这两个概念
“专门用来存放另一个变量的地址”,井蛙之见
double (*p)( double )=sin;
p算不算是指针变量?
如果不算它算什么?
如果算,它存放了哪个变量的地址啦?

指针变量就是地址变量
评:C语言中压根就没有“地址变量”这种东西

指针变量的值是地址(即指针)
评:再次混淆指针与地址这两个概念
这是把读者往沟里带
P222
第8行输出pointer_1和pointer_2的值。其中的“”表示“指向”
评:“
”表示“指向”是胡扯
在这里这是一个运算符
P223
定义指针变量的一般形式为
类型名 指针变量名;
评:void (
(*p) (void () (char *, long , long ))) (char *, long , long);
这个不知道老谭如何解释

int **pointer_1,*pointer_2;
左端的int是在定义指针变量时必须指定的“基类型”。
评:“基类型”是臆造出的概念
P224
(int *),(float *),(char *)是3种不同的类型
评:(int *),(float *)

网页文学空间程序作业 <html> <head> <title>我的日记</title> </head> <body> <style type="text/css"> body,td,th { font-size: 20px; color: #F0F; font-style: italic; } body { background-image: url(Mypsd_13870_201102141734070002B.jpg); } .b1{border:5px solid blue} .b2{border:2px solid green} </style> <center> <h2>我的日记</h2> </center> <img class=b2 src="50da81cb39dbb6fda57034890924ab18972b3772.jpg"width="100px" height="100px"alt="broadview banner"> ■<a href="登录成功.html"><input name="button" type="button" value="主页"></a> ■<a href="我的小文库.html"><input name="button" type="button" value="我的小文库"></a> ■<a href="我的相册.html"><input name="button" type="button" value="相册"></a> <hr> <marquee onmouseover="this.stop()"onmouseout="this.start()" direction="up" scrollamount="4"> <a href="日记1.html"> 读《谈艺录》之“诗分唐宋”、“神韵”、“长吉诗”一.诗分唐宋 1.“所谓古今之别,非谓时代,乃言体制”(顾复);“性情原自无今古,格调何须辨唐宋”(戴昺)——这话非常通透、恰切。但至今诗仍以时代划分,主要是因为容易且方便,毕竟时代是划定了的,而性情则是仁者见仁,主观色彩太多,不可能有一个统一的标准。 2.“初、盛、中、晚…犹之乎春、夏、秋、冬…四序之中,各有良城美景…不得谓夏劣于春,冬劣于秋也…”(黄周星)——如陈眉公所言:黄花红树,春不如秋,白雪青松,冬亦胜夏,春夏园林,秋冬山谷。各有风致,不必厚此薄彼,只可以说个人自由偏爱。本人仍爱以“丰神情韵”擅长的唐诗,然后是宋诗的江湖派,汉诗,不喜清诗。 六.神韵 1.“上学、中学、下学”(《文子.道德篇》)与艾克哈特之“学分上、中、下”之论——王国维之“肉秀、骨秀、神秀”是也。京剧名家论声音最下者发于声,中者发于心,上者发于头脑,概也来源于《文子.道德篇》。 2.“体格声调,兴象风神”(胡元瑞)——二者的关系像是魂魄与精神,前者有质无形,后者有形无质。《史记.太史公自序》中论形神之语最透彻:神为形之本,形为神之具。 七.李长吉诗 1.“世间安得有奇…”(姚羹湖)之言——世间确有奇,且未必事事都可解,不可解处不必强解,不能解未必是无知。马未都先生论收藏曾言此,杨琪老师讲梵高曾言此。 2.“考据家不足与言诗”(张箦斋)——就像读史必究语出何处,见“管锥编.左传正义.杜预序”。又像牵强附会者,如读鲁迅读红楼必要把一字一词都说成有所隐喻。又像看一些意大利、法国新浪潮电影,非要说成性是拯救孤独空虚的不二法门,其实只是导演的意淫而已。有些人便摸准了这种观者的脾性,写一些、画一些、拍一些只代表自己意淫,甚至连意淫都没有的,或者是如支棋者的“哑谜”、反讽富家子之类的垃圾,因为他们深知,这些垃圾一定会被那些观者看出许多连自己都不知道的深意。 3.“行布拉杂,凌乱,重叠”——为辞而写,联句可也,成诗未免低级。 4.“若必经此境,始能道此语,则其为才也隘矣”(李仁卿)——正是未必经此境,却能道此语,方为天才,不止诗家如此。 八.长吉诗境 1.“唐人作唐人诗序,亦多夸词”(贺贻孙)——今人作今人书序,夸词亦多,若为尚且健在之人作序,夸词则多多,若为尚且健在且相识之人作序,则夸词如韩苏之文,“长江秋注”、“万斛泉源”。 九.长吉字法 丁敦龄乃无耻,苏曼殊乃知识有限,一为刻意,一为无意,但二者均起到了相同的作用:误导。若想不被误导,最好的办法就是自己读原文,不要“于卖花担子上看桃李”。世人以“诗仙”、“诗圣”、“诗鬼”等词赞众诗人,欧言苏曼殊竟以此眼光看拜伦雪莱太白长吉,可当“失明”。 “于文字海中飘零,未尝得筏登岸”——连边还没摸着呢。 一〇.长吉曲喻 “二物相似,以此喻彼,然彼此相似,只在一端,非为全体,苟全体相似,则物数虽二,物类则一”——“酽茶”与“黄酒”。 一一.长吉用啼泣字 1.“彼此有合,故乐”——有共鸣才乐。又可见人是群居动物,期许外物与己身心相合,渴望沟通、理解,即“情契”。 2.“宜诗不宜画”、“文字语言之特长,非他艺所可几及”——想象的空间。 3.“风景即心境”——人的心境多少都有所“载”,“虚”谈何容易。“载”或可看朱成碧,但正是因为有“载”,才会有自己的特点,而非原样复制。 一二.长吉用代字 “只知有花草蜂蝶,而不知世间一切皆诗”(张戒)——李贺诗类似唯美主义绘画,又不完全像,他比唯美主义多了许多诡异,又像日本动漫,精美第一,如果精美不够,就添些诡异,以奇取胜。不知平淡甚至丑陋中皆有美存焉。屡见喜欢所谓唯美的人不能欣赏“窃听风暴”、“僵尸肖恩”。看来此二者难以兼容,因一旦爱“尽俗能雅”者不会再被表象的精美吸引。一言以蔽之,李贺奇才,但小器,“化腐为奇,尽俗能雅”的苏韩乃大器。 一三.长吉与杜韩 李贺李白性情迥异,诗既源于性灵,李贺断写不出太白风骨。“突兀”二字极贴。李贺仅于个别用字上模仿了些李白的形罢了,李白诗乃自然流露,李贺似太白处乃雕琢而出。 一四.长吉年命之嗟 “茫茫来日愁如海,给予羲和快着鞭”——与次韵中“更酌羲和一杯酒,劝停征辔莫加鞭”同看。心境不同,辞与意全然相反。 推崇黄仲则诗有李白风,更以“前后观潮”诗为最者,不知作何想法。前后观潮好则好矣,但其与太白相比,远失灵动飘逸、浑然大器,频繁换韵更是缺少太白之“飞流直下三千尺”之流畅。或许与其本人诗风相比这两首算是豪迈磅礴,但就如小孩耍大锤,耍倒是耍动了,却让人看着吃力。哪里有他的《绮怀》十六首顺溜 </a> </marquee> <hr class=b1> <marquee diretion="right"> 我的好友::<a href="李刚.html" >李刚</a>::<a href="王城.html" >王城</a>::成龙::巴菲特::马云::李彦宏::马化腾::奥巴马::普京、、 </marquee> <hr> <marquee onmouseover="this.stop()"onmouseout="this.start()" direction="left"> <a href="李刚.html" ><img src="4b90f603738da97757032aa4b051f8198618e315.jpg"width="100px" height="100px"alt="broadview banner"></a> <a href="王城.html" ><img src="908fa0ec08fa513de7b5295e3d6d55fbb2fbd917.jpg"width="100px" height="100px"alt="broadview banner"></a> <img src="b64543a98226cffc640b0c42b9014a90f603ea19.jpg"width="100px" height="100px"alt="broadview banner"> <img src="b8014a90f603738d9120f788b31bb051f819ec36.jpg"width="100px" height="100px"alt="broadview banner"> <img src="a8014c086e061d95cb995db47bf40ad163d9ca5b.jpg"width="100px" height="100px"alt="broadview banner"> <img src="72f082025aafa40fdadb9b51ab64034f78f01918.jpg"width="100px" height="100px"alt="broadview banner"> <img src="7a899e510fb30f24403f3cf3c895d143ac4b039c.jpg"width="100px" height="100px"alt="broadview banner"> <img src="dcc451da81cb39db44f4baa6d0160924ab18300e.jpg"width="100px" height="100px"alt="broadview banner"> <img src="42a98226cffc1e1791905fc14a90f603738de917.jpg"width="100px" height="100px"alt="broadview banner"> </marquee> <hr> <audio src="老男孩.MP3" controls="controls" loop="loop"> </body> </html>
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C?est bien d?�tre seul

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值