《C程序设计》(第四版)习题辅导错误汇集

P14
假如我国国民生产总值的年增长率为10%,计算10年后我国国民经济生产总值与现在相比增长多少百分比。计算公式为:
p=(1+r)^n
r为年增长率,n为年数,p为与现在相比的倍数。
解:从附录D(库函数)可以查到: 可以用pow函数求x^y的值,调用pow函数的具体形式是pow(x,y)。在使用pow函数时需要在程序的开头用#include指令将<math.h>头文件包含到本程序模块中。可以用下面的程序求出10年后国民生产总值是现在的多少倍。
#include <stdio.h>
#include <math.h>
int main()
{float p,r,n;
r=0.1;
n=10;
p=pow(1+r,n);
printf(“p=%f\n”,p);
return 0;
}
运行结果:
p=2.593742
即10年后国民生产总值是现在的2.593742倍。
评:这个解答至少存在如下几方面的问题:

1.驴唇不对马嘴
从小学开始,老师就会教导我们不要答非所问。这个程序犯的第一个错误就是答非所问。
题目中要求“计算10年后我国国民经济生产总值与现在相比增长多少百分比”,然而程序给出的结果却是“10年后国民生产总值是现在的2.593742倍”。程序错没错,小学生都知道。
写程序最基本的一个常识是,要满足功能要求。不能装疯卖傻,指东打西。
把功能要求撇在一边,自说自话、自娱自乐且像盲人骑瞎马一样地写代码,是头脑不清的表现。这样的代码把小学老师多年的辛勤教诲毁之于一旦,是教小朋友学坏。

2.古怪的常数
10%、10这两个数是问题给出的条件,这种数据通常应该写成符号常量的方式:
#define GROWTH_RATE ((double)10/(double)100)
#define YEARS 10
这才是良好的编程习惯。这样的好处至少有以下三点:
1.在某种程度上实现了数据与代码的分离,“把上帝的还给上帝,把魔鬼的交给魔鬼”。这是现代程序设计的一个基本思想。那种把数据和代码不分青红皂白地搅和在一起“乱炖”的写法,是缺乏基本编程素养的表现。
2.代码更具有可读性。显然“r=GROWTH_RATE;”的写法要比“r=0.1;”要好的多。
3.便于测试。只要将
#define YEARS 10
稍做修改就可以测试其他年后(比如1年后)的情况。注意,这个修改是在预处理命令部分进行的,因此对main()中的代码没有任何影响。这从另一个角度表明了把数据与代码分离开的优越性。

3.滥用变量
很容易看出,代码中的那个变量p是压根不必要的。这个p变量唯一起到的作用是记录“pow(1+r,n)” 的值完成输出,然而“pow(1+r,n)” 本身既然有值,为什么不直接输出呢?printf(“p=%f\n”,pow(1+r,n));不是很漂亮的一条语句吗?何必画蛇添足地定义一个p变量呢?
同理,r与n这两个变量也是毫无必要的,因为代码根本就不需要这两个值改变,pow(1+0.1,(double)10)的值和pow(1+r,n)完全一样。所以定义r与n这两个变量纯属于脱裤子放屁,多此一举,是糊里糊涂、莫名其妙的写法。
或问,pow(1+r,n)写起来不是比 pow( 1 + 0.1 ,(double)10)更简洁么?不然。常量就是常量,变量就是变量,把常量的值赋值给变量,利用变量的值进行运算事实上增加了代码出错的风险,因为变量的值可能无意中被错误地被改变,并且毫无补偿地浪费内存资源。用增加错误风险并浪费内存资源的代价来换取代码的简洁不是正路子。

4.数据类型的问题
代码中的p、n、r被定义成了float类型,然而 0.1、pow(1+r,n)都是double类型,在
r=0.1
p=pow(1+r,n)
这两个赋值表达式中基本上必然会产生精度损失。这种精度牺牲非常无谓,没有任何回报。

5.文件名的问题
题解中说“将<math.h>头文件包含到本程序模块中”,抱歉,谭大爷,“<”和“>”怎么竟然成了文件名的一部分?(这两个符号可以出现在文件名中吗?——此句有误,删除)那是预处理命令的成分,根本不是文件名的组成成分。

6.不规范
C标准中提到main()的写法有两种
main(void){//}

main(int argc,char *agrv[]){/
/}
把“()”内的形参省略,不符合规范。

最后,给出一个参考代码
#include <stdio.h>
#include <math.h>

#define GROWTH_RATE ( (double)10 / (double)100 )
#define YEARS 10

int main( void )
{

printf("%d年后我国国民经济生产总值比现在增长 %f%%\n" , 
        YEARS , 
        ( pow ( 1. + GROWTH_RATE , ( double ) YEARS ) - 1. ) / 1. * 100. 
      ) ;

return 0;

}

P14~16
2.存款利息的计算。有1000元,想存5年,……
(1)一次存5年期。
……
5年期定期存款利息为5.85%;
……
运行结果:
p1=5292.500000
……
评:想钱想疯了是怎么的
很想问一下,谭大爷是在哪家银行存钱的?
许霆可是给人家判了个无期的,您这个要是真敢去取估计判您个恶意取款是一点问题都没有的

P17
……
float d=300000,
……
评:这是很糟糕的写法
在一定条件下会发生错误
P17
只要知道:用char类型变量时,给它赋的值应在0~127范围内
评:作茧自缚
C语言根本没有这个限制

P18
int c1,c2;
c1=197;
c2=198;
printf(“c1=%c,c2=%c”,c1,c2);
评:谭认为
printf(“c1=%c,c2=%c”,c1,c2);的输出结果“不可预料”
这是错误的
P18
scanf(“a=%db=%d”,&a,&b);
评:变态和误导
P18

只要知道:用char类型变量时,给它赋的值应在0~127范围内
评:作茧自缚
C语言根本没有这个限制

P19
关于习题解答(习题6)
评:滥用变量
P19
7.设圆半径r=1.5,……求……圆球表面积,圆球体积……
P20~23
关于习题解答(习题8)
评:把溢出行为当成正常行为

P25~26
从键盘输入一个小于1000的正数,要求输出它的平方根(如平方根不是整数,则输出其整数部分)。要求在输入数据后先对其进行检查是否为小于1000的正数。若不是,则要求重新输入
……将程序改为多次检查,直到正确输入为止。程序如下:
#include <stdio.h>
#include <math.h>
#define M 1000
int main()
{
int i,k;
printf(“请输入一个小于%d的整数i:”,M);
scanf(“%d”,&i);
while(i>M)
{printf(“输入的数据不符合要求,请重新输入一个小于%d的整数i:”,M);
scanf(“%d”,&i);
k=sqrt(i);
}
printf(“%d的平方根的整数部分是%d\n”,i,k);
return 0;
}
评:错误百出
首先,输入 -1 ,竟然能输出结果
其次,输入100,输出一个莫名其妙的结果
第三,题目:“输入一个小于1000的正数”,代码:“i>M”,
第四,scanf()写了两次,啰嗦
第五,sqrt()并不能保证结果>=平方根
第六,double sqrt(double), sqrt(i)的写法不严谨

这种“学习辅导”让学习者情何以堪?

评:首先说说题目。编程的题目一般有两种形式:要么描述程序的行为,仅仅要求编程者设计代码;要么纯粹地只描述问题,程序的行为及代码都由编程者设计。前者相当于给出了软件规格说明(Software Specification)。后者则相当于提出了一个软件需求。这个题目无疑属于前一种情况。
软件规格说明应当对软件应满足的要求,以可验证的方式作出完全、精确的描述。
但是题目中的“从键盘输入一个小于1000的正数”却并不是一个完全、精确的描述。程序员在看到这个要求之后不可能知道这个数据究竟应该具有什么样的性质,这个“正数”究竟是整数还是小数?如果不清楚这个,在代码中就无法在确定这个数据的类型。当然也不可能完成程序。
那么,自作聪明地假设一个怎么样?对不起,这是一种职业恶习,完全背离程序员的职业的基本准则。如果学习编程的结果是养成了一种职业恶习,显然与学习编程的初衷南辕北辙。因此这样的题目简直就是打着红旗反红旗。
描述程序的行为的题目中可能涉及到输入。输入是由程序用户完成的,用户的输入可能正确也可能有错误。
如果考虑到程序的强健性,题目通常要求程序考虑用户输入有错误的情况。如果不考虑程序的强健性,那么代码可以只考虑用户输入没有错误的情况。通常,题目要么要求编程者考虑强健性,要么不考虑强健性。
从题目中的“检查是否为小于1000的正数”来看,题目显然是要求程序具有一定的强健性。然而题目却没有说明在输入不满足的情况下程序应有的行为——即第二次输入不小于1000的正数时程序的行为,所以这个题目本身就是不完整的。
既然要求考虑健壮性,就应该把这个意图贯彻始终。不能虎头蛇尾,前后自相矛盾。因而针对用户输入不满足的“正数”的情况下程序的行为,题目也应该给出相应的说明。然而题目中对此却只字未提,毫无疑问,这个题目本身就是一个不合格的题目。
求解烂题危害是很大,因为烂题本身就是违背程序员根本职业要求——周密、严谨。解这样的题目,一无所获不说,反而有伤自身的素质,而得到的代码也必然似是而非,经不起推敲。
测试一下前面引文中的代码就不难发现,当输入“-1”时,程序立刻崩溃——这个程序无比脆弱。然而自相矛盾的是题目却暗示需要考虑程序的健壮性。
此外当第一次输入大于等于1000的数,且第二次依然输入大于等于1000的数的情况下,程序依然能给出结果。代码中的那句if语句在这种情况下竟然毫无意义。这样的代码毫无价值。
结论就:路边的野花不要采,书上的滥题不要做。初学者,乱做习题你伤不起啊!!!!!!!
此外需要指出的是,代码中的
“k=sqrt(i);”
也是一种武断的错误写法,它是建立在sqrt(i)得到的值一定大于或等于i的平方根这个假设之上的,然而这个假设没有任何依据。
“要求输出它的平方根(如平方根不是整数,则输出其整数部分)”,这句话也说的缺乏素质,至少不够简洁。无非就是输出它平方根的整数部分么,如此简单的意思怎么会说的那么复杂且啰嗦不清呢

评:这题目设计的漏洞百出,莫名其妙
而且在刚刚学完选择结构的条件下根本没办法完成

“从键盘上输入一个正数”是一个不明确的要求,输入整数还是实数?
“要求输出它的平方根(如平方根不是整数,则输出其整数部分)”更是啰嗦的不成话,无非是输出其平方根的整数部分而已
对于不可能求解的题目,老谭给出的解答
#include <stdio.h>
#include <math.h>
#define M 1000
int main()
{
int i,k;
printf(“请输入一个小于%d的整数i:”,M);
scanf(“%d”,&i);
if(i>M)
{printf(“输入的数据不符合要求,请重新输入一个小于%d的整数i:”,M);
scanf(“%d”,&i);
}
k=sqrt(i);
printf(“%d的平方根的整数部分是%d\n”,i,k);
return 0;
}
评:当输入一个负值的时候,立刻崩溃
而且如果两次输入都是大于1000的情况下,依然能输出结果
P29
70~70分为’C’

P30~31
9.给一个不多于5位的正整数,要求:
1)求出它是几位数;
2)分别输出每一位数字;
3)按逆序输出各位数字,例如原数为321,应输出123.
解:程序如下:
#include <stdio.h>
#include <math.h>
int main()
{
int num,indiv,ten,hundred,thousand,ten_thousand,place;
//分别代表个位,十位,百位,千位,万位和位数
printf(“请输入一个整数(0-99999):”);
scanf(“%d”,&num);
if(num>9999)
place=5;
else if(num>999)
place=4;
else if(num>99)
place=3;
else if(num>9)
place=2;
else place=1;
printf(“位数:%d\n”,place);
printf(“每位数字为:”);
ten_thousand=num/10000;
thousand=(int)(num-ten_thousand10000)/1000;
hundred=(int)(num-ten_thousand
10000-thousand1000)/100;
ten=(int)(num-ten_thousand
10000-thousand1000-hundred100)/10;
indiv=(int)(num-ten_thousand10000-thousand1000-hundred100-ten10);
switch(place)
{case 5:printf(“%d,%d,%d,%d,%d”,ten_thousand,thousand,hundred,ten,indiv);
printf(“\n反序数字为:”);
printf(“%d%d%d%d%d”,indiv,ten,hundred,thousand,ten_thousand);
break;
case 4:printf(“%d,%d,%d,%d”,thousand,hundred,ten,indiv);
printf(“\n反序数字为:”);
printf(“%d%d%d%d”,indiv,ten,hundred,thousand);
break;
case 3:printf(“%d,%d,%d”,hundred,ten,indiv);
printf(“\n反序数字为:”);
printf(“%d%d%d”,indiv,ten,hundred);
break;
case 2:printf(“%d,%d”,ten,indiv);
printf(“\n反序数字为:”);
printf(“%d%d”,indiv,ten);
break;
case 1:printf(“%d”,indiv);
printf(“\n反序数字为:”);
printf(“%d”,indiv);
break;
}
return 0;
}
评:1.似是而非
例如:
thousand=(int)(num-ten_thousand*10000)/1000;
#include <math.h>

2.拖泥带水
例如那个switch语句

3.标识符有点怪异。
英语不好,不敢妄加评论,呵呵
但发现只要写的像英文单词,总是受到格外的宽容。

P35
有4个圆塔,圆心分别为(2,2),(-2,2),(-2,-2),(2,-2),圆半径为1,见图4.5。这4个塔的高度为10m,塔以外无建筑物。今输入任一点的坐标,求该点的建筑物高度(塔外的高度为零)。
#include <stdio.h>
int main( )
{
int h=10;
float x1=2,y1=2,x2=-2,y2=2,x3=-2,y3=-2,x4=2,y4=-2,x,y,d1,d2,d3,d4;
printf(“请输入一个点(x,y):”);
scanf(“%f,%f”,&x,&y);
d1=(x-x4)(x-x4)+(y-y4)(y-y4);//求该点到各中心点距离 d2=(x-x1)(x-x1)+(y-y1)(y-y1);
d3=(x-x2)(x-x2)+(y-y2)(y-y2);
d4=(x-x3)(x-x3)+(y-y3)(y-y3);
if(d1>1&&d2>1&&d3>1&&d4>1) h=0 ; //判断该点是否在塔外
printf(“该点高度为%d\n”,h);
return 0;
}
评:int h=10;
float x1=2,y1=2,x2=-2,y2=2,x3=-2,y3=-2,x4=2,y4=-2
这些都是常量,没有理由设置变量

d1=(x-x4)(x-x4)+(y-y4)(y-y4); //求该点到各中心点距离
量纲不对,这不是距离

d1>1&&d2>1&&d3>1&&d4>1
概念错误。仿佛用面积与长度相比较
P37~38
1.请画出例5.6中给出的3个程序段的流程图。
……
图5.1
……
图5.2
……
图5.3
评:终于有幸见到传说中史前的“耗子窝”和“烂面条”了
比见到恐龙复活还惊奇

P40
输入两个正整数,求其最大公约数和最小公倍数。
#include <stdio.h>
int main( )
{
int p , r , n , m , temp ;
printf(“请输入两个正整数n,m:”);
scanf(“%d,%d,”,&n,&m);
if(n<m)
{
temp=n;
n=m;
m=temp;
}
p=nm;
while(m!=0)
{
r=n%m;
n=m;
m=r;
}
printf(“它们的最大公约数为:%d\n”,n);
printf(“它们的最小公倍数为:%d\n”,p/n );
return 0;
}
评:经典的算法,竟然给糟蹋成这样
那个比较交换完全没必要
后面的while语句也写的拖泥带水
p=n
m;也不够好,使代码的适用范围降低
下面的写法是常识性的写法
while((r=m%n)!=0)
{
m=n;
n=r;
}
P42~43
100 50 10
7.求 ∑ k + ∑k^2 + ∑ (1/k)
k=1 k=1 k=1
#include <stdio.h>
int main()
{
int n1=100,n2=50,n3=10;
double k,s1=0,s2=0,s3=0;
for(k=1;k<=n1;k++)
{s1=s1+k;}
for(k=1;k<=n2;k++)
{s2=s2+k*k;}
for(k=1;k<=n3;k++)
{s3=s3+1/k;}
printf(“sum=%15.6f\n”,s1+s2+s3);
return 0;
}
评:最大的毛病是用double类型做循环的计数器变量
记数应该用整数类型
这是一个编程常识

n1,n2,n3明显是画蛇添足
连变量常量都没弄清楚

s1,s2,s3也没必要那么多

{sl=sl+k;}
这种怪异的风格不伦不类
搞不清写{}的意义何在

此外
s1=s1+k
应该写成
s1+=k;

p43~44
9.一个数如果恰好等于它的因子之和,这个数就称为“完数”。例如,6的因子为1,2,3,而6=1+2+3,因此6是“完数”。编程找出1000之内所有完数,并按下面格式输出其因子:
6 ,Its factors are 1 2 3
解:方法一。
程序如下:
#define M 1000 //定义寻找范围
#include <stdio.h>
int main()
{
int k1,k2,k3,k4,k5,k6,k7,k8,k9,k10;
int i,a,n,s;
for(a=2;a<=M;a++) //a是2~1000之间的整数,检查它是否完数
{n=0; //n用来累计a的因子的个数
s=a; //s用来存放尚未求出的因子之和,开始时等于a
for(i=1;i<a;i++) //检查i是否a的因子
if(a%i0) //如果i是a的因子
{n++; //n加1,表示新找到一个因子
s=s-i; //s减去已找到的因子,s的新值是尚未求出的因子之和
switch(n) //将找到的因子赋给k1~k9,或k10
{case 1:
k1=i;break; //找到的第1个因子赋给k1
case 2:
k2=i;break; //找到的第2个因子赋给k2
case 3:
k3=i;break; //找到的第3个因子赋给k3
case 4:
k4=i;break; //找到的第4个因子赋给k4
case 5:
k5=i;break; //找到的第5个因子赋给k5
case 6:
k6=i;break; //找到的第6个因子赋给k6
case 7:
k7=i;break; //找到的第7个因子赋给k7
case 8:
k8=i;break; //找到的第8个因子赋给k8
case 9:
k9=i;break; //找到的第9个因子赋给k9
case 10:
k10=i;break; //找到的第10个因子赋给k10
}
}
if(s
0)
{
printf(“%d,Its factors are “,a);
if(n>1)printf(”%d,%d”,k1,k2); //n>1表示a至少有2个因子
if(n>2)printf(“,%d”,k3); //n>2表示a至少有3个因子
if(n>3)printf(“,%d”,k4); //n>3表示a至少有4个因子
if(n>4)printf(“,%d”,k5); //以下类似
if(n>5)printf(“,%d”,k6);
if(n>6)printf(“,%d”,k7);
if(n>7)printf(“,%d”,k8);
if(n>8)printf(“,%d”,k9);
if(n>9)printf(“,%d”,k10);
printf(“\n”);
}
}
return 0;
}

评:这代码!丑的都能惊动D中秧了
1.题目本身是错的:“一个数如果恰好等于它的因子之和”

6的因子为1,2,3 6的因子还有6

2.int k1,k2,k3,k4,k5,k6,k7,k8,k9,k10;

这个巨生猛,一口气生了10个,颇有愚公移山的气概,吃奶的力气都使出来了
为什么偏偏定义10个呢?莫名其妙

