字符串类中运算符重载出现的一个问题

最后更新于:2022-04-01 14:38:32

上机辅导。学生的一个程序莫名出问题。她是在做一个String类,主要是要实现字符串的连接。    程序是这样的,请读者将其拷到IDE,边看边调: ~~~ #include <iostream> #include <cassert> #include <cstring> using namespace std; class String { private: char* p; int len; public: String(); String(const char* s); String(const String& s); ~String();//问题出现在这儿 void show(); friend String operator+(const String& s1,const String& s2); friend String operator-(const String& s1,const String& s2); }; String::String() { len=0; p=NULL; } String::String(const char* s) { len=strlen(s); p=new char[len+1]; strcpy(p,s); } String::String(const String& s) { len=s.len; if(p!=NULL)delete []p; p=new char[len+1]; strcpy(p,s.p); } String operator+(const String& s1,const String& s2) { String total; total.len=s1.len+s2.len; total.p=new char[total.len+1]; strcpy(total.p,s1.p); strcat(total.p,s2.p); return total; } String operator-(const String& s1,const String& s2) { char* c1=new char[s1.len+1]; strcpy(c1,s1.p); int i=s1.len-1; while(s1.p[i]!=' '&&i>=0)--i; c1[i+1]='\0'; char* c2=new char[s2.len+1]; strcpy(c2,s2.p); i=0; while(i<s2.len&&c2[i]==' ')++i; int j=0; while(c2[i]!='\0'&&i<s2.len) { c2[j]=c2[i]; ++i; ++j; } c2[j]='\0'; String s; s.len=s1.len+s2.len; s.p=new char[s.len]; strcpy(s.p,c1); strcat(s.p,c2); delete c1; delete c2; return s; } void String::show() { cout<<p<<endl; } String::~String() { delete []p; } int main() { String str1("123"),str2("456"),str3; str3=str1+str2; str3.show(); str3=str1-str2; str3.show(); return 0; } ~~~   问题的表现是,`str3.show();`的输出是几个莫名的符号!    我让她单步跟踪一下,却发现无论断点设在哪里,总是要进到析构函数~String()中去!    恰其他同学的问题不少,我让她将这个程序给我发邮件,她不要在此纠缠。我清楚,这里有指针成员。祸水应该与此相关。    学习越来越深入,学生出问题的程序,常需要仔细阅读后才能给指导意见了。    午睡后,打开邮件。将程序拷入IDE,已经注意到了执行`str3=str1+str2;`时,需要有的赋值运算符=的重载。试图找一下单步时进入~String()时的场景,却未遂。    在我的IDE下,`str3=str1+str2;`的结果正常,`str3=str1-str2;`的结果却异常。野指针的表征,真伪混杂最头疼。    重载赋值运算=,在类声明中加: ~~~ String &operator=(const String& s1); ~~~   实现为: ~~~ String &String::operator=(const String &s1) { if(!this->p) delete []p; p=new char(s1.len+1); strcpy(p,s1.p); len=s1.len; return *this; } ~~~   先向学生交作业。进入~String()的问题是否还在?暂时放下,待回音。
';

一个冒号引发的“血案”

最后更新于:2022-04-01 14:38:30

备课中。    从下载的PPT中复制出例题,调试一下,结果:    ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_575640863ea8b.jpg)    何其多的错误,红红的。    不正在声明Base2吗?怎么会“error: ‘Base2’ has not been declared”    斗争!    ……    小样,就一个符号的问题,犯得着兴师动众,给出那么多提示?!红红的一片,血案发生?    编译器就是这么好,严格把关,各种提示。    亲爱的读者,看出来了,会心一笑吧。    编程就这样,给人带来愉悦。
';

C++返回值为对象时复制构造函数不执行怎么破

最后更新于:2022-04-01 14:38:27

先说点背景知识,调用复制构造函数的三种情况:   1.当用类一个对象去初始化另一个对象时。   2.如果函数形参是类对象。   3.如果函数返回值是类对象,函数执行完成返回调用时。   在辅导学生上机时,有同学第3点提出异议。有教材上的例题为证: ~~~ #include <iostream> using namespace std; class Point //Point 类的定义 { public: Point(int xx=0, int yy=0) { x = xx; //构造函数,内联 y = yy; } Point(const Point& p); //复制构造函数 void setX(int xx) { x=xx; } void setY(int yy) { y=yy; } int getX() const { return x; //常函数(第5章) } int getY() const { return y; //常函数(第5章) } private: int x, y; //私有数据 }; //成员函数的实现 Point::Point (const Point& p) { x = p.x; y = p.y; cout << "Calling the copy constructor " << endl; } //形参为Point类对象的函数 void fun1(Point p) { cout << p.getX() << endl; } //返回值为Point类对象的函数 Point fun2() { Point a(1, 2); return a; } //主程序 int main() { Point a(4, 5); //第一个对象A Point b = a; //情况一,用A初始化B。第一次调用复制构造函数 cout << b.getX() << endl; fun1(b); //情况二,对象B作为fun1的实参。第二次调用复制构造函数 b = fun2(); //情况三,函数的返回值是类对象,函数返回时调用复制构造函数 cout << b.getX() << endl; return 0; } ~~~   证据是,在CodeBlocks中,运行结果是:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564049cca61.jpg)   而不是期望的:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564049e2213.jpg)   显然,第3种情况下,复制构造函数没有被执行。   确定问题后,我知道道理是对的,看过的几本书,厚的、薄的,都是这么写的。会不会是编译器的差别?CodeBlocks用的是gcc。gcc开源,跟标准的变化跟得紧,莫不是第3种情况已经成了老黄历,而各种书来不及变?   我让她到VC++6.0中运行。一会儿她的反馈,在VC++6.0中复制构造函数执行了。   真相明白了。   这个问题需要有个交待。   回家后再翻各种书,无果。网络搜索,CSDN上有个贴子[《函数返回值是对象,是调用了拷贝构造函数?》](http://bbs.csdn.net/topics/390803716),其中大家给的结论,是gcc做了优化,返回值为对象时,不再产生临时对象,因而不再调用复制构造函数。   看来不是标准发生变化。   那如果一定想要让这个构造函数执行呢?只需让忽略gcc不要搞这个优化就行了。贴子中给了个线索,在新浪博客《[命名返回值优化](http://blog.sina.com.cn/s/blog_4ab8464c0100kybj.html)》。文章称通过搜索知道“这是一个称为命名返回值优化的问题,而且g++的这个编译优化竟然没有直接的关闭方法给出解决办法”。作者是用命令行工作的,他后来解决的办法,是在编译命令中加上“-fno-elide-constructors”参数,例g++ -fno-elide-constructors testReturn.cpp 。   我的学生还处在用IDE的阶段。本文的价值来了,如何在CodeBlocks下也忽略这个优化项呢?   在CodeBlocks中,通过菜单依次选:settings->Compiler...,在Global compiler settings部分,选择Other options,在文本框中写入“-fno-elide-constructors”,如图,然后就可以ok啦。   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564085f34e2.jpg)   然后,如同苦难的公主终于和王子过上了幸福的生活一样,期望的结果有了。   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564049e2213.jpg)   “-fno-elide-constructors”选项起了作用,有图为证。下面中加上这个参数后,编译完看到的提示信息:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_5756408627958.jpg)
';

数据结构实践——初始化顺序表怎么就内存溢出了?

