0
点赞
收藏
分享

微信扫一扫

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

归零者245号 2022-04-24 阅读 80
c语言

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”);

没有意义,在输入错误时依然会导致后面错误的输出
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);

过于啰嗦
P 322
由于class是C++的关键字,用Visual C++时不应该用class作成员名,故clas代表
评:不要侮辱Visual C++
是老谭自己掰不清C和C++
Visual C++还是能分得清C和C++的
P 322
union Categ
{ int banji;
char position[10];
}

评:int banji;很有趣,与char position[10]; 堪称中西合璧的绝配
9.6 使用枚举类型
P 323
如果一个变量只有几种可能的值,则可以定义为枚举(enumeration)类型,所谓“枚举”就是指把可能的值一一列举出来,变量的值只限于列举出来的值的范围内。
评:两个错误:
1.不是所有的东西都可以枚举,必须是整数
2.枚举变量的值并非限于列举出的值的范围
P 323
枚举变量workday和weekend的值只能是sun到sat之一
评:不对
P 323
……枚举常量。不要因为它们是标识符(有名字)而把它们看做变量,
评:在同书42页却是这样说的
常量是没有名字的不变量
P 324
由于枚举变量的值是整数,因此C99把枚举类型也作为整型数据中的一种,即用户自行定义的整数类型
评:这个“由于”、“因此”是在瞎扯
根本不存在这样的因果关系
而且这和C99没什么关系,早在C89中枚举就是整数类型之一
这里“整型数据”是个很烂的概念
因为在书中“整型”不知道有多少种含义
再则,这里的说法与前面43页图3.4对数据的分类是根本互相矛盾的
P 324~326
例9.12 口袋中有红、黄、蓝、白、黑5种颜色的球若干个。每次从口袋中先后取出3个球,问得到3种不同颜色的球的可能取法,输出每种排列的情况。
#include <stdio.h>
int main()
{enum Color{red,yellow,blue,white,black};
enum Color i,j,k,pri;
int n,loop;
n=0;
for(i=red;i<=black;i++)
for(j=red;j<=black;j++)
if(i!=j)
{ for(k=red;k<=black;k++)
if((k!=i)&&(k!=j))
{n=n+1;
printf(“%-4d”,n);
for(loop=1;loop<=3;loop++)
{switch(loop)
{case 1:pri=i;break;
case 2:pri=j;break;
case 3:pri=k;break;
default :break;
}
switch(pri)
{case red:printf(“%-10s”,“red”);break;
case yellow:printf(“%-10s”,“yellow”);break;
case blue:printf(“%-10s”,“blue”);break;
case white:printf(“%-10s”,“white”);break;
case black:printf(“%-10s”,“black”);break;
default :break;
}
}
printf(“\n”);
}
}
printf(“\ntotal:%5d\n”,n);
return 0;
}