3.s=a; //s用来存放尚未求出的因子之和,开始时等于a
实际上s不是注释中所说的含义
赋值为a的做法也非常笨拙

  1.   if(a%i==0)                   //如果i是a的因子 
    
    {n++;
    雷人的风格

5.6
switch(n) //将找到的因子赋给k1~k9,或k10
{case 1:

“将找到的因子赋给k1~k9,或k10 ”,看起来不像中国话
case 1:的位置也很扎眼

if(n>1)printf(“%d,%d”,k1,k2); //n>1表示a至少有2个因子
居然能一口气连写9句
作者耐力很好

if(n>3)printf(“,%d”,k4); //n>3表示a至少有4个因子
if(n>4)printf(“,%d”,k5); //以下类似
原来注释还能这么写

更滑稽的是运行结果
6,Its factors are 1,2,3
28,Its factors are 1,2,4,7,14
496,Its factors are 1,2,4,8,16,31,62,124,248
根本不符合题目的要求
按下面格式输出其因子:
6 ,Its factors are 1 2 3
P47~48
用迭代法求x=√a。求平方根的迭代公式为
xn+1=1/2(xn+a/xn)
要求前后两次求出的x的差的绝对值小于10^-5。
#include <stdio.h>
#include <math.h>
int main( )
{
float a,x0,x1;
printf(“enter a positive number:”);
scanf(“%f”,&a);
x0=a/2;
x1=(x0+a/x0)/2;
do
{x0=x1;
x1=(x0+a/x0)/2;
}while(fabs(x0-x1)>=1e-5);
printf(“The square root of %f is %f\n”,a,x1);
return 0;
}
评:1.重复笨拙
x1=(x0+a/x0)/2;
do
{x0=x1;
x1=(x0+a/x0)/2;
2.fabs(x0-x1)>=1e-5 基本没有意义,因为float类型的精度非常有限

P51~52
16.输出以下图案:
*
***
*******


  *******
    ***
     *

#include <stdio.h>
int main()
{int i,j,k;
for(i=0;i<=3;i++)
{for(j=0;j<=2-i;j++)
printf(" “);
for(k=0;k<=2i;k++)
printf("
”);
printf(“\n”);
}
for(i=0;i<=2;i++)
{for(j=0;j<=i;j++)
printf(" “);
for(k=0;k<=4-2i;k++)
printf("
”);
printf(“\n”);
}
return 0;
}

评:生动地介绍了如何把一个极其简单的问题弄得无比复杂的方法

实际上这个题目只要
#include <stdio.h>
int main()
{
printf(" * \n");
printf(" *** \n");
printf(" ******* \n");
printf(“*********\n”);
printf(" ******* \n");
printf(" *** \n");
printf(" * \n");
return 0;
}
就可以了

回复 1453# pmerofc
这个貌似把后面的几个printf都去掉更好看一些

是的。谢谢
下面写法更好
#include <stdio.h>
int main()
{
printf( " * \n"
" *** \n"
" ******* \n"
“*********\n”
" ******* \n"
" *** \n"
" * \n" );

return 0;
}
P52~53
17.两个乒乓球队进行比赛,各出3人。甲队为A、B、C 3人,乙队为X、Y、Z 3人。已抽签决定比赛名单。有人向队员打听比赛的名单。A说他不和X比,C说他不和X、Y比。请编程找出3对赛手的名单。
#include <stdio.h>
int main()
{
char i,j,k;
for(i=‘x’;i<=‘z’;i++)
for(j=‘x’;j<=‘z’;j++)
if(i!=j)
for(k=‘x’;k<=‘z’;k++)
if(i!=k&&j!=k)
if(i!=‘x’&&k!=‘x’&&k!=‘z’)
printf(“A–%c\nB–%c\nC–%c\n”,i,j,k);
return 0;
}
评:又是用最复杂最笨拙的方法解决最简单的问题
这道题小学生心算都能答出来
描述计算过程并也不复杂
根本没必要搞成6层循环与条件语句的嵌套
这个代码的风格也极烂

第6章
P54
1.用筛选法求100之内的素数。
评:自打中国人民知道陈景润起就知道了数学里有个筛法
筛法这个名字精简且信息充分
并且早已为大家所接受
老谭非要整出个“筛选法”
好像不这样就显不出“大师”具有独创性似的

解:所谓“筛选法”指的是“埃拉托色尼(Eratoshenes)筛法”。埃拉托色尼是古希腊的著名数学家。他采用的方法是,在一张纸上写上1~1000的全部整数,然后逐个判断它们是否素数,找出一个非素数,就把它挖掉,最后剩下的就是素数,见图6.1。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50……
图6.1
评:“求100之内的素数”,却要“在一张纸上写上1~1000的全部整数”
而且要“逐个判断”(那还是筛法吗?)
著名数学家的智商居然比小学生还要低
这是一个奇迹
不管你们信不信,反正我信

(2)用2除它后面的各个数,把能被2整除的数挖掉,即把2的倍数挖掉。
(3)用3除它后面各数,把3的倍数挖掉。
评:“把能被2整除的数挖掉,即把2的倍数挖掉”没有问题
但“用2除它后面的各个数”就太2了,
Eratosthenes还不至于如此愚蠢
“用3除它后面各数”同样愚蠢

上面的算法可表示为:
(1)挖去1;
评:这没什么问题,尽管也要看怎么挖

(2)用下一个未被挖去的数p除p后面的各数,把p的倍数挖掉;
评:“把p的倍数挖掉”是对的
但使用的手段——“除p后面的各数”则是极其愚蠢的。这种做法根本就能不称其为筛法

(3)检查p是否小于√n的整数部分(如果n=1000,则检查p<31是否成立),如果是,则返回(2)继续执行,否则就结束。
(4)……
评:这里的主要错误是次序,这个步骤应该在(2)的前面而不是在它后面
这次高铁追尾就有这么个特点,发车的次序反了

(4)分别用4,5……各数作为除数除这些数以后的各数。这个过程一直进行到在除数后面的数全部被挖掉为止。例如在图6.1中找150之间的素数,要一直进行到除数为47为止。事实上,可以简化,如果需要找1n范围内的素数,只须进行到除数为√n(取其整数)即可,例如对1~50,只须进行到将√7作为除数即可。请读者思考为什么?
评:4已经被你“挖掉”了,怎么做除数?
至于“为什么”“对1~50,只须进行到将√7作为除数即可”
我思考的结果是:
没有最蠢,只有更蠢

1.用筛选法求100之内的素数。
#include <stdio.h>
#include <math.h> //程序中用到求平方根函数sqrt
int main( )
{int i,j,n,a[101]; //定义a数组包含101个元素
for(i=1;i<=100;i++) //a[0]不用,只用a[1]~a[100]
a[i]=i; //使a[1]a[100]的值为1100
a[1]=0; //先“挖掉”a[1]
for(i=2;i<sqrt(100);i++)
for(j=i+1;j<=100;j++)
{if(a[i]!=0&&a[j]!=0)
if(a[j]%a[i]0)
a[j]=0; //把非素数“挖掉”
}
printf(“\n”);
for(i=2,n=0;i<=100;i++)
{ if(a[i]!=0) //选出值不为0的数组元素,即素数
{printf(“%5d”,a[i]); //输出素数,宽度为5列
n++; //累计本行已输出的数据个数
}
if(n
10)
{printf(“\n”);
n=0;
}
}
printf(“\n”);
return 0;
}

评:第4行:
int i,j,n,a[101]; //定义a数组包含101个元素
这个在合格的程序员看来绝对是自甘堕落的写法
但初学者对这种写法通常并不能引起什么反感
正规的写法应该是
int i,j,n,a[100];

第5~6行:
for(i=1;i<=100;i++) //a[0]不用,只用a[1]~a[100]
a[ i ]=i; //使a[1]a[100]的值为1100

这是继续堕落
“a[0]不用”,“不用”你定义它作甚?典型的“豆浆要两碗,喝一碗,倒一碗”
应该
for(i=0;i<100;i++)
a[ i ]=i+1;
第7行:
a[1]=0;
错倒没有,写得too naive

第8行:
for(i=2;i<sqrt(100);i++)
这行错的非常离谱
首先,表达式i<sqrt(100)中的<其实应该是<=,(把题目中的100换成121很容易发现这个马脚)
其次它的语意是每次循环都毫无意义地调用一下sqrt(),效率底下
(当然有的编译器可能会对此进行优化,但这不表明代码不垃圾)
最后没人保证i<sqrt(100)等价于i<10
第9~13行:
for(j=i+1;j<=100;j++)
{if(a[i]!=0&&a[j]!=0)
if(a[j]%a[i]==0)
a[j]=0; //把非素数“挖掉”
}
这里的j++和a[j]%a[i]极其笨拙
完全不是筛法
筛法并不需要逐个检验
也根本不需要做费时的求余运算

即使按照原来不合理的数据结构

第8~13行也应该写成
for(i=2 ; i*i<=100;i++)
if(a[i]!=0)
for(j=i+a[i];j<=100;j+=a[i])
a[j]=0;

才称得上是筛法
可以看到这里 j 并不是每次加1
而且不需要费时的%运算
效率方面天壤之别

第15~24行:
这几行没有什么错误
只是写的很傻
那个很次要的
if(n==10)
{printf(“\n”);
n=0;
}
很笨拙也很扎眼
P55~56
2.用选择法对10个整数排序。
#include <stdio.h>
int main( void )
{int i,j,min,temp,a[11];
printf(“enter data:\n”);
for(i=1;i<=10;i++)
{ printf(“a[%d]=”,i);
scanf(“%d”,&a[i]);
}
printf(“\n”);
printf(“The orginal numbers:\n”);
for(i=1;i<=10;i++)
printf(“%5d”,a[i]);
printf(“\n”);
for(i=1;i<=9;i++)
{ min=i;
for(j=i+1;j<=10;j++)
if(a[min]>a[j])min=j;
temp=a[i]; //以下3行将a[i+1]~a[10]中最小值与a[i]对换
a[i]=a[min];
a[min]=temp;
}
printf(“\nThe sorted numbers:\n”);
for(i=1;i<=10;i++)
printf(“%5d”,a[i]);
printf(“\n”);
return 0;
}

评:“以下3行将a[i+1]~a[10]中最小值与a[ i ]对换”
是错误的
实际上是将a[ i ]~a[10]中最小值与a[ i ]对换

需要一个10个元素的数组,却定义了int a[11],明显属于不上路子
for(i=1;i<=10;i++) 是半吊子写法

P56
3.求一个3×3的整型矩阵对角线元素之和。
评:这个题目本身就很成问题
因为一共有两条对角线
究竟是求那条对角线上的元素之和
还是求所有处于对角线上的元素之和是不明确的

int a[3][3],sum=0;
……
for(i=0;i<3;i++)
for(j=0;j<3;j++)
scanf(“%3d”,&a[ i ][j]);
……
评:那个3明显是愚蠢的作茧自缚

P57
如果将程序中的第7~9行改为
for(j=0;j<3;j++)
scanf(“%d%d%d”,&a[0][j],&a[1][j],&a[2][j]);
应如何输入?……
答案是可以按此方式输入,也可以不按此方式输入,而采用前面介绍的方式输入……
评:误导。这行代码和所替代的1601楼的代码功能完全不同
而且谭在这里的讨论毫无意义,因为这属于scanf()的用法,和数组没有关系

p57~58
4.有一个已排好序的数组,要求输入一个数后,按原来排序的规律将它插入数组中。
#include <stdio.h>
int main( )
{ int a[11]={1,4,6,9,13,15,19,18,40,100};
int temp1,temp2,number,end,i,j;
printf(“array a:\n”);
for(i=0;i<10;i++)
printf(“%5d”,a[ i ]);
printf(“\n”);
printf(“insert data:”);
scanf(“%d”,&number);
end=a[9];
if(number>end)
a[10]=number;
else
{for(i=0;i<10;i++)
{if(a[ i ]>number)
{temp1=a[ i ];
a[ i ]=number;
for(j=i+1;j<11;j++)
{temp2=a[j];
a[j]=temp1;
temp1=temp2;
}
break;
}
}
}
printf(“Now array a:\n”);
for(i=0;i<11;i++)
printf(“%5d”,a[ i ]);
printf(“\n”);
return 0;
}
评:很简单的题目
如此笨拙的写法倒是第一次看到
最可笑的就是
end=a[9];
if(number>end)
为什么不直接用number与a[9]比较呢
定义end这个变量并对其赋值极其无聊

{for(i=0;i<10;i++)
{if(a[ i ]>number)
{temp1=a[ i ];
a[ i ]=number;
for(j=i+1;j<11;j++)
{temp2=a[j];
a[j]=temp1;
temp1=temp2;
}
break;
}
}
}
评:笨拙且复杂的令人拍案惊奇
是我所见到过的最复杂的思路不清

p61
7.输出“魔方阵”。所谓魔方阵是指这样的方阵,它的每一行、每一列和对角线之和均相等。例如,三阶魔方阵为
8 1 6
3 5 7
4 9 2
要求输出1~n*n的自然数构成的魔方阵。
评:倒是很有兴趣见识一下老谭如何输出2阶魔方阵
p61
解:魔方阵中各数的排列规律如下:
(1)将1放在第1行的中间一列。
【因惨不忍睹,下面删去XXXX个字】

评:2阶魔方阵的中间一列是哪列?

p61~62
#include <stdio.h>
int main()
{ int a[15][15],i,j,k,p,n;
p=1;
while(p==1)
{printf(“Enter n(1–15):”);//要求阶数为1~15之间的奇数
scanf(“%d”,&n);
if((n!=0)&&(n<=15)&&(n%2!=0))
p=0;   
}
//初始化
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
a[i][j]=0;+++
//建立魔方阵
j=n/2+1;
a[1][j]=1;
for(k=2;k<=n*n;k++)   
{i=i-1;
j=j+1;
if((i<1)&&(j>n))
 {i=i+2;
j=j-1;
}
else   
{if(i<1) i=n;
if(j>n) j=1;
}
if(a[i][j]==0)
a[i][j]=k;   
else   
{i=i+2;   
j=j-1;   
a[i][j]=k;  
}
}
//输出魔方阵
for(i=1;i<=n;i++)  
{for(j=1;j<=n;j++)
  printf(“%4d”,a[i][j]);   
printf(“\n”);   
}
return 0;
}

其中一段:
p=1;
while(p==1)
{printf(“Enter n(1–15):”);//要求阶数为1~15之间的奇数
scanf(“%d”,&n);
if((n!=0)&&(n<=15)&&(n%2!=0))
p=0;   
}

评:这段的功能貌似是输入一个1~15之间的奇数给n(但题目中根本就没提到n应该是奇数)
写得啰嗦笨拙不说
压根就给写错了
这里就不详细解释错在哪里了
看不懂错在哪里的童鞋请举手

//初始化
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
a[i][j]=0;+++

评:这段错的更是无法无天
i=1,j=1
i<=n , j<=n 越界
最后居然还有个 +++这连编译都通过不了
可笑的是老谭居然给出了运行结果
老谭给出的结果只可能是伪造的

//建立魔方阵
j=n/2+1;
a[1][j]=1;
for(k=2;k<=n*n;k++)   
{i=i-1;
j=j+1;
if((i<1)&&(j>n))
 {i=i+2;
j=j-1;
}
else   
{if(i<1) i=n;
if(j>n) j=1;
}
if(a[i][j]==0)
a[i][j]=k;   
else   
{i=i+2;   
j=j-1;   
a[i][j]=k;  
}
}

评:如果看完这段代码你竟然没呕吐
那我算你狠!

//输出魔方阵
for(i=1;i<=n;i++) 
{for(j=1;j<=n;j++)
  printf(“%4d”,a[i][j]);   
printf(“\n”);   
}

评:如果你天真地在前面输入15
这段没让你死机就算你走运

代码后居然有运行结果(咋弄出来的呢)
接着下面一行小字
说明:魔方阵的阶数应为奇数。

啥叫坑爹啊
这就是

P63
8 找出一个二维数组中的鞍点,即该位置上的元素在该行上最大,在该列上最小。也可能没有鞍点。
评:这个题目本身就有毛病
自然语言中的“最大”、“最小”是一个含糊的说法
题目并没有明确这两个词的真正含义

解:一个二维数组最多只有一个鞍点,也可能没有。解题思路是:先找出一行中值最大的元素,然后检查它是否为该列的最小值,如果是,则是鞍点(不需要再找别的鞍点了),输出该鞍点;如果不是,则再找下一行的最大数……如果每一行的最大数都不是鞍点,则此数组无鞍点。
评:这个解题思路有着十分明显的逻辑漏洞
造成这个漏洞的原因就是它假定每行都存在最大值

#include <stdio.h>
#define N 4
#define M 5
int main()
{
int i,j,k,a[N][M],max,maxj,flag;
printf(“please input matrix:\n”);
for(i=0;i<N;i++)
for(j=0;j<M;j++)
scanf(“%d”,&a[ i][j]);
for(i=0;i<N;i++)
{max=a[ i][0];
maxj=0;
for(j=0;j<M;j++)
if(a[ i][j]>max)
{max=a[ i][j];
maxj=j;
}
flag=1;
for(k=0;k<N;k++)
if(max>a[k][maxj])
{flag=0;
continue;}
if(flag)
{printf(“a[%d][%d]=%d\n”,i,maxj,max);
break;
}
}
if(!flag)
printf(“It is not exist\n”);
return 0;
}

评:当输入
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1

程序输出为:
a[0][0]=1

就是说
要么这个数组存在20个鞍点(注:这和“一个二维数组最多只有一个鞍点”相矛盾)它只能求出一个
要么这个数组不存在鞍点但它居然硬给求出来了一个
无论怎样都是错的

记得老谭的这本书最早是91年出的
20年来居然连个鞍点都没找对
这真的是一个奇迹!
不管你信不信
反正我是信了

P65~66
#include <stdio.h>
#define N 15
int main()
{ int i,number,top,bott,mid,loca,a[N],flag,sign;
char c;
printf(“enter data:\n”);
scanf(“%d”,&a[0]);
i=1;
while(i<N)
{scanf(“%d”,&a[ i]);
if(a[ i]>=a[i-1])
i++;
else
printf(“enter this data again:\n”);
}
printf(“\n”);
for(i=0;i<N;i++)
printf(“%5d”,a[ i]);
printf(“\n”);
while(flag)
{printf(“input number to look for:”);
scanf(“%d”,&number);
sign=0;
top=0;
bott=N-1;
if((number<a[0])||(number>a[N-1]))
loca=-1;
while((!sign)&&(top<=bott))
{mid=(bott+top)/2;
if(numbera[mid])
{loca=mid;
printf(“Has found%d,its position id %d:\n”,number,loca+1);
sign=1;
}
else if(number<a[mid])
bott=mid-1;
else
top=mid+1;
}
if(!sign||loca
-1)
printf(“cannot find %d.\n”,number);
printf(“continue or not(Y/N)?”);
scanf(" %c",&c);
if(c==‘N’||c==‘n’)
flag=0;
}
return 0;
}
有15个数按由小到大顺序存放在一个数组中
评:表明对于程序来说这15个数构成的有序数组是一个已知的前提条件

但代码却擅自将其改为由用户输入

printf(“enter data:\n”);
scanf(“%d”,&a[0]);
i=1;
while(i<N)
{scanf(“%d”,&a[ i]);
if(a[ i]>=a[i-1])
i++;
else
printf(“enter this data again:\n”);
}
printf(“\n”);
评:表明代码作者要么根本就无视需求,要么根本就没读懂题目
这种情况
在小学生身上叫答非所问
在中学生身上叫不解题意
在软件工程叫曲解需求
在软件测试叫多做之过

while(flag)
{……
printf(“continue or not(Y/N)?”);
scanf(" %c",&c);
if(c==‘N’||c==‘n’)
flag=0;
}
评:是另一处多做之过
因为题目根本就没有要求
题目的要求是“输入一个数”
谭书最大的危害不是把C讲错了
而是破坏摧毁了小朋友本来就单薄脆弱的逻辑思维能力
脑残是怎样炼成的?
就是这样炼成的

P67
10.有一篇文章,共有3行文字,每行有80个字符。要求分别统计出其中英文大写字母、小写字母、数字、空格以及其他字符的个数。
评:这叫什么烂题啊
根本就没有说清楚程序的输入
P68
#include <stdio.h>
int main()
{int i,j,upp,low,dig,spa,oth;
char text[3][80];
upp=low=dig=spa=oth=0;
for(i=0;i<3;i++)
{printf(“please input line %d:\n”,i+1);
gets(text[ i]);
for(j=0;j<80&&text[ i][j]!=‘\0’;j++)
{if(text[ i][j]>=‘A’&&text[ i][j]<=‘Z’)
upp++;
else if(text[ i][j]>=‘a’&&text[ i][j]<=‘z’)
low++;
else if(text[ i][j]>=‘0’&&text[ i][j]<=‘9’)
dig++;
else if(text[ i][j]==’ ')
spa++;
else
oth++;
}
}
printf(“\nupper case:%d:\n”,upp);
printf(“lower case:%d:\n”,low);
printf(“digit :%d:\n”,dig);
printf(“space :%d:\n”,spa);
printf(“other :%d:\n”,oth);
return 0;
}
评:并非是“一篇文章”,也没有“3行文字”,更谈不上“每行有80个字符”

for(j=0;j<80&&text[ i][j]!=‘\0’;j++)中的
j<80&&text[ i][j]!=‘\0’ 是滑稽可笑的

运行结果:
please input line 1:
I am a student.
please input line 2:
123456
please input line 3:
ASDFG

upper case:6:
lower case:10:
digit :6:
space :3:
other :1:
评:不是有3行文字吗?
那other至少也应该是2啊(因为至少要有2个换行符)
怎么可能是1呢

此外在同一个for语句中完成输入和统计
char text[3][80];
就成了脱裤子放屁
因为只要
char text[80];
就完全可以了
P69
11.输出以下图案:
*****
*****
*****
*****
*****
解:程序如下:
#include <stdio.h>
int main()
{ char a[5]={‘‘,’’,‘‘,’’,‘*’};
int i,j,k;
char space=’ ';
for(i=0;i<5;i++)
{printf(“\n”);
printf(" “);
for(j=1;j<=i;j++)
printf(”%c",space);
for(k=0;k<=i;k++)
printf(“%c”,a[k]);
}
printf(“\n”);
return 0;
}
评:没把人给累死也把人给笨死了

12.有一行电文,已按下面规律译成密码:
A-Z a-z
B-Y b-y
C-X c-x
……
即第1个字母变成第26个字母,第i个字母变成第(26-i+1)个字母,非字母字符不变。要求编程序将密码译回原文,并输出密码和原文。
评:比较恶心的地方有:
两种几乎一模一样的代码(区别仅仅在于第一种多定义了一个本可以不定义的数组)
tran[j]=155-ch[j];
tran[j]=219-ch[j];
ch[j]=155-ch[j];
ch[j]=219-ch[j];
最恶心的一句是
ch[j]=ch[j];

p71
13.编一程序,将两个字符串连接起来,不要用strcat函数。
#include <stdio.h>
int main()
{ char s1[80],s2[40];
int i=0,j=0;
printf(“input string1:”);
scanf(“%s”,s1);
printf(“input string2:”);
scanf(“%s”,s2);
while(s1[ i]!=‘\0’)
i++;
while(s2[j]!=‘\0’)
s1[ i++]=s2[j++];
s1[ i]=‘\0’;
printf(“\nThe new string is:%s\n”,s1);
return 0;
}
评:基本写对了
难得

while(s2[j]!=‘\0’)
s1[ i++]=s2[j++];
s1[ i]=‘\0’;
应为
while( ( s1[i++]=s2[j++]) !=‘\0’)
;

P71~72
14.编一个程序,将两个字符串s1和s2比较,若s1>s2,输出一个正数;若s1=s2,输出0;若s1<s2,输出一个负数。不要用strcpy函数。……
#include <stdio.h>
int main()
{ int i,resu;
char s1[100],s2[100];
printf(“input string1:”);
gets(s1);
printf(“\ninput string2:”);
gets(s2);
i=0;
while((s1[ i]s2[ i])&&(s1[ i]!=‘\0’))i++;
if(s1[ i]
‘\0’&&s2[ i]‘\0’)
resu=0;
else
resu=s1[ i]-s2[ i];
printf(“\nserult:%d.\n”,resu);
return 0;
}
评:if(s1[ i]
‘\0’&&s2[ i]==‘\0’)
resu=0;
else
resu=s1[ i]-s2[ i];
非常滑稽

“不要用strcpy函数”
也很滑稽
比较字符串和strcpy有什么关系

p72
15.编写一个程序,将字符数组s2中的全部字符复制到字符数组s1中,不用strcpy函数。复制时,‘\0’也要复制过去。’\0’后面的字符不复制。
评:话都说不明白
颠三倒四
开始说“全部字符”
后来又“'\0’后面的字符不复制”
自相矛盾

不就是复制字符串吗

p72~73
#include <stdio.h>
#include <string.h>
int main()
{ char s1[80],s2[80];
int i;
printf(“input s2:”);
scanf(“%s”,s2);
for(i=0;i<=strlen(s2);i++)
s1[ i]=s2[ i];
printf(“s1:%s\n”,s1);
return 0;
}
评:还不如调用strcpy函数

P74
写两个函数,分别求两个整数的最大公约数和最小公倍数,用主函数调用这两个函数,并输出结果。两个整数由键盘输入。
评:题目本身就有问题
求两个整数的最大公约数和最小公倍数
本身若不是一个错误的问题就是一个问题的错误提法
P74~75
方法一:
#include <stdio.h>
int main()
{int hcf(int,int);
int lcd(int,int,int);
int u,v,h,l;
scanf(“%d,%d”,&u,&v);
h=hcf(u,v);
printf(“H.C.F=%d\n”,h);
l=lcd(u,v,h);
printf(“L.C.D=%d\n”,l);
return 0;
}

int hcf(int u,int v)
{int t,r;
if(v>u)
{t=u;u=v;v=t;}
while((r=u%v)!=0)
{u=v;
v=r;}
return(v);
}

int lcd(int u,int v,int h)
{
return(u*v/h);
}

评:首先把函数类型声明挤在main()中是一种拙劣的做法
其次,用l作为变量名违反了一般性的编程忌讳,况且l这个变量本来就是多余的
if(v>u)
{t=u;u=v;v=t;}
是废话,根本不需要
return(u*v/h);
是蠢话,不经意间缩小了函数的适用范围

恶劣的风格:
{int hcf(int,int);
{int t,r;
{t=u;u=v;v=t;}
{u=v;
v=r;}
赤裸的 scanf(“%d,%d”,&u,&v);及“,”
最荒唐的是
int lcd(int,int,int);
问题只提供两个数据
可这个函数竟然有三个参数
这样的函数毫无意义,是废品
#include <stdio.h>

unsigned gcd(unsigned,unsigned);
unsigned lcm(unsigned,unsigned);

int main( void )
{
int u,v;

printf(“输入两个正整数:”);
scanf(“%d%d”,&u,&v);

if ( u <= 0 || v <= 0 ){
printf(“无法计算\n”);
return 1;
}

printf(“最大公约数为=%u\n” , gcd(u,v) );
printf(“最小公倍数为=%u\n” , lcm(u,v) );

return 0;
}

unsigned gcd(unsigned u,unsigned v)
{
unsigned r;
while((r=u%v)!=0){
u = v ;
v = r ;
}
return v;
}

unsigned lcm(unsigned u,unsigned v)
{
return u / gcd(u,v) * v ;
}

P75~76
方法二:
#include <stdio.h>
int Hcf,Lcd;
int main()
{void hcf(int,int);
void lcd(int,int);
int u,v;
scanf(“%d,%d”,&u,&v);
hcf(u,v);
printf(“H.C.F=%d\n”,Hcf);
lcd(u,v);
printf(“L.C.D=%d\n”,Lcd);
return 0;
}

void hcf(int u,int v)
{int t,r;
if(v>u)
{t=u;u=v;v=t;}
while((r=u%v)!=0)
{u=v;
v=r;
}
Hcf=v;
}

void lcd(int u,int v)
{
Lcd=u*v/Hcf ;
}

评:这个写法比2130楼的“方法一”更蠢
外部变量用得非常恶劣,有百害而无一利
甚至,它根本不满足题目要求
这是在教唆初学者学坏

P76
求方程ax2+bx+c=0的根,用3个函数分别求当:b2-4ac大于0、等于0和小于0时的根并输出结果。从主函数输入a,b,c的值。
评:题目要求有点怪异
P76~77
#include <stdio.h>
#include <math.h>
float x1,x2,disc,p,q;
int main()
{void greater_than_zero(float,float);
void equal_to_zero(float,float);
void smaller_than_zero(float,float);
float a,b,c;
printf(“input a,b,c:”);
scanf(“%f,%f,%f”,&a,&b,&c);
printf("equation:%5.2fxx+%5.2fx+%5.2f=0\n",a,b,c);
disc=b
b-4ac;
printf(“root:\n”);
if(disc>0)
{
greater_than_zero(a,b);
printf(“x1=%f\t\tx2=%f\n”,x1,x2);
}
else if(disc==0)
{equal_to_zero(a,b);
printf(“x1=%f\t\tx2=%f\n”,x1,x2);
}
else
{smaller_than_zero(a,b);
printf(“x1=%f+%fi\tx2=%f-%fi\n”,p,q,p,q);
}
return 0;
}

void greater_than_zero(float a,float b)
{x1=(-b+sqrt(disc))/(2a);
x2=(-b-sqrt(disc))/(2
a);
}

void equal_to_zero(float a,float b)
{
x1=x2=(-b)/(2*a);
}

void smaller_than_zero(float a,float b)
{
p=-b/(2a);
q=sqrt(-disc)/(2
a);
}

评:垃圾得简直难以评说
有句名言,再好的程序设计语言也挡不住有人写出垃圾代码。
2146楼的代码完美地诠释了这一点
本来,函数是实施结构化程序设计的利器
但2146楼的代码却使得函数成了破坏或违反结构化程序设计的利器
而帮凶就是不伦不类的外部变量

评:垃圾得简直难以评说
首先
void greater_than_zero(float a,float b)
这个函数有两个参数迹近胡扯
因为解这个方程只两个参数根本就不充分
其次
看不出把float x1,x2,disc,p,q;作为外部变量
而把float a,b,c;作为局部变量的任何理由
如果暂时不考虑外部变量的危害
倒不如把 a,b,c同样作为外部变量

下面的代码比原来要强很多
说明了
原来把float x1,x2,disc,p,q;作为外部变量,而把float a,b,c;作为局部变量,完全是一种稀里糊涂的无厘头之举
是一种没有经过大脑的行为
因而也没有任何有益的效果
#include <stdio.h>
#include <math.h>
void greater_than_zero(void);
void equal_to_zero(void);
void smaller_than_zero(void);

float x1,x2,disc,p,q;
float a,b,c;

int main(void)
{
printf(“input a,b,c:”);
scanf(“%f,%f,%f”,&a,&b,&c);

printf("equation:%5.2fxx+%5.2fx+%5.2f=0\n",a,b,c);
disc=b
b-4ac;

printf(“root:\n”);
if(disc>0)
{
greater_than_zero();
printf(“x1=%f\t\tx2=%f\n”,x1,x2);
}
else if(disc==0)
{equal_to_zero();
printf(“x1=%f\t\tx2=%f\n”,x1,x2);
}
else
{smaller_than_zero();
printf(“x1=%f+%fi\tx2=%f-%fi\n”,p,q,p,q);
}
return 0;
}

void greater_than_zero(void)
{x1=(-b+sqrt(disc))/(2a);
x2=(-b-sqrt(disc))/(2
a);
}

void equal_to_zero(void)
{
x1=x2=(-b)/(2*a);
}

void smaller_than_zero(void)
{
p=-b/(2a);
q=sqrt(-disc)/(2
a);
}

是的。math.h提供的只是函数原型。C语言确实把函数原型、宏、类型分别写在不同的.h中,但这不意味着要求编译器分别提供若干个库
我的印象,谭书没有正式介绍过库的概念,在这方面,老谭一向是喜欢用空洞的概念搪塞的
而且,即使在库中,也谈不上“定义”,因为库是已经编译过了的东西,而“定义”则是代码层面的概念

从此不难看出那几个函数非常垃圾
同时外部变量也没有任何意义
不使用这两者
代码反而更好——至少更简洁
#include <stdio.h>
#include <math.h>

int main(void)
{
float x1,x2,disc,p,q;
float a,b,c;

printf(“input a,b,c:”);
scanf(“%f,%f,%f”,&a,&b,&c);

printf("equation:%5.2fxx+%5.2fx+%5.2f=0\n",a,b,c);
disc=b
b-4ac;

printf(“root:\n”);

if(disc>0)
{
x1=(-b+sqrt(disc))/(2a);
x2=(-b-sqrt(disc))/(2
a);
printf(“x1=%f\t\tx2=%f\n”,x1,x2);
}
else if(disc==0)
{
x1=x2=(-b)/(2a);
printf(“x1=%f\t\tx2=%f\n”,x1,x2);
}
else
{
p=-b/(2
a);
q=sqrt(-disc)/(2*a);
printf(“x1=%f+%fi\tx2=%f-%fi\n”,p,q,p,q);
}
return 0;
}

同时展示一下自己的垃圾代码供初学者临摹?

这个题目的正确提法应该是用函数求解方程并输出
#include <stdio.h>
#include <math.h>

void solve(double,double,double);

int main(void)
{
double x1, x2, disc, p, q;
double a, b, c;

printf("input a,b,c:");
scanf("%lf%lf%lf", &a, &b, &c);

printf("equation:%5.2f*x*x+%5.2f*x+%5.2f=0\n", a, b, c);
printf("root:\n");
solve(a,b,c);

return 0;

}

void solve( double a , double b , double c )
{
double disc , p , q ;

disc = b * b - 4. * a * c ;
p = -b / (2. * a) ;

if ( disc >= 0.0 ){
q = sqrt( disc ) / ( 2. * a );
printf(“x1=%f\tx2=%f\n”, p + q , p - q );
}
else {
q = sqrt( -disc ) / (2 * a);
printf(“x1=%f+%fi\tx2=%f-%fi\n”, p, q, p, q);
}
}

P77
写一个判素数的函数,在主函数输入一个整数,输出是否为素数的信息。
评:有个初学者前几天发现这个题目的解答有错误

P77~78
#include <stdio.h>
int main ()
{int prime (int);
int n;
printf (“input an integer:”);
scanf (“%d”,&n);
if (prime(n))
printf (“%d is a prime. \n”,n);
else
printf (“%d is not a prime. \n”,n);
return 0;
}
int prime (int n )
{int flag=1,i;
for (i=2;i<n/2&&flag1;i++)
if (n%i
0)
flag=0;
return (flag);
}

评:正如huangzhenfan这名初学者所指出的那样
按照这个程序
4也是素数

非但这样
其实按照这个程序
1也是素数

代码中的flag用得非常愚蠢
完全没有必要
而且既然题目中所说的是“整数”
应该考虑排除负整数的情况
这有两种写法
一种是由调用者排除
另一种是由函数判断
下面代码是后一种写法
但在工程实践中前一种写法可能更为常见
#include <stdio.h>

#define TRUE 1
#define FALSE 0

int prime (int);

int main ( void )
{
int n;

printf (“输入一个整数:”);
scanf (“%d”,&n);

printf (“%d%s是素数。\n” , n , prime (n)?“”:“不” ) ;
return 0;
}

int prime ( int n )
{
int i;

if ( n <= 1 )
return FALSE;

for ( i = 2 ; i <= n / 2 ; i++ )
if ( n % i == 0 )
return FALSE;

return TRUE ;
}

P78~79
4.写一个函数,使给定的一个3*3的二维整型数组转置,即行列互换。
#include <stdio.h>
#define N 3
int array[N][N];
int main()
{ void convert(int array[][3]);
int i,j;
printf(“input array:\n”);
for(i=0;i<N;i++)
for(j=0;j<N;j++)
scanf(“%d”,&array[i][j]);
printf(“\noriginal array:\n”);
for(i=0;i<N;i++)
{for(j=0;j<N;j++)
printf(“%5d”,array[i][j]);
printf(“\n”);
}
convert(array);
printf(“\nconvert array:\n”);
for(i=0;i<N;i++)
{for(j=0;j<N;j++)
printf(“%5d”,array[i][j]);
printf(“\n”);
}
return 0;
}
void convert(int array[][3])
{int i,j,t;
for(i=0;i<N;i++)
for(j=i+1;j<N;j++)
{t=array[i][j];
array[i][j]=array[j][i];
array[j][i]=t;
}
}

评:这段代码的愚蠢体现在
定义了一个外部数组
而void convert(int array[][3])居然同时使用了参数

#define N 3
……
void convert(int array[][3])
{int i,j,t;
for(i=0;i<N;i++)
for(j=i+1;j<N;j++)
{t=array[i][j];
array[i][j]=array[j][i];
array[j][i]=t;
}
}

函数里的两个N非常滑稽
同时使得形参中的那个3显得很荒唐
此外这个函数的参数不完整

更荒唐的是
2167楼的代码中居然出现了两段完全一模一样的代码(输出数组)
这种代码出现在函数这章
简直是一种讽刺

P79
写一个函数,使输入的一个字符串按反序存放,在主函数中输入和输出字符串。
#include <stdio.h>
#include <string.h>
int main()
{ void inverse(char str[]);
char str[100];
printf(“input string:”);
scanf(“%s”,str);
inverse(str);
printf(“inverse string:%s\n”,str);
return 0;
}
void inverse(char str[])
{char t;
int i,j;
for(i=0,j=strlen(str);i<(strlen(str)/2);i++,j–)
{t=str[ i];
str[ i]=str[j-1];
str[j-1]=t;
}
}

评:1.求字符串长度是件很简单的事情,j=strlen(str)完全没有必要,作为练习就更不应该
2.i<(strlen(str)/2)很愚蠢,非但不必而且费时
3.str[j-1]也很蹩脚,之所以如此蹩脚是因为一开始 j 的初始值就是错误的

这个题目可以简单地写为
#include <stdio.h>

void inverse(char str[]);

int main( void )
{
char str[100];

puts(“input string:”);
gets(str);

inverse(str);

puts(“inversed string:”);
puts(str);

return 0;
}

void inverse(char str[])
{
int i = 0 ,j = 0;

while(str[j]!=‘\0’)
j ++ ;

j – ; //'\0’之前的位置

while ( i < j )
{
char t;
t = str[ i];
str[i++] = str[j];
str[j–] = t;
}
}

P80
6.写一个函数,将两个字符串连接。
#include <stdio.h>
int main( void )
{ void concatenate(char string1[],char string2[],char string[]);
char s1[100],s2[100],s[100];
printf(“input string1:”);
scanf(“%s”,s1);
printf(“input string2:”);
scanf(“%s”,s2);
concatenate(s1,s2,s);
printf(“\nThe new string is %s\n”,s);
return 0;
}

void concatenate(char string1[],char string2[],char string[])
{ int i , j ;
for(i=0;string1[i]!=‘\0’;i++)
string[i]=string1[i];
for(j=0;string2[j]!=‘\0’;j++)
string[i+j]=string2[j];
string[i+j]=‘\0’;
}

评:1.在C语言中concatenate通常指把一个字符串接到另一个字符串后面,这个代码对连接的含义有误导之嫌,s这个字符数组没有必要
2.把string作为最后一个参数也不符合常规
3.
for(j=0;string2[j]!=‘\0’;j++)
string[i+j]=string2[j];
很蹩脚,不如
for(j=0;string2[j]!=‘\0’;j++,i++)
string[ i]=string2[j];

这个函数应该这样写
#include <stdio.h>

void concatenate(char [] , const char []);

int main( void )
{
char s1[100],s2[100];

puts(“input string1:”);
gets(s1);
puts(“input string2:”);
gets(s2);

concatenate(s1,s2);

puts("\nThe new string is ");
puts(s1);

return 0;
}

void concatenate(char str1[] , const char str2[])
{
int i = 0 , j = 0 ;

while(str1[i]!=‘\0’)
i ++ ;

while((str1[i++]=str2[j++])!=‘\0’)
;
}

写一个函数,将一个字符串中的元音字母复制到另一字符串,然后输出。
……
if(s[ i]‘a’||s[ i]‘A’||s[ i]‘e’||s[ i]‘E’||s[ i]‘i’||s[ i]‘I’||s[ i]==
‘o’||s[ i]‘O’||s[ i]‘u’||s[ i]==‘U’)
……

评:这段代码最可圈可点的就是这个罕见的、巨长无比的表达式
要是复制的辅音字母恐怕就更壮观无比了
我认为代码长一点没什么,良好的结构才是最重要的

P81
8.写一个函数,输入一个4位数字,要求输出这4个数字字符,但每两个数字之间空一个空格。如输入1990,应输出“1 9 9 0”
评:“一个4位数字”?天知道这是什么东西
要求也极其无聊

#include <stdio.h>
#include <string.h>
int main( void )
{void insert(char []);
char str[80];
printf(“input four digit:”);
scanf(“%s”,str);
insert(str);
return 0;
}
void insert(char str[])
{int i;
for(i=strlen(str);i>0;i–)
{str[2i]=str[i];
str[2
i-1]=’ ';
}
printf(“output:\n%s\n”,str);
}

评:1.函数要求能输入,insert()并没有实现这个要求
2.这题跟数字不数字的毫无关系,题目要求本身画蛇添足
3.不但如此,代码完成的方式同样画蛇添足,与题目配成了绝代双足

实际上只要简单地
#include <stdio.h>

void whatever( void );

int main( void )
{
whatever();
return 0;
}

void whatever( void )
{
int i ;
for( i = 0 ; i < 4 ; i++ )
{
putchar(getchar());
putchar(’ ');
}
}

9.编写一个函数,由实参传来一个字符串,统计此字符串中字母、数字、空格和其他字符的个数,在主函数中输入字符串以及输出上述的结果。
评:就谭书的结构安排和进度来说
在主函数中输出结果这个要求基本上是一个逼良为娼的要求

况且,即使从老谭的代码(2191楼)中,也搞不清楚题目中提到的“输入一个4位数字,要求输出这4个数字字符,但每两个数字之间空一个空格”究竟有什么意义,完全是废话

P82
#include <stdio.h>
int letter,digit,space,others;
int main()
{void count(char []);
char text[80];
printf(“input string:\n”);
gets(text);
printf(“string:”);
puts(text);
letter=0;
digit=0;
space=0;
others=0;
count(text);
printf(“\nletter:%d\ndigit:%d\nspace:%d\nothers:%d\n”,letter,digit,space,others);
return 0;
}
void count(char str[])
{int i;
for(i=0;str[i]!=‘\0’;i++)
if((str[i]>=‘a’&&str[i]<=‘z’)||(str[i]>=‘A’&&str[i]<=‘Z’))
letter++;
else if(str[i]>=‘0’&&str[i]<=‘9’)
digit++;
else if(str[i]== 32)
space++;
else
others++;
}

评:果然,使用了丑陋无比的外部变量。这就是逼良为娼几乎必然的后果
其他可吐槽的也很多

外部变量使得count()函数的参数变得很傻
还不如把char text[80];也作为外部变量
这样还免得传递参数

第10~13行的赋值更蠢
因为外部变量的初值本来就是0
如果把这几个清零看成必要的初始化
它们仍然愚蠢
因为这几个清零根本就不该在main()中进行
如果main()还需要统计若干字符串的话
难道再写几次清零的语句?
这几个清零的语句正确的位置应该在count()函数中

str[ i]== 32,不具备可移植性,可读性也很差,应该str[ i]== ’ ’

count()函数中for语句与if语句齐头并进,风格极其垃圾
P82~84
10.写一个函数,输入一行字符,将此字符串中最长的单词输出。
#include <stdio.h>
#include <string.h>
int main()
{int alphabetic(char);
int longest(char []);
int i;
char line[100];
printf(“input one line:\n”);
gets(line);
printf(“The longest word is:”);
for(i=longest(line);alphabetic(line[i]);i++)
printf(“%c”,line[i]);
printf(“\n”);
return 0;
}

int alphabetic(char c)
{
if((c>=‘a’&&c<=‘z’)||(c>=‘A’&&c<=‘z’))
return(1);
else
return(0);
}

int longest(char string[])
{int len=0,i,length=0,flag=1,place=0,point;
for(i=0;i<=strlen(string);i++)
if(alphabetic(string[i]))
if(flag)
{point=i;
flag=0;
}
else
len++;
else
{flag=1;
if(len>=length)
{length=len;
place=point;
len=0;
}
}
return(place);
}

评:所谓"一个函数"不知道是指的哪个
alphabetic()和longest()函数都没有实现题目要求

此外一个明显的BUG是
(c>=‘a’&&c<=‘z’)||(c>=‘A’&&c<=‘z’)

另外alphabetic()写的也过于愚蠢,其实只要
Int alphabetic(char c)
{
return (c>=‘a’&&c<=‘z’)
|| (c>=‘A’&&c<=‘Z’);
}

这个代码非但是逻辑混乱的,而且是错误的,甚至使用了垃圾值

P83
图 7.1
评:这个NS图是错误的
与2244楼的代码也根本对不上
P84
11.写一个函数,用“起泡法”对输入的10个字符按由小到大顺序排序。
评:如果一个函数只能对10个字符排序
这种函数几乎是没有任何意义的
按照这种愚昧的想法
对2个字符排序需要写一个函数
对3个字符排序需要另外再写一个函数
……
代码更荒谬
char str[N];
int main()
{
……
scanf(“%s”,&str);
……
}
P85
#include <stdio.h>
#include <string.h>
#define N 10
char str[N];
int main()
{void sort(char [ ]);
int i,flag;
for(flag=1;flag==1;)
{scanf(“%s”,&str);
if(strlen(str)>N)
printf(“string too long,input again!”);
else
flag=0;
}
sort(str);
printf(“string sorted:\n”);
for(i=0;i<N;i++)
printf(“%c”,str[i]);
printf(“\n”);
return 0;
}
void sort(char str[ ])
{int i,j;
char t;
for(j=1;j<N;j++)
for(i=0;(i<N-j)&&(str[i]!=‘\0’);i++)
if(str[i]>str[i+1])
{t=str[i];
str[i]=str[i+1];
str[i+1]=t;
}

}

评:有必要更正一下2249楼的说法
不是“代码更荒谬”
而是代码荒谬绝伦
for(flag=1;flag==1;)
{scanf(“%s”,&str);
if(strlen(str)>N)
printf(“string too long,input again!”);
else
flag=0;
}

flag,scanf(“%s”,&str);用得很蠢不说
费了这么大劲,代码还是错的
P87
输入10个学生5门课的成绩,分别用函数实现下列功能:
1.计算每个学生的平均分;
2.计算每门课的平均分;
3.找出所有50个分数中最高分数所对应的学生和课程;
4.计算平均分方差
评:“3.找出所有50个分数中最高分数所对应的学生和课程”

“所有”二字含义不清,属于题目本身的缺陷。题目没有说明存在并列最高分的情况应该如何处理

P88~90
#include <stdlib.h>
#define N 10
#define M 5
float score[N][M];
float a_stu[N],a_cour[M];
int r,c;
int main()
{int i,j;
float h;
float s_var(void);
float hightest( );
void input_stu(void);
void aver_stu(void);
void aver_cour(void);
input_stu();
aver_stu();
aver_cour();
printf(“\n NO. cour1 cour2 cour3 cour4 cour5 aver\n”);
for(i=0;i<N;i++)
{printf(“\n NO%2d”,i+1);
for(j=0;j<M;j++)
printf(“%8.2f”,score[i][j]);
printf(“%8.2f\n”,a_stu[i]);
}
printf(“\naverage:”);
for(j=0;j<M;j++)
printf(“%8.2f”,a_cour[j]);
printf(“\n”);
h=hightest( );
printf(“hightest:%7.2f NO%2d court%2d\n”,h,r,c);
printf(“variance %8.2f\n”,s_var());
return 0;
}

void input_stu(void)
{int i,j;
for(i=0;i<N;i++)
{printf(“\ninput score of student%2d:\n”,i+1);
for(j=0;j<M;j++)
scanf(“%f”,&score[i][j]);
}
}

void aver_stu(void)
{int i,j;
float s;
for(i=0;i<N;i++)
{for(j=0,s=0;j<M;j++)
s+=score[i][j];
a_stu[i]=s/5.0;
}
}

void aver_cour(void)
{int i,j;
float s;
for(j=0;j<M;j++)
{s=0;
for(i=0;i<N;i++)
s+=score[i][j];
a_cour[j]=s/(float)N;
}
}

float hightest( )
{float high;
int i,j;
high=score[0][0];
for(i=0;i<N;i++)
for(j=0;j<M;j++)
if(score[i][j]>high)
{high=score[i][j];
r=i+1;
c=j+1;
}
return(high);
}

float s_var(void)
{int i;
float sumx,sumxn;
sumx=0.0;
sumxn=0.0;
for(i=0;i<N;i++)
{sumx+=a_stu[i]a_stu[i];
sumxn+=a_stu[i];
}
return(sumx/N-(sumxn/N)
(sumxn/N));
}

评:主要有这样一些毛病:
1.使用外部变量
2.float hightest( ); 过时且与其他函数类型声明风格不统一
3.main()中粗细不分,结构混乱。把main()几乎弄成了一个专司输出的函数
4.几个函数没有参数,没有通用性,尤其是s_var(),没有参数是无论怎样都说不过去的
5.aver_stu() 中的5.0来历不明
6.在main()中声明函数对混乱起了令人无法轻视的作用
7.存在并列最高分时,只能输出一个

这种又臭又长的程序实在让人受不了
下不为例了
P91~92
15.写几个函数:
1.输入10个职工的姓名和职工号;
2.按职工号由小到大排序,姓名顺序也随之调整;
3要求输入一个职工号,用折半法找出该职工的姓名,从主函数输入要查找的职工号,输出该职工姓名
#include <stdio.h>
#include <string.h>
#define N 10
int main( void )
{void input(int num[],char name[][8]);
void sort(int num[],char name[][8]);
void search(int n,int num[],char name[][8]);
int num[N],number,flag=1,c;
char name[N][8];
input(num,name);
sort(num,name);
while(flag1)
{printf(“\ninput number to look for:”);
scanf(“%d”,&number);
search(number,num,name);
printf(“continue ot not(Y/N)?”);
getchar();
c=getchar();
if(c
’N’||c==‘n’)
flag=0;
}
return 0;
}

void input(int num[],char name[N][8])
{int i;
for(i=0;i<N;i++)
{printf(“input NO.:”);
scanf(“%d”,&num[i]);
printf(“input name:”);
getchar( );
gets(name[i]);
}
}

void sort(int num[],char name[N][8])
{int i,j,min,temp1;
char temp2[8];
for(i=0;i<N-1;i++)
{min=i;
for(j=i;j<N;j++)
if(num[min]>num[j])min=j;
temp1=num[i];
strcpy(temp2,name[i]);
num[i]=num[min];
strcpy(name[i],name[min]);
num[min]=temp1;
strcpy(name[min],temp2);
}
printf(“\n result:\n”);
for(i=0;i<N;i++)
printf(“\n %5d%10s”,num[i],name[i]);
}

void search(int n,int num[],char name[N][8])
{int top,bott,mid,loca,sign;
top=0;
bott=N-1;
loca=0;
sign=1;
if((n<num[0])||(n>num[N-1]))
loca=-1;
while((sign1)&&(top<=bott))
{mid=(bott+top)/2;
if(n
num[mid])
{loca=mid;
printf(“No.%d,his name is %s.\n”,n,name[loca]);
sign=-1;
}
else if(n<num[mid])
bott=mid-1;
else
top=mid+1;
}
if(sign1||loca-1)
printf(“%d not been found.\n”,n);
}

#include <stdio.h>
#define N 10
int main( void )
{
int num [N],number,flag=1,c;
char name[N][8];
/……/
while(flag1)
{printf(“\ninput number to look for:”);
scanf(“%d”,&number);
search(number,num,name);
printf(“continue ot not(Y/N)?”);
getchar();
c=getchar();
if(c
’N’||c==‘n’)
flag=0;
}
/……/
return 0;
}

评:又是flag!
看到flag总能从代码中闻到一股馊味
很显然,不应该用while语句,而应该用do-while语句
do
{
printf(“\ninput number to look for:”);
scanf(“%d”,&number);
search(number,num,name);
printf(“continue ot not(Y/N)?”);
getchar();
c=getchar();
if(c==‘N’||c==‘n’)
flag=0;
}
while(flag==1);

其次flag明显毫无必要
do
{
printf(“\ninput number to look for:”);
scanf(“%d”,&number);
search(number,num,name);
printf(“continue ot not(Y/N)?”);
getchar();
c=getchar();
}
while(!(c==‘N’||c==‘n’));

getchar();用的牵强蹩脚,且容错性差
应该
do{
printf(“\ninput number to look for:”);
scanf(“%d”,&number);
search(number,num,name);
printf(“continue ot not(Y/N)?”);
scanf(" %c",&c );
}
while( c!==‘N’ && c!=‘n’ );

P92
void input(int num[],char name[N][8])
{int i;
for(i=0;i<N;i++)
{printf(“input NO.:”);
scanf(“%d”,&num[i]);
printf(“input name:”);
getchar( );
gets(name[i]);
}
}

评:首先,参数不全
void input(int num[],char name[N][8])
这个N很垃圾,因为它毫无意义
for(i=0;i<N;i++)
这个N莫名其妙,使得函数丧失通用性
没有通用性的函数是废品函数
getchar( );
显得累赘多余,且效果可能并不干净
如果后面一定用gets()
这里应该
whille(getchar()!=‘\n’)
;

void sort(int num[],char name[N][8])
{int i,j,min,temp1;
char temp2[8];
for(i=0;i<N-1;i++)
{min=i;
for(j=i;j<N;j++)
if(num[min]>num[j])min=j;
temp1=num[i];
strcpy(temp2,name[i]);
num[i]=num[min];
strcpy(name[i],name[min]);
num[min]=temp1;
strcpy(name[min],temp2);
}
printf(“\n result:\n”);
for(i=0;i<N;i++)
printf(“\n %5d%10s”,num[i],name[i]);
}

评:参数不全,以及在函数内使用无厘头的常数N的错误同2276楼

最严重的错误是
strcpy(name[ i],name[min]);
是对strcpy()函数的错用
是潜藏着的BUG,可引起未定义行为
这里不可以用strcpy()函数
只能用memmove()
P93
void search(int n,int num[],char name[N][8])
{int top,bott,mid,loca,sign;
top=0;
bott=N-1;
loca=0;
sign=1;
if((n<num[0])||(n>num[N-1]))
loca=-1;
while((sign1)&&(top<=bott))
{mid=(bott+top)/2;
if(n
num[mid])
{loca=mid;
printf(“No.%d,his name is %s.\n”,n,name[loca]);
sign=-1;
}
else if(n<num[mid])
bott=mid-1;
else
top=mid+1;
}
if(sign1||loca-1)
printf(“%d not been found.\n”,n);
}

评:这部分主要问题是两个不必要的标志变量loca,sign
首先
loca=0; 毫无道理,没有任何意义
倒不如
loca=-1;
这样,只有在找到的情况下这个值才会改变
后面的if(sign1||loca-1)就可以简化为if(loca==-1)

void search(int n,int num[],char name[N][8])
{int top,bott,mid,loca,sign;
top=0;
bott=N-1;
loca=-1;
sign=1;
if((n<num[0])||(n>num[N-1]))
loca=-1;
while((sign1)&&(top<=bott))
{mid=(bott+top)/2;
if(n
num[mid])
{loca=mid;
printf(“No.%d,his name is %s.\n”,n,name[loca]);
sign=-1;
}
else if(n<num[mid])
bott=mid-1;
else
top=mid+1;
}
if(loca==-1)
printf(“%d not been found.\n”,n);
}

P94
16.写一个函数,输入一个十六进制数,输出相应的十进制数。
评:题目可笑
下面写法应该满足要求吧
void gun(void)
{
int i;
printf(“输入一个十六进制数:” ) ;
scanf(“%x”,&i);
printf(“相应的十进制数为%d\n”,i);
}
P94~96
16.写一个函数,输入一个十六进制数,输出相应的十进制数。
#include <stdio.h>
#include <stdlib.h>
#define MAX 1000
int main()
{ int htoi(char s[]);
int c,i,flag,flag1;
char t[MAX];
i=0;
flag=0;
flag1=1;
printf(“input a HEX number:”);
while((c=getchar())!=‘\0’&&i<MAX&&flag1)
{if(c>=‘0’&&c<=‘9’||c>=‘a’&&c<=‘f’||c>=‘A’&&c<=‘F’)
{flag=1;
t[i++]=c;
}
else if(flag)
{t[i]=‘\0’;
printf(“decimal number%d\n”,htoi(t));
printf(“continue or not?”);
c=getchar();
if(c==‘N’||c==‘n’)
flag1=0;
else
{flag=0;
i=0;
printf(“\ninput a HEX number:”);
}
}
}
return 0;
}
int htoi(char s[])
{ int i,n;
n=0;
for(i=0;s[i]!=‘\0’;i++)
{if(s[i]>=‘0’&&s[i]<=‘9’)
n=n16+s[i]-‘0’;
if(s[i]>=‘a’&&s[i]<=‘f’)
n=n
16+s[i]-‘a’+10;
if(s[i]>=‘A’&&s[i]<=‘F’)
n=n*16+s[i]-‘A’+10;
}
return(n);
}

评:竟然能写出 (c=getchar())!=‘\0’
实在雷人
#define MAX 1000很蠢
清楚地说明了什么叫心里没“数”
因为
char t[MAX];
htoi(t)
而htoi()原型是
int htoi(char s[]);

flag1 在main()中的主要作用有:
1.使代码变得混乱不堪
2.使代码显得重复啰嗦
3.表明作者虽然不会使用break语句也会写代码
因此删除flag1
#include <stdio.h>
#include <stdlib.h>
#define MAX 1000
int htoi(char s[]);
int main()
{
int c,i,flag;
char t[MAX];
i=0;
flag=0;

printf(“input a HEX number:”);
while((c=getchar())!=‘\0’&&i<MAX)
{
if(c>=‘0’&&c<=‘9’||c>=‘a’&&c<=‘f’||c>=‘A’&&c<=‘F’)
{
flag=1;
t[i++]=c;
}
else
if(flag)
{
t[i]=‘\0’;
printf(“decimal number %d\n”,htoi(t));
printf(“continue or not?”);
c=getchar();
if(c==‘N’||c==‘n’)
break;
else
{
flag=0;
i=0;
printf(“\ninput a HEX number:”);
}
}
}
return 0;
}

while((c=getchar())!=‘\0’&&i<MAX)
中,由于i的初值为0,i的变化只出现在 t[i++]=c; 之后,所以可以把这个条件移动到这条语句之后
(c=getchar())!='\0’是弱智才能想到写法,因为从键盘输入不可能使得getchar()的值为0,它起到的作用仅仅是c=getchar()
虽然如此,还是暂时对此不予理睬而把它等价地移入循环体内
#include <stdio.h>
#include <stdlib.h>
#define MAX 1000
int htoi(char s[]);
int main()
{
int c,i,flag;
char t[MAX];
i=0;
flag=0;

printf(“input a HEX number:”);
while( 1 )
{
if(!((c=getchar())!=‘\0’))
{
break ;
}
if(c>=‘0’&&c<=‘9’||c>=‘a’&&c<=‘f’||c>=‘A’&&c<=‘F’)
{
flag=1;
t[i++]=c;
if( !(i<MAX))
{
break ;
}
}
else
if(flag)
{
t[i]=‘\0’;
printf(“decimal number%d\n”,htoi(t));
printf(“continue or not?”);
c=getchar();
if(c==‘N’||c==‘n’)
break;
else
{
flag=0;
i=0;
printf(“\ninput a HEX number:”);
}
}
}
return 0;
}

现在总算可以看到这个晦涩不明的flag所起的作用了
1.如果一开始读入的不是十六进制数字字符,则产生一个死循环,直到输入十六进制数字字符
2.开始输入十六进制数字字符后,flag的值为1
3.在2之后,一旦输入非十六进制数字字符,则把这个字符改为’\0’写入字符数组,调用htoi()。
4.之后要求用户输入Y/N?如果是N则结束程序,否则重复1

这些功能要求完全没必要使用晦涩不明的flag
#include <stdio.h>
#include <stdlib.h>
#define MAX 1000
int htoi(char s[]);
int main()
{
int c,i;
char t[MAX];
i=0;

while( printf(“input a HEX number:”) )
{
do{ //跳过非十六进制字符
c=getchar();
if(c==‘\0’) //暂时保留原来代码中的愚蠢
return 1;
}
while(!((c>=‘0’&&c<=‘9’||c>=‘a’&&c<=‘f’||c>=‘A’&&c<=‘F’)));

  do{               //读十六进制字符 
     t[i++]=c;
     if( !(i<MAX))  //暂时保留原来代码中的愚蠢 
        return 1; ;
     c=getchar();   
  }
  while(c>='0'&&c<='9'||c>='a'&&c<='f'||c>='A'&&c<='F');
  
  t[i]='\0';
  printf("decimal number %d\n",htoi(t));  //输出转换结果  
  
  printf("continue or not?");             //询问是否继续 
  c=getchar(); 
  if(c=='N'||c=='n')
     break;
  else
     i = 0;
  printf("\n");

}

return 0;
}

进一步简化
#include <stdio.h>
#include <stdlib.h>
#define MAX 1000
int htoi(char s[]);
int main()
{
int c,i=0;
char t[MAX];

do
{
printf(“input a HEX number:”) ;
do{ //跳过非十六进制字符
c=getchar();
if(c==‘\0’) //暂时保留原来代码中的愚蠢
return 1;
}
while(!((c>=‘0’&&c<=‘9’||c>=‘a’&&c<=‘f’||c>=‘A’&&c<=‘F’)));

  do{               //读十六进制字符 
     t[i++]=c;
     if( !(i<MAX))  //暂时保留原来代码中的愚蠢 
        return 1; ;
     c=getchar();   
     if(c=='\0')    //暂时保留原来代码中的愚蠢 
        return 1;
  }
  while(c>='0'&&c<='9'||c>='a'&&c<='f'||c>='A'&&c<='F');
  
  t[i]='\0';
  printf("decimal number %d\n",htoi(t));  //输出转换结果  
  
  printf("continue or not?");             //询问是否继续 
  i = 0;
  c=getchar(); 

}
while( !(c==‘N’||c==‘n’) && printf(“\n”) );

return 0;
}

#include <stdio.h>
#include <stdlib.h>

#define MAX 1000

#define YES 1
#define NO 0

int htoi(char s[]);
int be_hex( int );
char read_until_hex ( void ) ;
void read_hex ( char [] , unsigned );
int ask_continue(void);

int main( void )
{
char t[MAX];

do
{
t[0] = read_until_hex () ; //跳过非十六进制字符
read_hex ( t , MAX ) ; //读十六进制字符

  printf( "decimal number %d\n", htoi(t) );  //输出转换结果  
  
  if( ask_continue() == NO )                 //询问是否继续 
     break;

}
while( 1 );

return 0;
}

int ask_continue(void)
{
int c;
printf(“continue or not?”);
c = getchar() ;
if(c==‘N’||c==‘n’)
{
return NO;
}
printf(“\n”);
}

void read_hex ( char t[] , unsigned max )
{
int i = 1 ;
int c ;
do
{
if( !( i < MAX ) ) //暂时保留原来代码中的愚蠢
exit(1) ;
c = getchar() ;
if ( c==‘\0’ ) //暂时保留原来代码中的愚蠢
exit(1) ;
if( be_hex( c ) == YES )
t [ i ++ ] = c ;
else
{
t[i] = ‘\0’;
return ;
}
}
while(1);

}

char read_until_hex ( void )
{
int c;
printf(“input a HEX number:”) ;
do{
c = getchar();
if ( c ==‘\0’ ) //暂时保留原来代码中的愚蠢
exit(1) ;
}
while( be_hex© == NO );
return c;
}

int be_hex( int c)
{
return (c>=‘0’&&c<=‘9’) ||
(c>=‘a’&&c<=‘f’) ||
(c>=‘A’&&c<=‘F’) ;
}

底子太差,大体上也只能改到这个地步了。
难怪有人说,不要试图修改垃圾代码,应该重写
如果可以用ungetc(),可以改得更好些(不过老谭的书没介绍这个函数,这里就不写了)
if ( c==‘\0’ ) exit(1) ;这样蠢话可以删去
此外老谭的代码还有一个很大的漏洞,就是压根没考虑到输入为负的情况

这个题目的代码可以这样写

首先,define MAX 为1000 是极其愚蠢的
应该
#define HEX_BIT 4 //一个十六进制数占4位
#define MAX ( sizeof(int) * ( CHAR_BIT / HEX_BIT ) + 1 + 1 ) // +、-,\0

跳过前面的非十六进制形式字符
可以
scanf(“%[^0123456789ABCDEFabcdef]“);
读入十六进制形式字符可以
scanf(”%9[0123456789ABCDEFabcdef]%
[^\n]”,hex_str);
这里的是MAX-1(暂且不考虑前面的正负号)

这个代码可以很简单地写为
#include <stdio.h>
#include <limits.h>

#define HEX_BIT 4 //一个十六进制数占4位
#define MAX ( sizeof(int) * ( CHAR_BIT / HEX_BIT ) + 1 + 1 ) // +、-,\0

int htoi(char []);

int main( void )
{
char hex_str[MAX];
char c;

do
{
printf(“input a HEX number:”);
scanf(“%[^0123456789ABCDEFabcdef]“);
scanf(”%10[0123456789ABCDEFabcdef]%
[^\n]”,hex_str);
printf(“decimal number %d\n”,htoi(hex_str));
printf(“continue or not?”);
scanf(" %c",&c);
}
while(!(c==‘N’||c==‘n’));

return 0;
}

int htoi(char s[])
{
int i,n=0;

for( i = 0 ; s[i] != ‘\0’ ; i++ )
{
if( s[i] >= ‘0’ && s[i] <= ‘9’ )
n = n16 + s[i] - ‘0’ ;
else if( s[i] >= ‘a’ && s[i] <= ‘f’ )
n = n
16 + s[i] - ‘a’ + 10 ;
esle if( s[i] >= ‘A’ && s[i] <= ‘F’ )
n = n*16 + s[i] - ‘A’ + 10 ;
}

return n ;
}

P96
17.用递归法将一个整数n转换成字符串。例如,输入483,应输出字符串“483”。n的位数不确定,可以是任意位数的整数。
评:“任意”两个字说明对计算机缺乏起码的认识
在计算机的词典中没有“任意”这两个字
P96~97
#include <stdio.h>
int main()
{ void convert(int n);
int number;
printf(“input an integer:”);
scanf(“%d”,&number);
printf(“output: “);
if(number<0)
{putchar(‘-’);putchar(’ ');
number=-number;
}
convert(number);
printf(”\n”);
return 0;
}

void convert(int n)
{int i;
if((i=n/10)!=0)
convert(i);
putchar(n%10+‘0’);
putchar(32);
}

运行结果:
input an integer:2345678
output: 2 3 4 5 6 7 8

input an integer:-345
output: - 3 4 5
评:这是指东打西,打哪算哪
1.程序压根就没有按照要求“将一个整数n转换成字符串”
2. 题目要求“例如,输入483,应输出字符串“483””,可结果呢,瞪着眼睛说瞎话
3.putchar(32); 是很恶心的写法
4.main()中的printf(“\n”);很蹩脚应该写在convert()中

P96~97

给出年、月、日,计算该日是该年的第几天
#include <stdio.h>
int main( )
{int sum_day(int month,int day);
int leap(int year);
int year,month,day,days;
printf (“input date(year,month,day):”);
scanf(“%d,%d,%d”,&year,&month,&day);
days=sum_day(month,day);
if(leap(year)&&month>=3)
days=days+1;
printf(“is the %dth day in this year.\n”,days);
return 0;
}

int sum_day(int month,int day)
{int day_tab[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int i;
for(i=1;i<month;i++)
day+=day_tab[ i];
return(day);
}

int leap(int year)
{int leap;
leap=year%40&&year%100!=0||year%400;
return(leap);
}
评:感觉最雷人的是这个
int leap(int year)
{int leap;
leap=year%4
0&&year%100!=0||year%400;
return(leap);
}

其实可以简单地写为
int leap(int year)
{
return year%4==0&&year%100!=0||year%400;
}

最终的days居然是在main()求得,乖张
sum_day()的功能不可说

days=days+1; 业余

sum_day()中的
int day_tab[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
应为
static int const day_tab[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
P99
#include <stdio.h>
int main()
{ void swap(int *p1,int *p2);
int n1,n2,n3;
int *p1,*p2,*p3;
printf(“input three integer n1,n2,n3:”);
scanf(“%d,%d,%d”,&n1,&n2,&n3);
p1=&n1;
p2=&n2;
p3=&n3;
if(n1>n2)swap(p1,p2);
if(n1>n3)swap(p1,p3);
if(n2>n3)swap(p2,p3);
printf(“Now,the order is:%d,%d,%d\n”,n1,n2,n3);
return 0;
}

void swap(int *p1,int *p2)
{int p;
p=*p1;*p1=*p2;*p2=p;
}

评:main()中的p1,p2,p3完全是多余的
写出这种啰嗦的代码多半是由于受到两个错误观念的影响:
& 是求地址运算
指针是一个变量

P99~100
/*
题目:输入3个字符串,按由小到大的顺序输出。
*/
#include <stdio.h>
#include <string.h>

int main()
{void swap(char *,char *);
char str1[20],str2[31],str3[20];
printf(“input three line:\n”);
gets(str1);
gets(str2);
gets(str3);
if(strcmp(str1,str2)>0)swap(str1,str2);
if(strcmp(str1,str3)>0)swap(str1,str3);
if(strcmp(str2,str3)>0)swap(str2,str3);
printf(“Now,the order is:\n”);
printf(“%s\n%s\n%s\n”,str1,str2,str3);
return 0;
}

void swap(char *p1,char *p2)
{char p[20];
strcpy(p,p1);strcpy(p1,p2);strcpy(p2,p);
}
运行结果:
input three line:
I study very hard.
C language is very interesting.
He is a professor.
Now,the order is:
C language is very interesting.
He is a professor.
I study very hard.

评:尽管这个运行结果似乎正确,然而却是瞎猫碰到死耗子。这个结果根本靠不住。
至少有两次数组越界错误
P106~107
7.有一字符串,包含n个字符。写一函数,将此字符串中从第m个字符开始的全部字符复制成为另一个字符串。
#include <stdio.h>
#include <string.h>
int main()
{void copystr(char *,char *,int );
int m;
char str1[20],str2[20];
printf(“input string:”);
gets(str1);
printf(“which character that begin to copy?”);
scanf(“%d”,&m);
if(strlen(str1)<m)
printf(“input error!”);
else
{copystr(str1,str2,m);
printf(“result:%s\n”,str2);
}
return 0;
}

void copystr(char *p1,char *p2,int m)
{int n;
n=0;
while(n<m-1)
{n++;
p1++;
}
while(*p1!=‘\0’)
{*p2=*p1;
p1++;
p2++;
}
*p2=‘\0’;
}
评:代码的主要毛病就是拖泥带水,思路不清
int n;
n=0;
while(n<m-1)
{n++;
p1++;
}
一段的功能
无非是p+=m-1;而已
字符串的拷贝也无比啰嗦
在调用函数中已经处理了m不合理的情况
没有任何理由再把m作为参数

代码应改为
#include <stdio.h>
#include <string.h>

void copystr(char *,char *);

int main( void )
{
int m;
char str1[20],str2[20];
printf(“输入字符串:”);
gets(str1);
printf(“从第几个字符开始拷贝?”);
scanf(“%d”,&m);

if(strlen(str1)<m)
printf(“input error!”);
else
{
copystr(str2 , str1 + m - 1 );
printf(“result:%s\n”,str2);
}

return 0;
}

void copystr(char *p1 , char *p2 )
{
while( ( * p1 ++ = * p2 ++ ) != ‘\0’ )
;
}
P107
#include <stdio.h>
int main()
{int upper=0,lower=0,digit=0,space=0,other=0,i=0;
char * p,s[20];
printf(“input string: “);
while((s[ i ]=getchar())!=‘\n’)i++;
p=&s[0];
while(*p!=‘\n’)
{if((‘A’<=*p)&&(*p<=‘Z’))
++upper;
else if((‘a’<=*p)&&(*p<=‘z’))
++lower;
else if(*p ==’ ')
++space;
else if((*p<=‘9’)&&(*p>=‘0’))
++digit;
else
++other;
p++;
}
printf(“upper case:%d lower case:%d”,upper,lower);
printf(” space:%d digit:%d other:%d”,space,digit,other);
return 0;
}

评:这段代码写得要多傻有多傻
char * p,s[20]; 简直是在无病呻吟
根本用不着
#include <stdio.h>
int main( void )
{
int upper = 0 , lower = 0 , digit = 0 , space = 0 , other = 0 ;
char c ;

printf(“输入一行字符\n”);
while(( c = getchar() )!=‘\n’)
{
if( ( ‘A’ <= c ) && ( c <= ‘Z’ ) )
++upper;
else if( ( ‘a’ <= c ) && ( c <= ‘z’ ) )
++lower;
else if( c == ’ ’ )
++space;
else if( ( ‘0’ <= c ) && ( c <= ‘9’ ) )
++digit;
else
++other;
}
printf(“upper case:%d\tlower case:%d\t”,upper,lower);
printf(“space:%d\tdigit:%d\tother:%d\n”,space,digit,other);
return 0;
}
P108
将一个55的矩阵中最大的元素中最大的元素放在中心,4个角分别放4个最小的元素(顺序为从左到右,从上到下依次从小到大存放),写一函数实现之,用main函数调用。
P109~110
#include <stdio.h>
int main()
{void change(int p);
int a[5][5],p,i,j;
printf(“input matrix:\n”);
for(i=0;i<5;i++)
for(j=0;j<5;j++)
scanf(“%d”,&a[i][j]);
p=&a[0][0];
change§;
printf(“Now matrix:\n”);
for(i=0;i<5;i++)
{for(j=0;j<5;j++)
printf(“%d “,a[i][j]);
printf(”\n”);
}
return 0;
}
void change(int p)
{int i,j,temp;
int pmax,pmin;
pmax=p;
pmin=p;
for(i=0;i<5;i++)
for(j=i;j<5;j++)
{if(pmax<(p+5
i+j))pmax=p+5
i+j;
if(pmin>(p+5
i+j))pmin=p+5
i+j;
}
temp=
(p+12);
(p+12)=pmax;
pmax=temp;
temp=p;
p=pmin;
pmin=temp;
pmin=p+1;
for(i=0;i<5;i++)
for(j=0;j<5;j++)
if(((p+5
i+j)!=p)&&(pmin>(p+5
i+j)))pmin=p+5
i+j;
temp=pmin;
pmin=(p+4);
(p+4)=temp;
pmin=p+1;
for(i=0;i<5;i++)
for(j=0;j<5;j++)
if(((p+5
i+j)!=(p+4))&&((p+5
i+j)!=p)&&(pmin>(p+5
i+j)))
pmin=p+5
i+j;
temp=pmin;
pmin=(p+20);
(p+20)=temp;
pmin=p+1;
for(i=0;i<5;i++)
for(j=0;j<5;j++)
if(((p+5
i+j)!=p)&&((p+5
i+j)!=(p+4))&&((p+5
i+j)!=(p+20))&&
(pmin>(p+5
i+j))) pmin=p+5
i+j;
temp=*pmin;
pmin=(p+24);
*(p+24)=temp;
}

评:从没见过比change更丑的函数
谭给出的测试为

运行结果:
input matrix:
35 34 33 32 31
30 29 28 27 26
25 24 23 22 21
20 19 18 17 16
15 14 13 12 11
Now matrix:
11 34 33 32 12
30 29 28 27 26
25 24 35 22 21
20 19 18 17 16
13 23 15 31 14

不过代码依然是错的

P112
char str[10][6]; //p是指向由6个元素组成的一维数组的指针

评:这个注释雷人
P112
void sort(char s[10][6])

评:这个函数参数不全
那个10要多傻有多傻
P117
14.将n个数按输入时顺序的逆序排列,用函数实现。
评:这个题目本身就有毛病
“n个数”是含糊不清的提法
P117
#include <stdio.h>
int main()
{ void sort(char *p,int m);
int i,n;
char *p,num[20];
printf(“input n:”);
scanf(“%d”,&n);
printf(“please input these number:\n”);
for(i=0;i<n;i++)
scanf(“%d”,&num [ i ]);
p=&num[0];
sort(p,n);
printf(“Now ,the sequense is:\n”);
for(i=0;i<n;i++)
printf(“%d”,num[ i ]);
printf(“\n”);
return 0;
}

void sort(char *p,int m)
{int i;
char temp,*p1,*p2;
for(i=0;i<m/2;i++)
{p1=p+i;
p2=p+(m-1-i);
temp=*p1;
*p1=*p2;
*p2=temp;
}
评:解答更是错误的惊人
char *p,num[20];
for(i=0;i<n;i++)
scanf(“%d”,&num[ i ]);
已经错的没边了
其他的错误就不提了
(注:测试一下输入
1234 234 8 7 6 5 4 3 2 1
不难发现代码是错误的)
P117~120
劣质代码评析——兼谈指针越界问题
【题目】
15.有一个班4个学生,5门课程:①要求计算第一门课程的平均分;②找出两门以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;③找出平均成绩在90分以上或全部成绩在85分以上的学生。分别编写3个函数实现以上3个要求。
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p117
【评析】
题目前提基本充分,要求大体也还算合理,除了“输出他们的学号”让人感到有些突兀,因为前面从来没提到“学号”,看来是打算用4个学号表示“4个学生”。但是“分别编写3个函数实现以上3个要求”这种要求,则属于僭越无理,这种要求是一种误导和教唆,在这种荒唐变态的要求下代码必然扭曲。
【原代码】
#include <stdio.h>
int main()
{void avsco(float *,float );
void avscour1(char (
)[10],float *);
void fali2(char course[5][10],int num[],float *pscore,float aver[4]);
void good(char course[5][10],int num[4],float *pscore,float aver[4]);
int i,j,*pnum,num[4];
float score[4][5],aver[4],*pscore,*paver;
char course[5][10],(pcourse)[10];
printf(“input course\n”);
pcourse=course;
for(i=0;i<5;i++)
scanf(“%s”,course[i]);
printf(“input NO. and scores:\n”);
printf(“NO.”);
for(i=0;i<5;i++)
printf(“,%s”,course[i]);
printf(“\n”);
pscore=&score[0][0];
pnum=&num[0];
for(i=0;i<4;i++)
{scanf(“%d”,pnum+i);
for(j=0;j<5;j++)
scanf(“%f”,pscore+5
i+j);
}
paver=&aver[0];
printf(“\n\n”);
avsco(pscore,paver);
avscour1(pcourse,pscore);
printf(“\n\n”);
fali2(pcourse,pnum,pscore,paver);
printf(“\n\n”);
good(pcourse,pnum,pscore,paver);
return 0;
}

void avsco(float pscore,float paver)
{int i,j;
float sum,average;
for(i=0;i<4;i++)
{sum=0.0;
for(j=0;j<5;j++)
sum=sum+(
(pscore+5
i+j));
average=sum/5;
*(paver+i)=average;
}
}

void avscour1(char (pcourse)[10],float pscore)
{int i;
float sum,average1;
sum=0.0;
for(i=0;i<4;i++)
sum=sum+(
(pscore+5
i));
average1=sum/4;
printf(“course 1:%s average score:%7.2f\n”,*pcourse,average1);
}

void fali2(char course[5][10],int num[],float pscore,float aver[4])
{int i,j,k,label;
printf(" ====Student who is fail in two courses=\n");
printf(“NO.”);
for(i=0;i<5;i++)
printf(“%11s”,course[i]);
printf(" average\n");
for(i=0;i<4;i++)
{label=0;
for(j=0;j<5;j++)
if(
(pscore+5i+j)<60.0)label++;
if(label>=2)
{printf(“%d”,num[i]);
for(k=0;k<5;k++)
printf(“%11.2f”,
(pscore+5*i+k));
printf(“%11.2f\n”,aver[i]);
}
}
}

void good(char course[5][10],int num[4],float pscore,float aver[4])
{int i,j,k,n;
printf(" ====Students whose score is good=\n");
printf(“NO.”);
for(i=0;i<5;i++)
printf(“%11s”,course[i]);
printf(" average\n");
for(i=0;i<4;i++)
{n=0;
for(j=0;j<5;j++)
if(
(pscore+5i+j)>85.0)n++;
if(n==5||(aver[i]>=90))
{printf(“%d”,num[i]);
for(k=0;k<5;k++)
printf(“%11.2f”,
(pscore+5i+k));
printf(“%11.2f\n”,aver[i]);
}
}
}
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p117~120
下面逐条评析。
【原代码】
02. int main()
【评析】
这是一个老生常谈的问题,定义函数而不写形参类型亦即非函数原型形式(not prototype-format)是一种过时的写法(an obsolescent feature)。main()函数的头部应该写为:
int main ( void )
【原代码】
03.{void avsco(float ,float );
04.void avscour1(char (
)[10],float );
05.void fali2(char course[5][10],int num[],float pscore,float aver[4]);
06.void good(char course[5][10],int num[4],float pscore,float aver[4]);
【评析】
这个也是谭氏代码特有的一个毛病。把函数类型声明写在函数之内,不但使函数臃肿不堪,而且限制了函数的使用范围。应该从函数中移出,放在main()之前为好。
【原代码】
07.int i,j,pnum,num[4];
08.float score[4][5],aver[4],pscore,paver;
【评析】
int num[4];和float score[4][5];是这段代码核心的数据结构,如果不考虑使用结构体数据类型的话,尚属合理。但把i,j,pnum和num放在一起定义,尤其是把i,j和num放在一起声明,则显得不伦不类。
把score数组元素设计为float类型并非不可,但矛盾的是,后面出现了
【原代码】
69. if(
(pscore+5
i+j)<60.0)label++;
89. if(
(pscore+5
i+j)>85.0)n++;
90. if(n==5||(aver[i]>=90))
【评析】
前两句中的表达式中有float与double的混合运算,后一句中出现了float与int的混合运算,这在一般情况下是应该竭力避免的。因为这可能会导致截断误差的警告,而且至少在理论上存在着不必要的类型转换导致程序低效。
至于“aver[i]>=90”,通常是对类型不清醒而写出的似是而非的表达式,正规的写法应该是:aver[i]>=90.0F。
【原代码】
10.printf(“input course\n”);
11.pcourse=course;
12.for(i=0;i<5;i++)
13. scanf(“%s”,course[i]);
【评析】
这几句话的作用是输入5门课程名称,但却是画蛇添足。因为题目并没有要求输入课程名称,所以课程名称应该视为已知。
不满足题目要求是一种错误,完成题目额外要求也是一种错误,这叫做多做之过。
即使是出于纯粹个人练习的目的故意多写一段不必要的代码,这段代码也应该写成一个函数。
其中最可笑的是11.行那条语句,因为它和这段代码的功能毫无关系,不当不正地插在这里显得非常不伦不类。后面可以看到这条语句不但位置是错误的,而且压根就是一句可以删除的废话。
【原代码】
14.printf(“input NO. and scores:\n”);
15.printf(“NO.”);
16.for(i=0;i<5;i++)
17. printf(“,%s”,course[i]);
18.printf(“\n”);
【评析】
这段代码的作用是输出一段提示信息,并为输入提供一个表头。但是",%s"这个格式设计得很拙劣,它的输出的效果是:
NO.,English,Computer,Math,Physics,Chemistry
NO.与English之间的“,”不但不伦不类,而且这种样式容易让程序的使用者误认为输入数据之间应该用“,”分隔。
实际上这段代码没有必要,如果需要写也应该用函数完成。
【原代码】
19.pscore=&score[0][0];
20.pnum=&num[0];
【评析】
这是两句无厘头的废话。
首先它们完全可以写成更简洁的形式:
pscore=score[0];
pnum=num;
其次,这两句话完全可以不写。也就是说 pscore 和 pnum 这两个变量完全是多余的。
【原代码】
21.for(i=0;i<4;i++)
22. {scanf(“%d”,pnum+i);
23. for(j=0;j<5;j++)
24. scanf(“%f”,pscore+5
i+j);
25. }
【评析】
这段代码中的
scanf(“%d”,pnum+i);
一句应该写成
scanf(“%d”,num+i);

scanf(“%f”,pscore+5
i+j);
则等价于
scanf(“%f”, score + 5 * i + j );
但是需要特别指出的是,这两者都是错误的写法。这个错误就是本文副题所提到的指针越界问题。
指针可以与整数做加、减运算是有前提的。前提之一是这个指针必须是指向数据对象(Object)。例如:
int i;
&i这个指针可以+0、+1。但是指向函数的指针或指向void类型的指针没有加减法运算。
前提之二是这个指针必须指向数组元素(单个Object视同一个元素的数组)或指向数组最后一个元素之后的那个位置。例如:
int a[2];
&a[0]、&a[1]、&a[1]+1(即a、a+1、a+2)这些指针可以进行加减法运算。
第三,指针进行加减法运算的结果必须也指向数组元素或指向数组最后一个元素之后的那个位置。例如,对于指向a[0]的指针a,只能+0、+1、+2,对于a+2这个指针,只能-0、-1、-2。如果运算结果不是指向数组元素或指向数组元素最后一个元素之后的位置的情况,C语言并没有规定这种运算行为的结果是什么,换句话说这是一种未定义行为(Undefined Behavior,后面简称UB)。
前面【原代码】中的pscore是一个float 类型的指针,它指向的是
float score[4][5];
中的score[0][0],score[0][0]是score[0]这个一维数组(float [5])的元素,因此pscore只可以+0、+1、+2、+3、+4、+5,但是绝对不能加其他的整数。而
pscore+5
i+j
在i≥1,j≥0时显然违背了这种规则。因此是一种未定义行为,亦即是一种错误的代码。
很多人以为,既然
pscore+5
i+j
没有超出二维数组的范围,因此就没有问题。这种看法是错误的。构成二维数组的元素并非是float类型的数据对象,而是float [5]类型的一维数组数据对象。
在这个例子中,float类型的数据对象是构成一维数组的对象,指向float类型的指针最多只能+5。否则就是一种UB,这种行为是一种严重的软件安全隐患。
【原代码】
21.for(i=0;i<4;i++)
22. {scanf(“%d”,pnum+i);
23. for(j=0;j<5;j++)
24. scanf(“%f”,pscore+5
i+j);
25. }
【评析】
应该写为
for( i = 0 ; i < 4 ; i++ )
{
scanf(“%d” , num + i );
for( j = 0 ; j < 5 ; j++ )
scanf(“%f” , score[i] + j );
}
pnum和pscore都是完全用不着的。当然,用函数完成这个功能更好。
【原代码】
26.paver=&aver[0];
27.printf(“\n\n”);
28.avsco(pscore,paver);
【评析】
26.行完全是多余的。
27.行很丑陋,应该写在相应的函数内。从效率上来说不如写puts(“\n”);
28.行参数不完整,缺少两个数组第一维的维度;pscore,paver是两个完全不必要的变量,因为他们完全等价于
score,aver。
下面再看avsco()函数的定义
【原代码】
37.void avsco(float pscore,float paver)
38.{int i,j;
39.float sum,average;
40.for(i=0;i<4;i++)
41. {sum=0.0;
42. for(j=0;j<5;j++)
43. sum=sum+(
(pscore+5
i+j));
44. average=sum/5;
45. (paver+i)=average;
46. }
47.}
【评析】
首先函数的参数不完整,导致函数内有两个Magic Number :4,5。
43.行的“
(pscore+5i+j)”这种写法是错误的。前面提到过,这是一种UB。
假使不考虑这个错误43.行也应该写为
sum += (pscore+5i+j) ;
这样显然更简洁。
44.行混合运算,前面提过,这里不再赘述。
39.行的average是一个完全多余的变量,因为44.行和45.行可以简单地写为:
(paver+i) = sum/5.0F;
下面再回到main()。
【原代码】
29.avscour1(pcourse,pscore);
【评析】
这句很垃圾。
首先,pcourse、pscore这两个变量是多余的,因为这两个实参无非是course和
score而已。
其次,这个函数的功能并不满足“计算第一门课程的平均分”而是输出第一门课程的平均分。输出是main()的事情,而不是“计算”平均分的功能。
第三,“计算第一门课程的平均分”这个功能要求提得很愚蠢。难道计算二门课程的平均分还要另外写一个函数不成?如果可以写一个计算第n门课程的平均分,没有人会愚蠢地提出“计算第一门课程的平均分”这样的函数功能要求。
第四,作为“计算第一门课程的平均分”的函数,pcourse这个参数是多余的,因为这个参数对于计算是没有用处的。另一方面这个函数又缺少数组长度的参数。
再来看一下avscour1()的函数定义:
【原代码】
49.void avscour1(char (pcourse)[10],float pscore)
50.{int i;
51.float sum,average1;
52.sum=0.0;
53.for(i=0;i<4;i++)
54. sum=sum+(
(pscore+5
i));
55.average1=sum/4;
56.printf(“course 1:%s average score:%7.2f\n”,pcourse,average1);
57.}
【评析】
52.行,多余,应在定义sum时顺便初始化。
53.行,这里有一个Magic Number:4,这是因为函数参数不完整造成的。
54.行,这里同样存在数组越界的错误:“
(pscore+5
i)”
54.行,不考虑越界错误,这句话应该写成
sum +=(pscore+5i) ;
这样代码更简洁。
55.行,混合运算。
56.行,从这里不难看出 average1这个变量是多余的。因为这句话可以写为
printf(“course 1:%s average score:%7.2f\n”,pcourse,sum/4.0F );
29.行及49.行~57.行应写为:
int main( void )
{
/
*/
printf(“course %s average score:%7.2f\n”,course[1-1]
,average(score,5,1-1));//average(score , sizeof score / sizeof score,1-1)
/
*/
}

float average( float p_score[][5],int num_stu , int n)
{
float sum = 0.0 ;
int num_stu_ = num_stu ;
while( num_stu_ – > 0 )
{
sum += (p_score++ + n ) ;
}
return sum / (float) num_stu ;
}
【原代码】
31.fali2(pcourse,pnum,pscore,paver);
【评析】
这个函数的4个参数同样很垃圾。很多初学者都容易犯这种幼稚病,他们偏执地只使用变量做实参,却不懂得实参是一个表达式。事实上那些变量是多余的。
与此函数调用相应的函数类型声明:
【原代码】
05.void fali2(char course[5][10],int num[],float pscore,float aver[4]);
【评析】
同样丑陋不堪,其中的5和4是概念不清的表现,对应的函数定义也存在同样的毛病:
【原代码】
59.void fali2(char course[5][10],int num[],float pscore,float aver[4])
60.{int i,j,k,label;
61.printf(" ====Student who is fail in two courses=\n");
62.printf(“NO.”);
63.for(i=0;i<5;i++)
64. printf(“%11s”,course[i]);
65.printf(" average\n");
66.for(i=0;i<4;i++)
67.{label=0;
68. for(j=0;j<5;j++)
69. if(
(pscore+5
i+j)<60.0)label++;
70. if(label>=2)
71. {printf(“%d”,num[i]);
72. for(k=0;k<5;k++)
73. printf(“%11.2f”,
(pscore+5
i+k));
74. printf(“%11.2f\n”,aver[i]);
75. }
76.}
77.}
【评析】
这个函数还有很多毛病:
比如过于复杂;因为任务离数据过于遥远而导致参数过多。
这是由于main()无所作为,没有对任务做适当的分解,只是把数据一股脑地推给了函数的缘故。
同时也说明了问题要求把“找两门课程以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩”写成一个函数十分荒谬愚蠢。
61.64.行和71.74.行的代码完全应该写在这个函数的外部由单独的函数完成。
67.行的label是莫名奇妙的名字。
69.行的*(pscore+5i+j)是错误的表达式,存在混合运算问题。
70.行的代码效率低下,它应该与前面的if语句一起成为前面for循环的内部语句,即应写为:
for(j=0;j<5;j++)
{
if(
(pscore+5i+j)<60.0F)
label++;
if(label==2)
{
printf(“%d”,num[i]);
for(k=0;k<5;k++)
printf(“%11.2f”,
(pscore+5i+k));
printf(“%11.2f\n”,aver[i]);
break ;
}
}
顺便说一句,这里的
(pscore+5i+k)也是错误的表达式。
此外,这个函数的函数名fali2也非常怪异,不清楚是哪国英语这样表述不及格。
【原代码】
33.good(pcourse,pnum,pscore,paver);
【评析】
和前面fali2()函数调用存在同样的毛病,不再赘述。
【原代码】
79.void good(char course[5][10],int num[4],float pscore,float aver[4])
80. {int i,j,k,n;
81. printf(" ====Students whose score is good=\n");
82. printf(“NO.”);
83. for(i=0;i<5;i++)
84. printf(“%11s”,course[i]);
85. printf(" average\n");
86. for(i=0;i<4;i++)
87. {n=0;
88. for(j=0;j<5;j++)
89. if(
(pscore+5
i+j)>85.0)n++;
90. if(n==5||(aver[i]>=90))
91. {printf(“%d”,num[i]);
92. for(k=0;k<5;k++)
93. printf(“%11.2f”,(pscore+5i+k));
94. printf(“%11.2f\n”,aver[i]);
95. }
96.}
97.}
【评析】
这个函数的毛病和fali2()的函数定义一样
值得一提的是这段代码中的81.行85.行居然和61.行65.行完全一致,91.行55.行居然和71.行75.行完全一致。对任何程序员来说,这种代码都是一种耻辱。
下面是对该问题及代码的修正。
——————————————————————————————
【修正】
对题目的修正:
有一个班4个学生,5门课程:
①要求计算第一门课程的平均分;
②找出两门以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;
③找出平均成绩在90分以上或全部成绩在85分以上的学生。
注:原来要求“编写3个函数实现以上3个要求”实在太愚蠢。
【重构】
#include <stdio.h>

#define NUM_STUDENT 4
#define NUM_COURSE 5

int main( void )
{
int num[NUM_STUDENT] = { 101 , 102 , 103 , 104 };
char * course[NUM_COURSE] = {“English”,“Computer”,“Math”,“Physics”,“Chemistry”} ;
double score[NUM_STUDENT][NUM_COURSE]
= {
{ 34.0 , 56.0 , 88.0 , 99.0 , 89.0 },
{ 27.0 , 88.0 , 99.0 , 67.0 , 78.0 },
{ 99.0 , 90.0 , 87.0 , 86.0 , 89.0 },
{ 78.0 , 89.0 , 99.0 , 56.0 , 77.0 }
};

return 0;
}
注:因为题目并没有要求输入学号、课程名称、成绩,所以这些数据应该视同已知。此外如果使用结构体类型,数据结构将更为合理。
【分析】
首先解决“①要求计算第一门课程的平均分;”
这个问题的一般提法是,求4个学生的5门课程中第1门课程的平均分。
程序员应该学会根据特殊问题提出具有一般性的问题并加以解决,而不能就事论事地盯着单独的具体问题。这样写出的代码才具有较好的通用性性、可复用性和可维护性。
因此这个函数的函数原型应该是
double get_average( double score[][5] , int num_student , int order_course );
其中,score:存储成绩的二维数组;num_student:学生人数;order_course :课程的序号。
这样的函数,显然不但可以解决计算第一门课程的平均分这样的问题,也可以计算第 n (1<=n<=5)门课程的平均分这样的问题;不但可以解决4个学生情况下的这类问题,也可以解决k(k>0)个学生情况下的这类问题。
【重构】
#include <stdio.h>

#define NUM_STUDENT 4
#define NUM_COURSE 5
double get_average( double [][NUM_COURSE] , int , int );
int main( void )
{
int num[NUM_STUDENT] = { 101 , 102 , 103 , 104 };
char * course[NUM_COURSE] = {“English”,“Computer”,“Math”,“Physics”,“Chemistry”} ;
double score[NUM_STUDENT][NUM_COURSE]
= {
{ 34.0 , 56.0 , 88.0 , 99.0 , 89.0 },
{ 27.0 , 88.0 , 99.0 , 67.0 , 78.0 },
{ 99.0 , 90.0 , 87.0 , 86.0 , 89.0 },
{ 78.0 , 89.0 , 99.0 , 56.0 , 77.0 }
};

//①计算第一门课程的平均分;
printf(“第%d门课程%s的平均分为%7.2f\n” ,
1 , course[ 1 - 1 ] , get_average( score , NUM_STUDENT , 1 - 1 ) );

return 0;
}
double get_average( double score[][NUM_COURSE] , int n , int k )
{
double sum = .0 ;
int i ;

for( i = 0 ; i < n ; i ++ )
sum += score[i][k];

return sum/(double)n;
}
【分析】
再看第二个要求:②找出两门以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;
“找出”和“输出”是两件事情,不宜作为一个函数完成。此外“两门以上课程不及格的学生”是一个集合,除非构造出“集合”这种数据类型,否则单纯用函数无法完成,因为函数只能返回一个值。所以在“找出”这件事上main()不应该无所作为。
【重构】
#include <stdio.h>

#define NUM_STUDENT 4
#define NUM_COURSE 5
double get_average( double [][NUM_COURSE] , int , int );
void print_head(char *([]), int );
int less_than(double [], int , double , int);
void output(double [], int);
int main( void )
{
int num[NUM_STUDENT] = { 101 , 102 , 103 , 104 };
char * course[NUM_COURSE] = {“English”,“Computer”,“Math”,“Physics”,“Chemistry”} ;
double score[NUM_STUDENT][NUM_COURSE]
= {
{ 34.0 , 56.0 , 88.0 , 99.0 , 89.0 },
{ 27.0 , 88.0 , 99.0 , 67.0 , 78.0 },
{ 99.0 , 90.0 , 87.0 , 86.0 , 89.0 },
{ 78.0 , 89.0 , 99.0 , 56.0 , 77.0 }
};

//①计算第一门课程的平均分;
printf(“第%d门课程%s的平均分为%7.2f\n” ,
1 , course[ 1 - 1 ] , get_average( score , NUM_STUDENT , 1 - 1 ) );

//②找两门课程以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;
{
int i;
puts(“\n两门课程以上课程不及格的学生”);
print_head( course , NUM_COURSE ); //输出表头
for( i = 0 ; i < NUM_STUDENT ; i ++ )
if( less_than(score[i],NUM_COURSE,60.,2) ) //有2科成绩低于60.0
{
printf("%d ",num[i]); //学号
output(score[i],NUM_COURSE); //输出各科成绩及平均分
}
}

return 0;
}

//输出一名学生各科成绩及平均分
void output(double score[], int n)
{
double sum = 0.0 ;
int i ;
for( i = 0 ; i < n ; i ++ )
{
printf(“%11.2f”,score[i]);
sum += score[i] ;
}
printf( “%11.2f\n” , sum / (double) n );
}

//score中有amount科成绩低于fail返回1,否则返回0
int less_than(double score[], int n , double fail , int amount )
{
int i , num = 0 ;
for( i = 0 ; i < n ; i ++ )
{
if( score[i] < fail )
num ++ ;

  if( num == amount )
     return 1 ;

}
return 0;
}

//输出表头
void print_head(char *(course[]), int n )
{
int i;
printf(“学号 “);
for( i = 0 ; i < n ; i ++ )
{
printf(”%11s”,course[i]);
}
printf(" 平均分\n");

}

// 返回score[n][5]中第k科平均分
double get_average( double score[][NUM_COURSE] , int n , int k )
{
double sum = .0 ;
int i ;

for( i = 0 ; i < n ; i ++ )
sum += score[i][k];

return sum/(double)n;
}
【分析】
继续解决第三个问题:③找出平均成绩在90分以上或全部成绩在85分以上的学生。
由于“平均成绩在90分以上或全部成绩在85分以上的学生”是一个集合,所以解决这个问题同样不适合用一个函数完成,而应该由main()和其他函数配合完成。但是这里判断“平均成绩在90分以上”及“全部成绩在85分以上”应该分别由两个函数实现。
而“全部成绩在85分以上”显然可以由前面写过的函数less_than()来表达:! less_than( score[ i ],NUM_COURSE,85.0 , 1 )。
此外增加了一个求一名学生平均成绩的函数,并对前面的output()函数做了适当修改。
最后的代码为:
【重构】
#include <stdio.h>

#define NUM_STUDENT 4
#define NUM_COURSE 5
double get_average( double [][NUM_COURSE] , int , int );
void print_head(char *([]), int );
int less_than(double [], int , double , int);
void output(double [], int);
double get_average_student(double [], int);
int main( void )
{
int num[NUM_STUDENT] = { 101 , 102 , 103 , 104 };
char * course[NUM_COURSE] = {“English”,“Computer”,“Math”,“Physics”,“Chemistry”} ;
double score[NUM_STUDENT][NUM_COURSE]
= {
{ 34.0 , 56.0 , 88.0 , 99.0 , 89.0 },
{ 27.0 , 88.0 , 99.0 , 67.0 , 78.0 },
{ 99.0 , 90.0 , 87.0 , 86.0 , 89.0 },
{ 78.0 , 89.0 , 99.0 , 56.0 , 77.0 }
};

//①计算第一门课程的平均分;
printf(“第%d门课程%s的平均分为%7.2f\n” ,
1 , course[ 1 - 1 ] , get_average( score , NUM_STUDENT , 1 - 1 ) );

{//②找两门课程以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩;
int i ;
puts(“\n两门课程以上课程不及格的学生”);
print_head( course , NUM_COURSE ); //输出表头
for( i = 0 ; i < NUM_STUDENT ; i ++ )
if( less_than(score[i],NUM_COURSE,60.,2) ) //有2科成绩低于60.0
{
printf("%d ",num[i]); //学号
output(score[i],NUM_COURSE); //输出各科成绩及平均分
}
}

{//③找出平均成绩在90分以上或全部成绩在85分以上的学生。
int i ;
puts(“\n平均成绩在90分以上或全部成绩在85分以上的学生有:”);
for( i = 0 ; i < NUM_STUDENT ; i ++ )
if( get_average_student(score[i],NUM_COURSE) > 90.0//平均成绩在90分以上
||!less_than( score[i],NUM_COURSE , 85.0 , 1 ) //全部成绩在85分以上
)
printf(“第%d号\n”,num[i]); //学号
}

return 0;
}

//求一名学生各科成绩平均分
double get_average_student(double score[], int n)
{
double sum = 0.0 ;
int i ;
for( i = 0 ; i < n ; i ++ )
sum += score[i] ;

return sum / (double) n ;
}

//输出一名学生各科成绩及平均分
void output(double score[], int n)
{
int i ;
for( i = 0 ; i < n ; i ++ )
printf(“%11.2f”,score[i]);

printf( “%11.2f\n” , get_average_student( score , n) );
}

//score中有amount科成绩低于fail返回1,否则返回0
int less_than(double score[], int n , double fail , int amount )
{
int i , num = 0 ;
for( i = 0 ; i < n ; i ++ )
{
if( score[i] < fail )
num ++ ;

  if( num == amount )
     return 1 ;

}
return 0;
}

//输出表头
void print_head(char *(course[]), int n )
{
int i;
printf(“学号 “);
for( i = 0 ; i < n ; i ++ )
printf(”%11s”,course[i]);

printf(" 平均分\n");

}

// 返回score[n][5]中第k科平均分
double get_average( double score[][NUM_COURSE] , int n , int k )
{
double sum = .0 ;
int i ;

for( i = 0 ; i < n ; i ++ )
sum += score[i][k];

return sum/(double)n;
}
P120~121
输入一个字符串,串内有数字和非数字字符。例如:
A123x456 17960?302tab5876
将其中连续的数字作为一个整数,依次存放到一数组a中。例如,123放在a[0],456放在a[1]……,统计出共有多少个整数,并输出这些数。
评:这个题目本身就有问题。
首先是“输入一个字符串”。字符串是C代码语境下的术语和概念,而不是问题语境下的。
其次没有提到长度,这是很不严谨的。
“将其中连续的数字作为一个整数”,同样没有规定范围,不是任何长度连续的数字都可以转换为整数存入数组的。这些数字的进制也应该提一下
总共有多少“连续的数字”也没有提,这同样是不严谨的
P121~122
01.#include <stdio.h>
02.int main()
03.{
04.char str[50],pstr;
05.int i,j,k,m,e10,digit,ndigit,a[10],pa;
06.printf(“input a string\n”);
07.gets(str);
08.pstr=&str[0];
09.pa=&a[0];
10.ndigit=0;
11.i=0;
12.j=0;
13.while(
(pstr+i)!=‘\0’)
14. {if((
(pstr+i)>=‘0’)&&((pstr+i)<=‘9’))
15. j++;
16. else
17. {if(j>0)
18. {digit=
(pstr+i-1)-48;
19. k=1;
20. while(k<j)
21. {e10=1;
22. for(m=1;m<=k;m++)
23. e10=e1010;
24. digit=digit+(
(pstr+i-1-k)-48)e10;
25. k++;
26. }
27. pa=digit;
28. ndigit++;
29. pa++;
30. j=0;
31. }
32. }
33. i++;
34. }
35.if(j>0)
36.{digit=
(pstr+i-1)-48;
37. k=1;
38. while(k<j)
39. {e10=1;
40. for(m=1;m<=k;m++)
41. e10=e10
10;
42. digit=digit+((pstr+i-1-k)-48)e10;
43. k++;
44. }
45. pa=digit;
46. ndigit++;
47. j=0;
48.}
49.printf(“There are %d numbers in this line,they are:\n”,ndigit);
50.j=0;
51.pa=&a[0];
52.for(j=0;j<ndigit;j++)
53. printf("%d ",
(pa+j));
54.printf(“\n”);
55.return 0;
56.}
评:仅有一个main()函数,从这点就能足以判定这是一段垃圾代码
04.char str[50],pstr;
05.int i,j,k,m,e10,digit,ndigit,a[10],pa;
这两行中的str、a、和ndigit是这段代码所采用的基本数据结构。其中的char str[50]尚属差强人意,权且接受,就当做输入的字符串不超过50个字符。
但是,int a[10]就绝对莫名其妙了。因为在char str[50]中最多可以存储50/2个整数。所以,如果输入为:
1a2a3a4a5a6a7a8a9a0a1a2a3a4a5a6a7a8a9a0a
的情况下,程序得到的结果必然是错误的。这一点连运行测试都不必做就可以确定。
04.char str[50],pstr;
05.int i,j,k,m,e10,digit,ndigit,a[10],pa;
把变量不分主次地定义在一起,尤其是把那些i,j,k,m与程序的主要数据结构眉毛胡子一把抓地定义在一起,也是一种恶劣的编程习惯。
此外还要说明的是,这里定义的pstr和pa都是毫无意义的变量。在一个函数内操作数组,绝对可以不通过指针变量。
i,j,k,m这种垃圾命名法这里就不赘了。
08.pstr=&str[0];
09.pa=&a[0];
10.ndigit=0;
11.i=0;
12.j=0;
喧宾夺主。垃圾
应该在定义变量时初始化
13.while(
(pstr+i)!=‘\0’)
(pstr+i)无非就是a[ i ]而已,写成(pstr+i)毫无必要,显得十分做作。在后面的代码中pstr这个变量从没有改变,所以这个变量根本就是多余的。
此外对这个问题而言,从头到尾地读取字符串中的字符属于无策。因为一旦
(pstr+i)!=‘\0’,很可能前面还有没有处理的数字。所以13.行的写法是事先缺乏思考的随手写法。
一句话
根本就不应该while(
(pstr+i)!=‘\0’)
14. {if((
(pstr+i)>=‘0’)&&(
(pstr+i)<=‘9’))
15. j++;
完全不知所云
j那个名字太拙劣之故
17.行~32.行
35.行~48.行
一样的垃圾代码居然写了两次
垃圾的平方
17. {if(j>0)
18. {digit=
(pstr+i-1)-48;
19. k=1;
20. while(k<j)
21. {e10=1;
22. for(m=1;m<=k;m++)
23. e10=e1010;
24. digit=digit+(
(pstr+i-1-k)-48)*e10;
25. k++;
26. }
27. pa=digit;
28. ndigit++;
29. pa++;
30. j=0;
31. }
32. }
这是典型的垃圾代码。
首先,18.行:digit=
(pstr+i-1)-48;这句,其实完全可以写到后面的循环语句中:

  1.    k=1;
    
  2.    digit =0;
    
  3.    while(k<j)
    
  4.      {e10=1;
    
  5.       for(m=1;m<=k;m++)
    
  6.         e10=e10*10;
    
  7.       digit=digit+(*(pstr+i-1-k)-48)*e10;
    
  8.       k++;  
    
  9.      }
    

现在不难看出,这个while语句其实应该用for语句实现:

  1.        for(k=0,digit=0;k<j;k++)
    
  2.        {
    
  3.           e10=1;
    
  4.           for(m=1;m<=k;m++)
    
  5.              e10=e10*10;
    
  6.           digit += (*(pstr+i-1-k)-48)*e10;
    
  7.        }
    

现在会发现内部那个循环很2,因为这句话可以写成

  1.        for(k=0,digit=0,e10=1;k<j;k++,e10*=10)
    
  2.        {
    
  3.           digit += (*(pstr+i-1-k)-48)*e10;
    
  4.        }
    

其中的48是很糟糕的写法,应该写成’0’。因此18.~20.行无非是

  1.     for(k=0,digit=0,e10=1;k<j;k++,e10*=10)
    
  2.     {
    
  3.        digit += (*(pstr+i-1-k)-'0')*e10;
    
  4.     }
    

而已。也可以写为

  1.    for(k=0,digit=0,e10=1;k<j;k++,e10*=10)
    
  2.     {
    
  3.        digit += ( str[i-1-k] -'0' ) * e10 ;
    
  4.     }
    

所以说pstr这个变量是完全多余的。
01.27. pa=digit;
02.28. ndigit++;
03.29. pa++;
因为有了ndigit,所以变量pa完全是多余的。这几句话可以简单地写为:
a[ndigit++]=digit;
01.35.if(j>0)
02.36.{digit=
(pstr+i-1)-48;
03.37. k=1;
04.38. while(k<j)
05.39. {e10=1;
06.40. for(m=1;m<=k;m++)
07.41. e10=e1010;
08.42. digit=digit+(
(pstr+i-1-k)-48)e10;
09.43. k++;
10.44. }
11.45. pa=digit;
12.46. ndigit++;
13.47. j=0;
14.48.}
这种代码居然能在一个函数中出现两次,实在令人瞠目结舌。
值得一说的是其中的j=0不知所云,毫无意义。因此代码可改为:
if( j > 0 )
{
for(k=0,digit=0,e10=1;k<j;k++,e10
=10)
{
digit += (str[i-1-k] -‘0’) * e10 ;
}
a[ndigit++]=digit;
}
50.j=0;
51.pa=&a[0];
52.for(j=0;j<ndigit;j++)
53. printf("%d ",
(pa+j));
54.printf(“\n”);
前两句完全多余,代码可改写为
for(j=0;j<ndigit;j++)
printf("%d ",a[j] );
putchar(‘\n’);
下面稍微整理一下,修改后代码为:
#include <stdio.h>
int main( void )
{
char str[50];
int ndigit = 0 , a[10] = {0} ;
int i , j = 0 , k , e10 ;

printf(“input a string\n”);
gets(str);

for( i = 0 ; str[i] != ‘\0’ ; i ++ )
if(( str[i] >= ‘0’ ) && ( str[i] <= ‘9’ ) )
j++;
else
if( j > 0 )
{
for( k = 0 , e10 = 1 ; k < j ; k++ , e10 *= 10 )
a[ndigit] += (str[i-1-k] -‘0’) * e10 ;
ndigit++;
j = 0 ;
}

if( j > 0 )
{
for( k = 0 , e10 = 1 ; k < j ; k++ , e10 *= 10 )
a[ndigit] += ( str[i-1-k] -‘0’ ) * e10 ;
ndigit++;
}

printf(“There are %d numbers in this line,they are:\n”,ndigit);
for( j = 0 ; j < ndigit ; j++ )
printf("%d " , a[j] );
putchar(‘\n’);

return 0;
}
下面重写这个题目的代码
首先需要对题目做适当修正

输入一行字符(少于80个),其中含有十进制数字字符和其他字符。例如:
A123x456 17960?302tab5876
其中连续的数字的个数皆不超过9个。将其中各连续的数字均作为一个整数依次存储。
统计共有多少个整数,并输出这些数。
01.#include <stdio.h>
02.#define LEN 80
03.int main( void )
04.{
05. char str[LEN];
06.
07. puts(“输入一行字符”);
08. fgets(str,LEN,stdin);
09.
10. return 0;
11.}
这是对输入的基本考虑
其中的stdin表示从标准输入设备输入
这段代码没有考虑输入出现错误的情况,因此从更严格的意义上讲有些瑕疵。
更安全的写法是:
01.#include <stdio.h>
02.#define LEN 80
03.int main( void )
04.{
05. char str[LEN];
06.
07. puts(“输入一行字符”);
08. if( fgets( str , LEN , stdin ) == NULL )
09. {
10. return 1;
11. }
12.
13. return 0;
14.}
这可以保证程序不至于处理str没有正确输入的情况。
求解问题的关键在于找到数字开始和结束的位置,所以

  1. char * begin = NULL , //数字开始位置
  2.    * end   = str    ; //数字结束位置
    

由于每次寻找数字开始位置总是从上次数字结束位置开始,所以设end的初值为str。
通过函数查找字符串中数字开始的位置和结束位置。

#include <stdio.h>
#define LEN 80
char * find_begin ( char * );
char * find_end ( char * );
int main( void )
{
char str[LEN];
char * begin = NULL , //数字开始位置
* end = str ; //数字结束位置

puts(“输入一行字符”);
if( fgets( str , LEN , stdin ) == NULL )
return 1;

while( ( begin = find_begin ( end ) )!= NULL )
{
end = find_end ( begin ) ;

  {   //测试
      char *p = begin ;
      while( p < end )
      {
         putchar(*p++);
      }
  } 

}

return 0;
}
//返回数字结束位置
char * find_end ( char *s )
{
while( ‘0’ <= *s && *s <= ‘9’ )
{
s++ ;
}
return s ;
}

//返回数字开始位置。若无数字,返回NULL
char * find_begin ( char *s )
{
while( ! ( ‘0’ <= *s && *s <= ‘9’ ) )
{
if( *s == ‘\0’)
{
return NULL;
}
s ++ ;
}
return s;
}
存放整数的数组的尺寸至少应该为LEN/2:
unsigned value[LEN/2] ,
num = 0 ;

P122
17编写一个函数,实现两个字符串的比较。即自己写一个strcmp函数,函数原型为:
strcmp(char p1,charp2)
设p1指向字符串s1,p2指向字符串s2。要求当s1=s2时,返回值为0;若s1≠s2,返回它们二者第一个不同字符的ASCII码差值(如”BOY”与BAD,第2个字母不同,“O”与“A”之差为79-65=14);如果s1>s2,则输出正值,如果s1<s2,则输出负值
评:首先要说的是“strcmp(char p1,charp2)”,这不是函数原型。因为函数原型包括返回值类型。
其次“自己写一个strcmp函数”这个要求过分,因为这个函数是一个库函数。库函数的名字是保留的,自定义函数不应该使用库函数的名字。
此外“s1=s2”、“s1<s2”这种描述方法不严格。至少在C代码层面来看是错误的。
P122
strcmp(char *p1,char *p2)
{……
}
评:这个名字与标准库相冲突
此外没有说明函数返回值类型也是陈腐过时的写法
P122~123

#include <stdio.h>
int main( )
{int m;
char str1[20],str2[20],p1,p2;
printf(“input two strings:\n”);
scanf(“%s”,str1);
scanf(“%s”,str2);
p1=&str1[0];
p2=&str2[0];
m=strcmp(p1,p2);
printf(“result:%d,\n”,m);
return 0;
}
strcmp(char p1,char p2)
{int i;
i=0;
while(
(p1+i)==
(p2+i))
if(
(p1+i++)==‘\0’)return(0);
return(
(p1+i)-*(p2+i));
}
主要有三个问题
1.没写函数类型声明
2.使用库函数名strcmp
3.函数定义没写函数返回值类型,这在C99中是错误的

其他
01.p1=&str1[0];
02.p2=&str2[0];
怎么看都觉得无聊
m更无聊
p123
18.编一程序,输入月份号,输出该月的英文月名.例如,输入“3”,则输出“March”,要求用指针数组处理。
01.#include <stdio.h>
02.int main()
03.{char month_name[13]={“illegal month”,“January”,“Februry”,“March”,“April”,“May”,“June”,
04. “July”,“August”,“Septemper”,“October”,“November”,“December”};
05.int n;
06.printf(“input month:\n”);
07.scanf(“%d”,&n);
08.if(n<=12&&n>=1)
09. printf(“It is %s.\n”,
(month_name+n));
10.else
11. printf(“It is wrong.\n”);
12.return 0;
13.}
评:这段代码的毛病在于month_name数组有13个元素,而第一个元素没用
p124
19.(1)编写一个函数new,对n个字符开辟连续的存储空间,此函数应返回一个指针(地址),指向字符串开始的空间。new(n)表示分配n个字节的内存空间,见图8.4。
(2)写一函数free,将前面用new函数占用的空间释放。free§表示将p(地址)指向的单元以后的内存段释放。
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p124
评:所谓返回指向“字符串”开始的空间是错误的说法,因为为n个字符申请空间并不意味着存储的就是字符串。
要求写一个名为free函数也是错误的,因为这是保留的库函数的名字。
p124
01.#include <stdio.h>

02.#define NEWSIZE 1000

03.char newbuf[NEWSIZE];

04.char *newp=newbuf;

05.char *new( int n )

  1. {if(newp+n<=newbuf+NEWSIZE)

  2.   {newp+=n;
    
  3.    return(newp-n);
    
  4.   }
    
  5.   else
    
  6.     return(NULL);
    
  7. }
    ——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p124
    评:
    01.#include <stdio.h>
    只有一个作用,后面代码使用了NULL。
    感觉很别扭
    其实写为
    01.#include <stddef.h>
    更地道些

01.03.char newbuf[NEWSIZE];
02.04.char *newp=newbuf;
照猫画虎
可惜画虎不成反类犬
作者完全不懂得应该把它们封装起来

  1. {if(newp+n<=newbuf+NEWSIZE)
    这是一个似是而非的错误写法。
    正确的写法应该是
    01.{
  2.   if(  n <= newbuf + NEWSIZE - newp )
    

p124~125
#include <stdio.h>
#define NEWSIZE 1000
cha newbuf[NEWSIZE];
char *newp=newbuf;
void free(char *p)
{if(p>=newbuf&&p<newbuf+NEWSIZE)
newp=p;
}
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p124~125
评:
free()函数定义居然另写了一段,完全丧失了意义。更滑稽的是又重新写了一遍
01.01.#include <stdio.h>
02.02.#define NEWSIZE 1000
03.03.cha newbuf[NEWSIZE];
04.04.char *newp=newbuf;
这就使人完全不清楚到底在写什么东西了。

此外free()函数与库函数重名
这也是一个错误。
P125
用指向指针的指针的方法对5个字符串排序并输出
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p125

不看代码
无论如何猜不出到底要求做什么
P125
13.p=pstr;
14.sort§;
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p125
巨无聊
P125
21.void sort(char ** p)
22.{ int i,j;
23. char * temp;
24. for(i=0;i<5;i++)
25. {for(j=i+1;j<5;j++)
26. {if(strcmp((p+i),(p+j))>0)
27. {temp=*(p+i);
28. (p+i)=(p+j);
29. *(p+j)=temp;
30. }
31. }
32. }
33.}

——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p125
参数不全
算法古怪
有点像选择法
但却大量交换
126
用指向指针的指针的方法对n个整数排序并输出。要求将排序单独写成一个函数。n个整数在主函数中输入,最后在主函数中输出.——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p126
“n个整数在主函数中输入,最后在主函数中输出”这种要求非常荒诞无理,仿佛是在要求只能把代码写烂,不许写好一样。除了绑架代码写作者写出糟糕别扭的代码之外,没有任何价值。作为一个习题提出这样的要求,是误导学习者。
126
01.04.int i,n,data[20],**p,*pstr[20];
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p126
很显然,代码把data这个具有20个int类型元素的数组作为输入的“n个整数”的存储空间。然而题目并没有明确输入的整数不超过20个,所以这个属于自欺欺人的做法。

126
01.04.int i,n,data[20],**p,*pstr[20];
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p126
pstr这个名字荒唐的很够层次。字面上看这应该是一个与字符串有关的指针,但实际上这个标识符和字符串八竿子打不着。这比那种简单的、无意义的单字符变量名更烂。单字符变量名仅仅会让人费解,但pstr这种名字是直接让人误解。
126
07.for(i=0;i<n;i++)
08. pstr[i]=&data[i];
09.printf(“input %d integer numbers:”,n);
10.for(i=0;i<n;i++)
11. scanf(“%d”,pstr[i]);
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p126
这几行代码其实可以简单地通过一个循环实现。
printf(“input %d integer numbers:”,n);
for( i = 0 ; i < n ; i ++ )
scanf(“%d”,pstr[i] =&data[i] );

126
12.p=pstr;
13.sort(p,n);
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p126
无聊。

127
21.void sort(int p,int n)
22.{int i,j,*temp;
23.for(i=0;i<n-1;i++)
24. {for(j=i+1;j<n;j++)
25. {if(
(p+i)>**(p+j))
26. {temp=*(p+i);
27. (p+i)=(p+j);
28. *(p+j)=temp;
29. }
30. }
31. }
32.}
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p127
这段代码的主要问题是算法低效。
128 第九章
1.定义一个结构体变量(包括年、月、日)。计算该日在本年中是第几天,注意闰年问题。
仅仅“定义一个结构体变量”是无法计算“该日在本年中是第几天”的
题目没有交代这个变量值的来源
128
#include<stdio.h>
struct
{int year;
int month;
int day;
}date;
int main()
{int days;
printf(“input year,month,day:”);
scanf(“%d,%d,%d”,&date.year,&date.month,&date.day);
switch(date.month)
{case 1: days=date.day; break;
case 2: days=date.day+31; break;
case 3: days=date.day+59; break;
case 4: days=date.day+90; break;
case 5: days=date.day+120; break;
case 6: days=date.day+31; break;
case 7: days=date.day+181; break;
case 8: days=date.day+212; break;
case 9: days=date.day+243; break;
case 10: days=date.day+273; break;
case 11: days=date.day+304; break;
case 12: days=date.day+334; break;
}
if((date.year%40&&date.year%100!=0
||date.year%400
0)&&date.month>=3) days+=1;
printf(“%d/%d is the %dth day in%d.”,date.month,date.day,days,date.year);
return 0;
}
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p128
这段代码的结构给人的感觉是莫名其妙。它定义了一个外部变量date,但是整个程序却只有一个main()。换句话说,这个date从定义位置来看貌似是给许多函数使用的,但是实际上却只有一个函数在使用它。这种自相矛盾显得很滑稽。
128
11.switch(date.month)
12.{ case 1: days=date.day; break;
13. case 2: days=date.day+31; break;
14. case 3: days=date.day+59; break;
15. case 4: days=date.day+90; break;
16. case 5: days=date.day+120; break;
17. case 6: days=date.day+151; break;
18. case 7: days=date.day+181; break;
19. case 8: days=date.day+212; break;
20. case 9: days=date.day+243; break;
21. case 10: days=date.day+273; break;
22. case 11: days=date.day+304; break;
23. case 12: days=date.day+334; break;
24.}
这段代码写得极其笨拙,这不是让计算机算,是人自己在算
129
25.if((date.year%40&&date.year%100!=0
26. ||date.year%400
0)&&date.month>=3) days+=1;

正常的思路应该是
if( date.month > 2 &&
(date.year%40&&date.year%100!=0
||date.year%400
0) ) days+=1;
这个表达式过于臃长复杂
判断是否闰年应该由一个函数或宏实现
129
解法二
#include<stdio.h>
struct
{int year;
int month;
int day;
}date;
int main()
{int i , days;
int day_tab[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
printf(“input year,month,day:”);
scanf(“%d,%d,%d”,&date.year,&date.month,&date.day);
days=0;
for(i=1;i<date.month;i++)
days=days+day_tab[i];
days=days+date.day ;
if((date.year%40&&date.year%100!=0|date.year%4000)&&date.month>=3)
days=days+1;
printf(“%d/%d is the %dth day in%d.\n”,date.month,date.day,days,date.year);
return 0;
} ——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p129
因为偷懒把变量定义写在不恰当的位置会导致代码整体结构失和,因小失大。
存储12个月每个月的天数,只需要12个元素的数组。毫无意义地定义有13个元素的数组是因为对C语言数组下标从0开始的特性掌握不够,但又不肯认真掌握,而强迫C语言迁就自己对C的不理解。
129~130
写一个函数days,实现第1题的计算。由主函数将年、月、日传递给days函数,计算后将日子输出传回主函数输出。
02.struct y_m_d
03. {/* ……/
06. }date; //由于变量定义的位置,这个date在说,其他函数都可以直接使用我
07.int main()
08. {/
……/
days(date) //这里在说,由我来告诉days()函数date的值
/
……/
12. }
13.
14.int days(struct y_m_d date1)//无意义地准备了一个date1,并费时地接受main()转发的date
//的值。但其实这个函数是可以直接使用date变量的。
15.{ /
……*/
33.} ——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p129~130
这个代码结构让人哭笑不得。
08. {int days(struct y_m_d date1); //定义date1为结构体变量,类型为struct y_m_d
这里是一个函数类型声明,说明的是days这个标识符的含义。根本不存在date1的定义。实际上这里的date1写不写都可以。
main()函数明显缺少return 0;语句。如果是在C89标准下编译,main()的返回值是不确定的。在需要用的这个返回值的场合,这个错误极其严重。
130~131
#include<stdio.h>
struct y_m_d
{int year;
int month;
int day;
}[color=Red]date[/color]; //位置不当
int main()
{[color=Red]int days(int year,int month,int day);
int days(int ,int ,int );[/color] //唠唠叨叨,同一件事反复说了两遍
[color=Red] int day_sum;[/color] //多余的变量
printf(“input year,month,day:”);
scanf(“%d,%d,%d”,&date.year,&date.month,&date.day);
day_sum=days(date.year,date.month,date.day);
printf(“%d/%d is the %dth day in%d.\n”,date.month,date.day,day_sum,date.year);
return 0;
}

int days(int year,int month,int day) //点金成铁的接口设计,糟蹋结构体类型的范例,不辞辛苦地把代码弄糟
{int day_sum,i;
int day_tab[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};//多了一个没有用的元素
day_sum=0;
for( i = 1 ; i < month ; i++ )
day_sum+=day_tab[ i ];
day_sum+=day ;
if((year%40&&year%100!=0||year%4000)&&month>=3)
day_sum+=1;
return(day_sum);
}
P131
3.编写一个函数print,输出一个学生的成绩数组,该数组中有5个学生的数据记录,每个记录包括num、name、sore[3],用主函数输入这些记录,用print函数输出这些记录。
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p131

没人搞得清“每个记录包括num、name、sore[3]”是在说什么。
P 131~132
这是一个错误的代码。首先
01.06. int score[4];
这个“4”莫名其妙,后面代码根本没有用到这数组成员的第四个元素。而且这与题目中的“sore[3]”也根本对不上(尽管题目本身也有问题)。
01.07. }stu[N];
这里随手定义了一个外部数组,这个数组在后面任何地方都可以使用。效果是把所有的代码都紧密地连接了起来,这通常是一种恶劣的风格

01.09.{void print(struct student stu[6]);
这个函数类型声明中的“6”同样莫名其妙不知所云。比石头里跳出个孙悟空还让人惊诧。
10.int i,j ;
11.for(i=0;i<N;i++)
12. {printf(“\ninput score of student %d:\n”,i+1);
13. printf(“NO.:”);
14. scanf(“%s”,stu[i].num);
15. printf(“name:”);
16. scanf(“%s”,stu[i].name);
17. for(j=0;j<3;j++)
18. {printf(“score%d:”,j+1);
19. scanf(“%d”,&stu[i].score[j]);
20. }
21. printf(“\n”);
22. }
在main()中输入数据是一个馊主意。

23.print(stu);
这条语句不但有煞有介事的成分,也有装傻充愣的成分。
26.void print(struct student stu[6])
如果这里的“6”写得是“5”或“N”,说明代码作者概念不清,但这里居然写的是“6”,只能说明代码作者很2。
如果写“5”或“N”说明代码作者的意图可能是向这个函数传递一个具有5个元素或N个元素的数组。但是这个愿望是会落空的,因为编译器根本对形参后面第一个[]内的数视而不见。
但是这里居然写的是“6”,搞不清这个“6”是哪儿来的“天外飞仙”。

29.for(i=0;i<N;i++)
30. {printf(“%5s%10s”,stu[i].num,stu[i].name);
31. for(j=0;j<3;j++)
32. printf(“%9d”,stu[i].score[j]);
33. printf(“\n”);
34. }
这里的N和3都属于天外飞仙,不知道从哪里来的。
P 133~134
canf(“%s”,stu[i].num);
rintf(“name:”)
可是在134页居然声称
运行情况与第3题相同。
公然扯谎
P 134~136
#include <stdio.h>
#define N 10

struct student
{char num[6];
char name[8];
float score[3];
float avr;
}stu[N];

int main()
{int i,j,maxi;
float sum,max,average;
//输入数据
for(i=0;i<N;i++)
{printf(“input scores of student %d:\n”,i+1);
printf(“NO.:”);
scanf(“%s”,stu[i].num);
printf(“name:”);
scanf(“%s”,stu[i].name);
for(j=0;j<3;j++)
{printf(“score %d:”,j+1);
scanf(“%f”, &stu[i].score[j]);
}
}
//计算
average=0;
max=0;
maxi=0;
for(i=0;i<3;i++)
{sum=0;
for(j=0;j<3;j++)
sum+=stu[i].score[j];
stu[i].avr=sum/3.0;
average+=stu[i].avr;
if(sum>max)
{max=sum;
maxi=i;
}
}
average/=N;
//输出
printf(“NO. name score1 score2 score3 average\n”);
for(i=0;i<N;i++)
{printf(“%5s%10s”,stu[i].num, stu[i].name);
for(j=0;j<3;j++)
printf(“%9.2f”,stu[i].score[j]);
printf(“%8.2f\n”,stu[i].avr);
}
printf(“average=%5.2f\n”,average);
printf(“The highest score is : student %s,%s\n”,stu[maxi].num,stu[maxi].name);
printf(“his scores are:%6.2f,%6.2f,%6.2f,average:%5.2f.\n”,
stu[maxi].score[0],stu[maxi].score[1],stu[maxi].score[2],stu[maxi].avr);
return 0;
}
P136页的运行结果是伪造的
这个代码不可能得到136页的运行结果
代码的结构莫名其妙,因为整段代码只有一个函数main(),没有任何理由设置外部变量。
01.24. //计算
02.25. average=0;
03.26. max=0;
04.27. maxi=0;
05.28. for(i=0;i<3;i++)
06.29. {sum=0;
07.30. for(j=0;j<3;j++)
08.31. sum+=stu[i].score[j];
09.32. stu[i].avr=sum/3.0;
10.33. average+=stu[i].avr;
11.34. if(sum>max)
12.35. {max=sum;
13.36. maxi=i;
14.37. }
15.38. }
16.39. average/=N;
这段代码能够记住第一个sum最高值,并用maxi记录其序号i。但问题在于如果后面某个sum等于max的话应该如何处理呢?代码的功能是把第二个忽视,但这并非是题目的要求。
01.40. //输出
02.41. printf(“NO. name score1 score2 score3 average\n”);
03.42. for(i=0;i<N;i++)
04.43. {printf(“%5s%10s”,stu[i].num, stu[i].name);
05.44. for(j=0;j<3;j++)
06.45. printf(“%9.2f”,stu[i].score[j]);
07.46. printf(“%8.2f\n”,stu[i].avr);
08.47. }
09.48. printf(“average=%5.2f\n”,average);
10.49. printf(“The highest score is : student %s,%s\n”,stu[maxi].num,stu[maxi].name);
11.50. printf(“his scores are:%6.2f,%6.2f,%6.2f,average:%5.2f.\n”,
12.51. stu[maxi].score[0],stu[maxi].score[1],stu[maxi].score[2],stu[maxi].avr);
输出部分更为混乱。41.行~47.行根本不是问题要求。这属于“多做之过”。程序应该完成且只应该完成所要求的功能,擅自增加功能是画蛇添足,也是一种错误。
49.行~50.行不可能是正确的,原因前面已经解释过了。需要说一下的是
01.50. printf(“his scores are:%6.2f,%6.2f,%6.2f,average:%5.2f.\n”,
02.51. stu[maxi].score[0],stu[maxi].score[1],stu[maxi].score[2],stu[maxi].avr);
用这种方法(stu[maxi].score[0],stu[maxi].score[1],stu[maxi].score[2])输出数组的各个元素,可谓笨拙之极,这使代码完全丧失了可维护性。
P 147
#include <malloc.h>
古老过时已经腐朽的写法
P 148~149
{struct student student *creat();
struct student student *del(student *,long);
struct student student *insert(student *,student *);
void print(struct student *head);
这种地沟油代码根本无法通过编译
可老谭在150页居然说有运行结果
这是在欺骗读者

struct student *creat()
这是一个很不规范的写法。
n=0;
它用了一个蹩脚的外部变量来表示节点个数。n蹩脚在它与链表不是紧密结合为一体,而是分崩离析各自为政,如果n被意外改变,程序将错得一塌糊涂。

 p1=p2=(struct student*)malloc(LEN);

这句有些昏头昏脑。它不分青红皂白地申请了一块内存——malloc(LEN),但是却还没弄清楚是否真的需要这块内存,然后就向结构体中写数据
scanf(“%ld,%f”,&p1->num,&p1->score);
如果输入的数据使p1->num为0,会导致内存泄露
p1=p2=(struct student*)malloc(LEN);

p1=(struct student*)malloc(LEN);
没考虑malloc()函数返回值为NULL即没考虑内存可能失败的情况也是错误的
head=NULL;
这句不应该写,应该在定义head变量时初始化。

scanf(“%ld,%f”,&p1->num,&p1->score);
/……/
while(p1->num!=0)
{/……/
scanf(“%ld,%f”,&p1->num,&p1->score);
这种重复表明代码书写者根本不会写C代码。
p1=(struct student*)malloc(LEN);
这句最后一定会导致内存泄露。

P 149
//删除结点的函数
struct student *del(struct student *head,long num)
{struct student *p1,*p2;
if(headNULL)
{printf(“\nlist null\n”);
return(head);
}
p1=head
while(num!=p1->num&&p1->next!=NULL)
//p1指向的不是所要找的结点且后面还有结点
{p2=p1;p1=p1->next;}
if(num
p1->num)
{if(p1==head)head=p1->next;
else p2->next=p1->next;
printf(“delete:%ld\n”,num);
n=n-1;
}
else
printf(“%ld not been found!\n”,num);
return(head);
}
这个函数风格更烂,而且竟然有语法错误:
p1=head
很显然,这里缺少一个“;”。

while(num!=p1->num&&p1->next!=NULL)
//p1指向的不是所要找的结点且后面还有结点
{p2=p1;p1=p1->next;}
这个注释更是奇葩,它居然把一个完整的语句彻底割裂开来,不知道是为了让人看得懂还是为了让人更加看不懂?通常注释即使不成功也不会对代码照成损害,但这个注释却是空前绝后地破了这个惯例。

            if( head == NULL )
            {printf("\nlist null\n");
              return(head);
            }

这是多此一举的,也是自相矛盾的。因为调用函数自己可以判断链表是否为空,而且headNULL和n0都可以作为判断链表是否为空的条件,再次证明n那个外部变量是多余的累赘。
即使由del()可能接受空链表,后面的语句也完全能够完成正确的功能。

    1. while(num!=p1->num&&p1->next!=NULL)
      02.10. //p1指向的不是所要找的结点且后面还有结点
      03.11. {p2=p1;p1=p1->next;}
      体现初学者常见的怯懦,不敢更进一步让p1的值为NULL,并以此作为循环的结束条件。结果只好在后面写出很猥琐的代码

01.19. if( p1 == head )
02.20. head=p1->next;
03.21. else
04.22. p2->next=p1->next;

这里有个严重的错误,就是没有把删除节点所占用的内存释放,自己不用,也不让别人用,随手乱丢垃圾,造成内存泄露。函数调用结束后p1指向的内存块就像太空中的垃圾一样,无论是操作系统还是自己的程序,谁都够不着。
P 150

//定义输出链表的print函数
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)
}
评:很滑稽的写法
首先由于if(head!=NULL) 之前p=head;
所以if(head!=NULL)可以写成if(p!=NULL)
这时再来看一下代码

//定义输出链表的print函数
void print(struct student *head)
{struct student *p;
printf(“\nNow,These %d records are:\n”,n);
p=head;
if(p!=NULL)
do
{printf(“%ld%5.1f\n”,p->num,p->score);
p=p->next;
}while(p!=NULL);
}

不难发现

if(p!=NULL)
  do
  {printf("%ld%5.1f\n",p->num,p->score);
   p=p->next;
  }while(p!=NULL);

其实不过是while语句的一种变态写法
这句应该写为
while(p!=NULL)
{
printf(“%ld%5.1f\n”,p->num,p->score);
p=p->next;
}

再回头看一下这个函数

//定义输出链表的print函数
void print(struct student *head)
{ struct student *p;
printf(“\nNow,These %d records are:\n”,n);
p=head;
while(p!=NULL)
{
printf(“%ld%5.1f\n”,p->num,p->score);
p=p->next;
}
}

会发现p这个变量是多余的,函数应该写为

void print(struct student *p)
{
printf(“\nNow,These %d records are:\n”,n);
while( p != NULL )
{
printf(“%ld%5.1f\n”,p->num,p->score);
p = p -> next ;
}
}

这里的n是一个外部变量,完全不入流。应该删除
P 154~155
05.struct student
06. {int num;
07. char name[8];
08. struct student *next;
09. }a[LA],b[LB];
11.int main( )
12.{struct student a[LA]={{101,“Wang”},{102,“LI”},{105,“zhang”},{106,“Wei”}};
13. struct student b[LB]={{103,“Zhang”},{104,“Ma”},{105,“Chen”},{107,“Guo”},{108,“Lui”}};

写出这种代码说明头脑不清,糊涂混乱
这种代码连下流都谈不上
只能说是不入流
02.#include <string.h>

这行也很无厘头
18.head2=b;

位置很傻,显得东一榔头西一棒子。起码应该写在29行
17.head1=a;

19.printf(“list A :\n”);
20.for(p1=head1,i=1;i<=LA;i++)
21. {if(i<LA)
22. p1->next=a+i;
23. else
24. p1->next=NULL;
25. printf(“%4d%8s\n”,p1->num,p1->name);
26. if(i<LA)
27. p1=p1->next;
28. }
这几行代码很煞有介事。其实它的主要功能无非是
for( i = 0 ; i < sizeof a/sizeof *a - 1 ; i++ )
a[ i ].next=a[ i + 1 ]

a[ i ].next=NULL;

head1=a;
30.for(p2=head2,i=1;i<=LB;i++)
31. {if(i<LB)
32. p2->next=b+i;
33. else
34. p2->next=NULL;
35. printf(“%4d%8s\n”,p2->num,p2->name);
36. if(i<LB)
37. p2=p2->next;
38. }

完全重复20~28行,丑陋得惨不忍睹
40.//对a链表进行删除操作
41.p1=head1;
42.while(p1!=NULL)
43. {p2=head2;
44. while((p1->num!=p2->num)&&(p2->next!=NULL))
45. p2=p2->next;
46. //使p2后移直到发现与a链表中当前的结点的学号相同或已到b链表最后一个结点
47. if(p1->nump2->num)
48. {if(p1
head1)
49. head1=p1->next;
50. else
51. {p->next=p1->next;
52. //使p->next指向p1的下一个结点,即删去p1当前指向的节点
53. p1=p1->next;
54. }
55. }
56. else
57. {p=p1;p1=p1->next;}
58. }

一团乱麻一样复杂的代码,源自颠三倒四的混乱思维。
它的想法是逐个查看a链表的节点,如果该节点的学号在b链表中存在,则删除该节点。这样成功地把简单的问题复杂化了——把两个链表交织在了一起。
P 156
一上来就是一只苍蝇。
02.#include <malloc.h>
这条预处理命令的用意是给出代码中调用malloc()函数的函数类型声明。然而这是几十年前的编译器的写法。现代C语言中malloc()函数的函数类型声明由stdlib.h提供。
P 156~157
04.struct student
05.{char num[6];
06.char name[8];
07.char sex[2];
08.int age;
09.struct student* next;
10.}stu[10];
这是代码中的第二只苍蝇——stu[10]。这是一个外部变量,但是自相矛盾的是整个源程序只有一个main(),更滑稽的是这个数组在整个源程序中根本就没有被用到。
P 157
14.int i,length,iage,flag=1;
15.int find=0;
每次见到flag都仿佛能闻到代码中有种馊味。后面的find显然也是同样性质的变量。
P 157
41.p=head;
42.printf(“\n NO. name sex age\n”);
43.while(p!=NULL)
44. {printf(“%4s%8s%6s%6d\n”,p->num, p->name, p->sex, p->age);
45. p=p->next;
46. }
这段代码几乎和
72.p=head;
73.printf(“\n NO.name sex age\n”);
74.while(p!=NULL)
75.{printf(“%4s%8s”,p->num,p->name);
76. printf(“%6s%6d\n”,p->sex,p->age);
77. p=p->next;
78.}
一模一样,是一种拙劣的败笔,甚至是比一模一样还要拙劣的败笔。

P 157
23.//建立链表
24.for(i=0;i<length;i++)
25.{p=(struct student *)malloc(LEN);
26. if(i0)
27. head=pt=p;
28. else
29. pt->next=p;
30. pt=p;
31. printf(“NO:”);
32. scanf(“%s”,p->num);
33. printf(“name:”);
34. scanf(“%s”,p->name);
35. printf(“sex:”);
36. scanf(“%s”,p->sex);
37. printf(“age:”);
38. scanf(“%d”,&p->age);
39.}
40.p->next=NULL;
正如前面所指出的那样,链表根本就不应该在建立之前指定length。因此从根本上来说,建立链表根本就不应该使用for语句。这里之所以出现如此怪异的建立方法是因为代码作者不懂得正确的建立方法。
在代码细节上这段代码也有很多问题,例如没有处理malloc()返回值为NULL的情况,此外
26. if(i
0)
27. head=pt=p;
28. else
29. pt->next=p;
30. pt=p;
中的27.行的pt=p是多此一举的画蛇添足,因为在30.行有一句pt=p;,无论如何都会被执行。

P 157
48.//删除结点
49.printf(“input age:”);
50.scanf(“%d”,&iage);
51.pt=head;
52.p=pt;
53.if(pt->ageiage)
54. {p=pt->next;
55. head=pt=p;
56. find=1;
57. }
58.else
59. pt=pt->next;
这一段处理的是第一个节点。第一个节点可能需要被删除也可能不需要被删除。紧接着的一段代码
60.while(pt!=NULL)
61. {if(pt->age
iage)
62. {p->next=pt->next;
63. find=1;
64. }
65. else
66. p=pt;
67. pt=pt->next;
68. }
处理的是第一个节点之后亦即非head所指向的各个节点。然而在第一个节点被删除的情况下,第二个节点就会成为第一个结点,这种情况下必须继续把第二个节点作为head所指向的那个节点来处理。这时如果把第二个节点作为第一个节点之后的后续节点就会发生错误。
所以,不言而喻,这段关于删除的代码是绝对错误的。这一点不需要运行测试就能发现。
P159
从键盘输入一个字符串,将其中的小写字母全部转换成大写字母,然后输出到一个磁盘文件“test”中保存。输入的字符串以“!”结束。

评:“ 字符串以“!”结束”,算是奇闻

P159
题目的要求是“输出到一个磁盘文件‘test’中保存”,可是代码中却是
8. if((fp=fopen(“a1”,“w”))==NULL)
显然“码”不对题。
P159
10. exit(0);

那个0似是而非。
P159
13. gets(str);
这是一个错误的写法。因为gets()函数的作用是输入一行字符,但是题目中并没有提到输入“!”之前没有换行。
P159
16. str[ i ]=str[ i ]-32;

32看着很扎眼
P159
21. fp=fopen(“a1”,“r”);
22. fgets(str,strlen(str)+1,fp);
23. printf(“%s\n”,str);
24. fclose(fp);
这属于多做之过。完成问题根本没要求的功能也是一种错误。其中的第22.行更是错得荒谬无边。
P160
有两个磁盘文件”A”和”B”,各存放一行字母,要求把这两个文件中的信息合并(按字母顺序排列),输出到一个新文件”C”中。

——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p160

题目不是一般的无聊
是挖空心思的无聊

P160~161
#include <stdio.h>
#include <stdlib.h>
int main( )
{
FILE *fp;
int i,j,n,i1;
char c[100],t ,ch;
if((fp=fopen(“a1”,“r”))==NULL)
{printf(“can not open the file\n”);
exit(0);
}
printf(“\n file A:\n”);
for(i=0;(ch=fgetc(fp))!=EOF;i++)
{
c[i]=ch;
putchar(c[i]);
}
fclose(fp);

i1=i;
if((fp=fopen(“b1”,“r”))==NULL)
{printf(“\n can not ipen the file”);
exit(0);
}
printf(“\nfile B:\n”);
for(i=i1;(ch=fgetc(fp))!=EOF;i++)
{c[i]=ch;
putchar(c[i]);
}
fclose(fp);

n=i;
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
if(c[i]>c[j])
{t=c[i];
c[i]=c[j];
c[j]=t;
}
printf(“\n file C:\n”);
fp=fopen(“c1”,“w”);
for(i=0;i<n;i++)
{putc(c[i],fp);
putchar(c[i]);
}
printf(“\n”);
fclose(fp);
return 0;
}

02.#include <stdlib.h>

典型的无病呻吟。

  1. char c[100],t ,ch;
    这里的“100”毫无依据,不但是天真的也是武断的。写代码不能自说自话地假设问题的条件。那个t根本就不应该在这里定义。ch声明为char类型是有问题的。

  2. if((fp=fopen(“a1”,“r”))==NULL)

  3. {printf(“can not open the file\n”);

  4. exit(0);

  5. }
    文不对题货不对板。题目说的是文件“A”,这里却自作主张地打开了“a1”。另外只写文件名不写文件的位置也是一种陋习。

23.   exit(0);

外行写法

20. i1=i;

这句看起来没有问题,然而结合
26. for(i=i1;(ch=fgetc(fp))!=EOF;i++)
来看就能看出其中的滑稽了,因为从20.行到26.行无论是i的值还是i1的值都没有改变,因而20.行的i1=i与26.行的i=i1就如同一个人把右手中的筷子换到了左手,然后又把左手中的筷子换到了右手一样,其实筷子原本就在右手。

  1. n=i;
  2. for(i=0;i<n;i++)
  3. for(j=i+1;j<n;j++)
  4. if(c[ i ]>c[j])
  5.  {t=c[ i ];
    
  6.   c[ i ]=c[j];
    
  7.   c[j]=t;
    
  8.  }
    

这段代码的算法既非冒泡也非选择,有点非驴非马的味道。

  1. fp=fopen(“c1”,“w”);
    这行代码有两处错误,其一是打开的文件与题目要求不符,其二是没有考虑到文件打不开的情形

P161~162

#include <stdio.h>
struct student
{char num[10];
char name[8];
int score[3];
float ave;
}stu[5];
int main( )
{int i,j,sum;
FILE *fp;
for(i=0;i<5;i++)
{printf(“\n input score of student%d:\n”,i+1);
printf(“NO.:”);
scanf(“%s”,stu[i].num);
printf(“name:”);
scanf(“%s”,stu[i].name);
sum=0;
for(j=0;j<3;j++)
{printf(“score %d :”,j+1);
scanf(“%d”,&stu[i].score[j]);
sum+=stu[i].score[j];
}
stu[i].ave=sum/3.0;
}
//将数据写入文件
fp=fopen(“stud”,“w”);
for(i=0;i<5;i++)
if(fwrite(&stu[i],sizeof(struct student),1,fp)!=1)
printf(“File write error\n”);
fclose(fp);
fp=fopen(“stud”,“r”);
for(i=0;i<5;i++)
{fread(&stu[i],sizeof(struct student),1,fp);
printf(“\n%s,%s,%d,%d,%d,%6.2f\n”,stu[i].num,stu[i].name,stu[i].score[0],
stu[i].score[1], stu[i].score[2] ,stu[i].ave);}
system(“PAUSE”);return 0;
}

  1.    struct student
    
  2.    {char num[10];
    
  3.     char name[8];
    
  4.     int score[3];
    
  5.     float ave;
    
  6.    }stu[5];
    

许多初学者之所以在声明结构体类型时顺手定义外部变量是因为偷懒,不愿意再写一次“struct student”这样的结构体类型名称。这是贪小便宜的行为。俗话说贪小便宜吃大亏,在这里也是如此。顺手定义外部变量的后果是把整个代码结构弄得不堪入目。

  1.      {int i,j,sum;
    

这里定义的j和sum都很无聊。从main()全局来看,它们与问题无关。

  1.       fp=fopen("stud","w");
    

这里没有处理文件无法打开的情况,更严重的是,由于后面使用的是fwrite()函数写文件,打开模式明显应该是"wb"而不是"w"。

  1.       for(i=0;i<5;i++)
    
  2.         if(fwrite(&stu[ i ],sizeof(struct student),1,fp)!=1)
    
  3.           printf("File write error\n");
    

这段代码最大的问题是它检查了写入不正常的情况(fwrite(&stu[ i ],sizeof(struct student),1,fp)!=1),但除了输出“File write error”之外没有做其他任何处理。实际上在这种情况下后面的代码以及失去意义。
&stu[ i ]这个写法其实可以更简洁地写为stu + i 。另外,循环语句在这里是根本没有必要的,fwrite()函数可以写入成组的数据:
fwrite(stu+i,sizeof(struct student),5,fp);

下面这段代码的作用是进行测试。
01.30. fp=fopen(“stud”,“r”);

02.31. for(i=0;i<5;i++)

03.32. {fread(&stu[ i ],sizeof(struct student),1,fp);

04.33. printf(“\n%s,%s,%d,%d,%d,%6.2f\n”,stu[ i ].num,stu[ i ].name,stu[ i ].score[0],

05.34. stu[ i ].score[1], stu[ i ].score[2] ,stu[ i ].ave);}
它有这样一些问题:
首先
01.34. stu[i].score[1], stu[i].score[2] ,stu[i].ave);}
把“}”写在行末,就如同把袜子穿在了手上,寻常人等是没有这般勇气的。
01.32. {fread(&stu[i],sizeof(struct student),1,fp);
同样根本用不着放在循环语句中,可以直接
fread( stu , sizeof(struct student),5,fp);
就足以完成任务。
01.33. printf(“\n%s,%s,%d,%d,%d,%6.2f\n”,stu[i].num,stu[i].name,stu[i].score[0],

02.34. stu[i].score[1], stu[i].score[2] ,stu[i].ave);
把所有的东西都塞在一个函数调用之中是懒婆娘的作风,就如同把内衣外衣袜子一股脑地塞进一只箱子。score这样数组,居然分别写出然后一字排开stu[ i ].score[0] ,stu[ i ].score[1], stu[ i ].score[2],更是笨拙之中尤其之笨拙之写法。
最后要说的是,这段测试代码是无效的。因为在
01.27. if(fwrite(&stu[i],sizeof(struct student),1,fp)!=1)
之前,stu数组中已经写好了数据,即使在fwrite()没起作用的情况下,比如一个数据都没写进去,那么后面的
01.32. {fread(&stu[i],sizeof(struct student),1,fp);
同样不起作用。但是这时数组中的数据还在,依然能够输出。所以这段测试代码是一种自欺欺人的伪测试。
P163~164

#include <stdio.h>
#define SIZE 5
struct student
{char name[10];
int num;
int score[3];
float ave;
}stud[SIZE];

int main( )
{void save(void);
int i;
float sum[SIZE];
FILE *fp1;
for(i=0;i<SIZE;i++)
{scanf(“%s%d%d%d%d”,stud[i].name,&stud[i].num,&stud[i].score[0],
&stud[i].score[1],&stud[i].score[2]);
sum[i]=stud[i].score[0]+stud[i].score[1]+stud[i].score[2];
stud[i].ave=sum[i]/3;
}
save();
fp1=fopen(“stu.dat”,“rb”);
printf(“\n name NO. score1 score2 score3 ave\n”);
printf(“------------------------------------------------------\n”);
//输出表头
for(i=0;i<SIZE;i++)
{fread(&stud[i],sizeof(struct student),1,fp1);
printf(“%-10s%3d%7d%7d%7d%8.2f\n”,stud[i].name,stud[i].num,
stud[i].score[0],stud[i].score[1],stud[i].score[2] ,stud[i].ave);
}
fclose(fp1);
system(“PAUSE”);return 0;
}

void save(void)
{
FILE *fp;
int i;
if((fp=fopen(“stu.dat”,“wb”))==NULL)
{printf(“The file can not open\n”);
return ;
}
for(i=0;i<SIZE;i++)
if(fwrite(&stud[i],sizeof(struct student),1,fp)!=1)
{printf(“File write error\n”);
return ;
}
fclose(fp);
}

这是同一问题的另一写法。这个写法更成问题。
2. struct student
3. {char name[10];
4. int num;
5. int score[3];
6. float ave;
7. }stud[SIZE];
这个外部变量也是贪小便宜吃大亏,为了取暖不惜烧掉房子。

9.          {void save(void);

位置不当。
11. float sum[SIZE];
这个数组定义得莫名其妙,后面会看到它完全是多余的。

14.            {scanf("%s%d%d%d%d",stud[ i].name,&stud[ i].num,&stud[ i].score[0],
  1.              &stud[ i].score[1],&stud[ i].score[2]);
    

这个调用实在有些太雷人了,居然一口气写出了3个数组元素实参&stud[ i].score[0] , &stud[ i].score[1] , &stud[ i].score[2]。看来C语言根本就不需要发明循环语句。
另外,这里的scanf()调用是“裸体”的,对用户没有做任何提示,这种对用户一点也不友好的编程作风非常不值得效法。

  1.         sum[i]=stud[i].score[0]+stud[i].score[1]+stud[i].score[2];
    

02.17. stud[i].ave=sum[i]/3;
16.行完全多余,因为17.可以写为
01.stud[i].ave=( stud[i].score[0]+stud[i].score[1]+stud[i].score[2])/3.0F;
完全没必要为记录,stud[ i].score[0]+stud[ i].score[1]+stud[ i].score[2]而定义一个数组,因为stud[ i].score[0]+stud[ i].score[1]+stud[ i].score[2]这个值只是临时性的一次性使用。难道有人会为一次性使用的卫生纸制作一个精致且耐用的包装吗?

  1.    void save(void)
    

这个函数第一个错误是没有任何参数,它究竟如何知道应该打开哪个文件并向其中写什么呢?看来是生而知之的。凡是这种生而知之的函数基本上都是废品函数,因为只能打开某个特定文件并向其中写特定的数据(而且必须是外部变量),那么如果需要写到另一个文件就只能再克隆一个几乎一模一样的函数。从这个意义上来说,这种函数是废品函数,因为它没有任何适应能力和通用性。

36.         if((fp=fopen("stu.dat","wb"))==NULL)

这句显然文不对题。题目要求写入的文件并非stu.dat。当无法打开文件时
37. {printf(“The file can not open\n”);
38. return ;
39. }
诡异的是那个return;。因为无法打开文件应该视为程序执行过程中的一种错误或异常,这时应该对此进行必要的处理之后程序才能继续执行,如果无法处理则直接体面地结束程序。可是现在错误已经发生了,但是这句return;却像什么事都没发生一样心平气和脸不变色心不跳地返回main(),显然后面将无法保证程序的正确运行。这时程序往往“死”得很难看。

    1.     for(i=0;i<SIZE;i++)
      

02.41. if(fwrite(&stud[i],sizeof(struct student),1,fp)!=1)

03.42. {printf(“File write error\n”);

04.43. return ;

05.44. }
这里再次重复了一次前面所说的错误:在异常情况下假装正常,返回调用处。

回到main()之后是一段测试用的代码,这与题目要求无关。
20. fp1=fopen(“stu.dat”,“rb”);
再度打开文件,但却不对打不开的情况进行处理。

01.21. printf(“\n name NO. score1 score2 score3 ave\n”);

02.22. printf(“------------------------------------------------------\n”);

03.23. //输出表头
在main()这么重要的地方尽弄些鸡毛蒜皮。因而弄出
01.24. for(i=0;i<SIZE;i++)

02.25. {fread(&stud[i],sizeof(struct student),1,fp1);

03.26. printf(“%-10s%3d%7d%7d%7d%8.2f\n”,stud[i].name,stud[i].num,

04.27. stud[i].score[0],stud[i].score[1],stud[i].score[2] ,stud[i].ave);

05.28. }
这样的一地鸡毛。除此之外还有不少鸡屎:
01.fread(&stud[i],sizeof(struct student),1,fp1);
,没有处理读入异常情况,而且由于stud是数组,这里本来就不需要循环语句。至于
01.26. printf(“%-10s%3d%7d%7d%7d%8.2f\n”,stud[i].name,stud[i].num,

02.27. stud[i].score[0],stud[i].score[1],stud[i].score[2] ,stud[i].ave);
这样的语句,稍有自尊的程序员都是写不出手的。把数组元素一字排开挤在一条scanf()调用中,这简直是丑疯了。

这段代码本质上根本无法检测数据是否正确写入文件。因为若没写入文件,一般也无法正确地读出,但是由于这里输出的是stud数组,数组中原本就有数据,即使没有写入和再度读出,依然能输出相同的数据。

P164
将上题stud文件中的学生数据按平均分进行排序处理,并将已排序的学生数据存入一个新文件stu-sort中。
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p164
题目多少有些无聊,本质上无非还是读写文件。因为无聊,所以要加点料,这回加的作料是排序。

P165~166

#include <stdio.h>
#include <stdlib.h>
#define N 10
struct student
{char num[10];
char name[8];
int score[3];
float ave;
}st[N],temp;

int main( )
{FILE *fp;
int i,j,n;

//读文件
if((fp=fopen(“stud”,“r”))==NULL)
{printf(“can not open the file”);
exit(0);
}
printf(“\n File ‘stud’:”);
for(i=0;fread(&st[i],sizeof(struct student),1,
fp)!=0;i++)
{printf(“\n%8s%8s”,st[i].num,st[i].name);
for(j=0;j<3;j++)
printf(“%8d”,st[i].score[j]);
printf(“%10.2f”,st[i].ave);
}
printf(“\n”);
fclose(fp);
n=i;

//排序
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
if(st[i].ave<st[j].ave)
{temp=st[i];
st[i]=st[j];
st[j]=temp;
}

//输出
printf(“\nNow:”);
fp=fopen(“stu-sort”,“w”);
for(i=0;i<n;i++)
{fwrite(&st[i],sizeof(struct student),1,fp);
printf(“\n%8s%8s”,st[i].num,st[i].name);
for(j=0;j<3;j++)
printf(“%8d”,st[i].score[j]);
printf(“%10.2f”,st[i].ave);
}
printf(“\n”);
fclose(fp);
return 0;
}

  1.    #define N 10
    

这个10不知道是从哪来的,题目中根本就没有提。自作主张地添加条件是一种恶习。看到这里可以得到一个结论,要么题目条件不完全因而是错误的,要么代码是错误的。

  1.    struct student
    
  2.    {char num[10];
    
  3.     char name[8];
    
  4.     int score[3];
    
  5.     float ave;
    
  6.    }st[N],temp;
    

纵观整段代码,只有一个main()函数,这本身就是一种初学者的幼稚病。在只有一个函数的情况下,因为偷懒顺手定义外部变量,这是一种恶习。令人大跌眼镜的是这里居然定义了一个八竿子打不着的只是在千里之外的角落里才用得着的temp,实在令人匪夷所思。这太雷人了也。

  1.     //读文件
    
  2.     if((fp=fopen("stud","r"))==NULL)
    
  3.       {printf("can not open the file");
    
  4.        exit(0);
    
  5.       }
    

从后面对fp的使用来看,这里用"r"模式打开是错误的,应该用"rb"模式打开。
exit(0)似是而非。

  1.     for(i=0;fread(&st[i],sizeof(struct student),1,
    
  2.     fp)!=0;i++)
    
  3.       {printf("\n%8s%8s",st[i].num,st[i].name);
    
  4.        for(j=0;j<3;j++)
    
  5.          printf("%8d",st[i].score[j]);
    
  6.        printf("%10.2f",st[i].ave);
    
  7.       }
    
  8.     printf("\n");
    
  9.     fclose(fp);
    
  10.     n=i;
    

把读入数据和输出搅在一起,不是明智写法。
再有,这里的i的作用是记数,用for语句极不自然。最后再把i的值赋给n,是很笨拙的写法。

01.28. //排序
02.29. for(i=0;i<n;i++)
03.30. for(j=i+1;j<n;j++)
04.31. if(st[i].ave<st[j].ave)
05.32. {temp=st[i];
06.33. st[i]=st[j];
07.34. st[j]=temp;
08.35. }
排序通常都是指从小到大排,除非另外特别说明。但是这里的排序确是一反常规地从大到小,而题目中并没有说明要求从大到小排序。
这里的算法也不够好,是一种效率很低的交换法。其中的“i<n”是一个不够精准的条件,应该写为“ i < n – 1 ”。

01.36. //输出
02.37. printf(“\nNow:”);
03.38. fp=fopen(“stu-sort”,“w”);
04.39. for(i=0;i<n;i++)
05.40. {fwrite(&st[i],sizeof(struct student),1,fp);
06.41. printf(“\n%8s%8s”,st[i].num,st[i].name);
07.42. for(j=0;j<3;j++)
08.43. printf(“%8d”,st[i].score[j]);
09.44. printf(“%10.2f”,st[i].ave);
10.45. }
11.46. printf(“\n”);
12.47. fclose(fp);
这段代码的问题较多,首先"w"这种打开模式是错误的,二进制文件应该用"wb"模式打开。其次,没考虑无法打开文件时如何处理。第三,用不着使用循环语句向文件写数据。第四,没有考虑写入出错情况下的处理。最后,41.44.行完全是20.23.行的重复,这是烂代码的一个标志。

P168~169
#include <stdio.h>
#include <stdlib.h>
struct student
{char num[10];
char name[8];
int score[3];
float ave;
}st[10],s;

int main( )
{FILE *fp, * fp1 ;
int i,j,t,n;
printf(“\n NO.:”);
scanf(“%s”,s.num);
printf(“name:”);
scanf(“%s”,s.name);
printf(“score1,score2,score3:”);
scanf(“%d,%d,%d”,&s.score[0], &s.score[1], &s.score[2]);
s.ave=(s.score[0]+s.score[1]+s.score[2])/3.0;

//从文件读数据
if((fp=fopen(“stu_sort”,“r”))==NULL)
{printf(“can not open file.”);
exit(0);
}
printf(“original data:\n”);
for(i=0;fread(&st[i],sizeof(struct student),1,fp)!=0;i++)
{printf(“\n%8s%8s”,st[i].num,st[i].name);
for(j=0;j<3;j++)
printf(“%8d”,st[i].score[j]);
printf(“%10.2f”,st[i].ave);
}

n=i;
for(t=0;st[t].ave>s.ave&&t<n;t++);

//向文件写数据

printf(“\nNow:\n”);
fp1=fopen(“sort1.dat”,“w”);
for(i=0;i<t;i++)
{fwrite(&st[i],sizeof(struct student),1,fp1);
printf(“\n%8s%8s”,st[i].num,st[i].name);
for(j=0;j<3;j++)
printf(“%8d”,st[i].score[j]);
printf(“%10.2f”,st[i].ave);
}
fwrite(&s,sizeof(struct student),1,fp1);
printf(“\n%8s%7s%7d%7d%7d%10.2f”,s.num,s.name,s.score[0],
s.score[1],s.score[2],s.ave);

for(i=t;i<n;i++)
{fwrite(&st[i],sizeof(struct student),1,fp1);
printf(“\n %8s%8s”,st[i].num,st[i].name);
for(j=0;j<3;j++)
printf(“%8d”,st[i].score[j]);
printf(“%10.2f”,st[i].ave);
}

printf(“\n”);
fclose(fp);
fclose(fp1);

return 0;
}
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p168~169

  1.    struct student
    
  2.    {char num[10];
    
  3.     char name[8];
    
  4.     int score[3];
    
  5.     float ave;
    
  6.    }st[10],s;
    

看来是认准外部变量Only You了。

  1. for(t=0;st[t].ave>s.ave&&t<n;t++);

这里明显存在一个逻辑错误

01.25. {printf(“\n%8s%8s”,st[i].num,st[i].name);
02.26. for(j=0;j<3;j++)
03.27. printf(“%8d”,st[i].score[j]);
04.28. printf(“%10.2f”,st[i].ave);
05.29. }

01.37. printf(“\n%8s%8s”,st[i].num,st[i].name);
02.38. for(j=0;j<3;j++)
03.39. printf(“%8d”,st[i].score[j]);
04.40. printf(“%10.2f”,st[i].ave);
以及
01.47. printf(“\n %8s%8s”,st[i].num,st[i].name);
02.48. for(j=0;j<3;j++)
03.49. printf(“%8d”,st[i].score[j]);
04.50. printf(“%10.2f”,st[i].ave);
甚至
01.43. printf(“\n%8s%7s%7d%7d%7d%10.2f”,s.num,s.name,s.score[0],
02.44. s.score[1],s.score[2],s.ave);
是重复的
重复是万恶之源

最后,169页的运行结果纯粹是伪造的,这很恶劣
P171~172
0. #include<stdio.h>

  1.    #include <stdlib.h>
    
  2.    #include <string.h>
    
  3.    struct employee
    
  4.    {char num[6];
    
  5.     char name[10];
    
  6.     char sex[2];
    
  7.     int age;
    
  8.     char addr[20];
    
  9.     int salary;
    
  10.     char health[8];
    
  11.     char class[10];
    
  12.    }em[10];
    
  13.    struct emp
    
  14.    {char name[10];
    
  15.     int salary;
    
  16.    }em_case[10];
    
  17.    int main( )
    
  18.      {FILE *fp1, *fp2;
    
  19.       int i,j;
    
  20.      if ((fp1=fopen("employee","r"))==NULL)
    
  21.        {printf("can not open the file.");
    
  22.         exit(0);
    
  23.        }
    
  24.      printf("\n  NO.  name  sex  age  addr  salary  health  class\n");
    
  25.      for(i=0;fread(&em[i],sizeof(struct employee),1,fp1)!=0;i++)
    
  26.       {printf("\n%4s%8s%4s%6d%10s%6d%10s%8s",em[i].num,em[i].name,em[i].sex,
    
  27.             em[i].age, em[i].addr, em[i].salary, em[i].health, em[i].class);
    
  28.        strcpy(em_case[i].name, em[i].name);
    
  29.        em_case[i].salary=em[i].salary;
    
  30.       }
    
  31.       printf("\n\n*******************************************");
    
  32.       if((fp2=fopen("emp_salary","wb"))==NULL)
    
  33.         {printf("can not open the file.");
    
  34.          exit(0);
    
  35.         }
    
  36.       for(j=0;j<i;j++)
    
  37.         {if(fwrite(&em_case[j],sizeof(struct emp),1,fp2)!=1)
    
  38.            printf("error!");
    
  39.          printf("\n %12s%10d",em_case[j].name,em_case[j].salary);
    
  40.         }
    
  41.      printf("\n*******************************************");
    
  42.      fclose(fp1);
    
  43.      fclose(fp2);
    
  44.      return 0;
    
  45.    }
    

——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p171~172

  1.    struct employee
    
  2.    {char num[6];
    
  3.     char name[10];
    
  4.     char sex[2];
    
  5.     int age;
    
  6.     char addr[20];
    
  7.     int salary;
    
  8.     char health[8];
    
  9.     char class[10];
    
  10.    }em[10];
    
  11.    struct emp
    
  12.    {char name[10];
    
  13.     int salary;
    
  14.    }em_case[10];
    

定义了两个外部数组不但非常可笑,而且也是一种作茧自缚的行为。因为在文件中数据超过10个的时候程序失效。

此外既然这里可以把name和salary聚合成一个结构体类型的数据,那么当初写文件的时候就应该把struct employee类型定义为
struct emp
{
char name[10];
int salary;
};

struct employee
{
struct emp name_salary;
char num[6];
char sex[2];
int age;
char addr[20];
char health[8];
char class[10];
}
这样代码要简单很多。

  1.      if ((fp1=fopen("employee","r"))==NULL)
    

这里的错误地把"rb"写成了"r",这会导致读取数据提前结束的异常。

  1.    if((fp2=fopen("emp_salary","wb"))==NULL)
    

这次打开模式意外地写对了,应该表扬一下。然而
34. exit(0);
这里又错了。

  1.       for(j=0;j<i;j++)
    
  2.         {if(fwrite(&em_case[j],sizeof(struct emp),1,fp2)!=1)
    
  3.            printf("error!");
    
  4.          printf("\n %12s%10d",em_case[j].name,em_case[j].salary);
    
  5.         }
    

这里的错误是出现异常不做任何处理,还当作平安无事地继续执行程序,勇敢向前走,尽管灾难在招手,全然不觉已经大事不好了。

P173
#include<stdio.h>
#include <stdlib.h>
struct employee
{char num[6];
char name[10];
char sex[2];
int age;
char addr[20];
int salary;
char health[8];
char class[10];
}em[10];

int main( )
{
FILE *fp;
int i;
printf(“input NO.,name,ex,age,addr,salary,health,class\n”);
for(i=0;i<4;i++)
scanf(“%s%s%s%d%s%d%s%s”,em[i].num,em[i].name,em[i].sex,
&em[i].age,em[i].addr,&em[i].salary, em[i].health,em[i].class);

    //将数据写入文件

if((fp=fopen(“employee”,“w”))==NULL)
{printf(“can not open the file.”);
exit(0);
}

for(i=0;i<4;i++)
if(fwrite(&em[i],sizeof(struct employee),1,fp)!=1)
printf(“error!”);
fclose(fp);
return 0;
}
——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p173

这段代码用于建立文件

  1.    struct employee
    
  2.    {char num[6];
    
  3.     char name[10];
    
  4.     char sex[2];
    
  5.     int age;
    
  6.     char addr[20];
    
  7.     int salary;
    
  8.     char health[8];
    
  9.     char class[10];
    
  10.    }em[10];
    

描述职工数据的数据结构不合理。例如职工号用char [6],性别用char [2]以及工资用int类型。

最荒唐的是外部数组em。如果像代码中那样从键盘输入数据建立文件的话根本不需要外部变量,更不需要数组。更何况这个具有10个元素的数组完全把程序的功能给无理地限定非常狭仄。

  1.      for(i=0;i<4;i++)
    
  2.      scanf("%s%s%s%d%s%d%s%s",em[i].num,em[i].name,em[i].sex,
    
  3.           &em[i].age,em[i].addr,&em[i].salary, em[i].health,em[i].class);
    

这个“i<4”,进一步限制了程序功能

  1.      if((fp=fopen("employee","w"))==NULL)
    
  2.        {printf("can not open the file.");
    
  3.         exit(0);
    
  4.        }
    

文件打开模式错误;exit(0)似是而非。

  1.      for(i=0;i<4;i++)
    
  2.        if(fwrite(&em[i],sizeof(struct employee),1,fp)!=1)
    
  3.          printf("error!");
    

这里就更奇怪了。已经发生了错误,只输出了一个"error!",然后就像没事人一样若无其事地继续执行,就像开车发现发动机漏油,只是跟乘客打了声招呼但依旧全速继续开一样。

P174~175
0. #include <stdio.h>

  1.    #include <stdlib.h>
    
  2.    #include <string.h>
    
  3.    struct employee
    
  4.    {char name[10];
    
  5.    int salary;
    
  6.    }emp[20];
    
  7.    int main( )
    
  8.      {FILE *fp;
    
  9.       int i,j,n,flag;
    
  10.       char name[10];
    
  11.       if((fp=fopen("emp_salary","rb"))==NULL)
    
  12.         {printf("can not open file.");
    
  13.          exit(0);
    
  14.         }
    
  15.       printf("\noriginal data:");
    
  16.       for(i=0;fread(&emp[i],sizeof(struct
    
  17.          employee),1,fp)!=0;i++)
    
  18.          printf("\n %8s %7d",emp[i].name,emp[i].salary);
    
  19.       fclose(fp);
    
  20.       n=i;
    
  21.       printf("\n input name deleted:\n");
    
  22.       scanf("%s",name);
    
  23.       for(flag=1,i=0;flag&&i<n;i++)
    
  24.        {if(strcmp(name,emp[i].name)==0)
    
  25.           {for(j=i;j<n-1;j++)
    
  26.            {strcpy(emp[j].name,emp[j+1].name);
    
  27.             emp[j].salary=emp[j+1].salary;
    
  28.            }
    
  29.            flag=0;
    
  30.           }
    
  31.         }
    
  32.       if(!flag)
    
  33.         n=n-1;
    
  34.       else
    
  35.         printf("\nnot found!");
    
  36.       printf("\n Now,the content of file:\n");
    
  37.       if((fp=fopen("emp_dalary","wb"))==NULL)
    
  38.         {printf("can not open file.");
    
  39.          exit(0);
    
  40.         }
    
  41.       for(i=0;i<n;i++)
    
  42.         fwrite(&emp[i],sizeof(struct employee),1,fp);
    
  43.       fclose(fp);
    
  44.       fp=fopen("emp_salary","r");
    
  45.       for(i=0;fread(&emp[i],sizeof(struct employee),1,fp)!=0;i++)
    
  46.         printf("\n%8s%7d",emp[i].name,emp[i].salary);
    
  47.       printf("\n");
    
  48.       fclose(fp);
    
  49.       return 0;
    
  50.    }
    

——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p174~175

  1.    struct employee
    
  2.    {char name[10];
    
  3.    int salary;
    
  4.    }emp[20];
    

这个让人喷饭。因为在前面的题目中
struct emp
{char name[10];
int salary;
}em_case[10];
已经引刀自宫地限定了文件中最多有10个数据,现在居然企图人为地把数据数目加大为20,这是自欺欺人。

  1.       int i,j,n,flag;
    

看见flag就仿佛能嗅出代码中的馊味。不信?等着瞧。

  1.       if((fp=fopen("emp_salary","rb"))==NULL)
    
  2.         {printf("can not open file.");
    
  3.          exit(0);
    
  4.         }
    

这里的exit(0);是错误的,打开方式是错误的。"rb"只表示想要读文件,但程序显然不仅仅需要读这个文件,还需要写

  1.       for(i=0;fread(&emp[i],sizeof(struct
    
  2.          employee),1,fp)!=0;i++)
    
  3.          printf("\n %8s %7d",emp[i].name,emp[i].salary);
    
  4.       fclose(fp);
    
  5.       n=i;
    

题目并没有要求做输出数据这件事,这叫多管闲事,也叫多做之过。
这段代码的另一个功能是统计文件中有多少个数据,它设计在fread()返回值为0时停止循环,然后指望根据i的值来了解数据文件中有多少数据。但是这个得意算盘的失算之处在于,fread()并非仅仅在读到文件结尾时返回值为0,当读入过程中发生错误fread()的返回值也同样为0。

  1.       for(flag=1,i=0;flag&&i<n;i++)
    
  2.        {if(strcmp(name,emp[ i ].name)==0)
    
  3.           {for(j=i;j<n-1;j++)
    
  4.            {strcpy(emp[j].name,emp[j+1].name);
    
  5.             emp[j].salary=emp[j+1].salary;
    
  6.            }
    
  7.            flag=0;
    
  8.           }
    
  9.         }
    

这段代码不伦不类非常蹩脚,实际上它表达的无非是

  1. for( i = 0 ; i < n ; i++ )

  2.  if(strcmp(name,emp[i].name)==0)
    
  3.  {
    
  4.     for( j = i ; j < n - 1 ; j++ )
    
  5.     {
    
  6.        strcpy(emp[j].name,emp[j+1].name);
    
  7.        emp[j].salary=emp[j+1].salary;
    
  8.     }
    
  9.     break;
    
  10.  }
    

复制代码而已。原来代码中的flag像正在发炎的阑尾,除了带出毛病,本身是没有什么用的。这段代码更简单的写法是

  1. for( i = 0 ; i < n ; i++ )

  2.  if(strcmp(name,emp[i].name)==0)
    
  3.     break;
    
  4. for( j = i ; j < n - 1 ; j++ )

  5. {

  6.  strcpy(emp[j].name,emp[j+1].name);
    
  7.  emp[j].salary=emp[j+1].salary;
    
  8. }
    复制代码原来那种复杂的循环嵌套显然是源自一种变态的想法,是把简单问题复杂化。

  9.         printf("\nnot found!");
    

这行代码的问题是,既然明明知道没找到,那么数据文件不用做任何处理,直接退出就可以了。可是它没有退出,表现出一副非常留恋的样子,继续执行后面在这种情况下其实完全没有意义的操作。

  1.       fp=fopen("emp_salary","r");
    
  2.       for(i=0;fread(&emp[i],sizeof(struct employee),1,fp)!=0;i++)
    
  3.         printf("\n%8s%7d",emp[i].name,emp[i].salary);
    
  4.       printf("\n");
    
  5.       fclose(fp);
    

很不幸,打开文件的模式"r"又是错的,应该是"rb"。而且一相情愿地假设读入不会发生错误。此外问题根本没有要求重新打开文件输出数据。

P175
从键盘输入若干行字符(每行长度不等),输入后把它们存储到一磁盘文件中。再从该文件中读入这些数据,将其中小写字母转换成大写字母后在显示屏上输出。——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p175

题目不明确的地方在于没有说明输入如何结束,而所谓“每行长度不等”又是毫无意义的废话。
P175~176
0. #include <stdio.h>

  1.    int main( )
    
  2.       {int i,flag;
    
  3.        char str[80],c;
    
  4.        FILE *fp;
    
  5.            fp=fopen("text","w");
    
  6.            flag=1;
    
  7.            while(flag==1)
    
  8.              {printf("\n Input string:\n");
    
  9.               gets(str);
    
  10.               fprintf(fp,"%s",str);
    
  11.               printf("\nContinue?");
    
  12.               c=getchar();
    
  13.               if((c=='N')||(c=='n'))
    
  14.               flag=0;
    
  15.               getchar();
    
  16.              }
    
  17.            fclose(fp);
    
  18.            fp=fopen("text","r");
    
  19.            while(fscanf(fp,"%s",str)!=EOF)
    
  20.              {for(i=0;str[i]!='\0';i++)
    
  21.               if((str[i]>='a')&& (str[i]<='z'))
    
  22.               str[i]-=32;
    
  23.               printf("%s\n",str);
    
  24.              }
    
  25.            fclose(fp);
    
  26.            return 0;
    
  27.       }
    

——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p175~176

由于问题并没有限制每行输入字符数不超过80,所以
3. char str[80],c;
这种数据结构毫无依据。如果用户输入的某行字符数超过80,程序可能面临灾难性的崩溃。

  1.    fp=fopen("text","w");
    

这里有两个错误,第一没有检查文件是否打开,即没有考虑fp的值为NULL的情况;此外没有对fopen()的类型进行声明,亦即代码缺少
#include <stdlib.h>

  1.            flag=1;
    
  2.            while(flag==1)
    
  3.              {printf("\n Input string:\n");
    
  4.               gets(str);
    
  5.               fprintf(fp,"%s",str);
    
  6.               printf("\nContinue?");
    
  7.               c=getchar();
    
  8.               if((c=='N')||(c=='n'))
    
  9.               flag=0;
    
  10.               getchar();
    
  11.              }
    

这通常是BASIC、FORTRAN等语言的一种笨拙写法,由于语言的表达能力不强,在那些语言中只好这样写。但在C语言中这样写就笨得有些可笑了。那个flag怎么看怎么别扭。

这里使用的gets(str)函数调用也很成问题,第一,由于前面限定了
3. char str[80],c;
所以可能发生越界的可能。第二,gets()函数并不将读入的回车换行写入数组,造成信息丢失,这样将来也无法将回车换行符写入文件,以后再从文件读出的内容根本分不清究竟是哪行的,成了一锅粥。

  1.            fp=fopen("text","r");
    

这里的错误同样是没有检查fopen()返回值为NULL的情况。

  1.            while(fscanf(fp,"%s",str)!=EOF)
    
  2.              {for(i=0;str[i]!='\0';i++)
    
  3.               if((str[i]>='a')&& (str[i]<='z'))
    
  4.               str[i]-=32;
    
  5.               printf("%s\n",str);
    
  6.              }
    

这段代码的主要问题是,没有检查fscanf()返回EOF是到达文件结尾还是读入过程出错。此外“str[i]-=32;”这种写法非常差劲,32是一个莫名其妙的常数。

最为恶劣的是
176页的运行结果完全是伪造的
这和错误不同
是欺骗
第11章 预处理命令
P177
第11章 预处理命令
在预处理阶段,预处理器把程序中的注释全部删除;
评:不是“删除”
“程序”这个术语也不妥当
应该是“源程序”
P177
将程序中的符号常量用指定的字符串代替
评:“字符串”这个术语是错误的
P177
最后再由编译程序对预处理后的源程序进行实际的编译处理,得到可供执行的目标代码
评:“预处理后的源程序”,应该是翻译单元
“实际的编译处理”,看来还有虚幻的编译处理了
“得到可供执行的目标代码”,得到的并不是可供执行的目标代码
P177
C语言与其他高级语言的一个重要区别是可以使用预处理指令和具有预处理的功能
评:胡扯
P178
#define 标识符 字符串
评:“字符串”术语错用
P178
这就是已经介绍过的定义符号常量
评:只能说符号常量是用这种方式定义的
这种宏并不是定义符号常量专用的
P178
#define PI 3.1415926
……
将程序中……所有的“PI”都用“3.1415926”
评:不是“所有”
P178
这种方法使用户能以一个简单的名字代替一个长的字符串,因此把这个标识符(名字)称为“宏名”
评:“因此”二字万分可笑
谭从来搞不清什么叫因什么叫果
因此,“因此”二字在他那里只是一种语气词而已
阅读时务请特别注意小心
P179
C语言把所有的实数都作为双精度数处理
评:扯淡
事实是
谭使用float在新的编译器中遇到了“警告”
但并不知道如何处理
只好把所有的数据都改成了double类型
P179
由于变量为double型,故在输出时用%lf格式符(在f之前加小写字母l),否则会输出不正确的数字。读者可以试一下。
评:读者只要试一下就会知道这绝对是在胡扯
P179
使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量
评:“字符串”术语错用
此外这不是宏最重要的用意
P179
#define array_size 1000
评:忘记在同页刚刚说过“宏名习惯用大写”
P179
宏定义只是用宏名代替一个字符串
评:根本没搞清楚什么叫“宏定义”
P180
#define指令出现在程序中的函数的外面
评:没有这回事

P181
对程序中用双撇号括起来的字符串内的字符,即使与宏名相同,也不进行置换
评:这没错
但和老谭前面自己说过的“所有的都……代替”自相矛盾
P181
#define 宏名(参数表) 字符串
评:字符串,是程序处理的对象,其含义是在尾部包含\0字符的字符序列。用在这里明显是关公战秦琼
P181
带参数的宏定义不是进行简单的字符串替换,还要进行参数替换
评:这和179页所说的“宏定义只是用宏名代替一个字符串”明显是自相矛盾的
P181
在程序中如果有带实参的宏(如S(3,2)),则按#define指令行中指定的字符串从左到右进行置换
评:并不存在什么“从左到右进行置换”
这个说法也没有任何实际意义
P181
#define S® PIrr
评:这是一个不合格的宏定义
在例题中写出这种东西是一个误导
P182
实参字符串
评:术语错误
P182
将S(a)中的实参字符a代替宏定义中的字符串"PIrr"中的形参r,
评:前面说“实参字符串”
这里说“实参字符”
自相矛盾,而且都属于错误的术语

“字符串"PIrr"中的形参r”,错的更没边了,这里哪来的形参呢

P182
字符串中的形式参数
评:同前
P182
#define S ® PIrr //在S后有一空格
系统会认为S是符号常量(不带参数的宏名)
评:这不是“符号常量”
“符号常量”和“不带参数的宏名”也并非等价的说法
P182
#define S ® PIrr //在S后有一空格
……
area=S(a);
则被置换为
aArea=® PIrr(a);
评:aArea:明显的错误
P183
实参字符“a+b”
评:这是“字符”吗?
P183
对函数中的实参和形参都要定义类型
评:对“实参”“定义类型”?
怎么定义?
P183
定义宏时,字符串可以是任何类型的数据
评:简直不敢相信我的眼睛
哪怕喝高了也不至于说出这种胡话吧
P183
#define CHAR1 CHINA (字符)
#define A 3.6 (数值)
……在程序中凡遇“CHAR1”均以字符“CHINA”代之;凡遇“A”均以字符"3.6"代之
评:字符“CHINA”
字符"3.6"

一个字:雷人
P183
调用函数只可得到一个返回值,而用宏可以设法得到几个结果
评:前半句是对的,
后半句纯粹是胡扯
P183
#define CIRCLE(R,L,S,V) L=2PIR;S=PIRR;V=4.0/3.0PIRRR
评:如果用意是展示一下什么是垃圾
我同意
P183
double r,……
scanf(“%f”,&r);
评:不妨留给初学者作为改错题吧
P184
程序中只给出一个实参r的值,就可以从宏CIRCLE的置换中得到3个值(l,s,v)
评:牛!
金庸应该来学习学习怎样写武侠
P184
使用宏次数多时,宏展开后源程序变长,因为每展开一次都使程序增长,而函数调用不会使源程序变长
评:没有根据的说法
而且这种讨论没有意义
P184
#define MAX(x,y) (x)>(y)?(x) : (y)
评:这是一个不合格的宏定义
P184~185
可以事先将程序中的“输出格式”定义好,以减少在输出语句中每次都要写出具体的输出格式的麻烦。
评:这是书房里的异想天开
P185
#define PR printf
#define NL “\n”
#define D “%d”
#define D1 D NL
……
PR(D1,a);
……
评:如果想让老板和同事的讥笑和痛斥
你不妨这样写好了

建议学习者完全无视185页,直接跳过为好
P185
把它们单独编成一个文件,它相当于一个“格式库”
评:Too simple,sometimes naive
对开发过程完全无知
对“库”的概念也一无所知
P186
图11.2(a)为文件file1.c,该文件中有一个#include <file2.c>指令
评:笑喷了
参见
《谭浩强的书我就不说什么了,居然教学生include一个.c文件》
http://bbs.chinaunix.net/thread-1603177-1-1.html

其中有一种观点认为,在某些特定情况下需要include .c文件

starwing83 网友对此发表了精彩的见解:(3140楼)

我看了一下那个帖子,发现根本就没有说到重点上。include .c,如果.c声明的是非static的函数,那么根本就没法解决重名问题。比如说两个库都有OpenFile,而且不是static的,那么无论你是不是include .c都会链接错误,因为.o里面的名字相同,这个没法骗人的。

所以这种情况下,要么在.c里面写static函数并且include .c(后缀名不重要),这样即使.c被意外编译也无所谓,要么就是脱了裤子放屁,照样得死。唯一的解决方案只有加前缀。

如果.c里面的是static函数,用这个后缀名也是不合适的,.c.inc就好多了。

最后,如果实在懒得加前缀,可以考虑zlib的做法,即写个头文件,写一堆的
#define OpenFile A_OpenFile

然后用在.c文件里面(绝对不能被包含到.h文件中),然后.c里面就可以用OpenFile了,当然这样还是不需要include .c,直接分离编译即可。

否则,所有内部函数都要加static,这个工作量看不出来和加前缀有什么区别,有功夫给所有的内部函数加static,不如用刚才说的技术,写个那种头文件。

当然如果还是嫌麻烦,先ctags搞出一个列表,然后vim处理一下做个脚本,用sed处理一遍,也很方便。

总的来说,include .c完全是没有必要的。而且是危险的、不能解决问题的。

再说说:以上这堆估计村长是知道的,但是他:

  • 就是不想让函数被除了本模块的人以外的人知道(那么加前缀就没用了)
  • 希望编辑器能高亮(那就必须.c结尾)
  • static只有在一个文件内才有用(那就只有include了)

这种情况下,.c.inc至少对Vim和Emacs来说也是有用的(点分隔的扩展名只要有一个有效就会默认高亮),另外,还有一个非常困难的解决方案:
– 究竟是为什么,一个模块会有几十万行代码,而且连接紧密?能不能分解为几个模块?

村长的答案是不行,我觉得这个很匪夷所思,就算有static,如果要将一个模块分解成几个模块也不是很难的——最简单的思路,如果模块有几十万行,我很难想像其对外接口会只有一个公开函数,那么如果有多个公开函数,那么很容易就可以让一个公开函数一个文件,然后这个只有公开函数需要的功能写成static放到这个文件中,对于模块内部需要使用的公共服务,这个是绝对可以抽象成一个独立模块的(因为都是服务,就肯定能做事,因为共有了,就肯定有协议,有协议又做实事的,是绝对可以作为一个独立模块而存在的),最终会形成一个模块的树,然后依次都可以简单地.h .c了,当然这就是重构。

这里仍然有个问题没法解决:假设A,B两个模块都变成了模块树,那么底层的小模块就相当于透明了,一旦小模块的API相同,那么仍然逃不过链接错误的命运,而且实际上这样仍然是没有隐藏掉底层小模块的接口的。

所以,实际的解决方法是——让函数的实现和命名有必然关系,让一个功能只能会有一个名字,让一个名字一定是某个特定的功能而不会重复,这仍然是重构,在这个基础上,是根本就不可能出现名字相同而功能不同的情况的——这种情况是设计缺陷。很多时候,名字重复而功能不同是概念定义模糊的结果,Open还是Create?NetFile还是LocalFile?如果真的严格区分了项目所有的概念,就不大可能出现重名问题了,就算都是Matrix,你也可以命名成m3x3或者m4x4。

这里仍然有个问题,假设两个模块都有OpenFile,但是具体的配置不一样(比如一个立即打开,一个延缓打开),这样这个OpenFile是可以抽出作为独立模块的,然后包括所有的配置(这是模块的责任)然后分别给重名的位置使用——这仍然是重构。

那么,如果在设计之初函数名和功能没有本质上的联系(都叫OpenFile,一个是打开本地文件,一个是打开网络文件),而且又不愿意重构,那就只能include .c了,然而,即使是这种极端情况,include .c.inc也好很多,有include .c的所有优点,又没有缺陷。

总的来说,include .c是一个信号——“你的模块划分有问题,你的模块API命名有问题,你项目里的概念不够清晰”,这是需要解决的,include .c绝对是一个红灯,当然闯红灯是可以的——只要你爸是李刚即可,但是大多数情况,我们还是要为自己的生命安全着想。
P186
“文件包含”指令是很有用的,它可以节省程序设计人员的重复劳动
评:前半句是废话
后半句表明作者根本不清楚其实际使用方法
另:“节省”“重复劳动”是病句
P186
例11.6 将例11.5的格式宏做成头文件,把它包含在用户程序中
评:错上加错,而且极其无聊
建议初学者直接无视
P187
……“头文件”,常以“.h”为后缀……当然不用“.h”为后缀而用“.c”为后缀或者没有后缀也是可以的
评:这是在教人如何在项目中捣乱
P188
如果文件1需要包含文件2,而在文件2中又要用到文件3的内容,则可在文件1中用两个#include指令分别包含文件2和文件3,而且文件3应出现在文件2之前,即在file1.c中定义:
#include “file3.h”
#include “file2.h”
评:这是完全不懂代码管理的外行话
“在file1.c中定义”:居然能冒充“定义”两个字
P188
头文件除了可以包含函数原型和宏定义外,也可以包括结构体类型定义(见第10章)和全局变量定义
评:“全局变量”本身是错误的概念
在.h中定义变量是极其错误的做法
P189
#ifdef COMPUTER_A
#define INTEGER 16
#else
#define INTEGER_SIZE 32
#endif
评:#define INTEGER
明显是
#define INTEGER_SIZE
之误
P192
此时运行结果为
C language
评:这又是一个伪造的结果
P192
对这个问题完全可以不用条件编译处理而用if语句处理,但那样做,目标程序长(因为所有语句都编译),运行时间长……
评:这属于不懂装懂
if语句和条件编译所要解决的问题是截然不同的
用条件编译根本不是为了解决“目标程序长,运行时间长”这样的问题
P192
只是为了说明怎样使用条件编译,有人会觉得其优越性不太明显
评:自己都不懂得为什么使用条件编译
怎么可能说明怎样使用条件编译呢
以其昏昏使人昭昭
是不可能的
P192
预处理功能是C语言特有的
评:是井底之蛙
还是没话找话?
而且自相矛盾
因为在177页说过“它不是C语言本身的组成部分”
怎么又成了“C语言特有的”的呢
P192
善于利用预处理命令,对提高程序的质量会有好处的
评:程序质量不高不可能是因为“善于利用预处理命令”
但像谭这样滥用预处理命令会败坏程序质量则是必然的

第12章 位运算
P193
评:开篇第一句就很有气势
位运算是C语言的重要特色,是其他计算机高级语言所没有的
评:什么话都敢说
C++er对此不知作何感想
P193
所谓位运算是指以二进制位为对象的运算
评:不好意思谭先生
C语言里没有以位对运算对象的运算

P193
在系统软件中,常要处理二进制位的问题
评:有这事儿吗?
P193
例如,将一个存储单元的各二进制位左移或右移一位,两个数按位相加等
评:这个“例如”难道是在佐证“在系统软件中,常要处理二进制位的问题”吗?
依据不足啊

两个数“按位相加”是什么意思?恐怕又是谭先生的“发明”吧

P193
C语言提供位运算的功能,与其他高级语言相比,它显然具有很大的优越性。
评:有挑起语言战争之嫌
我认为Ritchie是绝对不敢这样讲的

P193
指针运算和位运算往往是编写系统软件所需要的
评:不写OS就不必学了吧
P193
在计算机检测和控制领域也要用到位运算的知识
评:嗯、长见识了
P193
参加位运算的对象只能是整型或字符型的数据
评:从来搞不清谭先生的“整型”是什么个概念
P193
如果参加“&”运算的是负数(如-7&-5),则以补码形式表示为二进制
评:假定计算机采用补码是不正确的
P194
按位与有一些特殊的用途
(1)清零。如果想将一个单元清零,即使其全部二进制为0,只要找一个二进制数,其中各个位符合以下条件:原来的数中为1的位,新数中相应位为0.然后使二者进行&运算,即可达到清零目的。
例如,原有数为00101011,另找一个数,设它为10010100,它符合以上条件,即在原数为1的位置上,它的位值均为0,将两个数进行&运算:
00101011
(&) 10010100

  00000000

其道理是显然的。当然也可以不用10010100这个数,而用其他数(如01000100)也可以,只要符合上述条件即可。
评:这样实际上是无法达到“将一个单元清零”的目的的(且不说一个单元这个概念在这里是模糊不清的)
因为这只是得到了一个某种类型的0值而已
必须还要经过赋值才能达到“将一个单元清零”的目的
既然如此,还不如直接赋值
即使是希望通过&得到这个0值
也完全没必要去寻找什么“原来的数中为1的位,新数中相应位为0”这样的二进制数
用0就可以了
P194
(2)取一个数中某些指定为……
评:基本上把&运算解释成了一种算术运算而不是一种C语言的运算,因为是完全脱离数据类型来讲的,这个问题在这一章非常明显
P194
将八进制数60与八进制数17进行按位与运算
评:问题的提法就不正确
根本不存在八进制数按位与运算

C语言中没有任何一种运算的运算对象是八进制数
P195
若参见位运算的两个二进制位异号,则得到1(真),若同号,则结果为0(假)
评:位运算哪来的什么真假?
P195
012^00=012
评:00
P197
~运算符的优先级别比算术运算符、关系运算符、逻辑运算符的其他运算符都高
评:谭一定是忘记他把 !也说成是逻辑运算符了
P197
“<<”用来将一个数的各二进制位全部左移若干位
评:将“一个数”是误导

P197
假设以一个字节(8位)存一个整数,若a为无符号整型变量,则a=64时,左移一位时溢出的是0,而左移2位时,溢出的最高位中包含1.
由表12.2可以看出,若a的值为64,在左移1位后相当于乘2,左移2位后,值等于0。
评:不考虑措辞的不严谨
仅就一个字节(8位)的无符号整数类型变量a而言(只能是unsigned char 或char类型)
上面所描述的情形在任何编译器中都不可能发生

P198
例如:a是short型变量,用两个字节存放数,若a值为八进制数113755,即最高位为1,对它进行右移两位的运算:a>>1。请看结果
a: 1001011111101101
a>>1: 01001011111101101 (算术右移时)
a>>1: 11001011111101101 (逻辑右移时)
评:右移两位的运算:a>>1

a>>1的结果与具体实现有关
未必是列出的那两种样子

P198
如果两个数据长度不同(例如short和int型)进行位运算时(如a&b,而a为short,b为int型),系统会将二者按右端对齐。如果a为正数,则左侧16位补满0;若a为负数,左端应补满1;如果a为无符号型,则左侧填满0.
评:这个绝对是大错特错,胡说八道
所谓“右端对齐”绝对是捏造出来的
P198
例12.1 从一个整数a中把右端开始的4~7位取出来
评:这个问题本身就成问题
“整数a” ,“右端” ,都是非计算机领域的很不规范的说法
P199
(a>>4)&(0<<4)
评:所谓“取出”,也没有要求>>4
程序运行结果甚至输出了这4位的值,更是莫名其妙的做法
P199
例12.2 循环移位。要求将a进行右循环移位,见图12.4。图12.4表示将a右循环移位n位,即将a中原来左面(16-n)位右移n位,原来右端n位移到最左面n位。今假设用两个字节存放一个短整数(short int型)。
评:题目出的就稀里糊涂
a是什么根本没有交代
16-n 中的16是哪里来的?
“今假设用两个字节存放一个短整数(short int型)”更是莫名其妙
P200
程序如下:
#include <stdlib.h>
int main()
{unsigned a,b,c;
int n;
printf(“please enter a & n”\n);
scanf(“a=%o,n=%d”,&a,&n);
b=a<<(16-n);
c=a>>n;
c=c|b;
printf(“a:\nc”,a,c);
return 0;
}
评:如果把这叫做程序,那垃圾应该叫什么?
代码写得令人作呕不说
(unsigned a,b,c;
scanf(“a=%o,n=%d”,&a,&n);
b=a<<(16-n);

甚至错得连编译都无法通过
(printf(“a:\nc”,a,c);
printf(“please enter a & n”\n);

可书中竟然给出了运行结果

而且这里面压根就不干题目中所提到的“短整数(short int型)”什么事
P201
data=data&~(15<<4)|(n&15)<<4
评:目的是将n(=12)写到data的第4~7位
其中的 &15 毫无必要
P201
struct Packed_data
{unsigned a:2;
unsigned b:2;
unsigned c:2;
unsigned d:2;
short i;
}data;
……其中的a,b,c,d段分别占2位、6位、4位、4位,i为short型,以上共占4个字节。
评:“占4个字节”的说法是武断的
P202
请注意位段允许的最大值的范围。如果写成
data.a=8;
就错了。因为data.a只占两位,最大值为3。在此情况下,系统会自动取赋予它的数的低位。
评:是错了
但“在此情况下,系统会自动取赋予它的数的低位。”也错了
如果自动取低位,data.a=8;也就不成为错误了
P202
位段成员的类型可以指定为unsigned int 或int型。
评:错
对于C90来说还可以指定为signed int类型
对于C99来说还可以指定为_Bool等类型
P202
“宽度”……必须小于或等于指令类型的位长
评:“指令类型的位长”?
莫名其妙的说法
不知所云
P202
对位段组(……,至少占一个存储单元(即一个机器字,4个字节),即使实际长度只占一个字节,但也得分配4个字节
评:一连串错误:
至少占一个存储单元
(即一个机器字,4个字节)
即使实际长度只占一个字节,但也得分配4个字节
P203
一个位段必须存储在同一存储单元中,不能跨越两个单元
评:错
P203
位段的长度不能大于存储单元的长度
评:不存在这种问题
P203
位段中的数可以用整型格式输出,例如
printf(“%d,%d,%d”,data.a,data.b,data.c);
当然,也可以用%u,%o,%x
评:这是有条件的
不是所有的“位段中的数”(应该是位段成员)都可以随意用这几种转换格式
P203
位段可以在数值表达式中引用,它会被系统自动地转换成整型数
评:到底转变成什么类型?
这里的说法是模棱两可的
总结:12.3 位段部分几乎到处是错,尤其是后面说明部分,一共7条,全都是错的

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
第4章选择结构程序设计85 4.1选择结构和条件判断85 4.2用if语句实现选择结构87 4.2.1用if语句处理选择结构举例87 4.2.2if语句的一般形式 89 4.3关系运算符和关系表达式91 4.3.1关系运算符及其优先次序91 4.3.2关系表达式92 4.4逻辑运算符和逻辑表达式92 4.4.1逻辑运算符及其优先次序93 4.4.2逻辑表达式94 4.4.3逻辑型变量96 4.5条件运算符和条件表达式97 4.6选择结构的嵌套100 4.7用switch语句实现多分支选择结构102 4.8选择结构程序综合举例106 习题112 第5章循环结构程序设计115 5.1为什么需要循环控制115 5.2用while语句实现循环116 5.3用do…while语句实现循环118 5.4用for 语句实现循环121 5.5循环的嵌套125 5.6几种循环的比较126 5.7改变循环执行的状态126 5.7.1用break语句提前终止循环127 5.7.2用continue语句提前结束本次循环128 5.7.3break语句和continue语句的区别129 5.8循环程序举例132 习题141 第6章利用数组处理批量数据143 6.1怎样定义和引用一维数组143 6.1.1怎样定义一维数组144 6.1.2怎样引用一维数组元素145 6.1.3一维数组的初始化146 6.1.4一维数组程序举例147 6.2怎样定义和引用二维数组149 6.2.1怎样定义二维数组150 6.2.2怎样引用二维数组的元素151 6.2.3二维数组的初始化152 6.2.4二维数组程序举例153 6.3字符数组155 6.3.1怎样定义字符数组155 6.3.2字符数组的初始化156 6.3.3怎样引用字符数组中的元素156 6.3.4字符串和字符串结束标志157 6.3.5字符数组的输入输出160 6.3.6使用字符串处理函数162 6.3.7字符数组应用举例166 习题169 第7章用函数实现模块化程序设计171 7.1为什么要用函数171 7.2怎样定义函数173 7.2.1为什么要定义函数173 7.2.2定义函数的方法174 7.3调用函数175 7.3.1函数调用的形式175 7.3.2函数调用时的数据传递176 7.3.3函数调用的过程178 7.3.4函数的返回值179 7.4对被调用函数的声明和函数原型181 7.5函数的嵌套调用183 7.6函数的递归调用185 7.7数组作为函数参数193 7.7.1数组元素作函数实参193 7.7.2数组名作函数参数195 7.7.3多维数组名作函数参数198 7.8局部变量和全局变量200 7.8.1局部变量200 7.8.2全局变量201 7.9变量的存储方式和生存期205 7.9.1动态存储方式与静态存储方式205 7.9.2局部变量的存储类别206 7.9.3全局变量的存储类别209 7.9.4存储类别小结213 7.10关于变量的声明和定义215 7.11内部函数和外部函数216 7.11.1内部函数216 7.11.2外部函数216 习题219 第8章善于利用指针221 8.1指针是什么221 8.2指针变量223 8.2.1使用指针变量的例子223 8.2.2怎样定义指针变量224 8.2.3怎样引用指针变量225 8.2.4指针变量作为函数参数227 8.3通过指针引用数组232 8.3.1数组元素的指针232 8.3.2在引用数组元素时指针的运算233 8.3.3通过指针引用数组元素234 8.3.4用数组名作函数参数239 8.3.5通过指针引用多维数组247 8.4通过指针引用字符串257 8.4.1字符串的引用方式 257 8.4.2字符指针作函数参数261 8.4.3使用字符指针变量和字符数组的比较265 8.5指向函数的指针268 8.5.1什么是函数指针268 8.5.2用函数指针变量调用函数268 8.5.3怎样定义和使用指向函数的指针变量270 8.5.4用指向函数的指针作函数参数272 8.6返回指针值的函数276 8.7指针数组和多重指针279 8.7.1什么是指针数组 279 8.7.2指向指针数据的指针282 8.7.3指针数组作main函数的形参284 8.8动态内存分配与指向它的指针变量287 8.8.1什么是内存的动态分配287 8.8.2怎样建立内存的动态分配287 8.8.3void指针类型 289 8.9有关指针的小结290 习题293 第9章用户自己建立数据类型295 9.1定义和使用结构体变量295 9.1.1自己建立结构体类型295 9.1.2定义结构体类型变量 297 9.1.3结构体变量的初始化和引用299 9.2使用结构体数组302 9.2.1定义结构体数组302 9.2.2结构体数组的应用举例304 9.3结构体指针305 9.3.1指向结构体变量的指针305 9.3.2指向结构体数组的指针306 9.3.3用结构体变量和结构体变量的指针作函数参数308 9.4用指针处理链表311 9.4.1什么是链表 311 9.4.2建立简单的静态链表312 9.4.3建立动态链表313 9.4.4输出链表317 9.5共用体类型319 9.5.1什么是共用体类型319 9.5.2引用共用体变量的方式320 9.5.3共用体类型数据的特点321 9.6使用枚举类型325 9.7用typedef声明新类型名328 习题332 第10章对文件的输入输出333 10.1C文件的有关基本知识333 10.1.1什么是文件333 10.1.2文件名334 10.1.3文件的分类334 10.1.4文件缓冲区335 10.1.5文件类型指针335 10.2打开与关闭文件337 10.2.1用fopen函数打开数据文件337 10.2.2用fclose函数关闭数据文件339 10.3顺序读写数据文件340 10.3.1怎样向文件读写字符340 10.3.2怎样向文件读写一个字符串343 10.3.3用格式化的方式读写文件346 10.3.4用二进制方式向文件读写一组数据347 10.4随机读写数据文件351 10.4.1文件位置标记及其定位351 10.4.2随机读写 354 10.5文件读写的出错检测355 习题356 第11章常见错误分析374 附录390附录A在Visual C++ 6.0环境下运行C程序的方法390 附录CC语言中的关键字398 附录D运算符和结合性398 附录EC语言常用语法提要400 附录FC库函数404 参考文献410
由谭浩强教授著、清华大学出社出的《C程序设计》是一本公认的学习C语言程序设计的经典教材。根据C语言的发展和计算机教学的需要,作者在《C程序设计(第三)》的基础上进行了修订。本书按照C语言的新标准C 99进行介绍,所有程序都符合C 99的规定,使编写程序更加规范;对C语言和程序设计的基本概念和要点讲解透彻,全面而深入;按照作者提出的“提出问题—解决问题—归纳分析”三部曲进行教学、组织教材;本书的每个例题都按以下几个步骤展开:提出任务—解题思路—编写程序—运行程序程序分析—有关说明。符合读者认知规律,容易入门与提高。   本书内容先进,体系合理,概念清晰,讲解详尽,降低台阶,分散难点,例题丰富,深入浅出,文字流畅,通俗易懂,是初学者学习C程序设计的理想教材,可作为高等学校各专业的正式教材,也是一本自学的好教材。本书还配有辅助教材《C程序设计(第四)学习辅导》。 《C程序设计》累计发行逾1100万册,不仅创同类书的全国最高纪录,在世界上也是罕见的。 《C程序设计(第四)》保持了前三的写作风格和概念清晰、通俗易懂的特点,并在以下几个方面作了修改: (1) 按照C99标准进行介绍,以适应C语言的发展,使编写程序更加规范 (2)采用编译系统 (3)加强算法,以程序设计为中心把算法与语言工具紧密结合 (4)通俗易懂,容易学习 (5)根据需要重新组织教材内容

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为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、付费专栏及课程。

余额充值