最后更新于:2022-04-01 14:38:25

  有学生调程序,是要建顺序表。   他的程序是这样的: ~~~ #include <stdio.h> #include <malloc.h> #define MaxSize 50 //Maxsize将用于后面定义存储空间的大小 typedef int ElemType; //ElemType在不同场合可以根据问题的需要确定,在此取简单的int typedef struct { ElemType data[MaxSize]; //利用了前面MaxSize和ElemType的定义 int length; } SqList; //声明自定义函数 SqList InitList(SqList *L); //初始化顺序表 void ListInsert(SqList *L,int i,int b); //插入函数 void DispList(SqList *L); //输出函数 bool ListEmpty(SqList *L);//判定是否为空表ListEmpty(L) int main() { SqList *sq; InitList(sq); ListInsert(sq, 1, 5); ListInsert(sq, 2, 3); ListInsert(sq, 1, 4); DispList(sq); return 0; } //输出线性表DispList(L) void DispList(SqList *L) { int i; if (ListEmpty(L)) return; for (i=0; i<L->length; i++) printf("%d ",L->data[i]); printf("\n"); } //判定是否为空表ListEmpty(L) bool ListEmpty(SqList *L) { return(L->length==0); } //初始化顺序表InitList(*L) SqList InitList(SqList *L) { L=(SqList *)malloc(sizeof(SqList));//这里申请了结点空间 L->length=0; return *L; } void ListInsert(SqList *L,int i,int b) //插入函数 { int j,k; if(i<1) { printf("插入位置非法/n"); } for(j=L->length; j>i; j--) { L->data[j]=L->data[j-1]; } L->data[i]=b; L->length++; } ~~~   运行结果是这样的: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564049a297f.jpg "")   他找我帮忙。基本可以断定,内存使用不当,有溢出。   看一下编译提示的信息,有一个警告:   D:\CB\DS\main.cpp|21|warning: ‘sq’ is used uninitialized in this function [-Wuninitialized]|   说在21行,sq未经初始化就使用了。通俗的说法,野指针。   围绕着sq找。在main()函数中有: ~~~ SqList *sq; InitList(sq); ~~~   这里在调用InitList时,实际参数sq就是野指针。但这还不是出问题的关键,看InitList函数的定义是: ~~~ //初始化顺序表InitList(*L) SqList InitList(SqList *L) { L=(SqList *)malloc(sizeof(SqList));//这里申请了结点空间 L->length=0; return *L; } ~~~   调用时,L得到的是野指针,但在函数里为其分配空间了。但调用完,这个地址并未返回到main函数中。调用完InitList,sq仍然还是野指针。这是关键!   沿这个思路,希望能将分配的空间地址能返回给main函数。return *L就不合适了,return L是返回地址。于是,函数定义改为: ~~~ //初始化顺序表InitList(*L) SqList *InitList(SqList *L) { L=(SqList *)malloc(sizeof(SqList));//这里申请了结点空间 L->length=0; return L; } ~~~   既然参数SqList *L调用时给的是个野指针,不要也罢。于是改选成无参函数: ~~~ //初始化顺序表InitList(*L) SqList *InitList() { SqList *L=(SqList *)malloc(sizeof(SqList));//这里申请了结点空间 L->length=0; return L; } ~~~   在调用时,main函数定义为: ~~~ int main() { SqList *sq; sq = InitList(); ListInsert(sq, 1, 5); ListInsert(sq, 2, 3); ListInsert(sq, 1, 4); DispList(sq); return 0; } ~~~   为保证程序能够正确编译,函数声明等处的语法问题不一一列出。解决了这一环节的问题,程序能够运行了,但结果:   ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564049b75fd.jpg "")   断定问题出在ListInsert函数中。看调用,插入的位置用的是逻辑序(从1开始记数),但函数定义中,直接L->data[i]=b;没有考虑物理存储中,下标是从0开始的。   所以,在ListInsert中加入一个 i–,完成逻辑序号向物理序号的转换,Done。   正确的结果不贴图了,最后改过的程序是: ~~~ #include <stdio.h> #include <malloc.h> #define MaxSize 50 //Maxsize将用于后面定义存储空间的大小 typedef int ElemType; //ElemType在不同场合可以根据问题的需要确定,在此取简单的int typedef struct { ElemType data[MaxSize]; //利用了前面MaxSize和ElemType的定义 int length; } SqList; //声明自定义函数 SqList *InitList(); //初始化顺序表 void ListInsert(SqList *L,int i,int b); //插入函数 void DispList(SqList *L); //输出函数 bool ListEmpty(SqList *L);//判定是否为空表ListEmpty(L) int main() { SqList *sq; sq = InitList(); ListInsert(sq, 1, 5); ListInsert(sq, 2, 3); ListInsert(sq, 1, 4); DispList(sq); return 0; } //输出线性表DispList(L) void DispList(SqList *L) { int i; if (ListEmpty(L)) return; for (i=0; i<L->length; i++) printf("%d ",L->data[i]); printf("\n"); } //判定是否为空表ListEmpty(L) bool ListEmpty(SqList *L) { return(L->length==0); } //初始化顺序表InitList(*L) SqList *InitList() { SqList *L=(SqList *)malloc(sizeof(SqList));//这里申请了结点空间 L->length=0; return L; } void ListInsert(SqList *L,int i,int b) //插入函数 { int j; if(i<1) { printf("插入位置非法/n"); } i--; for(j=L->length; j>i; j--) { L->data[j]=L->data[j-1]; } L->data[i]=b; L->length++; } ~~~
';

找出诡异的Bug:数据怎么存不进去