评:枚举是个好东西
可惜被i、j、k以及那两个丑陋不堪的switch给糟蹋了,代码味道很恶
繁琐不堪,丑陋不堪,根本没有体现出半点enum的从容和优美
一本书都快结束了,居然还是一个main()写到底,老谭究竟会不会写代码啊
P 326
显然用枚举变量(red、yellow等)更直观
评:那i,j,k又怎么说?
P 326
此外,枚举变量的值限定在定义时规定的几个枚举元素范围内,如果赋予它其他值,就会出现出错信息,便于检查
评:胡扯
P 327
typedef int Integer;
typedef float Real;
……
这样可以使熟悉FORTRAN的人能用Integer和Real定义变量,以适应他们的习惯
评:异想天开
这样的人根本不可能写C代码
他们应该适应C而不是应该让C去适应他们
P 327
float [] (指针数组)
评:错
P 327
float (
)[5] (指向10个元素的一维数组的指针)
评:两处错误
P 327
typedef struct
( int month;
int day;
int year;
}Date;
评:居然能犯这种低级错误
P 328
按定义变量的方式,把变量名换上新类型名,并且在最前面加“typedef”,就声明了新类型名代表原来的类型。
评:C语言并没有要求一定要“在最前面”
P 329
同样可以定义字符串类型
评:C语言根本就没有“字符串类型”
P 329
#define 是在预编译时处理的,它只能作简单的字符串替换。
评:错
P 329
ypedef是在编译阶段处理的。实际上它并不是作简单的字符串替换,例如:
typedef int Num[10];
Num a;
并不是用"Num[10]“去替代“int”,而是采用如同定义变量的方法先生成一个类型名(就是前面介绍过的讲原来的变量名换成类型名),然后用它去定义变量。
评:这是一种对typedef实现方式的虚妄、无知且无聊的揣测
对编程没有任何用处
P 329
数值范围为-32768~32767……数值范围为±21亿
评:语文问题
P 330
在移植时只须改动typedef定义体即可:
typedef long Integer;
评:移植没那么简单
只改typedef是远远不够的
第10章 对文件的输入输出
P 331
第10章 对文件的输入输出
评:这话通吗?
P 331
10.1 C文件的有关基本知识
评:“C文件”?
到底想说什么
没这种东西
P 331
操作系统把各种设备都统一作为文件来处理
评:不知道OS把CPU作为了什么文件?
P 331
从操作系统的角度看,每一个与主机相连的输入输出设备都看做一个文件
评:看来磁盘驱动器也是文件喽
P 331
所谓“文件”一般指在外部介质上数据的集合
评:嗯。纸张也算“外部介质”吧?
P 331
操作系统是以文件为单位对数据进行管理的
评:哦,那就是说一张格式化而没有文件的磁盘上没有数据了
P 331
常将输入输出形象地称为流(stream),即数据流
评:是谭大爷把它形象地理解为流吧
stream其实恰恰是一个抽象的概念
P 332
数据可以运行环境流入程序中,或从程序流至运行环境
评:对运行环境(execution environment)的误解
P 332
C的数据文件由一连串得字符(或字节)组成,而不考虑行的界限,两行数据间不会自动加分隔符,对文件的存取是以字符(或字节)为单位的。
评:错。文本流是由行分隔的字符序列
P 332
文件标识包括3部分:(1)文件路基;(2)文件名主干;(3)文件后缀
评:文件名主干,又开始发明新概念了
通常的说法是主文件名
P 332
文件名主干命名规则遵循标识符的命名规则
评:常识性错误
P 332
数据文件可分为ASCII文件和二进制文件
评:ASCII文件:这恐怕又是老谭臆造的新概念,没有这种东西
应该是文本文件和二进制文件
P 332
二进制文件……也称之为映像文件(imagine file)
评:前所未闻
至少C标准从来没有这种说法
P 332
字符一律以ASCII形式存储
评:错
C语言与ASCII码没有必然的关系
P 333
ANSI C标准采用“缓冲文件系统”处理数据文件
评:没有这回事
再说不是号称“按照C99标准”
怎么露怯了
扯什么ANSI C标准去了
P 333
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”
评:驴唇不对马嘴
跟缓冲与否无关
P 334
在程序中可以直接用FILE类型名定义变量。每一个FILE类型变量对应一个文件的信息区,在其中存放该文件的有关信息。例如,可以定义以下FILE类型的变量:
FILE f1;
定义了一个结构体变量f1,用它来存放一个文件的有关信息。这些信息是在打开文件时由系统根据文件的情况自动放入的,用户不必过问
评:胡扯
P 334
一般不对FILE类型变量命名,也就是不通过变量的名字来引用这些变量,而是设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE变量。这样使用起来方便。
评:这与方便无关
P 334
通过文件指针变量能够找到与它相关的文件
评:错
这个指针操作的对象是流
P 335
ANSI C规定了用标准输入输出函数fopen来实现打开文件
评:友情提醒,这里又忘记把ANSI C改成C99了
实际上还有一个freopen()函数
P 335
fopen(文件名,使用文件方式);
例如
fopen(“a1”,“r”);
评:不是“文件标识”吗?(332页)
怎么又改成“文件名”了
“例如”中怎么直接使用了“文件名主干”
P 335
表10.1 使用文件方式
如果文件不存在
“a”(追加) 出错
“ab”(追加) 出错
评:这也能弄错
P 336
表10.1 使用文件方式
如果文件不存在
“a+” (追加) 出错
“ab+”(追加) 出错
评:错
P 336
用“r”方式打开的文件只能用于向计算机输入……
评:看来在老谭的概念里,文件不属于计算机
P 336
用“w”方式打开的文件只能用于向该文件写数据(即输出文件),而不能用来向计算机输入。
评:看来在老谭的概念里,文件不属于计算
P 336
如果原来已存在一个以该文件名命名的文件,则再打开文件前先将该文件删去,然后重新建立一个文件。评:毫无根据的臆测
P 336
如果希望向文件末尾添加新的数据(不希望删除原有的数据),则应该用“a”方式打开。但此时应保证该文件已存在;否则将得到出错信息。
评:胡扯
P 336
打开文件时,文件读写位置标记移到文件末尾
评:错
这是由实现定义的
P 336
在每个数据文件中自动设置了一个隐式的“文件读写位置标记”,它指向的位置就是当前进行读写的位置
评:看到这个还以为是科幻小说呢
P 337
但目前使用的有些C编译系统可能不完全提供所有这些功能(例如,有的只能用"r”、“w”、“a"方式),有的C版本不用"r+”,“w+”,“a+”,而用"rw",“wr”,"ar"等,请读者注意所用系统的规定。
评:这是几十年前的陈词滥调
几十年后还这样写就太可笑了
P 337
计算机输入从ASCII文件读入字符时,遇到回车换行符,系统把它转换为一个换行符,在输出时把换行符转换成为回车和换行两个字符。
评:这只是DOS或Windows下的情况而已

