我于2002年、2003年、2004年在北京民办大学教授C++,使用的教材是经济科学出版社2000年出版的全国高自考指定教材,作者是中国科技大学的教授刘振安、ISBN:7-5058-1997-6/G.422,书中错误多如牛毛,我每次在课堂上都要拿出时间来领着学生改书,急得我满头大汗,这是我一生用过的最烂的一本书,我如今退休在家,每思及此仍然耿耿于怀。我曾经到经济科学出版社兴师问罪,当时的主编是一个姓莫的老年妇女,态度极其客气,承认他们不懂计算机,就是为了经济效益。这本书许多的地方都是抄袭美国TedFaison 所著《Borland C++3.0编程指南》(清华大学出版社1993年出版,蒋维杜等译),甚至连印刷错误都照抄。虽时过境迁,我仍想把我当年为此书所写的勘误表公布于众,让大家看一看,粗制滥造和抄袭能在我国能达到怎样的程度,希望后来人不要再做这样的事。
明明是一本千疮百孔的烂书,作者居然还列举出一个感谢名单,如下:
“原安徽大学副校长程慧霞教授、中国计算机函授学院秘书长胡学联副教授级中国科技大学苏仕华副教授审阅了全书并提出许多宝贵意见。”
不知程慧霞教授、胡学联副教授级、苏仕华是否真的审阅过此书,还是他们的水平就这样。
有认识刘振安教授的朋友,请将此文转告与他。
面向对象程序设计全国自学考试教科书勘误表
【1】 本书的第一个例子就是错误的。原题为:
#include <stdio.h>
main(){
char*name; //相当于刘振安教授造了一门炮,指向谁没准
printf("%s","Pleaseinput your name:");
scanf("%s",name);//开炮!打死谁没准,活该。
printf("Hello,%s!\n",name);
}
现在让我们分析这个程序。他的语法没有错(尽管编译系统会提出警告),但是在运行时就有可能出错甚至是系统崩溃。它的问题出在指针name上。语句char*name;定义了一个指针变量name且没有初始化它。name是一个auto型变量,它在栈区里占两个字节的存储(以turboC为例)。虽然我们没为它赋值,但是它不一定是零。它是一个随机的不确定的值,我们可以把它看成一个垃圾值。又由于name是一个指针变量,所以这个垃圾值又是一个地址值。让我们看看后面的语句scanf(“%s”,name);这里的name是一个指针类型的表达式,现在其值就是垃圾值。系统函数scanf将把这个垃圾值作为一个起始地址,同时把用户在键盘上输入的一串字符连续地放到内存。一般地,系统每次运行这个程序时,这个垃圾值是不同的。第一种情况,我们假定这个值是零。此程序立即出错而退出运行;第二种情况,我们假定这个值不是零并且恰好以这个垃圾值为地址的内存单元及其后面足够多的单元都是系统当时没有用到的空闲单元。则此程序能够正确的运行;第三种情况,我们假定这个值不是零并且以这个垃圾值为地址的内存单元是系统当前占用的单元。则此程序不能正确的运行。如果我们使用的操作系统是DOS则有可能引起系统崩溃而无法运行。即使windows这样的具有自我保护的操作系统,笔者也曾遇到这种情况,在运行此程序出错后屏幕变得又绿又亮。这是系统受到破坏的结果。
【2】 第6页第14行“C++是自带输入和输出的,......”一句中的‘自带’二字值得商榷。
我猜测作者指的是流的抽取符“>>”和插入符“<<”。但是它们都是在流类库中重载的运算符而不是语句。这个问题似乎很难说清楚。让我们看看basic语言(我指的是古老的basic语言,新的我不知道)和C语言在这方面的区别吧。Basic有输入语句input和输出语句print.而C语言没有输入输出语句,它是靠标准函数实现的。我认为有没有输入语句和输出语句是判断是否自带输入和输出的标准。由此,我认为C语言和c++都不‘自带’。
【3】 第6页倒数第6行“由于const和内联函数的引入,无论带参数的宏还是不带参数的宏,都失去了存在的必要性......”的结论值得商榷。让我们看看visual C++中的一个带参数的宏吧:
#define BEGIN_MESSAGE_MAP(theClass,baseClass)AFX_MSGMAP* \ theClass::GetMessageMap()const\
{return &thClass::messageMap;}\
AFX_MSGMAP theClass::messageMap=\
......请原谅我不再抄写下去,有兴趣请参阅侯俊杰著《深入浅出MFC》133页。
如果没有这个宏我们还能使用消息映射吗?这是因为带参数的宏能起到类似扩展程序原码的作用,而const和内联函数能吗?
【4】 第7页第16行“对基本数据类型的变量,一旦加上const 修饰符,编译器就将其视为一个常量,不再为它分配内存......”值得商榷。我认为此话不对。
const 变量是一定要占有内存的。我们甚至能够取它的地址。让我们来看本书此页第23行的程序片段:
const int *p;
const intvar=5555;
p=&var;
如果第16行是正确的则第25行就是错的,反之亦然。当然第25行是正确的了。
【5】 第7页倒数第12行的程序片段
char const*p[4]={"a" ,"b","c","d"};
p[2]=m;
请问这个m是什么?根本没定义,谁能猜出来?这样不负责任的书让人去自学简直是草菅人命。
【6】 第11页第5行“为引用提供的初始值必须是......如果是一个常量......,则编译器首先建立一个临时变量,然后......对引用的操作就是对该临时变量的操作”值得商榷。
我认为引用一个常量的问题本身就是不可思议的。编译系统虽然不报错,但是要提出警告,同时采取引用一个临时变量的策略。本书的讲述势必让读者误认为引用一个常量是正确的。
【7】 第11页第8行的程序片段也有问题。
const int num=500;
int &ref=num;
//turbo c++编译系统将对此句提出警告。变成:const int &ref=num;即可。
//而VC++干脆报错为:
//error C2440: 'initializing' :cannot convert from 'const int' to 'int &'
//A reference that is not to 'const' cannotbe bound to a non-lvalue
cout <<ref;
ref= =ref+50; //双等号是什么意思?本书的“编者的话”里提到,有众多的人审阅
//过此书,难道都视而不见乎?其实根本没视。
【8】 第11页第17行“由于引用不是....,所以,不能说明引用的引用......”值得商榷。
我有一程序编译连接运行皆正确。此例可以作为一个反证,此例如下:
#include <iostream.h>
void main( )
{
intnum =50;
int&ref = num;
int&ref2 = ref; //说明了ref2是引用的引用
int*p = &ref; //point to refrance
int* &rp =p;
*rp=60;
}
让我们来看看运算符“<<”重载(可参阅本书第136页最后部分)
ostream & operator <<(ostream& stream, CLASS &a) //最后一个&是我加上的
{ :
returnstream;
}
这里的形参stream等于定义为是实参的引用,而函数返值实际上等于定义为stream的引用,即引用的引用。如本书所言,此函数就有错误。但事实上,它是正确的。
【9】 第12页倒数第6行的范例:“#include<\user\prog.h>”让人百思不得其解。尖括号的目的是为了让编译系统快速找到系统头文件而设立的。它使得省略了去当前目录查找的冤枉路,直接到编译系统内置的include目录里去找。它只是用于系统头文件。且尖括号里不应该有路径。而\user\prog.h的英语含义更让人理解成尖括号里放的是用户的头文件。此处应该为:#include"\user\prog.h"
【10】 第20页倒数第7行的范例:“一个类变量和函数的标识符必须保证不与其它类的标识符冲突,类是具有唯一标识符的实体”这段话容易让人误解为两个不同的类。应改为“一个类成员变量和成员函数的标识符必须保证不与本类的其它标识符冲突”。至于“类是具有唯一标识符的实体”一句话有错。为不是实体,对象才是实体。
【11】 第21页第9行的“类体”不对,应为“函数体”。
【12】 第22页第6行以下的表中所有的“A”都应该是“A1”;所有的“B”都应该是“A2”。
【13】 第22页中间“我们仍要将对象理解为是由数据和代码组成的”最好把“代码”改为函数或方法。
【14】 第23页例题2.6中的类location 没有定义,它的定义在20页和21页。本例题不可能编译通过。改正的方法有两种。第一种方法,将单独做成头文件。然后在再主文件所在的文件用include预处理命令将类location的定义包含进来;第二种方法,将类location的定义直接写到主函数之前。
【15】 第25页倒数第4行有错。变量i 没有说明过,改为for(int i=0;i<10;i++)即可。
【16】 第28页第17行voidvalueX(int){X=val;}有错,应为void valueX(int val){X=val;}
【17】 第28页第19行voidvalueY(int){X=val;}有错,应为void valueY(int val){Y=val;}
【18】 第31页第5行的有错,应为“floatfvalue;} x;”,其中的x是我加上的,因为定义一个无名联合如果不同时定义它的起码一个变量的话,此无名联合将没有任何用处了。
【19】 第31页第7行的 “}”有错,应为“};”
【20】 第31页第11行的 “但无名联合不能有成员函数,......”有错。请看下例,它可是一个编译连接运行都正确的程序。但是它有成员函数:
#include <iostream.h>
struct myStruct
{
union
{public:
doubled;
unsignedchar c[sizeof(double)];
voidinit(){d=3.2;show_bits();}
voidshow_bits(){cout<<"Ok"<<d<<endl;}
}unionMember;
};
int main()
{ myStructob;
ob.unionMember.init();
return0;
}
【21】 第33页的“空类”一节全部是一字不错地从Ted Faison 所著《Borland C++3.0编程指南》(清华大学出版社1993年出版,蒋维杜等译)第48页抄袭而来。
【22】 第33页第19行的 “......也不能取成员函数的地址,......”,应为“......也不能取私有成员函数的地址......”
【23】 第34页-35页的“嵌套类”一节全部是一字不错地从Ted Faison 所著《Borland C++3.0编程指南》(清华大学出版社1993年出版,蒋维杜等译)第48页抄袭而来。
【24】 第35页的“类的实例化”一节的前4行是一字不错地从Ted Faison 所著《Borland C++3.0编程指南》(清华大学出版社1993年出版,蒋维杜等译)第50页抄袭而来。
【25】 第36页第7行的“D.对象可以用作另一对象的成员”有二义性。上述的两个对象是否是同一类的?这将影响到此选项的正确与否。
【26】 第39页中间“[例3.1]使用初始化列表初始化类”应为“[例3.1]使用初始化列表初始化对象”
【27】 第39页中间“[例3.1]中“float num[2]”应为“float num[2];”
【28】 第46页倒数第2行“当使用delete释放动态对象数组时,必须告诉......几个......”有错。恰好相反,应该是“当使用delete释放动态对象数组时,不必须告诉……几个……”。下一页第一行的“delete[] ptr;”才是正确的,只是作者自相矛盾了。
【29】 第48页第20行“//析构第一次建立的对象Try”错。应为:“//析构隐藏对象”
【30】 第48页第21行“//输出建立的隐藏对象之数据”错。应为:“//输出对象try的数据”
【31】 第48页第23行“//析构隐藏对象Try(num=5)”错。隐藏对象的名字不可能是Try。
【32】 第48页第25行“//析构强制类型转换所建立的对象Try(num=10)”错。应为析构第一次建立的对象Try。
【33】 第57页第14行“Constructor”错, 应为“Destructor”。
【34】 第57页第15行“Constructor”错, 应为“Destructor”。
【35】 第57页第16行“Constructor”错, 应为“Destructor”。
【36】 第57页第26行“default”错, 应为“Destructor”。
【37】 第57页第27行“default”错, 应为“Destructor”。
【38】 第57页第28行“default”错, 应为“Destructor”。
【39】 第58页作业多选题的第1题的选项D的“在一个类中可以说明具有类类型的数据成员”。有二义性。必须指出这个对象成员的类型是否与句中所述的第一个类的类型相同。
【40】 第58页作业改错题的第1题出现了“protected”。而本书到此为止还没有讲到protected。
【41】 第59页的作业分析题的第2题出现了转换函数“operatorint( )”。而本书到119页才讲到。
【42】 第59页的作业分析题的第2题出现了继承。而本书到62页才讲到。作者纯粹拿读者(而且是自学者)寻开心。
【43】 第63页倒数的5行知道本页末尾是一字不错地从Ted Faison 所著《Borland C++3.0编程指南》(清华大学出版社1993年出版,蒋维杜等译)第63页抄袭而来。我们的作者仅加上了4个字“由此可见”。
【44】 第67页第8行的“类的派生可以分成......两种”错,应为三种。
【45】 第73页倒数第7行至74页中间的例题丢了主函数,应加上:
void main()
{
derived d(5,8,9);
}
【46】 第76页倒数第10行之上,应加上“public:”,将gunc( )和hunc( )变成公有成员函数,同样的,第76页的倒数第2行之前也要加“public”。否则下一页的第8,23,24行将有错。
【47】 第77页倒数第3行有错。既然函数C::gunc( )的返值被定义成void 型,又怎能将它的返值赋给一个变量呢?荒唐!应将“int x=”去掉。
【48】 同样的,第77页倒数第2行有错。应将“int y=”去掉。
【49】 81页第2行“int b;”之前必须加上“public:”,否则本页倒数第6行将引起错误。
【50】 86页编程题第4题只有学习了下一章才能做出来。
【51】 第92页倒数第2行有错。“......输出:Int A”应为“...输出:Int C”.
【52】 第96页全页是我们的作者一字不错地从Ted Faison 所著《Borland C++3.0编程指南》(清华大学出版社1993年出版,蒋维杜等译)第164页抄袭而来。
【53】 第97页例题5.6是我们的作者一字不错地从Ted Faison 所著《BorlandC++3.0编程指南》(清华大学出版社1993年出版,蒋维杜等译)第167页抄袭而来。
【54】 第98页第12行有错。“show函数”应为“printOn( )”。
【55】 第99页的例题有严重缺陷。在主函数的最后应加一条语句for(int i=0;i<4;i++) delete s[i];
【56】 第100页倒数第16行“B *b” 应为“B *pb”。
【57】 第100页倒数第17行之前应加上一行:C c;
【58】 第100页倒数第2行至101页第1行是我们的作者一字不错地从Ted Faison 所著《Borland C++3.0编程指南》(清华大学出版社1993年出版,蒋维杜等译)第167页抄袭而来。
【59】 第101页倒数第15行“多态与简单的隐蔽很...”,最好改为“...屏蔽...”
【60】 第102页第14行的“对象A”的说法是错误的。标识符A和B都是类名,怎么变成了对象了呢?
【61】 第102页第14行的“::”应改为“,”。
【62】 第103页倒数第12行的“DestructorA”应改为“DestructorB”。
【63】 104页单选题第4小题有问题。四个选项都不能选。请问调用虚函数与调用非虚函数有区别吗?
【64】 105页第3行“4.一个抽象类不能说明为”一句,应该把其中的“为”字去掉。这是一个汉语语法问题,否则误解是在所难免的。
【65】 105页编程题的第1题给出的最后一条语句有毛病。因为w和z都是对象,所以 w>z 是非系统预定义类型的关系运算。这个 ‘>’ 运算符必须重载。否则,即使自学者把题目要求写出的主函数写得完全正确,还是编译通不过去。
【66】 第105页编程题第2小题所要用到的转换函数在后面117页才将到。现在让自学者去做,简直是害人。
【67】 第105页编程题第3小题所要用到的运算符重载在后面第7章才讲到。现在让自学者去做,简直是害人。
【68】 第105页编程题第4小题中所有的“A.”都应该为“A”。 因为它不符合标识符规则。
【69】 第105页倒数第6行“class B2::private A.{”应为:“class B2::public A{”。否则是达不到题目要求的用指向基类A的指针指向该类的对象的,原因是他们不存在is~a关系。
【70】 第105页第2行的“......inside B.A::iis”应改为“......inside B2.A::iis”。
【71】 第105页编程题第4小题太烂。我有一个改编的版本,仅供参考。
#include<iostream.h>
class A{
public: int i;
virtual voidprinti(){cout<<i<<"inside A"<<endl;}
};
class B1:publicA{
public:
voidprinti(){cout<<i<<"inside B1"<<endl;};
};
class B2:publicA{
public:
B2(){A::i=4;}
int i;
voidprinti(){cout<<i<<"inside B2.A::i is"<<A::i<<endl;};
};
void main()
{ A *p;
A a; B1 b1; B2 b2;
p=&a;
p->printi();
p=&b1;
p->printi();
p=&b2;
p->printi();
}
【72】 第107页倒数第13行和倒数第14行都是从TedFaison 所著《Borland C++3.0编程指南》(清华大学出版社1993年出版,蒋维杜等译)第59页倒数第6行和第7行页抄袭而来。请注意本书倒数第13行的“可以不指向某个具体的对象,只与类名连用”的“指向”二字,我认为是原书的翻译有问题。因为这里没有指针的问题。这里仅讲述静态成员的一种引用形式:类名::静态成员名。很显然,这里的“指向”应翻译成“指出”。我认为我们的作者在抄袭这部分内容时一定是很珍惜他的时间而不肯拿出少许时间来思考。我认为我们的作者根本没把读者放在心上。
【73】 第111页,前两个自然段是刘振安从Ted Faison 中译本第72页抄袭而来,但例子不是。
【74】 第111页例题6.2,倒数第15行
"fried float distance (point &a, point &b); 应该为:
"friedfloat distance (Location &a, Location &b);
【75】 第112页,第3行末尾应加上“和保护成员”
【76】 第113页倒数第13行,void C::fun(A *p) 应改为:void C::fun(A *pt)
【77】 114页的大标题“const 对象和volatile 对象” 应改为“const 成员函数和volatile 成员函数”
【78】 114页是作者根据Ted Faison书第60页“§2.4.3cons成员函数”改造而成,作者刘振安加上的几乎全是错的,刘振安自己都没理解。
【79】 114页第5行 "1.返回对象",应改成"cons成员函数",Ted faison书的原来小标题就是"const 成员函数"。刘振安改得不对。
【80】 114页第7行 "如果定义一个const 对象,则只能访问该对象的const 成员函数"。
这一句话在Ted Faison原书中没有,此句是刘振安加上的。这句话加在此处是不正确的,其原因是刘振安把const成员函数和const this成员函数混淆了,如果把这一段话改成"如果
定义一个const 对象,则只能访问该对象的cons this 成员函数"。则是正确的。
【81】 114页例题6.3 此题是作者根据TedFaison中译本第60页的例子简单改造成的。
改造后的例题6.3千疮百孔:
1.例题中 const int f5( ) const { return5;}一句中的第二个const 还没讲就写出来了。
2.此例的主函数中的语句:int y=obj( ); 显然应该是int y=s.obj( );
3.此例的主函数中的语句:int j=s.f5( ); 我猜测作者的本意是int j=d.f5( );
【82】 114页例题6.4来自TedFaison 中译本第61页。刘教授把原题中的 “value” 改成 “val”;把 “f4()”改成 “f8”。
【83】 114页例题6.4中的volatile 限定符是做什么用的,我们的作者只字未提。作者大概是让自学者去猜吧?我怀疑作者自己都不明白。
【84】 115页第7行的黑体字 “2.使用带有this指针的成员函数”, Ted Faison中译本为“使用带有const this 指针的成员函数” 。刘教授大概以为TedFaison写错了。
【85】 115页例题6.5中的主函数中的第一条语句constFun s(98);的前面应该加上const 修饰符,只有这样才能体现出const this 的功能。
【86】 115页最后三行的例子是从Ted Faison 中译本中抄袭而来。原书中Type 和Bag之间有一个空格。这是原书的印刷错误,应该写成TypeBag 才对。刘教授真是抄得妙,连印刷错误也规规矩矩的照搬。读书人窃书不算偷。
【87】 116页第4行 “上面的讨论也适合 volatile 成员函数”,应为“上面的讨论也适合 volatile this成员函数”。
【88】 116页的例题6.6和例题6.7 是从Ted Faison书的第63-64页抄袭而来,仅仅是把类名和某几个变量名有所改变。
【89】 116页倒数第10行的“使用volatile 成员函数”应为“使用volatile this 成员函数”。
【90】 117页例题6.8是从TedFaison 书的第64页抄袭而来,唯一的改写是把原书的函数int GetValue()…..的返值int 改为void ,这一下可糟了,这个函数里有returnvalue; 语句。请问刘教授,void 型的函数有返值乎?请同学们把这个void 改成 int 。
【91】 从118页最后一行开始的这个例子,上机编译后报错:Ambiguiey between ‘ostream::operator<<(constchar )’ and ‘ostream::operator<<(void )’ 。其意思是说出现了二意性。我们很容易推断出最后一条语句有问题。编译器认为,即可以把 *p 的结果转换成 char * ,又可以把 *p 的结果转换成 void * 。我请同学们把最后一条语句改为:cout<<(char *)*p<<endl; 即可。
【92】 120页倒数第9行:x . *pai = 1; 应改为:x .*p = 1
【93】 121页第11行的语句“pafn=A::fa; ”,请同学们注意,此行并非有错。如果你使用的编译器是turbo C++ 或 Borland C++,那么你应该将此句改为 :pafn=&A::fa; ,否则是通不过编译的;如果你使用的编译器是 Visual C++,则可以不改。第19行和倒数6行与上述情况相似。122页第20行与上述情况相似,如果你使用的编译器是turbo C++ 或 Borland C++,那么你应该将此句改为 :display (&d.&base::print);
【94】 121页第17行有错,应改为:type(X::*pointer)(list);
【95】 122页倒数第6行的 “ A: num ” 应改为 “ A::num ”
【96】 123页第4行到第11行是刘教授从Ted Faison 中译本的77页中照搬抄袭而来,只是刘教授给加上了序号而已。
【97】 125行“第15行“在函数体之前加____”应改为“在函数体之前且函数头之后加____”否则这题就有毛病。
【98】 127页编程题中的第2小题和第3小题的题目是让学生写出运算符重载函数,而这个内容在下一章才讲到,我觉得这是刘教授在拿自学者寻开心。
【99】 130页倒数第6行的关键字friend 不应该有。编译报错为:storage class ‘friend’ isnot arrowed here 。
【100】 134页第3行的 “num” 应改为 “number”
【101】 136页中间部分的“C++的流库预定义了4个流”,应改为“C++的流库预定义了4个流对象”。
【102】 137页倒数第15行的 “c.in” 应为 “ c.In”
【103】 137页最后一行有两个错误,少了一个‘>>’和‘&’,应为:ostream& operator >> (ostream& stream, CLASS &a),必须要有‘&’,否则达不到目的。
【104】 138页第9 行的 “iso” 应改为 “ios”
【105】 138页倒数第5 行的 “long flag( )” 应改为 “long flag(long)”,才能与后面的解释匹配。
【106】 138页倒数第3行的long setf(long, long)的解释完全不对,完全是作者的主观猜测。此函数的作用是将当前流对象的状态标志字的第一个参数所对应的那些标志位置为1,将第二个参数所对应的那些标志位置为0。我们发现在格式标志字里有一些标志在意义上某一时刻只能有一个为1,例如枚举值dec、oct、hex所对应的标志。我们通常就用这个带两个参数的setf函数实现设置进制位。例如语句:cout.setf (ios::hex,ios::dec|ios::oct|ios::hex); 它将第二个参数所对应的三个进制标志全置为0,同时又把第一个参数所对应的十六进制的标志置为1。为了使这个函数的使用更方便,C++还在ios类库里定义了一个static const long basefield; 来代替dec | oct | hex。
【107】 139页第3 行 “int width” 应改为 “int width( )”
【108】 139页第7行关于函数precision(int)的解释是错误的,打印精度的概念不是小数点后的位数而是有效数字的位数。
【109】 140页第19行 “showbase” 应改为 “setbase”
【110】 140页第22行 “end” 应改为 “endl”
【111】 141页图7.2中fstream 类有两个基类,其中一个是 iostream 类。因此,在图中的fstream 方框和 iostream 方框之间应该有一个向下的箭头。
【112】 142页中间偏下部分的“ios::nnoreplace” 应改为 “ios::noreplace”
【113】 148页倒数第12 行至倒数第8 行的内容关系到容器类。可是在整本书里都没有介绍容器类是什么东西。
【114】 151页中的倒数第5行“return (strcmp(c1,c2))?…”应改为“return (strcmp(c1,c2))>=0?…”或“return (strcmp(c1,c2))>0?…”
【115】 149页倒数第9行“显式函数模板”应为“显式模板函数”
【116】 152页倒数第8行“class Tany temp{” 应为“class TAny temp{”
【117】 176页倒数第14行应为“virtual void show( ) =0;”