最后更新于:2022-04-01 14:38:23

  带着学生做课程设计。程序一大,课程中做过了小项目,练过了分解动作,一到合起来了,难免还是要乱了分寸。其实,实战的功夫,就是这样出来的。(课程设计指导视频[链接](http://edu.csdn.net/course/detail/474)(第36课时,3.18 银行系统开发),课程主页在[链接](http://blog.csdn.net/sxhelijian/article/details/44117039),指导文档见[链接](http://blog.csdn.net/sxhelijian/article/details/45046901),示例程序见[链接](http://blog.csdn.net/sxhelijian/article/details/45046497))。   话说,已经有两位做银行系统的同学和我说,“文件中写不进去数据。程序一退出,明明写进去了,结果却是空文件。”这不是一个小打击。   做软件,找Bug,有些像打空气,使半天劲,人家就不理你。学计算机的人,练的就是这样的功夫,要学会自己创建线索,找出问题所在。   话说,出问题的两位同学的程序,框架大体如下: ~~~ int main() { Bank b; //创建一个银行对象 if (pass()) //用pass校验用户 { Bank b; b.work(); //完成各种业务 } return 0; } class Bank { …… } Bank::Bank() { ifstream infile("account.dat",ios::in); if(!infile) { cerr<<"open error!"<<endl; exit(1); } //下面的代码,将之前发生过的业务数据从文件读入银行对象 infile.close(); } Bank::~Bank() { ofstream outfile("account.dat",ios::out); if(!outfile) //测试文件打开操作是否成功,不成功则提示后退出。 { cerr<<"open error!"<<endl; exit(1); } //下面的代码,将银行对象中的业务数据写入文件 outfile.close(); delete p; } ~~~   因为数据要在文件里存储,所以,可选的方案是,在构造函数中读文件,在析构函数中写文件。上面的程序就是照这种思路设计的。   然而,程序退出后,文件就是空的。   老贺看了也纳闷,写文件的语句中规中矩,然而就是不对。   仔细审查析构函数中文件的打开方式ios::out,似乎有嫌疑,但排除了。在实际运行的系统中,ios::out的方式不常用,因为这样一打开,也就意味着存在的文件也要重建,用ios::app的更多。   可是,在这个由大一学生实施的设计中,简化的方案是,将所有的数据读入内存,操作针对内存中的数据,而最后,就是要重建文件,将内存中的全部数据重写一遍。   几百行的程序,就不可以用眼睛盯着找问题了。单步跟踪,对这样的程序,如果问题具体在哪儿都不清楚,也不是一个好办法。   析构函数中写文件的部分最可疑。我在析构函数~Bank中加了一句`“cout<<"in destructor."<<endl;”`。结果发现,最后的,析构函数执行了两次。   然后,在main函数的return 0;前加了一句`“cout<<"end of main"<<endl;”`,发现输出的信息是: > in destructor. end of main in destructor.   再看main函数,真相大白了。问题出在main函数中:Bank b出现了两次:一个是属于main函数的局部对象b(前者,第3行),另一个的作用范围,只在if语句的一对花括号内的对象b(后者,第6行)。  程序初次执行,文件为空,前者执行构造函数,b中保存的是空业务。当用户密码验证成功,会创建后者,自然业务信息也空。当执行完b.work();,会执行后者的析构函数,将这次业务后的业务信息保存在了文件中。文件内容不会是空。   然而,当程序的执行离开main函数时,其局部的变量b(前者)也要析构,这时就是问题之所在,这个b中的业务信息是空的,文件打开重建后,没有要写入的信息,最后就是空文件了。   所以,解决的办法,将两个Bank b;,无论前者或后者,去掉一个即可。   问题解决了,再反思。前述的问题自然不该发生,但这里设计的缺陷也存在。在程序中直接将文件名写定,并且写在构造函数和析构函数中,也就意味着该类的所有对象都用同一个文件(如同Person类中的每个对象都用同一个碗吃饭,多家银行将数据存在一个文件中,太可怕了)。合理的做法是,采取某种机制,不同对象,使用不同的文件。   当然,对于本文中的问题,就是不该定义两个 Bank b。
';

藏身时间类中的妖孽

最后更新于:2022-04-01 14:38:21

下面的代码,是一位同学为《[初识对象](http://blog.csdn.net/sxhelijian/article/details/44116231)》中的[项目3时间类](http://blog.csdn.net/sxhelijian/article/details/44116309)写的。但错误有点诡异,他在QQ群中求助。 ~~~ #include <iostream> using namespace std; class Time { public: void set_time(); void show_time(); void add_sec(int); void add_minute(int); void add_hour(int); void add_a_sec() { sec=sec+1; if(sec==60) { sec=0; minute=minute+1; if(minute==60) { minute=0; hour=hour+1; } } void add_a_minute() { minute=minute+1; if(minute==60) { minute=0; hour=hour+1; } } void add_an_hour() { hour=hour+1; } private: bool is_time(int, int, int); //这个成员函数设置为私有的,是合适的,请品味 int hour; int minute; int sec; }; void Time::set_time() { char c1,c2; cout<<"请输入时间(格式hh:mm:ss)"; while(1) { cin>>hour>>c1>>minute>>c2>>sec; if(c1!=':'||c2!=':') cout<<"格式不正确,请重新输入"<<endl; else if (!is_time(hour,minute,sec)) cout<<"时间非法,请重新输入"<<endl; else break; } } void Time::show_time() { cout<<hour<<":"<<minute<<":"<<sec<<endl; } bool Time::is_time(int h,int m, int s) { if (h<0 ||h>24 || m<0 ||m>60 || s<0 ||s>60) return false; return true; } void Time::add_sec(int adds) { sec=sec+adds; while(sec>=60) { sec=sec-60; minute=minute+1; if(minute==60) { minute=0; hour=hour+1; } } } void Time::add_minute(int addm) { minute=minute+addm; while(minute>=60) { minute=minute-60; hour=hour+1; } } void Time::add_hour(int addh) { hour=hour+addh; } int main( ) { Time t1; int adds,addm,addh; cout<<"请输入要增加的秒,分,小时"<<endl; cin>>adds>>addm>>addh; t1.set_time( ); T1.show_time( ); t1.add_a_sec(); t1.add_a_minute(); t1.add_an_hour(); t1.add_sec(adds); t1.add_minute(adds); t1.add_hour(addh); return 0; } ~~~ 读者朋友,你编译一下试试? 我在CodeBlocks中编译,提示一大堆: ~~~ ||=== Build: Debug in example (compiler: GNU GCC Compiler) ===| D:\CPP\codeBlock\example\main.cpp|43|error: extra qualification 'Time::' on member 'set_time' [-fpermissive]| D:\CPP\codeBlock\example\main.cpp|43|error: 'void Time::set_time()' cannot be overloaded| D:\CPP\codeBlock\example\main.cpp|6|error: with 'void Time::set_time()'| D:\CPP\codeBlock\example\main.cpp|57|error: extra qualification 'Time::' on member 'show_time' [-fpermissive]| D:\CPP\codeBlock\example\main.cpp|57|error: 'void Time::show_time()' cannot be overloaded| D:\CPP\codeBlock\example\main.cpp|7|error: with 'void Time::show_time()'| D:\CPP\codeBlock\example\main.cpp|61|error: extra qualification 'Time::' on member 'is_time' [-fpermissive]| D:\CPP\codeBlock\example\main.cpp|67|error: extra qualification 'Time::' on member 'add_sec' [-fpermissive]| D:\CPP\codeBlock\example\main.cpp|67|error: 'void Time::add_sec(int)' cannot be overloaded| D:\CPP\codeBlock\example\main.cpp|8|error: with 'void Time::add_sec(int)'| D:\CPP\codeBlock\example\main.cpp|81|error: extra qualification 'Time::' on member 'add_minute' [-fpermissive]| D:\CPP\codeBlock\example\main.cpp|81|error: 'void Time::add_minute(int)' cannot be overloaded| D:\CPP\codeBlock\example\main.cpp|9|error: with 'void Time::add_minute(int)'| D:\CPP\codeBlock\example\main.cpp|90|error: extra qualification 'Time::' on member 'add_hour' [-fpermissive]| D:\CPP\codeBlock\example\main.cpp|90|error: 'void Time::add_hour(int)' cannot be overloaded| D:\CPP\codeBlock\example\main.cpp|10|error: with 'void Time::add_hour(int)'| D:\CPP\codeBlock\example\main.cpp|109|error: expected '}' at end of input| D:\CPP\codeBlock\example\main.cpp||In member function 'void Time::add_a_sec()':| D:\CPP\codeBlock\example\main.cpp|13|error: 'sec' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|17|error: 'minute' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|21|error: 'hour' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|25|error: a function-definition is not allowed here before '{' token| D:\CPP\codeBlock\example\main.cpp|42|error: expected '}' at end of input| D:\CPP\codeBlock\example\main.cpp||In member function 'void Time::set_time()':| D:\CPP\codeBlock\example\main.cpp|48|error: 'hour' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|48|error: 'minute' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|48|error: 'sec' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp||In member function 'void Time::show_time()':| D:\CPP\codeBlock\example\main.cpp|59|error: 'hour' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|59|error: 'minute' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|59|error: 'sec' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp||In member function 'void Time::add_sec(int)':| D:\CPP\codeBlock\example\main.cpp|69|error: 'sec' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|73|error: 'minute' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|77|error: 'hour' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp||In member function 'void Time::add_minute(int)':| D:\CPP\codeBlock\example\main.cpp|83|error: 'minute' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|87|error: 'hour' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp||In member function 'void Time::add_hour(int)':| D:\CPP\codeBlock\example\main.cpp|92|error: 'hour' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp||In member function 'int Time::main()':| D:\CPP\codeBlock\example\main.cpp|101|error: 'T1' was not declared in this scope| D:\CPP\codeBlock\example\main.cpp|103|error: 'class Time' has no member named 'add_a_minute'| D:\CPP\codeBlock\example\main.cpp|104|error: 'class Time' has no member named 'add_an_hour'| D:\CPP\codeBlock\example\main.cpp|109|error: expected unqualified-id at end of input| ||=== Build failed: 38 error(s), 0 warning(s) (0 minute(s), 1 second(s)) ===| ~~~ 第一个提示“extra qualification 'Time::' on member 'set_time'”,说Time::多余,类外定义成员函数,不多余啊! 第二个提示“ 'void Time::set_time()' cannot be overloaded”,Time::set_time()不能被重载。 我大概知道怎么回事了。 用resource code formatter整理程序的格式,缩进发生变化,妖孽立刻现身。 下面是我和学生在QQ群中的对话,读者如果没有找出错误,不妨边看边试,按我的提示,自己找出问题来。 贺老师 2015-3-21 18:54:18 在Code::Blocks中,你整理一下格式,你会知道这个错有多么低级。然后回头再看这些错误提示,可以引出不少有价值的东西来。 贺老师 2015-3-21 18:55:28 其他同学,也可以从XX的低级错误中学到不少,试试吧。 学生 2015-3-21 18:57:37 糗大了 贺老师 2015-3-21 18:59:26 这样的错误,在学习过程中,价值连城!你经过了这样的错误,你就拥有了特别的财富。 学生 2015-3-21 19:00:37 还是有一点不大明白 贺老师 2015-3-21 19:01:13 重排版了吗? 学生 2015-3-21 19:01:25 排了 贺老师 2015-3-21 19:01:34 你能确认void Time::set_time()是在类外了吗? 学生 2015-3-21 19:02:37 嗯  类内定义  类外写函数 贺老师 2015-3-21 19:03:06 如果类外定义,这个函数该顶头才是, 贺老师 2015-3-21 19:03:19 类内定义,才会缩进去。 贺老师 2015-3-21 19:03:46 你类内声明,类内定义。 贺老师 2015-3-21 19:04:22 这就重复了。 贺老师 2015-3-21 19:07:50 发现了吗?要不要再给点线索? 学生 2015-3-21 19:08:34 在给点吧  贺老师 2015-3-21 19:09:07 你看类内定义的void add_a_sec(),在哪儿结束的? 学生 2015-3-21 19:10:09 落了一个括号 贺老师 2015-3-21 19:10:26 手边有砖吗? 学生 2015-3-21 19:10:54 不是 是括号括错了   我去买块豆腐砖 学生 2015-3-21 19:11:45 谢谢贺老     贺老师 2015-3-21 19:11:48 没砖为师就放心了。不必拍,我怕豆腐疼。学习过程中,很正常。 贺老师 2015-3-21 19:12:40 以后错误莫名其妙时,可以排版一下。若缩进不正常,问题一下就出来了。这也是讲究规范的价值。 学生 2015-3-21 19:13:00 恩恩 学到了
';

记录:50多行程序中找出多写的一个字母e

最后更新于:2022-04-01 14:38:18

  [小霍同学](http://blog.csdn.net/u012369069)调程序,做的是第11周的[项目1 - 存储班长信息的学生类](http://blog.csdn.net/sxhelijian/article/details/25141489),但是她写的程序(就在下面),呃,请读者自己运行一下吧。(下午在机房调试时用的是Code::Blocks10.05,输出的是很长的莫名的符号,晚上在家用的是CodeBlocks12.11,典型的内存溢出症状。) ~~~ #include <iostream> #include <string> using namespace std; class Stu //声明基类 { public: Stu(int n, string nam ):num(n),name(nam) {} //基类构造函数 void display( ); //成员函数,输出基类数据成员 protected: //(*)访问权限为保护型的数据成员[不能被外界引用但可以被派生类引用] int num; //学生学号 string name; //学生姓名 }; void Stu::display( ) //成员函数,输出基类数据成员 { cout<<"学号:"<<num<<endl; cout<<"姓名:"<<name<<endl; } class StuDetail: public Stu //声明派生类StuDetail { public: //学生nam,学号n,a岁,家住ad,他的班长是nam1,学号n1 StuDetail(int n, string nam,int a, string ad,int n1, string nam1); //派生类构造函数 void show( ); //成员函数,输出学生的信息 void show_monitor( ); //成员函数,输出班长信息 private: Stu monitor; //学生所在班的班长,班长是学生,是Stu类的成员 int age; //学生年龄 string addr; //学生的住址 }; StuDetail::StuDetail(int n, string nam,int a, string ad,int n1, string nam1):Stu(n,name),monitor(n1,nam1) { age=a; addr=ad; } void StuDetail::show( ) //成员函数,输出学生的信息 { cout<<"学生信息:"<<endl; cout<<"学号:"<<num<<endl; cout<<"姓名:"<<name<<endl; cout<<"年龄:"<<age<<endl; cout<<"住址:"<<addr<<endl; } void StuDetail::show_monitor( ) //成员函数,输出班长信息 { cout<<"班长信息:"<<endl; monitor.display(); } int main( ) { //学生王力,10010号,19岁,家住上海的北京路,他的班长是李孙,学号10001 StuDetail s(10010,"Wang-li",19,"115 Beijing Road,Shanghai",10001,"Li-sun"); s.show( ); //输出学生信息 s.show_monitor(); //输出班长信息 return 0; } ~~~   老贺及时来帮忙,单步执行是法宝。   在机房调试时用的是Code::Blocks10.05,51行定义并初始化对象s没有问题,顺利通过,而在52行s.show(),输出了莫名的文字。   再次单步,step into到s.show()中,发现混乱来自于39行输出name的地方。很自然,name的值有问题。name值是通过构造函数获取的,需要找源头。问题出在39行,但思维必须得跳出show函数,找到真正的罪犯。   昏花的老眼看30行的构造函数,一眼就看出了冒号后面对基类构造函数的调用Stu(n,name)有蹊跷:形式参数中声明的是(int n, string nam, ...),而实际参数怎么就出来了(n,name),去年一个字母e,构造函数的调用该为Stu(n,nam)。编译再运行,小霍紧锁的眉头放开了。刚才还在怨着计算机的她,应该在反思着自己的大意了。   为何会这样?错误的调用,实际上在用未经初始化的基类成员name作实参,那初始化的结果,name成员仍然是那个“野对象”。string是C++中增加的类,其中的字符符串实际也是用指针实现的。未经初始化的对象,其中就有野指针。   写博文时用的是Code::Blocks12.11,单步执行时,51行的构造函数就下不去,焦点仍然可以锁定在派生类的构造函数上,问题仍能解决。   编程序,练的就是这番功力。 | ~~~ =================== 迂者 贺利坚 CSDN博客专栏================= |== IT学子成长指导专栏 专栏文章的分类目录(不定期更新) ==| |== C++ 课堂在线专栏  贺利坚课程教学链接(分课程年级) ==| |== 我写的书——《逆袭大学——传给IT学子的正能量》    ==| ===== 为IT菜鸟起飞铺跑道,和学生一起享受快乐和激情的大学 ===== ~~~ | |-----|
';

一个数组越界的C++程序

最后更新于:2022-04-01 14:38:16

  学生给我发了私信,一个程序运行了好久,在OJ就是提交不了。   题目是: **Description** 输入10个整数,将其中最小的数与第一个数对换,把最大的数与最后一个数对换。写三个函数; ①输入10个数;②进行处理;③输出10个数。 **Input** 10个整数 **Output** 整理后的十个数,每个数后跟一个空格(注意最后一个数后也有空格) **Sample Input** 2 1 3 4 5 6 7 8 10 9 **Sample Output** 1 2 3 4 5 6 7 8 9 10  **HINT** 主函数已给定如下,提交时不需要包含下述主函数 ~~~ /* C/C++代码 */ int main() { const int n=10; int a[n]; input(a,n); handle(a,n); output(a,n); return 0; } ~~~   学生的解答: ~~~ #include<iostream> #include<cstdio> using namespace std; void input(int a[],int); void handle(int a[],int); void output(int a[],int); int main() { const int n=10; int a[n]; //freopen("input.txt","r",stdin); input(a,n); handle(a,n); output(a,n); return 0; } void input(int a[],int n) { for(int i=0;i<n;i++) cin>>a[i]; } void handle(int a[],int n) { int max,i=0,t,min,z,zd,zx; max=a[i]; min=a[i]; for(i=0;i<n;i++) { if(max<a[i]) { max=a[i]; zd=i; //记录最大值的位置 } if(min>a[i]) { min=a[i]; zx=i; //记录最小值的位置 } } t=a[9]; a[9]=max; a[zd]=t; //进行值交换 z=a[0]; a[0]=min; a[zx]=z; } void output(int a[],int n) { for(int i=0;i<n;i++) { cout<<a[i]<<" "; } } ~~~   这个解答在CodeBlocks中编译通过,屡经测试数据,没有异常,然而,提交后提示“Runtime error”,具体是:   Runtime Error:Segmentation fault   辅助解释:Segmentation fault:段错误,检查是否有数组越界,指针异常,访问到不应该访问的内存区域   这不科学!由“一定是我错了”的思维,转向了“可能是机器错了”,利用管理员帐号,看了测试数据,也没有问题。   再逐句看程序,一个地方引起我的注意,会是25、26行引用a[i],但 i 没有赋值吗?不是,i 的初值是0,于算法没有问题。就在第24行,zd、zx两个变量,也应该是同步赋值的,它们表示最大、最小的数的下标,后面循环中,也及时对这两个数做了更新。   问题逐渐明白了,在24行不给zd、zx赋初值的情况下,如果29和34行的if条件有一个始终未能为真,zd、zx就不会赋值,40行开始的交换,就会出现数组越界。 **  在这个程序上小修改的办法有了:第24行,加上给zd、zx赋初值,即:int max,i=0,t,min,z,zd=0,zx=0;**   这个程序,没有用上单步执行。对这种小几率的问题,单步可能也不管用,思维要严密。   这个程序写得稍显罗嗦,下面给出一个改写版。 ~~~ #include<iostream> #include<cstdio> using namespace std; void input(int a[],int); void handle(int a[],int); void output(int a[],int); int main() { const int n=10; int a[n]; //freopen("input.txt","r",stdin); input(a,n); handle(a,n); output(a,n); return 0; } void input(int a[],int n) { for(int i=0;i<n;i++) cin>>a[i]; } void handle(int a[],int n) { int i,t,zd=0,zx=0;//默认第0个为最大、最小 for(i=1;i<n;i++) //从第1个开始 { if(a[zd]<a[i]) zd=i; //记录最大值的位置 if(a[zx]>a[i]) zx=i; //记录最小值的位置 } t=a[n-1]; //比用含有“神秘数”的a[9]好 a[n-1]=a[zd]; a[zd]=t; //进行值交换 t=a[0]; a[0]=a[zx]; a[zx]=t; } void output(int a[],int n) { for(int i=0;i<n;i++) { cout<<a[i]<<" "; } } ~~~
';

&#39;abc&#39;的值是多少?

最后更新于:2022-04-01 14:38:14

看见一个贴子,发贴人搞不清输出结果中的“为什么”(见[http://bbs.csdn.net/topics/390623518](http://bbs.csdn.net/topics/390623518))。给出的程序是: ~~~ #include <stdio.h> int main(void) { /* 将单个字符赋值给整形变量,结果为字符的ASCII值 */ int a = 'a'; printf("a = %d\n", a); /* ?这里如何解释 */ a = 'abc'; printf("a = %d\n", a); /* 将字符串赋值给整形变量,结果为字符串的地址值 */ a = "a"; printf("a = %d\n", a); a = "abc"; printf("a = %d\n", a); return 0; } ~~~   'abc'这样的写法,我也是第一次见到,居然编译器只给一个警告。   程序的运行结果是:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_575640493855f.jpg)   第二行的结果,即a = 'abc';后a的值显得诡异,跟贴中,大家纷纷猜测。   我对这种情况也不清楚,这时想到的是,要将整型数a中的4个字节中每个字节的值能分隔出来,应该就一目了然了,最好将每个字节解释成一个之字符直接输出来看。   想到了“联合体”union,将程序改造了一下,得到下面的程序: ~~~ #include <stdio.h> union un { int i; char c[4]; }; int main(void) { /* 将单个字符赋值给整形变量,结果为字符的ASCII值 */ int a = 'a'; union un b; printf("a = %d\n", a); /* 这里的b.i就相当于原来的a,而b.c数组与b.i占用同一段4字节的空间 */ b.i = 'abc'; printf("b.i = %d\n", b.i); printf("b.c = %c %c %c %c\n", b.c[0], b.c[1],b.c[2],b.c[3]); /* 将字符串赋值给整形变量,结果为字符串的地址值 */ a = "a"; printf("a = %d\n", a); a = "abc"; printf("a = %d\n", a); return 0; } ~~~   运行结果是:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_5756404955930.jpg)   结果不解释了,原理应该在“计算机组成原理”或“计算机体系结构”中介绍,学过汇编语言的人也明白。程序员学软件,要对计算机系统有理解,这是专业人员该关注的。   C/C++中的union,似乎在教学中都是一带而过,感兴趣的同学,再找其他资料了解了解即可。
';

C++字符数组越界问题的一个案例分析

最后更新于:2022-04-01 14:38:12

  我的学生[yang1067155909](http://my.csdn.net/yang1067155909http://blog.csdn.net/yang1067155909)给我来信,说的是[C++第11周项目3 - CEmployee类继承自CPerson类](http://blog.csdn.net/sxhelijian/article/details/8910979)中的一个细节: 贺老师:   老师,m_szDepartment=new char[strlen(department)+1];为何需要+1呢?在测试里去掉+1后和这个效果一样啊,不太明白……求指教……   学生,杨腾飞   我回答:   要给'\0'占个座。是用别人的地盘(越界的部分)保存了自己的信息了吧,不定哪次人家要用,运行结果就不一样了。这恰是最危险的问题。   他继续追问:   可是在定义字符数组时,比如a[4]时,可以输入5个字符,那么这个数组的'\0'的位置是不是也占用了别人的?   我为这个机灵的同学的问题感到激动,读程序,切忌只是读,要会提问题。能自己提出问题,就一定能学好。老师给出解答,接着再提出新问题,自己解答,或再问老师,这就是交流。为着学生提出了好问题,作为老师,我骄傲。我的回答是:   你提的问题不是一般的好,见我新发表的博文谈这个问题。   下面就是我对这个问题的解答。不妨针对问题设计一个程序试一试。程序是: ~~~ #include <iostream> using namespace std; int main() { char a[4]; cin>>a; cout<<a<<endl; return 0; } ~~~   亲爱的读者,读这篇文章时,请不要只“读”,打开你熟悉的编程环境,边读边运行。你会发现什么?   输入abcd然后回车,输出是abcd。cout<<a是将字符数组当字符串输出的,显然abcd已经占满了自己的地盘a[0]到a[3],能够“如愿”输出,实际上已经侵占了不该占的内存a[4]单元。当然,恰好a[4]处给脸,就是'\0'。如果”烫烫烫烫烫烫“不必意外。   再运行,输入abcde。我运行的结果是,在VC++6.0中,输出abcde,并弹出了我们熟悉的内存越界错误提示。在codeBlocks下,输出abcde,什么也没提示。   请读者想想,这是一个多么凶险的Bug。   下面再给出一个程序: ~~~ #include <iostream> using namespace std; int main() { char a[4],b[4]; cin>>a; cin>>b; cout<<a<<endl; cout<<b<<endl; return 0; } ~~~   运行的任务交给读者了,观察输入3个字符、4个字符、5个字符的情形,也可以在多个平台上试试,针对结果想想为什么。用单步执行的手段跟踪一下内存中的数据存储,是个强烈建议的办法。   下面是为a和b数组输入3个字符后(分别是abc和hij),利用单步执行看到的结果:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564048eed88.jpg)   下面是为a和b数组输入5个字符后(分别是abcef和hijkl),利用单步执行看到的结果:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_5756404910e06.jpg)   从中看出,VC++6.0中,先定义的a数组的地址大于后定义的数组b的地址,本来为a中输入了abcde,侵占了别人的地盘,随后为b输入hijkl,侵占的就是a的地盘,b[4]即a[0]为l,b[5]即a[1],存储的是'\0'!   下图是在codeBlocks下,用同样的输入调试截出的结果,结果一样:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_575640492441f.jpg)   接下来,再给一个程序,其实就是将输入a和b的顺序换了一下: ~~~ #include <iostream> using namespace std; int main() { char a[4],b[4]; cin>>b; cin>>a; cout<<a<<endl; cout<<b<<endl; return 0; } ~~~   运行结果会是怎样?读者你自己说吧。不要忘了,用调试工具这个法宝解除你的疑惑。
';

初学者遭遇离奇错误——求两点间的距离(C++)

最后更新于:2022-04-01 14:38:09

  有学生向我求助,他编了一个程序,设计一个“点”类,求出两点距离的程序。程序看着没有问题,却出了一大堆的错误。程序如下: ~~~ #include <iostream> #include <math.h> using namespace std; class point { public: double x; double y; }; double distance(point p1,point p2); int main() { point p1= {3,5},p2= {6,9}; cout<<distance(p1,p2); } double distance(point p1,point p2) { double d=sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y)); return d; } ~~~   在codeBlocks下编译,错误直接引到stl_iterator_base_types.h文件中,错误一大堆: ~~~ ||=== example, Debug ===| D:\C++\codeBlock\example\example.cpp|15|instantiated from here| d:\program files\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.4.1\include\c++\bits\stl_iterator_base_types.h|127|error: no type named 'iterator_category' in 'class point'| d:\program files\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.4.1\include\c++\bits\stl_iterator_base_types.h|128|error: no type named 'value_type' in 'class point'| d:\program files\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.4.1\include\c++\bits\stl_iterator_base_types.h|129|error: no type named 'difference_type' in 'class point'| d:\program files\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.4.1\include\c++\bits\stl_iterator_base_types.h|130|error: no type named 'pointer' in 'class point'| d:\program files\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.4.1\include\c++\bits\stl_iterator_base_types.h|131|error: no type named 'reference' in 'class point'| ||=== Build finished: 5 errors, 0 warnings ===| ~~~   对于这样的问题,初学C++的同学肯定直接蒙。这里的问题出在命名空间中名字的冲突,再多说一些,与STL也有些关系。不过,解决这样的问题并不一定得知道这么多。我还是试着与大家绕开这个环节,从其他途径找点感觉。   光标置到“D:\C++\codeBlock\example\example.cpp|15|instantiated from here|”一行,双击,发现错误在程序的第15行。鼠标放到15行的distance函数上时,浮现出了一行提示,见图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564048a2740.jpg)   看出了一点疑惑:distance不是在这个程序中编的自定义函数吗?怎么识别成了std::distance(...,...)?   这就是问题的根源!编译器没有将distance当作自定义函数处理!至于进一步的解释不再深入,抓住这个要点,程序就可以改好了。   **修改方法之一:**既然函数名字上出问题,试试改个名字?将distance改个名字,如dist,一切正常。   **修改方法之二:**凭什么让我改?distance多好的一个函数名(不过提醒,可以自学一下命名空间了,此是好机会,不必等着老师讲。)需要做的工作是,不用std命名空间(删除或注释掉using namespace std;一行)然后在依赖std的cout前加上std::,程序如下: ~~~ #include <iostream> #include <math.h> //using namespace std;//不再用命名空间std class point { public: double x; double y; }; double distance(point p1,point p2); int main() { point p1= {3,5},p2= {6,9}; std::cout<<distance(p1,p2); //保证编译系统知道用std命名空间中的cout } double distance(point p1,point p2) { double d=sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y)); return d; } ~~~   **修改方法之三:**方法二有点自私了。std中有不少常用的东东,就此全都得写std::,这个程序中无妨,如果再大些的程序呢?胳膊不必和大腿拧,换种思路,也是一样。将distance在调用时,写作为::distance,指出distance是当前程序中定义的名字。问题解决就此解决,程序如下: ~~~ #include <iostream> #include <math.h> using namespace std; class point { public: double x; double y; }; double distance(point p1,point p2); int main() { point p1= {3,5},p2= {6,9}; cout<<::distance(p1,p2);//指定distance不是别处的,就是本文件中定义的 } double distance(point p1,point p2) { double d=sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y)); return d; } ~~~   **修改方法之四:**前三种方法中,个人倾向于第一种,山不转水转,换个名字也妨。其实这也不是最好的。原始的程序中定义了类,但只有数据成员,没有成员函数,像求距离之类的,设计为成员函数多好。面向对象的机制就是为了信息封装等特性的,为何要如此浪费?这个程序我就不写了,请自行解决。   **补充:**用其他编程环境时,观察和修改的方法也类似,例如在VS2008下编译,错误居然有25个之多: ~~~ 1>------ 已启动生成: 项目: example, 配置: Debug Win32 ------ 1>正在编译... 1>example.cpp 1>d:\program files\microsoft visual studio 9.0\vc\include\xutility(764) : error C2039: “iterator_category”: 不是“point”的成员 1> d:\c++\vs2008 project\example\example\example.cpp(5) : 参见“point”的声明 1> d:\c++\vs2008 project\example\example\example.cpp(16): 参见对正在编译的类 模板 实例化“std::iterator_traits<_Iter>”的引用 1> with 1> [ 1> _Iter=point 1> ] (此处省略N多的提示) 1>生成日志保存在“file://d:\C++\VS2008 project\example\example\Debug\BuildLog.htm” 1>example - 25 个错误,0 个警告 ========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ========== ~~~   在源程序中,鼠标光临distance函数时,可以看出编译器对distance函数有两种解释,如下图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564048c83c8.jpg)   编译器对此局面真的很迷茫了。余下的修改思路相同,不再罗嗦。
';

编程学习,从意外中收获

最后更新于:2022-04-01 14:38:07

提倡同学们积累代码行数,于是有了大量的编程练习。做下来是一个基本的要求,对个别同学而言,已经是轻松面对,他们在基本编程能力方面,已经有了保障,这为今后的学习和自学打下了好的基础。一些同学一路紧随,跟住了都不是易事,有些依然不能按时按量完成。心中想着要多做几个,但调试中遇到问题,难免急躁。   看到一位同学在博文最后写的一段话:“直接抓狂了,经过个人分析,应该是函数change24与changefrom0没有实现其功能,可是定义函数,函数的赋值及调用都与其他同学的相同,问题究竟出在哪里啊?求指点!”   这是C++第7周项目1 - 静态成员应用于时间类(见[http://blog.csdn.net/sxhelijian/article/details/8795951](http://blog.csdn.net/sxhelijian/article/details/8795951)) 的题目。目标是学会静态数据成员以及静态成员函数的用法。   下面是只涉及静态数据成员以及静态成员函数的用法的“缩略版”解答,当然,包含这位同学犯的错误。请读者先读完这段程序。 ~~~ #include <iostream> #include<string> using namespace std; class Time { public: Time(int=0,int=0,int=0); void show_time( ); //根据is_24和from0,输出适合形式-20:23:5/8:23:5 pm/08:23:05 pm static void change24(); //改变静态成员is_24,在12和24时制之间转换 static void changefrom0(); //改变静态成员from0,切换是否前导0 private: static bool is_24; //为true时,24小时制,如20:23:5;为flase,12小时制,显示为8:23:5 pm static bool from0; //为true时,前导0,8:23:5显示为08:23:05 int hour; int minute; int sec; }; Time::Time(int h,int m,int s) { hour=h; minute=m; sec=s; } bool Time::is_24=true; bool Time::from0=false; void Time::show_time( ) { int h=(is_24)?hour:hour%12; if (h<10&&from0) cout<<'0'; cout<<h<<':'; if(minute<10&&from0) cout<<'0'; cout<<minute<<':'; if(sec<10&&from0) cout<<'0'; cout<<sec; if(!is_24) cout<<((hour>12)? " pm":" am"); cout<<endl; } void Time::change24() { is_24=!is_24; } void Time::changefrom0() { from0=!from0; } int main( ) { Time t1(23,14,25),t2(8,45,6); cout<<"24小时制,不导0:"<<endl; t1.show_time(); t2.show_time(); cout<<"切换是否前导0:"<<endl; t1.changefrom0(); t2.changefrom0(); t1.show_time(); t2.show_time(); cout<<"换一种制式:"<<endl; t1.change24(); t2.change24(); t1.show_time(); t2.show_time(); } ~~~   这段程序的实际运行结果是:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_575640485279e.png)   可以发现,切换是否前导0,换制式,根本没有起作用。预期中的结果应该是:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_5756404874513.png)   面对运行中的问题,最直接的办法就是采用单步执行的办法,跟踪一下程序在执行过程中,相关变量的变化过程。显然,这位同学并没有这样做过。   一再强调的调程序的“利器”,就被藏在“兵器库”中,任由其闲置。这种技能就是要在实践中学会的,这是学会的最佳时刻。有些同学已经用得纯熟,有些还在躲避。同一个教室上课,差别怎么这么大。   不说这些了。其实,不用单步调试,这个事不好做,也真不好说。初学有关知识,都要靠着推理式的排查,这是更难的办法。   这个程序中包括main()函数在内一共5个函数。类的构造函数Time()和用于显示的函数Time::show_time()嫌疑不大,先放过。main()似乎中规中矩,也放下。Time::changefrom0()和Time::change24()与以前编程的程序中有点不一样,声明前加了个static,这时可以看书了,明确这叫做静态成员函数,静态成员函数是用来使用静态数据成员的,再看static bool is_24; 和static bool from0; ,这两个成员不一般,静态的,静态的有何特点?它不属于任何一个对象,属于类。然后看看讲义,老师讲过这个图:   ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_575640488ba72.png)   围绕着“静态”,找到main()函数中有这么两句,调用的是静态成员函数: ~~~ t1.changefrom0(); t2.changefrom0(); ~~~   这两个静态成员函数操作的将是同一个数据Time::from0,尽管t1.changefrom0()和t2.changefrom0()前面打着t1、t2人民的旗号,干得都是服务大众的工作。from0和hour等不一样,它是静态的。   于是明白了吧。t1.changefrom0()将from0由假为真,t2.changefrom0()又将from0由真为假,最后输出没有变化。   发生在is_24上的事情也是一样。   程序运行结果不在预期中,这是学习的最佳机会。   稀里糊涂走进来,明明白白走出去。这大概是积累代码行的路线中应该收获的,而其中需要的,是冷静,以及实践与看书的结合。   最后,还说,单步调试,闲着太可惜。
';

找出运行错误的元凶

最后更新于:2022-04-01 14:38:05

收到一位同学的求助信,解决的是[http://blog.csdn.net/sxhelijian/article/details/8737365](http://blog.csdn.net/sxhelijian/article/details/8737365)中的问题。她的程序如下: ~~~ #include <iostream> using namespace std; class CFraction {private: int nume; // 分子 int deno; // 分母 public: void input(); //按照"nu/de"的格式,如"5/2"的形式输入 void simplify(); //化简(使分子分母没有公因子) void amplify(int n); //放大n倍,如2/3放大5倍为10/3 void output(int style); //输出:以8/6为例,style为0时,原样输出8/6; //style为1时,输出化简后形式4/3; //style为2时,输出1(1/3)形式,表示一又三分之一; //style为3时,用小数形式输出,如1.3333; //不给出参数和非1、2,认为是方式0 }; //主函数 int main() { CFraction c; c.input(); c.simplify(); c.amplify(3); c.output(1); c.output(2); c.output(3); c.output(0); return 0; } //按照"nu/de"的格式,如"5/2"的形式输入 void CFraction::input() { char c; cout<<"请输入分数的值:"<<endl; while(1) { cin>>nume>>c>>deno; if(c!='/') cout<<"格式不正确,请重新输入!"<<endl; else break; } } //化简(使分子分母没有公因子) void CFraction::simplify() { int num1,num2,temp; int x,y; num1=nume; num2=deno; if(num1<num2) //找出较大的值 { temp=num1; num1=num2; num2=temp; } x=nume; y=deno; while(y>0) { temp=x%y; x=y; y=temp; }; num1=num1/x; num2=num2/y; cout<<num1<<'/'<<num2<<endl; } //放大n倍,如2/3放大5倍为10/3 void CFraction::amplify(int n) { int num1,num2; num1=nume*n; num2=deno*n; cout<<num1<<'/'<<num2<<endl; } //输出:以8/6为例,style为0时,原样输出8/6;为1时,输出化简后形式4/3;为2时,输出1(1/3)形式,表示一又三分之一;为3时,用小数形式输出,如1.3333; //不给出参数和非1、2,认为是方式0 void CFraction::output(int style) { if(style==1) { int num1,num2,temp; int x,y; num1=nume; num2=deno; if(num1<num2) //找出较大的值 { temp=num1; num1=num2; num2=temp; } x=nume; y=deno; while(y>0) { temp=x%y; x=y; y=temp; }; num1=num1/x; num2=num2/y; cout<<num1<<'/'<<num2<<endl; } else { if(style==2) { int a,b,c=0; a=nume; b=deno; while(a>=b) { a=a-b; c++; } if(a==0) cout<<c<<endl; else cout<<c<<'('<<a<<'/'<<b<<')'<<endl; } if(style==3) { double c; c=nume/deno; cout<<c<<endl; } else cout<<nume<<'/'<<deno<<endl; } } ~~~ 她的问题是:编译,链接都没错,但不能运行??? 尝试运行,输入后,运行出错。初步判断,调用CFraction::input()没有出问题,CFraction::simplify()的嫌疑很大。 输入分子、分母,单步跟踪进CFraction::simplify(),几步之后,真相出现了。见图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_5756404836a58.png) 阶数为0。 这个程序在算法设计上有问题,请自行再去考虑。需要提醒的是,单步调试,可以学会。
';

BUG现形记(二)——偷工减料的复制构造函数

最后更新于:2022-04-01 14:38:03

  【课程支撑】[我的 C++程序设计课程教学材料](http://blog.csdn.net/sxhelijian/article/details/7056008)   【摘要】设计数组类,要实现数组类中两个数组相加的运算,程序却陷入死循环。逐层排查,重载的加法正确,重载的赋值运算也看不出问题。跟踪到赋值运算的实现中发现,传递的参数中有异常,终于找出了嫌疑犯——编制的复制构造函数偷工减料。   【阅读提示】现在打开你熟悉的c++,跟随作者的的思路,重走发现嫌犯的过程。   题目是建立专门的数组类处理有关数组的操作,要完成支持数组操作的类的设计,增强C++内置数组类型功能。——见:[第14周-任务1-数组类的构造](http://blog.csdn.net/sxhelijian/article/details/7587387)   有同学向我求助,他的程序如下: ~~~ #include <iostream> using namespace std; class MyArray { private: int *arr; //用于存放动态分配的数组内存首地址 int size; //数组大小 public: MyArray(int sz=50); MyArray(int a[],int sz); //由一个内置类型的数组初始化 MyArray(const MyArray &A); //拷贝构造函数 ~MyArray(void); //析构函数,注意释放空间 MyArray&operator =(const MyArray &A); //重载“=”使得数组对象可以整体赋值 int& operator[](int i); //重载[],使得Array对象也可以如C++普通数组一样,用a[i]形式取出值【选做】 bool operator == (MyArray& A); //重载==,使得Array对象能整体判断两个数组是否相等(size相等且对应元素相等) MyArray operator + (MyArray& A); //重载+,使两个Array对象可以整体相加(前提大小相等)【选做】 friend ostream& operator << (ostream& out,MyArray& A); //重载<<,输出数组 int GetSize(void)const; //取数组大小; void Resize(int sz); //修改数组的大小,如果sz大于数组的原大小,增加的元素初始为;sz小于数组的原大小,舍弃后面的元素【选做】 }; MyArray::MyArray(int sz) { size = sz; arr = new int[size]; for( int i = 0; i < size; i++ ) { *(arr + i) = 0; } } MyArray::MyArray(int a[],int sz) //由一个内置类型的数组初始化 { size = sz; arr = new int[size]; for(int i = 0; i < size; i++) { *(arr + i) = *(a + i); } } MyArray::MyArray(const MyArray &A) //拷贝构造函数 { arr = new int[A.size]; for(int i = 0; i < A.size; i++) { *(arr + i) = *(A.arr + i); } } MyArray::~MyArray(void) //析构函数,注意释放空间 { delete[]arr; } MyArray& MyArray::operator =(const MyArray &A) //重载“=”使得数组对象可以整体赋值 { int n = A.size; if( size != n ) { delete[]arr; arr = new int[n]; size = n; } int* destptr=arr; int* srcptr=A.arr; while(n--) { *destptr=*srcptr; destptr++; srcptr++; } return *this; } int& MyArray::operator[](int i) //重载[],使得Array对象也可以如C++普通数组一样,用a[i]形式取出值【选做】 { return arr[i]; } bool MyArray::operator == (MyArray& A) //重载==,使得Array对象能整体判断两个数组是否相等(size相等且对应元素相等) { bool m; m = true; if( A.size != size ) { m = false; } else { for( int i = 0; i < size; i++ ) if( *(A.arr + i) != *(arr + i) ) { m = false; break; } } return m; } MyArray MyArray::operator + (MyArray& A) { int n=A.size; //取A数组的大小 if (size!=n) //大小不一致不能相加 { cout<<"not same size for add!"<<endl; exit(1); } MyArray a(n); //指定size的数组 for (int i = 0; i < size; i++) { a[i]=arr[i]+A[i]; } return a;//返回当前对象的引用 } ostream& operator << (ostream& out,MyArray& A) //重载<<,输出数组 { for( int i = 0; i < A.size; i++) { out << A[i] << " "; } out << endl; return out; } int MyArray::GetSize(void)const //取数组大小; { return size; } void MyArray::Resize(int sz) //修改数组的大小,如果sz大于数组的原大小,增加的元素初始为;sz小于数组的原大小,舍弃后面的元素【选做】 { int *m = new int(sz); for( int i = 0; i < sz; i++) { *m = 0; } int n = ( sz <= size )?sz:size; for(int j = 0; j < n; j++) { *(m + j) = *(arr + j); } delete[]arr; arr = m; } int main() { int a[10]={1,2,3,4,5,6,7,8,9,10}; int b[10]={4,5,6,7,8,9,10,11,12,13}; MyArray arr1(a,10); MyArray arr2(b,10); MyArray arr3(10); cout<<arr3; arr3 = arr1 +arr2; cout<<arr3; // arr3.Resize(20); // cout<<arr3; // arr3.Resize(5); // cout<<arr3; system("pause"); return 0; } ~~~ 运行程序,结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_5756400be11e0.JPG)   光标在第二行一闪一闪,就是不见下文。   此症状一般是陷入了死循环。经阅读程序,已经输出的多个0是160行cout<<arr3;的结果。162行同样的语句应该不会出现问题。焦点锁定在第161行:arr3=arr1+arr2。   arr3=arr1+arr2涉及到两个运算符的重载:+ 和 =。   认真地读一下这两个重载函数,比较明显的是第115行的a[i]=arr[i]+A[i];有些别扭。a和A是MyArray类的对象,而arr是当前对象的一个成员(我想起一条胳膊和一个人要加起来,哪能这样!)修改为a.arr[i]=arr[i]+A.arr[i];(相当于a.arr[i]=this->arr[i]+A.arr[i];这就是胳膊和胳膊加了)。这样,对加法结果正确有了把握。   再次运行,问题依旧。也看不出明显的线索。请来法宝,祭起查找Bug的照妖镜——调试工具来帮忙。   在161行上设置断点运行程序,用F11逐语句执行,进入MyArray::operator +() 函数,即对加法的重载直至结束,未见异常。作为返回值的临时对象a,其中的结果正确。a 的两个属性 size 及 arr 和 arr 为起始地址指向的值,也恰好是该求得值的结果。可以在117行也设置一个断点,观察直至此时 相加结果 a 的值。下面是执行到此处时,从局部变量窗口看到的结果(如果要看a.arr[1]及之后的值,可以用监视窗口): ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564047ef381.JPG)   单步跟踪到MyArray::operator =()函数,即对赋值的重载。很意外地,函数进入到了第59行的 if 语句中。目前程序的运行,所用到的数组,其 size 均为10,应该这个 if 分支是执行不到的。而此时,局部变量的值却让人大吃一惊:MyArray::operator =()中形式参数 A  的 size 成员的值是一个负数!见下图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_575640481af11.JPG)   此时该整理一下其中存在的问题了:   (1)问题出在执行arr3=arr1+arr2;上;   (2)计算arr1+arr2,写成函数调用形式是arr1.operator+(arr2),函数调用时,返回的结果是正确的;   (3)接着计算赋值,MyArray::operator =(const MyArray &A) 的形式参数 A 对象,将获得 arr1+arr2 的结果,即实参是  arr1+arr2 的结果,更直观地,执行的是 arr3.operator=(arr1+arr2); ,计算结果本来是正确的,但实参传值给形参后,对象的 size 成员出错了。   在实参给形参传值时,采用的是复制的办法,对类(对象)而言,需要有一个复制构造函数支撑(这个学过)。复制构造函数可以是默认的。但是,如果类中有指针类型的成员时,必须自定义复制构造函数,从而能够处理指针所指向空间的分配和回收问题。   所以,嫌疑犯基本锁定:MyArray类的复制构造函数(也称拷贝构造函数)。看41行开始的拷贝构造函数MyArray::MyArray(const MyArray &A) 的定义,为this->arr成员分配了空间,并将形参对象中指向的数据一一进行复制,惟独没有做的,是为 this->size 赋值!我们需要在这个函数中,增加一行代码:size = A.size;。   至此,案情大白于天下,错误得以纠正,程序得以正确运行。   相关博文:[寻找Bug记实:一个多重继承程序的查错](http://blog.csdn.net/sxhelijian/article/details/7551594)   课程支撑:[我的 C++程序设计课程教学材料](http://blog.csdn.net/sxhelijian/article/details/7056008)
';

Bug现形记(一):一个多重继承程序的查错

最后更新于:2022-04-01 14:38:00

  【课程支撑】[我的 C++程序设计课程教学材料](http://blog.csdn.net/sxhelijian/article/details/7056008)   要完成的任务详见[第12周-任务2-双肩挑干部](http://blog.csdn.net/sxhelijian/article/details/7543916)。题目要求   分别定义Teacher(教师)类和Cadre(干部)类,采用多重继承方式由这两个类派生出新类Teacher_Cadre(教师兼干部)。要求:   (1)在两个基类中都包含姓名、年龄、性别、地址、电话等数据成员。   (2)在Teacher类中还包含数据成员title(职称),在Cadre类中还包含数据成员post(职务),在Teacher_Cadre类中还包含数据成员wages(工资)。   (3)对两个基类中的姓名、年龄、性别、地址、电话等数据成员用相同的名字,在引用这些数据成员时,指定作用域。   (4)在类体中声明成员函数,在类外定义成员函数。   (5)在派生类Teacher_Cadre的成员函数show中调用Teacher类中的display函数,输出姓名、年龄、性别、职称、地址、电话,然后再用cout语句输出职务与工资。   下面是某同学的解答: ~~~ #include <iostream> #include <string> using namespace std; class Teacher { public: Teacher(string nam, int a, char s, string addr, string tel,string t); void display(); protected: string name; int age; string title; string address; char sex; string telep; }; class Cadre { public: Cadre(string nam, int a, char s, string addr, string t,string p); void display1(); protected: string name; int age; string address; char sex; string telep; string post; }; class Teacher_Cadre:public Teacher,public Cadre { public: Teacher_Cadre(string nam, int a, char s, string addr, string tel,float w,string t,string p); void show(); protected: string name; int age; string title; string address; char sex; string telep; string post; float wages; }; void Teacher::display() { cout<<Teacher::name<<" "<<Teacher::age<<" "<<Teacher::sex<<" "<<Teacher::address<<" "<<Teacher::telep<<" "<<title<<endl; } void Cadre::display1() { cout<<post<<endl; } void Teacher_Cadre::show() { display(); cout<<wages<<endl; } Teacher::Teacher(string nam, int a, char s, string addr, string tel,string t) { name=nam; age=a; address=addr; telep=tel; title=t; } Cadre::Cadre(string nam, int a, char s, string addr, string tel,string p) { name=nam; sex=s; age=a; address=addr; telep=tel; post=p; }; Teacher_Cadre::Teacher_Cadre(string nam, int a, char s, string addr, string tel,float w,string t,string p):Teacher(nam,a,s,addr,tel,t),Cadre(nam,a,s,addr,tel,p) { wages=w; } void main() { Teacher_Cadre t1("malin",19,'f',"yantai","18253593419",10000,"student","study"); t1.show(); t1.display1(); system("Pause"); } ~~~ 程序的运行结果是: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_5756400ba0fb2.JPG)   注意到画红圈的地方。寻找程序执行的流程,应该执行的是第50行,是Teacher::display()输出对象t1信息时显示了这一行。很时显,红圈中的?应该是性别 f 。看第50行没有问题,那究竟在哪儿见着鬼了?   我决定用我们的法宝,调试工具这个照妖镜试试。问题出现在显示时,我在第88行t1.show();处加了断点并运行程序,打开自动窗口(窗口中出现的正是当前代码行涉及的对象的当前值),真相马上出现了。先看截屏: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_5756400bb6689.JPG)   可以看到,可疑之处要输出的性别sex出现了3次:①来自基类Teacher;②来自基类Cadre;③来自派生类Teacher_Cadre。根据第50行代码可以判断,运行时出现的异常由于Teacher::sex造成的,而①处取值恰好为 ‘?’(其ASCII码值为52)。症状掌握了。那病因何在呢?   显然,问题不在88行的show()函数,也不是48行Teacher::display()的毛病。问题出现在显示之前,对象根本没有获得正确的sex值。87行在定义对象时做了初始化,这个程序很短,疑点马上集中在对t1的初始化上。按照构造函数的执行过程逆着推上去,罪犯显形了:在63-70行Teacher的构造函数Teacher::Teacher(...)中,唯独缺少了对sex 数据成员的赋值!   罪状昭然于天下,Bug伏法吧!   单就运行结果看,程序没有问题了。但高度的责任感让我想到③处还有个问号。再一看,来自派生类Teacher_Cadre中的数据成员,除了wages的值正确,其他全……。推及Teacher_Cadre的构造函数(80-83行),确实,只给wages做了初始化。   如果简单些处理,在Teacher_Cadre::Teacher_Cadre(...)中再加些赋值不就行了?事情没有这么简单。看Teacher_Cadre的声明(32-46行),其数据成员多达8个!Teacher_Cadre继承了两个类的数据成员,其中有同名的造成了二义性,这还不够麻烦,又将那些数据成员照抄着来了一份,这样,在派生类的对象中,同名的数据成员将被存储3份。如果这样的话,继承还有何用?这反映了对继承的理解还没有到位,或许仅是粗心了。   如何更改程序,读者应该清楚了。   作为结尾,再次提醒同学们用好调试工具。   【课程支撑】[我的 C++程序设计课程教学材料](http://blog.csdn.net/sxhelijian/article/details/7056008)
';

前言

最后更新于:2022-04-01 14:37:58

> 原文出处:[代码侦探](http://blog.csdn.net/column/details/bugkiller.html) 作者:[贺利坚](http://blog.csdn.net/sxhelijian) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # 代码侦探 > 通过实例,介绍代码中Bug的寻找问题,帮助程序设计初学者提高程序的查错、纠错能力。专栏原创博文数量以作者平时大量辅导学生,帮助他们调试程序的工作为保证。
';