此外“ASCII文件”也是胡编乱造的不存在的概念
C90已经支持wchar,难道这也是ASCII?
P 337
可以通过这3个指针变量以上3种流进行操作
评:这是什么话呀
P 337
如果程序中指定要从stdin所指的文件输入数据,就是指从终端键盘输入数据
评:stdin 表示标准输入流,未必就是键盘,尽管很多的时候是
P 338
对文件读写数据的顺序和数据在文件中的物理顺序是一致的。
评:有没有搞错啊,什么叫文件的“物理顺序”
请谭先生先了解一下再说好吗
P 338
表10.2
fgetc(fp) 读成功,带回所读的字符,失败则返回文件结束标志EOF(即-1)
评:“带回所读的字符”这个说法含糊不清
文件结束标志和EOF不是一回事

EOF 也未必就是-1
P 338~339
例10.1
#include <stdio.h>
#include <stdlib.h>
int main()
{FILE *fp;
char ch,filename[10];
printf(“请输入所用的文件名:”);
scanf(“%s”,filename);
if((fp=fopen(filename,“w”))==NULL)
{
printf(“无法打开此文件\n”);
exit(0);
}
ch=getchar();
printf(“请输入一个准备存储到磁盘的字符串(以#结束):”);
ch=getchar();
while(ch!=‘#’)
{
fputc(ch,fp);
putchar(ch);
ch=getchar();
}
fclose(fp);
putchar(10);
return 0;
}

评:ilename[10];
这个10即使是在DOS年代也是一个不合格选择

scanf(“%s”,filename);
ch=getchar();

这个组合极其笨拙,不如用gets()干净利索
而且存在着越界的危险

exit(0);
很可笑,在main()中它和return 0没什么区别

ch=getchar();
while(ch!=‘#’)
{
fputc(ch,fp);
putchar(ch);
ch=getchar();
}
这里出现了两句ch=getchar(); ,完全没必要,丑陋

putchar(10);
令人作呕的写法
降低可移植性和可读性
P 340
例10.2
#include <stdio.h>
#include <stdlib.h>
int main()
{FILE *in,*out;
char ch,infile[10],outfile[10];
printf(“输入读入文件的名字:”);
scanf(“%s”,infile);
printf(“输入输出文件的名字:”);
scanf(“%s”,outfile);
if((in=fopen(infile,“r”))==NULL)
{printf(“无法打开此文件\n”);
exit(0);
}
if((out=fopen(outfile,“w”))==NULL)
{printf(“无法打开此文件\n”);
exit(0);
}
while(!feof(in))
{ch=fgetc(in);
fputc(ch,out);
putchar(ch);
}
putchar(10);
fclose(in);
fclose(out);
return 0;
}
评:这段代码除了有例10.1的所有毛病
还有个非常隐蔽的BUG
这个BUG是由于对feof()函数的误解造成的
二十多年来这个BUG在谭书中一直存在
P 340
在访问磁盘文件时,是逐个字符(字节)进行的
评:晕死
这种外行话都说得出来

P 341
feof(in)是检查in所指向的文件是否结束。如果是,则函数值为1(真),否则为0(假)
评:“函数值为1”,错
P 341
运行结果是将file1.dat文件中的内容复制到file2.dat中。
评:不只如此
还多复制了一个无意义的字符
P 341
C系统已把fputc和fgetc函数定义为宏名putc和getc:
#define putc(ch,fp) fputc(ch,fp)
#define getc(fp) fgetc(fp)
评:有的如此
有的不是
P 341
在程序中用putc和fputc作用是一样的,用getc和fgetc作用是一样的。在使用形式上,可以把它们当作相同的函数对待
评:恰恰是把前者定义为后者的宏的时候它们不一样,不能当作相同的函数
P 341
用feof函数可以检查到文件读写位置标记是否移到文件的末尾
评:feof()函数根本不检查文件读写位置
P 341
fgets(str,n,fp);
作用是从fp所指向的文件中读入一个长度为n-1的字符串,并在最后加一个’\0’字符,然后把这n个字符存放到字符数组str中
评:错
字符串是包含’\0’字符的
最多能读n-1个字符
P 341
表10.3 读写一个字符串的函数
从fp所指向的文件中读入一个长度为(n-1)的字符串,存放到字符数组str中
评:错
最多读n-1个字符,且添加null character
P 341
表10.3 读写一个字符串的函数
把str所指向的字符串写到文件指针变量fp所指向的文件中
评:错
结尾的’\0’并不写入
P 341
表10.3 读写一个字符串的函数
输出成功,返回0;否则返回非负值
评:胡扯
P 342
fputs函数……若输出成功,函数值为0,失败时函数值为EOF
评:这和表10.3 说的根本就是驴唇不对马嘴
而且都是错的
P 342
fgets和fgets这两个函数……
评:不好意思
那是一个函数
P 342
……
char str[3][10],temp[10];
int i,j,k,n=3;
……
for(i=0;i<n;i++)
gets(str[ i]);
评:str[3][10],小家子气,极易出错
n=3; 再次追认数组尺寸,前无古人的写法
P 343
为运行简单起见,本例只输入3个字符串,如果有10个字符,只须把第7行的n=3改为n=10即可。
评:很奇怪
如此明显的胡说八道竟能三番五次地出现在教科书里
P 343
见程序中第27行中的fputs(“\n”,fp);
评:其实不如写fputc(‘\n’,fp);
P 344
fprintf和fscanf函数的读写对象不是终端而是文件
评:fprintf和fscanf函数可以面向标准输入输出设备
printf和scanf函数也可以面向文件
P 345~346
struct Student_type
{ char name[10];
int num;
int age;
char addr[30];
}stud[40];
for(i=0;i<40;i++)
fread(&stud[ i],sizeof(struct Student_type),1,fp);
评:实在是有点辜负fread()设计者的苦心了
P 346
for(i=0;i<40;i++)
fwrite(&stud[ i],sizeof(struct Student_—type),1,fp);
fread或fwrite函数的类型为int型
评:没给说成是"整型"是一个进步
但这个说法还是错误的
P 346~347
#include <stdio.h>
#define SIZE 10
struct Student_type
{char name[10];
int num;
int age;
char addr[15];
}stud[SIZE];

void save()
{FILE *fp;
int i;
if((fp=fopen(“stu.dat”,“wb”))==NULL)
{printf(“cannot open file\n”);
return ;
}
for(i=0;i<SIZE;i++)
if(fwrite(&stud[i],sizeof(struct Student_type),1,fp)!=1)
printf(“file write error\n”);
fclose(fp);
}
int main()
{int i;
printf(“Please enter data of students:\n”);
for(i=0;i<SIZE;i++)
scanf(“%s%d%d%s”,stud[i].name,&stud[i].num,&stud[i].age,stud[i].addr);
save();
return 0;
}

评:1.随手定义外部变量
2.void save()函数定义不标准
3.void save()中的return ;是错误的写法
4.fwrite用得极其笨拙,且出错时没有恰当的处理
5.把main的定义写在后面,头重脚轻
P 347
一个struct Student_type类型结构体变量的长度为它的成员之和,即10+4+4+15=33,实际上占36字节,是4的倍数
评:“一个struct Student_type类型结构体变量的长度为它的成员之和”:这是胡扯
“实际上占36字节”:自相矛盾
“是4的倍数”:没有根据
P 347
用fopen函数打开文件时没有指定路径,只写了文件名stu.dat,系统默认其路径为当前用户所使用的子目录(即源文件所在路径),在此目录下建立一个新文件stu.dat,输出数据存放在此文件中
评:不指定路径本身就是一种恶习
“源文件所在路径”是胡扯,源文件能运行吗
P 347~348
#include <stdio.h>
#include <stdlib.h>
#define SIZE 10
struct Student_type
{char name[10];
int num;
int age;
char addr[15];
}stud[SIZE];

int main()
{int i;
FILE *fp;
if((fp=fopen(“stu.dat”,“rb”))==NULL) //打开输入文件stu.dat
{printf(“cannot open file\n”);
exit(0) ;
}
for(i=0;i<SIZE;i++)
{fread(&stud[i],sizeof(struct Student_type),1,fp);
printf(“%-10s%4d%4d%-15s\n”,stud[i].name,stud[i].num,stud[i].age,stud[i].addr);
}
fclose(fp); //关闭文件"stu_list"
return 0;
}

评:1.随手定义外部变量
2.exit(0)笨拙且丧失其应有的作用
3.fread(&stud[ i],sizeof(struct Student_type),1,fp); 笨拙且没有进行检查
4.“打开输入文件stu.dat” 最后 “关闭文件"stu_list"” 喝多了吧
P 348
在前一个程序中,从键盘输入10个学生的数据是ASCII码,也就是文本文件。
评:发散性思维!
居然能发散到“文本文件”上去
P 348
在送到计算机内存时,回车和换行符转换成一个换行符,再从内存以“wb”方式(二进制写方式)输出到“stu.dat”文件,此时不发生字符转换,按内存中存储形式原样输出到磁盘文件上。
评:没睡醒吧?
谭大爷!
那个代码根本就没有涉及到这个转换啊
P 348
最后在验证程序中,用printf函数输出到屏幕,printf是格式输出字符,输出ASCII码,在屏幕上显示字符。换行符又转换为回车加换行符
评:恭喜谭大爷
穿越成功!
能够对例题中根本不存在的现象发出如此一大篇无中生有的议论
令人佩服
(窃窃地小声问一下,秘诀是不是复制粘贴贴错地方?)
P 348
fread和fwrite函数一般用于二进制文件的输入输出。因为它们是按数据块的长度来处理输入输出的,
评:这个“因为”很可笑
因为根本不存在这个因果关系
P 348
stdin是指向标准输入流的指针变量
评:它是指针变量吗?
即使在VC++中它也不是变量啊
谭大爷这结论是哪来的
P 348
若写出
fread(&stud[ i],sizeof(struct Student_type),1,fp);
……
如用以下形式输入数据:
Zhang 1001 19 room_101
……
由于fread函数要求一次输入36个字节(而不问这些字节的内容),因此输入数据中的空格也作为输入数据而不作为数据间的分隔符了。连空格也存储到stu[ i]中了,显然是不对的。
评:告诉读者不可以fread(&stud[ i],sizeof(struct Student_type),1,fp);是对的
但后面的解释就太离谱了
谭大爷总能凭着无中生有的想象力为未定义的行为解释出根本不存在的因为所以
我得说
酱紫“显然是不对的”
P 349
这个题目……(至10.3 结束)
评:完全错乱
驴唇不对马嘴
复制粘贴错了都不至于如此
有理由怀疑嗑药了
而且编辑也嗑药了
这种出版奇迹只有天朝才能创造得出来
P 349
随机访问不是按数据在文件中的物理位置次序进行读写。
评:无语了
P 350
一般情况下,在对字符文件进行顺序读写时,文件位置标记指向文件开头
评:“字符文件”以及“指向文件开头”都是无中生有
P 350
在下一次执行写操作时把数据写入指针所指的位置
评:什么指针?哪个指针?
上下文中根本就没有这个所谓的“指针”
P 350
对文件读写的顺序和数据在文件中的物理顺序一般是不一致的
评:有没有搞清什么叫“物理顺序”啊
P 350
可以在任何位置写入数据,在任何位置读取数据
评:太夸张了吧
任何计算机中都不存在任何这两个字
P 350~351
例 10.5 有一个磁盘文件,内有一些信息。要求第1次将它的内容显示在屏幕上,第2次把它复制到另一文件上。
#include <stdio.h>
int main()
{FILE *fp1,*fp2;
fp1=fopen(“file1.dat”,“r”);
fp2=fopen(“file2.dat”,“w”);
while(!feof(fp1))putchar(getc(fp1));
putchar(10);
rewind(fp1);
while(!feof(fp1))putc(getc(fp1),fp2);
fclose(fp1); fclose(fp2);
return 0 ;
}

评:1.feof()错用
2.标准要求putc()等的实参不能有副效应,函数调用显然不应该作为实参
3.没对文件没打开进行处理
P 351
用函数feof可以测出文件位置标记是否已指到文件末尾
评:错
P 351
fseek(文件类型指针,位移量,起始点)
“起始点”用0、1或2代替,0代表“文件开始位置”,1为“当前位置”,2为“文件末尾位置”
评:这是教人学坏
这个参数应该用SEEK_SET,SEEK_CUR或SEEK_END

此外fseek()清除eof标志的功能没有提及
P 351
fseek函数一般用于二进制文件
评:文本文件也可以用
P 352
如调用函数出错,ftell返回值为-1L
评:不仅如此
还会设置error的值
P 353
每次位置指针的移动量是结构体变长度的两倍
评:不是说“指示文件读写位置的不宜称为’指针’”(350页脚注)吗?
这个“位置指针”是怎么回事?

“结构体变长度” ???
P 354
应当在调用函数时一个输入输出函数后立即检查ferror函数的值。否则信息会丢失
评:不切实际
P 354
在执行fopen函数时,ferror函数的初始值自动置0
评:要是fopen失败呢?
函数哪来的初始值?
P 354
clearerr的作用是使文件错误标志和文件结束标志置为0
评:“置为0”这个说法没有依据
P 354
假设在调用一个输入输出函数时出现错误,ferror函数值为一个非零值。应该立即调用cleaerr(fp),使ferror(fp)的值变为0,以便再进行下一次的检测。
评:这是毫无开发经验的人对开发所做的一种幻想
P 354
C要求对程序中用到的每一个变量都必须定义其类型
评:“定义其类型”,这叫什么话
应该是必须声明
第11章常见错误分析
P 355
应在函数体的开头加
int x,y;
评:实际上要在前面对x,y进行声明
在函数体的开头加 int x,y; 只是其中的一种声明形式而已
P 355
输入输出的数据的类型与用户指定的输入输出格式声明不一致
评:什么叫“用户指定的输入输出格式声明”?
谁是“用户”?

P 355
若a已定义为整型,b已定义为实型
评:“整型”,“实型” 都是错误的概念

P 356
nt num;
m=89101;
printf(“%d”,num);
如果用Turbo C编译系统,得到的却是23565,原因是……
评:不要污蔑Turbo C好不好
这段代码任何编译器都不可能得到23565
P 356
对于超过整数范围的数,要用long型
评:还真不清楚什么样的数会超过“整数范围”
分数?小数?复数?
long类型难道不是一种整数类型吗?
P 356
例如:
num=198607;
输出得-1.因为……
评:对未定义行为解释的头头是道

P 357
在数组名前面多加了&。例如
char a[20];
scanf(“%s”,&a);
……数组名a本身就是地址,再加&就是画蛇添足了。只须直接写数组名即可:
scanf(“%s”,a);
评:这里确实应该直接写数组名
但是“数组名a本身就是地址,再加&就是画蛇添足了”这个理由是错误的、荒谬的
因为它无法回答,&a也是地址,为什么a这个地址可以而&a这个地址不可以这样的问题
P 357
scanf(“%s”,a);
这样,输入的字符串就会送到数组名c所代表的地址去
评:不会吧。前面写的是a,怎么会输入到c中呢
P 357
int a[20];
scanf(“%d”,a);
这是错误的。
评:这不是错误的
其含义是输入a[0]的值
P 357~358
……多个数据,必须分别通过指定数组元素输入。即
int a[20];
int i;
for(i=0;i<20;i++)
scanf(“%d”,a[ i]);
评:O!MGD!
这个才是错误的
这一章是“常见错误分析”
谭大爷居然现身说法
用更大的错误去纠正错误
P 358
C语言规定语句末尾必须有分号。分号是C语句不可缺少的一部分。
评:胡扯
C语言根本就没有这个规定
语句可以没有分号
P 358
在C语言中,没有分号的就不是语句。
评:考考老谭:
题目:试写出一没有分号的C语句。
P 360
if(score=100)n++;
……if语句检查score是否为零。若为非零,则作为“真”;若为零作为“假”
评:错
P 361
在C语言中,数组名a代表数组a的首元素地址……
评:常见误解
P 363
if(score=100)n++;
……if语句检查score是否为零。若为非零,则作为“真”;若为零作为“假”
评:在附录D中根本没有写++的优先级高于*,而是把它们当作优先级相同
此外也没什么“先执行”
P 363
int main()
{int *p,a[5]={1,3,5,7,9};
p=a;
printf(“%d”,*p++);
}
……结论是先输出a[0]的值,然后再使p加1。
评:这个结论是错误的
此外示意代码本身有不符合规范的毛病
P 363
(24)忘记对所调用的函数进行函数原型声明。
……
在编译时有出错信息
评:应该是警告信息
P 363~364
(24)忘记对所调用的函数进行函数原型声明。
①……
②……
评:这部分讲的是“忘记对所调用的函数进行函数原型声明”这种错误
但老谭给出的① ② 两个写法本身也有“忘记对所调用的函数进行函数原型声明”这种错误
叫人情何以堪啊?
P 365
int main()
{int a,b,*p1,*p2;
a=3;b=4;
p1=&a;p2=&b;
swap(p1,p2);
printf(“%d,%d”,a,b);
}

void swap(int *pt1,int *pt2)
{ int temp;
temp=*pt1;*pt1=*pt2;*pt2=temp;
}

评:这是谭针对错误代码给出的修改后的代码
可是它本身就是错误连篇
不仅有他刚刚批评过的“忘记对所调用的函数进行函数原型声明”的错误
而且代码不规范
没有写他告诉读者应该写的return 0;
此外p1,p2两个变量多余
风格方面
a=3;b=4;
p1=&a;p2=&b;
temp=*pt1;*pt1=*pt2;*pt2=temp;
如同挤在一起的几条蛆
P 366
int *p1;
float *p2;
指向不同类型的指针间的赋值必须进行类型转换。例如:
p2=(float *)p1;
评:吃饱了撑的
这是想告诉读者什么啊
P 366
可以进行如下的类型转换:
struct studend
{ int num;
char name[20];
float score;
}
struct studend studend1,*p;
p=(struct studend)malloc(LEN);
评:LEN 是什么?
studend 是什么意思还真不清楚?古英语?
P 367
int i=3;
printf(“%d,%d,%d\n”,i,++i,++i);
……在Turbo C和Visual C++6.0系统中输出是
5,5,4
因为这些系统的处理方法是:按自左至右的顺序求函数参数的值。
评:这个写法本身就是错的
老谭居然能讲出“因为”
P 367
printf(“%d,%d,%d\n”,i,i++,i++);
……在Turbo C和Visual C++6.0系统中输出是
3,3,3
求值的顺序仍然是自由向左,但是需要注意的是:……由于i++是“后自加”,是在执行完printf语句后再使i加1
评:什么叫不懂装懂
这就是
P 368
nt a[5],*p;
p=a;
for(p=a;p<a+5;p++)
……
评:连续赋值两次可以更保险?
P 368
应改为
struct worker
{int num;
char name[10];
char six;
int age;
};
struct worker worker1;
……
strcpy(worker1.name ,“Zhang Fang”);。
评:哦卖糕的
应该再加一段
“应进一步改为”
P 369
(34)在打开文件时,指定的文件名找不到。
如:
if((fp=fopen(“test”,“r” ) )==NULL)
{printf(“cannot open this file\n” );
exit(0);
}
……应当在指定文件名时指出文件路径。如:
if((fp=fopen(“D:\temp\test”,“r” ) )==NULL)
{printf(“cannot open this file\n” );
exit(0);
}
评:居然能把正确的改成错误的
太有喜感了

本章谭浩强一共罗列了35种错误
但自己犯的错误绝对不少于35个
应该把这章的名字《常见错误分析》改为《常见错误展示》
P 370
Visual C++6.0有英文版和中文版
评:有中文版吗?
从没听说过
P 372
输入例1.1程序(见图A.4)
评:居然是
void main()
P 373
找到已有的C程序名……双击此文件名,则进入了Visual C++集成环境
评:未必
P 377
ASCII代码
评:听说过C代码
ASCII代码是什么东东?
P 377
128~255是IBM-PC上专用的
评:令人有“今夕是何年”之慨
大概可以用来进行IT考古
P 378
附录C C语言中的关键字
_bool
评:竟然能把关键字写错
而且是在附录中
P 378~379
附录 D 运算符和结合性
评:首先,标题就不正确
其次,这是一张过时的表格,应该是上个世纪80年代的
C90的一些运算阙如,如 一元 + 运算
C99的一些新运算就更没有
把两种++、–视为一种运算也是错误的
第3,优先级错误
根据C标准,这个表的cast运算优先级不正确
第4,逗号运算是几目运算搞不清楚
第5,乱起名称,运算符名称不当
如把 () 叫做“圆括号”
把->叫“指向结构体成员运算符”
把* 叫“指针运算符”
P 379
同一优先级的运算符,运算次序由结合方向决定
评:优先级和结合性,与运算次序没有关系
P 379
初等运算符 () [] -> .
评:概念不清,错乱
P 380
为了容易记忆,使用位运算时可加圆括号
评:看不懂。“加圆括号”怎么是“为了容易记忆”
P 380
附录E C语言常用语法提要
评:常识性和概念性错误很多
例如:
P 380
标识符可由字母、数字和下划线组成。
评:按照C99
这其实是早已过时的说法
P 380
不同的系统对标识符的字符数有不同的规定,一般允许7个字符
评:实在弄不清这个7有什么依据
但有一点可以肯定
这种说法至少过时二十多年了
P 380
十六进制常数(以0x开头的数字序列)
评:常识性错误
P 380
长整型常数(在数字后加字符L或l)
评:什么叫“数字”?
概念性错误
P 380
(2)字符常量
用单撇号括起来的一个字符,可以使用转义字符
评:“一个字符”,错
P 380
(3)实型常量(浮点型常量)
评:实数类型和浮点类型是两个完全不同的概念
P 381
算术表达式
评:与其说是自创的概念倒不如说是捏造的概念
C语言没有这个概念也不需要这个概念
P 380
用单撇号括起来的一个字符,可以使用转义字符
评:“一个字符”,错
P 380
用单撇号括起来的一个字符,可以使用转义字符
评:“一个字符”,错
P 381
实型表达式:参见运算的运算量是实型量,运算过程中先转换成double型,结果为double型
评:这是在胡扯
P 381
3.表达式
(1)算术表达式
(2)逻辑表达式
(3)字位表达式
(4)强制类型转换表达式
(5)逗号表达式
(6)赋值表达式
(7)条件表达式
(8)指针表达式
评:这个分类很荒唐,除了带来混乱没有任何意义
其中对逻辑运算,逗号运算的描述是片面的
对条件表达式加了不必要的苛刻的约束
P 381
也可以是不包含任何运算的初等量
评:把 Primary expression 说成是 初等量
概念错误
Primary expression也并非不包含任何运算
P 381
数据定义
评:乱点鸳鸯
根本就没有“数据定义”这回事
P 381
对数据要定义其数据类型,需要时要指定其存储类别
评:常量也是数据
怎么定义类型?
怎么指定存储类别?
P 381
(1)类型标识符可用
评:类型标识符 这个说法不妥
此外列表中缺少若干type-specifier
P 382
结构体与共用体的定义形式为
评:这里说的应该是类型声明
而不是变量的定义
P 382
如不指定存储类别,作auto处理
评:明显忘了还有外部变量
P 383
7.语句
(1)表达式语句;
(2)函数调用语句;
……
评:逻辑混乱
此外缺少一种语句labeled-statement
P 383
(5)switch语句……
评:描述错误
P 384
8.预处理命令
评:不全
P 384
#include <math.h>
int abs(int x);
评:abs()不是在math.h中声明的
P 384
本书列出ANSI C标准建议提供的、常用的库函数
评:记得本书是号称“按照C99标准”
C99标准和ANSI C标准完全是两回事

而且所列也并不符合ANSI C标准
P 384
使用数学函数时,应该在该源文件中使用以下命令行:
评:在源文件中使用“命令行”
P 385
int rand(void) 产生—90到32767间的随机整数
评:这个函数不是数学函数
-90 是错误的
32767也有问题
P 386
int isalnum (int ch) ; 是字母或数字返回1,否则返回0
评:返回1的说法不对
后面其余在ctype.h中声明的10个函数的返回值都不正确
P 386
int iscntrl(int ch); 检查ch是否控制字符(其ASCII码在0和0x1F之间)
评:第1,执行环境不一定使用ASCII码
第2,即使执行环境使用ASCII码,控制字符也不仅限于“在0和0x1F之间”那些
P 386
在ox21到ox7E之间),不包括空格
评:把0写成了o是初学者很常见的错误
P 386
ox20到ox7E
评:o 0 不分
P 386
int tolower(int ch); 将ch转换为小写字母
评:ch本身不可能被转换
就是转换了也没用
这是函数调用的常识
P 386
int toupper(int ch); 将ch转换为大写字母
评:返回和转换不是一回事
P 387
3.输入输出函数
评:表格中的内容非但不是C99的
而且连ANSI C的都算不上
陈旧不堪,错漏百出
P 387
int fclose(FILE *fp); 有错返回非0,否则返回0
评:实际上应该是
The fclose function returns zero if the stream was successfully closed, or EOF if any errors were detected.
P 387
int fputc(char ch, FILE *fp); 成功,则返回该字符;否则返回非0
评:建议谭先生再次印刷时学学“金瓶梅”
把这些自己弄不清的地方都改为
“XXXXXX(此处删去78字)”
这样不但可以增加一些神秘感
书也会干净许多
P 387
fputs
评:很奇怪,这种东西居然能抄错
P 387
fread ……
fscanf……
评:错
P 388
fseek ……
……
评:388页一共罗列了14个函数的原型、功能、返回值
其中有4个非标准函数
除去这4个非标准函数没有考察
考察了其余10个标准函数
其中对futc()的介绍基本正确(但不够全面)
对其余9个的介绍全部有错
这9个函数为:
fseek
ftell
fwrite
getc
getchar
printf
putchar
puts
rename
P 389
rewind 将fp指示的文件中的位置指针置于文件开头位置,并清除文件结束标志和错误标志
评:位置指针,这个说法不当
清除错误标志,无根据。况且rewind调用本身就可能产生错误,从逻辑上讲,它怎么也不可能清除错误标志
P 389
int scanf(char * format,args,…);
返回值:读入并赋值给args的数据个数,遇文件结束返回EOF,出错返回0
评:args,… : … 在C语言中有特定的含义,所以这样的描述很不规范

遇文件结束返回EOF,出错返回0 :这是错的
P 389
write 非ANSI 标准函数
评:对其正确性不予评价
但是一本自称“按照C99标准”的书把非标准的东西塞进来充数
无疑是货不对板以次充好的假冒伪劣
是对读者的欺骗
P 389
4.动态存储分配函数
目前有的C编译所提供的这类函数返回char指针
评:今夕是何年?
P 389
void *calloc(unsigned n, unsign size);
功能:分配n个数据项的内存连续空间,每个数据项的大小为size
返回值:……如不成功,返回0
评:unsign :
功能:calloc()的一个重要功能是初始化为0,压根没提
返回0:应为返回NULL。

此外,这是一个过时的原型
n和size的类型应该是size_t ,而size_t未必是unsigned类型
P 389
free
评:if the argument does not match a pointer earlier returned by the calloc, malloc, or
realloc function, or if the space has been deallocated by a call to free or realloc,
the behavior is undefined

压根没提
不过也难怪
老谭根本不懂什么是undefined behavior
undefined behavior他可以解释得津津有味
P 389
malloc
评:返回0:应为返回NULL。
unsigned应为size_t
P 389
realloc
评:没有提对原来存储区数据的处理情况
这使得realloc()看上去和malooc()毫无区别而失去意义
P 389
4.动态存储分配函数
评:“内存连续空间”、“内存单元”、“内存区”、“储存区”,其实指的都是一回事,希望能够统一术语。
这个看法是下面网页中的网友贡献的
http://zh.thqerrata.wikia.com/in … 9&variant=zh-cn
参考文献
P 389
[8]H M. Peitel,
评:应该是 H M. Deitel

(转载于网络,若侵权请联系删除)

举报

相关推荐

0 条评论